📂排序
🔖排序
2022-09-03 23:14阅读: 38评论: 0推荐: 0

归并排序

归并排序也是属于效率较高的排序,时间复杂度O(N logn),而且无论最好最坏情况都是O(N logn)
但是需要额外的O(N)的临时空间存放排序后的数组,这么说来是外部排序为不是内部排序
采用了分治的思想,

  1. 先分将数组元素分为最小只有一个元素的单位

分成两部分,称为二路归并,此外还有三路归并

  1. 再并:将每两组中的元素合并为一个有序数组
    结构上很类似于一颗完全二叉树,也可以采用迭代和递归两种写法

应用题目

代码实现

伪代码

递归实现

递归实现在于每一步合并都要copy一遍两个待合并,需要额外的空间
有一个问题在于,为什么空间复杂度是O(n)而不是O(n logn)?

我是这样分析的
每一次合并都要将两个待合并的数组复制一遍,那么相当于,每一层都将n个元素(整个数组)复制了一遍
那么一共多少层?按照二路归并的话log_2 n
也就是说空间复杂度应该是O(n logn)才对

解释是这样的:

实际上,递归代码的空间复杂度并不能像时间复杂度那样累加。刚刚我们忘记了最重要的一点,那就是,尽管每次合并操作都需要申请额外的内存空间,但在合并完成之后,临时开辟的内存空间就被释放掉了。
在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是O(n)

/*
* 归并排序
* 递归实现 来自GeeksForGeeks
* 把静态数据改成了动态数组的实现
*/
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
/*
* 并的每一步都copy了左右两个临时数组,然后遍历两个临时数组,把较小的放到原数组的响应位置中去
* 如果其中一个临时数组遍历完了,就把另一个临时数组中剩下的全部copy到原数组中去
*/
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> tempLeft(arr.begin()+left, arr.begin() + mid+1);// 不包括mid+1
vector<int> tempRight(arr.begin() + mid+1, arr.begin()+right+1);
//for (int i : tempLeft)cout << i << " ";
//cout << endl;
//for (int j : tempRight)cout << j << " ";
int i = 0, j = 0, k = left;
while (i < tempLeft.size() && j < tempRight.size()) {
if (tempLeft[i] < tempRight[j]) arr[k++] = tempLeft[i++];
else arr[k++] = tempRight[j++];
}
while (i < tempLeft.size()) {
arr[k] = tempLeft[i];
k++;
i++;
}
while (j < tempRight.size()) {
arr[k] = tempRight[j];
k++;
j++;
}
}
void mergeSort(vector<int> &arr,int left,int right) {
if (left < right) {
// same as (left+right)/2 but avoid overflow for INT_MAX
int mid = left + (right - left) / 2;
// 递归地去分左半边和右半边,并合并
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 合并左半边和右半边
merge(arr, left, mid, right);
}
}
int main() {
vector<int> nums = { 4,6,5,1,7,9,3 };
mergeSort(nums, 0, nums.size() - 1);
// merge(nums, 0, 3, 6);
for (int i : nums) cout << i << " ";
return 0;
}

迭代实现

采用自底向上的迭代写法可以优化额外的空间复杂度,使之达到O(1)
迭代和归并的不同主要体现在merge_sort()排序主函数中分的过程

来自菜鸟教程

递归版本

这个版本的写法很不一样,

  1. 首先,它每次都copy构造了两个子数组,然后再从这两个子数组中挑元素往原数组放
  2. 构造的两个子数组容量都+1,并且设置末尾值为max值,为了比较大小的时候方便

感觉不是很常规,同时创建了更多的数组,需要更多的空间与时间
感觉效率很差
但是非常好理解

// 合并操作
void Merge(vector<int>& arr, int front, int mid, int end) {
// 这是个什么构造方式,是类似于传入一个vector,然后copy一份对吧
vector<int> LeftSubArr(arr.begin() + front, arr.begin() + mid + 1);
// 这个很诡异,其实是从第一个参数开始,到第二个参数-1的复制
vector<int> RightSubArr(arr.begin() + mid + 1, arr.begin() + end + 1);
// 左半部分的指针和右半部分的指针
int idxLeft = 0, idxRight = 0;
// 后面的意思是返回编译器允许的int最大值
// 在数组末尾插入int最大值?方便后面比较,相当于是个辅助元素
LeftSubArr.insert(LeftSubArr.end(), numeric_limits<int>::max());
RightSubArr.insert(RightSubArr.end(), numeric_limits<int>::max());
// Pick min of LeftSubArray[idxLeft] and RightSubArray[idxRight], and put into Array[i]
for (int i = front; i <= end; i++) {
if (LeftSubArr[idxLeft] < RightSubArr[idxRight]) {
arr[i] = LeftSubArr[idxLeft];
idxLeft++;
}
else {
arr[i] = RightSubArr[idxRight];
idxRight++;
}
}
}
// 手写归并排序
// 待排序的数组,待排序序列的起始下标、结束下标
void MergeSort(vector<int>& arr, int front, int end) {
if (front >= end) return;// 当数组长度不合法或者数组长度为1时不理会
int mid = (front + end) / 2;
MergeSort(arr, front, mid);// 处理左半边
MergeSort(arr, front, mid);// 处理右半边
Merge(arr, front, mid, end);
}

本文作者:YaosGHC

本文链接:https://www.cnblogs.com/yaocy/p/16387362.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   YaosGHC  阅读(38)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起