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

文章结构概览
在这里插入图片描述

数组:顺序存储

读操作多,写操作少

  • 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也红心得到尽可能的均匀分配

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

  • 参考书籍:漫画算法-小灰的算法之旅

猜你喜欢

转载自blog.csdn.net/qq_35655602/article/details/106992164