線性表是一種十分基礎且重要的資料結構,它主要包括以下內容:
- 陣列
- 連結串列
- 佇列
- 棧
接下來,我將對這四種資料結構做一個詳細的總結,其中對連結串列實現了十幾種常見的操作。希望對你有所幫助。
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 送你一份 演算法與資料結構思維導圖。最後,希望我們一起進步,一起成長!