闲来无事,写了6种常见的排序算法,记录下随笔;
1、冒泡排序
交换排序,相邻两个数对比,小的往前移动,就像泡泡一样。时间复杂度O(n^2),稳定。
const bubble_sort = function(arr){
const {length} = arr;
for(let i = length - 1; i > 0; i --){
for(let j = length - 1; j >= 0; j -- ){
if(arr[j] < arr[j-1])
[arr[j-1], arr[j]] = [arr[j], arr[j-1]]
}
}
return arr;
}
2、插入排序
设定第一个数值为有序序列,从第二个数值开始,依次取值插入到有序序列里合适的位置,最终得到排序结果,时间复杂度O(n^2),稳定。
const insert_sort = function(arr){
const {length} = arr;
for(let i = 1; i < length; i++) {
for(let j = i; j > 0; j --) {//要维护的有序序列
if(arr[j] < arr[j-1]){
[arr[j-1], arr[j]] = [arr[j], arr[j-1]]
}
}
}
return arr;
}
3、选择排序
数组里选择最小(大)的值依次和有序序列的末尾位置的数值交换,比如第一次就是找到最小(大)的值和第一个位置的数交换位置,事件复杂度O(n^2),不稳定。
const choose_sort = function(arr){
const {length} = arr;
for(let i = 0; i < length; i++){
let min_index = i;
for(let j = i; j < length; j ++){
min_index = arr[j + 1] < arr[min_index] ? j+1 : min_index;
}
[arr[i], arr[min_index]] = [arr[min_index], arr[i]]
}
return arr;
}
4、快速排序
快排采用分治法,也是交换排序的一种,是冒泡排序的优化版,选择一个基值,通过比较交换把比它小的放它左边,比它大的放它右边,得到的两个序列再找一个基值,继续做比较交换,依次递归。时间复杂度O(nlog2n) ~ O(n^2),不稳定。
const quick_sort = function(arr){
let {length} = arr;
const sort = (subArr, left = 0, right = length - 1)=>{
if(left >= right) return;
let key = subArr[left];
let first = left;
let last = right;
while(left < right){
while(subArr[left] <= key && left < right) {
left ++;
}
while(subArr[right] >= key && left < right) {
right --;
}
[subArr[left], subArr[right]] = [subArr[right], subArr[left]];
}
sort(subArr, first, right-1);
sort(subArr, right, last)
}
sort(arr);
return arr;
}
5、堆排序
堆排序是选择排序的一种,堆其实就是把序列看作一个完全二叉树,从未排序的末端节点依次递归到根节点,做子节点和父节点数值的比较替换,保证父节点数值总是大(小)于等于子节点,得到大(小)顶堆,然后把根节点数值和未排序末端节点数值交换(和根节点交换过数值的节点组成有序序列),然后再继续下一轮的大(小)顶堆的调整。时间复杂度O(nlog2n),不稳定。
const heap_sort = function(arr){
const {length} = arr;
const changeMax = (sub_arr, root, limit) => {
let l_child = 2 * root + 1,
r_child = 2 * root + 2;
let max_child = r_child <= limit && sub_arr[l_child] < sub_arr[r_child] ? r_child : l_child;
if(max_child <= limit && sub_arr[root] < sub_arr[max_child]) {
[sub_arr[root], sub_arr[max_child]] = [sub_arr[max_child], sub_arr[root]]
}
}
for(let i = length - 1; i >= 0; i--){
for(let j = Math.round(i/2) -1; j >= 0; j --){
changeMax(arr, j, i)
}
[arr[i], arr[0]] = [arr[0],arr[i]]
}
return arr;
}
6、归并排序
归并排序也成为了合并排序,把序列分成n(序列长度)个小序列,然后进行两两合并得到[n/2]个有序小序列,然后再进行两两合并,依次循环直到所有有序序列的合而为一,最终得到一个长度为n的有序序列。时间复杂度O(nlog2n),稳定。
const merge = (left, right) => {
let result = [];
while(left.length > 0 && right.length > 0){
if(left[0] <= right[0]){
result.push(left.shift());
} else {
result.push(right.shift());
}
}
if(left.length > 0){
result.push(...left)
}
if(right.length > 0){
result.push(...right)
}
return result;
}
const merge_sort = function(arr){
const {length : l} = arr;
if(l < 2) return arr;
const middle = Math.floor(l / 2),
left_arr = arr.slice(0, middle),
right_arr = arr.slice(middle, l);
return merge(merge_sort(left_arr),merge_sort(right_arr));
}
总结:
类别 | 方法 | 时间复杂度 | 空间复杂度 | 稳定性 | |
最差 | 平均 | 辅助存储 | |||
插入排序 | O(n^2) | O(n^2) | O(1) | 稳定 | |
归并排序 | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | |
选择 | 选择排序 | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | |
交换 | 冒泡排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
快速排序 | O(n^2) | O(nlog2n) | O(1) | 不稳定 |