資料結構與演算法-連結串列

喵Ja發表於2020-11-01

連結串列

  • 動態陣列有個明顯的缺點:可能會造成記憶體空間的大量浪費。
  • 能否用多少就申請多少記憶體,連結串列就可以辦到這一點
  • 連結串列是一種鏈式儲存的線性表,所有元素的記憶體地址不一定是連續的

在這裡插入圖片描述

連結串列的設計

介面設計

連結串列的大部分介面和動態陣列是一致的。
把不需要修改的方法寫一個父類用來繼承,然後父類(抽象類)繼承介面
介面:

package lianbiao;

public interface List<E> {
    static final int ELEMENT_NOT_FOUND = -1;
    /**
     * 清除所有元素
     */
    void clear();

    /**
     * 元素的數量
     * @return
     */
    int size();

    /**
     * 是否為空
     * @return
     */
    boolean isEmpty();

    /**
     * 是否包含某個元素
     * @param element
     * @return
     */
    boolean contains(E element);

    /**
     * 新增元素到尾部
     * @param element
     */
    void add(E element);

    /**
     * 獲取index位置的元素
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 設定index位置的元素
     * @param index
     * @param element
     * @return 原來的元素ֵ
     */
    E set(int index, E element);

    /**
     * 在index位置插入一個元素
     * @param index
     * @param element
     */
    void add(int index, E element);

    /**
     * 刪除index位置的元素
     * @param index
     * @return
     */
    E remove(int index);

    /**
     * 檢視元素的索引
     * @param element
     * @return
     */
    int indexOf(E element);
}

父類(抽象)

package lianbiao;

/**
 * @Author :Ersan
 * @Date 2020/10/29
 * @Description
 */
public abstract class AbstractList<E> implements List<E>{
    /**
     * 元素的數量
     */
    protected int size;

    public int size(){
        return size;
    }

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

    /**
     * 是否包含某個元素
     * @param element
     * @return
     */
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 新增元素到尾部
     * @param element
     */
    public void add(E element) {
        add(size, element);
    }
    protected void outOfBounds(int index){
        throw new IndexOutOfBoundsException("Index:"+index+", Size:"+size);
    }
    protected void rangeCheck(int index){
        if (index<0||index>=size) {
            outOfBounds(index);
        }
    }
    protected void rangeCheckForAdd(int index){
        if (index<0||index>size) {
            outOfBounds(index);
        }
    }
}

清空元素

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

新增元素

    public void add(int index, E element) {
    	rangeCheckForAdd(index);
        if (index==0) {  //注意0的位置
            first = new Node<>(element,first);
        }else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<E>(element, prev.next);
        }
        size++;
    }

在編寫連結串列過程中,要注意邊界測試,比如 index 為 0 ,size - 0,size 時

node方法用於獲取index位置的節點

    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node=first;
        for (int i = 0; i < index; i++) {
            node=node.next;
        }
        return node;
    }

刪除元素 - remove(int index)

    public E remove(int index) {
        rangeCheck(index);
        Node<E> node = first;
        if (index==0){ //注意0的位置
            first=first.next;
        }else {
            Node<E> prev = node(index - 1);
            node=prev.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

SingleLinkedList程式碼

package lianbiao;

/**
 * @Author :Ersan
 * @Date 2020/10/29
 * @Description
 */
public class SingleLinkedList<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);
        if (index==0) {
            first = new Node<>(element,first);
        }else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<E>(element, prev.next);
        }
        size++;
    }

    @Override
    public E remove(int index) {
        rangeCheck(index);
        Node<E> node = first;
        if (index==0){
            first=first.next;
        }else {
            Node<E> prev = node(index - 1);
            node=prev.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;
    }


    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node=first;
        for (int i = 0; i < index; i++) {
            node=node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        StringBuffer string=new StringBuffer();
        string.append("size=").append(size).append(",  [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i!=0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;

//            if (i!=size-1){
//                string.append(",");
//            }
        }
        string.append("]");
        return string.toString();
    }
}

推薦一個神奇的網站

https://visualgo.net/zh

雙向連結串列

雙向連結串列可以提升連結串列的綜合效能

package lianbiao;

/**
 * @Author :Ersan
 * @Date 2020/10/29
 * @Description
 */
public class LinkedList<E> extends AbstractList<E> {
    private Node<E> first;
    private Node<E> last;

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

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

    @Override
    public void clear() {
        size = 0;
        first = null;
        last = null;
        /*
        * gc root :1.被棧指標指向的物件(區域性物件)
        * */
    }

    @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);
        if (index == size) { //往最後新增
            Node<E> oldLast = last;
            last = new Node<>(oldLast,element,null);
            if (oldLast == null){ //連結串列新增的第一個元素
                first = last;
            }else {
                oldLast.next = last;
            }
        }else {
            Node<E> next = node(index);
            Node<E> prev = next.prev;
            Node<E> node = new Node<>(prev, element, next);
            next.prev = node;
            if (prev == null) { //index == 0
                first = node;
            } else {
                prev.next = node;
            }
        }
        size++;
    }

    @Override
    public E remove(int index) {
        rangeCheck(index);
        Node<E> node = node(index);
        Node<E> prev = node.prev;
        Node<E> next = node.next;
        if (prev == null){ //index == 0
            first = next;
        }else {
            prev.next = next;
        }
        if (next == null){ // index == size-1
            last = prev;
        }else {
            next.prev = prev;
        }
        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;
    }


    private Node<E> node(int index) {
        rangeCheck(index);

        if (index < (size >> 1)) {
            Node<E> node = first;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        } else {
            Node<E> node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
            return node;
        }
    }
        public String toString(){
            StringBuffer string = new StringBuffer();
            string.append("size=").append(size).append(",  [");
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (i != 0) {
                    string.append(", ");
                }
                string.append(node.element);
                node = node.next;

//            if (i!=size-1){
//                string.append(",");
//            }
            }
            string.append("]");
            return string.toString();
    }
}

雙向連結串列 VS 動態陣列

  • 動態陣列:開闢,銷燬記憶體空間的次數相對較少,但可能造成記憶體空間的浪費(可以通過縮容解決)
  • 雙向連結串列:開闢,銷燬記憶體空間次數相對較多,但不會造成記憶體空間的浪費
  • 如果頻繁在尾部進行新增,刪除操作,動態陣列,雙向連結串列均可選擇
  • 如果頻繁在頭部進行新增,刪除操作,建議選擇使用雙向連結串列
  • 如果有頻繁的(在任意位置)新增,刪除操作,建議選擇使用雙向連結串列
  • 如果有頻繁的查詢操作(隨機訪問操作),建議選擇使用動態陣列

單向迴圈連結串列

雙向迴圈連結串列

相關文章