七大排序的个人总结(二) 归并排序(Merge

七大排序的个人总结(二)

归并排序(Merge 

 

 

归并排序(Merge Sort):

 

归并排序是一个相当“稳定”的算法对于其它排序算法,比如希尔排序,快速排序和堆排序而言,这些算法有所谓的最好与最坏情况。而归并排序的时间复杂度是固定的,它是怎么做到的?

两个有序数组的合并:

首先来看归并排序要解决的第一个问题:两个有序的数组怎样合成一个新的有序数组:

比如数组1{ 3,5,7,8 }数组2为{ 1,4,9,10 }:

首先那肯定是创建一个长度为8的新数组咯,然后就是分别从左到右比较两个数组中哪一个值比较小,然后复制进新的数组中:比如我们这个例子:

{ 3,5,7,8 } { 1,4,9,10 }  {  }一开始新数组是空的。

然后两个指针分别指向第一个元素,进行比较,显然,1比3小,所以把1复制进新数组中:

{ 3,5,7,8 } { 1,4,9,10 }  { 1, }

第二个数组的指针后移,再进行比较,这次是3比较小:

{ 3,5,7,8 } { 1,4,9,10 }  { 1,3, }

同理,我们一直比较到两个数组中有某一个先到末尾为止,在我们的例子中,第一个数组先用完。{ 3,5,7,8 } { 1,4,9,10 }  { 1,3,4,5,7,8 }

最后把第二个数组中的元素复制进新数组即可。

{ 1,3,4,5,7,8,9,10 }

由于前提是这个两个数组都是有序的,所以这整个过程是很快的,我们可以看出,对于一对长度为N的数组,进行合并所需要的比较次数最多为2 * N -1(这里多谢园友@icyjiang的提醒)

 

这其实就是归并排序的最主要想法和实现,归并排序的做法是:

将一个数组一直对半分,问题的规模就减小了,再重复进行这个过程,直到元素的个数为一个时,一个元素就相当于是排好顺序的。

接下来就是合并的过程了,合并的过程如同前面的描述。一开始合成两个元素,然后合并4个,8个这样进行。

所以可以看到,归并排序是“分治”算法的一个经典运用。

我们可以通过代码来看看归并算法的实现:

复制代码
public static int[] sort(int[] array, int left, int right) {if (left == right) {return new int[] { array[left] };}int mid = (right + left) / 2;int[] l = sort(array, left, mid);int[] r = sort(array, mid + 1, right);return merge(l, r);}// 将两个数组合并成一个public static int[] merge(int[] l, int[] r) {int[] result = new int[l.length + r.length];int p = 0;int lp = 0;int rp = 0;while (lp < l.length && rp < r.length) {result[p++] = l[lp] < r[rp] ? l[lp++] : r[rp++];}while (lp < l.length) {result[p++] = l[lp++];}while (rp < r.length) {result[p++] = r[rp++];}return result;}
复制代码

代码量其实也并不多,主要的工作都在合并两个数组上。从代码上看,

if (left == right) {return new int[] { array[left] };}

这个是递归的基准(base case),也就是结束的条件是当元素的个数只有一个时。

int mid = (right + left) / 2;int[] l = sort(array, left, mid);int[] r = sort(array, mid + 1, right);

这一部分显然就是分(divide),将一个大问题分成小的问题。

最后也就是治(conquer)了,将两个子问题的解合并可以得到较大问题的解。

所以可以说,归并排序是说明递归和分治算法的经典例子。

 

然后就又要回到比较原始的问题了,归并排序它为什么会快呢?

想回答这个问题可以先想一下之前说过的提高排序速度的两个重要的途径:一个是减少比较次数,一个是减少交换次数。

对于归并排序而言,我们来从之前的例子应该可以看到,两个数组的合并过程是线性时间的,也就是说我们每一次比较都可以确定出一个元素的位置。这是一个重要的性质。

我们来看一个可以用一个例子来体会一下假如有这样一个数组{ 3,7,2,5,1,0,4,6 },

冒泡和选择排序的比较次数是25次。

直接插入排序用了15次。

而归并排序的次数是相对稳定的,由我们上面提到的比较次数的计算方法,我们的例子要合并4对长度为1的,2对长度为2的,和1对长度为4的。

归并排序的最多的比较次数为4 * 1 + 2 * 3 + 7 = 17次。(感谢@icyjiang的提醒

 

再次说明一下,这个例子依然只是为了好理解,不能作为典型例子来看。

因为元素的随机性,直接插入排序也可能是相当悲剧的。但我们应该从中看到的是归并排序在比较次数上的优势

至于在种优势是怎么来的,我个人不成熟的总结一下,就是尽量的让上一次操作的结果为下一次操作服务

我们每一次合并出来的数组,是不是就是为下一次合并做准备的。因为两个要合并的数组是有序的,我们才可能高效地进行合并。

 

快速排序(Quick Sort):

这个算法的霸气程度从它的名字就可以看出来了。快速排序的应用也是非常广的的,各种类库都可以看到他的身影。这当然与它的“快”是有联系的,正所谓天下武功唯快不破。

快速排序的一个特点是,对数组的一次遍历,可以找到一个枢纽元(pivot)确定位置,还可以把这个数组以这个枢纽元分成两个部分,左边的元素值都比枢纽元小,右边的都比枢纽元大。我们递归地解决这两个子数组即可。

我们还是通过一个特殊的例子来看一下快速排序的原理:

我们假设有这样一个数组{ 4,7,3,2,8,1,5 }

对于快速排序来说,第一步就是找出一个枢纽元,而对于枢纽元的寻找是对整个算法的时间性能影响很大的,因为搞不好快速排序会退化成选择排序那样

对于这个不具有代表性的例子,我们选择的是第一个元素做为枢纽元。

pivot 4

{ 4,7,3,2,8,1,5 }

其中,红色为左指针,蓝色为右指针。一开始我们从右边开始,找到第一个比pivot小的数。停止,然后将该值赋给左指针,同样,左指针向右移动。

也就是说我们第一次得到的的结果是这样的:

{ 1,7,3,2,8,1,5 }

同样的道理,我们在左边找到一个比pivot大的值,赋值给右指针,同时右指针左移一步

得到的结果应该是这样的:

{ 1,7,3,2,8,7,5 }

请注意,我们的这个移动过程的前提都是左指针不能超过右指针的前提下进行的。

这两个过程交替进行,其实就是在对元素进行筛选。这一次得到的结果是:

{ 1,2,3,2,8,7,5 }

黄色高亮表示两个指针重叠了,这时候我们也就找到了枢纽元的位置了,将我们的枢纽元的值插入。

也就是说,我们接下来的工作就是以这个枢纽元为分割,对左右两个数组进行同样的排序工作。

来看看具体的代码是怎么实现的:

复制代码
public static void sort(int[] array, int start, int end) {if (start >= end) {return;}int left = start;int right = end;int temp = array[left];while (left < right) {while (left < right && temp < array[right]) {right--;}if (left < right) {array[left] = array[right];left++;}while (left < right && temp > array[left]) {left++;}if (left < right) {array[right] = array[left];right--;}}array[left] = temp;sort(array, start, left - 1);sort(array, left + 1, end);}
复制代码

 

接下来还是同样的问题,快速排序为什么会快呢?如果没有足够的强大,那不是“浪得虚名”吗? 

首先还是看看前面的例子。

首先可以比较容易感受到的就是元素的移动效率高了。比如说例子中的1,一下子就移动到了前面去。

这也是我个人的一点感受,只是觉得可以这样理解比较高效的排序算法的特性:

高效的排序算法对元素的移动效率都是比较高的。

它不像冒泡,直接插入那样,每次可能都是步进一步,而是比较快速的移动到“感觉是正确”的位置。

想想,希尔排序不就是这么做的吗?后面的堆排序也是这个原理。

 

其次,快速排序也符合我们前面说的,“让上一个操作的结果为下一次操作服务”。

很明显,在枢纽元左边的元素都比枢纽元要小,右边的都比枢纽元大。显然,数据的范围小了,数据的移动的准确性就高了。

 

但是,快速排序的一个隐患就是枢纽元的选择,我提供的代码中是选第一个元素做枢纽元,这是一种很冒险的做法

比如我们对一个数组{ 9,8,7,6,5 }想通过快速排序来变成从小到大的排序。如果还是选择以第一个元素为枢纽元的话,快速排序就变成选择排序了。

所以,在实际应用中如果数据都是是随机数据,那么选择第一个做枢纽元并没有什么不妥。因为这个本来就是看“人品”的。

但是,如果是对于一些比较有规律的数据,我们的“人品”可能就不会太好的。所以常见的有两种选择策略:

一种是使用随机数来做选择。呵呵,听天由命。

另一种是取数组中的第一个,最后一个和中间一个,选择数值介于最大和最小之间的

这一种又叫做“三数中值分割法”。理论上,这两种选择策略还是可能很悲剧的。但概率要小太多了。

 

堆排序用文字太难看懂了,想画一些图来帮助理解,求各位大大推荐可以比较方便画二叉树的工具。

分类: 数据结构

转载于:https://www.cnblogs.com/wangprince2017/p/7663457.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/399596.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从Eclipse转移到IntelliJ IDEA一点心得

本人使用IntelliJ IDEA其实并不太久&#xff0c;用了这段时间以后&#xff0c;觉得的确很是好用。刚刚从Eclipse转过来的很多人开始可能不适应&#xff0c;我就把使用过程中的一些经验和常用功能分享下&#xff0c;当然在看这篇之前推荐你先看完IntelliJ IDEA 的 20 个代码自动…

【转】教你何时开启水果机上的HDR拍照

原址&#xff1a;http://news.mydrivers.com/1/175/175922.htm 苹果在iOS 4.1操作系统中为iPhone 4增加了一项有趣的新功能&#xff1a;HDR拍照。虽然目前市场上支持HDR功能的数码相机已经不在少数&#xff0c;但能够让普通消费者注意到这一功能&#xff0c;iPhone 4依然居功至…

Python快速学习03:运算 缩进和选择

前言 系列文章&#xff1a;[传送门] 这篇昨晚本来要出的&#xff0c;去搭了帐篷&#xff0c;在学校的屋顶上。 运算 运算&#xff0c;不得不说的是运算符。 数学 , -, *, /, **, %,// 判断 , !, >, >, <, <, in 逻辑 and, or, not 数学运算符 例子 print (19) …

冯洛伊曼体系结构

布尔代数 是一种关于0 和 1 的代数系统&#xff0c;用基础的逻辑符号系统描叙物体和概念&#xff0c;是现代电子计算机的数学和逻辑基础 布尔量&#xff1a; 0 1   True, False 与&#xff1a; a, b ab a*b and 或&#xff1a; ab …

web基础,用html元素制作web页面

观察常用网页的HTML元素&#xff0c;在实际的应用场景中&#xff0c;用已学的标签模仿制作。 用div,form制作登录页面&#xff0c;尽可能做得漂亮。 练习使用下拉列表选择框&#xff0c;无序列表&#xff0c;有序列表&#xff0c;定义列表。 <!DOCTYPE html> <html la…

三级分类菜单的数据库设计

http://www.imooc.com/article/285246?block_idtuijian_wz 最近在设计一款进销存系统的时候&#xff0c;遇到一个分类的设计问题&#xff0c;就是如何将分类设计成数据库里的表&#xff0c;怎么样设计才比较灵活&#xff1f; 举个例子&#xff0c;一级分类&#xff1a;生鲜类&…

(二)单元测试利器 JUnit 4

JUnit 深入 当然&#xff0c;JUnit 提供的功能决不仅仅如此简单&#xff0c;在接下来的内容中&#xff0c;我们会看到 JUnit 中很多有用的特性&#xff0c;掌握它们对您灵活的编写单元测试代码非常有帮助。Fixture 何谓 Fixture&#xff1f;它是指在执行一个或者…

.net平台的MongoDB使用

网址&#xff1a;http://www.cnblogs.com/skychen1218/p/6595759.html 前言 最近花了点时间玩了下MongoDB.Driver&#xff0c;进行封装了工具库&#xff0c;平常也会经常用到MongoDB&#xff0c;因此写一篇文章梳理知识同时把自己的成果分享给大家。 本篇会设计到Lambda表达式的…

2018程序员最佳ssh免费登陆工具

https://www.jianshu.com/p/b29b894aa60f Linux 终端 Screenshot from 2018-09-15 00-12-41.png PAC Screenshot from 2018-09-15 00-12-00.png 参考资料 讨论qq群144081101 591302926 567351477本文涉及的python测试开发库 谢谢点赞&#xff01;本文相关海量书籍下载 Wind…

学习flex布局(弹性布局)

Flex是Flexible Box的缩写&#xff0c;意为弹性布局。是W3C早期提出的一个新的布局方案。可以便捷的实现页面布局&#xff0c;目前较高版本的主流浏览器都能兼容&#xff0c;兼容情况如下&#xff1a; Flex在移动端开发上已是主流&#xff0c;比如在h5页面&#xff0c;微信小程…

php创建无限级树型菜单以及三级联动菜单

http://www.php.cn/php-weizijiaocheng-373500.html 这篇文章主要介绍了php创建无限级树型菜单 &#xff0c;主要使用的是递归函数&#xff0c;感兴趣的小伙伴们可以参考一下 写递归函数&#xff0c;可考虑缓存&#xff0c;定义一些静态变量来存上一次运行的结果&#xff0c;多…

使用Docker镜像和仓库

为什么80%的码农都做不了架构师&#xff1f;>>> Docker镜像 由文件系统叠加而成最底端第一层是引导文件系统bootfs&#xff0c;类似grub镜像第二层是root文件系统rootfs列出镜像 huangyiHP ~ % sudo docker images REPOSITORY TAG IMAGE …

wordpress发布文章时右侧边栏选择作者的功能代码

因为本网络营销博客现在有了两个作者&#xff0c;在后台发布文章时&#xff0c;希望可以选择作者&#xff08;以前仅是一个管理员&#xff09;。通过在网上查找资料&#xff0c;并进行实践成功。特分享如下。 一 在当前使用主题目录下的functions.php中添加以下php代码&#x…

c# webbrowser  获取用户选中文字

c# webbrowser 获取用户选中文字 原文:c# webbrowser 获取用户选中文字最近一直被一个问题困扰&#xff0c;有一个文本框&#xff0c;一个webbrowser控件&#xff0c;一个上下文菜单&#xff0c; 用户用鼠标左键选中文字&#xff0c;右键点击搜索&#xff0c;就把选中的文字赋…

cannot be deleted directly via the port API: has device owner network:floatingip

cannot be deleted directly via the port API: has device owner network:floatingip posted on 2015-10-13 19:26 秦瑞It行程实录 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/ruiy/p/4875605.html

c# params 工作原理。

2019独角兽企业重金招聘Python工程师标准>>> 高级语言之所以成为高级语言&#xff0c;编译器付出了很多。params在给我们带来方便的同时&#xff0c;编译器也是贡献不少。 实例代码&#xff1a; using System; public class Test { public static void Main(){ C…

这7个在线工具,帮助您快速开发WordPress主题

由于WordPress遵从GPL协议&#xff0c;这个协议规定WordPress可以免费用于商业用途。这样做一个网站的成本就比较低。相对应的开发WordPress主题的需求就多起来了。结合笔者多年开发WordPress主题的经验&#xff0c;熟练使用下面一些免费的工具&#xff0c;在开发WordPress主题…

不用第三方插件如何统计自己wordpress的访问量

很多wordpress博主都很在乎自己的访问量&#xff0c;使用第三方统计插件又会拖慢自己博客的响应速度&#xff0c;下面就交给大家如何自己写代码统计自己博客的访问量。 工具/原料 wordpress站点 自己站点的各种权限 方法/步骤 登录自己博客的后台&#xff0c;输入正确的用户…

WordPress导航菜单函数register_nav_menus() 和 wp_nav_menu()

导航菜单是每一个WordPress主题必须的元素&#xff0c;如果你要制作一个WordPress主题&#xff0c;那就必须熟悉WordPress导航菜单注册函数 register_nav_menus() 和 导航菜单调用函数wp_nav_menu() &#xff0c;这两个参数一般都是配合使用的。今天我们就一起来解释一下这两个…

基因重组

1s / 32M 【问题描述】目前,科学家们正致力于对生物基因的重组进行深入研究。基因的物质载体是脱氧核糖核酸(DNA)。DNA 是一种仅由 A、T、G、C 四种基元构成的双螺旋结构的有机分子。DNA 的两条单链上,同一位置的两个基元是互相对应的。A 对 T,G 对 C,因此,我们只需用任意一条链…