链表是一种最简单的数据结构,当我们在使用数组存储数据的时候,频繁的插入和删除会损耗大量的性能,而链表正是一种适合频繁插入删除操作的线性数据结构。
有关链表的详细介绍可以看这里,通俗的来说,链表就是由一些节点构成,每个节点有一个指针,这个指针保存着下一个节点的位置。因此,链表就是由指针将这些物理上没有逻辑关系的节点连接了起来,构成一个有关系的线性表。
我们先来看“单链表”,在C语言当中,具有封装能力的数据类型是结构体。因此,我们用一个结构体来表示一个节点。定义如下:
1 struct Node{ 2 ElementType Element; 3 struct Node * Next; 4 };
在这里,我们假设元素的类型为 int ,于是这样定义:
1 typedef int ElementType;
对于一个链表,它的基本操作有:判断链表是否为空、当前元素是否为最后一个、查找一个元素、插入一个元素、删除一个元素、删除整个表等。所以我们有如下声明:
1 int IsEmpty(List L); 2 int IsLast(Position P); 3 Position Find(ElementType X, List L); 4 void Delete(ElementType X, List L); 5 void Insert(ElementType X, Position P); 6 void DeleteList(List L);
对了,忘了类型定义了。使用类型定义可以让代码看起来更简洁,也更容易理解。Position 和 List 类型都是“指向Node的指针”,不过我们用 List 来表示头结点,Position 来表示任意节点。这样,程序一目了然。
1 typedef struct Node * PtrToNode; 2 typedef PtrToNode List; 3 typedef PtrToNode Position;
接下来就是实现了,我们先从判断链表是否为空开始。我们传入头指针,只需要判断头结点的 Next 指针是否为 NULL 即可。若 Next 为 NULL,说明链表只有头结点,为空。
1 int 2 IsEmpty(List L) { 3 return L->Next == NULL; 4 }
判断当前元素是否为最后一个元素的方式也与此类似,因为当一个结点处于末尾时,它的 Next 指针为 NULL。
1 int 2 IsLast(Position P) { 3 return P->Next == NULL; 4 }
然后我把接下来的几个函数的实现全部放出来,都是很容易理解的。
1 Position 2 Find(ElementType X, List L) { 3 Position P; 4 P = L->Next; 5 while (P != NULL && P->Element != X) { 6 P = P-Next; 7 } 8 return P; 9 } 10 11 void 12 Insert(ElementType X, Position P) { 13 Position temp; 14 temp = malloc(sizeof(struct Node)); 15 16 temp->Element = X; 17 temp->Next = P->Next; 18 P->Next = temp; 19 } 20 21 void 22 DeleteList(List L) { 23 Position P, temp; 24 P = L->Next; 25 L-Next = NULL; 26 while (P != NULL) { 27 temp = P->Next; 28 free(P); 29 P = temp; 30 } 31 } 32 33 void 34 Delete(ElementType X, List L) { 35 Position P, temp; 36 P = FindPrevious(X,L); //找到X的前一个元素 37 if (!IsLast(P,L)) { 38 temp = P->Next; 39 P-Next = temp->Next; 40 free(temp); 41 } 42 }
我们发现上面有个 FindPrevious 函数用来找到当前元素的上一个元素,这个函数的出现是我们之前没预料到的。然而在删除操作当中我们发现需要这样一个函数,所以我们也声明这样一个函数,先不去考虑它的具体实现(这就是抽象)。事实上它的实现也是很简单的,不过我们会想到,既然可以用指针保存下一个结点,那为什么不用一个指针保存上一个结点呢?于是,双向链表就这么诞生了。
1 struct Node{ 2 ElementType Element; 3 struct Node * Next; 4 struct Node * Prev; 5 };
现在删除操作可以这样:
1 P->Prev->Next = P->Next; //P是指向要删除结点的指针 2 p->Next->Prev = P->Prev; 3 free(P);
当链表的头尾相连时,这个链表就成了循环链表。在C#当中有个 LinkedList 类实现了 双向循环链表 ,文档和源码可以看这里和这里。真是壮哉我大微软,文档资料应有尽有。
写到这里,应该算写完了吧。还是没有达到下笔如有神的状态,我想应该是和分享的对象有关系。这文章要给哪一类读者看呢?其实是给我自己看的,那就这样吧,这一系列文章将纯粹是我为了总结而作的记录,没有读者对象。坑已经给自己挖了,后面还有很多较深的知识点,我会填坑的。
下一篇应该是“栈”,而且我会讲解一个栈的实例。
(完)