内部排序
插入排序
在要排序的一组元素,假设前面n-1(n>=2)个元素已经是排好顺序的,先要把第n个元素插入到前面的有序序列,使得这n元素也是排好顺序的。如此反复循环,直到所有元素排好顺序。(PS:首次认为第1个元素已经拍好顺序)
插入排序在小规模数据和基本有序数据上最高效。
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
// 将待插入元素提取出来
let temp = arr[i];
let j = i - 1;
for (; j >= 0; j--) {
if (arr[j] > temp) {
// 插入元素小于比较元素,比较元素则向后移动一位
arr[j + 1] = arr[j];
} else {
// 否则,结束移位
break;
}
}
//将插入元素插入正确位置
arr[j + 1] = temp;
}
return arr;
}
希尔排序
先将要排序的一组元素按某个增量gap(arr.length/2,gap为要排序的个数)分成若干组,每组中记录的下标相差gap。对每组中全部元素进行直接插入排序,然后再用一个较小的增量(gap/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
function shellSort(arr) {
let gap = arr.length;
while (gap > 1) {
gap = Math.floor(gap / 2);
for (let i = 0; i < gap; i++) {
// 分为gap组
let tempArr = [];
for (let j = i; j < arr.length; j += gap) {
tempArr.push(arr[j]);
}
// 使用插入排序有序化
tempArr = insertSort(tempArr);
for (let j = i, k = 0; j < arr.length; j += gap, k++) {
arr[j] = tempArr[k];
}
}
}
return arr;
}
直接选择排序
每次选择待排序的元素中最小的值,放置在序列的首位。过程如下:
初始数组: [8, 5, 2, 6, 9, 3, 1, 4, 0, 7] 第1趟排序后:0, [5, 2, 6, 9, 3, 1, 4, 8, 7] 第2趟排序后:0, 1, [2, 6, 9, 3, 5, 4, 8, 7] 第3趟排序后:0, 1, 2, [6, 9, 3, 5, 4, 8, 7] 第4趟排序后:0, 1, 2, 3, [9, 6, 5, 4, 8, 7] 第5趟排序后:0, 1, 2, 3, 4, [6, 5, 9, 8, 7] 第6趟排序后:0, 1, 2, 3, 4, 5, [6, 9, 8, 7] 第7趟排序后:0, 1, 2, 3, 4, 5, 6, [9, 8, 7] 第8趟排序后:0, 1, 2, 3, 4, 5, 6, 7, [8, 9] 第9趟排序后:0, 1, 2, 3, 4, 5, 6, 7, 8, [9] 最终结果: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
function selectSort(arr) {
for (let i = 0; i < arr.length; i++) {
let min = arr[i];
let minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < min) {
// 找到最小值,并标注最小值索引,方便后续与元素arr[i]交换位置
min = arr[j];
minIndex = j;
}
}
arr[minIndex] = arr[i];
arr[i] = min;
}
return arr;
}
堆排序
堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。
由于我们的算法主要讲升序,它分为两步:
- 初始化堆:将数列a[1...n]构造成最大堆。
- 交换数据:将a[1]和a[n]交换,使a[n]是a[1...n]中的最大值;然后将a[1...n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1...n-1]中的最大值;然后将a[1...n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。
因为涉及到完全二叉树的概念,查看这里详细了解
function heapify(arr, i, len) {
// 堆调整
// 完全二叉树特性:左边子节点位置 = 当前父节点的两倍 + 1,右边子节点位置 = 当前父节点的两倍 + 2
let left = 2 * i + 1;
let right = 2 * i + 2;
let largest = i; // 默认最大值是父级节点
if (left < len && arr[left] > arr[largest]) {
// 如果有左子节点,且左子节点元素大于父级元素
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
// 如果有左子节点,且左子节点元素大于父级元素
largest = right;
}
if (largest !== i) {
// 最大元素不是父级节点,则交换
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, largest, len);
}
}
function heapSort(arr) {
// 建立最大堆
let len = arr.length;
for (let i = Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
for (let i = arr.length - 1; i > 0; i--) {
// 先把大根堆顶部最大值放在数组末尾
[arr[0], arr[i]] = [arr[i], arr[0]];
len--;
heapify(arr, 0, len);
}
return arr;
}
冒泡排序
在要排序的一组元素中,对当前还未排好序的范围内的全部元素,自上而下对相邻的两个元素依次进行比较和交换,让较大的元素往下沉,较小的往上冒。
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
// 每次比较时都已经有i个元素到最后去了,所以j < arr.length - 1 - i
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 元素互换
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
快速排序
选择一个基准元素(通常选择第一个元素),通过一趟排序将要排序的数据分割成独立的两部分,一部分的所有元素小于基准元素,另外一部分的所有元素大于等于基准元素。再用递归的方式继续切分。
function quickSort(arr) {
if (arr.length <= 1) {
// 切个到最小了,不需要比较
return arr;
}
const baseVal = arr.shift(); // 取第一个作为基准元素
const lessArr = [];
const moreArr = [];
for (const val of arr) {
if (val < baseVal) {
lessArr.push(val);
} else {
moreArr.push(val);
}
}
return quickSort(lessArr).concat([baseVal], quickSort(moreArr));
}
归并排序
将两个有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。即先划分为两个部分,最后进行合并。过程如下:
初始数组: [1, 5, 4, 6, 3, 2, 0] 第1次归并后:[1, 5], [4, 6], [2, 3], [0] 第2次归并后:[1, 4, 5, 6], [0, 2, 3] 第3次归并后:[1, 2, 3, 4, 5, 6, 7]
function merge(leftArr, rightArr) {
const retArr = [];
while (leftArr.length > 0 && rightArr.length > 0) {
// 拿出两个有序序列中最小的放到新的有序序列中
if (leftArr[0] < rightArr[0]) {
retArr.push(leftArr.shift());
} else {
retArr.push(rightArr.shift());
}
}
// leftArr和rightArr只有一个还有剩余的最大元素组
return retArr.concat(leftArr, rightArr);
}
function mergeSort(arr) {
if (arr.length === 1) {
return arr;
}
// 拆分成两个序列
const mid = Math.floor(arr.length / 2);
const leftArr = arr.slice(0, mid);
const rightArr = arr.slice(mid);
return merge(mergeSort(leftArr), mergeSort(rightArr))
}
基数排序
基数排序是一种非比较型整数排序算法,其原理是将数据按位数切割成不同的数字,然后按每个位数分别比较。
在类似对百万级的电话号码进行排序的问题上,使用基数排序效率较高。流程如下:
function radixSort(arr) {
// 求出最大数位长度,比如1就是1位,10就是2位
const maxDigit = Math.floor(Math.log10(Math.max.apply(Math, arr)));
for (let i = 0, dev = 1, mod = 10; i <= maxDigit; i++ , dev *= 10, mod *= 10) {
const counter = [];
for (let j = 0; j < arr.length; j++) {
// 求出元素在当前数位的值,比如50在十分位(第二分位)上的值是5
let bucket = parseInt((arr[j] % mod) / dev);
if (counter[bucket] == null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
let index = 0;
counter.forEach(bucketArr => {
bucketArr.forEach(val => {
arr[index++] = val;
});
});
}
return arr;
}