排序算法系列——冒泡排序

冒泡排序是交换排序的一种,其思想是从序列头部开始逐步往后遍历,每次遍历比较相邻两个元素,如果顺序不对则交换,n-1次遍历之后序列就完成了排序。由于每次遍历都是把最大的元素一步步让最后移动,类似于水泡慢慢浮出水面,于是得名冒泡算法。冒泡算法的思想很简单,实现起来也很容易,但是效率太低,所以即使是小数据量也很少推荐使用冒泡算法,更多的使用直接插入排序。

基本思想

从待排序序列头部开始循环遍历n-1次,每次遍历比较相邻两个元素,如果顺序不对则交换,也就是说如果你想要从小到大排列,那么如果相邻两个元素比较结果是前面的大于后面的,则交换就可以了,那么通过一次遍历最大的元素就到达了序列的尾部,n-1次遍历之后序列就完成了排序。这里显然有可优化的空间,下面会提到。

实现要点

第i次(i=0…n-1)遍历从0开始比较到n-1-i(n序列长度),因为每次遍历都会将未排序部分最大的元素移动到序列尾部,所以序列尾部是有序的,故在之后的遍历过程中无需继续比较有序的部分。

算法改进

该算法有两处可优化的地方:

  • 如果一次遍历过程中未发生任何交换,即所有相邻元素的顺序都是正确的,则说明整个序列已经完成排序,故无需继续遍历。
  • 每次遍历过程中记录下最后一次发生遍历的位置,则在改位置之后的部分已经是有序的,下次遍历时就可以提前结束。

实验表明以上两种改进之后的效率并未有太大的提高,第一种改进效率反而比为改进的低,第二种改进效率稍微提高一点点。虽然这两种改进从理论上来看是有一定的优化的,但是测试时使用的序列一般都是随机的,即在n-1次遍历之前完成排序以及部分有序的可能性都很小,所以这两种改进的效果都不是很明显,可能根本不会发生,反而由于加入了一些逻辑判断反而导致效率降低。不过如果是真是数据,那么之前提到的两种情况还是很可能发生的。

Java实现

package com.vicky.sort;

import java.util.Random;

/**
 * 交换排序:冒泡排序
 *
 * 时间复杂度:O(n^2)
 *
 * 空间复杂度:O(1)
 *
 * 稳定性:稳定
 *
 * @author Vicky
 *
 */
public class BubbleSort {

    /**
     * 未改进的冒泡排序
     *
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sort(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        // n-1趟遍历
        for (int i = 0; i < data.length - 1; i++) {
            // 每次遍历从0开始依次比较相邻元素
            for (int j = 0; j < data.length - 1 - i; j++) {
                // 前面元素>后面元素则交换
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                }
            }
        }
        System.out.println("sort, use time:" + (System.nanoTime() - start)
                / 1000000);
    }

    /**
     * 改进后冒泡排序
     *
     * 改进原理:如果一次遍历过程未发生交换,则说明序列已经是有序的,故无需再进行遍历。
     *
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sortImprove(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        boolean exchange = false;// 记录一趟遍历是否发生交换
        // n-1趟遍历
        for (int i = 0; i < data.length - 1; i++) {
            // 每次遍历从0开始依次比较相邻元素
            for (int j = 0; j < data.length - 1 - i; j++) {
                // 前面元素>后面元素则交换
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                    exchange = true;
                }
            }
            // 如果本次遍历未发生交换,则说明序列已是有序的,则无需继续遍历
            if (!exchange) {
                return;
            }
        }
        System.out.println("sortImprove1, use time:"
                + (System.nanoTime() - start) / 1000000);
    }

    /**
     * 改进后冒泡排序
     *
     * 改进原理:在冒泡排序的每趟扫描中,记住最后一次交换发生的位置lastexchange也能有所帮助。因为该位置之后的部分已经是有序的(未发生交换,
     * 所以是有序),
     * 故下一趟排序开始的时候,只需处理0到lastexchange部分,lastexchange到n-1是有序区。同时如果未发生交换则退出即可
     *
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sortImprove2(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        int lastChange = data.length - 1;// 记录一趟遍历最后一次发生交换的位置,该位置之后是有序的
        // 上次遍历发生交换则lastChange>0,继续遍历
        while (lastChange > 0) {
            // 本次遍历从0开始到上次遍历最后一次交换的位置结束
            int end = lastChange;
            lastChange = 0;
            // 每次遍历从0开始依次比较相邻元素
            for (int j = 0; j < end; j++) {
                // 前面元素>后面元素则交换
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                    lastChange = j + 1;
                }
            }
        }
        System.out.println("sortImprove2, use time:"
                + (System.nanoTime() - start) / 1000000);
    }

    public static void main(String[] args) {
        // 用于记录三种冒泡排序的用时
        long[] useTime1 = new long[10];
        long[] useTime2 = new long[10];
        long[] useTime3 = new long[10];
        // 循环测试10次,取均值
        for (int times = 0; times < 10; times++) {
            // 构建10000个元素的序列进行排序
            Random ran = new Random();
            Integer[] data = new Integer[10000];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(10000000);
            }
            // 使用System.arraycopy复制三个数组分别用于排序
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            System.arraycopy(data, 0, data1, 0, data.length);
            System.arraycopy(data, 0, data2, 0, data.length);
            System.arraycopy(data, 0, data3, 0, data.length);
            // 分别记录三种冒泡排序的用时
            long start = System.nanoTime();
            BubbleSort.sort(data1);
            useTime1[times] = (System.nanoTime() - start) / 1000000;
            // SortUtils.printArray(data1);
            start = System.nanoTime();
            BubbleSort.sortImprove(data2);
            useTime2[times] = (System.nanoTime() - start) / 1000000;
            start = System.nanoTime();
            // SortUtils.printArray(data2);
            BubbleSort.sortImprove2(data3);
            useTime3[times] = (System.nanoTime() - start) / 1000000;
            // SortUtils.printArray(data3);
        }
        // 计算用时最大值,最小值,均值
        long[] res1 = SortUtils.countArray(useTime1);
        long[] res2 = SortUtils.countArray(useTime2);
        long[] res3 = SortUtils.countArray(useTime3);
        System.out.println("method\tmax\tmin\tavg\t");
        System.out.println("sort" + "\t" + res1[0] + "\t" + res1[1] + "\t"
                + res1[2]);
        System.out.println("sortImprove" + "\t" + res2[0] + "\t" + res2[1]
                + "\t" + res2[2]);
        System.out.println("sortImprove2" + "\t" + res3[0] + "\t" + res3[1]
                + "\t" + res3[2]);
        // 测试结果,第一种改进方法效率比不改进还差一些,
        // 可能由于出现提前完成排序的可能性较小,每次遍历加入了过多的赋值以及判断操作导致效率反而降低
        // 第二种改进方法还是有一些效果的
        // method max min avg
        // sort 1190 1073 1123
        // sortImprove 1258 1097 1146
        // sortImprove2 1205 1056 1099
    }
}

效率分析

(1)时间复杂度

O(n^2)

冒泡排序最好的时间复杂度是O(n),一次遍历即可,无需交换(第一种改进)。最坏情况需要遍历n-1次,比较且交换n-1-i次,故时间复杂度是O(n^2)。

(2)空间复杂度

O(1)

从空间来看,它只需要一个元素的辅助空间,用于元素的位置交换O(1)。

(3)稳定性

稳定

排序过程中只有相邻两个元素会发生交换,同时为了减少交换次数相同的元素不会进行交换,所以两个相同元素的相对位置不会发生改变。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-09 11:24:21

排序算法系列——冒泡排序的相关文章

(转载)排序算法系列

排序算法系列 目录 概述 概念 排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列. 排序分为内部排序和外部排序. 若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序. 反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序. 排序分类 如果按照策略来分类,大致可分为:交换排序.插入排序.选择排序.归并排序和基数排序.如 图-排序策略分类图 所示. 图-排序策略分类图 算

排序算法之冒泡排序Java实现

排序算法之冒泡排序 一.初级的冒泡排序 import java.util.Arrays; /** * * @title BubbleSort * @describe 冒泡排序 * @author 张富昌 * @date 2016年10月1日下午3:56:30 */public class BubbleSortLow { // 起泡排序是快速排序的基础,但是排序速度较慢. // 基本思想:将序列中第 1 个元素与第 2 个元素进行比较,如前者大于后者,则两个元素交换位置,否则不交换: // 再将第

排序算法系列——堆排序

记录学习点滴,菜鸟成长记 堆排序引入了另一种算法设计技巧:使用一种我们称之为“堆”的数据结构来进行数据管理. 堆排序算是真正意义上的利用数据结构来求解数组排序的方法. “插入排序”和“归并排序”可以看做是一种“计算机体力活”,体现的思想更多的是去模拟最简单的人类思维,比如插入排序过程中的比较,归并中子问题合并时的比较. “堆排序”可以看做是“计算机脑力活”,他利用了一种结构化的语言来表达,这种结构化带来一些性质,比如左右孩子.比[堆大小的一半向下取整]大的下标都是叶节点不需要维护其最大堆性质等.

排序算法系列——归并排序

记录学习点滴,菜鸟成长记 归并排序的英文叫做Merge-Sort,要想明白归并排序算法,还要从“递归”的概念谈起. 1.递归 一般来讲,人在做决策行事的时候是往往是从已知出发,比如,我又要举个不恰当的例子了→_→: 看到漂亮姑娘→喜欢人家→追→女朋友→老婆 但是人家施瓦辛格不是这么想的,人家从小就立志当总统: 要当总统←先当州长←竞选州长要有钱←那得找个有钱妹子←妹子都喜欢明星←身材好能当明星←健身 递归,就像一个人对自己的发展有清晰的规划和坚定的信心一样,他知道每一步会有怎么样的结果,他需要仅

排序算法系列——快速排序

记录学习点滴 快速排序算法是一种很有趣的算法,短小精悍,性能强劲,对于大部分情况都可以胜任,但对极端环境难以应付. 快速排序我理解为:这是一个“以自我为中心的”+“分治法”思想的算法. 分治法不必多说,化繁为简,那就是逐个击破. 那什么是“以自我为中心”?顾名思义,就是每次都一个“我”,每个人都要围绕“我”行事,比“我”小的都去左边站着,比“我”他大的都去右边站着,而且“我”不去关心每一边都有谁,反正你没“我”大或者小就行.一旦“我”落位了妥帖了,“我”就不动了.然后再在左右两边分别产生新“我”

排序算法之冒泡排序(Java)

 冒泡排序即每次遍历.相邻数字间进行比较,前者大于后者进行交换,不断将最大值后移,直至沉至最后位置:算法关键要点在于确定每次循环的边界: 后面两种算法则是对冒泡排序一定程度上的改良,但相对于其他排序算法,冒泡排序性能依然较差. //冒泡排序 public class Bubble_Sort { //最原始的解法 public void bubble_sort1(int[] data) { int n = data.length; for(int i = 0; i < n; i++) { //

排序算法系列——插入排序

记录学习点滴,菜鸟成长记 接触算法是研究生期间做项目时,需要编写一些诸如GA.QGA的时候,第一次使用“排序”还是用的Java自带的Comparator接口.后来买了<算法导论>来看,发现果然所有知识都是有专业壁垒的,简单的一个问题尽然蕴藏着如此多的思想,发现此简直欣喜无比,遂决定要好好研究研究.只有深入后才发现,原来算法的不仅仅是按照逻辑顺序写个程序那么简单,好的算法要考虑到方方面面,最简单的时间复杂度就够我学习很长时间了. 将自己学习排序算法的一些理解和感悟记录于此,方便自己温故而知新.

排序算法系列:冒泡排序与双向冒泡排序

概述 排序算法应该算是一个比较热门的话题,在各个技术博客平台上也都有一些博文进行了一定程度的讲解.但还是希望能从自我完善的角度出发,可以更详细.全面.形象地表达这些算法的精髓.本文就先从最简单的冒泡排序开始说起,别说你已经彻底了解了冒泡排序算法(虽然一开始我也是这样以为的). 版权说明 本文链接:http://blog.csdn.net/lemon_tree12138/article/details/50474230 – Coding-Naga - 转载请注明出处 目录 概述 版权说明 目录 冒

排序算法系列——八大排序算法对比分析

本系列最后一篇,综合分析下前面介绍的八种排序算法的效率,以及各自的适用情况. 下面先看看八种排序算法的时间复杂度表格: 图中八种排序被分成了两组,一组时间复杂度为O(n^2),另一组相对高效些. 下面先对第一组O(n^2)的四种排序算法进行对比,分别取数组长度为100,1000,10000,100000四个数量级,各个元素在0-10000000之间随机获取.下面看下结果的分析. 排序算法 长度=100 长度=1000 长度=10000 长度=100000 直接插入排序 535 2,198 135