線性表(陣列、連結串列、佇列、棧)詳細總結

小超說發表於2020-06-26

線性表是一種十分基礎且重要的資料結構,它主要包括以下內容:

  • 陣列
  • 連結串列
  • 佇列

接下來,我將對這四種資料結構做一個詳細的總結,其中對連結串列實現了十幾種常見的操作。希望對你有所幫助。

1.陣列

陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空間,來儲存一組具有相同型別的資料。
注意點:①.陣列是一種線性表;②.連續的記憶體空間和相同型別的資料
由於第二個性質,陣列支援 “隨機訪問”,根據下表隨機訪問的時間複雜度為O(1);但與此同時卻使得在陣列中刪除,插入資料需要大量的資料搬移工作。

低效的“插入”和“刪除”

插入操作

假如陣列的長度為n,我們需要將一個資料插入到陣列的第k個位置,我們需要將第k~n位元素都順序地往後挪動一位。
最好情況時間複雜度為O(1),此時對應著在陣列末尾插入元素;
最壞情況時間複雜度為O(n),此時對應著在陣列開頭插入元素;
平均情況時間複雜度為O(n),因為我們在每個位置插入元素的概率相同,故(1+2+3+……+n)/n=O(n);
但是根據我們的需求,有一個特定的場景。如果陣列的資料是有序的,那麼我們在插入時就一定要那麼做;但是如果陣列中儲存的資料並沒有任何規律,陣列只是被當成一個儲存資料的集合,我們可以有一個取巧的方法:
直接將第k個元素搬移到陣列元素的最後,把新的資料直接放入第k個位置即可(是不是很簡單啊),這時插入元素的複雜度為O(1)。

刪除操作

和插入操作一樣,為了保證記憶體的連續性,刪除操作也需要搬移資料。
最好情況時間複雜度為O(1),此時對應著刪除陣列末尾的元素;
最壞情況時間複雜度為O(n),此時對應著刪除陣列開頭的元素;
平均情況時間複雜度為O(n),因為我們刪除每個位置的元素的概率相同,故(1+2+3+……+n)/n=O(n);
當然,在某些特殊情況下,我們並不一定非要進行複雜的刪除操作。我們只是將需要刪除的資料記錄,並且假裝它以經被刪除了。直到陣列沒有更多空間儲存資料時,我們再觸發一次真正的刪除操作即可。

這其實就和生活中的垃圾桶類似,垃圾並沒有消失,只是被“標記”成了垃圾,而直到垃圾桶塞滿時,才會清理垃圾桶。

警惕陣列訪問越界

在 C 語言中,只要不是訪問受限的記憶體,所有的記憶體空間都是可以自由訪問的。如果疏忽會造成嚴重的後果。當然,Java會自動檢測。

2.連結串列

  • 連結串列結點表示
  • 列印單連結串列
  • 單連結串列根據索引插入結點
  • 獲取單連結串列的長度
  • 列印單連結串列的長度
  • 單連結串列刪除指定索引的結點
  • 單連結串列實現元素查詢,返回是否存在布林值
  • 單連結串列刪除指定索引的後續節點
  • 單連結串列反轉
  • 遞迴地進行單連結串列反轉
  • 檢測連結串列中是否含有環
  • 刪除倒數第k個結點
  • 求中間節點
  • 有序連結串列合併

連結串列結點表示

public class Node{
    int data;
    Node Next;
}

列印單連結串列

public class Method {
    //列印單連結串列
    public static void PrintNode (Node list){
        for(Node x=list;x!=null;x=x.Next)
        System.out.print(x.data+" ");
        System.out.println();
    }

單連結串列根據索引插入結點

    public static Node insert(Node first,int index,Node a){
        Node ret = new Node();
        ret.Next=first;//建立一個虛擬頭節點
        Node p=ret;
        while((index--)!=0) p=p.Next;
        //完成節點的插入操作
        a.Next=p.Next;
        p.Next=a;
        //返回真正的連結串列頭節點地址
        return ret.Next;//函式返回連結串列的頭節點
    }

獲取單連結串列的長度

    public static int GetLength(Node first){
        int n=0;
        for(Node x=first;x!=null;x=x.Next){
            ++n;
        }
        return n;
    }

列印單連結串列的長度

    public static void PrintLength(Node first){
        System.out.println("Length : "+GetLength(first));
    }

單連結串列刪除指定索引的結點

    public static Node Delete(Node first,int index){
        if(index<0||index>=GetLength(first)) return first;
        else{
        Node ret=new Node();
        ret.Next=first;
        Node p=ret;
        while((index--)!=0) p=p.Next;
        //完成節點的刪除操作
        p.Next=p.Next.Next;
        return ret.Next;
        }
    }

單連結串列實現元素查詢,返回是否存在布林值

    public static boolean Find(Node first,int key){
        for(Node x=first;x!=null;x=x.Next){
            if(x.data==key) return true;
        }
        return false;
    }

單連結串列刪除指定索引的後續節點

    public static void RemoveAfter(Node first,int index){
        Node ret=new Node();
        ret.Next=first;
        Node p=ret;
        while((index--)!=0) p=p.Next;
        p.Next.Next=null;

    }

單連結串列反轉

    public static Node  reverse(Node list){
        Node curr=list,pre=null;
        while(curr!=null){
            Node next=curr.Next;
            curr.Next=pre;
            pre=curr;
            curr=next;
        }
        return pre;
    }

遞迴地進行單連結串列反轉

    public static Node reverseRecursively(Node head){
        if(head==null||head.Next==null) return head;//遞迴的終止條件,返回反轉後連結串列的頭節點
        Node reversedListHead=reverseRecursively(head.Next);
        head.Next.Next=head;//改變這兩個結點之間的指向順序
        head.Next=null;
        return  reversedListHead;//返回反轉後的連結串列頭節點
    }

檢測連結串列中是否含有環

    public static boolean checkCircle(Node list){
        if(list==null) return false;

        Node fast=list.Next;
        Node slow=list;

        while(fast!=null&&fast.Next!=null){
            fast=fast.Next.Next;
            slow=slow.Next;

            if(slow==fast) return true;
        }
        return false;
    }

刪除倒數第k個結點

    public static Node deleteLastKth(Node list,int k){
        //利用兩個指標,fast和slow,它們之間差k個位置,判斷如果fast.Nest=null,也就代表著slow這個位置就是倒數第k個結點
        Node fast=list;
        int i=1;
        while(fast!=null&&i<k){
            fast=fast.Next;
            ++i;
        }

        if(fast==null) return list;

        Node slow=list;
        Node prev=null;
        while(fast.Next!=null){
            fast=fast.Next;
            prev=slow;
            slow=slow.Next;
        }

        if(prev==null){
            list=list.Next;
        }else{
            prev.Next=prev.Next.Next;
        }
        return list;
    }

求中間節點

    public static Node findMiddleNode(Node list){
        if(list==null) return null;

        Node fast=list;
        Node slow=list;

        while(fast!=null&&fast.Next!=null){
            fast=fast.Next.Next;
            slow=slow.Next;
        }

        return slow;
    }

有序連結串列合併

    public static Node mergeTwoLists(Node l1,Node l2){
        Node soldier=new Node();
        Node p=soldier;

        while(l1!=null&&l2!=null){
            if(l1.data<l2.data){
                p.Next=l1;
                l1=l2.Next;
            }
            else{
                p.Next=l2;
                l2=l2.Next;
            }
            p=p.Next;
        }

        if(l1!=null){ p.Next=l1;}
        if(l2!=null){ p.Next=l2;}
        return soldier.Next;
    }

3.棧

  • 順序棧
  • 鏈式棧

1.基於陣列實現的順序棧

  • 建構函式
  • 入棧操作
  • 出棧操作
  • 列印操作
package Stack;

//基於陣列實現的順序棧
public class ArrayStack {
    private int[] items;
    private int count;//棧中的元素個數
    private int n;//棧的大小
  //初始化陣列,申請一個大小為n的陣列空間
public ArrayStack(int n){
    this.items=new int[n];
    this.n=n;
    this.count=0;
}

//入棧操作
public boolean push(int item){
    //陣列空間不足,直接返回false,入棧失敗
    if(count==n) return false;
    //將data放在下標為count的位置,並且count加一
    items[count]=item;
    ++count;
    return true;
}

//出棧操作
public int pop(){
    //棧為空,直接返回-1;
    if(count==0) return -1;
    //返回下標為count-1的陣列元素,並且棧中元素個數count減一
    int tmp=items[count-1];
    --count;
    return tmp;
}
public void PrintStack(){
    for(int i=count-1;i>=0;--i){
        System.out.print(items[i]+" ");
    }
    System.out.println();
    }
}

2.基於連結串列的鏈式棧

  • 入棧操作
  • 出棧操作
  • 列印操作
package Stack;

public class LinkedListStack {
    private Node top;//棧頂(最近新增的元素)
    private int N;//元素數量
    private class Node{
        //定義了結點的巢狀類
        int data;
        Node Next;
    }
    public boolean isEmpty(){
        return top==null;
    }
    public int size(){
        return N;
    }

    public void push(int data){
        /*Node newNode=new Node();
        //判斷是否為空棧
        //if(top==null) 
        newNode=top;
        top.data=data;
        top.Next=newNode;
        N++;*/
        Node newNode=top;
        top=new Node();
        top.data=data;
        top.Next=newNode;
        ++N;
    }
    public int pop(){
        //從棧頂刪除元素
        if(top==null) return -1;//這裡用-1表示棧中沒有資料
        int data=top.data;
        top=top.Next;
        N--;
        return data;
    }
    public void PrintStack(){
        for(Node x=top;x!=null;x=x.Next){
            System.out.print(x.data+" ");
        }
        System.out.println();
    }

}

4.普通佇列

  • 基於陣列實現的普通佇列
  • 基於連結串列實現的佇列
  • 基於陣列實現的迴圈佇列

1.基於陣列實現的普通佇列

  • 建構函式
  • 入隊操作
  • 出隊操作
  • 列印佇列中的元素
package Queue;

//用陣列實現佇列
public class ArrayQueue {
    //陣列:items,陣列大小:n
    private int[] items;
    private int n=0;
    //head表示隊頭下標,tail表示隊尾下標
    private int head=0;
    private int tail=0;

    //申請一個大小為capacity的陣列
    public ArrayQueue(int capacity){
        items=new int[capacity];
        n=capacity;
    }

    //入隊(一),基礎版
    public boolean enqueue(int item){
        //如果tail==n,表示佇列末尾已經沒有空間了
        if(tail==n) return false;
        items[tail]=item;
        ++tail;
        return true;
    }

    //入隊(二),改進版
    public boolean enqueue1(int item){
        //如果tail==n,表示佇列末尾已經沒有空間了
        if(tail==n){
            //tail==n&&head==0,表示整個佇列都佔滿了
            if(head==0) return false;
            //資料搬移
            for(int i=head;i<tail;++i){
                items[i-head]=items[i];
            }
            //搬移完成後重新更新head和tail
            tail=tail-head;
            head=0;
        }
        items[tail]=item;
        ++tail;
        return true;
    }

    //出隊
    public int dequeue(){
        //如果head==tail,表示佇列為空
        if(head==tail) return -1;//這裡用-1表示佇列為空
        int ret=items[head];
        ++head;
        return ret;
    }

    //列印佇列
    public void PrintQueue(){
        for(int i=head;i<tail;++i){
            System.out.print(items[i]+" ");
        }
        System.out.println();
    }

}

2.基於連結串列實現的佇列

  • 建構函式
  • 入隊操作
  • 出隊操作
  • 列印佇列中的元素
package Queue;

//基於連結串列實現的佇列
public class LinkedListQueue {

    private Node head;//指向最早新增的結點的連結
    private Node tail;//指向最近新增的結點的連結
    private int N;//佇列中的元素數量
    private class Node{
        //定義了結點的巢狀類
        int data;
        Node Next;
    }
    public boolean isEmpty(){
        return head==null;
    }
    public int size(){ return N;}

    //向表尾新增元素,即入隊
    public void enqueue(int data){
        Node newNode=tail;
        tail=new Node();
        tail.data=data;
        tail.Next=null;
        if(isEmpty()) head=tail;
        else newNode.Next=tail;
        ++N;
    }
    public int dequeue(){
        //從表頭刪除元素
        int data=head.data;
        head=head.Next;
        if(isEmpty()) tail=null;
        N--;
        return data;
    }

    //列印輸出佇列元素
    public void PrintQueue(){
        for(Node x=head;x!=null;x=x.Next){
            System.out.print(x.data+" ");
        }
        System.out.println();
    }
}

3.基於陣列實現的迴圈佇列

  • 建構函式
  • 入隊操作
  • 出隊操作
  • 列印佇列中的元素
package Queue;

public class CircularQueue {
    //陣列items,陣列大小n
    private int[] items;
    private int n=0;
    //head表示隊頭下標,tail表示隊尾下標
    private int head=0;
    private int tail=0;

    //申請一個大小為capacity的陣列
    public CircularQueue(int capacity){
        items = new int[capacity];
        n=capacity;
    }

    //入隊
    public boolean enqueue(int item){
        //佇列滿了
        if((tail+1)%n==head) return false;
        items[tail]=item;
        tail=(tail+1)%n;//實現計數的迴圈
        return true;
    }

    //出隊
    public int dequeue(){
        //如果head==tail,表示佇列為空
        if(head==tail) return -1;//以-1表示佇列為空
        int ret=items[head];
        head=(head+1)%n;
        return ret;
    }

    //列印佇列
    public void PrintQueue(){
        if(n==0) return;
        for(int i=head;i%n!=tail;i=(i+1)%n){
            System.out.print(items[i]+" ");
        }
        System.out.println();
    }
}

說明

文章程式碼太多,我本來是希望分成幾篇文章寫的,但是由於一些原因,最終放在了一起,略顯臃腫。程式碼都是經過測試用例測試過的,應該不會有錯誤。

如果體驗不太好,可以移步我的Github,裡面觀感較好。

題外話:對於演算法初學者,推薦一本十分 nice 的書籍 《演算法第四版》,裡面各種配圖十分詳細。如果你需要電子版檔案,後臺回覆演算法4即可獲得下載連結。後臺回覆 演算法01 送你一份 演算法與資料結構思維導圖。最後,希望我們一起進步,一起成長!

相關文章