從零開始學資料結構和演算法(二)線性表的鏈式儲存結構

DevYK發表於2019-03-22

連結串列

  • 鏈式儲存結構

    c.png

  • 定義

    • 線性表的鏈式儲存結構的特點是用一組任意的儲存單元的儲存線性表的資料元素,這組儲存單元是可以連續的,也可以是不連續的。
  • 種類

    • 結構圖
    • 單連結串列
      • 應用:MessageQueue
      • 插入 enqueueMessage(Message msg,Long when)。
      • 刪除 next ()。
    • 單迴圈連結串列
    • 雙連結串列
      • LinkedList
    • 雙向迴圈連結串列
  • 優缺點

    • 優點:插入刪除快

    • 缺點:不支援隨即訪問

學習例子

基於系統 API LinkedList 將麻將進行分組排序

  • 思想

    d.png

  • 邏輯步驟

    1. 把所有的點數分別裝入到對應的連結串列組中
    2. 在把連結串列中所有的點數合併在一起
    3. 在把所有的的點數分別裝在對應的連結串列中
    4. 最後把對應的點數連結串列裝在一個新的集合中
  • 程式碼編寫

    /**
     * 麻將資料 Bean
     */
    public class Mahjong {
        public int suit;//筒,萬,索
        public int rank;//點數 一  二  三
    
        public Mahjong(int suit, int rank) {
            this.suit = suit;
            this.rank = rank;
        }
    
        @Override
        public String toString() {
            return "("+this.suit+" "+this.rank+")";
        }
    }
    複製程式碼
    /**
     * 用系統自帶的 連結串列結構 LinkedList 來進行將麻將排序
     * @param list
     */
    private void radixSort(LinkedList<Mahjong> list) {
        //1. 先把所有的點數分別裝入到對應的連結串列組中
        //建立一個點數最大的為 9 的集合
        LinkedList[] linkedList = new LinkedList[9];
        //先初始化這 9 個連結串列目的是裝所有的對應的點數
        for (int i = 0; i < 9; i++) {
            linkedList[i] = new LinkedList();
        }
        while (list.size() > 0) {
            //取出對應的元素放入到連結串列中
            Mahjong mahjong = list.remove();
            //mahjong.rank - 1 的意思就是對應的集合下標
            linkedList[mahjong.rank - 1].add(mahjong);
        }
    
        //2. 然後再把所有的連結串列中的點數合併在一起
        for (int i = 0; i < linkedList.length; i++) {
            list.addAll(linkedList[i]);
        }
    
        //3. 在把所有的點數分別裝入到對應的連結串列中
        //花色有 3 個,那麼我們就建立 3 個連結串列來代表裝入對應的花色
        LinkedList[] linkedLists =new LinkedList[3];
        for (int i = 0; i < 3; i++) {
            linkedLists[i] = new LinkedList();
        }
    
        //把對應的花色裝在對應的連結串列中
        while (list.size()>0){
            Mahjong mahjong = list.remove();
            linkedLists[mahjong.suit - 1].add(mahjong);
        }
    
        //4. 最後在把對應的點數連結串列裝在一個新的集合中
        for (int i = 0; i < linkedLists.length; i++) {
            list.addAll(linkedLists[i]);
        }
    }
    複製程式碼
  • 應用

    資料量幾十個,插入操作多的情況

編寫雙向連結串列 LinkedList CURD

  • 參考圖解

    e.png

  • 編寫程式碼

/**
 * Created by yangk on 2019/1/29.
 * <p>
 * 自己定義的雙向連結串列結構 LinkedList
 */

public class CustomLinkedList<E> {

    /**
     * 連結串列的頭部
     */
    transient Node<E> head;

    /**
     * 連結串列的尾部
     */
    transient Node<E> tail;

    /**
     * 當前連結串列的大小
     */
    transient int size;

    /**
     * 存入資料
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }


    /**
     * 返回當前連結串列的大小
     *
     * @return
     */
    public int getSize() {
        return size;
    }

    /**
     * 將當前資料存入到連結串列的頭部
     *
     * @param e
     */
    public void addFirst(E e) {
        Node<E> h = head;
        //建立新節點
        Node<E> newNode = new Node<>(null, e, head);
        head = newNode;

        if (head == null) {
            tail = newNode;
        } else {
            h.pre = newNode;
        }
        size++;
    }

    /**
     * 根據索引搜尋到的節點資料
     *
     * @param index
     */
    public E get(int index) {
        if (isPositionIndex(index)) {
            return searchNode(index).data;
        }
        return null;
    }


    /**
     * 根據索引新增資料
     *
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        addIndex(index, e);
    }

    /**
     * 刪除第一個節點
     */
    public E remove() {
        return removeFirst();
    }
    
    /**
     * 刪除頭節點
     *
     * @return
     */
    private E removeFirst() {
        Node<E> h = head;
        if (h == null)
            throw new NoSuchElementException();
        return unlinkFirst(h);
    }

    private E unlinkFirst(Node<E> h) {
        //刪除的 資料
        E deleData = h.data;
        //找到要刪除的後驅
        Node next = h.next;

        //清理節點
        h.data = null;
        h.next = null;

        //將當前要刪除的後驅置為連結串列頭
        head = next;


        if (next == null) {
            tail = null;
        } else {
            next.pre = null;
        }
        h = null;

        size--;
        return deleData;
    }

    private void addIndex(int index, E e) {
        if (isPositionIndex(index)) {
            //找到當前需要插入的索引位置
            if (index == size) {
                linkLast(e);
            } else {
                add(e, searchNode(index));
            }
        }
    }

    /**
     * 新增新節點到 searchNode 前驅
     *
     * @param e
     * @param searchNode
     */
    private void add(E e, Node<E> searchNode) {
        //找到 searchNode 前驅節點
        Node<E> snPre = searchNode.pre;
        //建立新節點
        Node<E> newNode = new Node<>(snPre, e, searchNode);
        searchNode.pre = newNode;
        //這裡判斷 snPre 是否為空 入股為空說明 head 沒有資料,如果有資料 就直接把 snPre . next() = newNode
        if (snPre == null) {
            head = newNode;
        } else {
            snPre.next = newNode;
        }
        size++;
    }

    private Node<E> searchNode(int index) {
        //優化尋找節點 如果 index  > size / 2 就從尾部開始查詢,反之從 head 開始遍歷查詢
        if (index > (size >> 1)) {
            Node<E> t = tail;
            for (int i = size - 1; i > index; i--) {
                t = t.pre;
            }
            return t;
        } else {
            Node<E> h = head;
            for (int i = 0; i < index; i++) {
                h = h.next;
            }
            return h;
        }
    }

    /**
     * 將資料存入到當前連結串列尾部
     *
     * @param e
     */
    private void linkLast(E e) {
        //拿到尾部的節點資料
        Node<E> t = tail;
        //建立新的節點資料,因為是存在當前節點的尾部,
        // 那麼直接預設將當前新增進來的 E 的前驅設定為
        // 當前連結串列中的尾部資料,現在已經形成單連結串列了
        // 下一步直接形成雙向連結串列
        Node<E> newNode = new Node<>(t, e, null);
        //現在是雙向連結串列,要把新的節點指向當前的尾部節點,尾部節點指向新的節點
        tail = newNode;

        //如果尾部節點為空 那麼說明 head 也是空資料 那就吧新節點資料賦值給 head
        if (t == null) {
            head = newNode;
        } else {
            t.next = newNode;
        }
        size++;
    }


    /**
     * 建立一個空的構造者
     */
    public CustomLinkedList() {
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    /**
     * 定義一個內部節點
     * <p>
     * 雙向連結串列需要前驅,後驅,資料
     */
    public static class Node<E> {
        /**
         * 當前節點的前驅
         */
        private Node pre;
        /**
         * 當前節點的資料
         */
        private E data;
        /**
         * 當前節點的後驅
         */
        private Node next;

        public Node(Node pre, E data, Node last) {
            this.pre = pre;
            this.data = data;
            this.next = last;
        }
    }
}
複製程式碼
  • 測試程式碼

    @Test
    public void testCustomLinkedList() {
        CustomLinkedList<Integer> linkedList = new CustomLinkedList<Integer>();
        linkedList.add(22);
        linkedList.add(2);
        linkedList.add(77);
        linkedList.add(6);
        linkedList.add(43);
        linkedList.add(76);
        linkedList.add(89);
    
        linkedList.add(0,0);
    
        for (int i = 0; i < linkedList.size; i++) {
            int integer = linkedList.get(i);
            System.out.println("--CustomLinkedList--CustomLinkedList" +integer+"");
        }
        System.out.println("\n\n");
        Integer remove = linkedList.remove();
        System.out.println("--CustomLinkedList--CustomLinkedList" +remove);
        Integer remove1 = linkedList.remove();
        System.out.println("--CustomLinkedList--CustomLinkedList" +remove1+"");
        Integer remove2 = linkedList.remove();
        System.out.println("--CustomLinkedList--CustomLinkedList" + remove2 + "");
    
    
        System.out.println("\n\n");
        for (int i = 0; i < linkedList.size; i++) {
            int integer = linkedList.get(i);
            System.out.println("--CustomLinkedList--CustomLinkedList" +integer+"");
        }
    }
    複製程式碼
  • 測試結果

    ee.png

編寫簡單的 ArrayList CURD

public class CustomArrayList<E> {

    /**
     * 預設的空元素物件
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 空元素資料
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 預設的元素物件
     */
    private Object[] elementData = null;

    /**
     * 容量大小
     */
    private int size = 0;

    /**
     * 預設的容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 最大的數量
     *
     * TODO------------減 8 是什麼意思沒有搞懂
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE  - 8;

    /**
     * 賦值為一個空物件
     */
    public CustomArrayList(){
        elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 外部指定初始化一個容量大小
     * @param initCapacity
     */
    public CustomArrayList(int initCapacity){
        if (initCapacity > 0){
            elementData = new Object[initCapacity];
        }else if (initCapacity == 0){
            elementData = EMPTY_ELEMENTDATA;
        }else {
            throw  new IllegalArgumentException("Illegal Capacity: "+
                    initCapacity);
        }
    }

    /**
     * 新增資料
     */
    public boolean add(E e){
        //判斷是否需要開闢容量空間
        checkIsNeedCapacity(size + 1);
        //新增新增資料
        elementData[size++] = e;

        return true;
    }


    private void checkIsNeedCapacity(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * 開闢空間的核心程式碼
     * @param minCapacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }
}
複製程式碼

編寫單向連結串列結構的 CURD

f.jpg

/**
 * Created by yangk on 2019/2/19.
 */

public class SingleLinked<T> {

    /**
     * 頭節點
     */
    Node<T> head;
    /**
     * 連結串列長度
     */
    int size = 0;

    /**
     * 將資料新增到連結串列中
     *
     * @param data
     */
    public void add(T data) {
        Node node = new Node(data);
        if (head == null) {
            head = node;
            return;
        }
        Node<T> tmp = head;

        while (tmp.next != null) {
            tmp = tmp.next;
        }

        tmp.next = node;

        size++;
    }

    /**
     * 將資料新增到第一個位置
     *
     * @param data
     */
    public void addHead(T data) {
        Node node = new Node(data);
        if (head == null) {
            head = node;
            size++;
            return;
        }

        Node n = head;
        node.next = n;
        head = node;

        size++;
    }

    public void add(int index, T data) {
        Node node = new Node(data);
        if (index == 0) {
            addHead(data);
        } else {
            Node tmp = head;
            for (int i = 0; i < index - 1; i++) {
                tmp = tmp.next;
            }
            Node n = tmp.next;
            tmp.next = node;
            node.next = n;
            size++;
        }
    }

    /**
     * 全部清除資料
     */
    public void clear() {
        head = null;
        size = 0;
    }

    public boolean remove(int index) {//2
        Node<T> tmp = head;
        if (index == 0) {
            Node<T> newHead = tmp.next;
            head = null;
            head = newHead;
            size--;
            return true;
        }
        if (index < size) { // 1 2 3 4 5 6 7    3
            for (int i = 0; i < index - 2; i++) {
                tmp = tmp.next;
            }
            Node<T> pre = tmp;
            //要刪除的節點
            Node<T> next = tmp.next;
            Node<T> p = tmp.next.next;
            pre.next = p;
            next = null;
            size--;
            return true;
        }
        return false;

    }


    /**
     * 節點資料
     */
    private class Node<T> {
        T data;
        Node<T> next;

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }

        public Node(T next) {
            this.data = next;
        }

    }

    public void println() {
        if (head == null) return;
        Node n = head;

        while (n != null){
            System.out.println(n.data + "\n");
            n = n.next;
        }
    }
}
複製程式碼

資料結構系列文章

相關文章