选择排序 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。
以此类推,直到全部待排序的数据元素的个数为零。
算法的时间复杂度为O(n^2)、额外空间复杂度为O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function swap (arr, i, j ) { let num = arr[i] arr[i] = arr[j] arr[j] = num } const arr = [3 , 44 , 635 , 7 , 45 , 34 , 65 , 2 , 34 , 6262 , 432 , 4 ]function selectSort (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return let len = arr.length for (let i = 0 ; i < len; i++) { let index = i for (let j = i + 1 ; j < len; j++) { index = arr[index] > arr[j] ? j : index } swap (arr, i, index) } } console .log (arr) selectSort (arr)console .log (arr)
冒泡排序 首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。这样操作后数组最右端的元素即为该数组中所有元素的最大值。
接着对该数组除最右端的n-1个元素进行同样的操作,再接着对剩下的n-2个元素做同样的操作,直到整个数组有序排列。
算法的时间复杂度为O(n^2)、额外空间复杂度为O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 function bubbleSort (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return const len = arr.length for (let i = len - 1 ; i > 0 ; i--) { for (let j = 0 ; j < i; j++) { arr[j] > arr[j + 1 ] ? swap (arr, j, j + 1 ) : null } } } console .log (arr) bubbleSort (arr)console .log (arr)
插入排序 将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增 1 的有序表。
算法的时间复杂度为O(n^2)、额外空间复杂度为O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function insertSort (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return const len = arr.length for (let i = 1 ; i < len; i++) { let j = i while (j >= 0 && arr[j - 1 ] > arr[j]) { swap (arr, j - 1 , j) j-- } } } console .log (arr) insertSort (arr)console .log (arr)
简化写法
1 2 3 4 5 6 7 8 9 10 11 12 13 function insertSort2 (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return const len = arr.length for (let i = 1 ; i < len; i++) { for (let j = i - 1 ; j > -1 && arr[j] > arr[j + 1 ]; j--) { swap (arr, j, j + 1 ) } } } console .log (arr) insertSort2 (arr)console .log (arr)
适用于数据量不大,算法稳定性要求高,且数据局部或整体有序的数列排序。
对数器 生成随机长度和随机值的数组,用于测试排序算法是否正确
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 function randomArray (maxLen, maxVal ) { const arr = [] const len = Math .floor (Math .random () * maxLen) + 1 for (let i = 0 ; i < len; i++) { arr.push (Math .floor (Math .random () * maxVal)) } return arr } function copy (arr ) { const newArr = [] for (let i = 0 ; i < arr.length ; i++) { newArr.push (arr[i]) } return newArr } function isSorted (arr ) { if (arr.length < 2 ) return true for (let i = 1 ; i < arr.length ; i++) { if (arr[i - 1 ] > arr[i]) return false } return true } function testSort (sortFun ) { for (let i = 0 ; i < 1000 ; i++) { const arr = randomArray (10 , 1000 ) const backup = copy (arr) sortFun (arr) if (!isSorted (arr)) { console .log ('排序出错' ) console .log ('排序前:' + backup) console .log ('排序后:' + arr) return } } console .log ('排序成功' ) } testSort (selectSort) testSort (bubbleSort) testSort (insertSort) testSort (insertSort2)
归并排序 归并排序(Merge Sort)是建立归并操作上的一种有效,稳定的排序算法,该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
算法的时间复杂度为O(n*log n)、额外空间复杂度为O(n)。
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 32 33 34 35 function merge (arr, l, m, r ) { const help = [] let p1 = l let p2 = m + 1 while (p1 <= m && p2 <= r) { arr[p1] <= arr[p2] ? help.push (arr[p1++]) : help.push (arr[p2++]) } while (p1 <= m) { help.push (arr[p1++]) } while (p2 <= r) { help.push (arr[p2++]) } for (let i = 0 ; i < help.length ; i++) { arr[l + i] = help[i] } } function mergeSortProcess (arr, l, r ) { if (l === r) return let mid = l + Math .floor ((r - l) / 2 ) mergeSortProcess (arr, l, mid) mergeSortProcess (arr, mid + 1 , r) merge (arr, l, mid, r) } function mergeSort (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return let l = 0 let r = arr.length + 1 mergeSortProcess (arr, l, r) } testSort (mergeSort)
快速排序 快速排序(Quick Sort)算法是在冒泡排序的基础上进行改进的一种算法,从名字上看就知道该排序算法的特点是快、效率高,是处理大数据最快的排序算法之一。
实现的基本思想是:通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小。
然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就变成有序序列。
荷兰国旗问题 给定一个数组arr,数组的最后一个数为num,将arr划分为三个区域,左边的数都小于num,中间等于num,右边都大于num。
要求额外空间复杂度O(1),时间复杂度O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function partition (arr, l, r ) { let num = arr[r] let i = l while (i <= r) { if (arr[i] < num) { swap (arr, l++, i++) } else if (arr[i] === num) { i++ } else { swap (arr, i, r--) } } return [l, r] } let arr = [1 , 8 , 7 , 4 , 5 , 6 , 1 , 8 , 4 , 9 , 5 ]console .log (partition (arr, 0 , 10 )) console .log (arr)
实现快速排序 递归调用上面(荷兰国旗问题中)的方法,随机选择一个数作为num,划分为小于、等于、大于三个部分后,对左右两个区域执行相同的操作。
算法的时间复杂度为O(n*log n)、额外空间复杂度为O(log n)。
最坏情况下每一次基准元素都是数组中最小或者最大的元素,则快速排序就是冒泡排序,这种情况时间复杂度就是冒泡排序的时间复杂度:O(n^2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function quickSortProcess (arr, l, r ) { if (l >= r) return swap (arr, r, l + Math .floor (Math .random () * (r - l + 1 ))) const part = partition (arr, l, r) quickSortProcess (arr, l, part[0 ] - 1 ) quickSortProcess (arr, part[1 ] + 1 , r) } function quickSort (arr ) { if (!Array .isArray (arr) || arr.length < 2 ) return let l = 0 let r = arr.length - 1 quickSortProcess (arr, l, r) } testSort (quickSort)
是目前基于比较的内部排序中被认为最好的方法,当数据过大且数据杂乱无章时,则适合采用快速排序。
堆排序 是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树 的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。(小顶堆或大顶堆,下面代码中使用的是大顶堆)
算法的时间复杂度为O(n*log n)、额外空间复杂度为O(1)。
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 32 33 34 35 36 37 38 39 40 function parentIndex (index ) { if (!index) return index return Math .floor ((index - 1 ) / 2 ) } function heapInsert (arr, index ) { while (arr[index] > arr[parentIndex (index)]) { swap (arr, index, parentIndex (index)) index = parentIndex (index) } } function heapify (arr, index, heapSize ) { let left = index * 2 + 1 while (left < heapSize) { let max = left + 1 < heapSize && arr[left + 1 ] > arr[left] ? left + 1 : left max = arr[index] < arr[max] ? max : index if (max === index) break swap (arr, index, max) index = max left = index * 2 + 1 } } function heapSort (arr ) { let heapSize = arr.length for (let i = 0 ; i < heapSize; i++) { heapInsert (arr, i) } while (heapSize) { swap (arr, 0 , --heapSize) heapify (arr, 0 , heapSize) } } testSort (heapSort)
优化写法:(优化前一个循环:只需要使用heapify方法即可将一个数组转化为堆结构,且时间复杂度为O(n))
1 2 3 4 5 6 7 8 9 10 11 12 13 function heapSort (arr ) { let heapSize = arr.length for (let i = heapSize - 1 ; i >= 0 ; i--) { heapify (arr, i, heapSize) } while (heapSize) { swap (arr, 0 , --heapSize) heapify (arr, 0 , heapSize) } } testSort (heapSort)
稳定性 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
不稳定的排序算法:选择排序、快速排序、堆排序等
稳定的排序算法:冒泡排序、插入排序、归并排序等
目前没有发现时间复杂度为O(n*log n)、额外空间复杂度低于O(n)且具有稳定性的排序算法。
一般情况下,快速排序更常用,因为其常数项时间更低。额外空间要求很少时使用堆排序;要求稳定性时使用归并排序。
还可以使用综合排序的方法提高效率。(比如在快排过程中的一些样本量比较少的步骤中使用插入排序)