本文是stanford cs library中兩篇關於linked list的文章合體版
我覺得這兩篇講linked list的文章寫的非常非常好,所以在博客裏自己寫一下,也算是溫習鞏固數據結構的知識了
本文代碼下載:http://download.csdn.net/detail/stevemarbo/4090566
本文結合一個實際的例子來解釋什麼是linked list,如何使用linked list
不過在解釋linked list之前,必須先要了解指針的相關知識,這裏先做一個簡單回顧一下指針
1.指針存儲了對另一個變量的引用。 指針的值是一個地址。
2.在C語言中, * 這個符號被稱爲解析符號,它用來取得指針所指向的那個變量的值,舉個例子
#include<stdio.h>
void main ()
{
int a=3;
int* p = &a;
printf("*p=%d\n",*p);
}
上面這個例子在printf打印函數中,*p就是把指針p指向的變量a的值取出來
我記得我在學習C語言指針的時候,一直搞不清楚 * 這個符號,一個原因就是 int* p=&a 在這行代碼中, int* p是申明瞭一個指針,這一行代碼也能夠拆成兩行來寫
int *p;
p=&a;
但是請注意,int* p和 int *p這兩種寫法是一樣的,都是申明瞭一個指向整形變量的指針,但是 int* p更好,因爲 int* 表示這是一個指向整形的指針,名字叫做p,而 int *p 的寫法容易讓人誤解爲指針的名字叫做 *p,我當時學C語言時在這個問題上糾結了很久
3. & 這個符號叫做取地址符,在上面的例子中, int *p = &a; 這行代碼就是把變量 a 的地址賦給指針 p
關於指針內容其實非常多,這裏只是一個簡單的回顧,想看更多關於指針的資料,我推薦大家看這篇文章, pointer and memory, 這篇文章是stanford大學的教授寫的,我看完以後,有一種豁然開朗的感覺
linked list是一種和數組有幾分相似的數據結構,我相信大家都非常瞭解數組,數組有3個很明顯的缺點:
1. 數組的長度是固定的。比如申明瞭一個長度爲100的整形數組 int a[100], 數組a的長度就是100,如果這時需要存儲200個數字,那麼a就用不了了
2. 數組造成存儲空間的浪費。如果這時數組a只存儲了 1個整數,那麼它的其他99個位置就相當於浪費了
3. 往數組裏插入元素的開銷是非常大的。比如要在數組的第一個元素的位置上插入1個整數,那就得把數組其他位置上的元素都向後移動一位
正因爲數組有以上的這些缺點,所以產生了linked list,linked list很好的克服了以上的三個缺點
首先,定義一個名字叫做node的結構體
struct node {
int data;
struct node* next;
};
這個結構體中包含一個整數,和一個指向結構體node的指針
接下來我們寫一個函數,生成一個linked list
struct node* BuildOneTwoThreeFourFive() {
struct node* head = NULL;
struct node* second = NULL;
struct node* third = NULL;
struct node* forth = NULL;
struct node* fifth = NULL;
head = malloc(sizeof(struct node));
second = malloc(sizeof(struct node));
third = malloc(sizeof(struct node));
forth = malloc(sizeof(struct node));
fifth = malloc(sizeof(struct node));
head->data = 1;
head->next = second;
second->data = 2;
second->next = third;
third->data = 3;
third->next = forth;
forth->data = 4;
forth->next = fifth;
fifth->data = 5;
fifth->next= NULL;
return head;
}
現在,這個linked list就會有5個元素,如果打印出這個linked list的data值,那就是 {1,2,3,4,5}
求linked list長度的函數
int Length(struct node* head) {
struct node* current = head;
int count = 0;
while(current != NULL) {
count++;
current = current->next;
}
return count;
}
往這個linked list裏添加一個節點,這個節點要在linked list的頭部,
void Push(struct node** headRef, int data) {
struct node* newNode = malloc(sizeof(struct node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
這個Push函數是一個比較難理解的地方,因爲它的參數列表有一個參數 struct node** headRef。
我們都知道, struct node* head表示的是一個指向struct node的指針,名字是head
那麼,struct node** headRef表示的就是一個指向struct node的指針的指針,名字是headRef
當然,這個地方爲什麼用指針的指針,還是很有講究的,至於爲什麼,可以參考本文開頭時提到的第一篇文章,由於篇幅有限,這裏不做解釋了
寫一個函數,函數有兩個參數,一個是linked list,另一個是一個整數,查詢在這個linked list裏有幾個元素的data值和整個整數相等
int Length(struct node* head) {
struct node* current = head;
int count = 0;
while(current != NULL) {
count++;
current = current->next;
}
return count;
}
取得linked list中的第N個元素的data值
int GetNth(struct node* head, int index) {
struct node* current = head;
int count = 0;
while(current != NULL) {
if(count == index) return (current->data);
count++;
current = current->next;
}
assert(0);
}
刪除這個linked list
void DeleteList(struct node** headRef) {
struct node* current = *headRef;
struct node* next;
while(current != NULL) {
next = current->next;
free(current);
current = next;
}
*headRef = NULL;
}
取出這個linked list裏頭部的那個節點的data值,並且銷燬第一個節點
int Pop(struct node** headRef) {
struct node* head;
int result;
head = *headRef;
assert(head != NULL);
result = head->data;
*headRef = head->next;
free(head);
return result;
}
在第n個節點的位置上插入一個節點
void InsertNth(struct node** headRef, int index, int data) {
if(index == 0) Push(headRef,data);
else {
struct node* current = *headRef;
int i;
for(i=0; i<index-1; i++) {
assert(current != NULL);
current = current->next;
}
assert(current != NULL);
Push(&(current->next),data);
}
}
假設一個linked list已經按照data的升序排列好了,這時往這個linked list添加一個節點,這個節點會被加入的正確的位置上
void SortedInsert(struct node** headRef, struct node* newNode) {
if(*headRef == NULL || (*headRef)->data >= newNode->data) {
newNode->next = *headRef;
*headRef = newNode;
}
else {
struct node* current = *headRef;
while(current->next != NULL && current->next->data<newNode->data) {
current = current->next;
}
newNode->next = current->next;
current->next = newNode;
}
}
linked list插入排序
void InsertSort(struct node** headRef) {
struct node* result = NULL;
struct node* current = *headRef;
struct node* next;
while(current != NULL) {
next = current->next;
SortedInsert(&result, current);
current = next;
}
*headRef = result;
}
把一個鏈表加入到另一個鏈表的尾部
void Append(struct node** aRef, struct node** bRef) {
struct node* current;
if(*aRef == NULL) {
*aRef = *bRef;
}
else {
current = *aRef;
while(current->next != NULL)
current = current->next;
current->next = *bRef;
}
*bRef = NULL;
}
把一個鏈表對半分,比如,鏈表是{2,3,5,7,11}, 分割後產生兩個鏈表,第一個{2,3,5}, 第二個{7,11}
void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef) {
int len = Length(source);
int i;
struct node* current = source;
if(len<2) {
*frontRef = source;
*backRef = NULL;
}
else {
int hopCount = (len-1)/2;
for(i=0; i<hopCount; i++)
current = current->next;
*frontRef = source;
*backRef = current->next;
current->next = NULL;
}
}
去除鏈表中的重複節點
void RemoveDuplicates(struct node* head) {
struct node* current = head;
if(current == NULL) return;
// compare current node with next node
while(current->next != NULL) {
if(current->data == current->next->data) {
struct node* nextNext = current->next->next;
free(current->next);
current->next = nextNext;
}
else {
current = current->next;
}
}
}
把第二個鏈表的第一個節點添加到第一個鏈表的頭節點上
void MoveNode(struct node** destRef, struct node** sourceRef) {
struct node* newNode = *sourceRef;
assert(newNode != NULL);
*sourceRef = newNode->next;
newNode->next = *destRef;
*destRef = newNode;
}
AlternatingSplit
每隔一個節點取出的合成一個linked list,比如,{a,b,c,d,e,f}在 AlternatingSplit()後生成兩個鏈表 {a,c,e} 和 {b,d,f}
void AlternatingSplit(struct node* source, struct node** aRef, struct node** bRef) {
struct node* a = NULL;
struct node* b = NULL;
struct node* current = source;
while(current != NULL) {
MoveNode(&a,¤t);
if(current!=NULL)
MoveNode(&b,¤t);
}
*aRef = a;
*bRef = b;
}
ShuffleMerge
這個函數的目的需要舉例說明,比如,兩個鏈表{1,2,3}和{7,13,1},兩個鏈表shufflemerge後的結構就是{1,7,2,13,3,1}
也就是分別取出每個從每個鏈表頭節點合併成一個鏈表
struct node* ShuffleMerge(struct node* a, struct node* b) {
struct node* result;
struct node* recur;
if(a == NULL) return b;
else if(b == NULL) return a;
else {
recur = ShuffleMerge(a->next, b->next);
result = a;
a->next = b;
b->next = recur;
return result;
}
}
SortedMerge
假設兩個鏈表都是按照升序遞增的,把兩個鏈表合併,並且合併後的鏈表也是按照升序排列的
struct node* SortedMerge(struct node* a, struct node* b) {
struct node dummy;
struct node* tail = &dummy;
dummy.next = NULL;
while(1) {
if(a == NULL) {
tail->next = b;
break;
}
else if (b == NULL) {
tail->next = a;
break;
}
if(a->data <= b->data) {
MoveNode(&(tail->next),&a);
}
else {
MoveNode(&(tail->next),&b);
}
tail = tail->next;
}
return dummy.next;
}
MergeSort
鏈表的歸併排序
void MergeSort(struct node** headRef) {
struct node* head = *headRef;
struct node* a;
struct node* b;
if((head==NULL) || (head->next)==NULL)
return;
FrontBackSplit(head,&a,&b);
MergeSort(&a);
MergeSort(&b);
*headRef = SortedMerge(a,b);
}
SortedIntersect
假設兩個鏈表是按照升序排列的,找出兩個鏈表相等的節點
struct node* SortedIntersect(struct node* a, struct node* b) {
struct node dummy;
struct node* tail = &dummy;
dummy.next = NULL;
while(a != NULL && b != NULL) {
if(a->data == b->data) {
Push((&tail->next),a->data);
tail = tail->next;
a = a->next;
b = b->next;
}
else if(a->data < b->data) {
a = a->next;
}
else {
b = b->next;
}
}
return dummy.next;
}
Reverse
鏈表的倒置
這個是非遞歸版本
void Reverse(struct node** headRef) {
struct node* result = NULL;
struct node* current = *headRef;
struct node* next;
while(current != NULL) {
next = current->next;
current->next = result;
result = current;
current = next;
}
*headRef = result;
}
下面這個是遞歸版本
void RecursiveReverse(struct node** headRef) {
struct node* first;
struct node* rest;
if(*headRef == NULL) return;
first = *headRef;
rest = first->next;
if(rest == NULL) return;
RecursiveReverse(&rest);
first->next->next = first;
first->next = NULL;
*headRef = rest;
}
倒置的操作比較複雜,需要畫圖去跟指針的變化
當然,我覺得最好的方法還是用gdb單步調試,跟蹤指針變化