1. 程式人生 > >資料結構——【連結串列】

資料結構——【連結串列】

最基礎的動態資料結構:連結串列

Java中線性資料結構包括:陣列、棧、佇列【這三者底層都是基於動態陣列實現的,實現動態的機制依靠resize()動態擴容】、連結串列【真正的動態資料結構】。

 

連結串列可以分為單向連結串列和雙向連結串列。連結串列中的資料都儲存在Node節點中,連結串列與連結串列之間的連線依靠next指標

Class Node{
    //存放資料
    E e;
    //指向當前節點的下一個節點
    Node next;
}

為什麼說連結串列是真正的動態資料結構呢?

連結串列不像陣列,需要在初始化時候申明容量。連結串列中每一個節點都是一個Node物件,存放著資料和指向下一個Node的引用,在最後一個Node中,next為Null,連結串列不需要考慮和處理固定容量的問題,自然就不需要resize()了。

 

對於連結串列的特點,綜合而言:

優點:真正的動態資料結構,不需要處理固定容量的問題

缺點:不像陣列一樣可以通過Index直接定位元素,喪失了隨機訪問的能力,從底層來說,陣列開闢的空間在記憶體中是連續分佈的,所以我們可以直接通過index的偏移直接計算出響應資料所儲存的記憶體地址,直接通過O(1)即可取得元素。但是連結串列是通過next引用連線的,每一個Node所在的記憶體地址是不連續的,我們只能通過next來找到對應的元素。

 

下面我們來設計一個單向連結串列

使用了連結串列的虛擬頭節點dummyHead,這樣不需要對addFirst()進行特殊處理

package cn.itcats.linkedlist;

public class LinkedList<E> {

    //建立內部類Node
    private class Node{
        public E e;
        public Node next;

        public Node(E e , Node next){
            this.e = e ;
            this.next = next;
        }

        public Node(E e){
            this(e,null);
        }

        public Node(){
            this(null,null);
        }

        @Override
        public String toString(){
            return e.toString();
        }

    }
    //為連結串列設定虛擬頭結點,則不需要對頭結點進行特殊處理
    private Node dummyHead;
    private int size;

    public LinkedList(){
        dummyHead = new Node(null,null);
        size = 0;
    }

    //獲得連結串列中元素個數
    public int getSize(){
        return size;
    }

    //判斷連結串列是否為空
    public boolean isEmpty(){
        return size == 0 ;
    }

    //在連結串列的index位置新增新元素e
    public void add(int index ,E e){
        //對index進行判斷
        if(index < 0 && index > size){
            throw new IllegalArgumentException("index不合法");
        }
            Node prev = dummyHead;
            //當遍歷到插入索引的前一個索引
            for(int i = 0 ;i < index ; i++){
                //prev指向下一個元素(index)
                prev = prev.next;
            }
//            Node node = new Node(e);
//            node.next = prev.next;
//            prev.next = node;
            prev.next = new Node(e,prev.next);
            size ++ ;
        }

    //獲取連結串列中指定"索引"的元素,所謂索引,只不過是遍歷位置的次數而已
    public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("index不合法");
        }
        //從dummyHead的下一個節點遍歷
        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i++){
            cur = cur.next;
        }
        return cur.e;
    }

    //獲取連結串列中的第一個元素
    public E getFirst(){
        return get(0);
    }

    //獲取連結串列中的最後一個元素
    public E getLast(){
        return get(size-1);
    }

    //修改連結串列中第index個位置的元素為e
    public void set(int index, E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("index不合法");
        }
        //找到第index位置的元素
        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i++){
            cur = cur.next;
        }
        cur.e = e;
    }

    //查詢連結串列中是否存在元素e
    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e)){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    /**
     * 在連結串列頭部新增新的元素
     */
    public void addFirst(E e){
        add(0,e);
    }

    //在連結串列尾部新增元素
    public void addLast(E e){
        add(size,e);
    }

    //刪除節點
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("index不合法");
        }
        Node prev = dummyHead;
        for(int i = 0 ; i < index ;i++){
            //prev此時指向的是被刪除節點的前一個位置
            prev = prev.next;
        }
        //需要被刪除的節點
        Node removeNode = prev.next;
        prev.next = removeNode.next;
        removeNode.next = null;
        size --;
        return removeNode.e;
    }

    //從連結串列中刪除第一個元素,返回刪除的元素
    public E removeFirst(){
       return remove(0);
    }

    //從連結串列中刪除最後一個元素,返回刪除的元素
    public E removeLast(){
        return remove(size -1);
    }


    //遍歷連結串列
    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        for(Node cur = dummyHead.next ; cur != null ; cur = cur.next)
            sb.append(cur+" -->");
        sb.append("NULL");
        return sb.toString();
    }
}

 

測試方法

package cn.itcats.linkedlist;

public class Test {
    public static void main(String[] args) {
        LinkedList<Integer> linked = new LinkedList<>();
        for(int i = 0 ; i < 5 ; i++){
            linked.addFirst(i);
            System.out.println(linked);
        }
        linked.add(2,111);
        System.out.println(linked);

        linked.remove(3);
        System.out.println(linked);
    }
}

 

連結串列的時間複雜度分析

新增操作:

addLast(e)            O(n)

addFirst(e)            O(1)

add(index,e)          O(n)

 

刪除操作:

removeLast(e)            O(n)

removeFirst(e)            O(1)

add(index,e)                O(n)

 

修改操作        set(index,e)     O(n)

 

查詢操作

get(index)               O(n)

contains(e)             O(n)