Linked List 鏈表詳解

本文是stanford cs library中兩篇關於linked list的文章合體版

linked list basics

linked list problems

我覺得這兩篇講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單步調試,跟蹤指針變化




發佈了36 篇原創文章 · 獲贊 17 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章