漫画算法读书笔记(一) 数据结构基础

数组

  • 数据是由有限个相同类型的变量所组成的有序集合
  • 物理存储方式是顺序存储访问方式是随机访问
  • 利用下标查找数组元素和修改数组元素的时间复杂度是O(1);
  • 中间插入,删除数组元素的时间复杂度是O(n)。
  • 适合读操作多,写操作少的场景
  • 基本操作
    • 读取
    • 更新
    • 删除
    • 插入
      • 尾部插入
      • 中间插入
      • 超范围插入(双倍扩容)

链表

  • 链表是一种链式数据结构,右若干节点组成,每个节点包含指向下一个节点的指针;
  • 物理存储方式是随机存储,访问方式是顺序访问
  • 查找链表节点的时间复杂度是O(n);
  • 中间插入、删除节点的时间复杂度是O(1);
  • 适合插入删除元素多的场景;
  • 基本操作:
    • 查找节点
    • 更新节点
    • 插入节点
      • 尾部插入
      • 头部插入
      • 中间插入
    • 删除元素
      • 尾部删除
      • 中间删除
      • 头部删除

  • 栈是一种线性逻辑结构
  • 可以用数组实现,也可以用链表时间
  • 遵循先入后出的原则(FILO)
  • 基本操作
  • 出栈
  • 入栈

队列

  • 队列也是一种线性逻辑结构
  • 可以用数组实现,也可以用链表实现
  • 遵循先进先出的原则(FIFO)
  • 基本操作
    • 入队
    • 出队

散列表

  • 又叫哈希表,存储Key-Value的集合,对于某一个key,散列表可以在接近O(1)的时间内进行读写操作,散列表通过哈希函数实现key和数组下标的转换;
  • 通过开放寻址法和链表法来解决哈希冲突的问题。
  • 基本操作
    • 写操作
    • 读操作
    • 扩容

数组链表复杂度对比

类型 查找 更新 插入 删除
数组 O(1) O(1) O(n) O(n)
链表 O(n) O(n) O(1) O(1)

逻辑结构与物理机构

  • 逻辑结构
    • 线性结构: 顺序表、栈、队列
    • 非线性结构:树、图
  • 物理结构
    • 顺序存储结构:数组
    • 链式存储结构:链表

附 相关代码

手写数组

代码在 github 上, write-array 分支

package org.jake.write.array;

/**
 * The type Self array.
 */
public class SelfArray {
    //数组集合 用于存放数据
    private int[] array;
    //当前数组的大小
    private int size;

    /**
     * 构造函数 初始化array
     *
     * @param capacity 初始化大小
     */
    public SelfArray(int capacity) {
        this.array = new int[capacity];
        size = 0;
    }


    /**
     * 插入一条数据
     *
     * @param index   插入的位置
     * @param element 插入的元素
     */
    public void insert(int index, int element) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("数组下标越界");
        }
        //如果size 大于等于数组的长度了 先进行扩容
        if (size >= array.length) {
            resize();
        }
        //从右到左循环,每个元素向右移动
        for (int i = size - 1; i > index; i--) {
            array[i + 1] = array[i];
        }
        array[index] = element;
        //size 增加一位
        size++;
    }

    /**
     * 删除元素
     *
     * @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; i++) {
            array[i] = array[i + 1];
        }
        size--;
        return deletedElement;
    }

    /**
     * Output.
     */
    public void output(){
        for(int i=0; i<size; i++){
            System.out.println(array[i]);
        }
    }

    /**
     * 扩容 扩容的大小为当前容器的两倍
     */
    private void resize() {
        int[] newArr = new int[array.length * 2];
        System.arraycopy(array, 0, newArr, 0, array.length);
        array = newArr;
    }
}

手写链表

代码在 github 上, write-linkedlist 分支

Node.class

package org.jake.write.linked.list;

public class Node {
    int data;
    Node next;

    Node(int data) {
        this.data = data;
    }
}

SelfLinkedList.class

package org.jake.write.linked.list;

/**
 * The type Self linked list.
 */
public class SelfLinkedList {

    //头节点指针
    private Node head;
    //尾节点指针
    private Node last;
    //链表的实际长度
    private int size;


    /**
     * 插入元素
     *
     * @param data  插入的数据
     * @param index the index 插入的位置
     * @throws Exception the exception
     */
    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) {
            //如果是插入头部 之前的头部节点成为新增节点的下一个节点,新增节点成为头部节点
            insertedNode.next = head;
            head = insertedNode;
        } else if (size == index) {
            //如果是尾部插入 之前的尾部节点的next节点 成为新增新增节点  新增节点成为尾节点
            last.next = insertedNode;
            last = insertedNode;
        } else {
            Node prevNode = get(index - 1);
            insertedNode.next = prevNode.next;
            prevNode.next = insertedNode;
        }
        size++;
    }


    /**
     * 节点的删除
     *
     * @param index the index
     * @return the node
     */
    public Node remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("数组下标越界");
        }
        Node removedNode = null;
        if (index == 0) {
            //删除头结点
            removedNode = head;
            head = head.next;
        } else if (index == size - 1) {
            //删除尾节点
            Node prevNode = get(index - 1);
            removedNode = prevNode.next;
            prevNode.next=null;
            last = prevNode;
        }else {
            //删除中间节点
            Node prevNode = get(index-1);
            Node nextNode = prevNode.next.next;
            removedNode = prevNode.next;
            prevNode.next= nextNode;
        }
        size--;
        return removedNode;
    }


    /**
     * Get node.
     *
     * @param index the index
     * @return the node
     */
    public 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;
    }

    /**
     * Output.
     */
    public void output() {
        Node temp = head;
        while (temp != null) {
            System.out.println(temp.data);
            temp = temp.next;
        }
    }


}

手写栈

代码在 github 上, write-stack 分支

Stack.class

package org.yujuan.write.stack;

/**
 * The interface Stack.
 *
 * @param <E> the type parameter
 * @author yujuan
 * @time 2019 /09/26 10:09:37
 */
public interface Stack<E> {

    /**
     * Length int.
     *
     * @return the int
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    int length();

    /**
     * Pop e.
     *
     * @return the e
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    E pop();

    /**
     * Push.
     *
     * @param element the element
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    void push(E element);

    /**
     * Peek e.
     *
     * @return the e
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    E peek();

    /**
     * Empty boolean.
     *
     * @return the boolean
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    boolean empty();

    /**
     * Clear.
     *
     * @author yujuan
     * @time 2019 /09/26 10:09:37
     */
    void clear();
}

SelfStack.class

package org.yujuan.write.stack;

import java.util.Arrays;

public class SelfStack<E> implements Stack<E> {

    private int DEFAULT_SIZE = 16;
    private int capacity;

    private int size;

    private Object[] elementData;

    public SelfStack() {
        capacity = DEFAULT_SIZE;
        elementData = new Object[capacity];
    }

    public SelfStack(int initSize) {
        capacity = 1;
        //capacity 设置为initSize 的最小二次方
        while (capacity < initSize) {
            capacity <<= 1;
        }
        elementData = new Object[capacity];
    }

    @Override
    public int length() {
        return size;
    }

    @Override
    public E pop() {
        if (empty()) {
            throw new IndexOutOfBoundsException();
        }
        E oldValue = (E) elementData[size - 1];
        elementData[--size] = null;
        return oldValue;
    }

    @Override
    public void push(E element) {
        ensureCapacity(size + 1);
        elementData[size++] = element;
    }


    /**
     * 扩容
     *
     * @param minCapacity
     */
    private void ensureCapacity(int minCapacity) {
        if (minCapacity > capacity) {
            while (capacity < minCapacity) {
                capacity <<= 1;
            }
        }
        elementData = Arrays.copyOf(elementData, capacity);
    }

    @Override
    public E peek() {
        if (empty()) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return (E) elementData[size - 1];
    }

    @Override
    public boolean empty() {
        return size == 0;
    }

    @Override
    public void clear() {
        for (int i = 0; i < size; i++) {
            elementData[i] = null;
        }
        size = 0;
    }
}

手写hashMap

代码在 github 上, write-hash-map 分支

Node.class

package org.jake.write.hash.map;

import java.util.Map;

public class Node<K, V> implements Map.Entry<K, V> {
    private K key;
    private V value;
    private Node<K, V> nextNode; //下一节点

    public Node(K key, V value, Node<K, V> nextNode) {
        super();
        this.key = key;
        this.value = value;
        this.nextNode = nextNode;
    }

    @Override
    public K getKey() {
        return this.key;
    }

    @Override
    public V getValue() {
        return this.value;
    }

    @Override
    public V setValue(V value) {
        this.value = value;
        return this.value;
    }

    public Node<K, V> getNextNode() {
        return nextNode;
    }

    public void setNextNode(Node<K, V> nextNode) {
        this.nextNode = nextNode;
    }

    public void setKey(K key) {
        this.key = key;
    }

    //判断是否还有下一个节点
        /*private boolean hasNext() {
            return true;
        }*/

}

SelfHashMap.class

package org.jake.write.hash.map;

/**
 * The type Self hash map.
 *
 * @param <K> the type parameter
 * @param <V> the type parameter
 */
public class SelfHashMap<K, V> {
    /**
     * The Table.
     */
    private Node<K, V>[] table = null;

    /**
     * The constant DEFAULT_INITIAL_CAPACITY.
     */
    private static int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The constant DEFAULT_LOAD_FACTOR.
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The constant size.
     */
    private static int size;

    /**
     * Put v.
     *
     * @param k the k
     * @param v the v
     * @return the v
     */
    @SuppressWarnings("unchecked")
    public V put(K k, V v) {
        if (table == null) {
            table = new Node[DEFAULT_INITIAL_CAPACITY];
        }

        //如果size大于阈值则进行扩容
        if (size > DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR) {
            resize();
        }

        //2.计算出index角标
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);

        //3.将k-v键值对放进相对应的角标,如果计算出角标相同则以链表的形势存放
        Node<K, V> node = table[index];
        if (node == null) {
            table[index] = new Node<>(k, v, null);
            size++;
            return table[index].getValue();
        } else {
            Node<K, V> newNode = node;

            //循环遍历每个节点看看是否存在相同的key
            while (newNode != null) {
                //这里要用equals 和 == 因为key有可能是基本数据类型,也有可能是引用类型
                if (k.equals(newNode.getKey()) || k == newNode.getKey()) {
                    newNode.setValue(v);
                    size++;
                    return v;
                }
                newNode = node.getNextNode();
            }
            table[index] = new Node<K, V>(k, v, table[index]);
            size++;

            return table[index].getValue();
        }

    }

    /**
     * Gets index.
     *
     * @param key    the key
     * @param length the length
     * @return the index
     */
    public int getIndex(K key, int length) {
        int hashCode = key.hashCode();
        int index = hashCode % length;
        return index;
    }

    /**
     * Get v.
     *
     * @param k the k
     * @return the v
     */
    public V get(K k) {
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);
        Node<K, V> node = table[index];
        if (k.equals(node.getKey()) || k == node.getKey()) {
            return node.getValue();
        } else {
            Node<K, V> nextNode = node.getNextNode();
            while (nextNode != null) {
                if (k.equals(nextNode.getKey()) || k == nextNode.getKey()) {
                    return nextNode.getValue();
                }
            }
        }
        return null;
    }

    /**
     * Resize.
     */
    @SuppressWarnings("unchecked")
    public void resize() {
        //1.创建新的table长度扩展为以前的两倍
        int newLength = DEFAULT_INITIAL_CAPACITY * 2;
        Node<K, V>[] newtable = new Node[newLength];
        //2.将以前table中的取出,并重新计算index存入
        for (int i = 0; i < table.length; i++) {
            Node<K, V> oldtable = table[i];
            while (oldtable != null) {
                //将table[i]的位置赋值为空,
                table[i] = null;

                //方法1:重新计算index,然后按照put时候的方法进行放值,此种方法会不停的new 对象会造成效率比较低
                /*K key = oldtable.getKey();
                int index = getIndex(key, newLength);
                newtable[index] = new Node<K, V>(key, oldtable.getValue(), newtable[index]);
                oldtable = oldtable.getNextNode();*/

                //方法2:
                //计算新的index值
                K key = oldtable.getKey();
                int index = getIndex(key, newLength);

                //将以前的nextnode保存下来
                Node<K, V> nextNode = oldtable.getNextNode();

                //将newtable的值赋值在oldtable的nextnode上,如果以前是空,则nextnode也是空
                oldtable.setNextNode(newtable[index]);
                newtable[i] = oldtable;

                //将以前的nextcode赋值给oldtable以便继续遍历
                oldtable = nextNode;
            }

        }
        //3.将新的table赋值回老的table
        table = newtable;
        DEFAULT_INITIAL_CAPACITY = newLength;
        newtable = null;
    }

    /**
     * Size int.
     *
     * @return the int
     */
    public int size() {
        return size;
    }
}

手写栈

代码在 github 上, write-queue 分支

package org.jake.write.queue;

public class SelfQueue {
    private int[] array;
    // 头指针
    private int front;
    //尾指针
    private int rear;

    public SelfQueue(int capacity) {
        this.array = new int[capacity];
    }

    /**
     * 入队
     *
     * @param element 入队的元素
     */
    public void enQueue(int element) throws Exception {
        //循环队列
        if ((rear + 1) % array.length == front) {
            throw new Exception("队列已满!");
        }
        array[rear] = element;
        rear = (rear + 1) % array.length;
    }

    /**
     * 出队
     */
    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]);
        }
    }
}


原文地址:https://www.cnblogs.com/jakaBlog/p/11767860.html