2.3线性表的链式表示课后习题

本文详细介绍了多种链表操作的算法实现,包括删除指定值结点、带头结点链表的反向输出、删除最小值结点、链表逆置、排序、删除指定范围结点、查找两个链表的公共结点、合并排序链表、判断链表对称性、链接循环链表、删除循环链表最小元素、定位循环链表环、判断链表是否存在环等。这些算法涉及链表的遍历、比较、插入、删除和排序等基本操作,时间复杂度从O(1)到O(n^2)不等,体现了链表操作的多样性和复杂性。
摘要由CSDN通过智能技术生成

01.设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点。

void Del_X_3(Linklist &L,ElemType x){
//递归实现在单链表L中删除值为x的结点
	LNode *P;   //p指向待删除结点
	if(L==NULL)  //递归出口
		return;
	if(L->data==x){  //若L所指结点的值为x
		p=L;   //删除*L,并让L指向下一结点
		L=L->next;
		free(p);
		Del_X_3(L,x);   //递归调用
	}
	else   //若L所指结点的值不为x
		Del_X_3(L->next,x);   //递归调用
}

算法需要借助一个递归工作栈,深度为O(n),时间复杂度为O(n)。

02.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
算法思想:用p从头到尾扫描单链表,pre指向*p结点的前驱。若p所指结点的值为x,则删除,并让p移向下一个结点,否则让pre、p指针同步后移一个结点。

void Del_X_1(Linklist &L,ElemType x){
//L为带头结点的单链表,本算法删除L中所有值为x的结点
	LNode *p=L->next,*pre=L,*q;   //置p和pre的初始值
	while(p!=NULL){
		if(p->data==x){
			q=p;   //q指向该结点
			p=p->next;   
			pre->next=p;   //删除*q结点
			free(q);   //释放*q结点的空间
		}
		else{    //否则,pre和p同步后移
			pre=p;
			p=p->next;
		}
	}
}

03.设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

void R_Print(LinkList L){
//从头到尾输出单链表L中每个结点的值
	if(L->next!=NULL){
		R_Print(L->next);   //递归
	}
	if(L!=NULL)
		print(L->data);   //输出函数
}
void R_Ignore_Head(LinkList L){
	if(L->next!=NULL)
		R_Print(L->next);
}

04.试编写在带头结点的单链表工中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
算法思想:用p从头至尾扫描单链表,pre指向*p结点的前驱,用minp保存值最小的结点指针(初值为p),minpre指向minp结点的前驱。一边扫描,一边比较,若p->data小于minp->data,则将p、pre分别赋值给minp、minpre。当p扫描完毕时,minp指向最小值结点,minpre指向最小值结点的前驱结点,再将minp所指结点删除即可。

LinkList Delete_Min(LinkList &L){
//L是带头结点的单链表,本算法删除其最小值结点
	LNode *pre=L,*p=pre->next;   //p为工作指针,pre指向其前驱
	LNode *minpre=pre,*minp=p;   //保存最小值结点及其前驱
	while(p!=NULL){
		if(p->data<minp->data){
			minp=p;   //找到比之前找到的最小值结点更小的结点
			minpre=pre;
		}
		pre=p;   //继续扫描下一个结点
		p=p->next;
	}
	minpre->next=minp->next;   //删除最小值结点
	free(minp);
	return L;	
}

05.试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)。
算法思想:将头结点摘下,然后从第一结点开始,依次插入到头结点的后边(头插法建立单链表),直到最后一个结点为止,这样就实现了链表的逆置

LinkList Reverse_1(LinkList L){
//L是带头结点的单链表,本算法将L就地逆置
	LNode *p,*r;   //p为工作指针,r为p的后继,以防断链
	p=L->next;   //从第一个元素结点开始
	L->next=NULL;   //先将头结点L的next域置为NULL
	while(p!=NULL){   //依次将元素结点摘下
	r=p->next;   //暂存p的后继
	p->next=L->next;   //将p结点插入到头结点之后
	L->next=p;
	p=r;
	}
	return L;
}

06.有一个带头结点的单链表L,设计一个算法使其元素递增有序。
算法思想:采用直接插入排序算法的思想,先构成只含一个数据结点的有序单链表,然后依次扫描单链表中剩下的结点*p(直至p==NULL为止),在有序表中通过比较查找插入p的前驱结点pre,然后将p插入到pre之后。该算法的时间复杂度为O(n)

void Sort(LinkList &L){
//本算法实现将单链表L的结点重排,使其递增有序
	LNode *p=L->next,*pre;
	LNode *r=p->next;   //r保持*p后继结点指针,以保证不断链
	p->next=NULL;   //构造只含一个数据结点的有序表
	p=r;
	while(p!=NULL){
		r=p->next;   //保存*p的后继结点指针
		pre=L;
		while(pre->next!=NULL&&pre->next->data < p->data)
			pre=pre->next;   //在有序表中查找插入*p的前驱结点*pre
		p->next=pre->next;   //将*p插入到*pre之后
		pre->next=p;
		p=r;   //扫描原单链表中剩下的结点
	}
}

07.设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)。
算法思想:因为链表是无序的,所以只能逐个结点进行检查,执行删除。

void RangeDelete(LinkList &L,int min,int max){
	LNode *pr=L,*p=L->link;   //p是检测指针,pr是其前驱
	while(p!=NULL)
		if(p->data > min&&p->data < max){   //寻找到被删结点,删除
			pr->link=p->link;
			free(p);
			p=pr->link;
		}
		else{        //否则继续寻找被删结点
			pr=p;
			p=p->link;
		}
}

08.给定两个单链表,编写算法找出两个链表的公共结点。
算法思想:每个单链表结点只有一个next域,因此从第一个公共结点开始,之后它们所有的节点都是重合的,不可能再出现分叉,拓扑形状看起来像Y。
先分别遍历两个链表得到它们的长度,并求出两个长度之差。在长的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。此时,该方法的时间复杂度为O(len1+len2)。

LinkList Search_lst_Common(LinkList L1,LinkList L2){
//本算法实现在线性的时间内找到两个单链表的第一个公共结点
	int len1=Length(L1),len2=Length(L2);   //计算两个链表的表长
	LinkList longList,shortList;    //分别指向表长较长和较短的链表
	if(len1>len2){
		longList=L1->next;shortList=L2->next;   //不是很理解,L->next是什么呢?
		dist=len1-len2;
	}
	else{
		longList=L2->next;shortList=L1->next;
		dis=len2-len1;
	}
	while(dist--)    //表长的链表先遍历到第dist个结点,然后开始同步
		longList=longList->next;
	while(longList!=NULL){   //同步寻找公共结点
		if(longList==shortList)   //找到第一个公共结点
			return longList;
		else{   //继续寻找
			longList=longList->next;
			shortList=shortList->next;
		}
	}
	return NULL;
}

09.给定一个带表头结点的单链表,设head为头指针,结点结构为(data,next),data为整型元素,next为指针,试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求:不允许使用数组作为辅助空间)。
算法思想:对链表进行遍历,在每次遍历中找出整个链表的最小值元素,输出并释放节点所占空间;再查找次小值元素,输出并释放空间,如此下去,直至链表为空,最后释放头结点所占存储空间,该算法的时间复杂度为O(n方)。

void Min_Delete(LinkList &head){
//head是带头结点的单链表的头指针,本算法按递增顺序输出单链表中的数据元素
	while(head->next!=NULL){   //循环到只剩头结点
		LNode *pre=head;   //pre为元素最小值结点的前驱结点的指针
		LNode *p=pre->next;   //p为工作指针
		while(p->next!=NULL){
			if(p->next->data < pre->next->data)
				pre=p;   //记住当前最小值结点的前驱
			p=p->next;
		}
		print(pre->next->data);   //输出元素最小值结点的数据
		u=pre->next;   //删除元素值最小的结点,释放结点空间
		pre->next=u->next;
		free(u);
	}
	free(head);   //释放头结点
}

10 . 将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变。
算法思想:设置一个访问序号变量(初值为0),每访问一个结点序号自动加1,然后根据序号的奇偶性将节点插入到A表或B表中。重复以上操作直到表尾。

LinkList DisCreat_1(LinkList &A){
//将表A中结点按序号的奇偶性分解到表A或表B中
	int i=0;   //i记录表A中结点的序号
	LinkList B=(LinkList)malloc(sizeof(LNode));   //创建B表表头
	B->next=NULL;    //B表的初始化
	LNode *ra=A,*rb=B,*p;   //ra和rb将分别指向将创建的A表和B表的尾结点
	p=A->next;    //p为链表工作指针,指向待分解的结点
	A->next=NULL;   //置空新的A表
	while(p!=NULL){
		i++;   //序号加1
		if(i%2==0){   //处理序号为偶数的链表结点
			rb->next=p;   //在B表尾插入新结点
			rb=p;   //rb指向新的尾结点
		}
		else{      //处理原序号为奇数的结点
			ra->next=p;   //在A表尾插入新结点
			ra=p;
		}
		p=p->next;   //将p恢复为指向新的待处理结点
	}
	ra->next=NULL;
	rb->next=NULL;
	return B;
}

11.设C={a, b1, a2, b2,.…, an, bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A= {a1, a2,…, an},B= {bn…, b2, b1}。
算法思想:对于B表的建立不采用尾插法,而采用头插法。

LinkList DisCreat_2(LinkList &A){
	LinkList B=(LinkList)malloc(sizeof(LNode));  //创建B表表头,分配空间
	B->next=NULL;   //B表的初始化
	LNode *p=A->next,*q;   //p为工作指针
	LNode *ra=A;   //ra始终指向A的尾结点
	while(p!=NULL){
		ra->next=p;ra=p;   //将*p链到A的表尾
		p=p->next;
		if(p!=NULL){
			q=p->next;   //头插后,*p将断链,因此用q记忆*p的后继
			p->next=B->next;   //将*p插入到B的前端
			B->next=p;
			p=q;
		}
	}
	ra->next=NULL;   //A尾结点的next域置空
	return B;
}

12.在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如(7,10,10, 21,30,42,42,42,51,70)将变为(7,10,21,30,42,51,70)。
算法思想:由于是有序表,所有相同值域的结点都是相邻的。用p扫描递增单链表L,若*p结点的值域等于其后继结点的值域,则删除后者,否则p移向下一个结点。

void Del_Same(LinkList &L){
//L是递增有序的单链表,本算法删除表中数值相同的元素
	LNode *p=L->next,*q;   //p为扫描工作指针
	if(p==NULL)
		return;
	while(p->next!=NULL){   
		q=p->next;   //q指向*p的后继结点
		if(p->data=q->data){   //找到重复值的结点
			p->next=q->next;   //释放*q结点
			free(q);    //释放相同元素值的结点
		}
		else
			p=p->next;
	}
}

13.假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。
算法思想:两个链表已经按元素值递增次序排序,将其合并时,均从第一个结点起进行比较,将小的结点链入链表中,同时后移工作指针,新链表的建立应该采用头插法。比较结束后,可能会有一个链表非空,此时采用头插法将剩下的结点依次插入新链表中即可。

void MergeList(LinkList &La,LinkList &Lb){
//合并两个递增有序链表(带头结点),并使合并后的链表递减排列
	LNode *r,*pa=La->next,*pb=Lb->next;  //分别为La和Lb的工作指针
	La->next=NULL;   //La作为结果链表的头指针,先将结果链表初始化为空
	while(pa&&pb){   //当两个链表均不为空时,循环
		if(pa->data <= pb->data){
			r=pa->next;   //r暂存pa的后继结点指针
			pa->next=La->next;
			La->next=pa;   //将pa结点链于结果表中,同时逆置(头插法)
			pa=r;   //恢复pa为当前待比较结点
		}
		else{
			r=pb->next;   //r暂存pb的后继结点指针
			pb->next=La->next;   
			La->next=pb;   //将pb结点链于结果表中,同时逆置
			pb=r;   //恢复pb为当前待比较结点
		}
	}
	if(pa)
		pb=pa;    //通常情况下会剩一个链表非空,处理剩下的部分
	while(pb){   //处理剩下的一个非空链表
		r=pb->next;   //依次插入到La中(头插法)
		pb->next=La->next;
		La->next=pb;
		pb=r;
	}
	free(Lb);
}

14.设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
算法思想:表A、B都有序,可以从第一个元素起一次比较A,B两表的元素,若元素值不等,则值小的指针往后移,若元素值相等,则创建一个值等于两结点的元素值的新结点,使用尾插法插入到新的链表中,并将两个原表指针后移一位,直到其中一个链表遍历到表尾。

void Get_Common(LinkList A,LinkList B){
//本算法产生单链表A和B的公共元素的单链表C
	LNode *p=A->next,*q=B->next,*r,*s;
	LinkList C=(LinkList)malloc(sizeof(LNode));   //建立表C
	r=C;   //r始终指向C的尾结点
	while(p!=NULL&&q!=NULL){   //循环跳出条件
		if(p->data < q->data)
			p=p->next;   //若A的当前元素较小,后移指针
		else if(p->data > q->data)
			q=q->next;   //若B的当前元素较小,后移指针
		else{   //找到公共元素结点
			s=(LNode*)malloc(sizeof(LNode));
			s->data=p->data;   //复制产生结点*s
			r->next=s;   //将*s链接到C上(尾插法)
			r=s; 
			p=p->next;   //表A和表B继续向后扫描
			q=q->next;
		}
	}
	r->next=NULL;   //置C尾结点指针为空
}

15.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。
算法思想:采用归并的思想,设置两个工作指针pa和pb,对两个链表进行归并并扫描,只有同时出现在两集合中的元素才链接到结果表中且仅保留一个,其他的结点全部释放。当一个链表遍历完毕后,释放另一个表中剩下的全部结点。

LinkList Union(LinkList &la,LinkList &lb){
	pa=la->next;   //设工作指针分别为pa和pb
	pb=lb->next;
	pc=la;   //结果表中当前合并结点的前驱指针
	while(pa&&pb){
		if(pa-data==pb-data){   //交集并入结果表中
			pc->next=pa;   //A中结点链接到结果表
			pc=pa;
			pa=pa->next;
			u=pb;   //B中结点释放
			pb=pb->next;
			free(u);
		}
		else if(pa->data <pb->data){   //若A中当前结点值小于B中当前结点值
			u=pa;
			pa=pa->next;   //后移指针
			free(u);   //释放A中当前结点
		}
		else{   //若B中当前结点值小于A中当前结点值
			u=pb;
			pb=pb->next;    //后移指针
			free(u);   //释放B中当前结点
		}
	}
	while(pa){   //B已经遍历完,A未完
		u=pa;
		pa=pa->next;
		free(u);   //释放A中剩余结点
	}
	while(pb){   //A已经遍历完,B未完
		u=pb;
		pb=pb->next;
		free(u);   //释放B中剩余结点
	}
	pc->next=NULL;    //置结果链表尾指针为NULL
	free(lb);   //释放B表的头结点
	return la;
}

该算法的时间复杂度是O(len1+len2),空间复杂度为O(1)。

16.两个整数序列A =a1, a2, a3…, am和B= b1, b2, b3,…, bn,已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
算法思想:因为两个整数序列已存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾表示匹配成功。A链表到尾而B链表未到尾表示失败。操作中应记住A链表每次的开始结点,以便下次匹配时好从其后继开始。

int Pattern(LinkList A,LinkList B){
//A和B分别是数据域为整数的单链表,本算法判断B是否是A的子序列
	LNode *p=A;   //p为A链表的工作指针,本题假定A和B均无头结点
	LNode *pre=p;   //pre记住每趟比较中A链表的开始结点
	LNode *q=B;    //q是B链表的工作指针
	while(p&&q)
		if(p->data==q->data){   //结果值相同
			p=p->next;
			q=q->next;
		}
		else{
			pre=pre->next;
			p=pre;   //A链表新的开始比较结点
			q=B;   //q从B链表的第一个结点开始
		}
	if(q==NULL)   //B已经比较结束
		return 1;   //说明B是A的子序列
	else
		return 0;   //B不是A的子序列
}

17.设计一个算法用于判断带头结点的循环双链表是否对称。
算法思想:让p从左向右扫描,q从右向左扫描,直到他们指向同一结点为止,若他们所指结点值相同,则继续进行下去,否则返回0。若比较全部相等,则返回1.

int Symmetry(DLinkList L){
//本算法从两头扫描循环双链表,以判断链表是否对称
	DNode *p=L->next,*q=L->prior;   //两头工作指针
	while(p!=q&&p->next!=q)    //循环跳出条件
		if(p->data==q->data){   //所指结点值相同则继续比较
			p=p->next;
			q=q->prior;
		}
		else   //否则,返回0
			return 0;
		return 1;    //比较结束后返回1
}

18.有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
算法思想:先找到两个链表的尾指针,将第一个链表的尾指针与第二个链表的头结点链接起来,使之成为循环的。

LinkList Link(LinkList &h1,LinkList &h2){
//将循环链表h2链接到循环链表h1之后,使之仍保持循环链表的形式
	LNode *p,*q;   //分别指向两个链表的尾结点
	p=h1;
	while(p->next!=h1)   //寻找h1的尾结点
		p=p->next;
	q=h2;
	while(q->next!=h2)   //寻找h2的尾结点
		q=q->next;
	p->next=h2;   //将h2链接到h1之后
	q->next=h1;   //令h2的尾结点指向h1
	return h1;
}

19、设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。
对于循环单链表L,在不空时循环:每循环依次查找一个最小节点并删除。最后释放头结点

void Del_All(LinkList &L){
//本算法实现每次删除循环单链表中的最小元素,直到链表空为止
	LNode *p,*pre,*minp,*minpre;
	while(L->next!=L){   //表不空,循环
		p=L->next;pre=L;   //p为工作指针,pre指向其前驱
		minp=p;minpre=pre;   //minp指向最小值结点
		while(p!=L){   //循环一趟,查找最小值结点
			if(p->data < minp->data){
				minp=p;   //找到值更小的结点
				minpre=pre;
			}
			pre=p;   //查找下一个结点
			p=p->next;
		}
		printf("%d",minp->data);   //输出最小值结点元素
		minpre->next=minp->next;   //最小值结点从表中断开
		free(minp);   //释放空间
	}
	free(L);   //释放头结点
}

20.设头指针为工的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针)、data(数据)和next(后继指针)域外,还有一个访问频度域freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为×的结点中freq域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的Locate (L,x)运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
算法思想:首先在双向链表中查找数据值为x的结点,查到后,将结点从链表上摘下,然后顺着结点的前驱链查找该结点的插入位置,并插入到该位置。

DLinkList Locate(DLinkList &L,ElemType x){
	DNode *p=L->next,*q;   //p为工作指针,q为p的前驱,用于查找插入位置
	while(p&&p->data!=x)
		p=p->next;   //查找值为x的结点
	if(!p)
		exit(0);    //不存在值为x的结点
	else{
		p->freq++;   //令元素值为x的结点的freq域加1
		if(p->pre==L||p->pre->freq > p->freq)
			return p;   //p时链表首结点,或freq值小于前驱
		if(p->next!=NULL)
			p->next->pred=p->pred;
		p->pred->next=p->next;    //将p结点从链表上摘下
		q=p->pred;   //以下查找p结点的插入位置
		while(q!=L&&q->freq <=p->freq)
			q=q->pred;
		p->next=q->next;
		if(q->next!=NULL)
			q->next->pred=p;   //将p结点排在同频率的第一个
		p->pred=q;
		q->next=p;
	}
	return p;    //返回值为x的结点的指针
}

21.单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表的最后一个结点的指针域是空的)。试编写算法判断单链表是否存在环。
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
(1)算法思想:设置快慢两个指针分别为fast和slow,初始时都指向链表头head。slow每次走一步,fast每次走两步。由于fast走得快,如果有换,那么fast一定会先进入环,而slow后进入环。当两个指针都进入环后,经过若干操作后两个指针定能在环上相遇。
(2)代码如下:

LNode* FindLoopStart(LNode *head){
	LNode *fast=head,*slow=head;
	while(fast!=NULL&&fast->next!=NULL){
		slow=slow->next;
		fast=fast->next->next;
		if(slow==fast)  break;
	}
	if(slow==NULL||fast->next==NULL)
		return NULL;
	LNode *p1=head,*p2=slow;
	while(p1!=p2){
		p1=p1->next;
		p2=p2->next;
	}
	return p1;
}

(3)当fast与slow相遇时,slow肯定没有遍历完链表,故算法的时间复杂度为O(n),空间复杂度为O(1)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值