資料結構-各類排序演算法總結[續],資料結構演算法
各類排序演算法總結三.交換類排序[接上]
2.快速排序
快速排序是通過比較關鍵碼、交換記錄,以某個記錄為界(該記錄稱為支點),將待排序列分成兩部分。其中,一部分所有記錄的關鍵碼大於等於支點記錄的關鍵碼,另一部分所有記錄的關鍵碼小於支點記錄的關鍵碼。我們將待排序列按關鍵碼以支點記錄分成兩部分的過程,稱為一次劃分。對各部分不斷劃分,直到整個序列按關鍵碼有序.
如果每次劃分對一個元素定位後,該元素的左側子序列與右側子序列的長度相同,則下一步將是對兩個長度減半的子序列進行排序,這是最理想的情況!
【演算法如下】
//偽碼錶示//一趟快速排序演算法:int Partition1 (Elem R[], int low, int high){ pivotkey = R[low].key; // 用子表的第一個記錄作樞軸記錄 while (low<high) // 從表的兩端交替地向中間掃描 { while (low<high && R[high].key>=pivotkey) { --high; } R[low]←→R[high]; // 將比樞軸記錄小的記錄交換到低端 while (low<high && R[low].key<=pivotkey) { ++low; } R[low]←→R[high]; // 將比樞軸記錄大的記錄交換到高端 } return low; // 返回樞軸所在位置}
容易看出,調整過程中的樞軸位置並不重要,因此,為了減少記錄的移動次數,應先將樞軸記錄“移出”,待求得樞軸記錄應在的位置之後(此時low=high),再將樞軸記錄到位。
將上述“一次劃分”的演算法改寫如下:
int Partition2 (Elem R[], int low, int high){ R[0] = R[low]; // 用子表的第一個記錄作樞軸記錄 pivotkey = R[low].key; // 樞軸記錄關鍵字 while (low < high) // 從表的兩端交替地向中間掃描 { while (low<high && R[high].key>=pivotkey) { --high; } R[low] = R[high]; // 將比樞軸記錄小的記錄移到低端 while (low<high && R[low].key<=pivotkey) { ++low; } R[high] = R[low]; // 將比樞軸記錄大的記錄移到高端 } R[low] = R[0]; // 樞軸記錄到位 return low; // 返回樞軸位置}
//遞迴形式的快速排序演算法:void QSort (Elem R[], int low, int high){ // 對記錄序列R[low..high]進行快速排序 if (low < high-1) // 長度大於1 { pivotloc = Partition(L, low, high); // 將L.r[low..high]一分為二 QSort(L, low, pivotloc-1); // 對低子表遞迴排序,pivotloc 是樞軸位置 QSort(L, pivotloc+1, high); // 對高子表遞迴排序 }}void QuickSort(Elem R[], int n) // 對記錄序列進行快速排序{ QSort(R, 1, n);}
【效能分析】
(1)空間效率:快速排序是遞迴的,每層遞迴調用時的指標和參數均要用棧來存放,遞迴調用層次數與上述二叉樹的深度一致。因而,儲存開銷在理想情況下為O(log2n),即樹的高度;在最壞情況下,即二叉樹是一個單鏈,為O(n)。
(2)時間效率:在n 個記錄的待排序列中,一次劃分需要約 n 次關鍵碼比較,時效為O(n),若設T(n)為對 n 個記錄的待排序列進行快速排序所需時間。理想情況下:每次劃分,正好將分成兩個等長的子序列,則
T(n)≤cn+2T(n/2)c 是一個常數≤cn+2(cn/2+2T(n/4))=2cn+4T(n/4)≤2cn+4(cn/4+T(n/8))=3cn+8T(n/8)······≤cnlog2n+nT(1)=O(nlog2n)
可以證明,QuickSort的平均計算也是O(nlog2n).
最壞情況下:即每次劃分,只得到一個子序列,時效為O(n^2)。
快速排序是通常被認為在同數量級O(nlog2n)的排序方法中平均效能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以“三者取中法”來選取支點記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼置中的調整為支點記錄。
(3)快速排序是一個不穩定的排序方法.
(4)最慘情況:空間複雜度->O(n),時間複雜度->O(n^2)
平均情況:空間複雜度->O(log2n),時間複雜度->O(nlog2n)
(5)快速排序比較適用於輸入規模n較大的情況.
四.選擇類排序
1.選擇排序
簡單選擇排序是最簡單的一種選擇類的排序方法。假設排序過程中,待排記錄序列的狀態為:
並且有序序列中所有記錄的關鍵字均小於無序序列中記錄的關鍵字,則第i 趟簡單選擇排序是,從無序序列R[i..n]的n-i+1 記錄中選出關鍵字最小的記錄加入有序序列。
操作方法:第一趟,從n 個記錄中找出關鍵碼最小的記錄與第1 個記錄交換;第二趟,從第二個記錄開始的n-1 個記錄中再選出關鍵碼最小的記錄與第2 個記錄交換;如此,第i趟,則從第i 個記錄開始的n-i+1 個記錄中選出關鍵碼最小的記錄與第 i 個記錄交換,直到整個序列按關鍵碼有序。
【演算法如下】
//C++代碼int selectMinIndex(int *A,int index,int length){ int min = index; for (int i = index+1; i != length; ++i) { if (A[i] < A[min]) { min = i; } } return min;}void selectSort(int *A,int length){ for (int i = 0; i != length; ++i) { int k = selectMinIndex(A,i,length); if (k != i) { int temp = A[i]; A[i] = A[k]; A[k] = temp; } }}
【效能分析】
(1)空間效率:僅用了一個輔助單元,空間複雜度為O(1)。
(2)時間效率:簡單選擇排序的最好和平均時間複雜度均為O(n^2)。
(3)穩定性:不同教材對簡單選擇排序的穩定性有爭議,一般認為,若是從前往後比較來選擇第i 小的記錄則該演算法是穩定的,若是從後往前比較來選擇第i 小的記錄則該演算法是不穩定的。
2.堆排序
堆排序的特點是,在以後各趟的“選擇”中利用在第一趟選擇中已經得到的關鍵字比較的結果.
堆的定義: 堆是滿足下列性質的數列{r1, r2, …,rn}:
若將此數列看成是一棵完全二叉樹,則堆或是空樹或是滿足下列特性的完全二叉樹:其左、右子樹分別是堆,並且當左/右子樹不空時,根結點的值小於(或大於)左/右子樹根結點的值。
由此,若上述數列是堆,則r1 必是數列中的最小值或最大值,分別稱作小頂堆或大頂堆。
堆排序即是利用堆的特性對記錄序列進行排序的一種排序方法。具體作法是:設有 n 個元素,將其按關鍵碼排序。首先將這 n 個元素按關鍵碼建成堆,將堆頂元素輸出,得到n 個元素中關鍵碼最小(或最大)的元素。然後,再對剩下的n-1 個元素建成堆,輸出堆頂元素,得到n 個元素中關鍵碼次小(或次大)的元素。如此反覆,便得到一個按關鍵碼有序的序列。稱這個過程為堆排序。
因此,實現堆排序需解決兩個問題:
(1)如何將n 個元素的序列按關鍵碼建成堆。
建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。n 個結點的完全二叉樹,則最後一個結點是第 n/2 個結點的孩子。對第 n/2 個結點為根的子樹篩選,使該子樹成為堆,之後向前依次對各結點為根的子樹進行篩選,使之成為堆,直到根結點。
(2)輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其按關鍵碼成為一個新堆。
調整方法:設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂,堆被破壞,其原因僅是根結點不滿足堆的性質。將根結點與左、右孩子中較小(或小大)的進行交換。若與左子女交換,則左子樹堆被破壞,且僅左子樹的根結點不滿足堆的性質;若與右子女交換,則右子樹堆被破壞,且僅右子樹的根結點不滿足堆的性質。繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。稱這個自根結點到葉子結點的調整過程為篩選。
【演算法如下】
堆排序的演算法如下所示:
void heapSort ( Elem R[], int n ) // 對記錄序列R[1..n]進行堆排序。{ for ( i=n/2; i>0; --i ) // 把R[1..n]建成大頂堆 HeapAdjust ( R, i, n ); for ( i=n; i>1; --i ) { R[1]←→R[i]; //將堆頂記錄和當前未經排序子序列,R[1..i]中最後一個記錄相互交換 HeapAdjust(R, 1, i-1); // 將R[1..i-1] 重新調整為大頂堆 }}
其中篩選的演算法如下所示。為將R[s..m]調整為“大頂堆”,演算法中“篩選”應沿關鍵字較大的孩子結點向下進行。
void HeapAdjust (Elem R[], int s, int m){ /* 已知R[s..m]中記錄的關鍵字除R[s].key 之外均滿足堆的定義,本函數調整R[s] 的關 鍵字,使R[s..m]成為一個大頂堆(對其中記錄的關鍵字而言)*/ rc = R[s]; for ( j=2*s; j<=m; j*=2 ) // 沿key 較大的孩子結點向下篩選 { if ( j<m && R[j].key<R[j+1].key ) ++j; // j 為key 較大的記錄的下標 if ( rc.key >= R[j].key ) break; // rc 應插入在位置s 上 R[s] = R[j]; s = j; } R[s] = rc; // 插入}
【效能分析】
(1)空間效率:僅用了一個輔助單元,空間複雜度為O(1)。
(2)時間效率:
①對深度為k 的堆,“篩選”所需進行的關鍵字比較的次數至多為2(k-1);
②對n 個關鍵字,建成深度為h(=ëlog2nû+1)的堆,所需進行的關鍵字比較的次數至多為4n;
③調整“堆頂”n-1 次,總共進行的關鍵字比較的次數不超過
2(log2(n-1)+ log2(n-2)+ …+log22)<2n(log2n)
因此,堆排序的平均和最壞時間複雜度均為O(nlogn)。
(3)堆排序是一個不穩定的排序方法。
2.二路歸併排序:
【演算法思想】
歸併排序的基本思想是:將兩個或兩個以上的有序子序列“歸併”為一個有序序列。
在內部排序中,通常採用的是2-路歸併排序。即:將兩個位置相鄰的有序子序列,
空間複雜度為O(n),穩定,時間複雜度O(nlog2n)
【演算法如下】
//虛擬碼,不一定能夠運行void Merge(Elem SR[], Elem TR[], int i, int m, int n){// 將有序的SR[i..m]和SR[m+1..n]歸併為有序的TR[i..n] for (j=m+1, k=i; i<=m && j<=n; ++k) // 將SR 中記錄由小到大地併入TR { if (SR[i].key<=SR[j].key) TR[k] = SR[i++]; else TR[k] = SR[j++]; } if (i<=m) TR[k..n] = SR[i..m]; // 將剩餘的SR[i..m]複製到TR if (j<=n) TR[k..n] = SR[j..n]; // 將剩餘的SR[j..n]複製到TR}
歸併排序的演算法可以有兩種形式:遞迴的和遞推的,它是由兩種不同的程式設計思想得出的。
在此,只討論遞迴形式的演算法,這是一種自頂向下的分析方法:如果記錄無序序列R[s..t]的兩部分R[s..ë(s+t)/2û]和R[ë(s+t)/2+1..tû]分別按關鍵字有序,則利用上述歸併演算法很容易將它們歸併成整個記錄序列是一個有序序列,由此,應該先分別對這兩部分進行2-路歸併排序。
void Msort ( Elem SR[], Elem TR1[], int s, int t ){ if (s==t) TR1[s] = SR[s]; else { m = (s+t)/2; // 將SR[s..t]平分為SR[s..m]和SR[m+1..t] Msort (SR, TR2, s, m); // 遞迴地將SR[s..m]歸併為有序的TR2[s..m] Msort (SR, TR2, m+1, t); //遞迴地SR[m+1..t]歸併為有序的TR2[m+1..t] Merge (TR2, TR1, s, m, t); // 將TR2[s..m]和TR2[m+1..t]歸併到TR1[s..t] }}void MergeSort (Elem R[]) // 對記錄序列R[1..n]作2-路歸併排序。{ MSort(R, R, 1, n);}
【效能分析】
(1)空間效率:需要一個與表等長的輔助元素數組空間,所以空間複雜度為O(n)。
(2)時間效率:對n 個元素的表,將這n 個元素看作葉結點,若將兩兩歸併產生的子表看作它們的父結點,則歸併過程對應由葉向根產生一棵二叉樹的過程。所以歸併趟數約等於二叉樹的高度-1,即log2n,每趟歸併需移動記錄n 次,故時間複雜度為O(nlog2n)。
(3)穩定性:歸併排序是一個穩定的排序方法。
資料結構C語言——實現各種排序演算法
剛做完的
#include <iostream>
using namespace std;
void BiInsertsort(int r[], int n) //插入排序(折半)
{
for(int i=2;i<=n;i++)
{
if (r[i]<r[i-1])
{
r[0] = r[i]; //設定哨兵
int low=1,high=i-1; //折半尋找
while (low<=high)
{
int mid=(low+high)/2;
if (r[0]<r[mid]) high=mid-1;
else low = mid+1;
}
int j;
for (j=i-1;j>high;j--) r[j+1] = r[j]; //後移
r[j+1] = r[0];
}
}
for(int k=1;k<=n;k++) cout<<r[k]<<" ";
cout<<"\n";
}
void ShellSort ( int r[], int n) //希爾排序
{
for(int d=n/2;d>=1;d=d/2) //以d為增量進行直接插入排序
{
for (int i=d+1;i<=n;i++)
{
r[0] = r[i]; //暫存被插入記錄
int j;
for( j=i-d; j>0 && r[0]<r[j]; j=j-d) r[j+d] = r[j]; //記錄後移d個位置
r[j+d] = r[0];
}
}
for(int i=1;i<=n;i++) cout<<r[i]<<" ";
cout<<"\n";
}
void BubbleSort(int r[], int n) //起泡排序
{
int temp,exchange,bound;
exchange=n; //第一趟起泡排序的範圍是r[0]到r[n-1]
while (exchange) //僅當上一趟排序有記錄交換才進行本趟排序
{
bound=exchange;
exchange=0;
for (int j=1; j<bound; j++) //一趟起泡排序
if (r[j]>r[j+1])
{
temp=r[j];
r[j]=r[j+1];
r[j+1]=temp;
exchange=j; ......餘下全文>>
資料結構中比較各種排序演算法 詳解 ,,,,,,,,,,
排序演算法包括:插入排序、交換排序、選擇排序以及合并排序。
其中插入排序包括直接插入排序和Shell排序,交換排序包括冒泡排序和分化交換排序,選擇排序包括直接選擇排序和堆排序。
這些排序演算法中,直接插入排序、冒泡排序和直接選擇排序這三種排序的演算法平均時間複雜度是O(n的平方);分化交換排序、堆排序和合并排序這三種排序的演算法平均時間複雜度是