习题来自B站up:白话拆解数据结构
今日题目如下:
(1) 判断带头结点的循环双链表是否对称
(2)判断单链表是否有环,有就返回环的入口点,否则返回Null
题1
首先是结构体,比单链表多了一个前驱指针域。
typedef struct DLnode{
int data;
DLnode *next;
DLnode *prior;
}DLnode,*DLinklist;
所以建表的过程也需要对前驱进行操作。
DLinklist list_insertbytail(DLinklist &L) {
DLnode *s;
int x;
// 创建头节点
L = (DLnode*)malloc(sizeof(DLnode));
L->next = L; // 头节点的next指针指向自身,形成循环
L->prior = L; // 头节点的prior指针也指向自身,形成循环
DLnode *r = L; // 尾指针r初始化为头节点
cin >> x;
while (x != 9999) {
s = (DLnode*)malloc(sizeof(DLnode));
s->data = x;
// 插入新节点到链表尾部
s->next = L; // 新节点的next指针指向头节点
s->prior = r; // 新节点的prior指针指向当前的尾节点
r->next = s; // 当前尾节点的next指针指向新节点
L->prior = s; // 头节点的prior指向新节点,即更新链表的尾部
r = s; // 更新尾指针r为新节点
cin >> x;
}
return L;
}
判断是否对称,可以设置两个指针,分别指向表头和表尾,然后分别往前和往后走比较值,如果最后遇到了(单数),或者差一个才遇到(偶数),说明这个表是对称的。而且因为是循环双链表,找表头和表尾都是O(1)的复杂度。
// 判断带头结点的循环双链表是否对称
bool duichen(DLinklist L){
if (L == NULL || L->next == L) { // 处理空链表或只有一个节点的情况
return true;
}
DLnode *p,*q;
p=L->next;//第一个结点
q=L->prior;//第二个结点
while(p!=q&&p->next!=q){ // 偶数和奇数
if(p->data==q->data){
p=p->next;
q=q->prior;
}
else return false;
}
return true;
}
实践一下:
单数情形:
偶数情形:
不对称情形:
完整代码如下:
#include <iostream>
#include <cstdio>
#include <ctime>
using namespace std;
// 单链表结构体定义
typedef struct DLnode{
int data;
DLnode *next;
DLnode *prior;
}DLnode,*DLinklist;
DLinklist list_insertbytail(DLinklist &L) {
DLnode *s;
int x;
// 创建头节点
L = (DLnode*)malloc(sizeof(DLnode));
L->next = L; // 头节点的next指针指向自身,形成循环
L->prior = L; // 头节点的prior指针也指向自身,形成循环
DLnode *r = L; // 尾指针r初始化为头节点
cin >> x;
while (x != 9999) {
s = (DLnode*)malloc(sizeof(DLnode));
s->data = x;
// 插入新节点到链表尾部
s->next = L; // 新节点的next指针指向头节点
s->prior = r; // 新节点的prior指针指向当前的尾节点
r->next = s; // 当前尾节点的next指针指向新节点
L->prior = s; // 头节点的prior指向新节点,即更新链表的尾部
r = s; // 更新尾指针r为新节点
cin >> x;
}
return L;
}
// 判断带头结点的循环双链表是否对称
bool duichen(DLinklist L){
if (L == NULL || L->next == L) { // 处理空链表或只有一个节点的情况
return true;
}
DLnode *p,*q;
p=L->next;//第一个结点
q=L->prior;//第二个结点
while(p!=q&&p->next!=q){
if(p->data==q->data){
p=p->next;
q=q->prior;
}
else return false;
}
return true;
}
int main(){
DLinklist L;
list_insertbytail(L);
if(duichen(L)){
printf("this list is duichen de\n");
}
else printf("zhe shen me po biao\n");
return 0;
}
题2
首先要造一个有环的表,就是在尾插法那里更新一下尾指针就行了。
Linklist list_insertbytail(Linklist &L,int m){
Lnode *s;
int x;
L = (Lnode*)malloc(sizeof(Lnode));
L->next = NULL;
Lnode *r = L;
cin >> x;
while(x!=9999){
s = (Lnode*)malloc(sizeof(Lnode));
s->data=x;
s->next=NULL;
r->next = s;
r=r->next;
cin >> x;
}
Lnode *p=L;
for(int i=1;i<m;i++,p=p->next); // 连到第m个结点处
s->next=p;
printf("huan de ru kou:%d \n",p->data);
return L;
}
然后就是找环了,采用快慢指针法,所谓快慢指针,就是指一个指针一次走一步,另一个指针一次走两步,如果存在环,快指针一定可以追上慢指针。就好比操场跑圈,配速比你快的一定会在某个时间点超你一圈拉爆你!
有环之后,然后就是要返回环的入口,这个有点绕,我们结合例子推导一下,下面是一个带环的单链表:
环为4->2->2->2->4,我们设L到环入口(4)处的距离为a,设在环内某处相遇,设相遇的地方距离入口为b,然后设从相遇点往后一直走直至到达环入口处的距离为c。注意b+c即为环的长度,a+b+c为整个表的长度。
我们设c+b=L,慢指针走的距离为a+b+nL(nL为在内环绕了多少圈),快指针走的距离为a+b+mL;由于快慢指针一定会相遇,所以有2(a+b+nL)=a+b+mL,简化得a+b=(m-2n)L;由于c+b=L,所以b=L- c,代入a+b=(m-2n)L得到a=c+(m-2n-1)L,我们设m-2n-1=k,那么a=c+kL,这个公式表明头节点指针到达入口点所走的距离a与相遇点指针到达入口点所走的距离 c 之间相差一个或多个环的长度 L。
也就是说,如果我们将两个指针分别从链表的头节点和相遇点出发,并以相同的速度前进(每次一步),它们最终会在环的入口点相遇。
代入上图,假设在2处相遇:
此时a=3,b=1,c=3,b+c=4刚好等于环的长度,数一下他们两个相遇,慢指针走了0圈,快指针走了一圈,所以n=0,m=1,刚好也符合a+b=(m-2n)L这个公式,那么a=c+(m-2n-1)L=c+0L,说明从b开始走c=3步能到达环的入口,图片刚好也符合,而且a走c=3步也到达了环的入口。
结合上述思想,如下(代码直接p!=q就行了,多方便)
Linklist find_huan(Linklist L){
if(!L||!L->next) return NULL;
Lnode *p,*q;
p=L,q=L;
while (q && q->next) {
p = p->next; // 慢指针每次走一步
q = q->next->next; // 快指针每次走两步
if (p == q) { // 如果快慢指针相遇,则说明存在环
p = L;
while (p != q) { // 两个指针相遇处即为环的入口
p = p->next;
q = q->next;
}
return p; // 返回环的入口点
}
}
return NULL;
}
测试一下:
由于我们在建表的适合,打印了一下入口的值,所以我们只要比对find_huan的值和这个值是否一致就可以了。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
// 单链表结构体定义
typedef struct Lnode{
int data;
Lnode *next;
}Lnode,*Linklist;
Linklist list_insertbytail(Linklist &L,int m){
Lnode *s;
int x;
L = (Lnode*)malloc(sizeof(Lnode));
L->next = NULL;
Lnode *r = L;
cin >> x;
while(x!=9999){
s = (Lnode*)malloc(sizeof(Lnode));
s->data=x;
s->next=NULL;
r->next = s;
r=r->next;
cin >> x;
}
Lnode *p=L;
for(int i=0;i<m;i++,p=p->next);
s->next=p;
printf("huan de ru kou:%d \n",s->next->data);
return L;
}
Linklist find_huan(Linklist L){
if(!L||!L->next) return NULL;
Lnode *p,*q;
p=L,q=L;
while (q && q->next) {
p = p->next; // 慢指针每次走一步
q = q->next->next; // 快指针每次走两步
if (p == q) { // 如果快慢指针相遇,则说明存在环
p = L;
while (p != q) { // 两个指针相遇处即为环的入口
p = p->next;
q = q->next;
}
return p; // 返回环的入口点
}
}
return NULL;
}
int main(){
Linklist L;
list_insertbytail(L,4);
Lnode *p=find_huan(L);
printf("ru kou:%d\n",p->data);
return 0;
}