掌握了排序算法的稳定性,对排序算法学习将会事半功倍

写在前面

       今年5月份打算参加中级软考职称,力争在上海搞一个户口,这也是没办法的事情,毕竟自己不是211,985高校人才,只能靠自己一步一个脚印,另辟蹊径了。软考对数据结构和算法的要求还是蛮基础的,越基础知识往往越容易忽略。经历过多年的开发洗礼之后,相信大部分人都会跟我一样,最后能让你产生价值的东西不是做了多少项目,而是基础提高,有没有将项目汇总形成自己的一套方法论。

        查看了历年的真题,发现竟然对排序算法的稳定性考察了很多次,而我竟然对稳定性这个概念一直很模糊,大学期间也可能只是过过眼,没太留意。趁现在弄明白单独写出来,相信对有同命相连的朋友也有帮助。

稳定性的概念?

      稳定性是在一组未排序的序列中,如果存在两个相等的元素A和B,且在未排序序列中,A在B前面位置,如果在排序结束之后,A、B的位置没有发生变化,也就是说A仍然在B的前面,那么就称该排序算法是稳定的。

案例分析

这里通过冒泡和快速两种排序算法对稳定性进行说明;

冒泡排序思想:在未排序序列中,两个相邻的元素进行比较,如果互为逆序,就交换他们的值,并对每一对相邻元素做相同处理,直到最后一个元素为止。这一轮做完之后,最后一个元素将是序列中的最大值(或者最小值),继续对剩余未排序序列重新以上的步骤,直到排序完毕。

快速排序思想:通过一趟排序将待排序的序列划分为独立的两部分,即前半区和后半区,其中,前半区中的序列元素均不大于后半区的序列元素,之后再分别对这两个半区的序列进行快速排序,直到排序有序为止。

定义一个未排序的序列:

let arr = [6,7*,3,7,4,5];

我给arr[1]赋值成了7*,只是为了方便下面说明,在实际开发过程中肯定不能这么用。

根据冒泡排序思想,做增序排序

第一轮:

6,7*比较,7*>6,不交换值,序列不变;

7*,3比较,3<7*,交换值,此时序列变成[6,3,7*,7,4,5];

7*,7比较,值相等不交换,序列不变;

……

第一轮结束之后,序列变成[6,3,7*,4,5,7];

同理,第二轮结束之后,序列变成[3,6,4,5,7*,7];

最后,第三轮结束之后,序列变成[3,4,5,6,7*,7],即是已排序好的序列;

可以看出来,7*和7的相对位置并没有发生变化,说明冒泡排序是一种稳定排序。

根据快速排序思想,也做增序排序

快速排序中有两个重要的概念:指示变量枢轴关键字,其中枢轴关键字作为一趟快速排序过程的前后半区的划分,通常是序列中的第一个元素。

具体做法是:设置两个指示变量i,j,初始值分别指向未排序序列中的第一个元素和最后一个元素。设置枢轴关键字为pivot,首先从j位置向前检索,找到第一个小于pivot的元素时将该元素向前移动到i位置,之后从i位置向后检索,找到第一个大于pivot的元素时将该元素向后移动到j位置,重复这个过程直到ij相等为止。

还是用上面已定的未排序序列let arr = [6,7*,3,7,4,5],初始化pivot=6,i=0,j=5;

第一轮检索:

向前检索,j--,j=5时,发现arr[5]<pivot;i,j位置交换之后,即arr[0]=arr[5],序列变成[5,7*,3,7,4,5];

向后检索,i++,i=1时,发现arr[1]>pivot;i,j位置交换之后,即arr[5]=arr[1],序列变成[5,7*,3,7,4,7*];

此时,i\neq j,继续第二轮检索:

向前检索,j--,j=4时,发现arr[4]<pivot;i,j位置交换之后,即arr[1]=arr[4],序列变成[5,4,3,7,4,7*];

向后检索,i++,i=3时,发现arr[3]>pivot;i,j位置交换之后,即arr[3]=arr[4],序列变成[5,4,3,7,7,7*];

此时,i\neq j,继续第三轮检索:

 向前检索,j--,j=3时,发现i==j,此时,第一趟快速排序已经结束,将pivot插入i位置,最后序列变成[5,4,3,6,7,7*];

以上只模拟了第一趟快速排序,前后半区被划分成了[5,4,3]和[6,7,7*]。只需第一趟其实就已经看出来了,快速排序算法是一种不稳定的排序算法,因为7*在第一趟排序完成之后竟然跑到了7的后面,也就是说,位置排序前后相对位置发生了变化。

综合比较其他排序算法,对稳定性做了如下的对比汇总表:

 当然排序算法远远不止以上8种,但都会离不开稳定性这个概念。

附上冒泡排序和快速的排序的代码,结合代码学习更加高效快捷。

冒泡排序:

// 冒泡排序
function sortMP() {
    let arr = [6, 2, 3, 2, 4, 5];
    // console.log(arr, arr.length)
    // let arr1 = [1, 2, 4, 3, 5];
    for (let i = 0; i < arr.length - 1; i++) { // 第一个循环 n*n, arr.length*arr.length;
        let flag = false; // 判断是否已经提前排序完成了?
        for (let k = 0; k < arr.length - 1 - i; k++) { // 第二个循环
            let temp = 0;
            if (arr[k] > arr[k + 1]) {
                temp = arr[k];
                arr[k] = arr[k + 1];
                arr[k + 1] = temp;
                flag = true;
            }
        }
        if (!flag) { // 排序已经提前结束
            break;
        }
    }
    console.log(arr);
}

快速排序:

// 一趟排序过程
function partition(arr, low, high) {
    let i, j;
    let piovt = arr[low]; // 枢轴取第一个元素
    i = low;
    j = high;
    while (i < j) {
        while (i < j && arr[j] >= piovt) j--; // 从后往前检索
        arr[i] = arr[j];
        while (i < j && arr[i] <= piovt) i++; // 从前往后检索
        arr[j] = arr[i];
    }
    arr[i] = piovt; // 一趟快速排序完毕,将枢轴归位
    return i;
}

// 快速排序算法
function quickSort(arr, low, high) {
    if (low < high) {
        let mid = partition(arr, low, high);
        quickSort(arr, low, mid - 1); // 前半区快速排序
        quickSort(arr, mid + 1, high); // 后半区快速排序
    }
}

总结

  • 稳定性在排序算法中占有重要地位,以上只是针对整数序列的简单说明,如果涉及到复杂对象序列的排序,针对某个关键属性进行排序,如果不稳定排序,可能会跟期望的不一致。
  • 对于排序算法核心思想理解以前是一个混乱无章的存在,针对稳定性系统学习,终于理清了8大典型算法的核心思路。别人1分钟就能明白的事情,我可能要多花1个小时才吃透。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值