王道数据结构-----第二章线性表的单双链表练习题(应用题1-20)

第二章线性表的单双链表练习题

01.带头结点的单链表工中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。

算法基本思想:
解法1:用p从头至尾扫描单链表,pre指向*p结点的前驱。若p所指结点的值为x,则删除,并让p移向下一个结点,否则让 pre、p指针同步后移一个结点。

bool Del_x_1(LinKList &L,double 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;
        }
    }
}

本算法是在无序单链表中删除满足某种条件的所有结点,这里的条件是结点的值为x。实际上,这个条件是可以任意指定的,只要修改if条件即可。比如,我们要求删除值介于mink 和maxk之间的所有结点,则只需将if语句修改为if(p->data>mink&&p->data<maxk)
解法2:采用尾插法建立单链表。用p指针扫描L的所有结点,当其值不为x时,将其链接 到L之后,否则将其释放。

void Del_x_2(LinkList &L,double x){
    LNode *p=L->next,*r=L,*q;				//r指向尾节点,其初值为头节点
    while(p!=NULL){
        if(p->data!=x){						//*p节点值不为x时将其链接到L尾部
            r->next=p;
            r=p;
            p=p->next;						//继续扫描
        }else{								//*p节点值为x时将其释放
            q=p;
            p=p->next;						//继续扫描
            free(q)							//释放空间
        }
    }
    r->next=NULL;							//插入结束后置尾节点指针为NULL
}

上述两个算法扫描一遍链表,时间复杂度为O(n),空间复杂度为O(1)。

02.试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设该结点唯一)。

算法思想:用p从头至尾扫描单链表,pre 指向p结点的前驱,用minp 保存值最小的结点指针(初值为p),minpre指向minp结点的前驱(初值为pre)。一边扫描,一边比较,若p->data小于minp->data,则将p、pre分别赋值给 minp、minpre,如下图所示。当 p扫描完毕时,minp指向最小值结点,minpre 指向最小值结点的前驱结点,再将minp所指结点删 除即可。

void delete_min(LinkList &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;
}

算法需要从头至尾扫描链表,时间复杂度为O(n),空间复杂度为O(1)。

03.试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)。

解法 1:将头结点摘下,然后从第一结点开始,依次插入到头结点的后面(头插法建立单链表),直到最后一个结点为止,这样就实现了链表的逆置,如下图所示。

LinkList Reverse_1(LinkList L){
	LNode *p,*r; 						//p为工作指针,r为p的后维,以防断链
	p=L->next; 							//1从第一个元素结点开始
	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;
 }

解法 2: 大部分辅导书都只介绍解法 1,这对读者的理解和思维是不利的。为了将调整指针这个复杂的过程分析清楚,我们假设pre、p和r指向三个相邻的结点,假设经过若干操作后,pre 之前的 结点的指针都已调整完毕,它们的 next都指向其原前驱结点。现在令p 结点的 next 域指向 *pre 结点,注意到一旦调整指针的指向,*p的后继结点的链就会断开,为此需要用r来指向原 *p的后继结点。处理时需要注意两点:一是在处理第一个结点时,应将其 next域置为NULL,而不是指向头结点(因为它将作为新表的尾结点);二是在处理完最后一个结点后,需要将头结 点的指针指向它。

LinkList Reverse_2(Linklist L){
	LNode *pre,*p=L->next,*r=p->next;
	p->next=NULL: 							//处理第一个结点
	while(r!=NULL){ 						//r为空,则说明p为最后一个结点
		pre=p; 								//依次继续遍历
		p=r;
		r=r->next;
		p->next=pre; 						//指针反转
	}
	L->next=p; 								//处理最后一个结点
	return L;
}

上述两个算法的时间复杂度为O(n),空间复杂度为O(1)。

04.设在一个带表头结点的单链表中,所有结点的元素值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素(若存在)。

因为链表是无序的,所以只能逐个结点进行检查,执行删除

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;
 	}
}

05.给定两个单链表,试分析找出两个链表的公共结点的思想(不用写代码)。

两个单链表有公共结点,即两个链表从某一结点开始,它们的 next 都指向同一结点。由于每 个单链表结点只有一个
next域,因此从第一个公共结点开始,之后的所有结点都是重合的,不可能再出现分叉。所以两个有公共结点而部分重合的单链表,拓扑形状看起来像Y,而不可能像X。本题极容易联想到“蛮”方法:在第一个链表上顺序遍历每个结点,每遍历一个结点,在第二个链表上顺序遍历所有结点,若找到两个相同的结点,则找到了它们的公共结点。显然,该算法的时间复杂度为O(lenlxlen2)。 接下来我们试着去寻找一个线性时间复杂度的算法。先把问题简化:如何判断两个单向链表 有没有公共结点?应注意到这样一个事实:若两个链表有一个公共结点,则该公共结点之后的所有结点都是重合的,即它们的最后一个结点必然是重合的。因此,我们判断两个链表是不是有重合的部分时,只需要分别遍历两个链表到最后一个结点。若两个尾结点是一样的,则说明它们有 公共结点,否则两个链表没有公共结点。然而,在上面的思路中,顺序遍历两个链表到尾结点时,并不能保证在两个链表上同时到达尾结点。这是因为两个链表长度不一定一样。但假设一个链表比另一个长k个结点,我们先在长 的链表上遍历k个结点,之后再同步遍历,此时我们就能保证同时到达最后一个结点。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的,因此它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。根据这一思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长 的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到 链表结束。此时,该方法的时间复杂度为 O(lenl +len2)。

06.设C={a1,b1,a2,b2,…,am,bn}为线性表,采用带头结点的单链表存放,设计一个就地算
法,将其拆分为两个线性表,使得A={a1,a2,…,an},B={bn,…,b2,b1}。

算法思想:循环遍历链表 C,采用尾插法将一个结点插入表A,这个结点为奇数号结点,这 样建立的表 A与原来的结点顺序相同;采用头插法将下一结点插入表 B,这个结点为偶数号结点, 这样建立的表B与原来的结点顺序正好相反。

LinkList DisCreat_2(LinkList sA)(
	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;
}

该算法特别需要注意的是,采用头插法插入结点后,*p 的指针域已改变,若不设变量保存 其后继结点,则会引起断链,从而导致算法出错。

07.在一个递增有序的单链表中,存在重复的元素。设计算法删除重复的元素,例如(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){
	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;
	}
}

本题也可采用尾插法,将头结点摘下,然后从第一结点开始,依次与已经插入结点的链表的最后一个结点比较,若不等则直接插入,否则将当前遍历的结点删除并处理下一个结点,直到最 后一个结点为止。
本算法的时间复杂度为O(n),空间复杂度为O(1)。

08.设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的
公共元素产生单链表 C,要求不破坏 A、B的结点。

算法思想:表A、B都有序,可从第一个元素起依次比较A、B两表的元素,若元素值不等,则值小的指针往后移,若元素值相等,则创建一个值等于两结点的元素值的新结点,使用尾插法插入到新的链表中,并将两个原表指针后移一位,直到其中一个链表遍历到表尾。

void Get_Common(LinkList A,LinkList B){
	LNode *p=A->next,*q=B->next,*r,*s;
	LinkList C=(LinkList)malloc(sizeof(LNode));		//建立表 c
	r=C; 											//r 始终指向c的尾结点
	while(p!=NULL&Eq!=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尾结点指针为空
}

09.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交
集,并存放于A链表中。

算法思想:采用归并的思想,设置两个工作指针pa和pb,对两个链表进行归并扫描,只有同时出现在两集合中的元素才链接到结果表中且仅保留一个,其他的结点全部释放。当一个链表 遍历完毕后,释放另一个表中剩下的全部结点。

LinkList Union(LinkList sla,LinkList klb)(
	LNode *pa=la->next; 						//设工作指针分别为pa和 pb
	LNode *pb=lb->next;
	LNode *u,*pc=la; 							//结果表中当前合并结点的前驱指针 pc
	while(pakspb){
		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 结束
	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)。

10.两个整数序列A=a1,a2,a3,…,am和B=b1,b2,b3,…,bn已经存入两个单链表中,设计一
个算法,判断序列B是否是序列A的连续子序列。

算法思想:因为两个整数序列已存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表 仍从第一个结点开始比较,直到B链表到尾表示匹配成功。A链表到尾而B链表未到尾表示失败。操作中应记住A链表每次的开始结点,以便下次匹配时好从其后继开始。

int Pattern(LinkList A,LinkList B){
	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; 					//g从B链表第一个结点开始
		}
		if(q==NULL) 				//B 已经比较结束
			return 1; 				//说明B是A的子序列
		else
			return 0; 				//B不是 A的子序列
    }
}

11.设计一个算法用于判断带头结点的循环双链表是否对称。

算法思想:让p从左向右扫描,q从右向左扫描,直到它们指向同一结点(p==q,当循环双链表中结点个数为奇数时)或相邻(p->next=q或q->prior=p,当循环双链表中结点个数为偶数时)为止,若它们所指结点值相同,则继续进行下去,否则返回 0。若比较全部相等,则 返回1。

int Symmetry(DLinklist L){
	DNode *p=L->next,*q=L->prior;		//两头工作指针
	while(p!=qá6q->next!=p){			//循环跳出条件
		if(p->data==q->data){			//所指结点值相同则继续比较
			p=p->next;
			q=q->prior;
		}
		else							//否则,返回0
		return 0;
	return 1;							//比较结束后返回1
	}
}

12.有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。

算法思想:先找到两个链表的尾指针,将第一个链表的尾指针与第二个链表的头结点链接起 来,再使之成为循环的。

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

13.设有一个带头结点的非循环双链表 L,其每个结点中除有 pre、data 和 next 域外,还有一个访问频度域 freq,其值均初始化为零。每当在链表中进行一次 Locate(L,x)运算时,令值为x的结点中freq域的值增1,并使此链表中的结点保持按访问频度递减的顺序排列,且最近访问的结点排在频度相同的结点之前,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的 Locate(L,x)函数,返回找到结点的地址,类型为指针型。

算法思想:首先在双向链表中查找数据值为x的结点,查到后,将结点从链表上摘下,然后顺着结点的前驱链查找该结点的插入位置(频度递减,且排在同频度的第一个,即向前找到第一 个比它的频度大的结点,插入位置为该结点之后),并插入到该位置。

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

14.设将n(n>1)个整数存放到不带头结点的单链表L中,设计算法将L中保存的序列循环
右移k(0<k<n)个位置。例如,若k=1,则将链表{0,1,2,3}变为{3,0,1,2}。要求:
1)给出算法的基本设计思想。

首先,遍历链表计算表长 n,并找到链表的尾结点,将其与首结点相连,得到一个循环单链表。然后,找到新链表的尾结点,它为原链表的第n-k个结点,令L指向新链表尾结点的下一 个结点,并将环断开,得到新链表。

2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。

LNode *Converse(LNode *L,int k){
	int n=1; 									//n 用来保存链表的长度
	LNode *p=L; 								//p 为工作指针
	while(p->next!=NULL){ 						//计算链表的长度
		p=p->next;
		n++;
    }											//循环执行完后,p指向链表尾结点
	p->next=L;									//将链表连成一个环
	for(int i=1;i<=n-k;i++)						//寻找链表的第n-k个结点
		p=p->next;
	L=p->next; 									//令L指向新链表尾结点的下一个结点
	p->next=NULL; 								//将环断开
	return L;
    }

3)说明你所设计算法的时间复杂度和空间复杂度。

本算法的时间复杂度为O(n),空间复杂度为O(1)。

15.单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表
的最后一个结点的指针域是空的)。试编写算法判断单链表是否存在环。
1)给出算法的基本设计思想。

算法的基本设计思想. 设置快慢两个指针分别为 fast 和 slow 最初都指向链表头 head。slow 每次走一步,即 slow=slow->next;fast每次走两步,即 fast=fast->next->next。fast 比 slow 走得快,若有环,则 fast一定先进入环,而 slow 后进入环。两个指针都进入环后,经过若干操作后两个指针定能在环上相遇。这样就可以判断一个链表是否有环。

如下图所示,当 slow 刚进入环时,fast 早已进入环。因为 fast每次比 slow 多走一步 且 fast与slow的距离小于环的长度,所以fast与slow相遇时,slow所走的距离不超过环的 长度。
在这里插入图片描述

如下图所示,设头结点到环的入口点的距离为 a,环的入口点沿着环的方向到相遇点的距离 为x,环长为r,相遇时 fast 绕过了n圈。
在这里插入图片描述

2)根据设计思想,采用C或C+语言描述算法,关键之处给出注释。

LNode* FindLoopStart(LNode *head)(
	LNode *fast=head,*slow=head; 					//设置快慢两个指针
	while(fast!=NULL&6fast->next!=NULL){
		slow=slow->next; 							//每次走一步
		fast=fast->next->next; 						//每次走两步
		if(slow==fast)break; 						//相遇
    }
	if(fast==NULLllfast->next==NULL)
		return NULL; 								//没有环,返回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)。

16.设有一个长度n(n为偶数)的不带头结点的单链表,且结点值都大于0,设计算法求这
个单链表的最大孪生和。孪生和定义为一个结点值与其孪生结点值之和,对于第i个结
点(从0开始),其孪生结点为第n-i-1个结点。要求:
1)给出算法的基本设计思想。

算法的基本设计思想: 设置快、慢两个指针分别为 fast和slow,初始时 slow 指向L(第一个结点),fast 指向L->next(第二个结点),之后slow每次走一步,fast 每次走两步。当 fast 指向表尾(第n个结点)时,slow正好指向链表的中间点(第n/2个结点),即 slow正好指向链表前半部分的最后 一个结点。将链表的后半部分逆置,然后设置两个指针分别指向链表前半部分和后半部分的首结点,在遍历过程中计算两个指针所指结点的元素之和,并维护最大值。

2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。

int PairSum(LinkList L){
	LNode *fast=L->next,*slow=L;					//利用快慢双指针找到链表的中间点
	while(fast!=NULL&&fast->next!=NULL){
		fast=fast->next->next; 						//快指针每次走两步
		slow=slow->next;							//慢指针每次走一步
	}
	LNode *newHead=NULL,*p=slow->next,*tmp;
	while(p!=NULL){ 								//反转链表后一半部分的元素,采用头插法
		tmp=p->next; 								//p指向当前待插入结点,令 tmp指向其下一结点
		p->next=newHead 							//将p所指结点插入到新链表的首结点之前
		newHead=p; 									//newHead 指向刚才新插入的结点,作为新的首结点
    	p=tmp; 										//当前待处理结点变为下一结点
    }
	int mx=0;p=L;
	LNode*q=newHead;
	while(p!=NULL){									//用p和g分别遍历两个链表
		if((p->data+q->data)>mx)					//用mx记录最大值
			mx=p->data+q->data;
		p=p->next;
		q=q->next;
    }
return mx;
}

3)说明你的算法的时间复杂度和空间复杂度。

本算法的时间复杂度为O(n),空间复杂度为在这里插入图片描述
O(1)。

17.【2009统考真题】已知一个带有表头结点的单链表,结点结构为

假设该链表只给出了头指针 list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的 data域的值,并返回 1;否则,只返回0。要求:
1)描述算法的基本设计思想。

问题的关键是设计一个尽可能高效的算法,通过链表的一次遍历,找到倒数第k个结点的位 置。算法的基本设计思想是:定义两个指针变量p和q,初始时均指向头结点的下一个结点(链 表的第一个结点),P指针沿链表移动;当 p指针移动到第 k个结点时,q指针开始与 p指针同 步移动;当p指针移动到最后一个结点时,q指针所指示结点为倒数第k个结点。以上过程对链 表仅进行一遍扫描。

2)描述算法的详细实现步骤。

① count=0,p和q指向链表表头结点的下一个结点。

② 若p为空,转⑤。

③ 若 count等于k,则q指向下一个结点;否则,count=count+1。

④ p指向下一个结点,转②。

⑤若 count等于k,则查找成功,输出该结点的 data 域的值,返回1;否则,说明 k值 超过了线性表的长度,查找失败,返回0。

⑥算法结束。

3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用 C、C++或 Java 语言
实现),关键之处请给出简要注释。

	typedef int ElemType; 				//链表数据的类型定义
	typedef struct LNode{ 				//链表结点的结构定义
	ElemType data; 						//结点数据
	struct LNode *link; 				//结点链接指针
	}LNode,*LinkList;
int Search_k(LinkList list,int k){
	LNode *p=list->link,*q=list->link; 	//指针p、q指示第一个结点
	int count=0;
	while(p!=NULL){ 					//遍历链表直到最后一个结点
		if(count<k) count++; 			//计数,若 count<k只移动p
		else q=q->link;
		p=p->link; 						//之后让 p、g同步移动
	}//while
	if(count<k)
		return 0; 						//查找失败返回0
	else { 								//否则打印并返回1
	printf("%d",q->data);
	return 1;
    }
}

18.【2012 统考真题】假定采用带头结点的单链表保存单词,当两个单词有相同的后级时,
可共享相同的后缀存储空间,例如,loading和being.的存储映像如下图所示。
在这里插入图片描述

设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为data next
请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后级
的起始位置(如图中字符i所在结点的位置p)。要求:

(解析:顺序遍历两个链表到尾结点时,并不能保证两个链表同时到达尾结点。这是因为两个链表的长度不同。假设一个链表比另一个链表长k个结点,我们先在长链表上遍历k个结点,之后同步遍历两个链表,这样就能够保证它们同时到达最后一个结点。因为两个链表从第一个公共结点到链表的尾结点都是重合的,所以它们肯定同时到达第一个公共结点。)

在这里插入图片描述

1)给出算法的基本设计思想。

①分别求出str1和 str2所指的两个链表的长度m和n。

② 将两个链表以表尾对齐:令指针p、q分别指向str1和 str2 的头结点,若 m≥n,则指针p先走,使p指向链表中的第m-n+1个结点,;若m<n,则使q指向链表中的第n-m+1个结点,即使指针p和q所指的结点到表尾的长度相等。
③ 反复将指针p和q同步向后移动,,并判断它们是否指向同一结点。当p、q指向同一结点,则该点即为所求的共同后缀的起始位置。

2)根据设计思想,采用C或 C++或Java语言描述算法,关键之处给出注释。

typedef struct Node{
	char data;
	struct Node *next;
}SNode;
/*求链表长度的函数*/
int listlen(SNode *head){
	int len=0;
	while(head->next!=NULL){
		len++;
		head=head->next;
    }
	return len;
}
/*找出共同后缀的起始地址*/
SNode* find_list(SNode *strl,SNode *str2){
	int m,n;
	SNode *p,*q;
	m=listlen(strl); 							//求 str1的长度,0(m)
	n=listlen(str2);							//求 str2的长度,0(n)
	for(p=strl;m>n;m--) 						//若m>n,使p指向链表中的第m-n+1个结点
		p=p->next;
	for(q=str2;m<n;n--) 						//若m<n,使q指向链表中的第n-m+1个结点
		q=q->next;
	while(p->next!=NULL66p->next!=q->next)(		//查找共同后缀起始点
		p=P->next;								//两个指针同步向后移动
		q=q->next;
	}
	return p->next; 							//返回共同后缀的起始地址
}

3)说明你所设计算法的时间复杂度。

时间复杂度为O(len1+len2)或O(max(len1,len2)),其中len1、len2分别为两个链表的长度。

19.【2015统考真题】用单链表保存m个整数,结点的结构为[data] [link],且data|≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中 data 的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若
给定的单链表 head 如下:
在这里插入图片描述

1)给出算法的基本设计思想。

算法的核心思想是用空间换时间。使用辅助数组记录链表中已出现的数值,从而只需对链 表进行一趟扫描。

因为|data|≤n,故辅助数组q的大小为n+1,各元素的初值均为0。依次扫描链表中 的各结点,同时检查q[|data|]的值,若为0则保留该结点,并令q[ldatal]=1;否 则将该结点从链表中删除。

2)使用 C或 C++语言,给出单链表结点的数据类型定义。

typedef struct node {
	int data;
	struct node *link:
}NODE;
Typedef NODE *PNODE;

3)根据设计思想,采用C或 C+语言描述算法,关键之处给出注释。

void func(PNODE h,int n){
	PNODE p=h,r;
	int *q,m;
	q=(int *)malloc(sizeof(int)*(n+1));			//申请n+1个位置的辅助空间
	for(int i=0;i<n+1;i++) 						//数组元素初值置0
		*(q+i)=0;
	while(p->link!=NULL){
		m=p->link->data>0? p->link->data:-p->link->data;
		if(*(q+m)==0) 							//判断该结点的 data 是否已出现过
			*(q+m)=1; 							//首次出现
			p=P->link; 							//保留
		}
		else{ 									//重复出现
			r=p->link;							//删除
			p->link=r->link;
			free(r);
		}
	}
	free(q);
}

4)说明你所设计算法的时间复杂度和空间复杂度。

参考答案所给算法的时间复杂度为O(m),空间复杂度为0(n)

20.【2019统考真题】设线性表L=(a1,a2,a3,…,an-2,an-1,an)采用带头结点的单链表保存,链
表中的结点定义如下
typedef struct node
{int data;
struct node*next;
}NODE;
请设计一个空间复杂度为 O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得
到线性表L’=(a1,an,a2,an-1,a3,an-2,…)。要求:
1)给出算法的基本设计思想。

先观察L=(a1,a2,a3,…,an-1,an-2,an-3,)和L’=(a1,an,a2,an-1,a3,an-2,…),发现L’是由L.摘取第一个元素,再摘取倒数第一个元素……依次合并而成的。为了方便链表后半段取元素,需要先将 L后半段原地逆置[题目要求空间复杂度为O(1),不能借助栈],否则每取最后一个结点都需要遍历一次链表。①先找出链表L的中间结点,为此设置两个指针p和q,指针p每次走一步,指针 q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;②然后将L的后半段结点原地逆置。③从单链表前后两段中依次各取一个结点,按要求重排。

2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。

void change_list(NODE*h)(
	NODE *p,*q,*r,*s;
	p=q=h;
	while(q->next!=NULL)( 					//寻找中间结点
		P=p->next; 							//p 走一步
		q=q->next;
		if(q->next!=NULL)q=q->next; 		//q 走两步
	}
	q=p->next; 								//p所指结点为中间结点,q为后半段链表的首结点
	p->next=NULL;
	while(q!=NULL){ 						//将链表后半段逆置
		r=q->next;
		q->next=p->next;
		p->next=q;
		q=r;
	}
	s=h->next; 								//s 指向前半段的第一个数据结点,即插入点
	q=p->next; 								//q指向后半段的第一个数据结点
	p->next=NULL;
	while(q!=NULL){ 						//将链表后半段的结点插入到指定位置
		r=q->next;							//r 指向后半段的下一个结点
		q->next=s->next;					//将q所指结点插入到 s所指结点之后
		s->next=q;
		s=q->next; 							//s 指向前半段的下一个插入点
		q=r;
	}
}

3)说明你所设计的算法的时间复杂度。

第一步找中间结点的时间复杂度为O(n),第二步逆置的时间复杂度为O(n),第三步合并 链表的时间复杂度为 0(n),所以该算法的时间复杂度为O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值