常见排序算法及其JS实现
Posted 桥本环奈粤港澳分奈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见排序算法及其JS实现相关的知识,希望对你有一定的参考价值。
一、常见排序算法
常见排序算法有冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、基数排序、计数排序、桶排序
二、JS实现
1. 冒泡排序
主要思想: 对相邻的元素进行两两比较,顺序相反则进行交换,每一趟会将最小/大的元素浮到顶端,最终有序
function bubbleSort(array) //冒泡排序
if (!Array.isArray(array) || array.length <= 1) return;
let lastIndex = array.length - 1;
while (lastIndex > 0)
let flag = true;
let k = lastIndex;
for (let j = 0; j < k; j++)
if (array[j] > array[j + 1])
flag = false;
lastIndex = j;
[array[j], array[j + 1]] = [array[j + 1], array[j]];
if (flag) break;
优化后的冒泡排序,当排序序列为已排序序列时,为最好的时间复杂度为 O(n)。
冒泡排序的平均时间复杂度为 O(n²) ,最坏时间复杂度为 O(n²) ,空间复杂度为 O(1) ,是稳定排序。
2. 选择排序
主要思想: 每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止。
function selectSort(array) //选择排序
let arr = array.slice(0) // 复制数组
if (!Array.isArray(arr) || arr.length <= 1) return;
for (let i = 0; i < arr.length - 1; i++)
let minIndex = i;
for (let j = i + 1; j < arr.length; j++) // 每轮选出低i小的值
if (arr[minIndex] > arr[j])
minIndex = j
[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]] // 放在位置i
return arr
let array = [21, 45, 316, 3, 265, 266, 9];
console.log(selectSort(array)); // [3, 9, 21, 45, 265, 266, 316]
选择排序不管初始序列是否有序,时间复杂度都为 O(n²)。
选择排序的平均时间复杂度为 O(n²) ,最坏时间复杂度为 O(n²) ,空间复杂度为 O(1) ,不是稳定排序。
3. 插入排序
主要思想: 直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
function insertSort(array) //插入排序
for (let i = 1; i < array.length; i++) // 循环从 1 开始,0 位置为默认的已排序的序列
temp = array[i] // 保存当前需要排序的元素
j = i
// 在当前已排序序列中比较,如果比需要排序的元素大,就依次往后移动位置
while (j - 1 >= 0 && array[j - 1] > temp)
array[j] = array[j - 1]
j--
array[j] = temp // 将找到的位置插入元素
let array = [21, 45, 316, 3, 265, 266, 9];
insertSort(array);
console.log(array); // [3, 9, 21, 45, 265, 266, 316]
当排序序列为已排序序列时,为最好的时间复杂度 O(n)。
插入排序的平均时间复杂度为 O(n²) ,最坏时间复杂度为 O(n²) ,空间复杂度为 O(1) ,是稳定排序。
4. 希尔排序
主要思想: 把数组按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个数组恰被分成一组,算法便终止。
function hillSort(array) //希尔排序
let length = array.length
for (let gap = parseInt(length >> 1); gap >= 1; gap = parseInt(gap >> 1)) //gap为增量,每次增量大小减半
for (let i = gap; i < length; i++)
temp = array[i] // 保存当前需要排序的元素
j = i
while (j - gap >= 0 && array[j - gap] > temp) // 在当前已排序的序列中比较,如果比需要排序的元素大,则后移位置
array[j] = array[j - gap]
j = j - gap
array[j] = temp // 插入元素
平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(n^s) ,空间复杂度为 O(1) ,不是稳定排序。
5. 归并排序
主要思想: 归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略。递归的将数组两两分开直到只包含一个元素,然后将数组排序合并,最终合并为排序好的数组。
function merge(arr1, arr2) //归并
let i1 = 0
let i2 = 0
let res = []
// 左右两个数组的元素依次比较,将较小的元素加入结果数组中,直到其中一个数组的元素全部加入完则停止
while (i1 < arr1.length && i2 < arr2.length)
if (arr1[i1] <= arr2[i2])
res.push(arr1[i1++])
else
res.push(arr2[i2++])
while (i1 < arr1.length) // 如果是左边数组还有剩余,则把剩余的元素全部加入到结果数组中。
res.push(arr1[i1++])
while (i2 < arr2.length) // 如果是右边数组还有剩余,则把剩余的元素全部加入到结果数组中。
res.push(arr2[i2++])
return res
function mergeSort(array) //归并排序
if (!Array.isArray(array) || array.length === 0) return
// 将数组两两分开直到只包含一个元素
if (array.length === 1) return array
let mid = parseInt(array.length >> 1) // 找到中间索引值
let left = array.slice(0, mid) // 截取左半部分
let right = array.slice(mid, array.length) // 截取右半部分
return merge(mergeSort(left), mergeSort(right)) //递归分解后,将数组排序合并
归并排序将整个排序序列看成一个二叉树进行分解,首先将树分解到每一个子节点,树的每一层都是一个归并排序的过程,每一层归并的时间复杂度为 O(n),因为整个树的高度为 lgn,所以归并排序的时间复杂度不管在什么情况下都为O(nlogn)。
归并排序的空间复杂度取决于递归的深度和用于归并时的临时数组,所以递归的深度为 logn,临时数组的大小为 n,所以归并排序的空间复杂度为 O(n)。
归并排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(nlogn) ,空间复杂度为 O(n) ,是稳定排序。
6. 快速排序
主要思想: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
function quickSort(array, start, end) //快速排序
// 如果不是数组或者数组长度小于等于1,直接返回,不需要排序
if (!Array.isArray(array) || length <= 1 || start >= end) return
let x = array[start] // 取第一个值为枢纽值,获取枢纽值的大小
let i = start
let j = end
while (i < j)
while (i < j && array[j] >= x) // 从右往左找到第一个比枢纽值小的值array[j]
j--
if (i < j) // 将array[j]放到位置i, i右移
array[i++] = array[j]
while (i < j && array[i] <= x) // 从左往右找到第一个比枢纽值大的值
i++
if (i < j)
array[j] = array[i] //将array[i]放到位置j
array[i] = x // 将枢纽值x放在位置i,此时i往左的值全部比x小,i往右的值全部比x大
quickSort(array, start, i - 1) // 快速排序左部分
quickSort(array, i + 1, end) // 快速排序右部分
首先将第一个位置的数作为枢纽值,然后 end 指针向前移动,当遇到比枢纽值小的值或者 end 值等于 start 值的时候停止,然后将这个值填入 start 的位置,然后 start 指针向后移动,当遇到比枢纽值大的值或者start 值等于 end 值的时候停止,然后将这个值填入 end 的位置。反复循环这个过程,直到 start 的值等于 end 的值为止。将一开始保留的枢纽值填入这个位置,此时枢纽值左边的值都比枢纽值小,枢纽值右边的值都比枢纽值大。然后在递归左右两边的的序列。
当每次换分的结果为含 ⌊n/2⌋和 ⌈n/2⌉−1 个元素时,最好情况发生,此时递归的次数为 logn,然后每次划分的时间复杂度为 O(n),所以最优的时间复杂度为 O(nlogn)。一般来说只要每次换分都是常比例的划分,时间复杂度都为 O(nlogn)。
当每次换分的结果为 n-1 和 0 个元素时,最坏情况发生。划分操作的时间复杂度为 O(n),递归的次数为 n-1,所以最坏的时间复杂度为 O(n²)。所以当排序序列有序的时候,快速排序有可能被转换为冒泡排序。
快速排序的空间复杂度取决于递归的深度,所以最好的时候为 O(logn),最坏的时候为 O(n)。
快速排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(n²) ,空间复杂度为 O(logn) ,不是稳定排序。
7. 堆排序
主要思想:
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序序列了。
function heapSort(array) //堆排序
buildMaxHeap(array) //构建堆
for (let i = array.length - 1; i >= 0; i--)
[array[0], array[i]] = [array[i], array[0]]
heapify(array, 0, i) //每次拿到堆顶的最大值放到后面,再调整堆
function buildMaxHeap(array)
let length = array.length
let iparent = parseInt(length >> 1) - 1 //找到最后一个非叶子节点
for (let i = iparent; i >= 0; i--)
heapify(array, i, length) //循环调整子树
function heapify(array, index, length)
let imax = index
let li = index * 2 + 1
let ri = index * 2 + 2
while (1)
imax = index
li = index * 2 + 1
ri = index * 2 + 2
if (li < length && array[li] > array[imax])
imax = li
if (ri < length && array[ri] > array[imax])
imax = ri
if (imax !== index)
[array[imax], array[index]] = [array[index], array[imax]]
index = imax
else
break
建立堆的时间复杂度为 O(n),排序循环的次数为 n-1,每次调整堆的时间复杂度为 O(logn),因此堆排序的时间复杂度在不管什么情况下都是 O(nlogn)。
堆排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(nlogn) ,空间复杂度为 O(1) ,不是稳定排序。
8. 基数排序
主要思想: 将整数按位数切割成不同的数字,然后按每个位数分别比较。排序过程:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
function radixSort(array)
let length = array.length
if (!Array.isArray(array) || length <= 1) return;
let bucket = []
let max = array[0]
for (let i = 1; i < length; i++)
if (max < array[i]) max = array[i] // 先找到最大值
let loop = (max + "").length // 确定位数
for (let i = 0; i < loop; i++)
for (let j = 0; j < length; j++)
let str = (array[j] + "").length
if (str.length >= i + 1)
let k =
bucket[k]
基数排序的平均时间复杂度为 O(nk),k 为最大元素的长度,最坏时间复杂度为 O(nk),空间复杂度为 O(n) ,是稳定排序。
详细资料可以参考:
《常见排序算法 - 基数排序》
《排序算法之 基数排序 及其时间复杂度和空间复杂度》
算法总结可以参考:
《算法的时间复杂度和空间复杂度-总结》
《十大经典排序算法(动图演示)》
《各类排序算法的对比及实现》
以上是关于常见排序算法及其JS实现的主要内容,如果未能解决你的问题,请参考以下文章