将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,
这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。
循环链表解决了一个很麻烦的问题。如何从中间的一个结点出发,访问到链表的全部结点。
为了使空链表与非空链表处理一致,我们通常设一个头结点,
当然,这并不是说,循环链表一定要头结点,这需要注意。
- 循环链表带有头结点的空链表。
如图3-13-3所示:
图3-13-3
- 对于非空的循环链表就。
如图3-13-4所示:
图3-13-4
其实循环链表和单链表的主要差异就在于循环的判断条件上,
- 原来是判断p->next是否为空,
- 现在则是p->next不等于头结点,则循环未结束。
在单链表中,我们有了头结点时,我们可以用O(1)的时间访问第一个结点,但对于要访问到最后一个结点,却需要O(n)时间,因为我们需要将单链表全部扫描一遍。
有没有可能用O(1)的时间由链表指针访问到最后一个结点呢?当然可以。
不过我们需要改造一下这个循环链表,
不用头指针,而是用指向终端结点的尾指针来表示循环链表(如图3-13-5所示),
此时查找开始结点和终端结点都很方便了。图3-13-5
从上图中可以看到,终端结点用尾指针rear指示,
- 则查找终端结点是O(1),
- 而开始首结点,其实就是rear->next->next,其时间复杂也为O(1)。
举个程序的例子,要将两个循环链表合并成一个表时,有了尾指针就非常简单了。比如下面的这两个循环链表,它们的尾指针分别是rearA和rearB,如图3-13-6所示。
图3-13-6
要想把它们合并,只需要如下的操作即可,如图3-13-7所示。
图3-13-7
/* 保存A表的头结点,即① */
p = rearA->next;
/*将本是指向B表的第一个结点首结点(不是头结点) */
rearA->next = rearB->next->next;
/* 赋值给reaA->next,即② */
q = rearB->next;//保存B表的头结点
/* 将原A表的头结点赋值给rearB->next,即③ */
rearB->next = p;
//rearB->next = rearA->next;
//前后的->next有不同的意义,前式是(B表的尾结点的)指针域,后式是(A表尾结点指向的头结点的)地址,因为赋值符号前式的值是被后式的值覆盖,所以前式可以视为空地址的指针域,后式则不能
/* 释放q */
free(q);