1. 排序有幾種方法
一. 冒泡排序
冒泡排序是是一種簡單的排序演算法。它重復地遍歷要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把它們交換過來。遍歷數列的工作是重復的進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因為越小的元素會經由交換慢慢「浮」到數列的頂端
1.冒泡排序演算法的運作如下:
(1)比較相鄰的元素。如果第一個比第二個大(升序),就交換他們兩個
(2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素還是最大的數
(3)針對所有的元素重復以上的步驟,除了最後一個
二. 選擇排序
選擇排序是一種簡單直觀的排序演算法。他的工作原理如下:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置(末尾位置),然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢
選擇排序的主要優點與數據移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,他們當中至少有一個將被移到最終位置上,因此對n個元素的表進行排序總共進行至多n-1次交換。在所有的完全依靠交換去移動 元素的排序方法中,選擇排序屬於非常好的一種
三. 插入排序
插入排序是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在從後向前掃描的過程中,需要反復把已排序元素逐步向後挪位,為最新元素提供插入空間
四. 快速排序
快速排序,又稱劃分交換排序。通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都要小,然後再按此方法對兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列
五 希爾排序過程
希爾排序是插入排序的一種,也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,演算法便終止。
六. 歸並排序
歸並排序是採用分治法(把復雜問題分解為相對簡單的子問題,分別求解,最後通過組合起子問題的解的方式得到原問題的解)的一個非常典型的應用。歸並排序的思想就是先遞歸分解數組,再合並數組
將數組分解最小之後,然後合並兩個有序數組,基本思路是比較兩個數組的最前面的數,水小九先取誰,取了後相應的指針就往後移一位。然後比較,直至一個數組為空,最後把另一個數組的剩餘部分復制過來即可
2. 排序演算法通常使用什麼數據結構和存儲結構為什麼
排序演算法需要按照其中的某個或某些關鍵字的大小,遞增或遞減的排列起來的操作;首先要求其具有一定的穩定性,即當兩個相同的元素同時出現於某個序列之中,則經過一定的排序演算法之後,兩者在排序前後的相對位置不發生變化。
換言之,即便是兩個完全相同的元素,它們在排序過程中也是各有區別的,不允許混淆不清。
(2)排序演算法分布進行擴展閱讀:
快速排序的基本思想:通過一趟排序演算法把所需要排序的序列的元素分割成兩大塊,其中,一部分的元素都要小於或等於另外一部分的序列元素。
然後仍根據該種方法對劃分後的這兩塊序列的元素分別再次實行快速排序演算法,排序實現的整個過程可以是遞歸的來進行調用,最終能夠實現將所需排序的無序序列元素變為一個有序的序列。
3. 內部排序演算法比較
按平均時間將排序分為四類:
(1)平方階(O(n2))排序 一般稱為簡單排序,例如直接插入、直接選擇和冒泡排序;
(2)線性對數階(O(nlgn))排序 如快速、堆和歸並排序;
(3)O(n1+£)階排序 £是介於0和1之間的常數,即0<£<1,如希爾排序;
(4)線性階(O(n))排序 如桶、箱和基數排序。
各種排序方法比較 簡單排序中直接插入最好,快速排序最快,當文件為正序時,直接插入和冒泡均最佳。 影響排序效果的因素 因為不同的排序方法適應不同的應用環境和要求,所以選擇合適的排序方法應綜合考慮下列因素:
①待排序的記錄數目n;
②記錄的大小(規模);
③關鍵字的結構及其初始狀態;
④對穩定性的要求;
⑤語言工具的條件;
⑥存儲結構;
⑦時間和輔助空間復雜度等。
不同條件下,排序方法的選擇
(1)若n較小(如n≤50),可採用直接插入或直接選擇排序。 當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少於直接插人,應選直接選擇排序為宜。
(2)若文件初始狀態基本有序(指正序),則應選用直接插人、冒泡或隨機的快速排序為宜;
(3)若n較大,則應採用時間復雜度為O(nlgn)的排序方法:快速排序、堆排序或歸並排序。 快速排序是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短; 堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。 若要求排序穩定,則可選用歸並排序。但本章介紹的從單個記錄起進行兩兩歸並的 排序演算法並不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子文件,然後再兩兩歸並之。因為直接插入排序是穩定的,所以改進後的歸並排序仍是穩定的。
(4)在基於比較的排序方法中,每次比較兩個關鍵字的大小之後,僅僅出現兩種可能的轉移,因此可以用一棵二叉樹來描述比較判定過程。 當文件的n個關鍵字隨機分布時,任何藉助於"比較"的排序演算法,至少需要O(nlgn)的時間。
網路文庫里也有說明,詳見:http://wenku..com/view/51f3b202de80d4d8d15a4fa6.html
下面是一段測試程序:
用系統計時器算時間復雜度。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#define LIST_INIT_SIZE 50000
int bj1,yd1,n;
clock_t start_t,end_t;
typedef struct
{
int key;
}ElemType;
typedef struct
{
ElemType *elem;
int length;
}SqList;
void addlist(SqList &L)
{
int i;
a: printf("請輸入你要輸入的個數:");
scanf("%d",&n);
if(n>50000)
{
printf("超出范圍重新輸入!!!\n");
goto a;
}
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem)exit(0);
L.length=0;
for(i=1;i<n+1;i++)
{
b: L.elem[i].key=rand();
if(L.elem[i].key>30000)goto b;
++L.length;
}
}
void SelectSort(SqList &L)//選擇
{
start_t=clock();
int i,j,k,bj=0,yd=0;
for(i=1;i<L.length;i++)
{
k=i;
for(j=i+1;j<L.length;j++)
{
bj++;
if(L.elem[j].key<L.elem[k].key)k=j;
}
if(i!=k)
{
L.elem[0].key=L.elem[i].key;
L.elem[i].key=L.elem[k].key;
L.elem[k].key=L.elem[0].key;
yd+=3;
}
}
end_t=clock();
printf("比較次數為 %d移動次數為 %d\n",bj,yd);
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void qipao(SqList &L)//起泡
{
start_t=clock();
int i=1,j,bj=0,yd=0;
while(i<L.length)
{
for(j=1;j<L.length;j++)
{
bj++;
if(L.elem[j].key>L.elem[j+1].key)
{
L.elem[0].key=L.elem[j].key;
L.elem[j].key=L.elem[j+1].key;
L.elem[j+1].key=L.elem[0].key;
yd+=3;
}
}
i++;
}
end_t=clock();
printf("比較次數為 %d移動次數為 %d\n",bj,yd);
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void InsertSort(SqList &L)//直接插入
{
start_t=clock();
int i,j,yd=0,bj=0;
for(i=2;i<=L.length;i++)
{
if(L.elem[i].key<L.elem[i-1].key)
{
L.elem[0].key=L.elem[i].key;
yd++;
j=i-1;
bj++;
while(L.elem[0].key<L.elem[j].key)
{
L.elem[j+1].key=L.elem[j].key;
j--;
yd++;
bj++;
}
L.elem[j+1].key=L.elem[0].key;
yd++;
}
}
end_t=clock();
printf("比較次數為 %d移動次數為 %d\n",bj,yd);
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void xier(SqList &L)//希爾
{
start_t=clock();
int i,d=L.length/2,j,w=0,k,yd=0,bj=0;
while(w<d)
{
w=1;
for(i=w;i<L.length;i=i+d)
{
k=i;
for(j=i+d;j<L.length;j=j+d)
{
if(L.elem[i].key>L.elem[j].key)
{
k=j;
bj++;
}
}
if(i!=k)
{
L.elem[0].key=L.elem[i].key;
L.elem[i].key=L.elem[k].key;
L.elem[k].key=L.elem[0].key;
yd+=3;
}
w++;
}
d=d/2;
w=1;
}
end_t=clock();
printf("比較次數為 %d移動次數為 %d\n",bj,yd);
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void BeforeSort()
{
yd1=0,bj1=0;
}
void display(int m,int n)
{
printf("比較次數為 %d移動次數為 %d\n",m,n);
}
int Partition(SqList &L,int low,int high)//快速排序
{
int pivotkey;
L.elem[0]=L.elem[low];
yd1++;
pivotkey=L.elem[low].key;
while (low<high)
{
yd1++;
while(low<high&&L.elem[high].key>=pivotkey)
--high;
L.elem[low]=L.elem[high];
bj1++;
yd1++;
while (low<high&&L.elem[low].key<=pivotkey)
++low;
L.elem[high]=L.elem[low];
bj1++;
yd1++;
}
L.elem[low]=L.elem[0];
yd1++;
return low;
}
void QSort(SqList &L,int low,int high)
{
int pivotloc;
if(low<high)
{
pivotloc=Partition(L,low,high);
QSort(L,low,pivotloc-1);
QSort(L,pivotloc+1,high);
}
}
void QuickSort(SqList &L)
{
start_t=clock();
BeforeSort();
QSort(L,1,L.length);
display(yd1,bj1);
end_t=clock();
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void Merge(ElemType R[],ElemType R1[],int low,int m,int high)//歸並
{
int i=low, j=m+1, k=low;
while(i<=m&&j<=high)
{
if(R[i].key<=R[j].key)
{
bj1++;
R1[k]=R[i];
yd1++;
i++;
k++;
}
else
{
bj1++;
R1[k]=R[j];
yd1++;
j++;
k++;
}
}
while(i<=m)
{
R1[k]=R[i];
yd1++;
i++;
k++;
}
while(j<=high)
{
R1[k]=R[j];
yd1++;
j++;
k++;
}
}
void MergePass(ElemType R[],ElemType R1[],int length, int n)
{
int i=0,j;
while(i+2*length-1<n)
{
Merge(R,R1,i,i+length-1,i+2*length-1);
i=i+2*length;
}
if(i+length-1<n-1)
Merge(R,R1,i,i+length-1,n-1);
else
for(j=i;j<n;j++)
R1[j]=R[j];
}
void MSort(ElemType R[],ElemType R1[],int n)
{
int length=1;
while (length<n)
{
MergePass(R,R1,length,n);
length=2*length;
MergePass(R1,R,length,n);
length=2*length;
}
}
void MergeSort(SqList &L)
{
start_t=clock();
BeforeSort();
MSort(L.elem,L.elem,L.length);
display(yd1,bj1);
end_t=clock();
printf("排序用時為 %f\n",float(end_t-start_t)/CLK_TCK);
}
void main()
{
SqList L;
addlist(L);
printf("起泡排序: \n");
qipao(L);
addlist(L);
printf("直插排序: \n");
InsertSort(L);
addlist(L);
printf("選擇排序: \n");
SelectSort(L);
addlist(L);
printf("希爾排序: \n");
xier(L);
addlist(L);
printf("快速排續: \n");
QuickSort(L);
addlist(L);
printf("歸並排序: \n");
MergeSort(L);
}
4. 請問 排序演算法 可以分為哪幾大類
排序可分為:
1,穩定排序與不穩定排序
2,內排序和外排序
內部排序可分為:直接插入排序、冒泡排序、簡單選擇排序、希爾排序、快速排序、堆排序、歸並排序、基數排序。
5. 常用的數據排序演算法有哪些,各有什麼特點舉例結合一種排序演算法並應用數組進行數據排序。
排序簡介
排序是數據處理中經常使用的一種重要運算,在計算機及其應用系統中,花費在排序上的時間在系統運行時間中佔有很大比重;並且排序本身對推動演算法分析的發展也起很大作用。目前已有上百種排序方法,但尚未有一個最理想的盡如人意的方法,本章介紹常用的如下排序方法,並對它們進行分析和比較。
1、插入排序(直接插入排序、折半插入排序、希爾排序);
2、交換排序(起泡排序、快速排序);
3、選擇排序(直接選擇排序、堆排序);
4、歸並排序;
5、基數排序;
學習重點
1、掌握排序的基本概念和各種排序方法的特點,並能加以靈活應用;
2、掌握插入排序(直接插入排序、折半插入排序、希爾排序)、交換排序(起泡排序、快速排序)、選擇排序(直接選擇排序、堆排序)、二路歸並排序的方法及其性能分析方法;
3、了解基數排序方法及其性能分析方法。
排序(sort)或分類
所謂排序,就是要整理文件中的記錄,使之按關鍵字遞增(或遞減)次序排列起來。其確切定義如下:
輸入:n個記錄R1,R2,…,Rn,其相應的關鍵字分別為K1,K2,…,Kn。
輸出:Ril,Ri2,…,Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。
1.被排序對象--文件
被排序的對象--文件由一組記錄組成。
記錄則由若干個數據項(或域)組成。其中有一項可用來標識一個記錄,稱為關鍵字項。該數據項的值稱為關鍵字(Key)。
注意:
在不易產生混淆時,將關鍵字項簡稱為關鍵字。
2.排序運算的依據--關鍵字
用來作排序運算依據的關鍵字,可以是數字類型,也可以是字元類型。
關鍵字的選取應根據問題的要求而定。
【例】在高考成績統計中將每個考生作為一個記錄。每條記錄包含准考證號、姓名、各科的分數和總分數等項內容。若要惟一地標識一個考生的記錄,則必須用"准考證號"作為關鍵字。若要按照考生的總分數排名次,則需用"總分數"作為關鍵字。
排序的穩定性
當待排序記錄的關鍵字均不相同時,排序結果是惟一的,否則排序結果不唯一。
在待排序的文件中,若存在多個關鍵字相同的記錄,經過排序後這些具有相同關鍵字的記錄之間的相對次序保持不變,該排序方法是穩定的;若具有相同關鍵字的記錄之間的相對次序發生變化,則稱這種排序方法是不穩定的。
注意:
排序演算法的穩定性是針對所有輸入實例而言的。即在所有可能的輸入實例中,只要有一個實例使得演算法不滿足穩定性要求,則該排序演算法就是不穩定的。
排序方法的分類
1.按是否涉及數據的內、外存交換分
在排序過程中,若整個文件都是放在內存中處理,排序時不涉及數據的內、外存交換,則稱之為內部排序(簡稱內排序);反之,若排序過程中要進行數據的內、外存交換,則稱之為外部排序。
注意:
① 內排序適用於記錄個數不很多的小文件
② 外排序則適用於記錄個數太多,不能一次將其全部記錄放人內存的大文件。
2.按策略劃分內部排序方法
可以分為五類:插入排序、選擇排序、交換排序、歸並排序和分配排序。
排序演算法分析
1.排序演算法的基本操作
大多數排序演算法都有兩個基本的操作:
(1) 比較兩個關鍵字的大小;
(2) 改變指向記錄的指針或移動記錄本身。
注意:
第(2)種基本操作的實現依賴於待排序記錄的存儲方式。
2.待排文件的常用存儲方式
(1) 以順序表(或直接用向量)作為存儲結構
排序過程:對記錄本身進行物理重排(即通過關鍵字之間的比較判定,將記錄移到合適的位置)
(2) 以鏈表作為存儲結構
排序過程:無須移動記錄,僅需修改指針。通常將這類排序稱為鏈表(或鏈式)排序;
(3) 用順序的方式存儲待排序的記錄,但同時建立一個輔助表(如包括關鍵字和指向記錄位置的指針組成的索引表)
排序過程:只需對輔助表的表目進行物理重排(即只移動輔助表的表目,而不移動記錄本身)。適用於難於在鏈表上實現,仍需避免排序過程中移動記錄的排序方法。
3.排序演算法性能評價
(1) 評價排序演算法好壞的標准
評價排序演算法好壞的標准主要有兩條:
① 執行時間和所需的輔助空間
② 演算法本身的復雜程度
(2) 排序演算法的空間復雜度
若排序演算法所需的輔助空間並不依賴於問題的規模n,即輔助空間是O(1),則稱之為就地排序(In-PlaceSou)。
非就地排序一般要求的輔助空間為O(n)。
(3) 排序演算法的時間開銷
大多數排序演算法的時間開銷主要是關鍵字之間的比較和記錄的移動。有的排序演算法其執行時間不僅依賴於問題的規模,還取決於輸入實例中數據的狀態。
文件的順序存儲結構表示
#define n l00 //假設的文件長度,即待排序的記錄數目
typedef int KeyType; //假設的關鍵字類型
typedef struct{ //記錄類型
KeyType key; //關鍵字項
InfoType otherinfo;//其它數據項,類型InfoType依賴於具體應用而定義
}RecType;
typedef RecType SeqList[n+1];//SeqList為順序表類型,表中第0個單元一般用作哨兵
注意:
若關鍵字類型沒有比較算符,則可事先定義宏或函數來表示比較運算。
【例】關鍵字為字元串時,可定義宏"#define LT(a,b)(Stromp((a),(b))<0)"。那麼演算法中"a<b"可用"LT(a,b)"取代。若使用C++,則定義重載的算符"<"更為方便。
按平均時間將排序分為四類:
(1)平方階(O(n2))排序
一般稱為簡單排序,例如直接插入、直接選擇和冒泡排序;
(2)線性對數階(O(nlgn))排序
如快速、堆和歸並排序;
(3)O(n1+£)階排序
£是介於0和1之間的常數,即0<£<1,如希爾排序;
(4)線性階(O(n))排序
如桶、箱和基數排序。
各種排序方法比較
簡單排序中直接插入最好,快速排序最快,當文件為正序時,直接插入和冒泡均最佳。
影響排序效果的因素
因為不同的排序方法適應不同的應用環境和要求,所以選擇合適的排序方法應綜合考慮下列因素:
①待排序的記錄數目n;
②記錄的大小(規模);
③關鍵字的結構及其初始狀態;
④對穩定性的要求;
⑤語言工具的條件;
⑥存儲結構;
⑦時間和輔助空間復雜度等。
不同條件下,排序方法的選擇
(1)若n較小(如n≤50),可採用直接插入或直接選擇排序。
當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少於直接插人,應選直接選擇排序為宜。
(2)若文件初始狀態基本有序(指正序),則應選用直接插人、冒泡或隨機的快速排序為宜;
(3)若n較大,則應採用時間復雜度為O(nlgn)的排序方法:快速排序、堆排序或歸並排序。
快速排序是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;
堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。
若要求排序穩定,則可選用歸並排序。但本章介紹的從單個記錄起進行兩兩歸並的 排序演算法並不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子文件,然後再兩兩歸並之。因為直接插入排序是穩定的,所以改進後的歸並排序仍是穩定的。
4)在基於比較的排序方法中,每次比較兩個關鍵字的大小之後,僅僅出現兩種可能的轉移,因此可以用一棵二叉樹來描述比較判定過程。
當文件的n個關鍵字隨機分布時,任何藉助於"比較"的排序演算法,至少需要O(nlgn)的時間。
箱排序和基數排序只需一步就會引起m種可能的轉移,即把一個記錄裝入m個箱子之一,因此在一般情況下,箱排序和基數排序可能在O(n)時間內完成對n個記錄的排序。但是,箱排序和基數排序只適用於像字元串和整數這類有明顯結構特徵的關鍵字,而當關鍵字的取值范圍屬於某個無窮集合(例如實數型關鍵字)時,無法使用箱排序和基數排序,這時只有藉助於"比較"的方法來排序。
若n很大,記錄的關鍵字位數較少且可以分解時,採用基數排序較好。雖然桶排序對關鍵字的結構無要求,但它也只有在關鍵字是隨機分布時才能使平均時間達到線性階,否則為平方階。同時要注意,箱、桶、基數這三種分配排序均假定了關鍵字若為數字時,則其值均是非負的,否則將其映射到箱(桶)號時,又要增加相應的時間。
(5)有的語言(如Fortran,Cobol或Basic等)沒有提供指針及遞歸,導致實現歸並、快速(它們用遞歸實現較簡單)和基數(使用了指針)等排序演算法變得復雜。此時可考慮用其它排序。
(6)本章給出的排序演算法,輸人數據均是存儲在一個向量中。當記錄的規模較大時,為避免耗費大量的時間去移動記錄,可以用鏈表作為存儲結構。譬如插入排序、歸並排序、基數排序都易於在鏈表上實現,使之減少記錄的移動次數。但有的排序方法,如快速排序和堆排序,在鏈表上卻難於實現,在這種情況下,可以提取關鍵字建立索引表,然後對索引表進行排序。然而更為簡單的方法是:引人一個整型向量t作為輔助表,排序前令t[i]=i(0≤i<n),若排序演算法中要求交換R[i]和R[j],則只需交換t[i]和t[j]即可;排序結束後,向量t就指示了記錄之間的順序關系:
R[t[0]].key≤R[t[1]].key≤…≤R[t[n-1]].key
若要求最終結果是:
R[0].key≤R[1].key≤…≤R[n-1].key
則可以在排序結束後,再按輔助表所規定的次序重排各記錄,完成這種重排的時間是O(n)。
6. 順序表的排序演算法
給你舉一些比較常用的排序法:
交換排序法
冒泡排序 | 雞尾酒排序 | 奇偶排序 | 梳排序 | 侏儒排序 | 快速排序 |臭皮匠演算法 | Bogo排序
選擇排序法
選擇排序 | 堆排序 | Smooth排序 | 笛卡爾樹排序 | 錦標賽排序 | 循環排序
插入排序法
插入排序 | 希爾排序 | 二叉查找樹排序 | 圖書館排序 | Patience排序
歸並排序法
歸並排序 | 多相歸並排序 | Strand排序
分布排序法
美國旗幟排序 | 珠排序 | 桶排序 | 爆炸排序 | 計數排序 | 鴿巢排序 | 相鄰圖排序 | 基數排序 | 閃電排序
混合排序法
Tim排序 | 內省排序 | Spread排序 | 反移排序 | J排序
其他
雙調排序器 | Batcher歸並網路 | 兩兩排序網路
7. 常用的排序演算法有哪些
排序另一種分法
外排序:需要在內外存之間多次交換數據才能進行
內排序:
歸並排序
冒泡排序
快速排序
簡單選擇排序
堆排序
直接插入排序
希爾排序
插入類排序
選擇類排序
交換類排序
歸並類排序
8. C 排序問題
排 序:
程序員可以使用的基本排序演算法有5種:
·插入排序(insertionsort.)
·交換排序(exchangesOrt)
·選擇排序(selectionsort)
·歸並排序(mergesort)
·分布排序(distributionsort)
為了形象地解釋每種排序演算法是怎樣工作的,讓我們來看一看怎樣用這些方法對桌上一付亂序的牌進行排序。牌既要按花色排序(依次為梅花、方塊、紅桃和黑心),還要按點數排序(從2到A)。
插入排序的過程為:從一堆牌的上面開始拿牌,每次拿一張牌,按排序原則把牌放到手中正確的位置。桌上的牌拿完後,手中的牌也就排好序了。
交換排序的過程為:
(1)先拿兩張牌放到手中。如果左邊的牌要排在右邊的牌的後面,就交換這兩張牌的位置。
(2)然後拿下一張牌,並比較最右邊兩張牌,如果有必要就交換這兩張牌的位置。
(3)重復第(2)步,直到把所有的牌都拿到手中。
(4)如果不再需要交換手中任何兩張牌的位置,就說明牌已經排好序了;否則,把手中的牌放到桌上,重復(1)至(4)步,直到手中的牌排好序。
選擇排序的過程為:在桌上的牌中找出最小的一張牌,拿在手中;重復這種操作,直到把所有牌都拿在手中。
歸並排序的過程為:把桌上的牌分為52堆,每堆為一張牌。因為每堆牌都是有序的(記住,此時每堆中只有一張牌),所以如果把相鄰的兩堆牌合並為一堆,並對每堆牌進行排序,就可以得到26堆已排好序的牌,此時每一堆中有兩張牌。重復這種合並操作,就可以依次得到13堆牌(每一堆中有4張牌),7堆牌(有6堆是8張牌,還有一堆是4張牌),最後將得到52張的一堆牌。
分布排序(也被稱作radix sort,即基數排序)的過程為:先將牌按點數分成13堆,然後將這13堆牌按點數順序疊在一起;再將牌按花色分成4堆,然後將這4堆牌按花色順序疊在一起,牌就排好序了。
在選用排序演算法時,你還需要了解以下幾個術語:
(1)自然的(natural)
如果某種排序演算法對有序的數據排序速度較快(工作量變小),對無序的數據排序速度卻較慢(工作變數大),我們就稱這種排序演算法是自然的。如果數據已接近有序,就需要考慮選用自然的排序演算法。
(2)穩定的(stable)
如果某種排序演算法能保持它認為相等的數據的前後順序,我們就稱這種排序演算法是穩定的。
例如,現有以下名單:
Mary Jones
Mary Smith
Tom Jones
Susie Queue
如果用穩定的排序演算法按姓對上述名單進行排序,那麼在排好序後"Mary Jones」和"Tom Jones」將保持原來的Jr順序,因為它們的姓是相同的。
穩定的排序演算法可按主、次關鍵字對數據進行排序,例如按姓和名排序(換句話說,主要按姓排序,但對姓相同的數據還要按名排序)。在具體實現時,就是先按次關鍵字排序,再按主關鍵字排序。
(3)內部排序(internal sort)和外部排序(external sort)
待排數據全部在內存中的排序方法被稱為內部排序,待排數據在磁碟、磁帶和其它外存中的排序方法被稱為外部排序。
查 找:
和排序演算法一樣,查找(searching)演算法也是計算機科學中研究得最多的問題之一。查找演算法和排序演算法是有聯系的,因為許多查找演算法依賴於要查找的數據集的有序程度。基本的查找演算法有以下4種:
·順序查找(sequential searching)。
·比較查找(comparison searching)
·基數查找(radix searching)
·哈希查找(hashing)
下面仍然以一付亂序的牌為例來描述這些演算法的工作過程。
順序查找的過程為:從第一張開始查看每一張牌,直到找到要找的牌。
比較查找(也被稱作binarysearching,即折半查找)要求牌已經排好序,其過程為:任意抽一張牌,如果這張牌正是要找的牌,則查找過程結束。如果抽出的這張牌比要找的牌大,則在它前面的牌中重復查找操作;反之,則在它後面的牌中重復查找操作,直到找到要找的牌。
基數查找的過程為:先將牌按點數分成13堆,或者按花色分成4堆。然後找出與要找的牌的點數或花色相同的那一堆牌,再在這堆牌中用任意一種查找演算法找到要找的牌。
哈希查找的過程為:
(1)在桌面上留出可以放若干堆牌的空間,並構造一個函數,使其能根據點數和花色將牌映射到特定的堆中(這個函數被稱為hashfunction,即哈希函數)。
(2)根據哈希函數將牌分成若干堆。
(3)根據哈希函數找到要找的牌所在的堆,然後在這一堆牌中找到要找的牌。
例如,可以構造這樣一個哈希函數:
pile=rank+suit
其中,rank是表示牌的點數的一個數值;suit是表示牌的花色的一個數值;pile表示堆值,它將決定一張牌歸入到哪一堆中。如果用1,2,……,13分別表示A,2,…….K,用0,1,2和3分別表示梅花、方塊、紅桃和黑桃,則pile的值將為1,2,……,16,這樣就可以把一付牌分成16堆。
哈希查找雖然看上去有些離譜,但它確實是一種非常實用的查找演算法。各種各樣的程序,從壓縮程序(如Stacker)到磁碟高速緩存程序(如SmartDrive),幾乎都通過這種方法來提高查找速度,
排序或查找的性能:
有關排序和查找的一個主要問題就是速度。這個問題經常被人們忽視,因為與程序的其餘部分相比,排序或查找所花費的時間幾乎可以被忽略。然而,對大多數排序或查找應用來說,你不必一開始就花很多精力去編制一段演算法程序,而應該先在現成的演算法中選用一種最簡單的(見3.1和3.4),當你發現所用的演算法使程序運行很慢時,再換用一種更好的演算法(請參見下文中的介紹)。
下面介紹一種判斷排序或查找演算法的速度的方法。
首先,引入一個演算法的復雜度的概念,它指的是在各種情況(最好的、最差的和平均的)下排序或查找需要完成的操作次數,通過它可以比較不同演算法的性能。
演算法的復雜度與排序或查找所針對的數據集的數據量有關,因此,引入一個基於數據集數據量的表達式來表示演算法的復雜度。
最快的演算法的復雜度O(1),它表示演算法的操作次數與數據量無關。復雜度O(N)(N表示數據集的數據量)表示演算法的操作次數與數據量直接相關。復雜度O(logN)介於上述兩者之間,它表示演算法的操作次數與數據量的對數有關。復雜度為O(NlogN)(N乘以logN)的演算法比復雜度為O(N)的演算法要慢,而復雜度為O(N2)的演算法更慢。
注意:如果兩種演算法的復雜度都是O(logN),那麼logN的基數較大的演算法的速度要快些,在本章的例子中,logN的基數均為10
9. 排序演算法有多少種
排序(Sorting) 是計算機程序設計中的一種重要操作,它的功能是將一個數據元素(或記錄)的任意序列,重新排列成一個關鍵字有序的序列。
排序就是把集合中的元素按照一定的次序排序在一起。一般來說有升序排列和降序排列2種排序,在演算法中有8中基本排序:
(1)冒泡排序;
(2)選擇排序;
(3)插入排序;
(4)希爾排序;
(5)歸並排序;
(6)快速排序;
(7)基數排序;
(8)堆排序;
(9)計數排序;
(10)桶排序。
插入排序
插入排序演算法是基於某序列已經有序排列的情況下,通過一次插入一個元素的方式按照原有排序方式增加元素。這種比較是從該有序序列的最末端開始執行,即要插入序列中的元素最先和有序序列中最大的元素比較,若其大於該最大元素,則可直接插入最大元素的後面即可,否則再向前一位比較查找直至找到應該插入的位置為止。插入排序的基本思想是,每次將1個待排序的記錄按其關鍵字大小插入到前面已經排好序的子序列中,尋找最適當的位置,直至全部記錄插入完畢。執行過程中,若遇到和插入元素相等的位置,則將要插人的元素放在該相等元素的後面,因此插入該元素後並未改變原序列的前後順序。我們認為插入排序也是一種穩定的排序方法。插入排序分直接插入排序、折半插入排序和希爾排序3類。
冒泡排序
冒泡排序演算法是把較小的元素往前調或者把較大的元素往後調。這種方法主要是通過對相鄰兩個元素進行大小的比較,根據比較結果和演算法規則對該二元素的位置進行交換,這樣逐個依次進行比較和交換,就能達到排序目的。冒泡排序的基本思想是,首先將第1個和第2個記錄的關鍵字比較大小,如果是逆序的,就將這兩個記錄進行交換,再對第2個和第3個記錄的關鍵字進行比較,依次類推,重復進行上述計算,直至完成第(n一1)個和第n個記錄的關鍵字之間的比較,此後,再按照上述過程進行第2次、第3次排序,直至整個序列有序為止。排序過程中要特別注意的是,當相鄰兩個元素大小一致時,這一步操作就不需要交換位置,因此也說明冒泡排序是一種嚴格的穩定排序演算法,它不改變序列中相同元素之間的相對位置關系。
選擇排序
選擇排序演算法的基本思路是為每一個位置選擇當前最小的元素。選擇排序的基本思想是,基於直接選擇排序和堆排序這兩種基本的簡單排序方法。首先從第1個位置開始對全部元素進行選擇,選出全部元素中最小的給該位置,再對第2個位置進行選擇,在剩餘元素中選擇最小的給該位置即可;以此類推,重復進行「最小元素」的選擇,直至完成第(n-1)個位置的元素選擇,則第n個位置就只剩唯一的最大元素,此時不需再進行選擇。使用這種排序時,要注意其中一個不同於冒泡法的細節。舉例說明:序列58539.我們知道第一遍選擇第1個元素「5」會和元素「3」交換,那麼原序列中的兩個相同元素「5」之間的前後相對順序就發生了改變。因此,我們說選擇排序不是穩定的排序演算法,它在計算過程中會破壞穩定性。
快速排序
快速排序的基本思想是:通過一趟排序演算法把所需要排序的序列的元素分割成兩大塊,其中,一部分的元素都要小於或等於另外一部分的序列元素,然後仍根據該種方法對劃分後的這兩塊序列的元素分別再次實行快速排序演算法,排序實現的整個過程可以是遞歸的來進行調用,最終能夠實現將所需排序的無序序列元素變為一個有序的序列。
歸並排序
歸並排序演算法就是把序列遞歸劃分成為一個個短序列,以其中只有1個元素的直接序列或者只有2個元素的序列作為短序列的遞歸出口,再將全部有序的短序列按照一定的規則進行排序為長序列。歸並排序融合了分治策略,即將含有n個記錄的初始序列中的每個記錄均視為長度為1的子序列,再將這n個子序列兩兩合並得到n/2個長度為2(當凡為奇數時會出現長度為l的情況)的有序子序列;將上述步驟重復操作,直至得到1個長度為n的有序長序列。需要注意的是,在進行元素比較和交換時,若兩個元素大小相等則不必刻意交換位置,因此該演算法不會破壞序列的穩定性,即歸並排序也是穩定的排序演算法。
10. 各種排序演算法實現和比較
1、 堆排序定義
n個關鍵字序列Kl,K2,…,Kn稱為堆,當且僅當該序列滿足如下性質(簡稱為堆性質):
(1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ )
若將此序列所存儲的向量R[1..n]看做是一棵完全二叉樹的存儲結構,則堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。
關鍵字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分別滿足堆性質(1)和(2),故它們均是堆,其對應的完全二叉樹分別如小根堆示例和大根堆示例所示。
2、大根堆和小根堆
根結點(亦稱為堆頂)的關鍵字是堆里所有結點關鍵字中最小者的堆稱為小根堆。
根結點(亦稱為堆頂)的關鍵字是堆里所有結點關鍵字中最大者,稱為大根堆。
注意:
①堆中任一子樹亦是堆。
②以上討論的堆實際上是二叉堆(Binary Heap),類似地可定義k叉堆。
3、堆排序特點
堆排序(HeapSort)是一樹形選擇排序。
堆排序的特點是:在排序過程中,將R[l..n]看成是一棵完全二叉樹的順序存儲結構,利用完全二叉樹中雙親結點和孩子結點之間的內在關系,在當前無序區中選擇關鍵字最大(或最小)的記錄。
4、堆排序與直接插入排序的區別
直接選擇排序中,為了從R[1..n]中選出關鍵字最小的記錄,必須進行n-1次比較,然後在R[2..n]中選出關鍵字最小的記錄,又需要做n-2次比較。事實上,後面的n-2次比較中,有許多比較可能在前面的n-1次比較中已經做過,但由於前一趟排序時未保留這些比較結果,所以後一趟排序時又重復執行了這些比較操作。
堆排序可通過樹形結構保存部分比較結果,可減少比較次數。
5、堆排序
堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。
(1)用大根堆排序的基本思想
① 先將初始文件R[1..n]建成一個大根堆,此堆為初始的無序區
② 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key
③ 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整為堆。然後再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關系R[1..n-2].keys≤R[n-1..n].keys,同樣要將R[1..n-2]調整為堆。
……
直到無序區只有一個元素為止。
(2)大根堆排序演算法的基本操作:
① 初始化操作:將R[1..n]構造為初始堆;
② 每一趟排序的基本操作:將當前無序區的堆頂記錄R[1]和該區間的最後一個記錄交換,然後將新的無序區調整為堆(亦稱重建堆)。
注意:
①只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得文件遞增有序。
②用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任何時刻,堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往前逐步擴大至整個向量為止。
(3)堆排序的演算法:
void HeapSort(SeqIAst R)
{ //對R[1..n]進行堆排序,不妨用R[0]做暫存單元
int i;
BuildHeap(R); //將R[1-n]建成初始堆
for(i=n;i1;i--){ //對當前無序區R[1..i]進行堆排序,共做n-1趟。
R[0]=R[1];R[1]=R[i];R[i]=R[0]; //將堆頂和堆中最後一個記錄交換
Heapify(R,1,i-1); //將R[1..i-1]重新調整為堆,僅有R[1]可能違反堆性質
} //endfor
} //HeapSort
(4) BuildHeap和Heapify函數的實現
因為構造初始堆必須使用到調整堆的操作,先討論Heapify的實現。
① Heapify函數思想方法
每趟排序開始前R[l..i]是以R[1]為根的堆,在R[1]與R[i]交換後,新的無序區R[1..i-1]中只有R[1]的值發生了變化,故除R[1]可能違反堆性質外,其餘任何結點為根的子樹均是堆。因此,當被調整區間是R[low..high]時,只須調整以R[low]為根的樹即可。
"篩選法"調整堆
R[low]的左、右子樹(若存在)均已是堆,這兩棵子樹的根R[2low]和R[2low+1]分別是各自子樹中關鍵字最大的結點。若R[low].key不小於這兩個孩子結點的關鍵字,則R[low]未違反堆性質,以R[low]為根的樹已是堆,無須調整;否則必須將R[low]和它的兩個孩子結點中關鍵字較大者進行交換,即R[low]與R[large](R[large].key=max(R[2low].key,R[2low+1].key))交換。交換後又可能使結點R[large]違反堆性質,同樣由於該結點的兩棵子樹(若存在)仍然是堆,故可重復上述的調整過程,對以R[large]為根的樹進行調整。此過程直至當前被調整的結點已滿足堆性質,或者該結點已是葉子為止。上述過程就象過篩子一樣,把較小的關鍵字逐層篩下去,而將較大的關鍵字逐層選上來。因此,有人將此方法稱為"篩選法"。
具體的演算法
②BuildHeap的實現
要將初始文件R[l..n]調整為一個大根堆,就必須將它所對應的完全二叉樹中以每一結點為根的子樹都調整為堆。
顯然只有一個結點的樹是堆,而在完全二叉樹中,所有序號 的結點都是葉子,因此以這些結點為根的子樹均已是堆。這樣,我們只需依次將以序號為 , -1,…,1的結點作為根的子樹都調整為堆即可。
具體演算法。
5、大根堆排序實例
對於關鍵字序列(42,13,24,91,23,16,05,88),在建堆過程中完全二叉樹及其存儲結構的變化情況參見。
6、 演算法分析
堆排序的時間,主要由建立初始堆和反復重建堆這兩部分的時間開銷構成,它們均是通過調用Heapify實現的。
堆排序的最壞時間復雜度為O(nlgn)。堆排序的平均性能較接近於最壞性能。
由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的文件。
堆排序是就地排序,輔助空間為O(1),
它是不穩定的排序方法。