您的位置:首页 > 理论基础 > 数据结构算法

算法——数据结构基础(数组、队列、栈、链表、散链表)

2020-07-16 05:54 162 查看

算法——数据结构基础

  • 栈:
  • 队列:
  • 散列表(哈希表):hash table
  • 文章结构概览

    数组:顺序存储

    读操作多,写操作少

    • 1.读取(查找)元素:读取对应的下标
      高效的查找元素的算法:二分查找
    • 2.更新元素:对元素进行重新赋值
    • 3.插入元素:尾部插入、中间插入、超范围插入
      扩容问题,时间复杂度:O(n)
      插入并移动元素,时间复杂度:O(n)
    • 4.删除元素:1) 删除对应位置,后面的元素往前挪
      2) 把最后一个元素复制到删除元素所在的位置,再删除最后一个元素
      删除元素,时间复杂度:O(n)
    package array; /**
    * Copyright (C), 2019-2020
    * author  candy_chen
    * date   2020/6/16 9:58
    * version 1.0
    * Description: 数组
    */
    
    /**
    *测试数组从中间插入元素
    */
    
    public class test_array {
    private int[] array;
    private int size;
    
    public test_array(int capacity) {
    this.array = new int[capacity];
    size = 0;
    }
    
    /**
    *
    * @param element 插入的元素
    * @param index 插入的位置
    * @throws Exception
    */
    public void insert (int element,int index) throws Exception{
    //判断访问下标是否超出范围
    if (index <0 || index >size){
    throw new IndexOutOfBoundsException("超出数组实际范围");
    }
    //从右向左循环,将元素逐个向右挪1位
    for (int i=size -1;i>=index;i--){
    array[i+1] = array[i];
    }
    //腾出的位置放入新元素
    array[index] = element;
    size++;
    }
    
    /**
    * 数组扩容
    */
    
    public void resize(){
    int[] arrayNew = new int[array.length*2];
    //从旧数组复制到新数组
    System.arraycopy(array,0,arrayNew,0,array.length);
    array = arrayNew;
    }
    
    /**
    * 数组删除元素
    * @param index 删除的位置
    * @return
    */
    public int delete(int index){
    if (index <0 || index >size){
    throw new IndexOutOfBoundsException("超出数组实际范围");
    }
    int deletedElement = array[index];
    //从左向右循环
    for (int i=index;i<size-1;i++){
    array[i] = array[i+1];
    }
    size--;
    return deletedElement;
    }
    
    /**
    * 输出数组
    */
    public void output(){
    for (int i=0;i<size;i++){
    System.out.println(array[i]);
    }
    }
    
    public static void main(String[] args) throws Exception {
    test_array test_array = new test_array(10);
    test_array.insert(3,0);
    test_array.insert(7,1);
    test_array.insert(9,2);
    test_array.insert(5,3);
    test_array.insert(6,1);
    test_array.output();
    
    }
    
    }

    链表:随机存储

    单链表

    • 1.查找节点:时间复杂度:O(n)
      从头节点开始向后一个一个节点逐一查找
    • 2.更新节点:旧数据替换成新数据即可,时间复杂度:O(1)
    • 3.插入节点:尾部插入、中间插入、头部插入,时间复杂度:O(1)
      尾部插入:把最后一个节点的next指针指向新插入的节点
      头部插入:把新节点的next指针指向原先的头节点
      把新节点变成链表的头节点
      中间插入:插入位置的前置节点的next指针指向插入的新节点
      将新节点的next指针指向前置节点的next指针原先所指向的节点
    • 4.删除节点:尾部删除、头部删除、中间删除,时间复杂度:O(1)
      尾部删除:直接将倒数第二个节点的next指针指向空即可
      头部删除:链表的头节点设为原先头节点的next指针即可
      中间删除:把删除及诶单的前置节点的next指针指向要删除元素的下一个节点

    MyLinkedList.java

    package array;/**
    * Copyright (C), 2019-2020
    * author  candy_chen
    * date   2020/6/18 21:36
    * version 1.0
    * Description: 单链表的增删改查
    */
    
    /**
    *
    */
    public class MyLinkedList {
    
    //头节点指针
    private Node head;
    //尾节点指针
    private Node last;
    //链表实际长度
    private int size;
    
    /**
    * 链表插入元素
    * @param data  插入元素
    * @param index 插入位置
    */
    public void insert(int data,int index) throws Exception {
    if (index < 0 || index > size){
    throw new IndexOutOfBoundsException("超出链表节点范围");
    }
    
    Node insertedNode = new Node(data);
    if (size == 0){
    head = insertedNode;
    last = insertedNode;
    }else if (index == 0) {
    //插入头部   把新节点的next指针指向原先的头节点
    //                把新节点变成链表的头节点
    insertedNode.next = head;
    head = insertedNode;
    }else if (size == index){
    //插入尾部  把最后一个节点的next指针指向新插入的节点
    last.next = insertedNode;
    last = insertedNode;
    }else{
    //插入中间   插入位置的前置节点的next指针指向插入的新节点
    //                将新节点的next指针指向前置节点的next指针原先所指向的节点
    Node prevNode = get(index -1);
    Node nextNode = prevNode.next;
    prevNode.next = insertedNode;
    insertedNode.next = nextNode;
    }
    //插入元素后需要给链表长度加一
    size++;
    }
    
    /**
    * 链表删除元素
    * @param index 删除的位置
    * @return removeNode 返回删除的节点
    */
    public Node remove(int index){
    if (index < 0||index >= size){
    throw new IndexOutOfBoundsException("超出链表节点范围!!");
    }
    Node removeNode = null;
    if (index == 0){
    //删除头节点 链表的头节点设为原先头节点的next指针即可
    removeNode = head;
    head = head.next;
    }else if (index == size-1){
    //删除尾节点 直接将倒数第二个节点的next指针指向空即可
    Node prevNode = get(index -1);
    removeNode = prevNode.next;
    prevNode.next = null;
    last = prevNode;
    }else {
    //删除中间节点 把删除及诶单的前置节点的next指针指向要删除元素的下一个节点
    Node prevNode = get(index -1);
    Node nextNode = prevNode.next.next;
    removeNode = prevNode.next;
    prevNode.next = nextNode;
    }
    size--;
    return removeNode;
    }
    
    /**
    * 查找链表元素
    * @param index 查找的位置
    * @return temp
    */
    
    private Node get(int index) {
    if (index < 0 || index >= size){
    throw new IndexOutOfBoundsException("超出链表节点范围");
    }
    Node temp = head;
    for (int i = 0;i<index;i++){
    temp = temp.next;
    }
    return temp;
    }
    
    /**
    * 输出链表
    */
    
    public void output(){
    Node temp = head;
    while (temp != null){
    System.out.println(temp.data);
    temp = temp.next;
    }
    }
    
    /**
    * 链表节点
    */
    private static class Node{
    int data;
    Node next;
    
    public Node(int data) {
    this.data = data;
    }
    
    }
    
    public static void main(String[] args) throws Exception {
    MyLinkedList myLinkedList = new MyLinkedList();
    myLinkedList.insert(3,0);
    myLinkedList.insert(7,1);
    myLinkedList.insert(9,2);
    myLinkedList.insert(5,3);
    myLinkedList.output();
    System.out.println("------------------");
    myLinkedList.insert(6,1);
    myLinkedList.output();
    System.out.println("------------------");
    
    myLinkedList.remove(0);
    
    myLinkedList.output();
    System.out.println("------------------");
    
    }
    }

    如何判断链表有环?

    栈:

    先进后出,FILO

    1. 入栈 时间复杂度:O(1)
    2. 出栈 时间复杂度:O(1)

    MyStack.java

    package array;/**
    * Copyright (C), 2019-2020
    * author  candy_chen
    * date   2020/6/19 20:10
    * version 1.0
    * Description: 测试
    */
    
    /**
    *
    */
    public interface MyStack<Item> extends Iterable<Item> {
    
    MyStack<Item> push(Item item);
    Item pop() throws Exception;
    boolean isEmpty();
    int size();
    }

    ArrayStack.java

    package array;/**
    * Copyright (C), 2019-2020
    * author  candy_chen
    * date   2020/6/19 20:09
    * version 1.0
    * Description: 测试
    */
    
    import java.util.Iterator;
    
    /**
    *
    */
    public class ArrayStack<Item> implements MyStack<Item> {
    
    //栈元素数组,只能通过转型来创建泛型数组
    private Item[] a = (Item[]) new Object[1];
    
    //元素数量
    private  int N = 0;
    
    @Override
    public MyStack<Item> push(Item item) {
    check();
    a[N++] = item;
    return this;
    }
    
    private void check() {
    if (N > a.length){
    resize(2 * a.length);
    }else if (N > 0 && N<= a.length /4){
    resize(a.length /2);
    }
    }
    
    private void resize(int size) {
    Item[] tmp = (Item[]) new Object[size];
    
    for (int i = 0; i < N; i++) {
    tmp[i] = a[i];
    }
    
    a = tmp;
    }
    
    @Override
    public Item pop() throws Exception {
    if (isEmpty()) {
    throw new Exception("stack is empty");
    }
    
    Item item = a[--N];
    
    check();
    
    // 避免对象游离
    a[N] = null;
    
    return item;
    }
    
    @Override
    public boolean isEmpty() {
    return N == 0;
    }
    
    @Override
    public int size() {
    return N;
    }
    
    @Override
    public Iterator<Item> iterator() {
    // 返回逆序遍历的迭代器
    return new Iterator<Item>() {
    
    private int i = N;
    
    @Override
    public boolean hasNext() {
    return i > 0;
    }
    
    @Override
    public Item next() {
    return a[--i];
    }
    };
    }
    }

    队列:

    先进先出 FIFO
    出口端叫队头,入口端叫队尾

    1. 循环队列:
      队列满了的判定条件: (队尾下标 + 1) % 数组长度 = 队头下标
      (rear + 1 )%array.length == front
      队列最大容量比数组长度小1
    2. 双端队列:
      结合了栈和队列的特点,既可以先进先出,也可以先入后出
      从队头一端可以出队或入队,从队尾一端也可以出队或入队
    3. 优先队列:
      谁的优先级高,谁先出队(二叉堆实现的)

    MyQueue.java

    package array;/**
    * Copyright (C), 2019-2020
    * author  candy_chen
    * date   2020/6/19 11:12
    * version 1.0
    * Description: 循环队列
    */
    
    /**
    *
    */
    public class MyQueue {
    private int[] array;
    private int front;
    private int rear;
    
    public MyQueue(int capacity){
    this.array = new int[capacity];
    
    }
    
    /**
    * 入队
    * @param element 入队的元素
    * @throws Exception 队列已满
    */
    
    public void enQueue(int element) throws Exception {
    if ((rear + 1 )%array.length == front){
    throw new Exception("队列已满");
    }
    array[rear] = element;
    rear = (rear + 1)%array.length;
    }
    
    /**
    * 出队
    * @return
    * @throws Exception
    */
    public int deQueue() throws Exception {
    if (rear == front){
    throw new Exception("队列已空!");
    
    }
    int deQueueElement = array[front];
    front = (front+1)%array.length;
    return deQueueElement;
    }
    
    /**
    * 输出队列
    */
    public void output(){
    for (int i = front;i!= rear;i=(i + 1)%array.length){
    System.out.println(array[i]);
    }
    }
    
    public static void main(String[] args) throws Exception {
    MyQueue myQueue = new MyQueue(6);
    myQueue.enQueue(3);
    myQueue.enQueue(5);
    myQueue.enQueue(6);
    myQueue.enQueue(8);
    myQueue.enQueue(1);
    myQueue.deQueue();
    myQueue.deQueue();
    myQueue.deQueue();
    myQueue.enQueue(2);
    myQueue.enQueue(4);
    myQueue.enQueue(9);
    myQueue.output();
    
    }
    }

    散列表(哈希表):hash table

    散列表实现类(HashMap),提供了键(Key)和值(Value)的映射关系,只要给出一个Key就可以高效的找出匹配的Value,时间复杂度:O(1)

    基本原理:

    • 本质上是一个数组

    1. 哈希函数

    通过某种方式,把Key和数组下标进行转换,这个转换的中转站就叫做哈希函数

    • hashcode

    • HashMap

      通过哈希函数,我们可以把字符串或者其他类型的key转换成数组的下标index,想要转化成数组的下标,做简单的转换方式是按照数组长度当进行取模运算

      index = HashCode(key) % Array.length

    例:给出一个长度为8的数组,当
    key = 5720153303时,
    index = HashCode(“5720153303”) % Array.length = 1420036703 % 8 = 7
    key = this时,
    index = HashCode(“this”) % Array.length = 3559070 % 8 = 6

    2.散列表的读写操作:

    (1) 写操作:put

    就是在散列表中插入新的键值对(Entry) hashMap.put(“2020619”,“张三”)
    哈希冲突:
    数组的长度有限,当插入的Entry越来越多时,不同的Key通过哈希函数获得的下标有可能是相同的,则产生哈希冲突
    解决方法:

      1. 开放寻址法
        原理:当一个Key通过哈希函数获得对应的数组下标已经被占用时,则寻找下一个空档位置
        应用:在java中,ThreadLocal使用的就是开放寻址法
      1. 链表法
        原理:HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点,每一个Entry对象通过next指针指向它的下一个Entry节点
        当新的Entry映射与之冲突时,则与该位置为头节点,插入到该链表中
        应用:java中的HashMap

    (2) 读操作:get

    通过给定的Key,在散列表中查找对应的Value
    步骤:

      1. 通过哈希函数,把Key转换成数组下标
      1. 如果不冲突,则返回对应的Value; 如果冲突,则顺着链表往下找,查找匹配的节点

    (3) 扩容:resize

    • 当大量元素拥挤在相同数组下标位置,形成很长的链表,对后续插入和查询会有很大影响,则需要对散列表进行扩容
      散列表实现类(HashMap)而言,扩容因素:

      1. Capacity,即HashMap的当前长度
      1. LoadFactor,即HashMap的负载因子,默认为0.75f
        扩容条件:HashMap.Size >= Capacity x LoadFactor

      扩容步骤:

      1. 扩容:创建一个新的Entry数组,长度为原来的2倍
      1. 重新Hash:遍历元Entry数组,把原来的Entry重新Hash到新数组中。
        重新Hash是因为长度扩大,Hash的规则也随之改变。原本拥挤的散列表重新变得稀疏,原来的Entry也红心得到尽可能的均匀分配

    说明:根据网络资料进行搜索学习理解整理 若有侵权联系作者

    • 参考书籍:漫画算法-小灰的算法之旅
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: