数据结构个人笔记 第8课 循环链表
循环链表
只要将链表的两头相连,使其成为了一个环状链表,通常称之为循环链表
需要注意的是,虽然循环链表成环状,但本质上还是链表,因此在循环链表中,依然能够找到头指针和首元结点等。循环链表和普通链表相比,唯一的不同就是循环链表首尾相连,其他都完全一样
循环链表的建立与展示
在建立循环链表时,就比建立普通链表多一个尾指针指向第一个节点。
person * initPerson(int num){
person * head = (person *)malloc(sizeof(person));
head -> data = 1;
head -> next = NULL;
person * temp = head;
for(int i = 2;i<=num;i++){
person * body = (person *)malloc(sizeof(person));
body -> next =NULL;
body -> data = i;
temp -> next = body;
temp = temp -> next;
}
temp -> next = head;
return head;
}
在display循环链表时,不能用之前的尾指针指向NULL来判定链表结束了,需判定尾指针指向的是头节点,才算是结束
void display(person * head){
person * temp = head;
while(temp){
printf("%d ",temp -> data);
temp = temp -> next;
if(temp== head){
printf("\n");
break;
}
}
}
约瑟夫环
也即,已知n个人(分别编号1,2,3,。。。。,n表示)围坐在一张圆桌旁,从编号为k开始顺时针报数,数到m的那个人出列;他的下一个人又从1开始,还是顺时针开始报数,数到m的那个人又出列,依次重复下去,知道圆桌剩余一个人。
完整代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct node{
struct node * next;
int data ;
}person;
person * initPerson(int num){
person * head = (person *)malloc(sizeof(person));
head -> data = 1;
head -> next = NULL;
person * temp = head;
for(int i = 2;i<=num;i++){
person * body = (person *)malloc(sizeof(person));
body -> next =NULL;
body -> data = i;
temp -> next = body;
temp = temp -> next;
}
temp -> next = head;
return head;
}
void display(person * head){
person * temp = head;
while(temp){
printf("%d ",temp -> data);
temp = temp -> next;
if(temp== head){
printf("\n");
break;
}
}
}
void findKiller(person * head,int k, int m){
person * temp = head;
person * findK = head;
while (findK -> data != k){
findK = findK -> next;
}
temp = findK;
while(findK -> next != findK){
for(int i = 1;i<m;i++){
temp = findK;
findK = findK -> next;
}
temp -> next = findK -> next;
printf("出列人的编号为%d\n",findK -> data);
free(findK);
findK = temp -> next;
}
printf("最后一个出列人的编号为%d\n",findK -> data);
free(findK);
}
int main(){
printf("请输入一共多少人\n");
int num;
scanf("%d",&num);
person * head = initPerson(num);
display(head);
int k;
scanf("%d",&k);
printf("从第%d个人开始报数:\n",k);
int m;
scanf("%d",&m);
printf("数数到%d的人出列:\n",m);
findKiller(head,k,m);
return 0;
}
判断有环链表
也即判断一个链表中是否有环
- 这种问题常用快慢指针来解决,也即慢指针每走一步,则快指针走两步,一直走下去。如果链表中有环,快慢指针一定会在环中碰到,如果碰到就输出true,没有则false
- 从这个问题扩展出,环的入口在哪,环内有几个节点,链表一共有几个节点
在有环链表的输出中,因为要遍历整个链表,所以我们可以用遍历的方法检测所有点的地址是否相同来判断是否有环,也可以用哈希表的方法来判断。
如果使用快慢指针来解决问题,慢指针每向前走一步,快指针走两步,因此当慢指针进入环后,每一次操作都会使得快指针到慢指针的距离缩短一步,这样继续下去会使得两者之间的距离逐渐缩小,直到相遇,又因为在同一个环内,所以两者之间的距离不会大于环的长度,因此到两者相遇为止,慢指针一定还没走完一圈
从上面可知,当快慢指针相遇时,慢指针一定还没走完链表,假设快指针已经在环内循环了n圈(1<=n)。假设慢指针走了s步,则快指针走了2s步,又由于快指针走过的步数为s+nr,则可以得出下面这个等式:
2s = s+n*r —> s = n * r
如果假设整个链表的长度为L,入口和相遇点的距离是x,起点到入口有点的距离是a,则有:
a + x = s= n* r;
a + x = (n-1) * r + r = (n - 1) * r + (L - a)
由环的长度 = 链表总长度 - 起点到入口点的距离求出:
a = (n - 1) * + (L - a - x)
上面的所有问题都可以嵌套这些公式,于是总结代码为:
#include <stdio.h>
#include <stdlib.h>
typedef struct line{
struct line * next;
int data;
};
line * initLine(int num ,int data){
line * head = (line *)malloc(sizeof(line));
head -> data = 1;
head -> next = NULL;
line * temp = head;
for(int i = 2 ; i <= data ;i++){
line * body = (line *)malloc(sizeof(line));
body -> data = i;
body -> next =NULL;
temp -> next = body;
temp = temp -> next;
}
line * round = head;
for(int i = 1;i < num;i++){
round = round -> next;
}
temp -> next = round;
return head;
}
void display(line * head){
line * temp = head;
int i = 0;
while(temp){
printf("%d ",temp -> data);
temp = temp -> next;
i++;
line * round = head;
for(int j = 1;j< i ;j++){
if(temp == round){
printf("\n");
return;
}
round = round -> next;
}
}
}
int checkRound(line * head){
line * quick = head;
line * slow = head;
int i = 0;
while(slow != NULL && quick -> next != NULL){
i++;
slow = slow -> next;
quick = quick -> next -> next;
if (slow == quick){
printf("%d\n",i);
break;
}
}
line * ptr = head;
while(slow != NULL && ptr != NULL){
slow = slow -> next;
ptr = ptr -> next;
if(slow == ptr){
return ptr -> data;
}
}
}
int main(){
line * head = initLine(28,30);
display(head);
printf("在链表中存在环,环的入口为:%d\n",checkRound(head));
return 0;
}
这里只解决了判断入口的问题,其他的都可以以此类推
参考资料:判断链表有环