JavaScript十种排序算法总结

十种排序对比

在这里插入图片描述

排序的分类

在这里插入图片描述

1 冒泡排序(BubbleSort)

特点:稳定排序(stable sort)、原地排序(In-place sort)
思想:通过两两交换,像水中的泡泡一样,小的先冒出来,大的后冒出来。
最坏运行时间:O(n^2)
最佳运行时间:O(n^2)(当然,也可以进行改进使得最佳运行时间为O(n))

冒泡排序算法的运作如下:(从后往前)
(1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现
function bubbleSort(arr) {
  var i = arr.length, j;
   var tempExchangVal;
   while (i > 0) {
      for (j = 0; j < i - 1; j++) {
         if (arr[j] > arr[j + 1]) {
            tempExchangVal = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = tempExchangVal;
         }
      }
      i--;
   }
   return arr;
}

var arr = [3, 2, 4, 9, 1, 5, 7, 6, 8];
var arrSorted = bubbleSort(arr);
console.log(arrSorted);
alert(arrSorted);

function bubbleSort (arr) {
 let len = arr.length;
   for (let i = 0; i < len; i++) {
   	for (let j = 0; j < len - 1 -i; j++) {
		if (arr[j] > arr[j + 1]) {
			let temp = arr[j + 1];
			arr[j + 1] = arr[j];
			arr[j] = temp;
		}
   	}
   }
  return arr;
}

2 选择排序(SelectionSort)

特性:原地排序(In-place sort),不稳定排序(unstable sort)。
思想:每次找一个最小值。
最好情况时间:O(n^2)。
最坏情况时间:O(n^2)。

思想:
n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果:
①初始状态:无序区为R[1…n],有序区为空。
②第1趟排序
在无序区R[1…n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1…1]和R[2…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
……
③第i趟排序
第i趟排序开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

代码实现
/*方法1*/
var a = [3,46,55,2,5,9,8,4,33,7];
 function selectSort(arr) {
     var min=0;
     for (var i = 0; i < arr.length; i++) {
         min = i; //将数组排好序部分的后一个数作为基准数  
         for (var j = i + 1; j < arr.length; j++) {
             if (arr[min] > arr[j])
                 min = j; //找到正确的最小数的数组下标  
         }
         if (min != i) { //将上面找到的数组最小值同数组排好序部分的后一个数替换位置  
             arr[i]= [arr[min],arr[min]=arr[i]][0]; //交换元素的另一种方式,等效于上面冒泡的temp方式  
         }
         document.write(arr+'----');
         document.write('这是第'+(i+1)+'次排序结果'+'</br>')
     }
 }
 selectSort(a);

/*方法2*/
var arr1=[7,12,4,23,21,2,17,13,6,33];
function selectSort2 (arr) {
  let min = null;
  let temp = null;
  let index = null;
  for (let i = 0; i < arr.length; i++) {
    min = arr[i];
    for (let j = i + 1; j < arr,length; j++) {
      if (arr[j] < min) {
        min = arr[j];
        index = j;
      }
    }
    temp = arr[i];
    arr[i] = min;
    arr[index] = temp;
  }
  return arr;
}

3 插入排序(InsertSort)

特点:稳定排序(stable sort)、原地排序(In-place sort)
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n),而快速排序在这种情况下会产生O(n^2)的复杂度。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)
插入排序比较适合用于“少量元素的数组”。

分类
包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序)。属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) 。

直接插入排序
直接插入排序是一种简单的插入排序。
其基本思想是:把待排序的纪录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的纪录插入完为止,得到一个新的有序序列。
直接插入排序的算法思路:
(1) 设置监视哨r[0],将待插入纪录的值赋值给r[0];
(2) 设置开始查找的位置j;
(3) 在数组中进行搜索,搜索中将第j个纪录后移,直至r[0].key≥r[j].key为止;
(4) 将r[0]插入r[j+1]的位置上。

折半插入排序的算法思想:
算法的基本过程:
(1)计算 0 ~ i-1 的中间点,用 i 索引处的元素与中间值进行比较,如果 i 索引处的元素大,说明要插入的这个元素应该在中间值和刚加入i索引之间,反之,就是在刚开始的位置 到中间值的位置,这样很简单的完成了折半;
(2)在相应的半个范围里面找插入的位置时,不断的用(1)步骤缩小范围,不停的折半,范围依次缩小为 1/2 1/4 1/8 …快速的确定出第 i 个元素要插在什么地方;
(3)确定位置之后,将整个序列后移,并将元素插入到相应位置。

代码实现
/**
* 插入排序算法
* @param  {Array} arr 需要排序的数组
* @return {Array}     从小到大排序好的数组
*/
function insertSort(arr){
   var len = arr.length;
   for (var i = 1; i < len; i++) {
       var key = arr[i];
       var j = i - 1;
       while (j >= 0 && arr[j] > key) {
           arr[j + 1] = arr[j];
           j--;
       }
       arr[j + 1] = key;
   }
   return arr;
}

算法分析

最佳情况:输入数组按升序排列。T(n) = O(n)
最坏情况:输入数组按降序排列。T(n) = O(n^2)
平均情况:T(n) = O(n^2)

/**
 * 二分法插入排序
 * @param  {array} arr 需要排序的数组
 * @return {array}     排序后的数组
 */
function binaryInsertSort(arr){
    for (var i = 1; i < arr.length; i++) {
		var key = arr[i], left = 0, right = i - 1;
		while (left <= right) {
			var middle = parseInt((left + right) / 2);
			if (key < arr[middle]) {
			  right = middle - 1;
			} else {
			  left = middle + 1;
			}
		}
		for (var j = i - 1; j >= left; j--) {
		  arr[j + 1] = arr[j];
		}
		arr[left] = key;
	}
	return arr;
}

算法分析

最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n^2)
平均情况:T(n) = O(n^2)

4 Shell排序(ShellSort)

介绍:
希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1、插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
2、但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位

思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 dt=1( dt < d(t-1) …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。

var arr = [49,38,65,97,76,13,27,49,55,04];
            len = arr.length;
    for(var fraction = Math.floor(len/2); fraction>0;fraction = Math.floor(fraction/2)){
        for(var i = fraction;i < len;i++){
            for(var j = i-fraction; j >= 0 && arr[j] > arr[fraction+j]; j -= fraction){
                var temp = arr[j];
                arr[j] = arr[fraction+j];
                arr[fraction+j] = temp;
            }
        }
    }
    console.log(arr);

比较:
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起 快速排序(QuickSort),归并排序( MergeSort ),堆排序(HeapSort)慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。

优缺点:
优点:快,数据移动少、无需大量的辅助空间
缺点:不稳定,d增量的取值是多少,应取多少个不同的值,都无法确切知道,只能凭经验来取。shell排序是不稳定的

5 归并排序(MergeSort)

特点:稳定排序(stable sort)、Out-place sort
思想:运用分治法思想解决排序问题。
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)

介绍:
归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用

步骤:
(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4)重复步骤3直到某一指针达到序列尾
(5)将另一序列剩下的所有元素直接复制到合并序列尾

function merge(left, right){
    var result=[];
    while(left.length>0 && right.length>0){
        if(left[0]<right[0]){
        /*shift()方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。*/
            result.push(left.shift());
        }else{
            result.push(right.shift());
        }
    }
    return result.concat(left).concat(right);
}
function mergeSort(items){
    if(items.length == 1){
        return items;
}
var middle = Math.floor(items.length/2),
    left = items.slice(0, middle),
    right = items.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

优点:速度仅次于快速排序,为稳定排序算法
缺点:需要很多额外的空间

6 快速排序(QuickSort)

特性:不稳定排序(unstable sort)、原地排序(In-place sort)。
最坏运行时间:O(n^2)
最佳运行时间:O(nlgn)
快速排序的思想:分治法

介绍:
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来,且在大部分真实世界的数据,可以决定设计的选择,减少所需时间的二次方项之可能性。

步骤:
(1)从数列中挑出一个元素,称为 “基准”(pivot),
(2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
(3)递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

代码实现

function quickSort(array){
        function sort(prev, numsize){
            var nonius = prev;
            var j = numsize -1;
            var flag = array[prev];
            if ((numsize - prev) > 1) {
                while(nonius < j){
                    for(; nonius < j; j--){
                        if (array[j] < flag) {
                            array[nonius++] = array[j]; 
                            //a[i] = a[j]; i += 1;
                            break;
                        };
                    }
                    for( ; nonius < j; nonius++){
                        if (array[nonius] > flag){
                            array[j--] = array[nonius];
                            break;
                        }
                    }
                }
                array[nonius] = flag;
                sort(0, nonius);
                sort(nonius + 1, numsize);
            }
        }
        sort(0, array.length);
        return array;
    }

优点:极快、数据移动少
缺点:不稳定

7 堆排序(HeapSort)

1964年Williams提出
特性:不稳定排序(unstable sort)、原地排序(In-place sort)。
最优时间:O(nlgn)
最差时间:O(nlgn)
思想:运用了最小堆、最大堆这个数据结构,而堆还能用于构建优先队列

优先队列应用于进程间调度、任务调度等。
堆数据结构应用于Dijkstra、Prim算法。

步骤:
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

(1)用大根堆排序的基本思想
① 先将初始文件R[1…n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1…n-1]和有序区R[n],且满足R[1…n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1…n-1]调整为堆。然后再次将R[1…n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1…n-2]和有序区R[n-1…n],且仍满足关系R[1…n-2].keys≤R[n-1…n].keys,同样要将R[1…n-2]调整为堆。
……
直到无序区只有一个元素为止。

(2)大根堆排序算法的基本操作:
①建堆:建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。
首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程;
然后再将根节点取出,这样一直到所有节点都取出。
堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)[2]

注意
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止

代码实现

	 Array.prototype.buildMaxHeap=function(){
        for(var i=Math.floor(this.length/2)-1;i>=0;i--){
            this.heapAdjust(i,this.length);
        }
    };

    Array.prototype.swap=function(i,j){
        var tmp=this[i];
        this[i]=this[j];
        this[j]=tmp;
    };

    Array.prototype.heapSort=function(){
        this.buildMaxHeap();
        for(vari=this.length-1;i>0;i--){
            this.swap(0,i);
            this.heapAdjust(0,i);
        }

        return this;
    };

    Array.prototype.heapAdjust=function(i,j){
        var largest=i;
        var left=2*i+1;
        var right=2*i+2;

        if(left<j&&this[largest]<this[left]){
            largest=left;
        }

        if(right<j&&this[largest]<this[right]){
            largest=right;
        }

        if(largest!=i){
            this.swap(i,largest);
            this.heapAdjust(largest,j);
        }
    };

    var a=new Array();
    [].push.apply(a,[2,3,89,57,23,72,43,105]);
    console.log(a.heapSort());

特点
堆排序(HeapSort)是一树形选择排序。
堆排序的特点是:在排序过程中,将R[l…n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系(参见二叉树的顺序存储结构),在当前无序区中选择关键字最大(或最小)的记录

其他性能
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1).
它是不稳定的排序方法。(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化)

8 计数排序(CountingSort)

特性:stable sort、out-place sort。
最坏情况运行时间:O(n+k)
最好情况运行时间:O(n+k)
当k=O(n)时,计数排序时间为O(n)

思想:
计数排序对输入的数据有附加的限制条件:
(1)输入的线性表的元素属于有限偏序集S;
(2)设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。

算法过程:
假设输入的线性表L的长度为n,L=L1,L2,…,Ln;线性表的元素属于有限偏序集S,|S|=k且k=O(n),S={S1,S2,…Sk};则计数排序可以描述如下:
1、扫描整个集合S,对每一个Si∈S,找到在线性表L中小于等于Si的元素的个数T(Si);
2、扫描整个线性表L,对L中的每一个元素Li,将Li放在输出线性表的第T(Li)个位置上,并将T(Li)减1。

代码实现

function countSort(array) {
  const newArray = [], C = [];
  let min = array[0];
  let max = array[0];
  for (let i = 0; i < array.length; i++) {
      if (min >= array[i]) {
          min = array[i];
      }
      if (max <= array[i]) {
          max = array[i];
      }
      if (C[array[i]] = C[array[i]]) {
          C[array[i]]++;
      } else {
          C[array[i]] = 1;
      }
  }
  for (let j = min; j < max; j++) {
      C[j + 1] = (C[j + 1] || 0) + (C[j] || 0);
  }
  for (let k = array.length - 1; k >= 0; k--) {
      newArray[C[array[k]] - 1] = array[k];
      C[array[k]]--;
  }
  return newArray;
}

9 桶排序(BinSort)

假设输入数组的元素都在[0,1)之间。
特性:out-place sort、stable sort。
最坏情况运行时间:当分布不均匀时,全部元素都分到一个桶中,则O(n^2),当然[算法导论8.4-2]也可以将插入排序换成堆排序、快速排序等,这样最坏情况就是O(nlgn)。
最好情况运行时间:O(n)

基本思想:
桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序,介于MSD基数排序和LSD基数排序之间。关于基数排序可以参看上一篇博文《排序算法九:基数排序》。

基本流程:
建立一堆buckets;
遍历原始数组,并将数据放入到各自的buckets当中;
对非空的buckets进行排序;
按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。

图示:
在这里插入图片描述
代码实现

function bucketSort(array, num) {
    if(array.length <= 1) {
        return array;
    }
    var len = array.length,buckets = [],result = [], min = array[0],max = array[0], regex = '/^[1-9]+[0-9]*$/',space,n = 0;
    num = num || ((num > 1 && regex.text(num)) ? num : 10);
    console.time('桶排序耗时');
    for(var i = 1; i < len; i++) {
        min = min <= array[i] ? min : array[i];
        max = max >= array[i] ? max : array[i];
    }
    space = (max - min + 1) / num;
    for(var j = 0; j <= len; j++) {
        var index = Math.floor((array[j] - min) / space);
        if(buckets[index]) {
            var k = buckets[index].length - 1;
            while(k >= 0 && buckets[index][k] > array[j]) {
                buckets[index][k + 1] = buckets[index][k];
                k--;
            }
            buckets[index][k + 1] = array[j];
        }else {
            buckets[index] = [];
            buckets[index].push(array[j]);
        }
    }
    while(n < num) {
       result = result.concat(buckets[n]);
        n++;
    }
    console.timeEnd('桶排序耗时');
    return result;
}
var arr = [3, 44, 38, 5, 47, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(bucketSort(arr,4));

//输出结果
//桶排序耗时: 0.368ms
//[ 2, 3, 4, 5, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50 ]

10 基数排序(RadixSort)

特性:stable sort、Out-place sort。
最坏情况运行时间:O((n+k)d)
最好情况运行时间:O((n+k)d)

当d为常数、k=O(n)时,效率为O(n);
我们也不一定要一位一位排序,我们可以多位多位排序,比如一共10位,我们可以先对低5位排序,再对高5位排序。
引理:假设n个b位数,将b位数分为多个单元,且每个单元为r位,那么基数排序的效率为O[(b/r)(n+2^r)]。
当b=O(nlgn),r=lgn时,基数排序效率O(n)

思想:
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

代码实现:

//LSD Radix Sort
var counter = [];
function radixSort(arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        for(var j = 0; j < arr.length; j++) {
            var bucket = parseInt((arr[j] % mod) / dev);
            if(counter[bucket]==null) {
                counter[bucket] = [];
            }
            counter[bucket].push(arr[j]);
        }
        var pos = 0;
        for(var j = 0; j < counter.length; j++) {
            var value = null;
            if(counter[j]!=null) {
                while ((value = counter[j].shift()) != null) {
                      arr[pos++] = value;
                }
          }
        }
    }
    return arr;
}

基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值