簡單氣泡排序的時間複雜度及其兩種優化
在我們寫一些簡單的程式中,對一組資料進行簡單的有序的排列是經常會遇到的,所以我們必須熟悉幾種基本的演算法。
而氣泡排序就是我們學習排序的基礎
氣泡排序:
形象的可以理解為:水中上升的氣泡,在上升的過程中,氣泡越來越小,不斷比較相鄰的元素,將小的往後調
我們來解決這樣一個問題:
設有一陣列,其大小為6個元素(int array[6]),陣列內的資料是(4,2,5,3,2,6),此刻資料是無序的,現要求我們程式設計將其變成一個有序的從大到小的陣列,從下標0開始。
思考:按照題目要求,毫無疑問,最終的有序陣列應該是這樣(6,5,4,3,2,2),要做到這樣,最簡單的辦法就是進行比較交換
1:我們從陣列的第一個元素array[0]向後走,如果比相鄰的小,那麼交換,如此進行下去,可以發現,我們找到了所
有元素中最小的並且已經將它放在了最後一個位置array[5]
2:然後,我們重新從第一個元素向後走,還是相鄰的比較,並且交換,但是我們只需要比較到4,放在array[4]
3:重複第二步,直到比較到1
程式碼:
#include <stdio.h> #include <iostream> using namespace std; void Bubble_sort(int *array, int length) { //變數i,j用於迴圈,變數t用於交換中間值 int i, j, t; for(i = 1; i < length; i++) { //比較的次數越來越少,但是每一個都能在相應的範圍內確定一個最小值並放在合理的位置 for(j = 0; j < length - i ; j++) { //在滿足條件的情況下,交換兩個數 if(array[j] < array[j + 1]) { t = array[j]; array[j] = array[j + 1]; array[j + 1] = t; } } } //將6個數輸出 for(i = 0; i< length; i++) cout<<array[i]<<endl; } int main() { int array[] = {4,2,5,3,2,6}; //定義一個數組並簡單的初始化 int length = sizeof(array)/sizeof(int);//求定義陣列的具體長度 Bubble_sort(array, length); //呼叫相應的排序函式 return 0; }
關於優化:我們必須從時間複雜度和空間複雜度上面來看
好多人一直都在糾結氣泡排序的時間複雜度
在最壞的情況下是:O(N)
還是在最壞的情況下是比較比較次數是:n(n - 1)/ 2
他們兩到底是什麼關係???
其實:總而言之,氣泡排序是一種用時間在換空間的排序。
最壞的情況當然是:每一次的比較都會進行交換,也就是說要把逆序的數列變成順序或者要把順序變成逆序
5,4,3,2,1以冒泡升序排列
1,從第一個數5開始和後面的數進行比較比較到倒數第二個數2為止,過程為:5跟4比較,5比4大,兩者交換,然後
跟3比較,5比3大,繼續進行交換,依次進行比較,比較完成為:4,3,2,1,5
2,從第一個數4開始和後面的數進行比較,過程為:4跟3比較,4比3大,兩者進行交換,然後跟2進行比較,4比2大
,繼續交換,依次比較,比較完成為:3,2,1,4,5
3,同理
所以總的比較次數就是:4 + 3 + 2 + 1
所以,對於n位數的比較次數就是:n + (n - 1)+(n -2)+...+1 = n(n - 1)/ 2
而O(N^2)表示的是複雜度的數量級,舉個例子來說,如果n = 10000,那麼n(n -1 )/2 = (n^2 - n)/ 2
= (100000000 - 10000)/2,相對於10^8來說,10000可以忽略不計,所以總計算次數約為0.5 * N^2.
所以,就用O(N^2)表示了數量級(忽略了前面的係數0.5)
所以,我們可以從趟數上面處理,因為有可能我們比較一定的倘數之後就已經變成了有序的了,沒有必要再去做一些無用的比較了
如:5,1,2,3,4
冒泡一次之後就變成了:1,2,3,4,5已經有序了,我們沒有必要再去比較了
程式:
#include <stdio.h>
#include <iostream>
using namespace std;
void Bubble_sort(int *array, int length)
{
int i, j, t;
int flag; //標誌量,用於指示,數列已然是有序的
for(i = 1; i < length; i++)
{
flag = 1;
for(j = 0; j < length - i ; j++)
{
if(array[j] < array[j + 1]) //合理的交換相鄰的兩個數
{
flag = 0;
t = array[j];
array[j] = array[j + 1];
array[j + 1] = t;
}
}
if(flag == 1) //標誌位
break;
}
for(i = 0; i< length; i++)//輸出排序後的數列
cout<<array[i]<<endl;
}
int main()
{
int array[] = {4,2,5,3,2,6};
int length = sizeof(array)/sizeof(int);
Bubble_sort(array, length);
return 0;
}
這裡我們加入了標誌,flag,如果有一趟排序中沒有產生交換的話,那麼說明此刻數列以及變成了有序的數列
從上面時間複雜度看,時間主要花費在交換上面了,如果轉匯編的話,比較可能只需要一條指令,而交換可能就需要很多指令,所以我們的目的就是減少某一倘的交換次數即可
如下:為了便於講解,我們將上面用於排序的那些數列簡單的改改
4,2,3,2,6,7,8
主要的就是:4,3,2,2(前面的排列),後面就是比較就是:
array[3]跟array[4]比較,2小於6,那麼記錄下來,繼續比較array[3]和array[5],2小於7,繼續比較array[3]和array[6],
2小於8,但是由於8是數列的最後一個所以我們直接交換:
所以,第一次排序的結果就是:4,3,2,8,6,7,2
可以簡單的理解:
就變成這樣了,,,,(理解如上)
#include <stdio.h>
#include <iostream>
using namespace std;
void Bubble_sort(int *array, int length)
{
int i, j, t, k;
int flag; //flag表示減少的倘數
for(i = 1; i < length; i++)
{
flag = 1;
for(j = 0; j < length - i ; j++)
{
if(array[j] < array[j + 1])
{
flag = 0;
k = j+ 1;
while(k < length - i + 1)//確定其有效性
{
if(array[j] >= array[k]) //這裡新增等號,是為了不影響程式的穩定性
{
//交換array[j]與array[k - 1]
t = array[j];
array[j] = array[k - 1];
array[k - 1] = t;
break;
}
if(k == length - i) //如果走到最後了,那麼直接進行交換
{
t = array[j];
array[j] = array[k];
array[k] = t;
break;
}
k++;
}
if(k == length - i) //已經到了末尾了,可以直接退出本次的迴圈了
break;
k = k - 2;
j = k;
}
}
if(flag == 1)
break;
}
//輸出我們排
for(i = 0; i< length; i++)
cout<<array[i]<<endl;
}
int main()
{
//定義一個數組,並給其一個簡單的初始化
int array[] = {4,2,3,2,6,7,8};
//求出陣列的大小
int length = sizeof(array)/sizeof(int);
//呼叫排序函式
Bubble_sort(array, length);
return 0;
}
如上程式:上面的k值就是類似與一個簡單的標誌
關於第三步的優化方式,可能一部分人不太認同,因為對於冒泡而言,必須是跟相鄰的進行比較,然後進行交換,而我上面這個不完全是這樣的,但是,我還是認為,對於程式,重要的是:
時間複雜度,空間複雜度,如果我們能針對當前的數列選擇一個合理的排序方式,那就是極好的!!!
因為,不同的排序,塊排,插入,基數,堆排都有自己最適合的情況,我們不能要求某一個排序必須用某一個排序,是吧,,,,
鄙人的一點簡單看法,,謝謝大家指點,,,小子謝謝啦!!!