javascript 十大经典排序
关于时间复杂度:
平方阶 (O(n2)) 排序:各类简单排序,直接插入、直接选择和冒泡排序;
线性对数阶 (O(nlog2n)) 排序:快速排序、堆排序和归并排序;
希尔排序:O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数;
线性阶 (O(n)) 排序:基数排序,此外还有桶、箱排序。
关于稳定性:
排序后 2 个相等键值的顺序和排序之前它们的顺序相同。
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
n:数据规模。
k:“桶”的个数。
In-place:占用常数内存,不占用额外内存。
Out-place:占用额外内存。
首先生成一个数字数组:
let arr = Array.from({length:20},x=>{return Math.ceil(Math.random()*10**2)}) console.log(arr) function fn(a,b){ return a-b; } // console.log(arr.sort(fn))
// 十大排序
// 1.冒泡排序(Bubble Sort)
// 冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。
// 走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
// 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
function bubbleSort(a){ let k = 0; let h = 0; for(var i=0;i<a.length;i++){ for(var j=i+1;j<a.length;j++){ k++; if(a[i] > a[j]){ h++; [a[i],a[j]] = [a[j],a[i]]; } } } console.log('o=',k,'h=',h); return a; } console.log('bubbleSort', bubbleSort([].concat(arr)) ) function bubbleSort1(arr) { var len = arr.length; let k = 0; let h = 0; for (var i = 0; i < len; i++) { for (var j = 0; j < len - 1 - i; j++) { k++; if (arr[j] > arr[j+1]) { //相邻元素两两对比 h++; [arr[j+1],arr[j]] = [arr[j],arr[j+1]]; } } } console.log('o=',k,'h=',h); return arr; } console.log('bubbleSort1', bubbleSort1([].concat(arr)) )
// 2. 选择排序(Selection Sort)
// 在时间复杂度上表现最稳定的排序算法之一,因为无论什么数据进去都是O(n²)的时间复杂度。。。
// 所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧
// 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
// 然后,再从剩余未排序元素中继续寻找最小(大)元素,
// 然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
function selectionSort(a){ let k = 0; let h = 0;a for(var i=0;i<a.length;i++){ let tmp = a[i]; let pos = i; for(var j=i+1;j<a.length;j++){ k++; if(tmp>a[j]){ tmp=a[j]; pos = j; h++; } } if(tmp<a[i]){ [a[i], a[pos]] = [a[pos],a[i]]; } } console.log('o=',k,'h=',h); return a; } console.log('selectionSort', selectionSort([].concat(arr)) )
// 3. 插入排序(Insertion Sort)
// 工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,
// 找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),
// 因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
function insertionSort(arr){ let k=0; let h=0; for(var i=1;i<arr.length;i++){ let j = i; let cur = arr[i]; let flag = false; while(j>=0 && arr[j-1] > cur){ k++; h++; arr[j] = arr[j-1]; j--; flag=true; } if(!flag){ k++; } arr[j]=cur; } console.log('o=',k,'h=',h); return arr; } let insertArr = insertionSort([].concat(arr)); console.log('insertionSort', insertArr) console.log('insertionSort', insertionSort(insertArr)) console.log('insertionSort', insertionSort(insertArr.reverse())) function insertionSort1(arr){ let k=0; let h=0; for(var i=1;i<arr.length;i++){ let cur = arr[i]; let left = 0; let right = i-1; while(left <= right){ k++; let mid = parseInt((right+left)/2); if(arr[mid] > cur){ right = mid -1; }else{ left = mid + 1; } } for(var j=i-1;j>=left;j--){ h++; k++; arr[j+1]=arr[j]; } arr[left] = cur; } console.log('o=',k,'h=',h); return arr; } let insertArr1 = insertionSort1([].concat(arr)); console.log('insertionSort1', insertArr1) console.log('insertionSort1', insertionSort1(insertArr1)) console.log('insertionSort1', insertionSort1(insertArr1.reverse()))
// 4. 希尔排序(Shell Sort)
// 希尔排序是插入排序的一种更高效率的实现。它与插入排序的不同之处在于,它会优先比较距离较远的元素。
// 希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列
function shellSort(a){ let len = a.length; let gap = 1; let k=0; let h=0 while(gap<(len/3)){ gap = gap*3+1; } for(gap;gap>0;gap=Math.floor(gap/3) ){ for(var i=gap;i<len;i++){for(var j=i-gap;j>=0;j-=gap){ k++; h++;
if(a[j+gap]<a[j) {
[a[j+gap], a[j]] = [a[j], a[j+gap]];
} } } } console.log('o=',k,'h=',h); return a; } let shellArr = shellSort([].concat(arr)); console.log('shellSort', shellArr) console.log('shellSort', shellSort(shellArr)) console.log('shellSort', shellSort(shellArr.reverse()))
// 5. 归并排序(Merge Sort)
// 归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;
// 即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并
// 递归 recursion
function merge(a,b){ let len1 = a.length; let len2 = b.length; let i=0,j=0; let ret = []; while(i<len1 && j<len2){ if(a[i]<b[j]){ ret.push(a[i]); i++; }else{ ret.push(b[j]); j++; } } if(i<len1){ ret = ret.concat(a.slice(i)); } if(j<len2){ ret = ret.concat(b.slice(j)); } return ret; } function mergeSort(a){ let len = a.length; if(len == 1){ return a; } let mid=parseInt(len/2); let left = a.slice(0,mid); let right = a.slice(mid); return merge(mergeSort(left),mergeSort(right)); } let mergeArr = mergeSort([].concat(arr)); console.log('mergeSort', mergeArr) console.log('mergeSort', mergeSort(mergeArr)) console.log('mergeSort', mergeSort(mergeArr.reverse())) // 非递归 function mergeSort1(a){ let gap = 3; let k=0; for(gap;gap<a.length;gap=2*gap){ for(var i=0;i<a.length;i+=gap){ let r = []; k++; if((i+gap)>a.length){ r = merge(a.slice(i),[]); }else{ let mid = parseInt((i+i+gap)/2); let left = a.slice(i,mid); let right = a.slice(mid,i+gap); r = merge(left,right); } a.splice(i,r.length,...r); } if(gap*2>a.length){ a = merge(a.slice(0,gap),a.slice(gap, a.length)); } } console.log('k=',k) return a; } let mergeArr1 = mergeSort1([].concat(arr)); console.log('mergeSort1', mergeArr1) console.log('mergeSort1', mergeSort1(mergeArr1)) console.log('mergeSort1', mergeSort1(mergeArr1.reverse()))
// 6. 快速排序(Quick Sort)
// 又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法
//基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,
// 则可分别对这两部分记录继续进行排序,以达到整个序列有序
function quickSort(a){ if(a.length <= 1) return a; let pivotIndex = Math.ceil(a.length/2); let pivot = a.splice(pivotIndex,1)[0]; let left=[]; let right=[]; let k=0; for(var i=0;i<a.length;i++){ k++; if(a[i]<pivot){ left.push(a[i]); }else{ right.push(a[i]); } } console.log('k=',k) return quickSort(left).concat([pivot],quickSort(right)); } let quickArr = quickSort([].concat(arr)); console.log('quickSort', quickArr) function partition(a,left,right){ let pivot=a[right]; let i=left; for(var j=left;j<=right;j++){ if(a[j]<pivot) { [a[i], a[j]]=[a[j],a[i]]; i++; } } [a[i],a[right]]=[a[right],a[i]]; return i; } function quickSort1(a, left, right){ if(left<right){ let i = partition(a,left,right); quickSort1(a,left,i-1); quickSort1(a, i+1, right); } return a; } let quickArr1 = quickSort1([].concat(arr), 0, arr.length-1); console.log('quickSort1', quickArr1);
// 7.堆排序(Heap Sort)
// 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
function buildMaxHeap(a){ let len = a.length; for(var i=Math.floor(len/2);i>=0;i--){ heapify(a,i,len); } return a; } function heapify(a,i,len){ let left = 2*i+1; let right = 2*i+2; let largest = i; if(left<len && a[left] > a[largest]){ largest = left; } if(right<len && a[right] > a[largest]){ largest = right; } if(largest != i){ [a[largest],a[i]] = [a[i],a[largest]]; heapify(a,largest,len); } } function heapSort(a){ let len = a.length; buildMaxHeap(a); for(var i=(len-1);i>=0;i--){ [a[i],a[0]] = [a[0],a[i]]; heapify(a,0,--len); } return a; } let heapArr1 = heapSort([].concat(arr), 0, arr.length-1); console.log('heapSort', heapArr1);
// 8.计数排序(Counting Sort)
// 计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。
function countingSort(a){ let max = 0; let len = a.length; let k=0; for(var i=0;i<len;i++){ if(max<a[i]){ max=a[i]; } k++; } let bucket = new Array(max+1); for(var i=0;i<len;i++){ if(!bucket[a[i]]){ bucket[a[i]] = 0; } bucket[a[i]]++; k++; } let h=0; // for(var j=1;j<max+1;j++){ // while(bucket[j]>0){ // a[h++] = j; // bucket[j]--; // } // k++; // } // bucket.forEach((val,index)=>{ // while(val>0){ // a[h++] = index; // val--; // } // k++; // }) bucket.map((val,index)=>{ while(val>0){ a[h++] = index; val--; } k++; }) console.log('k=',k,h) return a; } let countingArr1 = countingSort([].concat(arr), 0, arr.length-1); console.log('countingSort', countingArr1);
// 9 桶排序(Bucket Sort)
// 桶排序是计数排序的升级版
// 它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定
function bucketSort(a){ let max = 0; let min = 0; let len = a.length; let size = Math.ceil(len/3); let count = 0; for(var i=0;i<len;i++){ if(a[i]<min){ min = a[i]; } if(a[i]>max){ max = a[i]; } count++; } let bucketLen = Math.floor( (max-min)/size ); let bucket = new Array(bucketLen+1); for(var j=0;j<bucketLen+1;j++){ bucket[j] = []; count++; } for(var k=0;k<len;k++){ bucket[Math.floor((a[k]-min)/size)].push(a[k]); count++; } a.length = 0; for(var l=0;l<bucketLen+1;l++){ countingSort(bucket[l]); if(bucket[l].length>0){ a.push(...bucket[l]); } count++; } console.log('count=',count); return a; } let bucketArr1 = bucketSort([].concat(arr), 0, arr.length-1); console.log('bucketSort', bucketArr1);
// 10. 基数排序(Radix Sort)
// 基数排序:根据键值的每位数字来分配桶
// 计数排序:每个桶只存储单一键值
// 桶排序:每个桶存储一定范围的数值
function radixSort(a){ let bucket = new Array(11); let max = Math.max(...a); let len = max.toString().length; let arrLen = a.length; let count=0; let rest = []; for(var i=0;i<len;i++){ bucket.length = 0; for(var j=0;j<a.length;j++){ let str = a[j].toString(); if(str.length>i){ let index = str[str.length-1-i]; if(bucket[index] == null){ bucket[index] = []; } bucket[index].push(a[j]); count++; }else{ rest.push(a[j]); } } a.length = 0; bucket.forEach((val,index)=>{ a.push(...bucket[index]); count++; }) } if(a.length<arrLen){ a=[].concat(rest,a); } console.log('count = ',count); return a; } let radixtArr1 = radixSort([].concat(arr), 0, arr.length-1); console.log('radixSort', radixtArr1);
参考:
https://www.cnblogs.com/onepixel/p/7674659.html