注:n
表示数据规模,k
表示桶的个数,In-place
表示占用常数内存或不占用额外内存,Out-place
表示占用额外内存。什么是稳定性?当排序后2个相等键值的顺序 和 排序前它们的顺序相同,则表示“稳定”。
一、冒泡排序
冒泡排序是最简单的排序算法之一,冒泡排序给我们的感觉就像是 Abandon在单词书里出现一样,每次都在第一页的第一位。
冒泡排序还有一种优化算法,就是立一个 flag,当在一次序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有太大作用。
冒泡排序在什么情况下最快?当输入的数据已经是正序时。那么在什么情况下最慢呢?当输入的数据是反序时。
function bubbleSort(arr){
var len = arr.length;
for(var i=0; i arr[j+1]){ // 相信元素比大小
var temp = arr[j+1]; // 元素位置交换,从小到大排列
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(bubbleSort(arr).join(','));
二、选择排序
选择排序是最稳定的排序算法之一,因为无论什么数据进去都是O(n²)的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。
function selectionSort(arr){
var len = arr.length;
var minIndex, temp;
for(var i=0; i
三、插入排序
插入排序的代码实现,虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的,因为只要打过扑克牌的人都应该能够秒懂。打扑克牌时,每摸起一张牌,通常按牌的大小进行插入排序。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
function insertSort(arr){
var len = arr.length;
var preIndex, current;
for(var i=0; i= 0 && arr[preIndex] > current){
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(insertSort(arr).join(','));
四、希尔排序
希尔排序是插入排序的一种更高效率的实现。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版》的合著者Robert Sedgewick提出的。
function shellSort(arr){
var len = arr.length;
var temp, gap=1;
while(gap0; gap=Math.floor(gap/3)){
for(var i=gap; i=0 && arr[j]>temp; j-=gap){
arr[j+gap] = arr[j];
}
arr[j+gap] = temp;
}
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(shellSort(arr).join(','));
五、归并排序
作为一种典型的“分而治之”思想的算法应用,归并排序的实现有两种方法:自上而下的递归、自下而上的迭代。
因为所有递归的方法都可以用迭代重写,所以就有了第2种方法 。在《数据结构与算法JavaScript描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
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 && right.length){
if(left[0] <= right[0]){
result.push(left.shift());
}else{
result.push(right.shift());
}
}
while(left.length){
result.push(left.shift());
}
while(right.length){
result.push(right.shift());
}
return result;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(mergeSort(arr).join(','));
六、快速排序
快速排序又是一种“分而治之”思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高。它是处理大量数据最快的排序算法之一。虽然Worst Case的时间复杂度达到了O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为O(n log n) 的排序算法表现要更好。
快速排序的最坏运行情况是O(n²),比如说顺序数列的快排。但它的平摊期望时间是O(n log n) ,且O(n log n)记号中隐含的常数因子很小,比复杂度稳定等于O(n log n)的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
function quickSort(arr,left,right){
var len = arr.length;
var partitionIndex,
left = typeof left != 'number' ? 0 : left,
right = typeof right != 'number' ? len-1 : right;
if(left
七、堆排序
堆排序可以说是一种利用了堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列。
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列。
var len;
// 建立大顶堆
function buildMaxHeap(arr){
len = arr.length;
for(var i=Math.floor(len/2); i>=0; i--){
heapify(arr,i);
}
}
// 堆调整
function heapify(arr,i){
var left = 2*i + 1;
var right = 2*i + 2;
var largest = i;
if(leftarr[largest]){
largest = left;
}
if(rightarr[largest]){
largest = right;
}
if(largest != i){
swap(arr,i,largest);
heapify(arr,largest);
}
}
// 交换位置
function swap(arr,i,j){
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 推排序
function heapSort(arr){
buildMaxHeap(arr); // 建立大顶堆
for(var i=arr.length-1; i>0; i--){
swap(arr,0,i);
len--;
heapify(arr,0);
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(heapSort(arr).join(','));
八、计数排序
计数排序的核心在于把输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
function countingSort(arr,maxValue){
var bucket = new Array(maxValue+1);
var sortedIndex = 0;
var arrLen = arr.length;
var bucketLen = maxValue+1;
for(var i=0; i0){
arr[sortedIndex++]=j;
bucket[j]--;
}
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(countingSort(arr,200).join(','));
九、桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
为了使桶排序更加高效,我们需要做到这两点:在额外空间充足的情况下,尽量增大桶的数量,使用的映射函数能够将输入的N个数据均匀地分配到K个桶中;同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
桶排序在什么情况下最快?当输入的数据可以均匀的分配到每一个桶中时。那么什么情况下最慢呢?当输入的数据被分配到了同一个桶中时。
function bucketSort(arr,bucketSize){ // 桶的数量不能为负
if(arr.length === 0){
return arr;
}
var i;
var minValue = arr[0];
var maxValue = arr[0];
for(i=1; i maxValue){
maxValue = arr[i]; // 输出最大的值
}
}
// 桶的初始化
var DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数量为5
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
var bucketCount = Math.floor((maxValue-minValue)/bucketSize) + 1;
var buckets = new Array(bucketCount);
for(i=0; i= 0 && arr[preIndex] > current){
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
// 测试
var arr = [6,2,22,45,1,6,8,200,56,111];
console.log(bucketSort(arr,9).join(','));
十、基数排序
基数排序有两种方法:MSD,从高位开始进行排序; LSD,从低位开始进行排序。
基数排序、计数排序、桶排序,这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:基数排序根据键值的每位数字来分配桶;计数排序中,每个桶只存储单一键值;桶排序中,每个桶存储一定范围的数值。
var counter = [];
function radixSort(arr,maxDigit){
var mod = 10;
var dev = 1;
for(var i=0; i
参考资源:十大经典排序算法
本篇完!! 2019年6月24日!!