作者:hustwing hustwing@126.com MSN: hustwing@hotmail.com
冒泡排序算是排序演算法中比較有名的了,初學排序之時,我們經常拿它來開刀,大學時學習C語言,老師講的第一個排序演算法就是冒泡。然而它的效率低也是眾所周知的。只要說起冒泡,大家無不皺起眉頭,彷彿用了冒泡就說明你這個人完全沒有考慮效率。但是,其實又有多少人真的理解冒泡呢?
我之前根本沒有準備在Algorithm裡專門為冒泡寫一篇文章,但是當我發覺我需要大概大半天的時間才能弄懂它時,我才覺得還是有必要把我所學的關於冒泡演算法的知識總結一下。
起初,我搞混了冒泡排序跟選擇排序,後來到網上一查,發現大概有一半的人跟我一樣弄混了這兩種演算法。下面是這兩種演算法的基本思想:
選擇法排序(升序)
基本思想:
1)對有n個數的序列(存放在數組a(n)中),從中選出最小的數,與第1個數交換位置;
2)除第1 個數外,其餘n-1個數中選最小的數,與第2個數交換位置;
3)依次類推,選擇了n-1次後,這個數列已按升序排列。
冒泡法排序(升序)
基本思想:(將相鄰兩個數比較,小的調到前頭)
1)有n個數(存放在數組a(n)中),第一趟將每相鄰兩個數比較,小的調到前頭,經n-1次兩兩相鄰比較後,最大的數已“沉底”,放在最後一個位置,小數上升“浮起”;
2)第二趟對餘下的n-1個數(最大的數已“沉底”)按上法比較,經n-2次兩兩相鄰比較後得次大的數;
3)依次類推,n個數共進行n-1趟比較,在第j趟中要進行n-j次兩兩比較。
從上面的基本思想可以看出,冒泡法與選擇法最大的不同是,冒泡法是相鄰的數比較交換,而選擇法則是其它所有的數與某個特定位置的數比較交換。反映到代碼上就是下面的區別;
---------------------------------簡單選擇排序-------------------------------------
void SimpleSelectSort(int* const arr,int arrLength)
{
int i,j,imin,temp;
for (i = 0; i {
for (j = i+1; j {
if (arr[i] > arr[j])//注意這一行,非相鄰比較,選擇排序
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
---------------------------------簡單冒泡排序-------------------------------------
void SimpleBubbleSort(int* const arr,int arrLength)
{
int i,j,temp;
for (i = 0; i {
for (j = 0;j {
if (arr[j] > arr[j+1])//注意這一行,是相鄰比較
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
------------------------------------------------------------------------------------------
為其正名之後,我們再來看看它還有些什麼變種,呵呵。按照其基本思想,我們可以這樣設想:設若是升序排列,那我們就有兩種選擇,一種是兩兩相鄰比較,選擇每次將最小的數"冒"到最左邊來(數組的最前端),這叫做前向冒泡或者正向冒泡!也可以選擇每次將最大的數"冒"到最右邊去,這個相應叫做後向冒泡或反向冒泡。當然,聰明人還想到了第三種方法,那就是兩邊同時"冒",一次冒出一個最大的和最小的,然後再一次冒出一個次大的和次小的(雙向冒泡!)!事實證明這的確是個不錯的想法,至少從效率上說是這樣。
下面是這三種冒泡演算法的具體實現代碼,代碼裡包含了與效能比較有關代碼,只有迴圈那部分的代碼(粗體)是冒泡演算法的具體實現,在文章的最後,給出關於這三種演算法的效能測試,當然,這個資料是我自己測試得出,不見得絕對客觀的。
--------------------------------------------------------------------三種冒泡演算法---------------------------------------------------------------------
SORT_PERFORMANCE ForwardBubbleSort(int* const arr,int arrLength)
{
int i,j,temp;
bool flag = false;
unsigned int circuTimes = 0,swapTimes = 0;
clock_t start,end;
SORT_PERFORMANCE sort_perform;
start = clock();
for (i = 0; i {
for (j = arrLength - 1;j > i; j--)
{
circuTimes++;
if (arr[j] {
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
flag = true;
swapTimes++;
}
}
if (!flag)
{
break;
}
}
end = clock();
sort_perform.CircuTimes = circuTimes;
sort_perform.MilliSeconds = end - start;
sort_perform.SwapTimes = swapTimes;
return sort_perform;
}
SORT_PERFORMANCE BackwardBubbleSort(int* const arr,int arrLength)
{
int i,j,temp;
bool flag = false;
unsigned int circuTimes = 0,swapTimes = 0;
clock_t start,end;
SORT_PERFORMANCE sort_perform;
start = clock();
for (i = 0; i {
for (j = 0;j {
circuTimes++;
if (arr[j] > arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
swapTimes++;
}
}
if (!flag)
{
break;
}
}
end = clock();
sort_perform.CircuTimes = circuTimes;
sort_perform.MilliSeconds = end - start;
sort_perform.SwapTimes = swapTimes;
return sort_perform;
}
SORT_PERFORMANCE DoubleDirectionBubbleSort(int* const arr,int arrLength)
{
int temp;
int left = 0;
int right =arrLength -1;
int t = 0,i;
unsigned int circuTimes = 0,swapTimes = 0;
clock_t start,end;
SORT_PERFORMANCE sort_perform;
start = clock();
do
{
//先正向冒泡
for (i = right; i > left; i--)
{
circuTimes++;
if (arr[i-1] > arr[i] )
{
temp = arr[i -1];
arr[i -1] = arr[i];
arr[i] = temp;
t = i - 1;
swapTimes++;
}
}
left = t +1;
//然後反向冒泡
for (i = left; i {
circuTimes++;
if (arr[i] > arr[i+1])
{
temp = arr[i+1];
arr[i+1] = arr[i];
arr[i] = temp;
t = i + 1;
swapTimes++;
}
}
right = t - 1;
} while(left end = clock();
sort_perform.CircuTimes = circuTimes;
sort_perform.MilliSeconds = end - start;
sort_perform.SwapTimes = swapTimes;
return sort_perform;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------
經過測試可以發現這三種冒泡方式的效能資料如下,其中引用的資料都是基於原生,當然有些是不用測試也能猜到的,其中還有點比較有趣的地方:
1.正向冒泡和反向冒泡的效能是一樣的,這一點我想不用測試也可以瞭解。
2.雙向冒泡的效能要比前兩種好一些,但是這種好處要在資料量比較大的時候才明顯(5000個以上),在執行時間上可以比前兩種快大概1/4,但是它的這種快並不想我在網上查到的資料所說的交換次數較少。而恰恰相反,交換次數與前兩種一樣,但是迴圈次數會比前兩種冒泡減少1/3左右,時間就是從迴圈次數減少這裡獲得的。
3.雙向冒泡一個比較有趣的地方是,當資料趨於有序時,它的效率也越來越高。2中的結論都是基於亂序(random)的數組測試得出的結論。當數組越來越有序時,它的迴圈次數較快地減少,所以效率也越來越高。但是當數組絕對有序時,它的迴圈次數是前兩種的2倍!交換次數都是0。我測試過,當數組中只有一個元素無序時,它可以將迴圈次數節省99%以上(交換次數不變)!從而可以幾乎100%的節省時間!但是一旦絕對有序,它的迴圈次數就上升到兩倍!彷彿是物極必反,o(∩_∩)o...哈哈!
文章參考連結:http://www.zxbc.cn/html/20070425/7523.html
可以到這裡下載本文的所有代碼,包含效能測試部分(BubbleSort.cpp):http://download.csdn.net/source/590585