“冒泡排序”是數列排序的算法之一
其思路引點來源於經典的“可樂雪碧問題”
“現有兩杯飲料,一杯是雪碧,一杯是可樂,試問如何可以將兩杯飲料交換?”
“答:最簡單的解決方案就是利用一個空杯,創造一個緩存區。”
冒泡排序就是利用不斷的對比、交換數據,從而實現對數列的排序。換言之,就是不斷的拿起兩個數,進行比較,如果是升序排序,就把小的放在前面,同理,降序排序,就將大的放在前面,不斷的有序的重複這個過程,就會將數列排序整齊,這種方法類似小孩子的思考方式,其特點就爲思維簡單、比較費時。
-
經典誤區
對於初次接觸到冒泡排序的人,在大致瞭解到該排序方法後,心裏會油然而生一種感覺—這個算法也太簡單了,交換循環就完事了。實則,實踐起來的時候,你就會發現:循環幾次?怎麼去比較?有必要有多少個數就循環幾次嗎?算法是一種優化思想,對於無用的重複的操作如果能避免掉,那就都要避免掉。試想:
按照一個數就循環一次,不斷全部比較換位的想法,如果有數列 {4 5 3 2 1} ,經過一次升序換位,就變成了 {4 5 3 2 1}->{1 4 5 3 2},這時就進行了4次比較。再來,{1 4 5 3 2}->{1 2 4 5 3}->{1 2 3 4 5},我們不難發現,到這裏經過12次比較就可以對這個數列進行升序排序的完成,但,如果我們算法將其設計爲簡單的循環5次,那麼我們就會進行20次比較,其中有8次,在做無用的比較。這是因爲,冒泡排序中有個隱藏的規律:**如果是升序,進行比較換位,在一次循環後,就會將最小的數放在首位,再一次循環後,次小的就會放在第二位,以此類推…就實現了升序排序。**所以我們有必要去避免做這“8次”無用的比較,其思路很簡單。
-
算法思路
我們先看一下動畫演示:
通過動畫演示,我們不難發現,爲了避免無用的比較,我們會在一次循環後將較小的數定位,並在下一次循環時不予考慮,因爲實際上它已經完成了它的排序,並且,循環的次數明顯也因此在減少,這纔是我們平時常說的“冒泡循環”。 -
代碼清單及測試結果
#include <iostream>
template <class T>
int getSizeOfArray(T& bs){
return sizeof(bs)/ sizeof(bs[0]);
}
void bubbleSort(int * bs,int size){
for(int i = 0;i<=size-1;i++){
for(int j = size-1;j>i;j--){
if(bs[j]<bs[j-1])
{
int cup = bs[j-1];
bs[j-1] = bs[j];
bs[j] = cup;
}
}
}
}
int main() {
using namespace std;
int bs[] = {2,3,5,1,0,8,6,9,7};
int size = getSizeOfArray(bs);
cout<< "原數列:";
for(int i = 0;i<size;i++)
{
cout<< bs[i] << " ";
}
cout<< "\n" << "冒泡排序後:";
bubbleSort(bs,size);
for(int i = 0;i<size;i++)
{
cout<< bs[i] << " ";
}
return 0;
}
演示結果:
- 算法分析
時間複雜度
若文件的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數 和記錄移動次數 均達到最小值: , 。
所以,冒泡排序最好的時間複雜度爲 。
若初始文件是反序的,需要進行 趟排序。每趟排序要進行次關鍵字的比較(1≤i≤n-1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:
冒泡排序的最壞時間複雜度爲 。
綜上,因此冒泡排序總的平均時間複雜度爲 。
算法穩定性
冒泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,是不會再交換的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定排序算法。