JS经典算法

前言

在传统的计算机算法和数据结构领域,大多数专业教材和书籍的默认语言都是Java或者C/C+ +,对于一个前端来说,尤其是笔试面试的时候,算法方面考的其实不难(十大排序算法或是和十大排序算法同等难度的),但就是之前没用javascript实现过或是没仔细看过相关算法的原理,导致写起来浪费很多时间。所以撸一撸袖子决定自己查资料自己总结一篇博客等用到了直接看自己的博客就OK了,正所谓靠天靠地靠大牛不如靠自己(ˉ(∞)ˉ)。

正文

排序算法说明

(1) 排序的定义:对一序列对象根据某个关键字进行排序。

输出:n个数:a1,a2,a3,…an 输出:n个数的排列:a1’,a2’,a3’,…an’。简单的说就是调整数组中数据的位置,使其按照由小到大排序

(2) 对于评述算法优劣术语的说明

稳定:例如a原来在b前面,而a=b数组交换,排序之后a仍然在b前面。 不稳定:例如a原来在b前面,而a=b数组交换,排序之后a可能会出现在b的后面。 内排序:所有排序操作都是在内存中完成。 外排序:由于数据太大,因此把数据放到磁盘中,那么排序只有通过磁盘和内存的数据传输才能进行排序。 时间复杂度:一个算法执行所耗费的时间。 空间复杂度:运行完成一个程序所需内存的大小。

(3) 排序算法图片

名词解释:
n:数据规模
k:”桶”的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存

排序分类:
一、内部排序
1.插入排序: 直接插入排序和希尔排序 2.选择排序:简单选择排序和堆排序 3.交换排序:冒泡排序和快速排序 4.归并排序 5.基数排序

二、外部排序 内存和外存结合使用

冒泡排序

(1)算法描述

冒泡排序是一种简单的排序算法。它重复访问要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们互相交换过来。
一直重复操作直到没有元素再需要交换为止。这个算法名字的由来的因为越小的元素会经由交换慢慢交换到数列的顶端。

(2)算法描述和实现

  • 1.比较相邻元素。如果第一个比第二个大,就交换它们两个。
  • 2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的元素。
  • 3.针对所有的元素重复以上的步骤,除了最后一个。
  • 4.重复步骤1~3,直到排序完成。

传统冒泡算法中每一趟排序操作只能找到一个最大值或者最小值。

JavaScript代码实现:

function bubbleSort(arr) {
    var len = arr.length;
    console.time('初始冒泡排序耗时');
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                var temp = arr[j+1];        //元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    console.timeEnd('初始冒泡排序耗时');
    return arr;
}

var arr = [8, 10, 3, 5, 6, 9, 2, 4, 7, 1];
console.log(bubbleSort3(arr));//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 初始冒泡排序耗时: 0.421142578125ms

改进后的冒泡算法我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大和最小),从而使排序次数几乎减少了一半

JavaScript代码实现:

function bubbleSort3(arr3) {
    var low = 0;
    var high= arr.length-1; //设置变量的初始值
    var tmp,j;
    console.time('改进后冒泡排序耗时');
    while (low < high) {
        for (j= low; j< high; ++j){ //正向冒泡,找到最大者
            if (arr[j]> arr[j+1]) {
                tmp = arr[j]; arr[j]=arr[j+1];arr[j+1]=tmp;
            }
        }
        --high;                 //修改high值, 前移一位
        for (j=high; j>low; --j){ //反向冒泡,找到最小者
            if (arr[j]<arr[j-1]) {
                tmp = arr[j]; arr[j]=arr[j-1];arr[j-1]=tmp;
            }
        }
        ++low;                  //修改low值,后移一位
    }
    console.timeEnd('改进后冒泡排序耗时');
    return arr3;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(bubbleSort3(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]// 改进后冒泡排序耗时: 0.056884765625ms

冒泡排序动图演示:

(3)算法分析

  • 最佳情况:T(n) = O(n)

    当输入的数据已经是正序时

  • 最差情况:T(n) = O(n2)

    当输入的数据是反序时

  • 平均情况:T(n) = O(n2)

选择排序

(1)算法描述

选择排序是一种简单直观的排序算法。
他的工作原理:
1.首先在未排序序列中找到最小(大)元素,存放在排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素。
3.放到已排序序列的末尾。
4.重复执行以上步骤,直到完成。

(2)算法描述和实现

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 1.初始状态:无序区为R[1…n],有序区为空。
  • 2.第i趟排序(i=1,2,3…,n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R[i..n]。该趟排序从当前无序区中选出关键字最小的记录为R[k],将他与无序区的第一个记录R交换, 使R[1…i]和R[i+1…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
  • 3.n-1趟结束,数组有序化了。

Javascript代码实现:

    function selectionSort () {
        var len = arr.length;
        var minIndex, temp;
        console.time('选择排序耗时');
        for (var i = 0; i < len - 1; i++) {
            minIndex = i; // 记录第一个位置;
            for (var j = i + 1; j < len;j++) {
                if (arr[minIndex] > arr[j]) {
                    minIndex = j // 记录最大的位置;
                }
            }
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        console.timeEnd('选择排序耗时');
        return arr;
    }
    
    var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
    console.log(selectionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
    // 选择排序耗时: 0.018798828125ms

选择排序动图演示:

(3)算法分析

  • 最佳情况:T(n) = O(n2)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(n2)

插入排序

(1)算法描述

插入排序的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

(2)算法描述和实现

一般来说,插入排序都采用in-place(占用常数内存,不占用额外内存 )在数组上实现。具体算法描述如下:

  • 1.从第一个元素开始,该元素可以认为已经被排序。
  • 2.取出下一个元素,再已经排序的元素序列中从后向前扫描。
  • 3.如果该已排序的元素大于新元素,将该元素往后移到下一位置。
  • 4.重复第3步,知道找到已排序的元素小于或者等于新元素的位置。
  • 5.将新元素插入到该元素后
  • 6.重复2~5

Javascript代码实现:

    function insertionSort (arr) {
        if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array') {
            console.time('插入排序耗时:');
            for (var i = 1; i < arr.length; 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;
            }
            console.timeEnd('插入排序耗时:');
            return arr
        }
        else {
            return 'array is not an Array!';
        }
    }
    
    var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
    console.log(insertionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
    // 插入排序耗时:: 0.031982421875ms

改进插入排序:查找插入位置时使用二分查找的方式

function binaryInsertionSort (arr) {
    if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array') {
        console.time('二分插入排序耗时:');
        for (var i = 1; i < arr.length; i++) {
            var key = arr[i]; // 记录第二个元素
            var left = 0; // 记录左边起始位置
            var 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;
        }
        console.timeEnd('二分插入排序耗时:');
        return arr
    }
    else {
        return 'array is not an Array!';
    }
}

var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(binaryInsertionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 二分插入排序耗时:: 0.023193359375ms

插入排序动图演示:

(3)算法分析

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

希尔排序

(1)算法简介

希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。

(2)算法描述和实现

先将整个待排序的记录序列分割成若干子序列分别进行直接插入排序,具体算法描述:

  • 1.选择一个增量序列t1,t2,….tk,其中ti>tj,tk=1。
  • 2.按增量序列个数k,对序列进行k趟排序。
  • 3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

Javascript代码实现:

function shellSort(arr) {
    var len = arr.length,
        temp,
        gap = 1;
    console.time('希尔排序耗时');
    while(gap < len/5) {  //动态定义间隔序列
        // gap = 6
        gap =gap*5+1;
    }
    for (gap; gap > 0; gap = Math.floor(gap/5)) {
        // gap = 1
        for (var i = gap; i < len; i++) {
            temp = arr[i]; // 记录第二个元素
            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                arr[j+gap] = arr[j];
            }
            arr[j+gap] = temp;
        }
    }
    console.timeEnd('希尔排序耗时');
    return arr;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 希尔排序耗时: 8.02392578125ms

希尔排序图示(图片来源网络):

归并排序

(1)算法简介

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

(2)算法描述和实现

具体算法描述如下:

  • 1.把长度为n的输入序列分成两个长度为n/2的子序列。
  • 2.对这两个子序列分别代用归并排序。
  • 3.将两个排序好的子序列合并风一个最终的排序序列,即二路归并。

Javscript代码实现:

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 = [];
    console.time('归并排序耗时');
    while (left.length && right.length) {
        // 如果左边子序列大于右边子序列那么就把左边的第一个子项插入到result
        if (left[0] <= right[0]) {
            result.push(left.shift());
        }
        // 否则就把右边子序列的第一个子项插入到result
        else {
            result.push(right.shift());
        }
    }
    while (left.length) {
        result.push(left.shift());
    }
    while (right.length) {
        result.push(right.shift());
    }
    console.timeEnd('归并排序耗时');
    return result
}


var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(mergeSort(arr));
// 归并排序耗时: 0.005126953125ms

归并排序动图演示:

(3)算法分析

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

快速排序

(1)算法简介
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

(2)算法描述和实现

快速排序使用分治法来把一个分为两个子lists。具体算法描述如下:

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

Javascript代码实现:

function quickSort2 (arr) {
    console.time('2.快速排序耗时');
    if (arr.length <= 1) {
        return arr;
    }
    var pivotIndex = Math.floor(arr.length / 2); // 获取中间值坐标
    var pivot = arr.splice(pivotIndex, 1)[0]; // 获取中间值
    var left = [];
    var right = [];
    for (var i = 0; i < arr.length; i++) {
        // 第一元素小于中间值
        if (arr[i] < pivot) {
            left.push(arr[i]);
        }
        else {
            right.push(arr[i]);
        }
    }

    console.timeEnd('2.快速排序耗时');
    return quickSort2(left).concat([pivot], quickSort2(right)); // 拼接数组
}

var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(quickSort2(arr));
// 快速排序耗时: 0.001953125ms

快速排序动图演示:

(3)算法分析

  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(nlogn)

堆排序

(1)算法简介

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

(2)算法描述和实现

具体算法描述如下:

  • 1.将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区。
  • 2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n]。
  • 3.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

Javascript代码实现:

/*方法说明:堆排序
@param  array 待排序数组*/
function heapSort (arr) {
    console.time('堆排序耗时');
    if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array') {
        // 建堆
        var heapSize = arr.length;
        var temp;
        for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
            heapify(arr, i, heapSize);
        }
        //堆排序
        for (var j = heapSize - 1; j >= 1; j--) {
            temp = arr[0];
            arr[0] = arr[j];
            arr[j] = temp;
            heapify(arr, 0, --heapSize);
        }
        console.timeEnd('堆排序耗时');
        return arr;
    }
    else {
        return 'array is not an Array!';
    }
}

/*方法说明:维护堆的性质
@param  arr 数组
@param  x   数组下标
@param  len 堆大小*/
function heapify (arr, x, len) {
    if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') {
        // 左边
        var left = 2 * x + 1;
        // 右边
        var right = 2 * x + 2;
        var largest = x;
        var temp;

        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }
        if (right < len && arr[right] > arr[largest]) {
            largest = right
        }
        if (largest !== x) {
            temp = arr[x];
            arr[x] = arr[largest];
            arr[largest] = temp;
            heapify(arr, largest, len)
        }
    }
    else {
        return 'arr is not an Array or x is not a number!';
    }
}

var arr=[91,60,96,13,35,65,46,65,10,30,20,31,77,81,22];
console.log(heapSort(arr));//[10, 13, 20, 22, 30, 31, 35, 46, 60, 65, 65, 77, 81, 91, 96]
// 堆排序耗时: 0.13916015625ms

堆排序动图演示:

(3)算法分析

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

计数排序

(1)算法简介

计数排序是一种稳定的排序算法。
计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

(2)算法描述和实现

具体算法描述如下:

  • 1.找出待排序的数组中最大和最小的元素。
  • 2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项。
  • 3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  • 4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

Javascript代码实现:

function countingSort (arr) {
    var len = arr.length;
    var B = [];
    var C = []; // 额外的数组
    var min = max = arr[0];
    console.time('计数排序耗时');
    for (var i = 0; i < len; i++) {
        min = min <= arr[i] ? min : arr[i]; // 获取数组中最小的元素
        max = max >= arr[i] ? max : arr[i]; // 获取数组中最大的元素
        C[arr[i]] = C[arr[i]] ? C[arr[i]] + 1 : 1; // 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
    }
    // 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
    for (var j = min; j < max; j++) {
        C[j + 1] = (C[j + 1] || 0) + (C[j] || 0);
    }
    // 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
    for (var k = len - 1; k >= 0; k--) {
        B[C[arr[k]] - 1] = arr[k];
        C[arr[k]]--;
    }
    console.timeEnd('计数排序耗时');
    return B;
}

var arr = [2, 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2];
console.log(countingSort(arr)); //[1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]
// 计数排序耗时: 0.0478515625ms   

JavaScript动图演示:

(3)算法分析

当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n+k)

桶排序

(1)算法简介

桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排。

(2)算法描述和实现

具体算法描述如下:

  • 1.设置一个定量的数组当作空桶。
  • 2.遍历输入数据,并且把数据一个一个放到对应的桶里去。
  • 3.对每个不是空的桶进行排序。
  • 4.从不是空的桶里把排好序的数据拼接起来。

Javascript代码实现:

/*方法说明:桶排序
@param  array 数组
@param  num   桶的数量*/
function bucketSort (arr, num) {
    if (arr.length <= 1) {
        return arr;
    }
    var len = arr.length;
    var buckets = []; // 设置一个定量的数组当作空桶
    var result = [];
    var min = max = arr[0];
    var regex = '/^[1-9]+[0-9]*$/';
    var space;
    var n = 0;
    num = num || ((num > 1 && regex.text(num)) ? num : 10);
    console.time('桶排序耗时');

    // 获取最大值和最小值
    for (var i = 1; i < len; i++) {
        min = min <= arr[i] ? min : arr[i];
        max = max >= arr[i] ? max : arr[i];
    }
    space = (max - min + 1) / num;
    for (var j = 0; j < len; j++) {
        var index = Math.floor((arr[j] - min) / space);
        // 遍历输入数据,并且把数据一个一个放到对应的桶里去;
        if (buckets[index]) {   //  非空桶,插入排序
            var k = buckets[index].length - 1;
            while (k >= 0 && buckets[index][k] > arr[j]) {
                buckets[index][k + 1] = buckets[index][k];
                k--;
            }
            buckets[index][k + 1] = arr[j];
        }
        // 空桶,初始化
        else {
           buckets[index] = [];
           buckets[index].push(arr[j]);
        }
    }
    // 从不是空的桶里把排好序的数据拼接起来。
    while (n < num) {
        result = result.concat(buckets[n]);
        n++
    }
    console.timeEnd('桶排序耗时');
    return result;
}

var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(bucketSort(arr, 4));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 桶排序耗时: 0.113037109375ms

桶排序图示(图片来源网络):

(3)算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n2)

基数排序

(1)算法简介
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

(2)算法描述和实现
具体算法描述如下:

  • 1.取得数值中的最大数,并取得位数。
  • 2.arr为元素数组,从最低位开始取每个位组成radix数组。
  • 3.对radix进行计数排序(利用计数排序适用于小范围数的特点)。

Javascript代码实现:

/**
 * 基数排序适用于:
 *  (1)数据范围较小,建议在小于1000
 *  (2)每个数值都要大于等于0
 * @author xiazdong
 * @param  arr 待排序数组
 * @param  maxDigit 最大位数
 */
function radixSort (arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    var counter = [];
    console.time('基数排序耗时');
    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;
                }
            }
        }
    }
    console.timeEnd('基数排序耗时');
    return arr;
}


var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(radixSort(arr, 2)); //[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 基数排序耗时: 0.073974609375ms

基数排序LSD动图演示:

(3)算法分析

  • 最佳情况:T(n) = O(n * k)
  • 最差情况:T(n) = O(n * k)
  • 平均情况:T(n) = O(n * k)

基数排序有两种方法:

  • MSD 从高位开始进行排序
  • LSD 从低位开始进行排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

1.基数排序:根据键值的每位数字来分配桶
2.计数排序:每个桶只存储单一键值
3.桶排序:每个桶存储一定范围的数值