冒泡排序
2016-07-17 16:10
190 查看
冒泡排序的思路很简单——从头至尾遍历数组元素,若前一项大于(或小于)后一项,则交换相邻两项。单次遍历整个数组可将某一个元素排列到正确位置,因此需要遍历元素数量n次。在代码中体现也就是内外两层循环,内层循环负责遍历中两两元素的交换操作,外层负责遍历次数控制。
首先看版本1:
很明显的两层循环,这里按从小到大排序,因此弱发现后一元素比当前元素大,则交换两个元素的位置,直到比较到最后一个元素为止。因为是
这个版本中没有做任何优化,因为遍历n次后,数组中后n个元素实际已经是排好序的了,因此不需要再进行比较了。所以内层循环还可以进一步优化,得到版本2:
由于遍历n次后,后n项已经排好序了,因此内层循环上界再减去遍历次数即可,即
虽然这时内层循环已经优化操作次数,但如果给一个部分有序的数列,如
{1, 2, 3, 4, 8, 7, 6, 5}
遍历前四次后,数组实际已经排好序了,这时就不需要再进行比较操作了,因此除了前四次排序,仅在需要一次遍历就可检验出数组有序,也就是公共遍历5次数组就可完成排序了。在当前的逻辑下,会导致出现3次无意义的遍历。
解决这个问题的方案也很简单,若数组已经有序,则不会出现交换操作。因此仅需设定一个标志变量,当有交换时置成需要排序(检验)的状态,当所有元素已经有序,检验过程没有交换操作,那么再下次遍历数组直接退出即可,便是版本3:
在每次进入内部循环时重置标志变量,若进行了交换操作则相应改变,进入内层循环前进行判断,若不需要再遍历,直接退出。
接下来写一些测试代码,来验证三种方案的效率。生成两个数组:一个完全逆序,长度为9999;另一个完全正序,长度同样为9999(不用再排序了),然后分别使用三种排序,统计排序时间,以下是一种方法的调用:
运行三种排序,统计到的时间:
最好情况下由于仅需比较,不需要交换,因此三种版本使用时间均比最差情况短,但由于第三种增加了交换情况的判断,仅需遍历一遍就可完成。
最差情况下由于版本2,版本3对内层排序进行了操作数优化,因此显著优于完全没有优化的版本1。
首先看版本1:
/** * 冒泡排序效率最低写法,没有任何优化步骤 * @param arrayToSort */ public static void sortAlpha(int[] arrayToSort) { for (int i = 0; i < arrayToSort.length; i++) { for (int j = 0; j < (arrayToSort.length - 1); j++) { int tmp; if (arrayToSort[j] > arrayToSort[j + 1]) { tmp = arrayToSort[j]; arrayToSort[j] = arrayToSort[j+1]; arrayToSort[j+1] = tmp; } } } }
很明显的两层循环,这里按从小到大排序,因此弱发现后一元素比当前元素大,则交换两个元素的位置,直到比较到最后一个元素为止。因为是
j与
j+1比较,为避免数组越界,内层for循环需以
arrayToSort.length - 1做边界。
这个版本中没有做任何优化,因为遍历n次后,数组中后n个元素实际已经是排好序的了,因此不需要再进行比较了。所以内层循环还可以进一步优化,得到版本2:
/** * 冒泡排序优化版本1,减少内层循环遍历个数,已经排序好的元素无需遍历。 * @param arrayToSort */ public static void sortBeta(int[] arrayToSort) { for (int i = 0; i < arrayToSort.length; i++) { for (int j = 0; j < (arrayToSort.length - 1 - i); j ++) { int tmp; if (arrayToSort[j] > arrayToSort[j + 1]) { tmp = arrayToSort[j]; arrayToSort[j] = arrayToSort[j+1]; arrayToSort[j+1] = tmp; } } } }
由于遍历n次后,后n项已经排好序了,因此内层循环上界再减去遍历次数即可,即
arrayToSort.length - 1 - i这样每次内层遍历便可减少n次比较操作,提升了效率。
虽然这时内层循环已经优化操作次数,但如果给一个部分有序的数列,如
{1, 2, 3, 4, 8, 7, 6, 5}
遍历前四次后,数组实际已经排好序了,这时就不需要再进行比较操作了,因此除了前四次排序,仅在需要一次遍历就可检验出数组有序,也就是公共遍历5次数组就可完成排序了。在当前的逻辑下,会导致出现3次无意义的遍历。
解决这个问题的方案也很简单,若数组已经有序,则不会出现交换操作。因此仅需设定一个标志变量,当有交换时置成需要排序(检验)的状态,当所有元素已经有序,检验过程没有交换操作,那么再下次遍历数组直接退出即可,便是版本3:
/** * 冒泡排序优化版2,减少内部排序遍历个数,并且添加了数据是否有序的检验, * 若数组已经有序,无需再进行遍历,直接退出。 * @param arrayToSort */ public static void sortGamma(int[] arrayToSort) { boolean needSort = true; for (int i = 0; i < arrayToSort.length; i++) { if (!needSort) { break; } for (int j = 0; j < (arrayToSort.length - 1 - i); j ++) { needSort = false; int tmp; if (arrayToSort[j] > arrayToSort[j + 1]) { tmp = arrayToSort[j]; arrayToSort[j] = arrayToSort[j+1]; arrayToSort[j+1] = tmp; needSort = true; } } } }
在每次进入内部循环时重置标志变量,若进行了交换操作则相应改变,进入内层循环前进行判断,若不需要再遍历,直接退出。
接下来写一些测试代码,来验证三种方案的效率。生成两个数组:一个完全逆序,长度为9999;另一个完全正序,长度同样为9999(不用再排序了),然后分别使用三种排序,统计排序时间,以下是一种方法的调用:
public static void main(String[] args) { final int len = 9999; int test[] = new int[len]; for (int i = 0; i < test.length; i++) { test[i] = len - 1 - i; } int testNoNeedSort[] = new int[len]; for (int i = 0; i < testNoNeedSort.length; i ++) { testNoNeedSort[i] = i; } long startTimeA = System.currentTimeMillis(); BubbleSort.sortGamma(test); long endTimeA = System.currentTimeMillis(); System.out.println("Time use A:" + (endTimeA - startTimeA)); long startTimeB = System.currentTimeMillis(); BubbleSort.sortGamma(testNoNeedSort); long endTimeB = System.currentTimeMillis(); System.out.println("Time use A:" + (endTimeB - startTimeB)); }
运行三种排序,统计到的时间:
版本 | 逆序(最差情况)ms | 正序(最好情况)ms |
---|---|---|
版本1 | 107 | 66 |
版本2 | 62 | 42 |
版本3 | 60 | 1 |
最差情况下由于版本2,版本3对内层排序进行了操作数优化,因此显著优于完全没有优化的版本1。
相关文章推荐
- 在命令行用 sort 进行排序
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- 文件遍历排序函数
- 关于C#中排序函数的总结
- C#递归算法之分而治之策略
- C#选择排序法实例分析
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- C#算法之大牛生小牛的问题高效解决方法
- C#实现Datatable排序的方法
- MYSQL必知必会读书笔记第五章之排序检索数据
- C#算法函数:获取一个字符串中的最大长度的数字
- 超大数据量存储常用数据库分表分库算法总结
- SQLSERVER的排序问题结果不是想要的
- Ruby实现插入排序算法及进阶的二路插入排序代码示例
- Windows Powershell排序和分组管道结果
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析