在学习单链表的应用时,我们应该把单链表的基础知识学明白,可以参考下面这篇文章~
单链表(含完整C代码)https://blog.csdn.net/weixin_54186646/article/details/123312019?spm=1001.2014.3001.5502
目录
🌟1.【问题描述】
将两个按升序排列的单链表,合并为一个按降序排列的单链表,要求利用原表的结点空间。
💓2.【问题分析】
个人认为若要在合并的过程中,直接得到降序单链表比较困难。但可以思考的是:如何将两单链表按升序合并,然后再对单链表进行逆置。
此时要注意要求为:利用原表的结点空间,即不开辟新的空间。所以,在合并与逆置的过程中只能在原有空间上进行,所以合并实为插入,而逆置需要就地。
思路:
- 合并:将表1元素按升序插入表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;
}
如果觉得有收获,点个赞👍不为过吧~