本文是stanford cs library中两篇关于linked list的文章合体版
我觉得这两篇讲linked list的文章写的非常非常好,所以在博客里自己写一下,也算是温习巩固数据结构的知识了
本文代码下载:
本文结合一个实际的例子来解释什么是linked list,如何使用linked list
不过在解释linked list之前,必须先要了解指针的相关知识,这里先做一个简单回顾一下指针
1.指针存储了对另一个变量的引用。 指针的值是一个地址。
2.在C语言中, * 这个符号被称为解析符号,它用来取得指针所指向的那个变量的值,举个例子
-
#include
-
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和 int *p这两种写法是一样的,都是申明了一个指向整形变量的指针,但是 int* p更好,因为 int* 表示这是一个指向整形的指针,名字叫做p,而 int *p 的写法容易让人误解为指针的名字叫做 *p,我当时学C语言时在这个问题上纠结了很久
3. & 这个符号叫做取地址符,在上面的例子中, int *p = &a; 这行代码就是把变量 a 的地址赋给指针 p
关于指针内容其实非常多,这里只是一个简单的回顾,想看更多关于指针的资料,我推荐大家看这篇文章,, 这篇文章是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
-
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->datadata) {
-
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
-
current = current->next;
-
-
*frontRef = source;
-
*backRef = current->next;
-
current->next = NULL;
-
}
-
}
去除链表中的重复节点
-
void RemoveDuplicates(struct node* head) {
-
struct node* current = head;
-
if(current == NULL) return;
-
-
-
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单步调试,跟踪指针变化