需要掌握(背代码)
- 快速排序 ⭐⭐⭐
- 归并排序 ⭐⭐⭐
- 计数排序 ⭐⭐⭐
- 堆排序
- 希尔排序
- 冒泡排序
- 选择排序
- 插入排序
初级排序
1 选择排序
每次找到最小值,然后放到待排序数组的起始位置,或者相反找最大值
动画演示
function selectSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
时间复杂度
- 最优时间复杂度:O(n2)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定(考虑升序每次选择最大的情况)
2 插入排序
从前到后逐步构建有序序列;
对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
function insertSort(arr) {
var len = arr.length;
var prev, cur;
for (var i = 0; i < len; i++) {
prev = i - 1;
cur = arr[i]; // 保存当前元素,用于替换
// 将大于它的元素依次后移,找适合它放置的位置(会覆盖当前元素,所以要实现保存)
while(prev >= 0 && arr[prev] > cur) {
arr[prev + 1] = arr[prev];
prev--;
}
arr[prev + 1] = cur; // 循环结束后,将元素放到正确位置
}
}
var arr = [9, 8, 7, 3, 2, 1, 6, 5, 4];
insertSort(arr);
console.log(arr)
时间复杂度
内存挪动数组中每一个元素O(n)外层比较每个元素O(n)
总共就是O(n2)的时间复杂度
3 冒泡排序(基本不用)
嵌套循环,每次查看相邻的元素如果逆序,则交换
function bubble_sort(arr) {
for (var i = arr.length - 1; i > 0; i--) {
// 一个个交换,每次就是将最大的排在最后
for (var j = 0; j < i; j++) {
// 逆序交换
if (arr[j] > arr[j + 1]) {
var cur = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = cur;
}
}
}
}
arr = [54,26,93,17,77,31,44,55,20]
bubble_sort(arr)
console.log(arr)
时间复杂度
- 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
高级排序
1 快速排序
-
又称划分交换排序(partition-exchange sort)
-
原理 基于分治
数组取标杆 pivot,将小元素放 pivot左边,大元素放右侧
然后依次对右边和右边的子数组继续快排
步骤
- 从数列中挑出一个元素,称为"基准"(pivot),这个基准大于左边的所有元素,小于右边的所有元素,所以可以先partition选出这个基准
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
function quickSort(arr, begin, end) {
if (end <= begin) return;
var pivot = partition(arr, begin, end);
quickSort(arr, begin, pivot - 1); // 快排标杆左边的元素
quickSort(arr, pivot + 1, end); // 快排标杆右边的元素
}
function partition(arr, begin, end) {
// pivot: 标杆位置,counter: 小于pivot的元素的个数
var pivot = end,
cnt = begin; // 起点为begin
for (var i = begin; i < end; i++) {
if (arr[i] < arr[pivot]) {
// 小于a[pivot]的元素放到左侧
var temp = arr[cnt];
arr[cnt] = arr[i];
arr[i] = temp;
cnt++;
}
}
// 此时新的的标杆pivot的位置就是下标为counter的位置,交换下标杆
var temp = arr[pivot];
arr[pivot] = arr[cnt];
arr[cnt] = temp;
return cnt;
}
var arr = [54, 26, 93, 17, 77, 31, 44, 55, 20];
quickSort(arr, 0, arr.length - 1);
console.log(arr);
时间复杂度
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定
2 归并排序
归并排序的思想就是先递归分解数组,再合并数组。
原理
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
分析实现
function mergeSort(arr) {
var len = arr.length;
if (len < 2) {
return arr;
}
var middle = Math.floor(len / 2);
var left = arr.slice(0, middle); // 左边的数组
var right = arr.slice(middle); // 右边的数组
// 继续分别对左右两个数组进行归并排序
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
var result = [];
// 比较左右两个数组的第一个元素,小的就推入结果数组,直到其中一个数组为空
while (left.length > 0 && right.length > 0) {
left[0] <= right[0] ? result.push(left.shift())
: result.push(right.shift());
}
// 因为左右两个数组一定是已经分别排好序了,所以剩下一个不为空的数组FIFO推到结果数组result就行了、
while (left.length) {
result.push(left.shift());
}
while (right.length) {
result.push(right.shift());
}
return result;
}
var arr = [54, 26, 93, 17, 77, 31, 44, 55, 20];
console.log(mergeSort(arr));
时间复杂度
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(nlogn)
- 稳定性:稳定
3 计数排序
分布式排序使用已组织好的辅助数据结构(称为桶),然后进行合并,得到排好序的数组。计数排序使用一个用来存储每个元素在原始数组中出现次数的临时数组。在所有元素都计数完成后,临时数组已排好序并可迭代以构建排序后的结果数组
5 希尔排序
也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本
原理
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
步骤过程
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然后我们对每列进行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之后变为:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最后以1步长进行排序(此时就是简单的插入排序了)
分析实现
def shell_sort(alist):
n = len(alist)
# 初始步长
gap = n / 2
while gap > 0:
# 按步长进行插入排序
for i in range(gap, n):
j = i
# 插入排序
while j>=gap and alist[j-gap] > alist[j]:
alist[j-gap], alist[j] = alist[j], alist[j-gap]
j -= gap
# 得到新的步长
gap = gap / 2
alist = [54,26,93,17,77,31,44,55,20]
shell_sort(alist)
print(alist)
时间复杂度
- 最优时间复杂度:根据步长序列的不同而不同
- 最坏时间复杂度:O(n2)
- 稳定想:不稳定
时间复杂度对比
重点在于O(nlogn)的排序