本文是stanford cs library中两篇关于linked list的文章合体版
我觉得这两篇讲linked list的文章写的非常非常好,所以在博客里自己写一下,也算是温习巩固数据结构的知识了
本文代码下载:http://download.youkuaiyun.com/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单步调试,跟踪指针变化