• 因为快速排序内存写的操作比归并排序少。 转载于:https://www.cnblogs.com/xuehongyang/p/5418672.html
因为快速排序内存写的操作比归并排序少。
转载于:https://www.cnblogs.com/xuehongyang/p/5418672.html
展开全文
• (2)快速排序归并排序的时间复杂度都是O(N lgN)，但是CLRS说了，实践证明快速排序的速度比归并排序的速度更为什么呢?另外其实这个结论是有限制范围的，当对数组进行排序的时候，这个结论适用。为什么对于链表，...
快速排序和归并排序的时间复杂度都是O(N lgN)，但是CLRS说了，实践证明快速排序的速度比归并排序的速度更快，为什么呢?另外其实这个结论是有限制范围的，当对数组进行排序的时候，这个结论适用。为什么对于链表，却是归并排序的速度优于快速排序呢？这里看到的一段对比说得挺好，直接抄过来。
One of the main sources of efficiency in quicksort is locality of reference, where the computer hardware is optimized so that accessing memory locations that are near one another tends to be faster than accessing memory locations scattered throughout memory.
The partitioning step in quicksort typically has excellent locality, since it accesses consecutive array elements near the front and the back. As a result, quicksort tends to perform much better than other sorting algorithms like heapsort even though it often
does roughly the same number of comparisons and swaps, since in the case of heapsort the accesses are more scattered.
Additionally, quicksort is typically much faster than other sorting algorithms because it operates in-place, without needing to create any auxiliary arrays to hold temporary values. Compared to something like merge sort, this can be a huge advantage because
the time required to allocate and deallocate the auxiliary arrays can be noticeable. Operating in-place also improves quicksort's locality.
When working with linked lists, neither of these advantages necessarily applies. Because linked list cells are often scattered throughout memory, there is no locality bonus to accessing adjacent linked list cells. Consequently, one of quicksort's huge performance
advantages is eaten up. Similarly, the benefits of working in-place no longer apply, since merge sort's linked list algorithm doesn't need any extra auxiliary storage space.
That said, quicksort is still very fast on linked lists. Merge sort just tends to be faster because it more evenly splits the lists in half and does less work per iteration to do a merge than to do the partitioning step.

展开全文
• 为什么快速排序冒泡和选择排序呢？ 先给个数学公式，对于不为零的整数m、n、k,并且满足n = m + k。一定满足如下公式：  n的平方 &gt; m的平方 + k的平方 这也是的原因。首先将n的长度分为m+k...
       一般对于随机的乱序数字进行排序，我们会选择快速排序，网上也有很多关于快速排序、冒泡、选择、希尔、归并排序的性能对比。问为什么快速排序会比冒泡和选择排序快呢？

先给个数学公式，对于不为零的整数m、n、k,并且满足n = m + k。一定满足如下公式：

n的平方 > m的平方 + k的平方

这也是快排快的原因。首先将n的长度分为m+k，然后m和k又继续分，如此递归下去，每一次递归会减少2mk的时间，这样积累下来就会非常多。
展开全文
• 本篇文章讲解三个高级排序算法，分别希尔排序、归并排序、快速排序。虽然它们的思想很复杂，但真的运用得非常得巧妙，我会用丰富的例子以及动图来让大家轻松地理解并掌握。
本系列文章【数据结构与算法】所有完整代码已上传 github，想要完整代码的小伙伴可以直接去那获取，可以的话欢迎点个Star哦~下面放上跳转链接

https://github.com/Lpyexplore/structureAndAlgorithm-JS

本篇文章来讲解一下更高级的排序算法，顾名思义，它们的排序思想一定更复杂，效率也一定比简单排序更高。为了更方便地理解高级排序算法，还是建议大家先把简单排序了解清楚，因为高级排序也多少借鉴了简单排序的思想，下面放上文章链接
【数据结构与算法】简单排序（冒泡排序、选择排序、插入排序）完整思路，并用代码封装排序函数
那么就让我们来了解一下三种高级排序算法吧
排序算法——高级排序一、希尔排序二、归并排序三、快速排序四、结束语
一、希尔排序
希尔排序是插入排序的改进版本，弥补了插入排序在某些情况下的缺点。
例如，当长度为100的数组，前面有序区域的数组长度为80，此时我们用第81个数去跟前面有序区域的所有元素比较大小，但恰巧第81个数又是这100个数里最小的，它本应该在索引为1的位置，如图所示

本例中第81个数据的值为1，那么前面有序区域里的80个元素都要往后移动一个位置，这种情况就非常影响排序性能。
因此，我们就要想办法尽可能早点让小的值靠前，让大的值靠后，这样就能避免上述情况了，这就是希尔排序要解决的问题。
希尔排序也叫做缩小增量排序，它通过先设置一个增量n，大小为数组长度的一半，将间隔为n的元素视作一个组，然后对每个组内部的元素进行从小到大进行插入排序；然后再将增量n缩小一半，再次进行分组插入排序，直到增量n为1，因为增量为1的时候，所有的元素都为同一个组了

为了方便大家理解，我用一个例子来展示一个完整的希尔排序过程，首先数据的初始状态如图所示，这里为了更好地体现希尔排序的优点，我特地把值较大的元素放到了靠左的位置，把值较小的元素放到了靠右的位置

该数组长度为8，因此我们设置初始的增量为 8 / 2 = 4，那么该数组的分组情况如下图所示：

图中颜色相同的元素为一组，每组内的各个元素间隔都为4，现在对每个组内进行从小到大排序，排序结果如下图所示：

此时我们将增量缩小一半，即 4 / 2 = 2，同样的，现在将所有元素重新组合，把所有间隔为2的元素视作一组，分组结果如下图所示：

图中颜色相同的元素为一组，每组内的各个元素间隔都为2，现在对每个组内进行从小到大排序，排序结果如下图所示：

我们继续将增量缩小一半，即 2 / 2 = 1，同样的，现在将所有元素重新组合，把所有间隔为1的元素视作一组，此时所有的元素都为同一组了，就相当于对所有的数据进行普通的插入排序，我们可以看到，对比最开始的数据，总得来说，小的值都比较靠左了，大的值也都比较靠右了，这样排序起来效率就很高了。结果如下图所示：

接下来用一个动图，演示一下完整的希尔排序全过程

了解完了希尔排序的实现过程，我们现在用代码来封装一下
function shellSort(arr) {
// 1. 获取数组长度
let length = arr.length

// 2.获取初始的间隔长度
let interval = Math.floor(length / 2)

// 3. 不断地缩小间隔的大小，进行分组插入排序
while(interval >= 1) {

// 4. 从 arr[interval] 开始往后遍历，将遍历到的数据与其小组进行插入排序
for(let i = interval; i < length; i++) {
let temp = arr[i]
let j = i
while(arr[j - interval] > temp && j - interval >= 0) {
arr[j] = arr[j - interval]
j -= interval
}

arr[j] = temp
}

// 5. 缩小间隔
interval = Math.floor(interval / 2)
}

return arr
}

来用刚才举得例子来验证一下我们封装的希尔排序是否正确
let arr = [63, 76, 13, 44, 91, 8, 82, 3]
let res = shellSort(arr)
console.log(res)
/* 打印结果
[3, 8, 13, 44, 63, 76, 82, 91]
*/

上述情况中，希尔排序最坏情况下的时间复杂度为O(n²)。其实希尔排序的时间复杂度跟增量也有关系，我们上面是通过数组长度一直取一半获取的增量，其实还有一些别的增量规则，可以使得希尔排序的效率更高，例如Hibbard增量序列、Sedgewick增量序列，本文就不对这两种增量做过多的讲解了，大家可以去网上搜索一下。
二、归并排序
归并排序的实现是使用了一种分而治之的思想，即将一个数组不断地通过两两分组的方式进行比较大小，最后直到所有元素都被分到一组里，那自然就是整体有序的了。
我们来看一下归并排序的主要思路，首先有如下图所示排列的一组数据：

首先从左往右，每两个元素视为一组，组合前要分别判断一下这两个元素的大小，小的在左，大的右，如图所示

继续再取两个元素组成一组并比较大小，如图所示：

继续上一个步骤，如图所示：

此时，原数组的所有元素都被两两分组完毕了，现在整个数组的长度变成了3，如下图所示：

此时，我们要重新从左向右，每次取两个元素组成一组，同时分别比较两个元素内的所有子元素的大小，因为此时的两个元素内部是有序的，所以我们比较两者大小的话，只需要每次比较数组的第一个元素即可，过程如下图所示：

此时原数组中只剩下一个元素了，所以就不对其做任何组合处理了，此时的数组是这样的：

此时的数组内只有两个元素了，所以我们只需要不断比较两个元素内部的子元素大小，即可获得完整的有序数组了，过程如下图所示：

这就是一个完整的归并排序的过程，接下来我们用代码来实现一下吧
function mergeSort(arr) {

// 将所有元素不断地两两组合，直到所有元素都被组合成一个组
while(arr.length > 1){
// 获取一下遍历前的数组长度，方便下面判断需要组合几次
let length = arr.length

// 创建空的新数组，用于存放所有组合后的元素
let next_arr = []

// 取两个元素进行组合，一共取 length / 2 次
for(let i = 0; i < Math.floor(length / 2); i++){
// 取出第一个元素
let left = [].concat(arr.shift())
// 取出第二个元素
let right = [].concat(arr.shift())
// 创建另一个新的空数组，用于存放组合后的所有元素
let new_arr = []

// 取两个数组中头部最小的值放到新数组中，直到一个数组为空
while(left.length > 0 && right.length > 0){
let min = left[0] > right[0]? right.shift() : left.shift()
new_arr.push(min)
}
// 将合并好的数组添加到新的数组中
next_arr.push(new_arr.concat(left.length == 0? right : left))
}
// 判断是否有一个未成组的数组
if(arr.length == 1) next_arr.push(arr[0]);

// 将所有组合后的元素构成的新数组作为下一次遍历的对象
arr = next_arr
}

// 返回完整有序数组
return arr[0]
}

我们来使用一下该方法，看看是否正确，为了方便大家理解，我在归并排序的函数里加了一条打印的代码，可以看到每次遍历后的数组情况，结果如下
let arr = [19, 97, 9, 17, 1, 8]
mergeSort(arr)

/* 打印结果：
第一次组合后：[ [ 19, 97 ], [ 9, 17 ], [ 1, 8 ] ]
第二次组合后：[ [ 9, 17, 19, 97 ], [ 1, 8 ] ]
第三次组合后：[ [ 1, 8, 9, 17, 19, 97 ] ]
*/

查看代码我们不难发现，归并排序运行起来非常得占内存，因为在组合的过程中，我们不断得在创建新的数组，然后又进行合并。但其比较次数却非常得少，只在每次合并元素时进行比较，因此归并排序的效率还是非常得高的。
三、快速排序
快速排序相信大家一定不陌生，就算没用过也一定听过，拥有这么大的名声，那么它的排序效率一定很高。而且快速排序也是面试中经常会被问到的，可能会让你当场手写哦~所以大家一定要掌握它的核心思想
快速排序也用到了分而治之的思想，它的实现思路非常得有意思：

先选一个元素作为基点pivot
将其余元素中所有比pivot小的值放到pivot的左边；将所有比pivot大的值放到pivot的右边
然后分别对pivot左边的所有元素、pivot右边的所有元素从步骤1开始排序依次，直到所有元素完整有序

思路看着很简单，那么我们来用一个例子具体看一下快速排序的全过程吧
首先有这样一组数据，如下图所示：

首先我们要选取一个元素作为基点pivot，最后排序完后，pivot左边的所有元素都是小于pivot的，右边的所有元素都是大于pivot的，因此我们希望的是尽量是两边平均一下，所以这里将采用一种方式寻找到一个大概的中点，即取数组两头的索引，分别为left 、right，再取一个中点 center，结果如下图：

然后在这三个元素中找到一个中等大小的值，并将其放到数组的开头位置，如下图所示：

到此，我们就可以将该数组的第一个元素作为此次遍历的基点pivot了，同时，我们将引入两个指针，即 i 和 j，分别指向 left 和 right，如图所示

接下来就可以进行遍历了，这里我们把遍历过程称为填坑法，因为现在我们取到了数组的第一个值为pivot，所以可以假设这个位置上没有元素了，留了一个坑，需要我们将别的元素填进来。所以我们要从指针 j 开始往右寻找到一个比pivot小的值，若找到了，则将找到的值填到坑里，但要注意的是，指针 j 不能找到 指针 i 的左边去，即当指针 j 与 指针 i 重合时就停止移动。
过程如下图所示：

此时我们可以看到，指针 j 找到了一个小于pivot的值 8，并将找到的值填到了坑里，那么此时指针 j 所指向的位置就留下了一个坑，又需要别的元素来填坑，所以此时我们就要让指针 i 向右找一个大于pivot的值，并将值填到坑里，同样的，指针 i 也不能找到指针 j 的右边，即当指针 i 与 指针 j 重合时就停止移动。
过程如下图所示：

指针 i 找到了一个大于pivot的值 97 并将其填到了坑里，此时指针 i 所在的位置就留下了一个坑，因此我们又要让指针 j 往左找小于pivot的值并填到坑里，过程如图所示：

紧接着，指针 i 又要向右找大于pivot的值，但是移动了两个位置都没有找到，并且此时指针 i 指针 j 重合了，此时我们只需要将pivot填入到坑里就实现了pivot左边的所有元素小于它，右边所有的元素都大于它了，如图所示：

接下来的操作，就是我们要单独对此时pivot的左边所有元素和右边的所有元素进行上述的一系列操作，就可以实现快速排序了。本例中的左右两边区域的元素个数都是小于等于3个的，因此直接将这几个值互相进行比较大小比较方便，过程如下图：

了解了快速排序的实现思路，我们来用代码来实现一下
function quickSort(arr) {
// 两个数据进行交换
function exchange(v1, v2) {
let temp = arr[v1]
arr[v1] = arr[v2]
arr[v2] = temp
}

// 找到相对合适的元素放到数组索引为0的位置作为基点pivot
function init(left, right) {
let center = Math.floor((left + right) / 2)

// 比较索引为left、center、right三个值的大小，从小到大排列
if(arr[left] > arr[right]) exchange(left, right)
if(arr[center] > arr[right]) exchange(center, right)
if(arr[left] > arr[center]) exchange(left, center)

// 判断数组长度是否大于3，若小于3，则数组已经排序好了，不需要做任何处理
if(right - left > 2) exchange(left, center)
}

function quick(left, right) {
init(left, right)
// 若数组长度小于等于2，则不需要做任何操作了，因为init函数已经排序好了
if(right - left <= 2) return;

// 创建指针i和j，分别指向left和right
let i = left
let j = right
// 将该数组区域的第一个元素作为基点pivot
let pivot = arr[i]

// 不断让指针i和j寻找合适的值填坑，直到两个指针重合
while(i < j) {
// 指针j不断向左找小于pivot的值，但指针j不能找到指针i的左边
while(arr[j] > pivot && j > i) {
j --
}
// 将找到的小于pivot的值填到指针i所指向的坑中
arr[i] = arr[j]

// 指针i不断向右找大于pivot的值，但指针i不能找到指针j的右边
while(arr[i] < pivot && i < j) {
i ++
}
// 将找到的大于pivot的值填到指针j所指向的坑中
arr[j] = arr[i]
}

// 将pivot填到指针i和指针j共同指向的坑中
arr[i] = pivot

// 对此时pivot的左边所有元素进行快排
quick(left, i - 1)
// 对此时pivot的右边所有元素进行快排
quick(i + 1, right)
}

quick(0, arr.length - 1)

return arr
}

我们可以简单来验证一下快速排序的正确性
let arr = [19, 97, 9, 17, 8, 1]
console.log(quickSort(arr))
/* 打印结果为：
[1, 8, 9, 17, 19, 97]
*/

四、结束语
排序算法中的高级排序（希尔排序、归并排序、快速排序）就已经讲完啦，下一篇文章为动态规划
大家可以关注我，之后我还会一直更新别的数据结构与算法的文章来供大家学习，并且我会把这些文章放到【数据结构与算法】这个专栏里，供大家学习使用。
然后大家可以关注一下我的微信公众号：前端印象，等这个专栏的文章完结以后，我会把每种数据结构和算法的笔记放到公众号上，大家可以去那获取。
或者也可以去我的github上获取完整代码，欢迎大家点个Star

https://github.com/Lpyexplore/structureAndAlgorithm-JS

我是Lpyexplore，一个因Python爬虫而进入前端的探索者。创作不易，喜欢的加个关注，点个收藏，给个赞~


展开全文
• 因为快速排序归并排序都能很好的考察一个人对分治策略的理解程度和编码的能力，所以经常出现在面试场合中，在纸上手写bugfree的排和归并排序算法可以说是基本要求，所以很有必要认真的学习一下这两种排序。...
• 掌握选择排序、冒泡排序、合并排序快速排序归并排序的算法原理 分析不同排序算法的时间效率和时间复杂度，以及理论值与实测数据的对比分析。 一、冒泡排序 算法伪代码： for i=1 to n for j=0 to n-i ...
• 1. 归并排序归 快速排序 归并排序和快速排序的区别
• 快速排序归并排序； 快速排序归并排序； 阅读之前注意：本文阅读建议用时：20min
• 本文主要讨论性质相对比较好且作者喜欢的快速排序算法和归并排序算法，并对此这做了一定比较。 正文： 常见的排序算法大致分为四类： 1.插入排序：直接插入排序，Shell排序 2.选择排序：直接选择排序，堆排序 3....
• 八大排序算法（直接插入排序，希尔排序，选择排序，堆排序，冒泡排序，快速排序归并排序，计数排序）
• 八大排序算法 一、直接插入 - 1.基本思路 - 2.代码实现 - 3....二、希尔排序 - 1....- 2....- 3....- 1....- 2....- 3....- 1....- 2....- 3....- 1....- 2....- 3....六、快速排序 - 1.基本思路 - 2.代码实现 - 3.时间复杂度和空间复杂度 七..
• 排序（2）–二分排序、快速排序归并排序
• 快速排序 归并排序1. 归并排序许多算法在结构上是递归的，为了解决一个给定的问题，算法一次或多次递归地调用其自身以解决紧密相关的若干子问题，这些算法典型地遵循分治法的思想。 分治模式在每层递归时都有3个...
• 快速排序归并排序运行时间的比较，因为排的时间复杂度nlogn~n^2，而归并的时间复杂度固定nlogn，所以理论上是归并比快，但是排可以适用的范围更广，所以一般更推荐使用排。 下面是相应实现的C++代码...
• 1.相较于冒泡排序，快速排序为什么快 我们直接比较一下冒泡和快排的排序过程:对于这样一个数组[6,1,2,7,9,3,4,5,10,8],快排的操作如下 1.选择一个数作为基准数,这里选择6 2.进行一次循环,将小于6的数放到左边,大于...
• 排序好的两部分数组进行合并成新的有序数组。 动态图演示 算法实现 利用分治算法，自顶向下的进行递归排序。动态图演示则自下往上的排序演示。 public static void main(String[] args) { int[] ...
• 快速排序排效率高，时间复杂度最理想 O(nlogn) ，最差时间O(n^2)，该算法不稳定。其主要思想如下：排是每次将序列第一个数当作一个参考值，分别将参考值大的数放到参考值右侧，小于参考值的数放到参考值...
• 快速排序归并排序的概念： 快速排序（Quicksort）是对冒泡排序的一种改进。 快速排序由C. A. R. Hoare在1962年提出。它的基本思想是：通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都...
• 分治思想是常见的算法思想之一，在排序算法中用到分治思想的有：快速排序归并排序。 分治法介绍如下图： 分治思想的关键点： 1、有问题可以一直分解形式相同的子问题，当子问题规模较小时，可自然求解，例如一...
• 之前两篇关于排序算法的综述以及平方阶复杂度的3种具体类型...快速排序 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下，排序 n 个项目要 O(nlogn)O(nlogn)O(nlogn)次比较。在最坏状况下则需要O(n2)O(n...
• JavaScript排序算法(希尔排序、快速排序归并排序）
• 而且当数据量过大时,归并排序 和 快速排序 会出现栈溢出.   以下是我写的源代码,请帮我分析一下是什么原因?   <pre name="code" class="java">package com.test; import java.util.Arrays; import java....
• 为何快速排序比冒泡/插入排序？因为其比较次数少很多。
• 归并排序和快速排序都是采用递归的结构实现的，不同的是归并排序在递归过程中有合并子序列的过程，而快速排序中没有，但是快速排序中有较为复杂的划分过程。 二者的平均时间复杂度均O(nlgn)，其中快速排序的系数...
• 排序算法：归并排序、快速排序 排序算法：桶排序、计数排序、基数排序 排序算法：堆排序 十大排序算法小结 一、归并排序： 1、工作原理： 归并排序的采用分治思想，如果要排序一个数组，我们先把数组从中间...
• 本文介绍排序算法中的快速排序归并排序、计数排序，并对每种排序算法进行了分析，附带java实现代码。
• 快速排序的时间复杂度 1.快速排序
• 基本思想：通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都另外一部分的所有数据都要小，然后再按此方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，以此达到整个数据变成...
• 快速排序1） 时间复杂度2） 快速排序是原地算法3） 快速排序不是稳定算法3. 总结 1. 归并排序 // 归并排序使用了分治的思想，首先分，缩小数据规模，然后再将部分结果合并起来 public void mergeSort(int[] a) { ...
• 算法设计实验报告，包括：快速排序归并排序两种算法各自的基本思想、时间复杂度分析，C++实现代码，两种算法运行时间的比较，运行截图，实验心得。

...