反向冒泡

411 阅读4分钟

原题出处: Java 数据结构与算法(第二版) Robert Lafore

因为看到这个泡反向走的思路挺有意思的, 又有可行性, 苦于没找到课后答案, 于是就调试并整理了出来 hh.

简介

是否可以在 in 索引从左移动, 直到比较到最大的更大值并移动到右边的 out 变量**后**, 
修改 bubbleSort() 方法, 使它称为双向移动的.
这样, in 索引先向以前一样, 将最大的数据项从左移到右, 当它到达 out 变量的位置时, 它掉头并将最小的数据项从右移到左.
提示: 需要 2 个外部索引变量, 一个在右边(之前的 out 变量), 另一个在左边.

分析

方案上是可行的!

复盘原版冒泡的过程:

in 经过相邻2个比较后将更大值移动到最右边, 经过一轮后, 更大的那个值经过反复拉扯, 总会移动到最右边的位置, 也就是 out 索引的位置.

那么在反向走的时候, 也可以将最小值移动到最左边嘛, 同样的道理.

过程疏通

在经过调试后, 有了下面的实现.

修改过程:

在 in 走到 out 位置的时候, 此时, in 已经结束了一轮的循环. 总是达到当前轮的最右边.

所以, 这个时候让 in 往回走是最合适的.

引入动态变量 back, 初始值为 in, 终点值为 1, 每次递减.
比较相邻2个数, 当遇到更小值的时候, 往左移动(交换)
直到最小值被交换到最左边.

同理, 反向一轮后, 左边部分也默认升序(无需进行冒泡)
修改 in 的初始值为 back+1, 下一轮从 back+1, 也就是左边已经升序部分的下一位开始.

代码实现(Java语言实现)

1.在 in 一轮循环后的位置, 添加 1 个以 in 为"起点"往回走的循环.

2.更新 in 从左边已升序序列的下一位开始.

//当 in 走到最右边, 往回走, 往回走的同时将最小值移动到最左边
public void bubbleSort() {
    int out, back, in;

    for (out = nElems - 1; out > 1; out--) {
        for (in = 0; in < out; in++) {
            if (a[in] > a[in+1]) {                
                swap(in, in+1);
            }
        }
        for (back = in-1; back > 1; back--) {
            if (a[back] < a[back-1]) {
                swap(back, back-1);
            }
        }
        in = back+1;
        //优化: 使in等于back+1, 由于back及其左边也已升序排列, 无需再继续冒泡
    }
}
调试图解

为了更加直观的感受, 这里只录制第一轮来回走的过程.

同样为了更加直观的体验, 推荐重点关注👉

观察黄色部分的 99 从上面移动到最下面的过程.

观察黄色部分的 0 从下面移动到最上面的过程.

实验数据

[77 99 44 55 22 88 11 0 66 33 ]

bubble-reverse.gif

冒泡算法的优化

1.冒泡在什么时候才交换?

答: 发现更大值的时候. 换句话说也就是非升序的时候.

2.有没有一种情况, 中间有一轮已经升序了? 后面的循环还有必要的吗?

答: 确实! 比如 [90, 1, 2, 3, 4, 5, 6, 7, 8].

我们知道, 冒泡只有在非升序的时候才进行交换. 那么, 如果没有发生交换的话, 则说明序列已经升序.

3.实现

增加一个布尔变量. 在交换过程修改. 当有一轮冒泡后发现布尔值没有被修改, 则直接返回序列.

public void bubbleSort() {
    int out, back, in;

    for (out = nElems - 1; out > 1; out--) {
        boolean hasSwap = false;
        for (in = 0; in < out; in++) {
            if (a[in] > a[in+1]) {
                hasSwap = true;		//*
                swap(in, in+1);
            }
        }
        for (back = in-1; back > 1; back--) {
            if (a[back] < a[back-1]) {
                hasSwap = true;		//*
                swap(back, back-1);
            }
        }
        in = back+1;
        //*
        if (hasSwap == false)
            return;
        //冒泡只有在非升序序列中发生交换		
    }
}
贴上完整代码

ArrayBub2.java

package chap4.srt;

/**
 * @name  ArrayBub2
 * @layer chap4.srt
 * @intro	这个例子是根据书后面的编程练习题改编的
 * @function	让 in 向右移动时把最大值移动最右边, 返回的时候把最小值移动到最左边.
 * */
public class ArrayBub2 {
	private long[] a;
	private int nElems;
	//
	public ArrayBub2(int max) {
		a = new long[max];
		nElems = 0;
	}
	//
	public void insert(long newV) {
		a[nElems++] = newV;		
	}
	
	//反向冒泡
	public void bubbleSort() {
		int out, back, in;
		
		for (out = nElems - 1; out > 1; out--) {
			boolean hasSwap = false;
			for (in = 0; in < out; in++) {
				if (a[in] > a[in+1]) {
					hasSwap = true;	
					swap(in, in+1);
				}
			}
			for (back = in-1; back > 1; back--) {
				if (a[back] < a[back-1]) {
					hasSwap = true;
					swap(back, back-1);
				}
			}
			in = back+1;
			//优化下一轮 in 的起点
			if (hasSwap == false)
				return;
			//优化冒泡的轮数			
		}
	}
	//看似交换坐标, 实则交换元素
	public void swap(int one, int two) {
		long temp = a[one];
		a[one] = a[two];
		a[two] = temp;
	}
	
	public void display() {
		for (int i = 0 ; i < nElems; i++) {
			System.out.print(a[i] + " ");
		}
		System.out.println();
	}
}

ArrayBub2App.java

package chap4.srt;

/**
 * @name  ArrayBub2App
 * @layer chap4.srt
 * @intro
 * @function
 * */
public class ArrayBub2App {
	public static void main(String[] args) {
		ArrayBub2 arr3;
		int maxSize = 10;
		arr3 = new ArrayBub2(maxSize);
						
		arr3.insert(77);
		arr3.insert(99);
		arr3.insert(44);
		arr3.insert(55);
		arr3.insert(22);
		arr3.insert(88);
		arr3.insert(11);
		arr3.insert(00);
		arr3.insert(66);
		arr3.insert(33);
		//77 99 44 55 22 88 11 0 66 33 
		
		arr3.display();
		arr3.bubbleSort();
		arr3.display();
		//0 11 22 33 44 55 66 77 88 99 
	}
}

2021-04-07

原题出处: 第三章简单排序 课后编程题 3.1 P79