【紙上得來終覺淺,絕知此事要躬行】
廢話不多說,網上有關冒泡排序的文章太多了,但是光看或者上來就使用別人最終優化的代碼總會沒有什麼感覺。所以這次一點點通過代碼,自己去感受一下優化的效果,文章是寫給自己以後看的,我是個話癆,寫的不會太專業。但是我還是要寫!要感悟
開整!
先亂寫一個數組,隨便敲了16個元素,太少了沒啥感覺。然後按照冒泡排序的思路去寫了個算法,並添加一些記錄參數供我們打印出來觀察比較用。
private static int[] array = { 5, 2, 6, 17, 8, 39, 10, 11, 22, 13, 15, 9, 56, 1, 22, 27 };
public static void main(String[] args) {
BubbleSort(array);
}
/**
* 原始冒泡算法
*/
public static void BubbleSort(int[] array) {
int round=0; //比較輪數
int count=0; //比較次數
int lastIndex=array.length-1; //末元素下標
for (int i = 0; i < lastIndex; i++) {
round++;
System.out.println("******************** 第" + round + "輪 ********************");
System.out.println("比較元素:" +printSortUnit(array, lastIndex));
for (int j = 0; j < lastIndex; j++) {
if(array[j]>array[j+1]){
swap(array, j, j+1);
}
count++;
System.out.println("第"+count+"次 "+Arrays.toString(array));
}
}
}
/**
* 交換數組元素
*/
public static void swap(int[] array, int p, int q) {
int temp = 0;
temp = array[p];
array[p] = array[q];
array[q] = temp;
}
/**
* 打印需要比較的所有元素
*/
public static String printSortUnit(int[] array, int end) {
StringBuffer str = new StringBuffer();
str.append(array[0]);
for (int i = 1; i <= end; i++) {
str.append(","+array[i] );
}
return str.toString();
}
複製上面代碼,跑一下,發現跑了15輪,每輪都比較了15次,所以一共15*15=225次比較。
這裏說明一下,1次表示某兩個相鄰元素比較大小,1輪表示這16個元素從左往右比較完一遍。
我們可以發現,每一輪結束後數組從右往左就會依次從大到小排列好元素,所以我們把整個數組分爲兩塊,左邊一塊是未排序完的元素數組,右邊一塊是從最大值開始依次排序的元素數組,並且未排序完的任意元素肯定小於已排序完的任意元素,所以我們可以將右邊已排好序的“大元素”數組在下一輪的排序中剔除掉,只比較左邊的這些元素並繼續從大到小依次剔除即可。
第一次優化
很簡單,改動一下j遍歷即可:
/**
* 冒泡算法第一次優化
*/
public static void BubbleSort1(int[] array) {
int count=0;
int round=0;
int lastIndex = array.length-1;
for (int i = 0; i < lastIndex; i++) {
round++;
System.out.println("******************** 第" + round + "輪 ********************");
System.out.println("比較元素:" +printSortUnit(array, lastIndex-i));
for (int j = 0; j < lastIndex-i; j++) {
if(array[j]>array[j+1]){
swap(array, j, j+1);
}
count++;
System.out.println("第"+count+"次 "+Arrays.toString(array));
}
}
}
這次再Run:
還是15輪,但是次數只用了120次!每一輪的比較元素都在一個一個減少,大大減少了比較次數。
第二次優化
觀察上面的圖片可以發現在第13輪比較的元素爲“2,1,5,6”還沒有拍好,但是第14輪比較的元素爲“1,2,5”,正好是排好序的,所以不管後面還要比較幾輪都可以提前結束了。所以我們需要設置一個flag,如果某一輪比較元素時沒有任何發生交換,說明這個數組已經排好序了,立刻結束。
代碼如下:
/**
* 冒泡算法第二次優化
*/
public static void BubbleSort2(int[] array) {
int count=0;
int round=0;
int lastIndex = array.length-1;
for (int i = 0; i < lastIndex; i++) {
boolean isOver = true;
round++;
System.out.println("******************** 第" + round + "輪 ********************");
System.out.println("比較元素:" +printSortUnit(array, lastIndex-i));
for (int j = 0; j < lastIndex-i; j++) {
if(array[j]>array[j+1]){
swap(array, j, j+1);
isOver=false;
}
count++;
System.out.println("第"+count+"次 "+Arrays.toString(array));
}
if(isOver){
break;
}
}
}
運行結果正好在第14輪後就結束了,應該是數組元素不多的原因吧,所以比第一次優化只少了一輪比較,效果不明顯,但確實是少了,不服氣的小老弟可以多敲幾十個元素再試試。
還能優化嗎?可以!
第三次優化
我們還是從第二次優化的打印結果裏找找看:
不難發現,每一輪我們都只是在一個個的去尋找【比較元素】裏的最大值放在右邊,但是如果【比較元素】裏右邊若干個元素已經排好序了,是不是就可以直接剔除比較了?
打個比方,如上圖。第5輪,【比較元素】中17,22很明顯已經是排好序的最大值,所以只需要比較前面十個元素即可,即比較9次即可。第6、7輪也是一樣的問題。通俗點說只要後面N個元素比較後一直不發生交換,就說明這N個元素都是排好序了,且左邊的元素都比這些值小(如果有大的肯定會通過交換推到右面),所以我們批量剔除這N個元素而不是每一輪只剔除一個最大元素。
因此,我們需要記錄每一輪最後交換元素的位置changeIndex,下一輪直接遍歷array[0]到array[changeIndex]即可,直到整輪一次交換都不發生(第二次優化的目的),就表示排序完成。
代碼如下:
/**
* 冒泡算法第三次優化
*/
public static void BubbleSort3(int[] array) {
int count = 0;
int round = 0;
int lastIndex = array.length - 1;
int changeIndex = lastIndex;
for (int i = 0; i < lastIndex; i++) {
int tempChangeIndex = -1;// 記錄每一輪最後交換元素的下標
round++;
System.out.println("******************** 第" + round + "輪 ********************");
System.out.println("比較元素:" + printSortUnit(array, changeIndex));
for (int j = 0; j < changeIndex; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
tempChangeIndex = j;
}
count++;
System.out.println("第" + count + "次 " + Arrays.toString(array));
}
if (tempChangeIndex <= 0) {
break; // 這裏表示前兩個元素交換或者一次都未交換的情況,均表示排序結束
} else {
changeIndex = tempChangeIndex;
}
}
}
幾處改動:
1.添加一個變量changeIndex,用來控制每輪j從0循環到changeIndex,並在每輪結束後及時更新,給下一輪使用。
2.添加tempChangeIndex變量,在一輪中的每一次交換後更新,一輪全部結束後把這一輪最後一次交換元素的下標值賦給changeIndex變量。
(注:這裏一定要分清changIndex是上一輪確定的值,tempChangeIndex是這一輪通過比較獲得的值,tempChangeIndex一直小於changeIndex,兩者不能混淆)
3.打印“比較元素”的下標也要改成changeIndex,而不是lastIndex-i。
4.因爲tempChangeIndex自身表示交換元素的下標,當一輪比較結束後tempChangeIndex仍爲“-1”時表示一次未交換,替代了之前的isOver==true(tempChangeIndex>=0表示isOver==false),所以可把isOver刪掉了,tempChangeIndex爲“0”時表示只有前兩個元素交換,所以這裏tempChangIndex爲-1或者0都表示整個數組排序結束。代碼最後的if語句就表示這個意思。
結果:
感悟:這一次比第二次優化又少了一輪,一共只比較了97次。反觀最原始的225次確實少了很多,而且這個測試數組只有16個元素,如果是幾萬幾十萬甚至更多的數組元素需要排序的話,效果可想而知。
————————————————————————————————————————————————————————
算法思想總結:
我們根據冒泡算法的原理寫了一個最原始的算法,然後根據結果優化了三次。
原始思想:每次都對N個元素兩兩相鄰進行比較,比較N-1輪,每輪N-1次,排序完成。
第一次優化思想:從次的角度考慮,每一輪都能得到一個元素按順序排在數組右邊,這些排好序的元素不用參與後面的比較,故每一輪結束後剔除右邊一個元素,下一輪只比較剩下的元素即可。
第二次優化思想:從輪的角度考慮,排序不一定要執行完N-1輪,當某一輪結束後未發生任何元素交換就表示已經排好順序,不用繼續比較了。
第三次優化思想:還是從次的角度考慮,是對第一次優化的再優化,第一次優化是一個一個剔除已排好序的元素,而第三次我們通過每輪最後一次交換元素的下標,快速排除已排好序的元素。一輪可剔除一個或多個最大有序元素。