单链表的归并算法思路总结

算法学习 专栏收录该内容
96 篇文章 13 订阅

刚在练习的时候需要将两个递增有序的单链表进行归并处理,之前碰到这种问题,心里总是有些害怕,害怕自己不能完全考虑到所有的情况,怕自己想不明白里面的流程,怕自己做不到。。。

但是,我慢慢理解并深以为然的是:越动手去做,越得心应手。

好像应了那句,越努力越幸运。

很多情况下,生活中的其他场景里,我能够很自然,自信的去思考,去行动,但是对应到程序世界里来,就有些畏手畏脚。明明背后的逻辑,需要的领域知识,自己全都能够灵活应用,却偏偏不敢动手写代码。

我不知道你是不是曾经或者现在也有这样的困惑。这花费了我许久的时间去想通。

OK,我们还是主要聊这段代码如何从自然的归并思路平滑转换到代码中。

先看题目:

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

#include <iostream>
#include <ctime>
#include <vector>
#include <algorithm>

using namespace std;
typedef int ElemType;
#define MAX 100

typedef 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之后的结点,解释和上面的相同。

到这里,可见归并单链表并不难理解,甚至非常简单自然。
但是,只有当你自己动手做到而不只是看懂然后默念,哦原来这么简单的时候,才是你真正理解掌握的时候。

对,还想分享一段我读来差点哭泣的话,以缅怀那些烦恼的日子,告诫我:烦恼即菩提,心无挂碍,才会安然活在当下,不惊不怖不畏。

“世界上在忧虑的人永远不会是天才或者是庸人,而是介于这两者之间的人。这些人有点儿小聪明,却又不够聪明。不懒,但也不够勤奋。他们就这样在自己的位置上来回走动。这样的人是痛苦的,一生都会活在某种无形的枷锁之中。”

“行动其实就是打开枷锁的钥匙。”

以上,愿有同感的人共勉。

  • 7
    点赞
  • 3
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值