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

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

编写一个 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 是要合并的两个列表的长度。

目录
相关文章
|
2月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
67 4
|
5天前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
25 10
|
5天前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
32 10
|
5天前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
24 7
|
5天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
22 5
|
19天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
80 5
|
2月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
130 4
|
2月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
2月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法