单链表的归并算法思路总结
来源:互联网 发布:java入门书籍 编辑:程序博客网 时间:2024/05/22 00:09
刚在练习的时候需要将两个递增有序的单链表进行归并处理,之前碰到这种问题,心里总是有些害怕,害怕自己不能完全考虑到所有的情况,怕自己想不明白里面的流程,怕自己做不到。。。
但是,我慢慢理解并深以为然的是:越动手去做,越得心应手。
好像应了那句,越努力越幸运。
很多情况下,生活中的其他场景里,我能够很自然,自信的去思考,去行动,但是对应到程序世界里来,就有些畏手畏脚。明明背后的逻辑,需要的领域知识,自己全都能够灵活应用,却偏偏不敢动手写代码。
我不知道你是不是曾经或者现在也有这样的困惑。这花费了我许久的时间去想通。
OK,我们还是主要聊这段代码如何从自然的归并思路平滑转换到代码中。
先看题目:
假设两个递增有序的线性表,均以单链表形式存储。将两个单链表归并为按元素递减次序排列的单链表,并要求:利用原来的结点存储。
#include <iostream>#include <ctime>#include <vector>#include <algorithm>using namespace std;typedef int ElemType;#define MAX 100typedef struct Node{ ElemType data; struct Node *next;}Node, *List;// 生成一个链表,数值随机生成// 返回指向生成链表的头结点指针List generateList(int n){ srand(n); // 定义头结点 List Head = (List)malloc(sizeof(Node)); Head->next = NULL; Node *temp = Head; //使用temp拿着L的位置,为的是不改变L的数值 // 先通过vector建立一个递增有序的数列 vector<int> ins; for(int i = 0; i < n; i++) { int x = rand() % MAX; ins.push_back(x); } sort(ins.begin(), ins.end()); //尾插法建立链表 for(int i = 0; i < n; i++) { Node *s = (Node*)malloc(sizeof(Node)); s->data = ins[i]; s->next = NULL; temp->next = s; temp = s; } return Head;}void mergeList(List &H1, List &H2){ // 两个递增 ==》 一个递减 // 思路:采用头插法进行 // 用两个指针p,q分别跟踪 // 如果p指向的结点较小,就插入并将p往后移动,否则将q插入并移动 // 当一方结点为空了,就将对方的结点依次头插法插入链表直到结束 List m = (List)malloc(sizeof(Node)); m->next = NULL; Node *p = H1->next; Node *q = H2->next; while(p && q) { if(p->data <= q->data) { Node *temp = p->next;// 暂存 p->next = m->next; m->next = p; p = temp; } else { Node *temp = q->next; q->next = m->next; m->next = q; q = temp; } } while(p) { Node *temp = p->next; // 注意一定要用temp暂存,总是会不注意p->next 已经被更改了,从而陷入死循环 p->next = m->next; m->next = p; p = temp; } while(q) { Node *temp = q->next; q->next = m->next; m->next = q; q = temp; } H1 = m;}int main(){ // 两个递增的单链表 // 合并成一个递减的单链表 // 且为了节省空间,只是修改链接 int n,m; cout << "Input two numbers of nodes: "; cin >> n >> m; List H1 = generateList(n); List H2 = generateList(m); Node *p = H1->next; //指向第一个结点 while(p) { cout << p->data << " "; p = p->next; } cout << endl; Node *q = H2->next; //指向第一个结点 while(q) { cout << q->data << " "; q = q->next; } cout << endl; // 题目的主要逻辑 mergeList(H1,H2); // 返回的是H1指向的合并好的链表 // 输出合并后的结果 p = H1->next; while(p) { cout << p->data << " "; p = p->next; } cout << endl; return 0;}
如何构建一个单链表这里不会再详细解释,假设已经建好,且如题要求的递增顺序。
这里主要关注的是如何归并。
// 思路:采用头插法进行 // 用两个指针p,q分别跟踪 // 如果p指向的结点较小,就插入并将p往后移动,否则将q插入并移动 // 当一方结点为空了,就将对方的结点依次头插法插入链表直到结束
这段注释其实就完全阐述了如何归并。所以思路非常清晰,转换为代码的过程,就个人体会而言,重点是记住要暂存结点,这是导致写出死循环的一大原因。
首先是:我们新建个头部用于导航合并的链表,结束后把值给H1.
List m = (List)malloc(sizeof(Node)); m->next = NULL; Node *p = H1->next;//指向工作结点 Node *q = H2->next; //指向工作结点
头插法中p和q都不空的情况:
while(p && q) { if(p->data <= q->data) { Node *temp = p->next;// 暂存 p->next = m->next; m->next = p; p = temp; } else { Node *temp = q->next; q->next = m->next; m->next = q; q = temp; } }
用temp暂存的原因是,,头插法时当前结点p或者q的next域要指向头指向的那个结点。因此,p或q的下一个结点就要被p或q放下了,这个不能忽视,因此,需要暂时存着,p和q跟踪的就是工作结点,他们后面的结点在下一轮就是工作结点了!
而当p或者q其中一个已经为空后,对方还拿着那个没插入的结点,因此在下面的后续处理中,我们只看一种p还没空的状态:
while(p){ Node *temp = p->next; // 注意一定要用temp暂存,总是会不注意p->next 已经被更改了,从而陷入死循环 p->next = m->next; m->next = p; p = temp;}
此时从p指向的结点开始,一个一个往头结点后面插入即可。注意仍然需要暂存p之后的结点,解释和上面的相同。
到这里,可见归并单链表并不难理解,甚至非常简单自然。
但是,只有当你自己动手做到而不只是看懂然后默念,哦原来这么简单的时候,才是你真正理解掌握的时候。
对,还想分享一段我读来差点哭泣的话,以缅怀那些烦恼的日子,告诫我:烦恼即菩提,心无挂碍,才会安然活在当下,不惊不怖不畏。
“世界上在忧虑的人永远不会是天才或者是庸人,而是介于这两者之间的人。这些人有点儿小聪明,却又不够聪明。不懒,但也不够勤奋。他们就这样在自己的位置上来回走动。这样的人是痛苦的,一生都会活在某种无形的枷锁之中。”
“行动其实就是打开枷锁的钥匙。”
以上,愿有同感的人共勉。
- 单链表的归并算法思路总结
- 归并排序的思路
- 单链表的归并算法
- 归并排序及排序算法的总结
- 解决算法问题的思路总结
- 算法求解方法与思路的总结
- 做算法题思路的一些总结
- 归并排序思路及算法实现
- 有序单链表的归并算法
- 常见算法思路总结
- leetcode算法思路总结
- 【算法总结】归并排序总结
- 关于全文检索的归并的算法总结:
- iOS算法总结-归并排序
- 与位运算有关的算法题思路总结
- 归并排序的算法
- 归并算法的实现
- 归并算法的实现
- logstash-out-hdfs
- echsop 之Strict Standards: Only variables should be passed by reference in******
- lua_code_cache off/on
- python mysql 学习笔记
- 猴子测试全网测试随机点击修改版
- 单链表的归并算法思路总结
- mybatis模糊查询
- Android广播sendBroadcast(intent,receiverPermission)解析
- BCG属性表单风格修改与删除“上一步”“下一步”“帮助”“确定”四个按钮
- Linux系统内存占用90%以上——解决方法
- uva 253 Cube painting
- 微信生成二维码
- Java内存模型
- Android Studio编译错误