直接插入排序、希尔排序、冒泡排序、快速排序、简单选择排序等排序对比

排序定义:排序是将一个数据元素的任意序列,重新排列成一个关键字有序的序列。简单来讲就是将一组杂乱的数据元素按照一定的规律排列成有序元素。

插入排序

插入排序类思想:逐个查看每一个待排序的元素,将每一个待排序的元素插入到前面已经排好序的序列中合适的位置,使的新的序列仍然是一个有序序列。

1.直接插入排序
思想:将n个待排序的元素分别分成一个有序表和一个无序表,开始时有序表中放入一个元素,剩下的n-1个无序元素依次插入有序序列,每插入一次有序表重排一次,直至得到n个元素有序的序列。

下面以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
初始关键字:(26) 36 49 11 15 36 32 13
第一趟:(26 36) 49 11 15 36 32 13
第二趟:(26 36 49) 11 15 36 32 13
第三趟:(11 26 36 49) 15 36 32 13
第四趟:(11 15 26 36 49) 36 32 13
第五趟:(11 15 26 36 36 49) 32 13
第六趟:(11 15 26 32 36 36 49) 13
第七趟:(11 13 15 26 32 36 36 49)

插入排序JavaScript代码:
function insertSort(arr) {
var len =arr.length;
for (var i=1;i<len; i++) {
    var temp=arr[i];
    var j=i-1;//默认已排序的元素
    while (j>=0 && arr[j]>temp) {  //在已排序好的队列中从后向前扫描
        arr[j+1]=arr[j]; //已排序的元素大于新元素,将该元素移到一下个位置
        j--;
    }
    arr[j+1]=temp;
    console.log(arr);
}
 return arr
}

时间复杂度
(1)最好情况:待排序数组有序,比较n-1次,移动0次。
(2)最差情况:待排序数组逆序,比较n(n-1)/2次,移动(n+2)(n-1)/2次
(3)平均情况:进行第j趟排序操作,待插入元素需与前面j/2个元素进行比较,移动次数j/2+1次
时间复杂度应为O(n²),较为稳定。
空间复杂度:需要一个辅存空间

2.希尔排序
思想:将待排序的元素分为多个子序列,使得每个子序列的元素相对较少,对各个子序列分别进行直接插入排序,待整个序列“基本有序”后,再对所有元素进行一次直接插入排序。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
初始关键字:26 36 49 11 15 36 32 13
第一趟排序:15 36 32 11 26 36 49 13 /n=4
第二趟排序:15 11 26 13 32 36 49 36 /n=2
第三趟排序:11 13 15 26 32 36 36 49 /n=1

希尔排序javaScript代码:
function shellSort(arr) {
var len = arr.length,
    temp,
    gap = 1;
while(gap < len/3) {          //动态定义间隔序列
    gap =gap*3+1;
}
for (gap; gap> 0; gap = Math.floor(gap/3)) {
    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.log(arr);
    }
}
return arr;
}

时间复杂度:时间复杂度依赖于增量序列。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),希尔排序时间复杂度的下界是n*log2n。在规模较大的数据序列下,希尔排序效率并不是很好,但希尔排序优于简单排序。选择步长时应尽量使步长值互质,最后一个步长必须等于1。
空间复杂度:需要一个辅存空间

3.冒泡排序
思想:将n个元素的第一个和第二个比较,如果第一个元素大于第二个,则交换两个元素位置;继续比较第二个和第三个,以此递推,相邻元素进行比较,直到比较到n-1个元素和第n个元素为止;冒泡排序第一次排序完成之后必然会将该组元素中的最大值放在了该序列的最后一个位置上,所以第二趟排序会将前n-1个元素的最大值放在该序列倒数第二的位置上,依次类推,直到序列有序。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
初始关键字:26 36 49 11 15 36 32 13
第一趟排序:26 36 11 15 36 32 13 49
第二趟排序:26 11 15 36 32 13 36 49
第三趟排序:26 11 15 32 13 36 36 49
第四趟排序:26 11 15 13 32 36 36 49
第五趟排序:11 15 13 26 32 36 36 49
第六趟排序:11 13 15 26 32 36 36 49
第七趟排序:11 13 15 26 32 36 36 49

 冒泡排序javaScript代码:
 function bubbleSort(arr){
    for(i=0;i<arr.length-1;i++){
        for(j=0;j<arr.length-1-i;j++){
            if(arr[j]>arr[j+1]){
                var temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
                console.log(arr);
            }
        }
    }
    return arr;
}

时间复杂度:若有n个元素,则总共需要进行n-1趟排序。最坏情况下,所有元素全部逆序,每趟排序要进行 n-i次关键字的比较(1≤i≤n-1),此时时间复杂度为O(n²)。最好情况下,所有元素正序排列,不需要移动,只需要对比n次,此时时间复杂度O(n)。平均时间复杂度O(n²)。
空间复杂度:需要一个辅存空间。

4.快速排序
思想:
(1)分解:先从数列中取出一个元素作为基准元素,以基准元素为标准,将问题分解为两个子序列,使小于或等于基准元素的子序列在左侧,大于基准元素的子序列在右侧;
(2)治理:对两个子序列进行快速排序;
(3)合并:将排好的两个子序列合并到一起,得到原问题的解。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
初始关键字:26 36 49 11 15 36 32 13
第一趟排序:13 15 11 26 49 36 32 36 //基准pivot = 26
第二趟排序:11 13 15 26 36 36 32 49 //左侧基准pivot=13,右侧基准pivot=49
第三趟排序:11 13 15 26 32 36 36 49 //右侧基准pivot=36

快速排序javaScript代码:

function quickSort(arr){
    //如果数组<=1,则直接返回
    if(arr.length<=1){return arr;}
    var pivotIndex=Math.floor(arr.length/2);
    //找基准,并把基准从原数组删除
    var pivot=arr.splice(pivotIndex,1)[0];
    //定义左右数组
    var left=[];
    var right=[];
    //比基准小的放在left,比基准大的放在right
    for(var i=0;i<arr.length;i++){
        if(arr[i]<=pivot){
            left.push(arr[i]);
            console.log('left:'+left);
        }
        else{
            right.push(arr[i]);
            console.log('right:'+right);
        }
    }
    //递归
    return quickSort(left).concat([pivot],quickSort(right));
}

时间复杂度:时间复杂度最好为O(nlogn),最差为O(n²)。
空间复杂度:变量占用一些辅助空间,辅助空间都是常数阶的,递归调用所使用的站空间为递归树的深度logn,空间复杂度为O(logn)。

5.简单选择排序
思想:第一趟,从n个元素中找出关键字最小的元素与第一个元素交换;第二趟,从剩下的n-1个元素中再选出关键字最小的元素与第二个元素交换;以此类推,每次选出剩下元素中的最小值,放在新序列中,直到整个序列关键字有序。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
第一趟:11 26 36 49 15 36 32 13
第二趟:11 13 26 36 49 15 36 32
第三趟:11 13 15 26 36 49 36 32
第四趟:11 13 15 26 36 49 36 32
第五趟:11 13 15 26 32 36 49 36
第六趟:11 13 15 26 32 36 49 36
第七趟:11 13 15 26 32 36 36 49

简单选择排序javaScript代码:

function simpleSelectSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {    //只要len-1趟就可以完成排序,注意i从0开始
        minIndex = i;
        for (var j = i + 1; j < len; j++) {   //要比到最后一个数,其索引为len-1
            if (arr[j] < arr[minIndex]) {     //寻找最小的数
                minIndex = j;                 //将最小数的索引保存
            }
        }
        if(minIndex != i) {
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
    return arr;
}

时间复杂度:共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,移动次数为n次。不稳定排序。平均时间复杂度O(n²)。
空间复杂度:需要一个辅存空间。

6.堆排序
思想:堆分为大根堆和小根堆。堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法。完全二叉树指的是: 除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐。
(1)构建初始堆;
(2)堆顶和最后一个记录交换,即r[1]和r[n]交换,将r[1…n-1]重新调整成堆;
(3)堆顶和最后一个记录交换,即r[1]和r[n-1]交换,将r[1…n-2]重新调整为堆;
(4)循环n次,得到一个有序序列。
大根堆指的是所有子节点的值均小于父节点。小根恰好相反,所有子节点的值均大于父节点。具体排序过程:若有n个元素,先将这n个元素按关键字建成堆,将堆顶元素输出,得到n个元素当中的最大值(或最小值)。重复执行此操作,直至只剩一个元素,此时可得到一个有序序列。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程(以大根堆为例):
在这里插入图片描述
整个排序过程如下图所示:
第一趟排序结果为:11 36 36 13 15 26 32 49 // 最大根节点与最后叶子节点交换位置
第二趟排序结果为:32 15 36 13 11 26 36 49
第三趟排序结果为:26 15 32 13 11 36 36 49
第四趟排序结果为:11 15 26 13 32 36 36 49
第五趟排序结果为:13 15 11 26 32 36 36 49
第六趟排序结果为:11 13 15 26 32 36 36 49

堆排序javaScript代码:
var len;
function buildMaxHeap(arr) {   //建堆
    len = arr.length;
    // [n/2-1]表示的是最后一个有子节点 (本来是n/2(堆从1数起),但是这里arr索引是从0开始,所以-1)
    for (var i = Math.floor(len/2)-1; i>=0; i--) {
        maxHeapify(arr, i);
    }
    //对每一个节点(非叶节点),做堆调整
}
function maxHeapify(arr, i) {     //堆调整
    var left = 2*i+1,
        right = 2*i+2,
        largest = i;   //i为该子树的根节点

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {  //即上面的if中有一个生效了
        swap(arr, i, largest);  //交换最大的为父节点
        maxHeapify(arr, largest);  //交换后,原值arr[i](往下降了)(索引保存为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--;
        maxHeapify(arr, 0);
    }
    return arr;
}

let array = [26,36,49,11,15,36,32,13]
heapSort(array);

时间复杂度:初始化比较次数为O(n),每次调整所需的比较次数为O(log n),n次输出的时间复杂度为O(nlog n)。
空间复杂度:需要一个辅存空间。

7.归并排序
归并排序是运用分治法解决问题的典范。
思想:
(1)分解:将待排序元素分成大小大致相同的两个子序列。
(2)治理:对两个子序列进行合并排序。
(3)合并:将排好的有序序列进行合并,得到最终的有序序列。

仍以序列{26,36,49,11,15,36,32,13}为例,演示排序过程:
在这里插入图片描述
归并排序javaScript代码:
// 融合两个有序数组,这里实际上是将数组 arr 分为两个数组

function mergeArray(arr, first, mid, last, temp) {
    let i = first;
    let m = mid;
    let j = mid+1;
    let n = last;
    let k = 0;
    while(i<=m && j<=n) {
        if(arr[i] < arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }
    while(i<=m) {
        temp[k++] = arr[i++];
    }
    while(j<=n) {
        temp[k++] = arr[j++];
    }
    for(let l=0; l<k; l++) {
        arr[first+l] = temp[l];
    }
    return arr;
}
// 递归实现归并排序
function mergeSort(arr, first, last, temp) {
    if(first<last) {
        let mid = Math.floor((first+last)/2);
        mergeSort(arr, first, mid, temp);    // 左子数组有序
        mergeSort(arr, mid+1, last, temp);   // 右子数组有序
        arr = mergeArray(arr, first, mid, last, temp);
    }
    return arr;
}

// example
let array = [26,36,49,11,15,36,32,13]
let temp = new Array();
let SortedArr = mergeSort(array, 0 ,array.length-1, temp);
console.log(SortedArr);

时间复杂度:时间复杂度为O(nlog n)
空间复杂度:需要一个辅存空间,大小不超过n,故空间复杂度为O(n)。

各种排序时间空间复杂度对比:
在这里插入图片描述

以上算法均为个人理解整理而成,若有偏差恳请批评指正。部分代码来源于网络。
参考文章:https://blog.csdn.net/longpersevere/article/details/78400892
https://segmentfault.com/a/1190000015488807

猜你喜欢

转载自blog.csdn.net/qq_33479841/article/details/100030542
今日推荐