排序算法是一種基本而且經常使用的算法。因爲實際工做中處理的數量巨大,因此排序算法 對算法自己的速度要求很高。 而通常咱們所謂的算法的性能主要是指算法的複雜度,通常用O方法來表示。在後面將給出詳細的說明。《計算機程序設計技巧》(第三卷,排序和查找)
對於排序的算法我想先作一點簡單的介紹,也是給這篇文章理一個提綱。 我將按照算法的複雜度,從簡單到難來分析算法。第一部分是簡單排序算法,後面你將看到他們的共同點是算法複雜度爲O(N*N)(由於沒有使用word,因此沒法打出上標和下標)。第二部分是高級排序算法,複雜度爲O(Log2(N))。這裏咱們只介紹一種算法。另外還有幾種 算法由於涉及樹與堆的概念,因此這裏不於討論。第三部分相似動腦筋。這裏的兩種算法並非最好的(甚至有最慢的),可是算法自己比較奇特,值得參考(編程的角度)。同時也可讓咱們從另外的角度來認識這個問題。如今,讓咱們開始吧:
1、簡單排序算法
因爲程序比較簡單,因此沒有加什麼註釋。全部的程序都給出了完整的運行代碼,並在個人VC環境
下運行經過。由於沒有涉及MFC和WINDOWS的內容,因此在BORLAND C++的平臺上應該也不會有什麼
問題的。在代碼的後面給出了運行過程示意,但願對理解有幫助。
1.冒泡法:(從後向前倒)
這是最原始,也是衆所周知的最慢的算法了。他的名字的由來由於它的工做看來象是冒泡:
#include <iostream.h>
void BubbleSort(int* pData,int Count)
{
int iTemp;
for(int i=1;i<Count;i++)
{
for(int j=Count-1;j>=i;j--)
{
if(pData[j]<pData[j-1])
{
iTemp = pData[j-1];
pData[j-1] = pData[j];
pData[j] = iTemp;
}
}
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
BubbleSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟狀況)
第一輪:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,10,8,9->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其餘:
第一輪:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交換2次)
第二輪:7,8,10,9->7,8,10,9->7,8,10,9(交換0次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
上面咱們給出了程序段,如今咱們分析它:這裏,影響咱們算法性能的主要部分是循環和交換,顯然,次數越多,性能就越差。從上面的程序咱們能夠看出循環的次數是固定的,爲1+2+...+n-1。 寫成公式就是1/2*(n-1)*n。如今注意,咱們給出O方法的定義:
若存在一常量K和起點n0,使當n>=n0時,有f(n)<=K*g(n),則f(n) = O(g(n))。(呵呵,不要說沒 學好數學呀,對於編程數學是很是重要的!!!)
如今咱們來看1/2*(n-1)*n,當K=1/2,n0=1,g(n)=n*n時,1/2* (n-1)*n<=1/2*n*n=K*g(n)。因此f(n) =O(g(n))=O(n*n)。因此咱們程序循環的複雜度爲O(n*n)。再看交換。從程序後面所跟的表能夠看到,兩種狀況的循環相同,交換不一樣。其實交換自己同數據源的有序程度有極大的關係,當數據處於倒序的狀況時,交換次數同循環同樣(每次循環判斷都會交換),複雜度爲O(n*n)。當數據爲正序,將不會有交換。複雜度爲O(0)。亂序時處於中間狀態。正是因爲這樣的緣由,咱們一般都是經過循環次數來對比算法。
2.交換法:
交換法的程序最清晰簡單,每次用當前的元素一一的同其後的元素比較並交換。
#include <iostream.h>
void ExchangeSort(int* pData,int Count)
{
int iTemp;
for(int i=0;i<Count-1;i++)
{
for(int j=i+1;j<Count;j++)
{
if(pData[j]<pData[i])
{
iTemp = pData[i];
pData[i] = pData[j];
pData[j] = iTemp;
}
}
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
ExchangeSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟狀況)
第一輪:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,9,10,8->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其餘:
第一輪:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交換1次)
第二輪:7,10,8,9->7,8,10,9->7,8,10,9(交換1次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
從運行的表格來看,交換幾乎和冒泡同樣糟。事實確實如此。循環次數和冒泡同樣也是1/2* (n-1)*n,因此算法的複雜度仍然是O(n*n)。因爲咱們沒法給出全部的狀況,因此只能直接告訴你們他們在交換上面也是同樣的糟糕(在某些狀況下稍好,在某些狀況下稍差)。
3.選擇法::(從第一個開始和後面的最大的進行比較)
如今咱們終於能夠看到一點但願:選擇法,這種方法提升了一點性能(某些狀況下) 這種方法相似咱們人爲的排序習慣:從數據中選擇最小的同第一個值交換,在從省下的部分中 選擇最小的與第二個交換,這樣往復下去。
#include <iostream.h>
void SelectSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=0;i<Count-1;i++)
{
iTemp = pData[i];
iPos = i;
for(int j=i+1;j<Count;j++)
{
if(pData[j]<iTemp)
{
iTemp = pData[j];
iPos = j;
}
}
pData[iPos] = pData[i];
pData[i] = iTemp;
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
SelectSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟狀況)
第一輪:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7-& gt;(iTemp=7)7,9,8,10(交換1次)
第二輪:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交換1次)
第一輪:7,8,9,10->(iTemp=9)7,8,9,10(交換0次)
循環次數:6次
交換次數:2次
其餘:
第一輪:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9-& gt;(iTemp=7)7,10,8,9(交換1次)
第二輪:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交換1次)
第一輪:7,8,10,9->(iTemp=9)7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
遺憾的是算法須要的循環次數依然是1/2*(n-1)*n。因此算法複雜度爲O(n*n)。咱們來看他的交換。因爲每次外層循環只產生一次交換(只有一個最小值)。因此f(n)<=n 因此咱們有f(n)=O(n)。因此,在數據較亂的時候,能夠減小必定的交換次數。
4.插入法:
插入法較爲複雜,它的基本工做原理是抽出牌,在前面的牌中尋找相應的位置插入,而後繼續下一張
#include <iostream.h>
void InsertSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=1;i<Count;i++)
{
iTemp = pData[i];
iPos = i-1;
while((iPos>=0) && (iTemp<pData[iPos]))
{
pData[iPos+1] = pData[iPos];
iPos--;
}
pData[iPos+1] = iTemp;
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
InsertSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟狀況)
第一輪:10,9,8,7->9,10,8,7(交換1次)(循環1次)
第二輪:9,10,8,7->8,9,10,7(交換1次)(循環2次)
第一輪:8,9,10,7->7,8,9,10(交換1次)(循環3次)
循環次數:6次
交換次數:3次
其餘:
第一輪:8,10,7,9->8,10,7,9(交換0次)(循環1次)
第二輪:8,10,7,9->7,8,10,9(交換1次)(循環2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)(循環1次)
循環次數:4次
交換次數:2次
上面結尾的行爲分析事實上形成了一種假象,讓咱們認爲這種算法是簡單算法中最好的,其實不是,由於其循環次數雖然並不固定,咱們仍可使用O方法。從上面的結果能夠看出,循環的次數f(n)<= 1/2*n*(n-1)<=1/2*n*n。因此其複雜度仍爲O(n*n)(這裏說明一下,其實若是不是爲了展現這些簡單排序的不一樣,交換次數仍然能夠這樣推導)。如今看交換,從外觀上看,交換次數是O(n)(推導相似選擇法),但咱們每次要進行與內層循環相同次數的‘=’操做。正常的一次交換咱們須要三次‘=’ 而這裏顯然多了一些,因此咱們浪費了時間。
最終,我我的認爲,在簡單排序算法中,選擇法是最好的。
2、高級排序算法:
高級排序算法中咱們將只介紹這一種,同時也是目前我所知道(我看過的資料中)的最快的。它的工做看起來仍然象一個二叉樹。首先咱們選擇一箇中間值 middle程序中咱們使用數組中間值,而後把比它小的放在左邊,大的放在右邊(具體的實現是從兩邊找,找到一對後交換)。而後對兩邊分別使 用這個過程(最容易的方法——遞歸)。
1.快速排序:(二分法)
#include <iostream.h>
void run(int* pData,int left,int right)
{
int i,j;
int middle,iTemp;
i = left;
j = right;
middle = pData[(left+right)/2]; //求中間值
do{
while((pData[i]<middle) && (i<right))//從左掃描大於中值的數
i++;
while((pData[j]>middle) && (j>left))//從右掃描大於中值的數
j--;
if(i<=j)//找到了一對值
{
//交換
iTemp = pData[i];
pData[i] = pData[j];
pData[j] = iTemp;
i++;
j--;
}
}while(i<=j);//若是兩邊掃描的下標交錯,就中止(完成一次)
//當左邊部分有值(left<j),遞歸左半邊
if(left<j)
run(pData,left,j);
//當右邊部分有值(right>i),遞歸右半邊
if(right>i)
run(pData,i,right);
}
void QuickSort(int* pData,int Count)
{
run(pData,0,Count-1);
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
QuickSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
這裏我沒有給出行爲的分析,由於這個很簡單,咱們直接來分析算法:首先咱們考慮最理想的狀況
1.數組的大小是2的冪,這樣分下去始終能夠被2整除。假設爲2的k次方,即k=log2(n)。
2.每次咱們選擇的值恰好是中間值,這樣,數組才能夠被等分。
第一層遞歸,循環n次,第二層循環2*(n/2)......
因此共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
因此算法複雜度爲O(log2(n)*n)
其餘的狀況只會比這種狀況差,最差的狀況是每次選擇到的middle都是最小值或最大值,那麼他將變成交換法(因爲使用了遞歸,狀況更糟)。可是你認爲這種狀況發生的概率有多大??呵呵,你徹底沒必要擔憂這個問題。實踐證實,大多數的狀況,快速排序老是最好的。若是你擔憂這個問題,你可使用堆排序,這是一種穩定的O(log2(n)*n)算法,可是一般狀況下速度要慢 於快速排序(由於要重組堆)。ios