返回 登录
0

数据结构与算法(二)合并两个有序链表

附上博客链接,欢迎大家前来交流学习
本系列的第一节概括性地简单介绍了一下数据结构和算法的概念,说实话有点虚,因为谁都知道链表和数组是什么,也都能说出散列和二叉树,但真正有难度的是,在实际开发中如何去用这些数据结构,根据不同的开发需求选择不同的数据结构和算法,才是真正需要并且很难掌握的。

以后的章节中,我都会通过一道实际编程题目或者一个场景,针对一种数据结构或者算法来解决问题,只有将数据结构和算法用来解决实际问题,才有意义,这一节我们要解决的题目是,合并两个有序链表。

题目要求:现在有两个链表A和链表B,A中元素是升序排列的,B中元素是降序排列的,现在要求将A和B合并为一个升序排列的链表。

题目的意思很明确,相信大家一眼都能看明白,不过先别着急写代码,先分析分析题目的要求,首先是用到的数据结构自然是链表了,由于给定的链表已经是有序的了,因此不需要排序,不过我们注意到,两个链表,一个是升序,一个是降序,这个需要额外注意,最后关于两个链表的合并,涉及到链表的拆分,也是个难点。综上所述,这道题的关键点有:
1、数据结构要用链表,并且是已经排序的链表
2、合并两个链表,需要注意指针的指向
话不多说,直接撸代码,作为程序员,最好的武器就是代码,先简单定义单向链表的结构体:

typedef struct ListNode
{
    ULONG NodeValue;
    ListNode *NextNode;
}ListNode;

这就是个最最简单的单链表了,里面存储的数据是32位无符号整型数据,现在我们要考虑一下如何合并两个链表,假如说两个链表都是升序排列,那么合并的方法就比较容易了,我们可以逐个比较两个链表的元素,根据大小然后较小的元素进入到新的链表中,并且指向它的下一个,这一段的代码如下:

/* 合并两个链表为一个链表 */
ListNode* MergeList(ListNode* ListHeadNodeOne, ListNode* ListHeadNodeTwo)
{
     if (NULL == ListHeadNodeOne)
     {
         return ListHeadNodeTwo;
     }
     else if (NULL == ListHeadNodeTwo)
     {
         return ListHeadNodeOne;
     }

     ListNode* ListResultNode = NULL;
     if (ListHeadNodeOne->NodeValue < ListHeadNodeTwo->NodeValue)
     {
         ListResultNode = ListHeadNodeOne;
         ListResultNode->NextNode = MergeList(ListHeadNodeOne->NextNode, ListHeadNodeTwo);
     }
     else
     {
         ListResultNode = ListHeadNodeTwo;
         ListResultNode->NextNode = MergeList(ListHeadNodeOne, ListHeadNodeTwo->NextNode);
     }

     return ListResultNode;
}

代码的思路很明确,入参就是两个链表中需要比较的结点,如果说传入的某一个结点为空,则直接返回另一个入参结点即可,如果说传入的结点均不为空,则比较两个结点所带的数据的大小,将较小的结点返回,同时要注意到,代码中用到了递归,这点比较好理解,因为对于我们需要合并的某一个结点来说,完成一次合并后,其下一个结点,自然也能用相同的方法来找到,这样就找到了递归开始的条件,作为一个递归算法,还需要找到递归结束的条件,代码一开始的判断,如果说有一个入参为空,这说明已经到链表末尾,如果说两个入参均为空,这说明已经遍历完两个链表,整个递归结束,合并完成。

合并的算法写完了,但大家别忘记,题目中给的链表,一个是升序排列,一个是降序排列,而我们的合并算法,前提是两个链表均为升序排列才行,因此我们还需要写一个将一个降序的链表反转,这部分的代码如下:

/*反转链表的全部元素*/
ListNode* InvertList(ListNode *ListHeadNode)
{
    ListNode* ListOldNode = NULL;
    ListNode* ListNewNode = NULL;
    ListNode* ListTempNode = NULL;

    ListOldNode = ListHeadNode;
    while (NULL != ListOldNode)
    {
        ListNode* ListTempNode2 = ListOldNode->NextNode;
        if (ListTempNode2 == NULL)
        {
            ListNewNode = ListOldNode;
        }
        ListOldNode->NextNode = ListTempNode;

        ListTempNode = ListOldNode;
        ListOldNode = ListTempNode2;
    }
    return ListNewNode;
}

这段代码实际上不难理解,只需要做到,记住将需要反转的结点的下一个结点暂存起来,然后将需要反转的结点与它指向的下一个结点交换指针位置,交换完毕以后,再将指针指向暂存的结点即可,这样相当于是改变了链表指针的指向,同时要注意到,返回值是原先链表的链表尾,可以通过判断下一结点是否为空,如果为空这说明到达链表末尾,此时就可以将结果返回,链表已经被反转成功。

经过上面链表的合并与反转,题目已经做完了,回顾一下这道题,不是很难,但是考察了对链表指针的掌握,如果说对于链表掌握不够熟练,在实际编程中很容易出现访问空指针这样的异常情况,因此建议大家在写代码时,要充分考虑代码的鲁棒性和健壮性,写完代码后尽可能再写一些测试用例去测一下自己的代码是否能够正常运行,顺便附上我这个程序的运行画面:
Before Merge List1 is :
Node 0, NodeValue is 0
Node 1, NodeValue is 1
Node 2, NodeValue is 2
Node 3, NodeValue is 3
Node 4, NodeValue is 4
Before Merge List2 is :
Node 0, NodeValue is 9
Node 1, NodeValue is 8
Node 2, NodeValue is 7
Node 3, NodeValue is 6
Node 4, NodeValue is 5

After Merge List is :
Node 0, NodeValue is 0
Node 1, NodeValue is 1
Node 2, NodeValue is 2
Node 3, NodeValue is 3
Node 4, NodeValue is 4
Node 5, NodeValue is 5
Node 6, NodeValue is 6
Node 7, NodeValue is 7
Node 8, NodeValue is 8
Node 9, NodeValue is 9

Process returned 0 (0x0) execution time : 0.283 s
Press any key to continue.

好了,这道题目就告一段落,想要源码的同学,可以私信联系我,或者直接在评论里留下你的邮箱,我看到后会第一时间发过去的(看样子是时候弄一个github玩玩喽),下一节我们将利用一个数组实现min栈问题,敬请期待!

评论