本文为学习排序算法时的自我思路梳理,代码以简单易懂为主。
几个概念
- 稳定:如果 a 原本在 b 前面,且 a = b,经过排序后,a 仍然在 b 的前面。
- 不稳定:如果 a 原本在 b 前面,且 a = b,经过排序后,a 可能会出现在 b 的后面。
- 时间复杂度:一个算法执行所耗费的时间,用 O 表示。
- 空间复杂度: 运行完一个程序所需内存的大小。
- 内排序:所有排序操作都在内存中完成。
- 外排序:数据存放在磁盘中,排序需要通过磁盘和内存的数据传输进行。
几种经典排序算法的对比
选择排序
1.基本思想
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
2. JS 代码实现(循环)
function minIndex(array) {
let index = 0
for (let i = 1; i < array.length; i++) {
array[i] < array[index] ? (index = i) : index
}
return index
}
function swap(array, i, j) {
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
function sort(array) {
for(let i = 0; i < array.length-1; i++) {
let index = minIndex(array.slice(i)) + i
if(index !== i) {swap(array, index, i)}
}
return array
}
快速排序
1. 基本思想
快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2. JS 代码实现(递归):
function quickSort(array) {
if(array.length <= 1) {
return array
}
// 确定以数组中的哪个位置基准
let pivotIndex = Math.floor(array.length / 2)
let pivot = array.splice(pivotIndex, 1)[0] // 此时的数组已经是剔除了作为基准的数之后的数组了
// 准备好基准位置左边和右边的空位
let leftArea = []
let rightArea = []
// 对此时数组里的每一项,判断它如果比基准小,就放到左边,否则放到基准右边
for (let i = 0; i < array.length; i++) {
array[i] < pivot ? leftArea.push(array[i]) : rightArea.push(array[i])
}
return quickSort(leftArea).concat([pivot], quickSort(rightArea))
}
归并排序
1. 基本思想
归并排序是建立在归并操作上的一种有效的排序算法,1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。
归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
2. JS 代码实现(递归)
function mergeSort(array) {
let len = array.length
if(len === 1) {return array} // 只有一个值的数组,即为有序的
// 数组长度大于1时,将数组分为左右两部分
let left = array.slice(0, Math.floor(len/2))
let right = array.slice(Math.floor(len/2))
// 对左右两部分的数组,分别各自再进行 mergeSort(),然后将左右两有序数组进行 merge 合并
return merge(mergeSort(left), mergeSort(right)) // 只管合并两个已经排好序的数组
}
// merge 接受两个 有序数组 作为参数
let merge = (arr1, arr2) => {
// 当其中一个数组为空数组时,则直接返回另一个数组
if(arr1.length === 0) {return arr2}
if(arr2.length === 0) {return arr1}
// 比较两个有序数组的第一项,取出较小的值后,再对剩余的两个数组继续进行 merge(), 该递归会一直进行到直到其中一个数组为空
return arr1[0] < arr2[0] ? [arr1[0]].concat( merge(arr1.slice(1), arr2) ) : [arr2[0]].concat( merge(arr2.slice(1), arr1) )
}
计数排序
1. 基本思想
计数排序本质就是统计不同元素出现的次数,然后将元素依次从小到大放置,每个元素看统计的次数,就紧挨着放置几个同样的元素。 这是通过空间换时间的方式。
2. JS 代码实现
// 对正整数组
function countSort(array) {
let hashTable = {}, max = 0, result = []
// 遍历数组
for(let i = 0; i < array.length; i++) {
// 存下数组中的最大值
array[i] > max ? max = array[i] : max
// 如果哈希表中没有记录该值,则将该值作为 key,其 value 记为 1;
// 如果哈希表中已有该值,则将其 value + 1
if (!(array[i] in hashTable)){
hashTable[array[i]] = 1
} else {
hashTable[array[i]] += 1
}
}
// 遍历哈希表
for (let j = 0; j <= max; j++) {
if (j in hashTable) {
for (let k = 0; k < hashTable[j]; k++) {
result.push(j)
}
}
}
return result
}