归并排序
归并排序也是属于效率较高的排序,时间复杂度O(N logn)
,而且无论最好最坏情况都是O(N logn)
但是需要额外的O(N)
的临时空间存放排序后的数组,这么说来是外部排序为不是内部排序
采用了分治的思想,
- 先分将数组元素分为最小只有一个元素的单位
分成两部分,称为二路归并,此外还有三路归并
- 再并:将每两组中的元素合并为一个有序数组
结构上很类似于一颗完全二叉树,也可以采用迭代和递归两种写法
应用题目
代码实现
伪代码
递归实现
递归实现在于每一步合并都要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()
排序主函数中分的过程
来自菜鸟教程
递归版本
这个版本的写法很不一样,
- 首先,它每次都copy构造了两个子数组,然后再从这两个子数组中挑元素往原数组放
- 构造的两个子数组容量都+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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步