A. 幾種排序的時間復雜度排序
從時間復雜度看,所有內部排序方法可以分為兩類。
1.插入排序 選擇排序 起泡排序
其時間復雜度為O(n2);
2.堆排序 快速排序 歸並排序
其時間復雜度為O(nlog2n)。
這是就平均情況而言的,如果從最好的情況考慮,
則插入排序和起泡排序的時間復雜度最好,為O(n),
而其他演算法的最好情況同平均情況大致相同。
如果從最壞的情況考慮,快速排序的時間復雜度為O(n2),插入排序和起泡排序雖然同平均情況相同,但系數大約增加一倍,運行速度降低一半,而選擇排序、堆排序和歸並排序則影響不大。
B. 快速排序演算法(free pascal)詳解,不要源程序,時間復雜度n(logn);謝了//
快速排序是對冒泡排序的一種改進。它的基本思想是:通過一躺排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一不部分的所有數據都要小,然後再按次方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
假設要排序的數組是A[1]……A[N],首先任意選取一個數據(通常選用第一個數據)作為關鍵數據,然後將所有比它的數都放到它前面,所有比它大的數都放到它後面,這個過程稱為一躺快速排序。一躺快速排序的演算法是:
1)、設置兩個變數I、J,排序開始的時候I:=1,J:=N;
2)以第一個數組元素作為關鍵數據,賦值給X,即X:=A[1];
3)、從J開始向前搜索,即由後開始向前搜索(J:=J-1),找到第一個小於X的值,兩者交換;
4)、從I開始向後搜索,即由前開始向後搜索(I:=I+1),找到第一個大於X的值,兩者交換;
5)、重復第3、4步,直到I>j;
詳細過程舉例如下:
原序: [26 5 37 1 61 11 59 15 48 19]
一: [19 5 15 1 11] 26 [59 61 48 37]
二: [11 5 15 1] 19 26 [59 61 48 37]
三: [1 5] 11 [15] 19 26 [59 61 48 37]
四: 1 5 11 [15] 19 26 [59 61 48 37]
五: 1 5 11 15 19 26 [59 61 48 37]
六: 1 5 11 15 19 26 [37 48] 59 [61]
七: 1 5 11 15 19 26 37 48 59 [61]
八: 1 5 11 15 19 26 37 48 59 61
快速排序法是所有排序方法中速度最快、效率最高的方法。程序如下:
var a:array[0..10] of integer;
n:integer;
procere qsort(l,r:longint);{r,l表示集合的左右邊界,即把第r到第l個數進行排序}
var i,j,m:longint;
begin
m:=a[l];{標准數}
i:=l; {I,J為指針}
j:=r;
repeat
while a[i]<m do inc(i);
while a[j]>m do dec(j);
if i<=j then begin
a[0]:=a[i];
a[i]:=a[j];
a[j]:=a[0];
inc(i);
dec(j);
end;
until i>j;
if l<j then qsort(l,j); {如果集合中不止一個數則進入下一層遞歸,l,J為新邊界}
if i<rthen qsort(i,r); {如果集合中不止一個數則進入下一層遞歸,i,r為新邊界}
end;
begin
for n:=1 to 10 do read(a[n]);
qsort(1,10);
for n:=1 to 10 do write(a[n]:4);
end.
C. 設序列長度為n,在最壞的情況下,時間復雜度為O(log2n)的演算法是什麼
二分法
二分法的基本思想如下:
假設數據是按升序排序的,對於給定值x,從序列的中間位置開始比較,如果當前位置值等於x,則查找成功;若x小於當前位置值,則在數列的前半段中查找;若x大於當前位置值則在數列的後半段中繼續查找,直到找到為止。
由於是數組是預先排序好的,所以可以採用折半查詢的方式,每次拋掉待查詢部分的一半
這樣,長度為N的數組,只需要log2N次查詢即可,2是對數的底。
例如,長度為7的數組,最多隻需要3次就可以找到
O(log2n)只是表示是log2N同一數量級,因為有個取整的問題,而且也有可能在查詢過程中就已經找到(也就是某個折半查詢點正好是待查詢數據),這樣O(log2n)就是一個上限
D. 排序演算法的復雜度
由於程序比較簡單,所以沒有加什麼注釋。所有的程序都給出了完整的運行代碼,並在我的VC環境
下運行通過。因為沒有涉及MFC和WINDOWS的內容,所以在BORLAND C++的平台上應該也不會有什麼
問題的。在代碼的後面給出了運行過程示意,希望對理解有幫助。 這是最原始,也是眾所周知的最慢的演算法了。他的名字的由來因為它的工作看來象是冒泡: #include<iostream>usingnamespacestd;voidBubbleSort(int*pData,intCount){intiTemp;for(inti=0;i<Count;i++){for(intj=Count-1;j>i;j--){if(pData[j]<pData[j-1]){iTemp=pData[j-1];pData[j-1]=pData[j];pData[j]=iTemp;}}}}voidmain(){intdata[7]={10,9,8,7,6,5,4};BubbleSort(data,7);for(inti=0;i<7;i++){cout<<data[i]<<;}cout<<endl;system(PAUSE);}倒序(最糟情況)
第一輪: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,9,10->7,8,10,9(交換1次)
(這是原撰寫人的--7,8,10,9->7,8,10,9->7,8,10,9(交換0次),第二輪應該是這樣的)
第三輪:7,8,9,10->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)。亂序時處於中間狀態。正是由於這樣的
原因,我們通常都是通過循環次數來對比演算法。 交換法的程序最清晰簡單,每次用當前的元素一一的同其後的元素比較並交換。 #include<iostream.h>voidExchangeSort(int*pData,intCount){intiTemp;for(inti=0;i<Count-1;i++){//共(count-1)輪,每輪得到一個最小值for(intj=i+1;j<Count;j++){//每次從剩下的數字中尋找最小值,於當前最小值相比,如果小則交換if(pData[j]<pData[i]){iTemp=pData[i];pData[i]=pData[j];pData[j]=iTemp;}}}}voidmain(){intdata[]={10,9,8,7,6,5,4};ExchangeSort(data,sizeof(data)/sizeof(int));for(inti=0;i<sizeof(data)/sizeof(int);i++){cout<<data[i]<<;}cout<<endl;system(PAUSE);}第一輪: 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)。由於我們無法給出所有的情況,所以
只能直接告訴大家他們在交換上面也是一樣的糟糕(在某些情況下稍好,在某些情況下稍差)。 現在我們終於可以看到一點希望:選擇法,這種方法提高了一點性能(某些情況下)
這種方法類似我們人為的排序習慣:從數據中選擇最小的同第一個值交換,在從剩下的部分中
選擇最小的與第二個交換,這樣往復下去。 #include<iostream.h>voidSelectSort(int*pData,intCount){intiTemp;intiPos;for(inti=0;i<Count-1;i++){iTemp=pData[i];iPos=i;for(intj=i+1;j<Count;j++){if(pData[j]<iTemp){iTemp=pData[j];iPos=j;}}pData[iPos]=pData[i];pData[i]=iTemp;}}voidmain(){intdata[]={10,9,8,7,6,5,4};SelectSort(data,7);for(inti=0;i<7;i++)cout<<data[i]<<;cout<<
;}倒序(最糟情況)
第一輪:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(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->(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)。所以,在數據較亂的時候,可以減少一定的交換次數。 插入法較為復雜,它的基本工作原理是抽出牌,在前面的牌中尋找相應的位置插入,然後繼續下一張 #include<iostream.h>voidInsertSort(int*pData,intCount){intiTemp;intiPos;for(inti=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;//插入數字的位置}}voidmain(){intdata[]={10,9,8,7,6,5,4};InsertSort(data,7);for(inti=0;i<7;i++)cout<<data<<;cout<<
;}其他:
第一輪:8,10,7,9->8,10,7,9(交換0次)(循環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)(推導類似
選擇法),但我們每次要進行與內層循環相同次數的『=』操作。正常的一次交換我們需要三次『=』
而這里顯然多了一些,所以我們浪費了時間。
最終,我個人認為,在簡單排序演算法中,選擇法是最好的。 高級排序演算法中我們將只介紹這一種,同時也是目前我所知道(我看過的資料中)的最快的。
它的工作看起來仍然象一個二叉樹。首先我們選擇一個中間值middle程序中我們使用數組中間值,然後
把比它小的放在左邊,大的放在右邊(具體的實現是從兩邊找,找到一對後交換)。然後對兩邊分別使
用這個過程(最容易的方法——遞歸)。
1.快速排序://這段代碼編譯可以通過,一運行就出錯,內部的細節有些問題,我還沒找到解決方法。 #include<iostream.h>voidrun(int*pData,intleft,intright){inti,j;intmiddle,iTemp;i=left;j=right;middle=pData[left];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);}voidQuickSort(int*pData,intCount){run(pData,0,Count-1);}voidmain(){intdata[]={10,9,8,7,6,5,4};QuickSort(data,7);for(inti=0;i<7;i++)cout<<data[i]<<;//原作者此處代碼有誤,輸出因為date[i],date數組名輸出的是地址cout<<
;}這里我沒有給出行為的分析,因為這個很簡單,我們直接來分析演算法:首先我們考慮最理想的情況
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)演算法,但是通常情況下速度要慢
於快速排序(因為要重組堆)。 雙向冒泡
通常的冒泡是單向的,而這里是雙向的,也就是說還要進行反向的工作。 #include<iostream.h>inlinevoidexchange(int*a,int*b){inttemp;temp=*a;*a=*b;*b=temp;}voidbubblesort(int*array,intnum){inti,j,k,flag=0;for(i=0;i<num;i++){printf(%d,array[i]);}printf(
);for(i=0;i<num;i++){//所有數的個數為num個flag=0;for(j=i;j<num-i-1;j++){//每循環一次最底端的數的順序都會排好,所以初始時j=i;if(array[j]>array[j+1]){exchange(&array[j],&array[j+1]);flag=1;}}for(k=num-1-i-1;k>i;k--){//每循環一次最頂端的數據的順序也會排好,所以初始時k=num-i-2if(array[k]<array[k-1]){exchange(&array[k],&array[k-1]);flag=1;}}if(flag==0){//如果flag未發生改變則說明未發生數據交換,則排序完成return;}}}voidmain(){intdata[]={10,9,8,7,6,5,4,3,2,1,-10,-1};bubblesort(data,12);for(inti=0;i<12;i++)cout<<data<<;cout<<
;} 這個程序我想就沒有分析的必要了,大家看一下就可以了。不明白可以在論壇上問。
MyData.h文件
///////////////////////////////////////////////////////
class CMyData
{
public:
CMyData(int Index,char* strData);
CMyData();
virtual ~CMyData();
int m_iIndex;
int GetDataSize(){ return m_iDataSize; };
const char* GetData(){ return m_strDatamember; };
//這里重載了操作符:
CMyData& operator =(CMyData &SrcData);
bool operator <(CMyData& data );
bool operator >(CMyData& data );
private:
char* m_strDatamember;
int m_iDataSize;
};
////////////////////////////////////////////////////////
MyData.cpp文件
////////////////////////////////////////////////////////
CMyData::CMyData():
m_iIndex(0),
m_iDataSize(0),
m_strDatamember(NULL)
{
}
CMyData::~CMyData()
{
if(m_strDatamember != NULL)
delete[] m_strDatamember;
m_strDatamember = NULL;
}
CMyData::CMyData(int Index,char* strData):
m_iIndex(Index),
m_iDataSize(0),
m_strDatamember(NULL)
{
m_iDataSize = strlen(strData);
m_strDatamember = new char[m_iDataSize+1];
strcpy(m_strDatamember,strData);
}
CMyData& CMyData::operator =(CMyData &SrcData)
{
m_iIndex = SrcData.m_iIndex;
m_iDataSize = SrcData.GetDataSize();
m_strDatamember = new char[m_iDataSize+1];
strcpy(m_strDatamember,SrcData.GetData());
return *this;
}
bool CMyData::operator <(CMyData& data )
{
return m_iIndex<data.m_iIndex;
}
bool CMyData::operator >(CMyData& data )
{
return m_iIndex>data.m_iIndex;
}
///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
//主程序部分
#include <iostream.h>
#include MyData.h
template <class T>
void run(T* pData,int left,int right)
{
int i,j;
T middle,iTemp;
i = left;
j = right;
//下面的比較都調用我們重載的操作符函數
middle = pData[(left+right)/2]; //求中間值
do{
while((pData<middle) && (i<right))//從左掃描大於中值的數
i++;
while((pData[j]>middle) && (j>left))//從右掃描大於中值的數
j--;
if(i<=j)//找到了一對值
{
//交換
iTemp = pData;
pData = 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);
}
template <class T>
void QuickSort(T* pData,int Count)
{
run(pData,0,Count-1);
}
void main()
{
CMyData data[] = {
CMyData(8,xulion),
CMyData(7,sanzoo),
CMyData(6,wangjun),
CMyData(5,VCKBASE),
CMyData(4,jacky2000),
CMyData(3,cwally),
CMyData(2,VCUSER),
CMyData(1,isdong)
};
QuickSort(data,8);
for (int i=0;i<8;i++)
cout<<data.m_iIndex<< <<data.GetData()<<
;
cout<<
;
E. 數組排序的最少時間復雜度O(nlog2n)怎麼計算的
for(int j=1; j<=n; j*=2)
這個循環最終執行的次數假設為x,則x次的時候j=2^x 。
當j>n時停止執行,於是2^x>n ,則可以認為該循環一共執行了log2(n)次。
所以該循環的時間復雜度為o(log2(n)),簡記為o(log n) ,忽略掉2的底數。
F. 原地歸並排序時間復雜度
原地歸並排序時間復雜度為O(nlog2n).
一般在提到Merge Sort時,大家都很自然地想到Divide-and-Conqure, O(n lgn)的時間復雜度以及額外的O(n)空間。O(n)的extra space似乎成了Merge Sort最明顯的缺點,但實際上這一點是完全可以克服的,也就是說,我們完全可以實現O(n lgn) time 以及 O(1) space 的Merge Sort。對於這種不用額外空間(即常數大小的額外空間)的演算法,有一個通用的名字叫做In-place Algorithms,因此我們稱該歸並演算法為in-place merge sort,也就是原地歸並排序。
G. C 語言快速排序最好情況時間復雜度為什麼是 nlog2n (菜鳥在線)
快速排序最好的情況是每次把上一次的數組平均分成兩個子數組。設數組總數一共為n,如果把這n個數每次分成2半最後每個數組只包含一個元素,假設要分k次,則2的k次方=n,解得k=log2 n(log以2為底對n取對數).也就是說要分log2 n次,而每次都是處理n個數據。所以總的時間復雜度為O(n*log2 n)。
H. 演算法的時間復雜度
時間復雜度的表示: O(執行次數)
一個有序的元素列表查找某個元素可以用二分查找,每次取中間元素進行比較大小,直到相等。因為每次不符合時總會排除一半的元素 ,所以查找的次數為log2n,那麼時間復雜度為O(log2n)。如果是一個無序的元素列表,查找從位置0開始,那麼簡單查找的次數為n,那麼時間復雜度為O(n)。
除此之外快速排序為O(n*log2n),選擇排序為O(n*n)。
旅行演算法就是n個旅行地點,你可從某個地方出發到餘下某下一個地點,走完所有地點。從最開始時走有n個地點可以選擇,接下來再走就有n-1個地點可以選擇,這樣直到只有一個地點可以選擇。那麼所有你可走的路徑就是一個階乘,選擇復雜度為O( n!)。
關於數組和鏈表的操作。先說數組,因為你有了元素的索引,可以隨機訪問,你就能快速找到這個元素,而且所有元素的讀取都是一樣的步驟,所以讀取時間復雜度為O(1),數組的插入和刪除的時間復雜度為O(n),因為要移動元素。鏈表的特性是每個都存儲了下一個元素的地址,只能順序訪問。那麼讀取插入刪除的時間復雜度分別是O(n)、O(1)、O(1)。
I. 誰能給一個時間復雜度是O(log2n)的演算法,謝謝。
(1)時間頻度
一個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。並且一個演算法花費的時間與演算法中語句的執行次數成正比例,哪個演算法中語句執行次數多,它花費時間就多。一個演算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。
(2)時間復雜度
在剛才提到的時間頻度中,n稱為問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時我們想知道它變化時呈現什麼規律。為此,我們引入時間復雜度概念。
一般情況下,演算法中基本操作重復執行的次數是問題規模n的某個函數,用T(n)表示,若有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值為不等於零的常數,則稱f(n)是T(n)的同數量級函數。記作T(n)=O(f(n)),稱O(f(n)) 為演算法的漸進時間復雜度,簡稱時間復雜度。
在各種不同演算法中,若演算法中語句執行次數為一個常數,則時間復雜度為O(1),另外,在時間頻度不相同時,時間復雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不同,但時間復雜度相同,都為O(n2)。
按數量級遞增排列,常見的時間復雜度有:
常數階O(1),對數階O(log(2)n),線性階O(n),
線性對數階O(nlog(2)n),平方階O(n^2),立方階O(n^3),...,
k次方階O(n^k),指數階O(2^n)。隨著問題規模n的不斷增大,上述時間復雜度不斷增大,演算法的執行效率越低。