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

1,047 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

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

🥇 往期优质文章

数据结构单链表之链表介绍 | 第一套
数据结构单链表之链表与数组 | 第二套
数据结构单链表之链表插入 | 第三套
数据结构单链表之删除节点 | 第四套
数据结构单链表之删除给定位置的链表节点 | 第五套
数据结构单链表之查看数组与链表的方法 | 第六套-1
数据结构单链表之查看数组与链表的方法 | 第六套-2
数据结构单链表之查找链表的长度(迭代和递归) | 第七套
数据结构单链表之交换链表中的节点而不交换数据 | 第八套
数据结构单链表之反转链表 | 第九套

📣尾注: 想要获取更多数据结构相关的知识,你可以关注我:海拥,我希望你觉得这篇文章有帮助。

如果你看到这里,感谢你的阅读 :)

💌 欢迎大家在评论区提出意见和建议!💌

如果你真的从这篇文章中学到了一些新东西,喜欢它,收藏它并与你的小伙伴分享。🤗最后,不要忘了❤或📑支持一下哦。