排序--归并排序
来源:互联网 发布:java前端开发 编辑:程序博客网 时间:2024/06/14 02:03
了解归并
归并排序算法和快速排序算法是java.util.Arrays中使用的排序算。对于一般的基本数据类型,Arrays.sort函数使用双轴快速排序算法,而对于对象类型使用归并排序(准确的说使用的是TimSort排序算法,它是归并排序的优化版本)。这样做的原因有两点,第一个原因,归并排序是稳定的,而快速排序不是稳定的。第二个原因,对于基本数据类型,排序的稳定性意义不大,但对于复合数据类型(比如对象)排序的稳定性就能帮助我们保持排序结果的某些性质。
归并排序也是一种基于分治法的排序。它是将原始无序序列划分成两个子序列,然后分别对每个子序列递归的进行排序,最后再将有序子序列进行合并。
自顶向下归并排序
自顶向下主要使用递归的思想,要保证整体序列有序,我们就应该同时保证原数组的左子序与右子序同时有序,然后将两个子序进行合并,然而要保证左子序和右子序有序,我们就要保证左子序的左右子序有序,然后进行合并;右子序的左右子序有序,然后进行合并… ….处理这种重复的工作,我们需要的就是递归。
图解自顶向下归并排序:
算法思想
- 既然我们要拆分数组,我们需要一个中枢值,它就是原数组的中点或近似中点的地方;
- 我们需要一个辅助空间,它用来保存当前需要合并的左右子序;
- 从图解中我们可以很清楚的看到,我们刚开始不断地将原数组拆分,知道保证每个子数组都有序,很显然,这个子数组就是单个元素;
- 合并子数组,将辅助数组中合并后产生的新数组覆盖至原数组,我们需要对原数组进行刷新。
下面是自顶向下的实现代码:
#include<iostream>using namespace std;#define SIZE 11class MergeTD {private: int *array_temp;public: MergeTD(int array_temp[]) { this -> array_temp = array_temp; } //我对sort函数进行了重载 void sort(int array[]); void merge(int array[], int mid, int l, int r); void sort(int array[], int l, int r);};void MergeTD :: sort(int array[]) { //进行归并排序,需要排序的数组和左右两个指标 sort(array, 0, SIZE-1);}void MergeTD :: sort(int array[], int l, int r) { //当已经拆分为单个元素或l已经大于r的时候退出当前函数 if(l >= r) return ; //计算中点值 int mid = l+(r-l)/2; //对左边进行拆分 sort(array, l, mid); //对右边进行拆分 sort(array, mid+1, r); //进行归并操作 merge(array, mid, l, r);}void MergeTD :: merge(int array[], int mid, int l, int r) { int m = l, n = mid+1; //将归并的数组存放到临时数组中 for(int k = l; k <= r; k++) { array_temp[k] = array[k]; } //归并操作在原数组上只对拆分的这一部分有效 for(int i = l; i <= r; i++) { if(m > mid) array[i] = array_temp[n++]; else if(n > r) array[i] = array_temp[m++]; else if(array_temp[n] < array_temp[m]) array[i] = array_temp[n++]; else array[i] = array_temp[m++]; }}int main(){ int array[SIZE]; //辅助数组,用来存储每个有序的小数组 int array_temp[SIZE]; MergeTD TDMerge(array_temp); for(int i = 0; i < SIZE; i++) { cin >> array[i]; } //归并排序 TDMerge.sort(array); for(int i = 0; i < SIZE; i++) { cout << array[i] << ' '; } return 0;}
效率分析
归并排序的时间主要花在了划分序列,子序列的排序过程以及合并过程,由于划分序列的时间为常数,对一个长度为n的记录序列进行归并排序,调用一趟归并排序的操作是调用n/2h次合并算法,时间复杂度为O(n)。整个归并排序需要进行logN趟归并操作,时间复杂度O(n) = NlogN。
空间复杂度:需要一个空间大小为N的辅助数组来存放待排序记录,空间杂度为O(n)。
最后,归并排序是稳定的。
自底向上归并排序
直接看图解吧:
可以看到在自顶向下归并排序中,我们先是对数组进行了不断的拆分,然后又合并,而在自底向上的归并中,我们不再对数组进行拆分,因为单个元素始终有序,所以我们只要刚开始控制子数组的长度为1,然后进行一一合并,合并后的子数组长度为2,那么我们就进行两两合并… …可以看到,自底向上的代码是比自顶向下的代码要好写的。
在这里我们需要注意的一点是数组按照归并长度划分,最后一个子数组可能不满足长度要求,这个情况需要特殊处理。自底向上一般使用循环:
#include<iostream>#include<cstdlib>#include<cstring>using namespace std;void sort(int array[], int size) { int aux[size]; //刚开始子数组长度为1,然后进行一一归并,两两归并,四四归并... ...当子数组长度大于等于原数组长度时,整个数组有序 for(int i = 1; i < size; i *= 2) { //将原数组复制到一个临时数组之中 memcpy(aux, array, 4*size); //进行归并操作,首先要判断好每次归并的start,mid,end for(int start = 0; start < size; start += i*2) { int end = start + i*2-1; int mid = (start+end)/2; int m = start, n = mid+1; //当我们对数组进行划分的时候,最后一个子数组的end有时候会大于数组长度,此时我们应该选取整体数组的长度 if(end >= size) { end = size-1; } //开始进行单趟归并 for(int k = start; k <= end; k++) { if(m > mid) { array[k] = aux[n++]; } else if(n > end) { array[k] = aux[m++]; } else if(aux[m] > aux[n]) { array[k] = aux[n++]; } else { array[k] = aux[m++]; } } } }}int main(){ int *array; int size; cin >> size; array = (int *)malloc(sizeof(int)*size); for(int i = 0; i < size; i++) { cin >> array[i]; } sort(array, size); for(int i = 0; i < size; i++) { cout << array[i] << " "; } return 0;}
- 归并排序-归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 排序::归并
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- 归并排序
- jbox 弹出多个对话框的问题解决方法
- STM32学习笔记之芯片简介
- oracle增删改字段、建表、建序列
- 编写Java程序,用三元运算符和if嵌套完成此题。
- 运行SSH框架的Javaweb项目时,提示Java.lang.ClassNotFoundException:org.apache.commons.lang.xwork.StringUtils。
- 排序--归并排序
- 实现一颗二叉树的层序遍历。
- 使用Hystrix对Dubbo消费者提供线程隔离保护
- oracle-启动、登录、关闭
- JAVA强制转换学习
- iOS手势(滑动)返回的实现(自定义返回按钮)
- Spring MVC学习笔记(一)
- POJ 2777 Count Color (线段树+状态压缩)
- ubuntu 16.04 安装php扩展