排序算法1——冒泡排序

  在介绍冒泡排序之前,优先介绍一种算法设计的策略——蛮力法。这是一种简单直接的解决问题的方法,常常直接基于问题的描述和所涉及的定义。由于蛮力法是基于问题的定义来思考的,那么可以说它是一种几乎什么问题都能解决的一般性的方法。当然,缺点也是显而易见的,那就是“笨”,即解决方法的过程既不巧妙,也不高效。而冒泡排序就是蛮力法在排序问题上的一个典型的应用场景。

  所谓冒泡排序,即是对于一个给定长度为n的无序数组,由初始位置开始,比较数组相邻两个元素,如果是逆序排列的,就交换它们的位置,重复多次之后,最大数就“沉”到了最下面的位置。第二次再从初始位置开始,将第二大的元素沉到倒数第二个位置。这样一直做n-1次,整个数组就是有序的了。

  当然,也可以从数组最后一位开始向前遍历,这样一来,每一次的操作就是将最小的数字“浮”到最上方。这也是“冒泡排序”名字的由来。这两种做法在效率上并无高下之分,全凭个人喜好。我自己偏爱“下沉”的做法。

  值得一提的是,在第一轮操作结束之后,第二轮的操作无需比较最后一位,因为最后一位已经是最大的元素了。所以对于一个长度为n的数组,整个算法消耗的时间为: (n-1)+(n-2)+…+1=n(n-1)/2,即时间复杂度为O(n^2)。同时,显而易见,整个算法只消耗一份数组的空间,所以空间复杂度为O(1)。

  下面用一个具体的场景来体会一下冒泡排序的过程。

场景:

现有一个无序数组,共7个数:89 45 54 29 90 34 68。使用冒泡排序对这个序列进行升序排序。

基础冒泡排序实现过程:

第一步:

89 45 54 29 90 34 68;

45 89 54 29 90 34 68;

45 54 89 29 90 34 68;

45 54 29 89 90 34 68;

45 54 29 89 90 34 68;

45 54 29 89 34 90 68;

45 54 29 89 34 68 90;

第二步:

45 54 29 89 34 68 90;

45 54 29 89 34 68 90;

45 29 54 89 34 68 90;

45 29 54 89 34 68 90;

45 29 54 34 89 68 90;

45 29 54 34 68 89 90;

第三步:

45 29 54 34 68 89 90;

29 45 54 34 68 89 90;

29 45 54 34 68 89 90;

29 45 54 34 68 89 90;

29 45 54 34 68 89 90;

第四步:

29 45 54 34 68 89 90;

29 45 54 34 68 89 90;

29 45 54 34 68 89 90;

29 45 34 54 68 89 90;

第五步:

29 45 34 54 68 89 90;

29 45 34 54 68 89 90;

29 34 45 54 68 89 90;

第六步:

29 34 45 54 68 89 90;

29 34 45 54 68 89 90;

  根据之前的描述,附上基础冒泡排序的代码:

 1 // 基础冒泡排序
 2 public static void basal(int[] array) {
 3     // 外循环,最后一次只剩一个数字未排序,自然有序,无需再排序
 4     for (int i = 0; i < array.length - 1; i++) {
 5         // 内循环,不计已经沉底的最大数
 6         // 即[array.length-i-1,array.length-1]区间已经有序
 7         for (int j = 0; j < array.length - 1 - i; j++) {
 8             if (array[j] > array[j + 1]) {
 9                 swap(j, j + 1, array);
10             }
11         }
12     }
13 }
14
15 // 交换数组中2个值的位置
16 private static void swap(int index1, int index2, int[] array) {
17     int temp = array[index1];
18     array[index1] = array[index2];
19     array[index2] = temp;
20 }

basal

  蛮力法的应用有一个显著的特点,就是在经过适当的努力之后,可以对算法进行一定的改良,从而它的性能,但并不会减弱算法本身的时间复杂度。冒泡排序作为蛮力法的典型应用,自然也有这种特性。

  我们首先观察上述示例的实现过程。不难发现,其实在“第五步”结束之后,整个数组已经有序,实际上并不需要执行“第六步”。这种情况不难想象,假定待排序的数组本身已经有序,那么我们难道还需要傻乎乎的执行n(n-1)/2次操作才能将整个数组排序吗?可以设定一个标志位,检查一次比较之后,是否有数据进行了交换,若是没有,那么整个数组就已经有序了,可以直接退出。极端情况下,如刚才提到的,对有序数组进行排序,只需要执行n-1次操作,就可以完成排序。

  附上优化冒泡排序1的代码:

 1 // 优化冒泡排序1
 2 public static void optimized_1(int[] array) {
 3     // 内循环数据交换标志
 4     boolean hasSwaped = false;
 5     for (int i = 0; i < array.length - 1; i++) {
 6         for (int j = 0; j < array.length - 1 - i; j++) {
 7             if (array[j] > array[j + 1]) {
 8                 swap(j, j + 1, array);
 9                 hasSwaped = true;
10             }
11         }
12         // 如果某次内循环,没有发生任何一次数据交换,
13         // 表示整个数组已经完全有序,没有必要继续做外循环,直接退出
14         if (!hasSwaped) {
15             break;
16         }
17     }
18 }

optimized_1

  其实还有更进一步的优化方法。再仔细观察上述的实现过程,可以发现,在“第二步”结束的时候,我们本来期待的,是89、90有序,但实际上倒数第三位68,其实是第三大的,那么是不是有方法可以确定68是第三大的呢?若是可以,我们在执行“第三步”的时候,就可以将68、89、90作为已经有序,从而减少一整轮的操作。

  方法是存在的。我们可以想象,冒泡排序中,整个的大数“下沉”的过程,实际上是层层下沉的,也就是说,只要最大数不在最后一位,那么总会存在最后一次会出现数据交换。那么如果交换出现在倒数第二次,而不是最后一次会是什么情况?最大数一定会出现在数组的最后一位,而次大数,作为排除掉最大数的最大值,一定会经过层层下沉,来到倒数第二的位置。以此类推。换一句话说,我们记原本数组一轮的遍历是在[0,n]区间,那么下一轮的遍历是在[0,n-1]区间。现在记数组本轮遍历的最后一次交换发生在lastSwapPos位置,那么下一轮的遍历就是在[0, lastSwapPos]区间。

  结合第1种优化方法,附上优化冒泡排序2的代码:

 1 // 优化冒泡排序2
 2 public static void optimized_2(int[] array) {
 3     int lastSwapPos = array.length - 1;
 4     int lastSwapPosTemp = array.length - 1;
 5     for (int i = 0; i < array.length - 1; i++) {
 6         lastSwapPos = lastSwapPosTemp;
 7         // 因为大数一定是不断下沉的,所以只要最大数不在遍历的终点上,最后一次一定会执行交换、
 8         // 换言之,若是最后一次交换未执行,而是在倒数第二次处执行了交换,一定可以保证倒数第二个数是次大数
 9         // 因此可以将倒数第三个数作为下一次交换的终点
10         // 依次类推,可以保证[lastSwapPos,array.length-1-i]是有序的,且其中任意一个数都比前面的数字大
11         // 所以内循环的退出条件,可以由j<array.length-1-i转变为j<lastSwapPos
12         for (int j = 0; j < lastSwapPos; j++) {
13             if (array[j] > array[j + 1]) {
14                 swap(j, j + 1, array);
15                 lastSwapPosTemp = j;
16             }
17         }
18         // 一次都未交换的情况
19         if (lastSwapPos == lastSwapPosTemp) {
20             break;
21         }
22     }
23 }

optimized_2

  如果存在描述或者代码错误的情况,欢迎指正,谢谢!

时间: 2024-10-27 03:05:28

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

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

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

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

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

java排序算法之 --- 冒泡排序

冒泡排序是我们比较常用的一种排序算法,它的原理是:从头遍历未排好序的序列,每相邻的两个元素进行比较,较大(或较小)的元素放在后面,一轮遍历之后最大(或最小)的元素已经放到最后,然后依次重复之前的步骤把未排好序的序列进行排序,遍历 n-1 轮之后,整个序列就排好序了(第 n 轮不必要,因为第 n-1 轮排序后只剩下最后一个元素).因为这种排序算法每次排好一个元素,就像冒泡一样,所以叫冒泡排序. 举个小例子: arr[] = {6,1,5,3,2,4} 第一趟, 6,1,5,3,2,4  :  1,

我的Java开发学习之旅------&gt;Java经典排序算法之冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 一.算法原理 冒泡排序算法的运作如下: 1.比较相邻的元素.如果第一个比第二个大,就交换他们两个. 2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 3.

基础排序算法(冒泡排序,选择排序,插入排序)

最近经常调用api中的排序算法,很少自己写了,有时候也只写写快速排序这些比较快的排序,然而刚开始学排序时用的一些基本的排序算法却有点忘了 正好今天Java老师让我们每个人写个选择排序热热手,趁这个机会再来复习下一些基本的排序好了. 一.冒泡排序(稳定排序) 学编程接触到的第一个排序算法,基本思路就是,给定一个无序数组a0.a1.a2.a3....an; 通过从左到右相邻的元素两两比较,把最大或者最下的数依次放到数组的右边,最后得到有序的序列 public static void maoPao(i

排序算法(一)冒泡排序

以前就学过C,最近学的JAVA,感觉不错. 明年研究生了,现在我想复习一下数据结构,都是基础的东西. 先从排序算法开始吧.第一个冒泡排序. public class BubbleSort { public static void sort(int[] a)//简单的冒泡算法. { //static 静态只能调用静态 int temp=0; int SwapSteps=0;//交换的次数 int CompareSteps=0;//比较的次数 int flag=1;//如果排好了,再循环就没用了,怎

排序算法之冒泡排序(Bubble Sort)

基本思想 假如按照从小到大的顺序排序,对待排序数组进行遍历,如果当前值大于其后一个值则进行交换,不断的进行遍历,直到没有交换动作的发生.冒泡排序的最好时间复杂度为O(n),最坏的时间复杂度为O(n2),所以冒泡排序的平均时间复杂度为O(n2),另外冒泡排序不会改变相同元素的前后顺序,故其是一种稳定的排序算法. 实现代码 #include<iostream> using namespace std; int main() { int MyData[10] = { 7,3,12,46,32,64,

排序算法之冒泡排序的两种方式

冒泡排序是排序算法的一种,思路清晰,代码简洁,常被用在大学生计算机课程中. 冒泡排序有两种方式,相邻的两个数比较,把大的数(或者小的数)放在上面,依次进行,像水泡一样,逐渐上浮. 也可以以相反的过程,把较大的数(或者较小的数)放在下面,推入湖底.这两种方式都是冒泡排序,因为冒泡排序是比较相邻的两个数,下标不具有跳跃性,同时也是一种稳定的算法. 方式一:较大的数(或者较小的数)上浮 1 void BubbleSort(int a[], int n) 2 { 3 for(int i=0; i<n-1

排序算法之冒泡排序的思想以及Java实现

1 基本思想 设排序表长为n,从后向前或者从前向后两两比较相邻元素的值,如果两者的相对次序不对(A[i-1] > A[i]),则交换它们,其结果是将最小的元素交换到待排序序列的第一个位置,我们称它为一趟冒泡.下一趟冒泡时,前一趟确定的最小元素不再参与比较,待排序序列减少一个元素,每趟冒泡的结果把序列中最小的元素放到了序列的"最前面". 2,算法的实现(Java) package Algorithm; public class BubleSort { /** * @param ar