数据结构单链表之合并两个已排序的链表 | 第十套

简介: 数据结构单链表之合并两个已排序的链表 | 第十套

编写一个 SortedMerge() 函数,该函数接受两个列表,每个列表都按升序排序,然后将这两个列表合并为一个按升序排列的列表。SortedMerge() 应该返回新列表。应该通过将前两个列表的节点拼接在一起来制作新列表。

例如如果第一个链表 a 是 5->10->15 而另一个链表 b 是 2->3->20,那么 SortedMerge() 应该返回一个指向合并链表 2-> 头节点的指针3->5->10->15->20。

有很多情况需要处理:a或b可能为空,在处理过程中a或b可能先用完,最后出现结果列表开始为空,构建的问题它在经历'a'和'b'时向上。

方法一(使用虚拟节点)

这里的策略使用临时虚拟节点作为结果列表的开始。指针 Tail 始终指向结果列表中的最后一个节点,因此添加新节点很容易。

当结果列表为空时,虚拟节点为尾部提供初始指向的对象。这个虚拟节点是有效的,因为它只是临时的,并且在堆栈中分配。循环继续,从“a”或“b”中删除一个节点,并将其添加到尾部。什么时候我们完成了,结果在 dummy.next 中。

下面是上述方法的实现:

#include <bits/stdc++.h>
using namespace std;
class Node
{
  public:
  int data;
  Node* next;
};
Node* SortedMerge(Node* a, Node* b)
{
  Node dummy;
  Node* tail = &dummy;
  dummy.next = NULL;
  while (1)
  {
    if (a == NULL)
    {
      tail->next = b;
      break;
    }
    else if (b == NULL)
    {
      tail->next = a;
      break;
    }
    if (a->data <= b->data)
      MoveNode(&(tail->next), &a);
    else
      MoveNode(&(tail->next), &b);
    tail = tail->next;
  }
  return(dummy.next);
}
void MoveNode(Node** destRef, Node** sourceRef)
{
  Node* newNode = *sourceRef;
  assert(newNode != NULL);
  *sourceRef = newNode->next;
  newNode->next = *destRef;
  *destRef = newNode;
}
void push(Node** head_ref, int new_data)
{
  Node* new_node = new Node();
  new_node->data = new_data;
  new_node->next = (*head_ref);
  (*head_ref) = new_node;
}
void printList(Node *node)
{
  while (node!=NULL)
  {
    cout<<node->data<<" ";
    node = node->next;
  }
}
int main()
{
  Node* res = NULL;
  Node* a = NULL;
  Node* b = NULL;
  push(&a, 15);
  push(&a, 10);
  push(&a, 5);
  push(&b, 20);
  push(&b, 3);
  push(&b, 2);
  res = SortedMerge(a, b);
  cout << "Merged Linked List is: \n";
  printList(res);
  return 0;
}

输出 :

Merged Linked List is: 
2 3 5 10 15 20

方法 2(使用本地引用)  

该解决方案在结构上与上述非常相似,但它避免使用虚拟节点。相反,它维护一个 struct node** 指针 lastPtrRef,该指针始终指向结果列表的最后一个指针。这解决了与虚拟节点相同的情况——在结果列表为空时处理它。如果您试图在其尾部构建一个列表,则可以使用虚拟节点或结构节点**“引用”策略(有关详细信息,请参阅第 1 节)。

Node* SortedMerge(Node* a, Node* b)
{
Node* result = NULL;
Node** lastPtrRef = &result;
while(1)
{
  if (a == NULL)
  {
  *lastPtrRef = b;
  break;
  }
  else if (b==NULL)
  {
  *lastPtrRef = a;
  break;
  }
  if(a->data <= b->data)
  {
  MoveNode(lastPtrRef, &a);
  }
  else
  {
  MoveNode(lastPtrRef, &b);
  }
  lastPtrRef = &((*lastPtrRef)->next);
}
return(result);
}

方法 3(使用递归)  

合并是那些很好的递归问题之一,其中递归解决方案代码比迭代代码清晰得多。但是,您可能不想将递归版本用于生产代码,因为它将使用与列表长度成正比的堆栈空间。

Node* SortedMerge(Node* a, Node* b)
{
  Node* result = NULL;
  if (a == NULL)
    return(b);
  else if (b == NULL)
    return(a);
  if (a->data <= b->data)
  {
    result = a;
    result->next = SortedMerge(a->next, b);
  }
  else
  {
    result = b;
    result->next = SortedMerge(a, b->next);
  }
  return(result);
}

时间复杂度: 因为我们完全遍历了两个列表。因此,时间复杂度为O(m+n) ,其中 m 和 n 是要合并的两个列表的长度。


方法 4(反转列表)

这个想法包括首先反转给定的列表,反转后,遍历两个列表直到最后,然后比较两个列表的节点,并在结果列表的开头插入具有较大值的节点。通过这种方式,我们将按递增顺序获得结果列表。

1) 将结果列表初始化为空:head = NULL。
2) 令 'a' 和 'b' 分别是第一个和第二个列表的头部。
3)反转两个列表。
4) While (a != NULL and b != NULL) 
    a) 找出两个(当前'a'和'b')中
    的较大者 b)在结果列表的前面插入节点的较大值。
    c) 在较大节点列表中向前移动。
5) 如果在 'a' 之前 'b' 变为 NULL,则将 'a' 的所有节点插入
   到结果列表的开头。
6) 如果在 'b' 之前 'a' 变为 NULL,则将 'b' 的所有节点插入
   到结果列表的开头。  

下面是上述解决方案的实现。

#include <iostream>
using namespace std;
struct Node {
  int key;
  struct Node* next;
};
Node* reverseList(Node* head)
{
  if (head->next == NULL)
    return head;
  Node* rest = reverseList(head->next);
  head->next->next = head;
  head->next = NULL;
  return rest;
}
Node* sortedMerge(Node* a, Node* b)
{
  a = reverseList(a);
  b = reverseList(b);
  Node* head = NULL;
  Node* temp;
  while (a != NULL && b != NULL) {
    if (a->key >= b->key) {
      temp = a->next;
      a->next = head;
      head = a;
      a = temp;
    }
    else {
      temp = b->next;
      b->next = head;
      head = b;
      b = temp;
    }
  }
  while (a != NULL) {
    temp = a->next;
    a->next = head;
    head = a;
    a = temp;
  }
  while (b != NULL) {
    temp = b->next;
    b->next = head;
    head = b;
    b = temp;
  }
  return head;
}
void printList(struct Node* Node)
{
  while (Node != NULL) {
    cout << Node->key << " ";
    Node = Node->next;
  }
}
Node* newNode(int key)
{
  Node* temp = new Node;
  temp->key = key;
  temp->next = NULL;
  return temp;
}
int main()
{
  struct Node* res = NULL;
  Node* a = newNode(5);
  a->next = newNode(10);
  a->next->next = newNode(15);
  a->next->next->next = newNode(40);
  Node* b = newNode(2);
  b->next = newNode(3);
  b->next->next = newNode(20);
  cout << "List A before merge: \n";
  printList(a);
  cout << "\nList B before merge: \n";
  printList(b);
  res = sortedMerge(a, b);
  cout << "\nMerged Linked List is: \n";
  printList(res);
  return 0;
}

输出:

List A before merge: 
5 10 15 40 
List B before merge: 
2 3 20 
Merged Linked List is: 
2 3 5 10 15 20 40 

时间复杂度: 因为我们完全遍历了两个列表。因此,时间复杂度为 O(m+n),其中 m 和 n 是要合并的两个列表的长度。

目录
相关文章
|
20天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
48 4
|
21天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
21天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
存储
[数据结构] -- 单链表
[数据结构] -- 单链表
26 1
|
20天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
39 0
|
1月前
|
存储
[数据结构] -- 双向循环链表
[数据结构] -- 双向循环链表
23 0
|
1月前
|
存储
数据结构(单链表)
数据结构(单链表)
14 0
|
1月前
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
49 0
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
31 0
|
1月前
|
存储
探索数据结构:便捷的双向链表
探索数据结构:便捷的双向链表

热门文章

最新文章

下一篇
无影云桌面