算法应用题中链表的一些常用方法和操作

快慢指针

在我现在所刷到的题目中,快慢指针有两个作用。

  1. 找中间结点
  2. 用来判环

找中间结点

不带头结点

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)    收藏  举报

导航