前端必学排序算法

排序算法序言

排序算法的分类

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

常用术语介绍

  • 稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
  • 不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
  • 内排序 :所有排序操作都在内存中完成;
  • 外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
  • 时间复杂度 : 一个算法执行所耗费的时间。
  • 空间复杂度 :运行完一个程序所需内存的大小

常用算法的代价


k:桶的个数

冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法描述

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

演示

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
function bubbleSort(arr) {
var len = arr.length;
for(var i = 0; i < len - 1; i++) {
for(var j = 0; j < len - 1 - i; j++) {
if(arr[j] > arr[j+1]) { // 相邻元素两两对比
var temp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}

选择排序

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法描述

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1..n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

演示

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function selectSort(arr){
let len = arr.length
for(let i = 0; i < len; i++){
let min = i
for(let j = i + 1; j < len; j++){
if(arr[j] < arr[min]){
min = j
}
}
let flag = arr[i]
arr[i] = arr[min]
arr[min] = flag
}
return arr
}

插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

演示

在这里插入图片描述

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function insertSort(arr){
let len = arr.length
for(let i = 0; i < len - 1; i++){
let j = i
//保存取出来的元素
let flag = arr[j + 1]
while(j >= 0 && index < arr[j]){
arr[j + 1] = arr[j]
j--
}
//放回取出来的元素
arr[j + 1] = flag
}
return arr
}

希尔排序(shell)

希尔排序介绍

通过对插入排序的了解我们不难发现,简单插入排序对于那种小规模和本身顺序性就比较好的(基本有序)数列效果很好,因为每一趟需要进行比较的次数比较少。

但是实际现实中我们很难要求一个数列基本有序而且规模小,实际上,这么苛刻的条件在现实中几乎不会存在。一旦一个数组基本无序,甚至于在不知情的情况下直接是一个反序数组,那么插入排序的时间复杂度就真真正正的成了O(n^2)。

希尔大叔这个时候大喊:有条件就上,没有条件创造条件也给劳资上!!!

于是从如何让排序的数列规模减小或者基本有序入手,希尔排序应运而生,突破O(n^2)的瓶劲。

希尔排序过程(图文)

首先它把较大的数据集合分割成若干个小组(逻辑上分组),然后对每一个小组分别进行插入排序,此时,插入排序所作用的数据量比较小(每一个小组),插入的效率比较高

可以看出,他是按下标相隔距离为4分的组,也就是说把下标相差4的分到一组,比如这个例子中a[0]与a[4]是一组、a[1]与a[5]是一组…,这里的差值(距离)被称为增量

然后对每个分组进行插入排序


此时,整个数组变的部分有序了(有序程度可能不是很高)

然后缩小增量为上个增量的一半:2,继续划分分组,此时,每个分组元素个数多了,但是,数组变的部分有序了,插入排序效率同样高

同理对每个分组进行排序(插入排序),使其每个分组各自有序

最后设置增量为上一个增量的一半:1,则整个数组被分为一组,此时,整个数组已经接近有序了,插入排序效率高

这就是希尔排序的过程,同时创造小规模和基本有序两个条件,实现插入排序最快化

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function shellSort(arr) {
var len = arr.length;
for(var gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
// 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行
for(let i = 0; i < len - gap; i ++){
let j = i
let flag = arr[j + gap]
while(j >= 0 && arr[j] > flag){
arr[j + gap] = arr[j]
j -= gap
}
arr[j + gap] = arr[j]
}
}
return arr;
}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别递归采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

演示


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function mergeSort(arr){
if(arr.length <2) return arr
let cen = Math.floor(arr.length / 2)
//mergeSore一路递归拆树
//merge二路递归合树
return merge(mergeSort(arr.slice(0, cen)), mergeSort(arr.slice(cen)))
}
function merge(left, right){
var result = [];
while(left.length > 0 && right.length > 0) {
if(left[0] <= right[0]) {
result.push(left.shift());
} else{
result.push(right.shift());
}
}
while(left.length)
result.push(left.shift());
while(right.length)
result.push(right.shift());
return result;
}

快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

演示

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function quickSort(arr){
//由于全程是在原数组上进行操作,所以使用start和end来截取控制当前正在处理的数组段
quicksort(arr, 0, arr.length - 1)
return arr
}
function quicksort(arr, start, end){
//使用start和end截取当前需要处理的数组部分
//将截取的数组数组通过基准元素,分为左右两个部分,然后对左右两个部分进行递归,直到截取的数组长度小于2
if(start < end){
let mid = qs(arr, start, end)
console.log(mid)
quicksort(arr, start, mid - 1)
quicksort(arr,mid + 1,end)
}
}
function qs(arr, start, end){
let mid = start + 1
for(let i = mid; i <= end; i++){
if(arr[i] < arr[mid]){
swap(arr, i, mid)
mid ++
}
}
swap(arr, mid - 1, start)
return mid - 1
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2015-2021 AURORA_ZXH
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信