冒泡排序(思路,代码,优化详解)
思路
思路:每一轮循环,通过俩俩比较,都会将一个最值"冒泡"出来。
比如下面的数组{2,5,3,8,6,7},若从小到大排序
第一轮循环会把最小的元素冒泡出来,就是2
第二轮循环会把第二小的元素冒泡出来,就是3
最多5次循环可以完成排序,也就是length-1次
private static int[] BubbleSort(int[] nums) {
//冒泡排序
//nums.length-1轮循环,每一轮循环,俩俩比较,找到一个最值,冒泡。
for(int i=1;i< nums.length;i++){
for(int j= nums.length-1;j>=i;j--){
//俩俩比较,小的放前面
if(nums[j]<nums[j-1]){
//交换
swap(nums,j,j-1);
}
}
}
return nums;
}
private static void swap(int[] nums, int i, int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
public static void main(String[] args) {
int[] nums={2,5,3,8,6,7};
System.out.println(Arrays.toString(BubbleSort(nums)));//[2, 3, 5, 6, 7, 8]
}
优化1:设置有序标志位flag
对于{2,5,3,8,6,7}
正常来说要进行5轮循环,每一轮冒泡出一个最值
但数组{2,5,3,8,6,7},第2轮循环后变为{2,3,5,6,7,8}。已经有序,所以剩下的循环是没有意义的。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
所以怎么避免“傻乎乎”地再去循环呢?那我们需要确定当前数组是有序的,有序了我们才可以不去循环了,对吧
那么如何确定数组已经有序了呢?
所以优化1:若上一次循环中,没有进行交换,表明数组已经有序就提前结束
试想一下:如果上一轮发生了交换,那我们不确定数组是否有序,还要循环一次看一下。
如果没有发生交换,那肯定就是有序了呀
如果用if-else架构,那么它应该是这样的逻辑:
if(!swap)//上一轮没交换过
then 不用看,数组有序了
else
不确定,再来一轮试试看
实现:记录标志位isSwap,若发生交换则重置为true,在每一轮循环前判断isSwap==true,是则说明上一轮进行过交换,这一轮循环是有意义的;否则直接break结束。
private static int[] BubbleSort1(int[] nums) {
//冒泡排序
//nums.length-1轮循环,每一轮循环,俩俩比较,找到一个最值,冒泡。
//优化1:记录标志位isSwap,若上一轮循环没有交换过,
// 则说明数组已经有序,直接break
boolean isSwap=true;
for(int i=1;i< nums.length;i++){
//先检查标志位,判断数组是否已经有序
if(!isSwap){
break;
}
//每一轮初始化为false
isSwap=false;
for(int j= nums.length-1;j>=i;j--){
//俩俩比较,小的放前面
if(nums[j]<nums[j-1]){
//交换
swap(nums,j,j-1);
isSwap=true;//标志位为true,说明交换过,继续下一轮
}
}
}
return nums;
}
优化2:设置结束边界lastSwapIndex
对于优化1,我们只是用“上一轮循环是否交换”来笼统地判断了一下“数组是否有序”。
如果有序了,就不排了;反之则循环i。
可范围实在过大,所以我们需要缩小判断”有序“的范围
为了更好的说明问题,我们需要换个例子
假设现在有数组{1,2,3,4,6,7,5}。我们发现这个数组好像是基本有序的(至少前几个很有序)
如果正常情况下(演示)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
我们看到这个序列,第一轮比较6次,第二轮是5次
但是第二轮比较时还是有点“傻”,至少5次比较很没必要吧
如果我们对优化1节省的循环次数还不满意,可以继续优化
我们发现:上一轮swap的位置之前,数组肯定是有序的(因为如果不有序,它还会继续swap,更新swap的位置,而一旦确定,在swap位置之前的数组序列肯定是有序的)
那么也就说明:也许以前j一直从length-1遍历到i是可以改变的。
如果用lastSwapIndex代替最后swap的位置的话,j只需要遍历到lastSwapIndex即可
我们再回过头看之前的例子,但这次我们会记录lastSwapIndex,看一下它节省了多少。
这次j从末尾往前遍历,只遍历到lastSwapIndex,比较了2次
不过这个优化并没有改变”冒泡不行“的事实
优化2:设置结束边界lastSwapIndex,记录上一次最后交换的位置,作为下一次循环的结束边界
这个优化的逻辑是:上一次最后交换的位置之前,数组元素是有序的,因为没发生交换
所以呢,j在遍历的时候只需要从nums.length-1遍历到lastSwapIndex即可。
也可以这样理解,原来j遍历到i,是因为每一轮冒泡出来一个最值,这个最值是有序的,
比如第一轮出来最小的2,第二轮出来次小的3,然后第三轮呢就遍历到j>=i(i=3即可),因为每一轮出来一个最值,它的排列就是有序的。
而我们通过记录上次最后交换的位置,知道了数组已经有序的序列:就是nums[0]到nums[lastSwapIndex]。所以nums.length-1遍历到lastSwapIndex即可。
public static SortResult BubbleSort_b1(int[] nums){
//优化1:设置标志位,如果上一轮循环没有交换过元素,说明后面已经有序,直接进入下一轮
//优化2:设置结束边界,记录上一次最后交换的位置,作为下一次循环的结束边界,比如
SortResult sortResult = new SortResult(nums);
boolean isSwap=true;
int lastSwapIndex=1;
for(int i=1;i< nums.length;i++){//如果isSwap为false,则直接下一轮
if(!isSwap){
break;
}
isSwap=false;
int end=lastSwapIndex;
for(int j= nums.length-1;j>=end;j--){//遍历到lastSwapIndex即可,不需要遍历到i
sortResult.incrementComparisons();
if(nums[j]<nums[j-1]){
swap(nums,j,j-1);
isSwap=true;//标志位为true,说明交换过,继续下一轮
lastSwapIndex=j;//记录交换的下标,以便下一轮j的遍历
sortResult.incrementSwaps();
}
}
System.out.println("第"+i+"次循环:"+ Arrays.toString(nums));
}
return sortResult;
}
总结
冒泡排序的思路是:每一轮循环,俩俩比较,冒泡出一个最值,经过最多length-1次循环,把数组排序。
优化思路是:
- 判断数组何时已经排好序,i不再多余循环 = > 记录标志位isSwap
- 判断数组已经排好序的范围,j不要多余遍历 => 设置结束边界lastSwapIndex
blog referenece
https://mrfzh.github.io/2019/11/17/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%8F%8A%E5%85%B6%E4%BC%98%E5%8C%96%EF%BC%88%E4%B8%89%E7%A7%8D%E4%BC%98%E5%8C%96%EF%BC%89/
https://www.jerrymei.cn/how-to-make-algorithm-visualizer/
https://chat.openai.com/
https://blog.csdn.net/dongming8886/article/details/123458790?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171464035216800178581362%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171464035216800178581362&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-2-123458790-null-null.142
http://www.donghuasuanfa.com/sort
https://visualgo.net/zh/sorting?slide=6-6
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html