排序算法及其复杂度(JavaScript实现)

排序算法经过了很长时间的演变,产生了很多种不同的方法,每种算法都有它特定的使用场合。本文总结了常用的排序算法及其JavaScript实现。
randomArray是一个随机数组生成方法。

/**
* 生成随机数组
* @param   {Number}  随机数组大小
* @param   {Number}  最小值
* @param   {Number}  最大值
* @return  返回随机数组
*/
function randomArray(len, min, max){    
    if(!len || Object.prototype.toString.call(len) !== '[object Number]' || len < 2){
        //默认随机数组长度为10
        len = 10;
    }

    if(!min || Object.prototype.toString.call(min) !== '[object Number]'){
        //默认最小值为0
        min = 0;
    }

    if(!max || Object.prototype.toString.call(max) !== '[object Number]'){
        //默认最大值为100
        max = 100;
    }

    if(min >= max){
        min = 0;
        max = 100;
    }

    var i, arr = [], rnd, diff = max - min;
    for(i = 0; i < len; i++){
        rnd = Math.random();
        arr.push(Math.round(rnd * diff) + min);
    }

    return arr;
}


冒泡排序

/**
* 冒泡排序
* 冒泡排序是稳定排序
* 冒泡排序空间复杂度O(1)
* 最优时间复杂度O(n),当序列已经排序好时,时间复杂度为O(n)
* 最坏时间复杂度O(n^2),当序列是倒序时,时间复杂度为O(n^2)
* 冒泡排序是一种交换排序
* 
* 冒泡排序原理:
* 在序列中对当前还未排序的数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
* 即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
*/
var BubbleSort = {
    /**
    * @param  {Array}  待排序数组
    */
    sort: function(arr){
        console.log(arr);

        var i, j, tmp, len = arr.length;
        for(i = 0; i < len; i++){       
            for(j = len - 1; j > i; j--){
                if(arr[j] < arr[j - 1]){
                    tmp = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = tmp;
                }
            }
        }

        console.log(arr);
    },

    /**
    * 冒泡排序优化
    * @param  {Array}  待排序数组
    */
    optimzeSort: function(arr){
        console.log(arr);

        var i, j, tmp, flag, len = arr.length;
        for(i = 0; i < len; i++){
            //用flag来判断是否有交换
            flag = false;
            for(j = len - 1; j > i; j--){
                if(arr[j] < arr[j - 1]){
                    tmp = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = tmp;
                    flag = true;
                }
            }

            //如果没有交换,则数组已排序好,退出循环
            if(!flag){
                break;
            }
        }

        console.log(arr);
    }
};

冒泡排序测试

BubbleSort.sort(randomArray());


插入排序

插入排序分为直接插入排序、希尔排序

var InserctionSort = {
    /**
    * 直接插入排序
    * 直接插入排序属于稳定排序
    * 直接插入排序空间复杂度O(1)
    * 最优时间复杂度O(n),当待排序的数组已经排序好时,直接插入排序的时间复杂度为O(n)
    * 最坏时间复杂度O(n^2),当待排序的数组是倒序时,直接插入排序的时间复杂度为O(n^2)
    * 直接插入排序适用于数量比较少的数组排序
    * 
    * 基本原理:
    * 将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
    */
    directInsert: function(arr){
        console.log(arr);

        var i, j, tmp, min, len = arr.length;
        for(i = 0; i < len - 1; i++){
            min = i;
            for(j = i + 1; j < len; j++){
                if(arr[j] < arr[min]){
                    min = j;
                }
            }

            if(min !== i){
                tmp = arr[min];
                arr[min] = arr[i];
                arr[i] = tmp;
            }
        }

        console.log(arr);
    },

    /**
    * 二分插入排序是直接插入排序的改进版 
    * 二分插入排序是不稳定排序
    * 
    * 原理:
    * 将一个记录插入到已排序好的有序序列中,从而得到一个新,记录数增1的有序序列。
    * 二分插入排序用二分法找出新记录在有序序列中的位置。
    */
    binaryInsert: function(arr){
        console.log(arr);

        var i, j, left, right, center, tmp, len = arr.length;
        for (i = 1; i < len; i++) {

            //如果新记录小于有序序列的最大元素,则用二分法找出新纪录在有序序列中的位置
            if (arr[i] < arr[i - 1]) {
                left = 0;
                right = i - 1;
                while (left < right) {
                    //获取中间位置索引,把有序序列分成两个子序列
                    center = Math.ceil((right + left) / 2);

                    if (arr[center] < arr[i]) {
                        //如果新纪录大于中间位置记录,则在右边序列继续进行二分
                        left = center + 1;
                    } else {
                        //如果新纪录小于中间位置记录,则在左边序列继续进行二分
                        right = center - 1;
                    }
                }

                tmp = arr[i];

                //把比arr[i]大的记录往后移
                for (j = i; j > left; j--) {
                    arr[j] = arr[j - 1];
                }

                arr[left] = tmp;
            }
        }

        console.log(arr);
    },

    /**
    * 希尔排序
    * 希尔排序又叫缩小增量排序,是直接插入排序算法的一种更高效的改进版本
    * 希尔排序属于不稳定排序
    * 希尔排序空间复杂度O(1)
    * 希尔排序的时间复杂度和其增量序列有关系,预计平均时间复杂度O(n^1.3)
    * 
    * 基本原理:
    * 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
    */
    shell: function(arr){
        console.log(arr);

        var i, j, tmp, swap, len = arr.length, step = Math.floor(len / 2);      
        while(step > 0){
            for(i = step; i < len; i++){
                tmp = arr[i];
                swap = false;
                for(j = i - step; j >= 0 && arr[j] > tmp; j -= step){
                    swap = true;
                    arr[j + step] = arr[j];
                }

                if(swap){
                    arr[j + step] = tmp;
                }
            }

            step = Math.floor(step / 2);
        }

        console.log(arr);
    }
};

插入排序测试

// 直接插入排序
InserctionSort.directInsert(randomArray());
// 希尔排序
InserctionSort.shell(randomArray());


直接选择排序

/**
* 直接选择排序
* 直接选择排序是一种不稳定的排序
* 时间复杂度为 O(n^2),当记录占用字节数较多时,直接选择排序通常比直接插入排序的执行速度快些。
* 空间复杂度为O(1)
* 
* 原理:从未排序序列中找到最小元素,存放到已排序序列的末尾,以此类推,直到所有元素均排序完毕。
*/
var DirectSelectionSort = {
    sort: function(arr){
        console.log(arr);

        var i, j, min, tmp, len = arr.length;

        for(i = 0; i < len; i++){
            min = i;

            //从arr[i]~arr[len-1]中找出最小的记录
            for(j = i + 1; j < len; j++){
                if(arr[min] > arr[j]){
                    min = j;
                }
            }

            //如果最小记录不是arr[i],把arr[i]和arr[min]交换位置
            //使得arr[0]~arr[i]是有序序列,而且比arr[i+1]~arr[len-1]中任何记录都小
            if(min != i){
                tmp = arr[i];
                arr[i] = arr[min];
                arr[min] = tmp;
            }
        }

        console.log(arr);
    }
};

直接选择排序测试

DirectSelectionSort.sort(randomArray());


堆排序

/**
* 堆排序 堆排序是一种树形选择排序,是直接选择排序的有效改进,是不稳定的排序
* 最优时间复杂度O(nlogn)
* 最坏时间复杂度O(nlogn)
* 空间复杂度O(1)
* 
* 堆排序原理:
* 1、根据序列建立大根堆(或者小根堆)。
* 2、将堆顶元素R[1]与最后一个元素R[n]交换,得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]。
* 3、对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,
*   得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
*/
var HeapSort = {
    sort: function(arr){
        console.log(arr);

        var i, len = arr.length;

        //创建大根堆
        for (i = Math.ceil((len - 1) / 2); i >= 0; i--) {
            HeapSort.adjuestMaxHeap(arr, i, len);
        }

        for (i = len - 1; i > 0; i--) {
            //把大根堆的顶部元素移到arr[i]处,使得arr[i] ~ arr[len - 1]为有序序列
            HeapSort.swap(arr, 0, i);

            //调整大根堆
            //大根堆的在数组中的位置为0 ~ i
            HeapSort.adjuestMaxHeap(arr, 0, i);
        }

        console.log(arr);
    },

    /**
    * 交换数组中i,j位置的值
    */
    swap: function(arr, i, j){
        var tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    },

    /**
    * 调整大根堆
    * 
    * @param  {Array}   待排序数组
    * @param  {Number}  父节点在数组中的索引
    * @param  {Number}  调整堆的长度
    */
    adjuestMaxHeap: function(arr, i, size){
        var lchild = 2 * i + 1, // 左孩子在数组中的索引
            rchild = lchild + 1, // 右孩子在数组中的索引
            max = i; // 临时变量,最大值索引

        while(lchild < size){
            if(arr[lchild] > arr[max]){
                max = lchild;
            }

            if (rchild < size && arr[rchild] > arr[max]) {
                max = rchild;
            }

            if (max !== i) {
                HeapSort.swap(arr, max, i);

                i = max;
                lchild = 2 * i + 1;
                rchild = lchild + 1;
            } else {
                break;
            }
        }
    }
};

堆排序测试

HeapSort.sort(randomArray());


快速排序

/**
 * 快速排序
 * 快速排序是不稳定排序
 * 快速排序是一种交换排序
 * 快速排序对序列的操作空间复杂度为O(1),如果快速排序用递归实现,则递归栈的空间复杂度为O(logn)~O(n)之间。
 * 最佳时间复杂度O(nlogn)
 * 平均时间复杂度O(nlogn) 
 * 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
 * 
 * 快速排序原理:
 * 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,
 * 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
 */
var QuickSort = {
    sort: function(arr){
        console.log(arr);
        var start = 0, end = arr.length - 1;
        QuickSort.recursion(arr, 0, end);
        console.log(arr);
    },

    recursion: function(arr, start, end){
        var split;
        if(start < end){
            split = QuickSort.partition(arr, start, end);
            QuickSort.recursion(arr, start, split - 1);
            QuickSort.recursion(arr, split + 1, end);
        }
    },

    /**
    * 交换数组中i,j位置的值
    */
    swap: function(arr, i, j){
        var tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    },

    /**
    * 对序列进行排序
    * @param   {Array}   待排序数组
    * @param   {Number}  排序开始索引
    * @param   {Number}  排序结束索引
    * @return  {Number}  返回用于比较的基准元素的索引
    */
    partition: function(arr, start, end){
        var privot = arr[start];

        while(start < end){
            while(start < end && arr[end] >= privot){
                end--;
            }

            QuickSort.swap(arr, start, end);

            while(start < end && arr[start] <= privot){
                start++;
            }

            QuickSort.swap(arr, start, end);
        }

        return start;
    }
};

快速排序测试

QuickSort.sort(randomArray());


归并排序

/**
 * 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法将已有序的子序列合并,最后得到完全有序的序列。
 * 归并排序是稳定排序,速度仅次于快速排序
 * 时间复杂度为O(nlogn)
 * 空间复杂度为O(n) 归并排序需要n空间的临时数组来存储子序列
 * 归并排序原理:
 * 将待排序序列分为若干个子序列,,对每个子序列进行排序。
 * 然后再把相邻的两个有序子序列合并,并排序成为新的有序子序列。
 * 依次类推,最终把所有子序列合并成一个有序序列。
 * 
 */
var MergeSort = {
    sort: function(arr){
        console.log(arr);       
        MergeSort._sort(arr, 0, arr.length - 1);        
        console.log(arr);
    },

    _sort: function(arr, start, end){
        if(start >= end){
            return;
        }

        var middle = Math.floor((start + end) / 2);
        MergeSort._sort(arr, start, middle);
        MergeSort._sort(arr, middle + 1, end);
        MergeSort.merge(arr, start, middle, end);
    },

    /**
     * 有序的相邻子序列合并
     * @param arr 数组
     * @param start 左边序列开始索引
     * @param middle 左边序列结束索引,middle+1是右边序列开始索引
     * @param end 右边序列结束索引
     */
    merge: function(arr, start, middle, end){
        //临时数组长度
        var tmplen = end - start + 1;

        //临时数组
        var tmp = [];

        //左边数组的索引
        var left = start;

        //右边数组的索引
        var right = middle + 1;

        //tmp数组的索引
        var i = 0;

        while(left <= middle && right <= end){
            if(arr[left] < arr[right]){
                tmp[i] = arr[left];
                left++;
            }else{
                tmp[i] = arr[right];
                right++;
            }

            i++;
        }

        while(left <= middle){
            tmp[i] = arr[left];
            left++;
            i++;
        }

        while(right <= end){
            tmp[i] = arr[right];
            right++;
            i++;
        }

        for(i = 0; i < tmplen; i++){
            arr[start + i] = tmp[i];
        }
    }
};

归并排序测试

MergeSort.sort(randomArray());


总结

排序是否稳定最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度
冒泡排序稳定O(n)O(n^2)O(n^2)O(1)
直接插入排序稳定O(n)O(n^2)O(n^2)O(1)
希尔排序不稳定O(n^1.3)O(n^1.3)O(n^1.3)O(1)
直接选择排序不稳定O(n^2)O(n^2)O(n^2)O(1)
堆排序不稳定O(nlog2n)O(nlog2n)O(nlog2n)O(1)
快速排序不稳定O(nlog2n)O(n^2)O(nlog2n)O(nlog2n)
归并排序稳定O(nlog2n)O(nlog2n)O(nlog2n)O(n)
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值