九大常见算法(一)—— 冒泡排序 及其优化

本文深入解析冒泡排序算法的原理,通过实例演示排序过程,并提供Java代码实现。此外,探讨了两种优化策略,以减少不必要的比较和提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是冒泡排序?

        冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

        它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

        这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

        上面的大串文字是从百度复制下来的,什么是冒泡排序?个人的理解是:

        冒泡,轻的往上浮重的往下沉。亦即小的元素浮到上面来大的元素沉到下面去。也就是说,冒泡排序要做的事,将一个乱序的元素列,转换成一个有序(从小到大排列)的元素列。

二、冒泡排序的原理

  1.  比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2.  对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3.  针对所有的元素重复以上的步骤,除了最后一个。
  4.  持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

三、举例说明

        要是觉得上面的原理粘贴的太官方,可以看一下下面的例子:初始数组 arr [5] = { 8, 6, 6, 9, 3 } ,希望对它进行从小到大排序,利用冒泡排序算法:

        1、第一趟排序:未排序的数组为初始数组arr [5] = { 8, 6, 6, 9, 3 },索引cur从0开始,下边界ver为5(这里的ver可以简单理解为数组实际要比较的长度length,cur < ver-1)。cur<4

        (1)cur = 0 , arr [cur] = 8 ,由于8>6,故两者调换位置,得到{ 6,8,6,9,3 } :

               

        (2)cur = cur + 1 =1; arr [cur] = 8, 由于8>6,故两者调换位置,得到{ 6,6,8,9,3 } :

                

        (3)cur = cur + 1 =2; arr [cur] = 8, 由于8<9,故两者不需要调换位置,仍是{ 6,6,8,9,3 } :

               

        (4)cur = cur + 1 =4; arr [cur] = 9, 由于9>3,故两者调换位置,得到{ 6,6,8,3,9 } :

               

    经过第一趟的排序,数组 arr[ ] 由 { 8, 6, 6, 9, 3 } 转变成了 { 6,6,8,3,9 } (arr2[ ])。我们将第一个最大的元素沉到了最后面,即arr[4] = 9。

    2、第二趟排序:对第一趟的数组 arr2[ ] = { 6,6,8,3,9 }继续进行排序,索引cur从0开始,下边界ver = ver - 1 = 4(为啥是4?因为由上一轮的排序,我们已经得到了arr[ ]中最大的元素arr[4] = 9,而且我们已经将该元素放到了数组的最末尾了,后续的排序也就不用再管它了)。cur<3

    (1)cur = 0 , arr [cur] = 6 ,由于6=6,故两者不需要调换位置,仍是{  6,6,8,3,9 } :

               

    (2)cur = cur + 1 =1; arr [cur] = 6, 由于6<8,故两者不需要调换位置,仍是{  6,6,8,3,9 } :

                

    (3)cur = cur + 1 =2; arr [cur] = 8, 由于8>3,故两者调换位置,得到{ 6,6,3,8,9 } :

               

        此时,cur = cur + 1 = 3 = ver - 1,第二趟排序结束。或者这样说,由第一趟排序我们已经得到arr[5] = 9是最大的元素,故arr[4]=8就不需要与arr[5]相比较了(此时的arr[4]肯定比arr[5]小啊)。

        那么,经过第二趟的排序,数组 arr[ ] 由 { 6,6,8,3,9 } (arr2[ ])转变成了 { 6,6,3,8,9 } (arr3[ ])。我们将第二大的元素,即arr[3] = 8。

3、第三趟排序:对第二趟的数组 arr3[ ] = { 6,6,3,8,9 }继续进行排序,索引cur从0开始,下边界ver = ver - 1 = 3。cur<2

    (1)cur = 0 , arr [cur] = 6 ,由于6=6,故两者不需要调换位置,仍是{  6,6,3,8,9 } :

               

    (2)cur = cur + 1 =1; arr [cur] = 6, 由于6 > 3,故两者调换位置,仍是{  6,3,6,8,9 } :

                

 

        此时,cur再+1就要等于ver了,故第三趟排序结束。那么,经过第三趟的排序,数组 arr[ ] 由 { 6,6,3,8,9 } (arr3[ ])转变成了 { 6,3,6,8,9 } (arr4[ ])。我们将第三大的元素,即arr[2] = 6。

4、第四趟排序:对第三趟的数组 arr4[ ] = { 6,3,6,8,9 }继续进行排序,索引cur从0开始,下边界ver = ver - 1 = 2。cur<1,那么,这次就只需要比较cur=0这一次就可以啦!

    (1)cur = 0 , arr [cur] = 6 ,由于6>3,故两者调换位置,得到{  3,6,6,8,9 } :

               

        经过第四趟的排序,数组 arr[ ] 由 { 6,3,6,8,9 } (arr4[ ])转变成了 { 3,6,6,8,9 } (arr5[ ])。我们将第四大的元素,即arr[1] = 6。

        经过4次(arr.length - 1)的排序,未排序的只剩a[0]了(绿色的表示已经排哈皮序了的元素),其实也就是说,我们的排序工作,已经完成了。吼吼吼~ 这也是冒泡排序的一个过程了!

          亦即 

四、Java编码实现

根据上面的思路,实现冒泡排序,编码如下:

public class BubbleSort {
    private static void sort(int arr[]){
	//这里-1,加不加都没什么说法,不加就都少循环一次没必要的循环吧!
	for(int i=0; i<arr.length-1; i++){
	    //打印只是为了方便看排序过程
	    System.out.println("----需要排序的数组:"+Arrays.toString(arr)+"----");
	    //这里-1,是为了防止溢出,不能少
	    for(int j=0; j<arr.length-i-1; j++){
		if(arr[j] > arr[j+1]){
		    int temp = arr[j];
		    arr[j] = arr[j+1];
		    arr[j+1] = temp;
		}
		System.out.println((j+1)+"、arr["+j+"]和arr["+(j+1)+"]比较后得到:"+Arrays.toString(arr));
	    }
	    System.out.println("----第"+(i+1)+"次排序得到的数组:"+Arrays.toString(arr)+"----");
	    System.out.println();
	}
    }
	
    public static void main(String[] args) {
	int arr[] = {8,6,6,9,3};
	sort(arr);	
    }
}

输出得到:

----需要排序的数组:[8, 6, 6, 9, 3]----
1、arr[0]和arr[1]比较后得到:[6, 8, 6, 9, 3]
2、arr[1]和arr[2]比较后得到:[6, 6, 8, 9, 3]
3、arr[2]和arr[3]比较后得到:[6, 6, 8, 9, 3]
4、arr[3]和arr[4]比较后得到:[6, 6, 8, 3, 9]
----第1次排序得到的数组:[6, 6, 8, 3, 9]----

----需要排序的数组:[6, 6, 8, 3, 9]----
1、arr[0]和arr[1]比较后得到:[6, 6, 8, 3, 9]
2、arr[1]和arr[2]比较后得到:[6, 6, 8, 3, 9]
3、arr[2]和arr[3]比较后得到:[6, 6, 3, 8, 9]
----第2次排序得到的数组:[6, 6, 3, 8, 9]----

----需要排序的数组:[6, 6, 3, 8, 9]----
1、arr[0]和arr[1]比较后得到:[6, 6, 3, 8, 9]
2、arr[1]和arr[2]比较后得到:[6, 3, 6, 8, 9]
----第3次排序得到的数组:[6, 3, 6, 8, 9]----

----需要排序的数组:[6, 3, 6, 8, 9]----
1、arr[0]和arr[1]比较后得到:[3, 6, 6, 8, 9]
----第4次排序得到的数组:[3, 6, 6, 8, 9]----


//如果第一个for循环是i<arr.length,即不 -1的话,输出结果后面还有:
//----需要排序的数组:[3, 6, 6, 8, 9]----
//----第5次排序得到的数组:[3, 6, 6, 8, 9]----

五、冒泡排序算法的优化(1)

        为什么要优化?怎么优化?看看类似这样的例子:arry[ ] = {1,2,3,5,4 },很显然,这个数组只需要将5和4调换就可以了。但如果使用上面的方法,进行的过程如下,后面那几次的排序,就显得多余了。那么,该怎么优化呢?

----需要排序的数组:[1, 2, 3, 5, 4]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 5, 4]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 5, 4]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 5, 4]
4、arr[3]和arr[4]比较后得到:[1, 2, 3, 4, 5]
----第1次排序得到的数组:[1, 2, 3, 4, 5]----             到这已经有结果了!

----需要排序的数组:[1, 2, 3, 4, 5]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 4, 5]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 4, 5]
----第2次排序得到的数组:[1, 2, 3, 4, 5]----

----需要排序的数组:[1, 2, 3, 4, 5]----                            这儿是不是显得多余了?
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 4, 5]
----第3次排序得到的数组:[1, 2, 3, 4, 5]----

----需要排序的数组:[1, 2, 3, 4, 5]----                           这儿是不是显得多余了?
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5]
----第4次排序得到的数组:[1, 2, 3, 4, 5]----

        针对类似 {1,2,3,5,4 }这种提早完成排序的情况,我们的优化思路是:给排序了的数组进行是否已经有序的判断,如果数组在 array.length-1次 排序前已经提早成为了有序数组,那我们也提早将后面的循环break掉!直接上代码吧:

public class BubbleSort {
    private static void sort2(int arr[]){
        for(int i = 0; i < arr.length-1; i++){
            //只是为了方便看过程,而做的打印
	    System.out.println("----需要排序的数组:"+Arrays.toString(arr)+"----");
            
            //有序标记   默认有序
	    boolean isSorted = true;
	
            for(int j = 0; j < arr.length-i-1; j++){
	        if(arr[j] > arr[j+1]){
		    //有元素交换,为无序,标记变为false
		    isSorted = false;
		               
		    int temp = arr[j];
		    arr[j] = arr[j+1];
		    arr[j+1] = temp; 
		}
		System.out.println((j+1)+"、arr["+j+"]和arr["+(j+1)+"]比较后得到:"+Arrays.toString(arr)); 
             }
	    System.out.println("----第"+(i+1)+"次排序得到的数组:"+Arrays.toString(arr)+"----");
            System.out.println();
		       
            if(isSorted)    //isSorted为true时,数组已有序,结束循环
		 break;      
        }
    }
	
    public static void main(String[] args) {
        int arr[] = {1,2,3,5,4};
        sort2(arr);
    }
}

运行结果:

----需要排序的数组:[1, 2, 3, 5, 4]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 5, 4]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 5, 4]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 5, 4]
4、arr[3]和arr[4]比较后得到:[1, 2, 3, 4, 5]
----第1次排序得到的数组:[1, 2, 3, 4, 5]----            到这已经有结果了!但它还不知道这是结果

----需要排序的数组:[1, 2, 3, 4, 5]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 4, 5]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 4, 5]
----第2次排序得到的数组:[1, 2, 3, 4, 5]----           所以这里还会进行一次,两次都相等了,后面的还不等嘛!

六、冒泡排序算法的优化(2)

        这里再列一种情况,就是无序列的前半部分无序,后半部分有序,如 arr[ ] = {2,3,1,4,5,6,7,8}。这种情况下,其实后半部分的元素我们没必要再反反复复的去比较了。下方的运行结果是用 五、优化方法(1) 的代码运行的结果。拿此运行结果与再次优化后的运行结果对比,就可以很明显看出差别了,这里就不多说了。

----需要排序的数组:[2, 3, 1, 4, 5, 6, 7, 8]----
1、arr[0]和arr[1]比较后得到:[2, 3, 1, 4, 5, 6, 7, 8]
2、arr[1]和arr[2]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
3、arr[2]和arr[3]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
4、arr[3]和arr[4]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
5、arr[4]和arr[5]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
6、arr[5]和arr[6]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
7、arr[6]和arr[7]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
----第1次排序得到的数组:[2, 1, 3, 4, 5, 6, 7, 8]----

----需要排序的数组:[2, 1, 3, 4, 5, 6, 7, 8]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
4、arr[3]和arr[4]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
5、arr[4]和arr[5]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
6、arr[5]和arr[6]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]

----第2次排序得到的数组:[1, 2, 3, 4, 5, 6, 7, 8]----

----需要排序的数组:[1, 2, 3, 4, 5, 6, 7, 8]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
2、arr[1]和arr[2]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
3、arr[2]和arr[3]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
4、arr[3]和arr[4]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
5、arr[4]和arr[5]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]

----第3次排序得到的数组:[1, 2, 3, 4, 5, 6, 7, 8]----

         针对无序列的前半部分无序,后半部分有序的情况,做出如下优化:

public class BubbleSort {
    private static void sort3(int arr[]){
	//最后一次交换的位置
	int lastSvop = 0;
		
	//无序数列的边界,每次比较只需要比到ver为止.
	//第一次比较的边界是ver=arr.length-1,即比较到最后一个元素
	int ver = arr.length - 1;
		
	for(int i = 0; i < arr.length-1; i++){
	    //只是为了方便看过程,而做的打印
	    System.out.println("----需要排序的数组:"+Arrays.toString(arr)+"----");
			
	    //有序标记
            boolean isSorted = true;
		    
            for(int j = 0; j < ver; j++){
	        if(arr[j] > arr[j+1]){
                    //有元素交换,即为无序,false
		    isSorted = false;
		            
		    int temp = arr[j];
		    arr[j] = arr[j+1];
		    arr[j+1] = temp;
		            
		    //把无序数列的边界更新为最后一次交换元素的位置
		    lastSvop = j;
		}
	        System.out.println((j+1)+"、arr["+j+"]和arr["+(j+1)+"]比较后得到:"+Arrays.toString(arr)); 
	    }
            System.out.println("----第"+(i+1)+"次排序得到的数组:"+Arrays.toString(arr)+"...最后一次交换元素的位置:"+lastSvop);
	    System.out.println();
	    ver = lastSvop;
	    if(isSorted)
		  break;
	}
    }
	
    public static void main(String[] args) {
        int arr[] = {2,3,1,4,5,6,7,8};
        sort3(arr);
    }
}

运行结果:

----需要排序的数组:[2, 3, 1, 4, 5, 6, 7, 8]----
1、arr[0]和arr[1]比较后得到:[2, 3, 1, 4, 5, 6, 7, 8]
2、arr[1]和arr[2]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
3、arr[2]和arr[3]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
4、arr[3]和arr[4]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
5、arr[4]和arr[5]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
6、arr[5]和arr[6]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
7、arr[6]和arr[7]比较后得到:[2, 1, 3, 4, 5, 6, 7, 8]
----第1次排序得到的数组:[2, 1, 3, 4, 5, 6, 7, 8]...最后一次交换元素的位置:1

----需要排序的数组:[2, 1, 3, 4, 5, 6, 7, 8]----
1、arr[0]和arr[1]比较后得到:[1, 2, 3, 4, 5, 6, 7, 8]
----第2次排序得到的数组:[1, 2, 3, 4, 5, 6, 7, 8]...最后一次交换元素的位置:0

----需要排序的数组:[1, 2, 3, 4, 5, 6, 7, 8]----
----第3次排序得到的数组:[1, 2, 3, 4, 5, 6, 7, 8]...最后一次交换元素的位置:0

      

        此文借鉴 https://blog.youkuaiyun.com/wubingju93123/article/details/81215984

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值