算法应用题中链表的一些常用方法和操作
快慢指针
在我现在所刷到的题目中,快慢指针有两个作用。
- 找中间结点
- 用来判环
找中间结点
不带头结点
typedef struct Lnode {
int data;
Lnode *next;
}Lnode ,*Linklist;
//不带头结点的版本
Lnode *Getmidnode(Linklist L)
{
Lnode *fast,*slow;
fast=L->next;//不带头结点fast要比slow多一个位置
slow=L;
while(fast->next!=NULL &&fast->next->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
}
//此时的slow便来到了中间结点的位置
}
带头结点
typedef struct Lnode {
int data;
Lnode *next;
}Lnode ,*Linklist;
//带头结点的版本
Lnode *Getmidnode(Linklist L)
{
Lnode *fast,*slow;
fast=L//带头结点起始点相同,都指向头结点开始
slow=L;
while(fast->next!=NULL &&fast->next->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
}
//此时的slow便来到了中间结点的位置
}
判环
链表中的环:单链表中的最后一个结点指向链表中的某个结点
原理:快指针会比慢指针先进入环中,fast每次走两步,slow每次走一步,且fast与slow的距离小于环的长度,所以两个指针会相遇,且slow所走的路程不会超过环的长度。
- 找环并返回环入口点(王道数据结构链表T15)
Linklist Findloop(Linklist L)
{
//带不带头结点,只需把快慢指针同时指向第一个结点即可
Lnode *fast,*slow;
fast=slow=L;
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
if(fast==slow) break;//相遇了跳出
}
if(fast==NULL||fast->next==NULL) return NULL;
Lnode *p=L,*q=slow;//指向开始点和相遇点
while(p!=q)
{
p=p->next;
q=q->next;
}
return p;//p此时为环的入口点
}
后半段逆置
本质上是头插法的运用,首先把中间结点当成一个头结点,然后更新头结点
void Reverse_Back(Linklist L)
{
Lnode *slow;
//带头结点的
slow=Getmidnode(L);//这里至少为了方便书写 建议全部写在一个函数里
//slow此时为中间结点
Lnode *p,*q,*r;
q=slow->next;
p=slow;
p->next=NULL;
while(p!=NULL)
{
r=q->next;
q->next=p->next;
p->next=q;//更新头结点
q=r;
}
//这样就实现了后半段的翻转了
}
将后半段翻转完以后,可以在O(n)的复杂度下实现很多操作
比如
- 找孪生结点的最大和(王道数据结构链表应用题T16)
- 将链表变为形式为\((a_1,a_n,a_2,a_{n-1},......)\)的形式(王道数据结构链表应用题T20)
将链表变为形式为\((a_1,a_n,a_2,a_{n-1},......)\)的形式
void ChangeList(Linklist L)
{
Lnode *fast,*slow;
fast=L;//带头结点起始点相同,都指向头结点开始
slow=L;
while(fast->next!=NULL &&fast->next->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
}
//-----------------------找中间结点
Lnode *p,*q,*r;
q=slow->next;
p=slow;
p->next=NULL;
while(p!=NULL)
{
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
//----------------后半段逆置
//此时p还在中间结点
Lnode *s,*k,*tmp;
s=L->next;//s指向前半段的第一个结点
k=p->next;//k指向后半段第一个结点
p->next=NULL;//当成新的头结点
while(!k)
{
tmp=k->next;//方便k更新位置
k->next=s->next;
s->next=k;
s=k->next;//更新位置
k=tmp;//更新位置
}
}
"延时指针"
这是我自己取的名字哈,指的是两个指针p和q,等p到达了某个位置以后,q在开始移动,也是可以在O(n)的复杂度下解决
- 找倒数第k个结点(王道数据结构链表T17)
int Search_K(Linklist L,int k)
{
int cnt=0;
Lnode *p,*q;
p=L->next;
q=L->next;
while(p!=NULL)
{
if(cnt<k) cnt++;
else q=q->next;
p=p->next;
}
if(cnt<k) return 0;//链表不够长无法到达k
else cout<<q->data;
return 1;
}
找共同后缀的起点原理其实也是相似的,先让较长的链表遍历(长-短)个结点,然后两条链表再同时开始遍历,也是在O(n)的复杂度下实现
- 寻找共同后缀起始位置(王道数据结构链表T18)
typedef struct Lnode {
char data;
Lnode *next;
}Lnode ,*Linklist;
int getlen(Linklist L)
{
Lnode *p=L;
int len=0;
while(p->next!=NULL){
len++;
p=p->next;
}
return len;
}
Lnode Searchcommon(Linklist str1,Linklist str2)
{
Lnode *p=str1,*q=str2;
int len1=getlen(str1);
int len2=getlen(str2);
for(int i=0;i<len1-len2;i++) //谁长就先遍历到短的位置的起点
p=p->next;
for(int i=0;i<len2-len1;i++)
q=q->next;
//找到开始位置后,同时向后移动
while(p!=q)
{
p=p->next;
q=q->next;
}
return p;
}
循环位移
这个方法其实就是先让单链表变成循环单链表,然后在某个位置断开即可,循环左移和循环右移是同理的,假设链表长度为k(不带头结点),则循环右移k位时,只需在n-k个位置断开,循环左移k位时,在第k个位置断开
- 循环右移(王道数据结构链表T14)
Lnode *Converse(Lnode *L,int k)
{
int n=1;//链表长度
Lnode *p=L;
while(p->next!=NULL) p=p->next,n++;
p->next=L;//形成环,此时p在尾结点
for(int i=1;i<=n-k;i++) p=p->next;//此时p在第n-k个结点
L=p->next;//设置新的表头;
p->next=NULL;
return L;
}
归并法
归并法,能在O(n)的复杂度内实现一些操作,比如找两个链表的交集(有序),慢慢积累例子总结吧。
- 找两个递增有序链表的交集并且保存在一个链表中(王道数据结构链表T9)
void GetCommon(Linklist &A,Linklist &B)
{ //带头结点
Lnode *pa,*pb;//工作指针
Lnode *pc;//总是指向链表A的尾结点
Lnode *tmp;//临时指针用来释放结点
pa=A->next;
pb=B->next;
pc=A;
while(pa&&pb)
{
if(pa->data==pb->data)
{
pc->next=pa;
pc=pa;//更新pc因为总是指向最后一个结点
pa=pa->next;
tmp=pb;
pb=pb->next;
free(tmp);
}else if(pa->data < pb->data)
{
tmp=pa;
pa=pa->next;
free(tmp);
}else{
tmp=pb;
pb=pb->next;
free(tmp);
}
}
//把没释放的释放完
while(pa)
{
tmp=pa;
pa=pa->next;
free(tmp);
}
while(pb)
{
tmp=pb;
pb=pb->next;
free(tmp);
}
free(B);//释放b的头结点
pc->next=NULL;
}
去重
去重可以分为有序去重和无序去重,如果链表是有序,去重只需要遍历一遍链表即可做到,如果是无序的则需要空间换时间,开一个标记数组来。
- 链表有序去重(王道数据结构链表T7)
- 链表无序去重(王道数据结构链表T19)
有序去重
void Deletesame(Linklist &L)
{
//去除重复元素 链表递增有序
Lnode *p;//工作指针
Lnode *tmp;//临时指针
p=L->next;
if(p==NULL) return ;
while(p->next!=NULL) //每遍历一个结点检查后面的是否与之重复
{
if(p->data != p->next->data)
{
p=p->next;
}else{
tmp=p->next;
p->next=tmp->next;
free(tmp);
}
}
}
无序去重
void Deletesame(Linklist &L,int n)//n是题目给的链表元素最大数值
{
Lnode *p=L->next;//工作指针
Lnode *r;//当作后继指针
int *s=(int *)malloc(sizeof(int)*(n+1));
for(int i=0;i<n+1;i++) *(s+i)=0;//初始化标记数组
//若首次出现则标记为1,再次出现就删掉
while(p->next!=NULL)
{
r=p->next;
int m=r->data;
//还是和无序同理,看下一个结点是不是出现过
if(*(s+m)==0)
{
*(s+m)=1;
p=p->next;
}else{
p->next=r->next;
free(r);
}
}
free(s);//把数组释放掉
}
posted on 2024-09-18 15:22 swj2529411658 阅读(33) 评论(0) 收藏 举报
浙公网安备 33010602011771号