4

老狗啃骨头之算法-冒泡排序

 3 years ago
source link: http://www.veiking.cn/blog/1021-page.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

冒泡排序是交换排序,是一种简单直观的排序算法。冒泡的算法原理是逐次循环遍历,比较两个相邻的元素,将小的(或大的)往前调。这样,每一轮都能得到一个最小的(或最大的),剩下的重复这个操作,最后完成排序。这个算法的名字,每轮这个逐个对比置换,很像那种气泡浮起,从水底慢慢浮到上面的样子,往上越晃荡越大,故曰“冒泡”

冒泡排序(Bubble Sort)

  冒泡排序是交换排序,是一种简单直观的排序算法。

  冒泡的算法原理是逐次循环遍历,比较两个相邻的元素,将小的(或大的)往前调。这样,每一轮都能得到一个最小的(或最大的),剩下的重复这个操作,最后完成排序。这个算法的名字,每轮这个逐个对比置换,很像那种气泡浮起,从水底慢慢浮到上面的样子,往上越晃荡越大,故曰“冒泡”。

1:从一端开始,比较相邻的元素,将大的置换至后面;
2:逐次对后续的相邻元素做行为1,直至最后;
3:除最后一个元素,剩余数据样本持续1、2、3步骤,直至排序结束。



import java.util.Arrays;

/**
 * @author :Veiking
 * @version :2020年10月1日 
 * 说明 :冒泡排序算法
 */
public class BubbleSort {
    /**
     * 冒泡排序(原始版)
     * @param arrays
     * @return
     */
    public static int[] sort(int[] arrays) {
        int temp;
        int index;
        int size = arrays.length;
        // 每一轮的冒泡会将最大的元素浮至最后,结下对剩余的重复操作,直至处理完毕
        for (int times = 1; times < size - 1; times++) {
            // 在剩余的待排数据中,相邻元素,大的后浮
            for (index = 0; index <= size - 1 - times; index++) {
                if (arrays[index] > arrays[index + 1]) {
                    temp = arrays[index];
                    arrays[index] = arrays[index + 1];
                    arrays[index + 1] = temp;
                }
            }
            // System.out.println("第" + times + "次排序操作结果:" + Arrays.toString(arrays));
        }
        return arrays;
    }

    // 执行
    public static void main(String[] args) {
        int[] input = new int[] { 3, 7, 2, 6, 1, 5, 4 };
        System.out.println("样本初始样本数据:" + Arrays.toString(input));
        int[] output = BubbleSort.sort(input);
        System.out.println("冒泡排序结果数据:" + Arrays.toString(output));
    }
}

  从代码运行的时候,我们可以看到:

样本初始样本数据:[3, 7, 2, 6, 1, 5, 4]
第1次冒泡操作结果:[3, 2, 6, 1, 5, 4, 7]
第2次冒泡操作结果:[2, 3, 1, 5, 4, 6, 7]
第3次冒泡操作结果:[2, 1, 3, 4, 5, 6, 7]
第4次冒泡操作结果:[1, 2, 3, 4, 5, 6, 7]
第5次冒泡操作结果:[1, 2, 3, 4, 5, 6, 7]
冒泡排序结果数据:[1, 2, 3, 4, 5, 6, 7]

  其实在执行第4次操作的时候,已经完成了排序,如果数据样本刚开始就是正序的,岂不是要空跑很多轮!于是,我们一般是在做了交换操作之后,做个判断标识,例:

    /**
     * 冒泡排序(加标识版)
     * @param arrays
     * @return
     */
    public static int[] sort_1(int[] arrays) {
        int temp;
        int index;
        int size = arrays.length;
        // 每一轮的冒泡会将最大的元素浮至最后,结下对剩余的重复操作,直至处理完毕
        for(int times = 1; times < size-1; times++) {
            boolean flag = true;
            //在剩余的待排数据中,相邻元素,大的后浮
            for(index = 0; index <= size-1-times; index++) {
                if(arrays[index]>arrays[index+1]){
                    temp = arrays[index];
                    arrays[index] = arrays[index+1];
                    arrays[index+1] = temp;
                    flag = false;
                }
            }
            if(flag) {
                break;
            }
            // System.out.println("第"+times+"次冒泡操作结果:"+Arrays.toString(arrays));
        }
        return arrays;
    }

  我们在一次遍历完成后,看他有没有“冒泡”交换,如果没有,可判定这剩下的数据,是已经符合排序规则了的,那就不用管了,直接跳出。
  还有,看到有些朋友玩了一个招式,说咱们冒泡,获取大或取小,一般都是一头开始,单向冒;那我给他升级下,从数据的两头分别开始,正向取大、反向取小,这样一次可以就得到两个最终值(最大者和最小者) ,看样子还可以节省一半时间,大致程序如下:

    /**
     * 冒泡排序(双开版)
     * @param arrays
     * @return
     */
    public static int[] sort_2(int[] arrays) {
        int head = 0;
        int tail = arrays.length - 1; // 设置变量的初始值
        int temp, index;

        while (head < tail) {
            boolean flag = true;
            // 从前往后,正向冒泡,冒出最大
            for (index = head; index < tail; ++index)
                if (arrays[index] > arrays[index + 1]) {
                    temp = arrays[index];
                    arrays[index] = arrays[index + 1];
                    arrays[index + 1] = temp;
                    flag = false;
                }
            // System.out.println("正向冒泡操作结果:"+Arrays.toString(arrays));
            tail--; // 修改tail值, 前移一位
            // 从后往前,反向冒泡,冒出最小
            for (index = tail; index > head; --index)
                if (arrays[index] < arrays[index - 1]) {
                    temp = arrays[index];
                    arrays[index] = arrays[index - 1];
                    arrays[index - 1] = temp;
                    flag = false;
                }
            // System.out.println("反向冒泡操作结果:"+Arrays.toString(arrays));
            head++; // 修改head值,后移一位
            if (flag) {
                break;
            }
        }
        return arrays;
    }

  其实细看细想,这一前一后双开,跟从一头单开,基本是没有区别的,因为冒泡算法的基本操作原理就在那里,你从前往后,从后往前,还是都要这么轮一遍,1×1是等于2×1/2的,这从时间复杂度上来说,并没有达成什么优化效果,反倒造成了思路复杂、代码臃肿。
  我们在刚开始学习算法的时候,可能经常会遇到这样的情况,尤其是一些东西搞得还不是太清楚的时候,做性能优化,是力不从心的,结果很有可能是走了弯路,弄巧成拙。不过对于我们学习者来说,怎么折腾都不为过,都是值得肯定的,学的时候不折腾,怎么可能搞清楚!

  冒泡算法的基本性能,归纳如下图:

1、 时间复杂度

  我们可以看到,如果数据文件的初始状态是正序的,冒泡这么比较着冒一遍,就可以排序完成。这时候,数据元素的比较次数和交换次数,分别是n-1和0,这时候冒泡排序是最爽的,也就是说冒泡排序最好时间复杂度为O(n);
  事实上哪那么多好事儿,排序还是要排的,你要正着排,偏来一个反序的,这就热闹了。冒泡排序的特点决定,当遇到反序的数据样本时,一遍又一遍,从头到尾要一直比一直换,这时候数据元素的比较次数和交换次数,分别是O(n2)和O(n2),都是O(n2),这时候冒泡排序是最颓的,我们说冒泡排序最坏时间复杂度为O(n2);此外,冒泡排序的平均时间复杂度为O(n2)。
  总结一下,冒泡排序最好时间复杂度为O(n),最坏时间复杂度为O(n2),平均时间复杂度为最坏时间复杂度为O(n2)。当数据越接近正序时,冒泡排序性能越好;当数据越接近反序时,冒泡排序性能越差。

2、空间复杂度

  冒泡排序的空间复杂度是O(1),因为只定义了一个辅助交换的变量,与n的大小无关,所以空间复杂度为O(1)。

3、稳定性

  我们通过图解和程序可以看出,在冒泡排序中,当对比出现等值的元素,是没必要互相动他们的,也就是说,即使经过排序,这些数据元素的相对顺序也是不受影响的,所以冒泡排序属于稳定的排序算法


Recommend

  • 8
    • www.veiking.cn 3 years ago
    • Cache

    老狗啃骨头之算法-插入排序

    插入排序,一般是说直接插入排序,是一种最直观最简单的排序算法。插入排序的原理是依次将未排序的数据元素插入已完成排序的有序数列,如此往复,最终完成所有数据的排序。在插入排序中,往前遍历的时候,遇到等值的元素就直接插入结...

  • 4
    • www.veiking.cn 3 years ago
    • Cache

    老狗啃骨头之算法-选择排序

    简单选择排序是一种相对简单直观的基础排序算法,每一次都做简单选择,每一次都选出最大或最小。选择排序的核心思想就是:在遍历的过程中,每次都选数据样本中最小的数据,放在首位。看起来简单纯朴吧,从第一个元素开始,每次都取剩...

  • 5
    • www.veiking.cn 3 years ago
    • Cache

    老狗啃骨头之算法-归并排序

    归并排序是一种非常典型的分治策略应用排序算法,简而概括:分而排之,合而并之。归并排序,据说是冯·诺伊曼在1945年首次提出。冯·诺伊曼,是现代计算机科学发展史上开天辟地的大佬之一,不单单是计算机领域,这哥们在整个数学、量子...

  • 8

    老狗啃骨头之数据结构-栈和队列_老狗啃骨头_Veiking百草园-知识点滴,日常分享 栈和队列也是比较常见的数据结构,它们是比较特殊的线性表。相对于数组和链表,栈和队列是一种更具特性的场景应用,栈和队列都可...

  • 5

    老狗啃骨头之算法-排序算法总结_老狗啃骨头_Veiking百草园-知识点滴,日常分享 关于排序的算法,还有很多种。还有一些排序算法的思想,在不同的使用场景下再结合其它的算法逻辑,又可以衍生出新的算法设计。比...

  • 6
    • www.veiking.cn 3 years ago
    • Cache

    老狗啃骨头之算法-基数排序

    基数排序是一种不在数据值本身之间比较的排序算法,而是通过数据按位数“切割”对比,从而实现排序的算法,所以基数排序也被认为是一种典型的非比较排序算法。在实际运用中,基数排序的使用场景不局限于整数,凡是整数可以表达的,或者...

  • 6
    • www.veiking.cn 3 years ago
    • Cache

    老狗啃骨头之算法-快速排序

    快速排序,又称分区交换排序,简称快排。它也是一种交换排序,它是一种在处理大量数据方面有优势的算法。当数据量巨大的时候,冒泡排序这种中规中矩,挨次遍历逐个对比的玩法,估计会让人抓毛的,于是据说在公元1960年,一位叫东尼·霍...

  • 5

    天地玄黄,宇宙洪荒……千年以前,南梁周大侍郎,用一夜白头给我们留下了包罗万象又朗朗上口的童谣,得以千年唱诵,这是古人原始纯真的智慧。但浩瀚如宇宙,细微如尘沙,世间如此繁杂,计算机是搞不懂的,计算机的一零世界努力模拟,也...

  • 4

    老狗啃骨头之算法-八大排序算法_老狗啃骨头_Veiking百草园-知识点滴,日常分享 排序,就是将一组无序的数据,按照一定规则,使其有序化排列。排序时是否根据比较来决定元素间的相对次序,还可以分为比较类排序...

  • 5

    老狗啃骨头之数据结构-小说算法_老狗啃骨头_Veiking百草园-知识点滴,日常分享 一般说,随着数据结构复杂度和解决问题的复杂度的增长,时间复杂度也是随之增长的。在相同的资源条件下,空间复杂度和时间复杂度...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK