戀上數據結構與算法:普通單向鏈表(三)

文章目錄

(一)鏈表(Linked List)的簡介
(二)鏈表(Linked List)的設計
(三)動態數組(Dynamic Array)的優化
(四)鏈表(Linked List)的實現:clear
(五)鏈表(Linked List)的實現:add
(六)鏈表(Linked List)的實現:get&set
(七)鏈表(Linked List)的實現:remove
(八)鏈表(Linked List)的實現:indexOf
(九)鏈表(Linked List)的代碼彙總&測試
(十)補充
(十一)虛擬頭結點

(一)鏈表(Linked List)的簡介

動態數組有個明顯的缺點:可能會造成內存空間的大量浪費
能否用到多少就申請多少內存?鏈表可以辦到這一點

鏈表是一種鏈式存儲的線性表,所有元素的內存地址不一定是連續的
在這裏插入圖片描述

(二)鏈表(Linked List)的設計

在這裏插入圖片描述
我們直接把方法都抽取到List父類是沒用的,因爲ArrayListLinkedList的實現有所差別(有相同,有不同)

正確的做法是讓List成爲接口,把方法的聲明都放在List接口,由AbstractList去實現這個接口,對於ArrayListLinkedList的實現都相同的方法可以直接抽取到AbstractList中,然後讓ArrayListLinkedList都繼承AbstractList,並且實現抽象方法

List接口代碼如下:
在這裏插入圖片描述
問題:爲什麼static final int ELEMENT_NOT_FOUND = -1;要寫在List接口中
在這裏插入圖片描述
當我們使用多態的寫法創建ArrayList時,執行list.indexOf(20)時,如果查詢不到,相當於list.indexOf(20) == List.ELEMENT_NOT_FOUND,所以要寫在List接口
其實也可以寫在AbstractList抽象類中,但是從設計的角度思考,AbstractList是不可見的,只是起到抽取公共方法的作用,最好不要加在這裏

AbstractList抽象類代碼如下:
在這裏插入圖片描述
最後讓LinkedList繼承AbstractList並實現相應方法(方法體先留空),如下:
在這裏插入圖片描述

(三)動態數組(Dynamic Array)的優化

public class ArrayList<E> extends AbstractList<E> {

    private E[] elements;//所有的元素
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定數組的容量
     *
     * @param capaticy 容量
     */
    public ArrayList(int capaticy) {
        capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
        elements = (E[]) new Object[capaticy];
    }

    /**
     * 不指定的話默認容量是10
     */
    public ArrayList() {
        this(DEFAULT_CAPACITY);
    }


    /**
     * 保證要有capacity的容量
     *
     * @param capacity
     */
    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) return;//不需要擴容,直接返回
        //位運算比浮點運算(oldCapacity * 2)效率高
        int newCapacity = oldCapacity + (oldCapacity >> 1);//相當於乘1.5(每次擴容1.5倍)
        E[] newElements = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;//舊的數組指向新的數組
        System.out.println(oldCapacity + "擴容爲:" + newCapacity);
    }

    /**
     * 清除所有元素
     */
    @Override
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    /**
     * 元素的數量
     *
     * @return
     */
    @Override
    public int size() {
        return size;
    }

    /**
     * 是否爲空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }


    /**
     * 獲取index位置的元素
     *
     * @param index
     * @return
     */
    @Override
    public E get(int index) {
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 設置index位置的元素
     *
     * @param index
     * @param element
     * @return 原來的元素ֵ
     */
    @Override
    public E set(int index, E element) {
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 在index位置插入一個元素
     *
     * @param index
     * @param element
     */
    @Override
    public void add(int index, E element) {
        if (element == null) return;
        rangeCheckForAdd(index);
        ensureCapacity(size + 1);//capacity取值爲size+1,就是保證比size多一個,不怕不夠用
        for (int i = size; i > index; i--) {
            elements[i] = elements[i - 1];
        }
        elements[index] = element;
        size++;
    }

    /**
     * 刪除index位置的元素
     *
     * @param index
     * @return
     */
    @Override
    public E remove(int index) {
        rangeCheck(index);
        E old = elements[index];
        for (int i = index + 1; i < size; i++) {
            elements[i - 1] = elements[i];
        }
        elements[--size] = null;
        return old;
    }

    /**
     * 查看元素的索引
     *
     * @param element
     * @return
     */
    @Override
    public int indexOf(E element) {
        if (element == null) {
            for (int i = 0; i < size; i++) {
                if (elements[i] == null) return i;
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(elements[i])) return i;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率會更高
        stringBuilder.append("size=").append(size).append(",[");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                stringBuilder.append(", ");
            }
            stringBuilder.append(elements[i]);
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

(四)鏈表(Linked List)的實現:clear

clear()方法清空所有元素,過程如下:
在這裏插入圖片描述
注意:不需要將next設置爲null,因爲first=null後,0號元素第一個被銷燬,接着是1號元素,直至所有元素被銷燬

代碼如下:

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

(五)鏈表(Linked List)的實現:add

add(int index,E element)方法添加元素,過程如下:
在這裏插入圖片描述
現在有一個元素,想添加到1號元素的位置
在這裏插入圖片描述
首先讓新元素指向1號元素
在這裏插入圖片描述
然後找到1號元素的前一個元素,讓它指向新元素
在這裏插入圖片描述
最後索引發生調整
在這裏插入圖片描述
最後size+1,添加完成
在這裏插入圖片描述
首先要寫一個私有方法,private Node<E> node(int index),實現根據index獲取對應的Node,代碼如下:

    private Node<E> node(int index) {
        rangeCheck(index);
        Node<E> node = first;
        //從first開始,next index次 就可以找到要找的結點
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

public void add(int index, E element)方法的代碼如下:

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //要特殊處理index=0的情況
        if (index == 0) {
            first = new Node<>(element, first);
        } else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }

注意:在編寫鏈表過程中,要注意邊界測試,比如index爲0、size-1、size時

(六)鏈表(Linked List)的實現:get&set

藉助於剛纔寫的private Node<E> node(int index)方法可以很簡單的實現get()set()方法,代碼如下:

    @Override
    public E get(int index) {
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

注意get()set()方法都調用了private Node<E> node(int index)方法,該方法內部已經做了索引的檢查,所以無需再檢查索引

(七)鏈表(Linked List)的實現:remove

remove(int index)方法刪除元素,過程如下:
比如想要刪除index=1的元素
在這裏插入圖片描述
直接讓0號元素指向2號元素
在這裏插入圖片描述
此時1號元素就會被銷燬了
在這裏插入圖片描述
最後調整元素序號,刪除完成
在這裏插入圖片描述
代碼如下:

    @Override
    public E remove(int index) {
        Node<E> node = first;
        if (index == 0) {
            first = first.next;
        } else {
            Node<E> prev = node(index - 1);
            node = prev.next;
//            prev.next = prev.next.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

(八)鏈表(Linked List)的實現:indexOf

    @Override
    public int indexOf(E element) {
        if (element == null) {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        } else {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

(九)鏈表(Linked List)的代碼彙總&測試

public class LinkedList<E> extends AbstractList<E> {
    private Node<E> first;

    private static class Node<E> {
        E element;
        Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E get(int index) {
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //要特殊處理index=0的情況
        if (index == 0) {
            first = new Node<>(element, first);
        } else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }

    private Node<E> node(int index) {
        rangeCheck(index);
        Node<E> node = first;
        //從first開始,next index次 就可以找到要找的結點
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    @Override
    public E remove(int index) {
        Node<E> node = first;
        if (index == 0) {
            first = first.next;
        } else {
            Node<E> prev = node(index - 1);
            node = prev.next;
//            prev.next = prev.next.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
        if (element == null) {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        } else {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率會更高
        stringBuilder.append("size=").append(size).append(",[");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                stringBuilder.append(", ");
            }
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

測試如下:
在這裏插入圖片描述

(十)補充

完善remove()方法
在這裏插入圖片描述
修改後如下:
在這裏插入圖片描述
注意:後面的node(int index)雖然會調用rangeCheck(int index),但是在那之前就有拋出異常的風險,不同於get()set()方法一開始就調用node(int index)

(十一)虛擬頭結點

我們之前的add(int index, E element)remove(int index)方法都對index=0做了特殊處理,如下:
在這裏插入圖片描述
在這裏插入圖片描述
有時候爲了讓代碼更加精簡,統一所有節點的處理邏輯,可以在最前面增加一個虛擬的頭結點(不存儲數據)
在這裏插入圖片描述
首先要增加一個構造函數,如下:

    public LinkedList2() {
        first = new Node<>(null, null);
    }

然後修改Node<E> node(int index)方法,如下:
在這裏插入圖片描述
修改add(int index, E element)方法,如下:

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //當index=0時,會過不了node方法裏面的索引判斷,所以也要特殊處理
        Node<E> prev = (index == 0) ? first : node(index - 1);
        prev.next = new Node<>(element, prev.next);
        size++;
    }

修改remove(int index)方法,如下:

    @Override
    public E remove(int index) {
        rangeCheck(index);
        Node<E> prev = (index == 0) ? first : node(index - 1);
        Node<E> node = prev.next;
        prev.next = node.next;
        size--;
        return node.element;
    }

最後修改toString()方法,如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章