排序算法详解:归并排序及其在链表排序中的应用
创作时间:
2025-01-22 07:11:48
作者:
@小白创作中心
排序算法详解:归并排序及其在链表排序中的应用
归并排序是一种高效的排序算法,它采用分治策略将数组分解成更小的子数组,然后将这些子数组合并成一个有序数组。本文将详细介绍归并排序的基本思想、步骤、代码实现、时间复杂度和空间复杂度分析,以及如何使用归并排序对链表进行排序。
思想:
归并排序的基本思想是将两个或多个有序的数组合并成一个有序数组。单个元素可以看作是一个有序数组。
步骤:
- 将数组分解,直到每个子数组只包含一个元素(因为单个元素的数组默认是有序的)。
- 将两两有序的子数组进行合并,重复此步骤,直到整个数组排序完成。
实现代码
#include <iostream> // 包含输入输出流头文件
#include <vector> // 包含vector头文件
using namespace std; // 使用标准命名空间
// 定义Merge函数,用于将两个有序部分合并
void Merge(vector<int>& vec, int l, int mid, int r) {
vector<int> temp; // 创建临时向量用于存储合并后的结果
int i = l; // 初始化左边部分的起始索引
int j = mid + 1; // 初始化右边部分的起始索引
// 将左右两部分按照大小顺序合并到temp中
while (i <= mid && j <= r) {
if (vec[i] <= vec[j]) {
temp.push_back(vec[i]);
i++;
} else {
temp.push_back(vec[j]);
j++;
}
}
// 将左边剩余的元素拷贝到temp中
while (i <= mid) {
temp.push_back(vec[i]);
i++;
}
// 将右边剩余的元素拷贝到temp中
while (j <= r) {
temp.push_back(vec[j]);
j++;
}
// 将合并后的结果拷贝回原始数组vec中的[l, r]区间
i = l;
for (auto x : temp) {
vec[i++] = x;
}
}
// 定义Mergesort函数,用于归并排序
void Mergesort(vector<int>& vec, int l, int r) {
// 若当前区间只有一个元素,则返回
if (l >= r) return;
// 计算中间位置
int mid = (l + r) / 2;
// 分别对左右两部分进行归并排序
Mergesort(vec, l, mid);
Mergesort(vec, mid + 1, r);
// 合并左右两部分
Merge(vec, l, mid, r);
}
int main() {
// 初始化待排序数组
vector<int> vec{ 5,7,3,9,4,8,2,9,1 };
int n = vec.size(); // 获取数组大小
// 对数组进行归并排序
Mergesort(vec, 0, n - 1);
// 输出排序后的结果
for (auto x : vec) {
cout << x << " ";
}
return 0;
}
时间复杂度分析:
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。
空间复杂度:
O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。
扩展:(使用归并排序的思想进行链表排序)
首先将待排序的数组转换为单链表,然后使用归并排序对链表进行排序,最后输出排序后的结果。
代码实现:
#include <iostream>
#include <vector>
using namespace std;
// 定义单链表节点结构
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
// 将链表切割成前i个节点和后面的部分,并返回后面部分的头指针
ListNode* cut(ListNode* node, int i) {
ListNode* p = node;
// 找到第i个节点
while (--i && p) {
p = p->next;
}
// 若未找到或p为空,返回nullptr
if (p == nullptr) return nullptr;
ListNode* nxt = p->next;
// 切断前i个节点和后面的部分
p->next = nullptr;
return nxt;
}
// 合并两个有序链表,并返回合并后的链表头指针
ListNode* merge(ListNode* l, ListNode* r) {
ListNode* dummyhead = new ListNode; // 创建虚拟头节点
ListNode* p = dummyhead; // 指针p指向虚拟头节点
// 遍历两个链表,按照升序依次将节点连接到p的后面
while (l && r) {
if (l->val <= r->val) {
p->next = l;
l = l->next;
p = p->next;
} else {
p->next = r;
r = r->next;
p = p->next;
}
}
// 将剩余的节点连接到p的后面
if (l) {
p->next = l;
}
if (r) {
p->next = r;
}
return dummyhead->next; // 返回合并后的链表头指针
}
// 对链表进行归并排序
ListNode* ListMergeSort(ListNode* head) {
int length = 0; // 记录链表长度
ListNode* p = head; // 指针p指向链表头节点
// 计算链表长度
while (p) {
p = p->next;
length++;
}
// 创建虚拟头节点,并指向原始链表头节点
ListNode* dummyhead = new ListNode;
dummyhead->next = head;
// 外层循环,每次将链表分成长度为i的段,i从1开始,每次翻倍,直到超过链表长度
for (int i = 1; i < length; i *= 2) {
ListNode* cur = dummyhead->next; // 指针cur指向当前待合并的段的头节点
ListNode* tail = dummyhead; // 指针tail指向合并后的段的尾节点
while (cur) {
ListNode* left = cur; // 左半部分的头节点
ListNode* right = cut(left, i); // 右半部分的头节点
cur = cut(right, i); // 更新cur指向下一个待合并的段的头节点
tail->next = merge(left, right); // 将左右两部分合并后连接到已排序部分的尾部
while (tail->next) {
tail = tail->next; // 移动tail到已排序部分的尾部
}
}
}
return dummyhead->next; // 返回排序后的链表头指针
}
int main() {
// 初始化待排序数组
vector<int> vec{ 5,7,3,9,4,8,2,9,1 };
// 创建虚拟头节点
ListNode* dummyhead = new ListNode;
ListNode* cur = dummyhead;
// 将待排序数组转换为链表
for (auto x : vec) {
ListNode* newnode = new ListNode;
newnode->val = x;
cur->next = newnode;
cur = cur->next;
}
// 对链表进行归并排序
cur = ListMergeSort(dummyhead->next);
// 输出排序后的结果
while (cur) {
cout << cur->val << " ";
cur = cur->next;
}
return 0;
}
热门推荐
如何做好Web前端的考核:从目标设定到实际操作的全面指南
国家乡村振兴战略规划需要多长时间才能看到显著成效?
来一场港片“圣地巡礼”,随手一拍就是纯正港风
上了年纪,没有这3种病,多半可以活过70岁
三峡工程三十而立:长江防洪体系的“中流砥柱”
西双版纳自由行攻略和费用:一场奇幻的热带风情之旅
《缘之空》第几集第几分钟精彩?剧情介绍
手动变速箱的寿命
吃中药大便是黑色的怎么回事
面部剧痛无法饮食说话 显微血管减压手术解除三叉神经痛
AI Alchemist:掌握人工智能的职业要素
左右手无差别进攻有多难?NBA史上仅五人做到
申花豪取主场10连胜!亚冠2-0力克川崎,多一人作战显冠军相!
王氏族谱字辈排序大全(王氏家谱24辈分查询表)
桃子吃不完要怎么腌制 桃子果酱可以放多久
汽车发电机损坏:修理还是更换?
为什么会得缺铁性贫血?
人中黄:一种独特的中药材及其应用
宇宙太阳系银河系地球按顺序排列 层级关系从大到小
小孩子为什么不能吃盐?肾脏发育尚未成熟是原因之一
7种室内设计风格,哪种才是你的style?
吃什么补铬最快最好
《卡萨布兰卡》:他者之地,乱世之歌
疯子的纵火还是历史的必然——纳粹德国是如何将世界拖入深渊的?
商标对品牌发展究竟有怎样的影响力?
超短线用什么指标较为有效?这些有效指标对短线交易有何帮助?
透析患者左卡尼汀缺乏的成因分析与补充策略探讨
心脏有点肥大要怎么治疗,你知道吗?
宇宙、银河系、太阳系、地球的层级关系与银河系特点
人迎穴有降压的作用