前面我们学习了单向链表,现在介绍单向循环链表,单向循环链表是单链表的一种改进,若将单链表的首尾节点相连,便构成单向循环链表结构,如下图:
对于一个循环链表来说,其首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,可以选择开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。指向整个列表的指针可以被称作访问指针。
循环链表中第一个节点之前就是最后一个节点,反之亦然。循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。对于新加入的节点应该是在第一个节点之前还是最后一个节点之后可以根据实际要求灵活处理,区别不大。当然,如果只会在最后插入数据(或者只会在之前),处理也是很容易的。
循环链表的应用
一、Joseph问题(约瑟夫环):
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人找到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
约瑟夫环用数学问题来描述就是:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。如何用循环链表来求解Josephu问题?
下面我们用单向循环链表来模拟这个问题:
- #include <stdio.h>
- #include <stdlib.h>
- typedef int data_t;
- typedef struct node_t
- {
- data_t data;
- struct node_t *next;
- }linknode_t,*linklist;
- linklist CreateList(int n)
- {
- int i;
- linklist p,head,tail;
- head = NULL;
- for(i = 1;i <= n;i++)
- {
- p = (linklist)malloc(sizeof(linklist));
- if(p == NULL)
- {
- printf("malloc fails!\n");
- }
- p->data = i;
- if(head == NULL)
- {
- head = p;
- tail = head;
- }
- else
- {
- tail->next = p;
- }
- tail = p;
- }
- tail->next = head;
- return head;
- }
- void Joseph(int n,int k,int m)
- {
- int i;
- linklist p,r;
- p = CreateList(n);
- for(i = 1;i < k;i++) //从第K个人开始数
- {
- p = p->next;
- }
- while(p->next != p)
- {
- for(i = 1;i <= m-2;i++) //数到第m个人,去自杀
- p = p->next;
- r = p->next;
- p->next = r->next;
- printf("%d->",r->data);
- free(r);
- p = p->next;//从下一个人继续数
- }
- printf("%d\n",p->data);
- }
- int main()
- {
- Joseph(41,1,3);
- return 0;
- }
- fs@ubuntu:~/qiang/linklist$ ./list1
- 3->6->9->12->15->18->21->24->27->30->33->36->39->1->5->10->14->19->23->28->32->37->41->7->13->20->26->34->40->8->17->29->38->11->25->2->22->4->35->16->31
二、判断一个链表是不是循环链表(如何判定这个链表当中是否包含有环路)
解决方法:
判断是否是循环链表时,也设置两个指针,慢指针和快指针,让快指针比慢指针每次移动快两次。如果快指针追赶上慢指针,则为循环链表,否则不是循环链表,如果快指针或者慢指针指向NULL,则不是循环链表。
代码如下:
- #include <stdio.h>
- #include <stdlib.h>
- typedef int data_t;
- typedef struct node_t
- {
- data_t data;
- struct node_t *next;
- }linknode_t,*linklist;
- linklist CreateList(int n)
- {
- int i;
- linklist p,head,tail;
- head = NULL;
- for(i = 1;i <= n;i++)
- {
- p = (linklist)malloc(sizeof(linklist));
- if(p == NULL)
- {
- printf("malloc fails!\n");
- }
- p->data = i;
- if(head == NULL)
- {
- head = p;
- tail = head;
- }
- else
- {
- tail->next = p;
- }
- tail = p;
- }
- tail->next = head;
- return head;
- }
- int JudgeIsloop(linklist list)
- {
- int flag = 0;
- linknode_t *slow,*fast;
- if(list == NULL)
- return 0;
- slow = list;
- fast = list->next;
- while(slow)
- {
- if(fast == NULL || fast->next == NULL)//走到头了
- return 0;
- else if(fast == slow || fast->next == slow)//二者相遇,因为fast走的快,如果fast->next指向slow,也是循环的
- {
- flag = 1;
- return 1;
- }
- else
- {
- slow = slow->next;//慢指针走一步
- fast = fast->next->next;//快指针走两步
- }
- }
- return 0;
- }
- int main()
- {
- int i;
- int flag = 0;
- linklist list;
- list = CreateList(10);
- JudgeIsloop(list);
- if(flag = 0)
- printf("The list is not a looplist!\n");
- else
- {
- printf("The list is a looplist!\n");//循环链表则打印出来
- for(i = 0;i < 10;i++)
- {
- printf("%d->",list->data);
- list = list->next;
- }
- printf("%d\n",list->data);
- }
- return 0;
- }
- fs@ubuntu:~/qiang/linklist$ ./list2
- The list is a looplist!
- 1->2->3->4->5->6->7->8->9->10->1
- fs@ubuntu:~/qiang/linklist$