資料結構-連結串列(一)
連結串列和陣列作為演算法中的兩個基本資料結構,在程式設計過程中經常用到。儘管兩種結構都可以用來儲存一系列的資料,但又各有各的特點。
陣列的優勢,在於可以方便的遍歷查詢需要的資料。在查詢陣列指定位置(如查詢陣列中的第4個數據)的操作中,只需要進行1次操作即可,時間複雜度為O(1)。但是,這種時間上的便利性,是因為陣列在記憶體中佔用了連續的空間,在進行類似的查詢或者遍歷時,本質是指標在記憶體中的定向偏移。然而,當需要對陣列成員進行新增和刪除的操作時,陣列內完成這類操作的時間複雜度則變成了O(n)。
連結串列的特性,使其在某些操作上比陣列更加高效。例如當進行插入和刪除操作時,連結串列操作的時間複雜度僅為O(1)。另外,因為連結串列在記憶體中不是連續儲存的,所以可以充分利用記憶體中的碎片空間。除此之外,連結串列還是很多演算法的基礎,最常見的雜湊表就是基於連結串列來實現的。基於以上原因,我們可以看到,連結串列在程式設計過程中是非常重要的。本文總結了我們在學習連結串列的過程中碰到的問題和體會。
接下來,我們將對連結串列進行介紹,用C語言分別實現:連結串列的初始化、建立、元素的插入和刪除、連結串列的遍歷、元素的查詢、連結串列的刪除、連結串列的逆序以及判斷連結串列是否有環等這些常用操作。並附上在Visual Studio 2013中可以執行的程式碼供學習者參考。
說到連結串列,可能有些人還對其概念不是很瞭解。我們可以將一條連結串列想象成環環相扣的結點,就如平常所見到的鎖鏈一樣。連結串列內包含很多結點(當然也可以包含零個結點)。其中每個結點的資料空間一般會包含一個數據結構(用於存放各種型別的資料)以及一個指標,該指標一般稱為next,用來指向下一個結點的位置。由於下一個結點也是連結串列型別,所以next的指標也要定義為連結串列型別。例如以下語句即定義了連結串列的結構型別。
定義連結串列結構體型別:
//連結串列的型別
struct Linklist
{
int val;
Linklist *next;
};
連結串列初始化:
Linklist* Init() { Linklist *HeadNode = (Linklist*)malloc(sizeof(Linklist));//建立節點 if (HeadNode==NULL) { printf("沒有記憶體可以開闢"); return HeadNode; } HeadNode->val = 0; HeadNode->next = NULL; return HeadNode; }
這裡引申一下,此處例程中返回的連結串列指標為該連結串列的頭結點,相對應的還有一個頭指標的概念。頭指標內只有指標的元素,並沒有資料元素,但頭結點除了指標還有資料。
頭指標就是連結串列的名字,僅僅是個指標而已。頭結點是為了操作的統一與方便而設立的,放在第一個有效元素結點(首元結點)之前,其資料域一般無意義(當然有些情況下也可存放連結串列的長度、用做監視哨等等)。一般情況下見到的連結串列的指標多為頭指標,但最近在一個程式設計師程式設計網站leetcode中發現,題目中所給的連結串列一般是首元結點作為第一個元素,而不是頭指標。
連結串列建立
建立連結串列需要將既定資料按照連結串列的結構進行儲存,本文以一種最簡單的方式來演示:使用陣列對連結串列賦值。將原來在連續空間存放的陣列資料,放置在不連續的連結串列空間中,使用指標進行連結。
連結串列建立的步驟一般使用給定的頭指標以及需要初始化的資料序列作為輸入引數,本文使用陣列作為輸入資料序列。在下面的例程中,先將首元結點使用陣列第一個元素初始化,再在首元結點之後建立新的連結串列結點賦值陣列內餘下的資料。具體實現如下:
void Creat_Linklist(Linklist *HeadNode,int *Indata,int Data_num)
{
Linklist *Current = (Linklist *)HeadNode;
int i = 0;
for (i = 0; i < Data_num; i++)
{
Current->val = Indata[i];
if (i<Data_num-1)//由於每次要開闢新的節點,條件限制是為了減少費節點
{
Current->next = (Linklist *)malloc(sizeof(Linklist));//開闢一塊區域
Current = Current->next;
}
Current->next = NULL;
}
}
顯示連結串列
//顯示連結串列
void Show_Linklist(Linklist *HeadNode)
{
Linklist *Current = HeadNode;
while (Current)
{
printf("元素為%d", Current->val);
Current = Current->next;
}
}
插入連結串列節點
連結串列建立完之後,下面我們將介紹如何向連結串列內插入結點。一般新增結點可以分為兩類:一類是在連結串列尾部插入;另一類為在中間插入。
連結串列結尾新增結點的步驟就是新建一個連結串列結點,將其連結到當前連結串列尾指標。
在中間結點插入結點的步驟稍微複雜一些,其中也包含兩種情況,分別是在指定結點前插入和指定結點後插入。其操作原理一樣,本文只對指定位置後插入結點進行介紹。指定結點前插入結點留給大家嘗試。
假設一個連結串列記憶體在幾個幾點A1,A2,A3,A4….,當根據要求需要在指定位置之後(比如A2結點)插入一個新結點時。首先我們需要新建立一個結點NodeToInsert,然後將新結點的next指向A3,並且將A2的next指標指向新建立的結點NodeToInsert,切記操作順序不要改變。如果操作順序變換一下,先將A2的next指向了新建立的結點,那麼我們就丟失了A3的定址方式。因此,在將A2的next指向其他任何地方之前,請務必將A3的地址存在NodeToInsert或者某個新建節點內。
//插入連結串列節點
bool Insert_List(Linklist *HeadNode, int Indata_dex, int data)
{
Linklist *Current = (Linklist *)HeadNode;
int i = 1;
while (Current&&i < Indata_dex-1)
{
Current = Current->next;
i+=1;
}
Linklist *Insert = (Linklist *)malloc(sizeof(Linklist));//建立節點
if (Insert == NULL)
{
printf("記憶體不足");
return false;
}
Insert->val = data;
Insert->next = Current->next;//將原本位置的地址,放在插入元素的後面
Current->next = Insert;//
return true;
}
刪除連結串列節點
對應於插入連結串列結點,連結串列的基本操作中同樣也有刪除連結串列結點。刪除結點包括刪除指定位置的結點和指定元素的結點。其基本原理都是先鎖定待刪除的結點的位置,然後將該結點的後置結點連結到前置結點的next指標處。這樣中間這個結點即我們要刪除的結點就從原來的連結串列中脫離開來。相對於原來的連結串列,即刪除了該結點。
//刪除連結串列節點
bool Delect_Link_list(Linklist * HeadNode, int index, int *DataToDel)
{
Linklist*Current = (Linklist*)HeadNode;
int i = 1;
while (Current&&i < index-1)
{
Current = Current->next;
i++;
}
Linklist* Delete = (Linklist*)malloc(sizeof(Linklist));
Delete = Current->next;
*DataToDel = Current->next->val;
printf("Delect is %d", *DataToDel);
Current->next = Current->next->next;
free(Delete);
return true;
}
獲取連結串列的長度
int Length_List(Linklist* HeadNode)
{
Linklist *Current = (Linklist*)HeadNode;
int i = 0;
while (Current)
{
i++;
Current = Current->next;
}
return i;
}
獲取連結串列的元素位置
//獲取連結串列的元素
int Locate_val(Linklist* HeadNode, int Data_locate)
{
Linklist*Current = (Linklist*)HeadNode;
int i = 1;
while (Current)
{
if (Current->val == Data_locate)
{
return i;
}
Current = Current->next;
i++;
}
return -1;//沒有該值
}
銷燬連結串列
//連結串列置空,銷燬連結串列
bool Destory_List(Linklist*HeadNode)
{
Linklist*Current = (Linklist*)HeadNode;
Linklist *pNext;
while (Current)
{
pNext = Current->next;
free(Current);
Current = pNext;
}
HeadNode->next = NULL;
return true;
}
判斷連結串列是否有環
//連結串列判斷是否有環
bool isListLoop(Linklist*HeadNode)
{
Linklist*slow, *fast;
fast = slow = HeadNode;
while (fast&&slow)
{
slow = slow->next;
if (fast->next)
{
fast = fast->next->next;
}
else
{
fast = fast->next;
}
if (slow==fast)
{
return true;
}
}
return false;
}