单链表的应用-合并两升序单链表,得降序单链表

在学习单链表的应用时,我们应该把单链表的基础知识学明白,可以参考下面这篇文章~

单链表(含完整C代码)https://blog.csdn.net/weixin_54186646/article/details/123312019?spm=1001.2014.3001.5502

目录

🌟1.【问题描述】

💓2.【问题分析】

2.1合并

2.2就地逆置

🌵3.【完整代码】


🌟1.【问题描述】

将两个按升序排列的单链表,合并为一个按降序排列的单链表,要求利用原表的结点空间

💓2.【问题分析】

个人认为若要在合并的过程中,直接得到降序单链表比较困难。但可以思考的是:如何将两单链表按升序合并,然后再对单链表进行逆置。

此时要注意要求为:利用原表的结点空间,即不开辟新的空间。所以,在合并与逆置的过程中只能在原有空间上进行,所以合并实为插入,而逆置需要就地

思路:

  1. 合并:将表1元素按升序插入表2
  2. 逆置:单链表就地逆置

2.1合并

合并即插入,那么我们需要选定其中一个单链表作为被插入的对象,将另外一个单链表的元素按合适的位置插入。我们还需要考虑几个问题。

选哪个作为被插入对象更好呢?

仔细一想,选择更长的表作为被插入对象更好。这样,更短的表被遍历一次并插入到合适的位置后,更长的表仍有结点未遍历到,但这些未遍历到的结点也无需再进行遍历,就已经完成了合并,节省了时间。

怎么判断哪个单链表更长呢?

由于我们事先不知道两单链表的长度,就需要通过遍历记录一下。

    Link* temp1 = node1->next;
	Link* temp2 = node2->next;
	int k1 = 0, k2 = 0;//表长
	while (temp1 != NULL)
	{
		temp1 = temp1->next;
		k1++;
	}
	while (temp2 != NULL)
	{
		temp2 = temp2->next;
		k2++;
	}

什么是合适的位置呢?

不妨我们假设表1更长,temp1指向表1中的结点,temp2指向表2中的结点:

我们需要遍历表2中的每个元素,与表1中的元素作比较;

  • 若temp1->data < temp2->data,则temp2->data插入到temp1->data后;
  • 若temp1->data > temp2->data,则temp2->data插入到temp1->data前;
  • 若temp1->data = temp2->data,则前后都可以;

自然是将小的插入到前面,大的插入到后面。

无论插入到哪里,我们都可以采用头插法。只要找准插入位置与插入元素

//node:插入结点,n:插入元素
void HeadInsert(Link* node, int n)
{
	Link* insert = (Link*)malloc(sizeof(Link));
	insert->data = n;
	insert->next = node->next;
	node->next = insert;
}

结点的移动太难理顺?

不妨先看看我的代码(可能有更好的嗷~),以表1更长举例:

//指针再指回头结点
	temp1 = node1, temp2 = node2;

	//表1的长度更大,将表2插入到表1中
	if (k1 >= k2)
	{
		//表2更短,表2遍历并插入后,即完成合并
		while (temp2->next != NULL)
		{
			//若表2元素更小,则将此元素插入到表1被比较元素之前
			if (temp2->next->data <= temp1->next->data)
			{
				HeadInsert(temp1, temp2->next->data);//temp1为插入到此元素前面
				//temp2向后移动1个位置
				temp2 = temp2->next;
				//表1由于插入了一个元素,temp1需向后移动2个位置
				//向后移动2个位置有风险,因为无法确保向后移动2个位置的结点!=NULL,所以要判断
				if (temp1->next->next != NULL)
				{
					temp1 = temp1->next->next;
				}
				//
				else
				{
					break;
				}
			}
			//若表2元素更大,则将此元素插入到表1被比较元素之后
			else
			{
				HeadInsert(temp1->next, temp2->next->data);//temp1->next为插入到此元素后面
				temp2 = temp2->next;
				if (temp1->next->next != NULL)
				{
					temp1 = temp1->next->next;
				}
				else
				{
					break;
				}
			}
		}

发现1:temp2是一个结点一个结点移动的

理由:因为我们需要将表2的每一个元素都插入到表1中,所以肯定每个元素都会被遍历。

发现2:temp1是两个结点两个结点移动的

理由:因为插入了一个新的元素在表1里面,所以要跳过新插入的元素,指向下一个原有元素。

发现3:temp1的移动需要判断temp1->next->next != NULL

理由:若不判断,会出现一个问题:temp1->next->next==NULL,当出现这种情况时,temp2可能还!=NULL,也就是大循环还没停止,于是就会报错;但我们发现,一旦出现这种情况,temp1此时指向倒数第二个结点,合并已经完成了,所以直接推出循环即可。

发现4:两元素作比较使用的是:if (temp2->next->data <= temp1->next->data)

理由:这里没有使用:if (temp2->data <= temp1->data)。temp指向的是被比较元素的前一个结点,此时方便头插法插入元素(node=temp1或者node=temp1->next)。若temp1已经指向了两比较元素,则找不到此元素的上一个元素,也就没法使用头插法了。

理解了以上四个发现,就能很好地明白结点的指向问题啦~

2.2就地逆置

如果可以再开辟一个单链表,那么单链表的逆置就很简单。

但题目要求就地逆置,就对我们的思路一定的考验了,其实,也比较好理解。

思路:

将每一个结点的next指针指向前一个结点,将头结点指向原链表最后一个结点即可。

要实现此过程,我们在遍历结点的过程中,需要保存好三个结点:

  • 被遍历的结点
  • 前驱结点
  • 后继结点

为什么需要保存好三个结点呢?

  • 前驱结点:因为一旦没保存好前驱结点,我们就找不到前驱结点,只找得到后继结点。
  • 后继结点:因为一旦没保存好后继结点,当我们把此结点的next指针指向前驱结点时,后继结点也找不到了。
/*单链表原地逆置*/
Link* reverse(Link* node)
{
	Link* temp1 = node->next;
	Link* temp2;
	//摘除头结点,将node指针域指向NULL
	node->next = NULL;
	//将此结点指向的下一个结点改为指向上一个结点
	//需要同时保存此结点的下一个结点与上一个结点
	//temp2用于保存下一个结点,以免结点丢失
	//node->next用于保存上一个结点,使此结点可以指向上一个结点
	while (temp1 != NULL)
	{
		temp2 = temp1->next;
		temp1->next = node->next;
		node->next = temp1;
		temp1 = temp2;
	}
	return node;
}

如果以上内容明白了的话,那恭喜你嗷~

🌵3.【完整代码】

#include<stdio.h>
#include<stdlib.h>

typedef struct LinkList
{
	int data;//数据域
	struct LinkList* next;//指针域
}Link;

Link* InitList();
void HeadInsert(Link* node, int n);
Link* Combine(Link* node1, Link* node2);
Link* reverse(Link* node);
void show(Link* node);

/*单链表初始化*/
Link* InitList()
{
	Link* node = (Link*)malloc(sizeof(Link));
	if (node == NULL)
		printf("初始化函数已执行,内存分配失败\n");
	else
		node->next = NULL;
	return node;
}

/*头插法*/
//在头结点后,其他结点前插入元素
//node:头结点,n:待插入元素,insert:待插入结点
void HeadInsert(Link* node, int n)
{
	Link* insert = (Link*)malloc(sizeof(Link));
	insert->data = n;
	insert->next = node->next;
	node->next = insert;
}

/*输出*/
void show(Link* node)
{
	Link* temp = (Link*)malloc(sizeof(Link));
	temp = node->next;
	while (temp != NULL)
	{
		printf("%d ", temp->data);
		temp = temp->next;
	}
	printf("\n");
}

/*单链表原地逆置*/
Link* reverse(Link* node)
{
	Link* temp1 = node->next;
	Link* temp2;
	//摘除头结点,将node指针域指向NULL
	node->next = NULL;
	//将此结点指向的下一个结点改为指向上一个结点
	//需要同时保存此结点的下一个结点与上一个结点
	//temp2用于保存下一个结点,以免结点丢失
	//node->next用于保存上一个结点,使此结点可以指向上一个结点
	while (temp1 != NULL)
	{
		temp2 = temp1->next;
		temp1->next = node->next;
		node->next = temp1;
		temp1 = temp2;
	}
	return node;
}

//将两表合并
Link* Combine(Link* node1, Link* node2)
{
	Link* temp1 = node1->next;
	Link* temp2 = node2->next;
	int k1 = 0, k2 = 0;//表长
	while (temp1 != NULL)
	{
		temp1 = temp1->next;
		k1++;
	}
	while (temp2 != NULL)
	{
		temp2 = temp2->next;
		k2++;
	}
	//指针再指回头结点
	temp1 = node1, temp2 = node2;

	//表1的长度更大,将表2插入到表1中
	if (k1 >= k2)
	{
		//表2更短,表2遍历并插入后,即完成合并
		while (temp2->next != NULL)
		{
			//若表2元素更小,则将此元素插入到表1被比较元素之前
			if (temp2->next->data <= temp1->next->data)
			{
				HeadInsert(temp1, temp2->next->data);//temp1为插入到此元素前面
				//temp2向后移动1个位置
				temp2 = temp2->next;
				//表1由于插入了一个元素,temp1需向后移动2个位置
				//向后移动2个位置有风险,因为无法确保向后移动2个位置的结点!=NULL,所以要判断
				if (temp1->next->next != NULL)
				{
					temp1 = temp1->next->next;
				}
				//
				else
				{
					break;
				}
			}
			//若表2元素更大,则将此元素插入到表1被比较元素之后
			else
			{
				HeadInsert(temp1->next, temp2->next->data);//temp1->next为插入到此元素后面
				temp2 = temp2->next;
				if (temp1->next->next != NULL)
				{
					temp1 = temp1->next->next;
				}
				else
				{
					break;
				}
			}
		}
		//链表逆置后返回
		return reverse(node1);
	}
	//表2的长度更大,将表1插入到表2中
	//以下内容类比上面,只需将temp1改为temp2,temp2改为temp1即可
	else
	{
		while (temp1->next != NULL)
		{
			if (temp2->next->data > temp1->next->data)
			{
				HeadInsert(temp2, temp1->next->data);
				temp1 = temp1->next;
				if (temp2->next->next != NULL)
				{
					temp2 = temp2->next->next;
				}
				else
				{
					break;
				}
			}
			else
			{
				HeadInsert(temp2->next, temp1->next->data);
				temp1 = temp1->next;
				if (temp2->next->next != NULL)
				{
					temp2 = temp2->next->next;
				}
				else
				{
					break;
				}
			}
		}
		return reverse(node2);
	}
}
int main()
{
	Link* node1 = InitList();
	Link* node2 = InitList();
	HeadInsert(node1, 8);
	HeadInsert(node1, 7);
	HeadInsert(node1, 5);
	HeadInsert(node1, 3);
	HeadInsert(node1, 1);
	show(node1);
	//输出1 3 5 7 8

	HeadInsert(node2, 11);
	HeadInsert(node2, 10);
	HeadInsert(node2, 9);
	HeadInsert(node2, 8);
	HeadInsert(node2, 7);
	HeadInsert(node2, 6);
	HeadInsert(node2, 4);
	HeadInsert(node2, 3);
	HeadInsert(node2, 2);
	show(node2);
	//输出2 3 4 6 7 8 9 10 11

	show(Combine(node1, node2));
	//11 10 9 8 8 7 7 6 5 4 3 3 2 1
	return 0;
}

 如果觉得有收获,点个赞👍不为过吧~

  • 12
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~在下小吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值