小白初识 - 归并排序(MergeSort)
归并排序是一种典型的用分治的思想解决问题的排序方式。
它的原理就是:将一个数组从中间分成两半,对分开的两半再分成两半,直到最终分到最小的单位(即单个元素)的时候,
将已经分开的数据两两合并,并且在合并的同时进行排序(先分解,再合并)。

将一个大的问题分而治之,拆分成若干个小问题,这就是分治的思想。
拆分不成问题,但是合并的时候稍微麻烦一些。合并的时候需要对合并的数据挨个儿排序。
我用Java实现了归并排序:
package com.structure.sort; /**
* @author zhangxingrui
* @create 2019-01-24 21:22
**/
public class MergeSort { public static void main(String[] args) {
int[] numbers = {9, 1, 2, 8, 7, 3, 6, 4, 3, 5, 0, 9, 19, 39, 25, 34, 17, 24, 23, 34, 20};
// 归并排序借助递归来实现,重要的是要找到递归的终结条件(不然容易发生堆栈异常)
// 递推公式:mergeSort(p...r) = merge(p, q) + merge(q+1, r)
// 终结条件:p >= r
mergeSort(numbers, 0, numbers.length - 1);
for (int number : numbers) {
System.out.println(number);
}
} /**
* @Author: xingrui
* @Description: 归并排序
* @Date: 21:26 2019/1/24
*/
private static void mergeSort(int[] numbers, int p, int r){
if(p >= r)
return;
int q = (p + r) / 2;
mergeSort(numbers, p, q);
mergeSort(numbers, q + 1, r);
merge(numbers, p, q, r);
} /**
* @Author: xingrui
* @Description: 合并数组
* @Date: 21:35 2019/1/24
*/
private static void merge(int[] numbers, int p, int q, int r){
int[] temp = new int[r - p + 1];
int i = p;
int j = q + 1;
int k = 0; while (i <= q && j <= r){
if(numbers[i] <= numbers[j]){
temp[k++] = numbers[i++];
}else{
temp[k++] = numbers[j++];
}
} while (i <= q)
temp[k++] = numbers[i++]; while (j <= r)
temp[k++] = numbers[j++]; for (int number : temp) {
numbers[p++] = number;
}
} }
像上面的代码看到的,拆分的时候都是从中间拆分:
private static void mergeSort(int[] numbers, int p, int r){
if(p >= r)
return;
int q = (p + r) / 2;
mergeSort(numbers, p, q);
mergeSort(numbers, q + 1, r);
merge(numbers, p, q, r);
}
所以这里每次都要(p + r) / 2 得到我们想要的中间位置。
代码实现起来很容易,那么我们再来分析一下归并排序:
1.归并排序是稳定的排序算法吗(即数组中有相同的元素,在排序结束时候,相同的元素的前后关系并没有发生变化)?
2.归并排序是原地排序吗(空间复杂度为O(1)就可以叫做原地排序)?
3.归并排序的时间复杂度怎么计算?
解答:
1.归并排序是稳定的排序的算法。我们可以看看发生元素位置交换的地方:
while (i <= q && j <= r){
if(numbers[i] <= numbers[j]){
temp[k++] = numbers[i++];
}else{
temp[k++] = numbers[j++];
}
}
i比j小,所以numbers[i]永远在numbers[j]的前面,当numbers[i] = numbers[j]的时候,我们是把numbers[i]放到了temp里面,
所以归并排序下来,相同的元素的前后关系没有发生变化。
2.归并排序不是原地排序。这个很明显,在merge过程中需要申请一个temp数组来临时存储数据,而这个temp数组大小不确定。
3.归并排序的时间复杂度是O(nlogn)。这个就需要推导一下了:
首先我们得知道归并排序怎么用表达式表达出来:
T(p...r) = T(p...q) + T(q+1...r) + k,其中k是合并两段数组需要的时间。
假设我们对n个元素进行排需要的时间为:T(n),那么对n/2个数组排序需要的时间就是T(n/2),合并数组的时间复杂度就是O(n)
我们可以得出以下公式:
T(1) = C (C表示一个常量级别的时间)
T(n) = 2 * T(n/2) + n
以此类推:
T(n) = 2*T(n/2) + n
= 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
= 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
......
= 2^k * T(n/2^k) + k * n
......
得到了2^k * T(n/2^k) + k * n,那么当T(n/2^k) = 1的时候,即n/2^k = 1,k=log2n,
再将k值代入2^k * T(n/2^k) + k * n中可得:T(n) = Cn + nlog2n,根据时间复杂的计算原则,取大的数nlog2n,
所以归并排序的时间复杂的就是O(nlogn),并且从上面的代码中也可以看到对于归并排序而言,它的执行效率与
要执行排序的数组的有序度无关,即最大最小平均时间复杂度都是O(nlogn)。
小白初识 - 归并排序(MergeSort)的更多相关文章
- 归并排序 MergeSort
今天第一次看懂了严奶奶的代码( ̄▽ ̄)~*,然后按照厌奶那的思路进行了一波coding,稍加调试后即可跑起来. 学习链接:排序七 归并排序.图解排序算法(四)之归并排序 merge函数:将两个有序序列 ...
- 排序算法THREE:归并排序MergeSort
/** *归并排序思路:分治法思想 O(nlogn) * 把数组一分为二,二分为四 * 四和为二,二和为一 * */ /** * 归并排序主方法 *@params 待排序的数组 *@params 初始 ...
- 普林斯顿大学算法课 Algorithm Part I Week 3 归并排序 Mergesort
起源:冯·诺依曼最早在EDVAC上实现 基本思想: 将数组一分为(Divide array into two halves) 对每部分进行递归式地排序(Recursively sort each ha ...
- 分治法——归并排序(mergesort)
首先上代码. #include <iostream> using namespace std; int arr[11]; /*两个序列合并成一个序列.一共三个序列,所以用 3 根指针来处理 ...
- [图解算法] 归并排序MergeSort——<递归与分治策略>
#include"iostream.h" void Merge(int c[],int d[],int l,int m,int r){ ,k=l; while((i<=m)& ...
- 《算法导论》归并排序----merge-sort
伪代码请见<算法导论>2.3节 merge-sort实现: public class MergeSort { public static void sort(double [ ...
- 小白初识 - 基数排序(RadixSort)
基数排序算是桶排序和计数排序的衍生吧,因为基数排序里面会用到这两种其中一种. 基数排序针对的待排序元素是要有高低位之分的,比如单词adobe,activiti,activiti就高于adobe,这个是 ...
- 小白初识 - 快速排序(QuickSort)
我个人觉得快速排序和归并排序有相似之处,都是用到了分治的思想,将大问题拆分成若干个小问题. 不同的地方是归并排序是先把大问题拆分好了之后再排序,而快速排序则是一边拆分,一边排序. 快速排序的原理就是, ...
- 算法Sedgewick第四版-第1章基础-2.1Elementary Sortss-006归并排序(Mergesort)
一. 1.特点 (1)merge-sort : to sort an array, divide it into two halves, sort the two halves (recursivel ...
随机推荐
- PAT——1057. 数零壹
给定一串长度不超过105的字符串,本题要求你将其中所有英文字母的序号(字母a-z对应序号1-26,不分大小写)相加,得到整数N,然后再分析一下N的二进制表示中有多少0.多少1.例如给定字符串“PAT ...
- 并发编程(三)------并发类容器Copy-On-Write容器
Copy-On-Write简称COW,是一种用于程序设计中的优化策略.JDK里的COW容器有两种: CopyOnWriteArrayList CopyOnWriteArraySet CopyOnWri ...
- [原创]HBase学习笔记(2)- 基本操作
1.使用hbase shell连接hbase 2.输入help可以查看帮助 3.输入list查看当前hbase中的所有表 4.使用create创建表test 其中test是表名,cf是列族.该表只创建 ...
- iOS 隐私政策
我们深知个人信息对您的重要性,您的信任对我们非常重要.本应用尊重并保护所有使用服务用户的个人隐私权.您在使用我们的产品与/或服务时,我们可能会收集和使用您的相关信息.我们将按法律法规要求,采取相应安全 ...
- 【最新最全】为 iOS 和 Android 的真机和模拟器编译 Luajit 库
编译 Luajit 库,的确是一个挑战.因为官网的教程,在当前版本的 Xcode 和 NDK 环境中,已经不适用了.以前只是编译了适用于真机的 Luajit 库.最近在尝试编译模拟器 Luajit 库 ...
- ionic ios 打包 真机测试常见问题
1.ionic 项目在windows下正常打包安卓包时 迁移到mac下打包ios时 不需要复制平台目录platforms即可 不用再mac下去安装各种插件信息 2.ionic 下不能访问api信 ...
- 前端用node+mysql实现简单服务端
node express + mysql实现简单服务端前端新人想写服务端不想学PHP等后端语言怎么办,那就用js写后台吧!这也是我这个前端新人的学习成果分享,如有那些地方不对,请给我指出. 1.准备工 ...
- Angular4 自制分页控件
过年后第一波,自制的分页控件,可能功能没有 PrimeNG 那么好,但是基本可以实现自定义翻页功能,包括:首页/最后一页/上一页/下一页. 用户可以自定义: 1. 当前默认页码(如未提供,默认为第一页 ...
- centos6.9安装虚拟机kvm
说明 以下所有操作都基于centos6.9 查看系统是否支持虚拟化 结果有vmx|svm才支持虚拟化 egrep '(vmx|svm)' --color=always /proc/cpuinfo 系统 ...
- 批量复制windows文件夹下所有文件名
第一步,打开文件夹 第二步,在该文件夹下新建一个txt文件,然后将“.txt”后缀名修改为“.bat” txt文件内容“DIR *.* /B >LIST.TXT” 第三步,双击“.bat”,直接 ...