引言
也許冒泡排序,一個剛出大學的的程序員可能寫的出來,反而工作了幾年的老程序員可能會寫不出來,你還寫的出來麼?在本篇博文中,詳細介紹了冒泡排序的概念,同時用數組和雙向鏈表來實現,附帶一種通俗的優化方法。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290
技術點
1、冒泡排序
它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成
之所以稱這種排序方式爲冒泡,是因爲大的元素就表示它的氣泡比較大,慢慢的就會按氣泡大小,把數據浮上來。
2、排序過程
假設要對如下數據進行排序:
目標待排序數據:4,6,3,1,8
排序次數 | 4,6,3,1,8 | 交換情況 | 輪數量 |
---|---|---|---|
第1次交換 | 4,6,3,1,8 | 4,6進行比較,發現6比4大,不需要交換 | 第一輪 |
第2次交換 | 4,3,6,1,8 | 6,3進行比較,發現3比6小,進行交換 | 第一輪 |
第3次交換 | 4,3,1,6,8 | 6,1進行比較,發現1比6小,進行交換 | 第一輪 |
第4次交換 | 4,3,1,6,8 | 6,8進行比較,發現8比6大,不需要交換 | 第一輪結束,最大的8已經冒泡到最上面 |
第5次交換 | 3,4,1,6,8 | 4,3進行比較,發現3比4小,進行交換 | 第二輪 |
第6次交換 | 3,1,4,6,8 | 4,1進行比較,發現1比4小,進行交換 | 第二輪 |
第7次交換 | 3,1,4,6,8 | 4,6進行比較,發現6比4大,不需要交換 | 第二輪結束,第二大的6已經冒泡到倒數第二位 |
第8次交換 | 1,3,4,6,8 | 3,1進行比較,發現1比3小,進行交換 | 第三輪 |
第9次交換 | 1,3,4,6,8 | 3,4進行比較,發現4比3大,不需要交換 | 第三輪結束,第三大的4已經冒泡到倒數第三位 |
第10次交換 | 1,3,4,6,8 | 1,3進行比較,發現3比1大,不需要交換 | 第四輪結束,排序完成 |
上面就是一次完整的冒泡排序的歷程,我們分析一下:
①在上面的目標數據中,有5個數據,會進行4輪排序,所以對於N個數據的排序,必然進行N-1輪的遍歷,這表示一個for循環,肯定是for(int i=0, i < target.length; i++)。
②在每一輪中會有遞減的判斷相鄰兩者的大小關係,如果關係不符合就進行交換。爲什麼是遞減的呢?因爲每一輪中都會把最大的冒泡的最後面,所以不必去判斷已經有序的數據。這就表示是另外一個for循環,肯定是for(int j = 0; j< target.length - i; j++)。所以冒泡排序的核心肯定是一個嵌套for循環和相鄰數據的位置交換。
3、算法分析
通過上面的分析,算法可能是for循環中嵌套一個for循環。假如說目標數據本身就是有序的,那麼只需要遍歷一次即可完成排序(一遍之後發現不需要交換就是有序的),那麼這個時候時間複雜度就是O(N);如果目標數據是反序的,那麼需要遍歷N-1遍,同時在每一遍的遍歷中需要一直交換相鄰位置,這個時候的時間複雜度就是O(N^2)。
數組實現冒泡排序
我們就用上面提到的目標數據:4,6,3,1,8來進行排序。一下是一般的數組實現:
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 關於類BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort {
/**
* 數組實現經典冒泡排序
*/
public static<T extends Comparable<? super T>> void arrBubbleSort(T[] targerArr){
for (int i = 0; i < targerArr.length; i++) {//排序趟數
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的數據比較中,判斷和進行相鄰兩者交換
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右邊的數據比左邊的小,那麼就需要進行交換
//下面開始兩者交換
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
System.out.println("目前第"+(i+1)+"輪,當前數組排序結果: "+result);
}
}
}
public static void main(String[] args) {
Integer[] targer = {4,6,3,1,8};
arrBubbleSort(targer);
}
}
//運行結果:
//目前第1輪,當前數組排序結果: 4 6 3 1 8
//目前第1輪,當前數組排序結果: 4 3 6 1 8
//目前第1輪,當前數組排序結果: 4 3 1 6 8
//目前第1輪,當前數組排序結果: 4 3 1 6 8
//目前第2輪,當前數組排序結果: 3 4 1 6 8
//目前第2輪,當前數組排序結果: 3 1 4 6 8
//目前第2輪,當前數組排序結果: 3 1 4 6 8
//目前第3輪,當前數組排序結果: 1 3 4 6 8
//目前第3輪,當前數組排序結果: 1 3 4 6 8
//目前第4輪,當前數組排序結果: 1 3 4 6 8
在代碼中,我用泛型來拓展了可以實現排序的對象,只要該對象重寫了compareTo方法都可以用上面的方法進行排序。可以看到在我們自己寫的冒泡排序每次操作的結果和我們上面的分析是一模一樣的。至此,經典的數組型冒泡排序就結束了。
我們對於上面的代碼進一步分析,如果在排序的過程中,存在一直不發生交換的情況,也就是說數據本身就是有序的,但是對於上面的代碼來說,即使是有序的仍舊需要一次次進行遍歷和判斷,所以我們試想:
如果在某一輪的遍歷當中,如果沒有發生位置交換,也就是if中沒有爲true的判斷,那麼我們就可以肯定數據已經排序完畢,結束循環。
冒泡排序的簡單優化
下面就是對這種情況的一種優化:
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 關於類BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort {
/**
* 數組實現經典冒泡排序
*/
public static<T extends Comparable<? super T>> void arrBubbleSort(T[] targerArr){
// 設立一個表示位,如果在某一趟的遍歷中一直沒有發生位置交換,表示數據排序已結束
boolean flag;
for (int i = 0; i < targerArr.length; i++) {//排序趟數
flag = true;//每次操作前都設置爲true
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的數據比較中,判斷和進行相鄰兩者交換
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右邊的數據比左邊的小,那麼就需要進行交換
//下面開始兩者交換
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
//如果發生了位置交換,那麼就把標誌位設爲false
flag = false;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
System.out.println("目前第"+(i+1)+"輪,當前數組排序結果: "+result);
}
if(flag){
System.out.println("在第" +(i + 1)+"輪中,未發生位置交換,排序已結束,循環結束");
break;
}
}
}
public static void main(String[] args) {
Integer[] targer = {4,1,3,6,8};
arrBubbleSort(targer);
}
}
//輸出:
//目前第1輪,當前數組排序結果: 1 4 3 6 8
//目前第1輪,當前數組排序結果: 1 3 4 6 8
//目前第1輪,當前數組排序結果: 1 3 4 6 8
//目前第1輪,當前數組排序結果: 1 3 4 6 8
//目前第2輪,當前數組排序結果: 1 3 4 6 8
//目前第2輪,當前數組排序結果: 1 3 4 6 8
//目前第2輪,當前數組排序結果: 1 3 4 6 8
//在第2輪中,未發生位置交換,排序已結束,循環結束
在上述的代碼中,對目標數據{4,1,3,6,8}進行排序,我們發現其實除了4,其他的都是有序的,那麼在第一次排序結束之後,數據已經是有序的了。如果不進行優化,那麼它還是會進行4輪排序,但是如上面代碼的優化方式,在第二輪執行結束,發現沒有發生位置交換,那麼就排序結束。
嘗試用雙鏈表實現冒泡排序
package com.brickworkers;
import java.awt.Point;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 關於類BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort<T> {
private static class Node<T>{
T data;
Node<T> prev;
Node<T> next;
public Node(T data) {
this.data = data;
}
public Node(T data, Node<T> pr, Node<T> ne) {
this.data = data;
this.prev = pr;
this.next = ne;
}
}
/**
* 用雙向鏈表來實現冒泡排序
*/
public static<T extends Comparable<? super T>> void LinkedBubbleSort(T[] targerLinked){//入參還是用數組,不過需要進行轉換,我們不考慮轉化時間
//先定義兩個不計算的頭結點和尾節點
Node<T> start = new Node<T>(null, null, null);
Node<T> end = new Node<T>(null, start, null);
start.next = end;//建立兩者關係
int size = 0; //給雙向鏈表定義個長度
//把數組轉化爲linked
for (T t : targerLinked) {
end.prev = end.prev.next = new Node<T>(t, end.prev, end);//把數據按照數組順序插入到雙向鏈表尾部
size++ ;
}
//進行冒泡排序
//定義一個指針,標誌目前操作的元素,去除我們設定的頭節點
long startTime = System.currentTimeMillis();
Node<T> pointer = start.next;
for(int i = 0; i < size; i++){
//指針歸位
pointer = start.next;
for (int j = 0; j < size - i -1; j++) {
if(pointer.data.compareTo(pointer.next.data) > 0){//如果左邊的數據比右邊要大,那麼進行交換
//處理當前節點的上個節點的指向問題
pointer.prev.next = pointer.next;
//處理比較節點的下個節點
pointer.next.next.prev = pointer;
//處理比較節點的prev
pointer.next.prev = pointer.prev;
//處理point的prev
pointer.prev = pointer.next;
//處理pointer的next
pointer.next = pointer.next.next;
//處理比較節點的next
pointer.prev.next = pointer;
//節點已經被交換,那麼pointer節點需要重新指向新的節點
pointer = pointer.prev;
}
String result = "";
Node<T> temp = start.next;
for (int k = 0; k < size; k++) {
result += temp.data+" ";
temp = temp.next;
}
// System.out.println("目前第"+(i+1)+"輪,當前數組排序結果: "+result);
//指針後移動
pointer = pointer.next;
}
}
System.out.println("雙向鏈表冒泡排序總耗時:"+(System.currentTimeMillis() - startTime));
}
/**
* 數組實現經典冒泡排序
*/
public static<T extends Comparable<? super T>> void arrBubbleSort(T[] targerArr){
// 設立一個表示位,如果在某一趟的遍歷中一直沒有發生位置交換,表示數據排序已結束
long startTime = System.currentTimeMillis();
for (int i = 0; i < targerArr.length; i++) {//排序趟數
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的數據比較中,判斷和進行相鄰兩者交換
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右邊的數據比左邊的小,那麼就需要進行交換
//下面開始兩者交換
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
// System.out.println("目前第"+(i+1)+"輪,當前數組排序結果: "+result);
}
}
System.out.println("數組冒泡排序總耗時:"+(System.currentTimeMillis() - startTime));
}
public static void main(String[] args) {
Integer[] targer = {4,3,1,6,2,9,7,978,55,54,5,6,1,7,98,4,45,32,56,6,9,5,4,45,85};
LinkedBubbleSort(targer);
arrBubbleSort(targer);
}
}
原理和數組實現一樣,在比較的過程中交換相鄰的兩者。所以在雙向鏈表的實現中,我是用交換兩個節點位置來實現的,其實如果交換值能否也算是冒泡排序呢?這個我也不知道。我單純的考慮從for循環開始來計算兩者的運行時間,不過從運行時間結果來說還是數組效果會更好一些,即使把其中的節點交換改成值交換也還是數組效果會更好。
尾記
我也不知道用雙向鏈表來實現它有沒有意義。應該有點意義吧?最起碼自己又熟悉了一邊底層的結構,不是麼。