通常将采用链式存储结构的线性表称为线性链表,简称链表。
从链接方式的角度看,链表可分为单链表、循环链表和双链表。
在顺序表中,用一组地址连续的存储单元来依次存放线性表的结点,因此结点的逻辑顺序和物理顺序是一致的。但链表则不然,链表是用一组任意的存储单元来存放线性表的结点,这组存储单元可以是连续的,也可以是非连续的,甚至是零散分布在内存的任何位置上。因此,链表中结点的逻辑顺序和物理顺序不一定相同。为了正确地表示结点间的逻辑关系,必须在存储线性表的每个数据元素值的同时,存储指示其后继结点的地址(或位置)信息,这两部分信息组成的存储映像叫做结点。链表正是通过每个结点的指针域将链表的n个结点按其逻辑顺序链接在一起。
链表之单链表
单链表(由于其每个结点的指针域只有一个next指针域)结点包括两个域,依次是:数据域(data)用来存储结点的值;指针域(next)用来存储数据元素的直接后继的地址(或位置)。单链表中每个结点的存储地址存放在其前驱结点的指针域中,由于线性表中的第一个结点无前驱,所以应设一个头指针指向第一个结点。由于线性表中的最后一个结点没有直接后继,因此指定单链表的最后一个结点的指针域为“空”(NULL)。
为了操作方便,可以在单链表的第一个结点之前附设一个头结点,头结点的数据域可以存储一些关于链表长度的附加信息,也可以什么都不存(一般都是什么也不存);而头结点的next指针域则被用来存储指向第一个结点的指针(即第一个结点的存储位置)。此时头指针就不再指向表中第一个结点,而是指向头结点。如果线性表为空,则头结点的next指针域为“空”,也就是NULL。
单链表定义如下
typedef int DataType;
typedef struct ListNode
{
DataType data;
struct ListNode *next;
} ListNode;
相关代码
无头单链表
链表之循环链表
循环链表(Circular Linked List)是一个首尾相连的链表。
循环单链表:将单链表中最后一个结点的next指针域由NULL改为指向单链表的头结点,就得到了单链形式的循环链表。同样还可以有多重链的循环链表。在循环单链表中,表中所有结点都被链在一个环上,为使得某些操作实现方便,在循环单链表中也可设置一个头结点。这样,空循环单链表仅由一个自成循环的头结点表示。
在循环单链表中附设尾指针有时比附设头指针操作变得更简单。如在用头指针表示的循环单链表中,找开始结点a1的时间复杂度是O(1),然而要找到终端结点an,则需要从头指针开始遍历整个链表,其时间复杂度是O(n)。如果用尾指针rear来表示循环单链表,则查找开始结点和终端结点都很方便,它们的存储位置分别是rear->next->next(带有头结点)和rear,显然,查找时间复杂度都是O(1)。因此,实用中多采用尾指针表示循环单链表。
链表之双向链表
循环单链表虽然能够实现从任一结点出发沿着链表能找到其前驱结点,但时间耗费是O(n)。如果希望从表中快速确定某一结点的前驱,另一解决方法就是在单链表中的每个结点里再添加一个指向其前驱的指针域prior。这样形成的链表中就有两条方向不同的链,称为双(向)链表(Double Linked List)。
与单链表类似,双链表也可增加头结点使双链表的某些运算变得更加方便。
同时双链表也可以有循环表,称为双向循环链表。
由于在双链表中既有前向链又有后向链,寻找任一结点的直接前驱结点与直接后继结点都变得非常方便。
设指针p指向双链表中的某一结点,则有下式成立:
p == p->next->prior
p->prior->next == p
双链表定义如下
typedef int DataType;
typedef struct ListDNode
{
DataType data;
struct ListDNode *prior;
struct ListDNode *next;
} ListDNode;