你真的会冒泡排序吗

图解十大排序

1.1引言

        在我们当下的生活中,排序随处可见,当每一次考试,老师都会按照成绩多少来排名,排队的时候,同学们又会按照学号顺序进行排队

        在编程的世界中,应用到排序的场景也比比皆 是。例如当开发一个学生管理系统时,需要按照学号 从小到大进行排序;当开发一个电商平台时,需要把 同类商品按价格从低到高进行排序;当开发一款游戏 时,需要按照游戏得分从多到少进行排序,排名第一 的玩家就是本场比赛的MVP,等等

        排序虽然很简单,它的背后却隐藏着多种多样的算法和思想。那么常用的排序算法都有哪些呢?我们又应该如何选择呢?接下来让我们来介绍主流的排序算法。

1.1.1 根据时间复杂度的不同,主流的排序算法可以分为3大类:

  1. 时间复杂度为O(n 2)的排序算法
    • 冒泡排序
    • 选择排序
    • 插入排序
  2. 时间复杂度为O(nlogn)的排序算法
    • 希尔排序
    • 归并排序
    • 快速排序
    • 堆排序
  3. 时间复杂度为O(n + k)的排序算法
    • 计数排序
    • 桶排序
    • 基数排序

1.1.2 根据稳定性, 划分为稳定排序和不稳定排序

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

1.1.3 根据是否需要额外的存储空间来存放数据,划分为内部排序和外部排序

内排序 :所有排序操作都在内存中完成;
外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

每一种排序算法的情况,下面这张表已经很好的概括出来,这张表会贯穿在我们所有排序算法的文章中,简单看一看,就让我们正式开始学习吧!

在这里插入图片描述

1.2 冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,在数列中,我们要把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相邻元素时,位置不变。详细过程如下:

在这里插入图片描述

那么冒泡排序的代码如何实现呢?

public static void sort(int [] arr) {
    for (int i = 0; i < arr.length - 1; i++) { // 第一层指循环 length - 1次
        for (int j = 0; j < arr.length - 1 - i; j++) { // 第二层指循环每次需要比较的次数
            int tmp = 0;
            if (arr[j] > arr[j + 1]) {
                tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

public static void main(String[] args) {
    int [] arr = new int[] {3, 5, 2, 6, 4, 8};
    sort(arr);
    System.out.println(Arrays.toString(arr)); // Arrays.toString打印数组的内容
}

算法分析:冒泡排序是一种稳定排序,值相等的元素并不会打乱原本的顺序。由于该排序算法的每一轮都要遍历 所有元素,总共遍历(元素数量-1)轮,所以平均时间复杂度是O(n 2)。

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

1.2.1 冒泡排序的优化

小伙伴们,思考一下,冒泡排序可以如何优化呢?
让我们来看一个例子,当排序算 法分别执行到第6、第7轮时,数列状态如下:

在这里插入图片描述

很明显可以看出,经过第6轮排序后,整个数列已 经是有序的了。
在这种情况下,如果能判断出数列已经有序,并 做出标记,那么剩下的几轮排序就不必执行了,可以 提前结束工作。

二代冒泡排序代码实现:

public static void sort(int [] arr) {
   // 第一层指循环 length - 1次
   for (int i = 0; i < arr.length - 1; i++) {
       // 判断该数组是否已经有序
       boolean isSorted = true;
       // 第二层指循环每次需要比较的次数
       for (int j = 0; j < arr.length - 1 - i; j++) {
           int tmp = 0;
           if (arr[j] > arr[j + 1]) {
               tmp = arr[j];
               arr[j] = arr[j + 1];
               arr[j + 1] = tmp;
               //有元素交换, 就证明该数组无序
               isSorted = false;
           }
       }
       // 如果已经有序, 跳出最外层for循环
       if (isSorted)
           break;
   }
}

public static void main(String[] args) {
   int [] arr = new int[] {3, 5, 2, 6, 4, 8};
   sort(arr);
   System.out.println(Arrays.toString(arr)); // Arrays.toString打印数组的内容
}

1.2.2 冒泡排序又优化

以为这就结束啦?同学们再想一想如何能再进一步提升它的性能呢?
同样来看一个例子

在这里插入图片描述

元素4和5比较,发现4小于5,所以位置不变。
元素5和6比较,发现5小于6,所以位置不变。
元素6和7比较,发现6小于7,所以位置不变。
元素7和8比较,发现7小于8,所以位置不变。

大家可以清楚的发现
当数组中右边许多元素已经是有序的时候,但每一轮还是需要浪费时间进行比较,那我们如何进行优化呢?让我们一起来分析分析:

从现有的逻辑来看,数组右边有序的长度和第一轮for循环排序的次数是相等的。例如第1轮排序过后的有序区长度是1,第2轮 排序过后的有序区长度是2……

实际上,数组真正的有序区可能会大于这个长度,因此后面的多次元素比较 是没有意义的。那么,该如何避免这种情况呢?我们可以在每一 轮排序后,记录下来最后一次元素交换的位置,该位置往后的数组就是有序的了

三代冒泡排序代码实现:

public static void sort(int [] arr) {
    // 记录最后一次交换的位置
    int lastExchange = 0;
    //边界点
    int sortBorder = arr.length - 1;
    // 第一层指循环 length - 1次
    for (int i = 0; i < arr.length - 1; i++) {
        // 判断该数组是否已经有序
        boolean isSorted = true;
        // 第二层指循环每次需要比较的次数
        for (int j = 0; j < sortBorder; j++) {
            int tmp = 0;
            if (arr[j] > arr[j + 1]) {
                tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                //有元素交换, 就证明该数组无序
                isSorted = false;
                lastExchange = j;
                //不能直接把边界j赋值给sortBorder,这样改变了for循环里sortborder
            }
        }
        
        sortBorder = lastExchange;
        if (isSorted)
            break;
    }
}

public static void main(String[] args) {
    int [] arr = new int[] {3, 5, 2, 6, 4, 8};
    sort(arr);
    System.out.println(Arrays.toString(arr)); // Arrays.toString打印数组的内容
}

1.2.3 冒泡排序双优化(选看)

~啊… 终于优化好了~,你们以为这就结束啦,哈哈哈,还有一种排序算法叫作鸡尾酒排序,是基于冒泡排序的一种升级排序法。

鸡尾酒排序的元素比较和交换过程是双向的。
同样来看一个例子:[2, 3, 4, 5, 6, 7, 1, 8]

在这里插入图片描述

按照之前的冒泡排序算法,我们需要进行7轮排序,是不是感觉像大冤种,呵呵,这时候就该鸡尾酒排序上场表演了。

第1轮(和冒泡排序一样,8和1交换)

在这里插入图片描述

第2轮 此时开始不一样了,我们反过来从右往左比较并进行交换。

在这里插入图片描述

在这里插入图片描述

第3轮(虽然实际上已经有序,但是流程并没有 结束)
重新从左向右进行比较并进行交换,排序过程就像是钟摆一样,今日我做主,为它改名叫钟摆排序

四代冒泡排序代码实现

public static void sort(int [] arr) {
   int tmp = 0;
   for (int i = 0; i < arr.length / 2; i++) {
       // 有序标记, 每一轮的初始值都是true
       boolean isSorted = true;
       // 奇数轮(从左向右)
       for (int j = i; j < arr.length - 1 - i; j++) {
           if (arr[j] > arr[j + 1]) {
               tmp = arr[j];
               arr[j] = arr[j + 1];
               arr[j + 1] = tmp;
               //有元素交换, 就证明该数组无序
               isSorted = false;

           }
       }
       if (isSorted)
           break;

       isSorted = true;
       //偶数轮(从右向左)
       for (int j = arr.length - 1 - i; j > i; j--) {
           if (arr[j - 1] > arr[j]) {
               tmp = arr[j];
               arr[j] = arr[j - 1];
               arr[j - 1] = tmp;
               isSorted = false;
           }
       }
       if (isSorted)
           break;
   }
}

public static void main(String[] args) {
   int [] arr = new int[] {3, 5, 2, 6, 4, 8};
   sort(arr);
   System.out.println(Arrays.toString(arr)); // Arrays.toString打印数组的内容
}

很明显,摆钟排序 鸡尾酒排序可以减少排序的回合数,但是代码量却增加了一倍,今天的冒泡排序我们就介绍到这里啦,都到这里啦,小伙伴该做什么应该都懂了吧

下篇预告 :图解十大排序篇二       选择排序插入排序

f75c99c4b3fb6e5a271a25937c6333c3.gif

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值