《数据结构》上机实验(第二章)——线性表 Ⅳ

《数据结构》上机实验(第二章) Ⅲ
在这里插入图片描述
在这里插入图片描述

12. 在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,去掉数值相同的元素,使表中不再有重复的元素。

void DeleteEle(LinkList &L)
{
	LNode* p = L->next, *q;
	if (p == NULL) return;
	while (p->next != NULL)
	{
		q = p->next;
		if (p->data == q->data)
		{
			p->next = q->next;
			free(q);
		}
	else 
		p = p->next;
	}
}

程序分析:

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

13. 假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。

void ConList(LinkList& LA, LinkList& LB)
{
	LNode* pa = LA->next, * pb = LB->next, * preb, * prea, * r=LA;
	LA->next = NULL;
	while (pa != NULL && pb != NULL)
	{
		if (pa->data >= pb->data)
		{
			preb = pb;
			pb = pb->next;
			preb->next = LA->next;
			LA->next = preb;
		}
		else if (pa->data < pb->data)
		{
			prea = pa;
			pa = pa->next;
			prea->next = LA->next;
			LA->next = prea;
		}
		
	}
	if (pa == NULL) pa = pb;
	while (pa != NULL)
	{
		prea = pa;
		pa = pa->next;
		prea->next = LA->next;
		LA->next = prea;
	}
	free(LB);
}

程序分析:

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(m+n),其中m、n分别是两个单链表的长度;空间复杂度:O(1)

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

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

  • 算法思想:第8题(2)
  • 该题与14题的区别是,14题中要求不破坏A、B的结点,那么应该将结果放入新链表C中,而此题可以修改A、B中某条链表,从而不开辟新空间。14题的空间复杂度为:O(m+n),其中m、n分别为两个链表的长度;该题的空间复杂度为:O(1)。

16. 两个整数序列 A = a 1 , a 2 , a 3 , … , a n A=a_1,a_2,a_3,…,a_n A=a1,a2,a3,,an B = b 1 , b 2 , b 3 , … , b n B=b_1,b_2,b_3,…,b_n B=b1,b2,b3,,bn已经存入两个单链表中,判断序列B是否是序列A的连续子序列。

  • 算法思想:因为两个整数序列已存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾表示匹配成功。A链表到尾而B链表未到尾表示失败。
    操作中应记住A链表每次的开始结点,以便下次匹配时好从其后继开始。
bool Pattern(LinkList& L1,LinkList &L2)
{
	LNode* p1 = L1->next, * p2 = L2->next, * pre = p1;
	while (p1 != NULL && p2 != NULL)
		{
			if (p1->data == p2->data)
			{
				p1 = p1->next;			
				p2 = p2->next;
			}
			else
			{
				pre = pre->next;
				p1 = pre;
				p2 = L2->next;
			}
		}
	if (p2 == NULL) return true;
	else return false;
}

程序分析:

  • 运行结果:
    在这里插入图片描述

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

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

19. 设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。

  • 算法思想:对于循环单链表L,在不空时循环:每循环一次查找一个最小结点(min指向最小结点,premin指向其前驱节点)并删除它,最后释放头结点。
void DeleteMin(LinkList& L)
{
	LNode* p, * pre, * premin, * min;
	while (L->next != L)
	{
		p = L->next;
		pre = L; 
		premin = L;
		min = L->next;
		while (p != L)
		{	
			if (p->data < min->data)
			{
				min = p;
				premin = pre;
			}
			pre = p;
			p = p->next;
		}
		printf("min=%d\n", min->data);
		premin->next = min->next;
		free(min);
	}
	free(L);
}

程序分析:

  • 运行结果:在这里插入图片描述

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

21. 已知一个带有表头结点的单链表,结点结构为data、link,假设该链表只给出了头指针list。在不改变链表的前提下,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。

  • 算法思想:
    ① count=0,p和q同时指向首结点。
    ②若p为空,转⑤。
    ③若count等于k,则q指向下一个结点;否则,count= count+1。
    ④p指向下一个结点,转②
    ⑤若count等于k,则查找成功,输出该结点的ata域的值,返回1;否则,说明k值超过了线性表的长度,査找失败,返回0
    ⑥算法结東。
bool Search(LinkList L, int k)
{
	LNode* p = L->link, * q = L->link;
	int count = 0;
	while (q != NULL)
	{
		if (count < k) count++; //将指针q移到指针p的后k位
		else p = p->link; //指针p、q同步移动
		q = q->link;

	}
	if (count < k) return false; //链表长度小于k
	else
	{
		printf("倒数第%d个位置的data域为:%d\n", k, p->data);
		return true;
	}
}

程序分析:

  • 运行结果:在这里插入图片描述

22. 假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间。设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为data、next。请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。

  • 算法思想:
    ①分别求出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指向同一位置时停止,即为共同后缀的起始位置,算法结束。
int ListLen(LinkList L)
{
	LNode* p = L->next;
	int len = 0;
	while (p != NULL)
	{
		len++;
		p = p->next;
	}
}

LNode* FindAddr(LinkList Str1, LinkList Str2)
{
	int m, n;
	LNode* p, * q;
	m = ListLen((Str1));
	n = ListLen((Str2));
	for (p = Str1; m > n; m--) p = p->next;
	for (q = Str2; m < n; n--) q = q->next;
	while (p != NULL && p->next != q->next)
	{
		p = p->next;
		q = q->next;
	}
	return p->next;
}

程序分析:

在这里插入图片描述

23. 用单链表保存m个整数,结点的结构为[data][link],且|datal≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。

  • 算法思想:空间换时间,使用辅助数组记录链表中以及出现的数值。
void DeleEle(LinkList& L)
{
	int a[10] = { 0 }; 
	LNode* p = L->next, * pre = L;
	while (p != NULL)
	{
		a[abs(p->data)]++;
		if (a[abs(p->data)] > 1)
		{
			pre->next = p->next;
			free(p);
			p = pre->next;
		}
		else 
		{
			pre = pre->next;
			p = p->next;
		}
	}
}

程序分析:

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(m);空间复杂度O(n):

24. 判断一个链表是否有环,如果有,找出环的入口点并返回,否则返回NULL。

  • 算法思路:设置快慢两个指针分别为fast和slow,初始时都指向链表头head。slow每次走一步,即slow=slow->next,fast每次走两步,即fast=fast->next->next。由于fast比slow走得快,如果有环,那么fast一定会先进入环,而slow后进入环。当两个指针都进入环后经过若干操作后两个指针定能在环上相遇。这样就可以判断一个链表是否有环。
LNode* FindLoopStart(LNode* head)
{
	LNode* fast = head, * slow = head; //设置快慢两个指针
	while (slow != NULL && fast->next != NULL)
	{
		slow = slow->next; //每次走一步
		fast = fast->next; //每次走两步
		if (slow == fast) break; //相遇
	}
	if (slow == NULL || fast->next == NULL) return NULL; //没有环,返回NULL
	LNode* p1 = head, * p2 = slow; //分别指向开始点、相遇点
	while (p1 != p2)
	{
		p1 = p1->next;
		p2 = p2->next;
	}
	return p1; //返回入口点
}

程序分析:

与之相关的四个问题:
①:判断单链表是否有环?
②:如果有环,环的入口?
③:求环长
④:求总长

  • 问题一:如果单链表有环,设置快慢两个指针,经过若干次移动操作后,一定会在某一时刻相遇。否则,最终会移动到表尾(即NULL)。
  • 问题二:设头结点到环的入口点的距离为a,环的入口点沿环的方向到相遇点的距离为x,环长为r,相遇时fast绕过了n圈。此时slow走过的距离d=(a+x);fast走过的距离2d=(a+x+nr)。则有2(a+x)=a+nr+x,因此可设置两个指针,一个指向head,一个指向相遇点,两个指针同步移动(均为一次走一步),相遇点即为环的入口点。
  • 问题三:保存相遇点,然后令某一个指针从相遇点出发向后移动,记录走过的结点数,直到指针回到相遇点,即可求出环长。
  • 问题四:令某一个指针从首结点开始向后移动,到相遇点停止,记录走过的结点数。将此长度加上环长即为表长。
    在这里插入图片描述

25. 设线性表 L = ( a 1 , a 2 , a 3 , … , a n − 2 , a n − 1 , a n ) L=(a_1,a_2,a_3,…,a_{n-2},a_{n-1},a_n) L=(a1,a2,a3,,an2,an1,an)采用带头结点的单链表保存,重新排列L中各结点,得到线性表 L ′ = ( a 1 , a n , a 2 , a n − 1 , a 3 , a n − 2 , . . . ) L'=(a_1,a_n,a_2,a_{n-1},a_3,a_{n-2},...) L=(a1,an,a2,an1,a3,an2,...)

  • 算法思路:①先找出链表L的中间结点,为此设置两个指针p和q,指针p每次走一步,q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;
    ②然后将L的后半段结点原地逆置;
    ③从单链表前后两段中依次各取一个结点,按要求重排。
void ChangeList(LinkList& L)
{
	LNode* p = L, * q = L, * r, * s;
	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 = L->next; //s指向前半段的第一个数据结点,即插入点
	q = p->next; //q指向后半段的第一个数据结点
	p->next = NULL;
	while (q != NULL) //将链表后半段的结点插入到指定位置
	{
		r = q->next; //r指向后半段的下一个结点
		q->next = s->next; //将q所指结点插入到所指结点之后
		s->next = q;
		s = q->next; //s指向前半段的下一个插入点
		q = r;	
	}
}

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

26. 若一个线性表采用顺序表L存储,其中所有元素为整数,每个元素的值只能取0、1或2。设计一个算法,将所有元素按0、1、2的顺序排列。

  • 算法思想:用0~i表示0元素区间,k~n-1表示2元素区间,中间部分为1元素区间。初始时,i=-1,k=n表示这些区间为空。用j扫描顺序表L中部的所有元素,j的初始值为0,当j所指的元素为0时,说明它一定属于前部,i增1(扩大0元素区间),将该元素交换到位置i(从前面交换过来的元素一定是1),j前进;当j所指的元素为2时,说明它一定属于后部,k减1(扩大2元素区间),将该元素交换到位置k,若此时j前进则会导致该位置不能被交换到前部,所以j不前进;当j所指的元素为1时,说明它一定属于中部,保持原来的位置不动,j前进。
void move(SqList*& L)
{
	int i = -1, j = 0, k = L->length;
	while (j < k)
	{
		if (L->data[j] == 0)
		{
			i++;
			swap(L->data[i], L->data[j]);
			j++;
		}
		else if (L->data[j] == 2)
		{
			k--;
			swap(L->data[k], L->data[j]);
		}
		else j++;
	}
}

程序分析:

  • 运行结果:
    在这里插入图片描述

27. 将顺序表中所有元素划分成两部分,其中前半部分的每个元素均小于等于整数 k 1 k_1 k1,后半部分的每个元素均大于等于整数 k 2 k_2 k2

  • 解:当k1≤k2时,先将所有小于等于整数k1前移,置low=0,high=n-1,从左向右找大于k1的元素data[low]、再从右向左找小于等于k1的元素data[high],将两者交换,如此直到low=high为止。然后采用类似的方法将data[low~n-1]中所有大于等于k2的元素移到右半部分,最后返回真。如果k1>k2,直接返回假。
bool fun(SqList* L,int x,int y)
{
	if (x > y) return false;
	int low=0, high=L->length-1;
	while (low < high)
	{
		while (L->data[low] <= x) low++;
		while (L->data[high] >= x) high--;
		if (low < high) swap(L->data[low], L->data[high]);
	}
	high = L->length - 1;
	while (low < high)
	{
		while (L->data[low] < y) low++;
		while (L->data[high] >= y) high--;
		if (low < high) swap(L->data[low], L->data[high]);
	}
	return true;
}

程序分析:

  • 运行结果:在这里插入图片描述

28. 设顺序表A中前k个元素有序,后n-k个元素有序,试设计一个算法使得整个顺序表有序。

  • 算法思想:先将整个顺序表进行原地逆置,再将前(length-k)个元素进行逆置,最后将后k个元素依次插入到前面的有序序列中,同时注意移动相应元素。这种方法适用于k较小的情况,如果k较大,则直接将后面的元素往前插入,此时不需要反复逆置,这样做效率反而会更快。
void reverse(SqList* L, int x, int y) //将元素逆置(x、y分别对应起始下标和终止下标)
{
	int mid = (y - x) / 2;
	int t;
	for (int i = 0; i <= mid; i++)
	{
		t = L->data[x];
		L->data[x] = L->data[y];
		L->data[y] = t;
		x++; y--;
	}
}

void fun(SqList* L,int k)
{
	reverse(L, 0, L->length - 1);
	reverse(L, 0, L->length - 1 - k);
	
	for (int i = L->length - k; i < L->length; i++)
	{
		int j = 0, t=L->data[i];
		while (L->data[j] <= L->data[i]) j++;
		for (int k = i - 1; k >= j; k--)
			L->data[k + 1] = L->data[k];
		L->data[j] = t;
	}
}

程序分析:

在这里插入图片描述

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n²),空间复杂度:O(1)
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值