概述:
如果被排序的文件可以裝載於內存中,則稱該排序方法爲內部排序(internal sort)。若排序文件來看磁帶或磁盤,則稱做外部排序(external sort)。兩者的主要區別是,內部排序可以輕鬆訪問任意項,而外部排序必須按順序訪問項,或至少要按大數據塊來訪問。
我們這裏說說八大排序就是內部排序。
當n較大,則應採用時間複雜度爲O(nlog2n)的排序方法:快速排序、堆排序或歸併排序序。
快速排序:是目前基於比較的內部排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;
1.插入排序
1.1直接插入排序
直接插入排序,就像我們撲克牌一樣。當我們拿到一張牌時,我們習慣將這張牌插入到手上已有的牌當中。
示例:
我們知道,該方法是從前面排好了的序列是插入。那做法就是先搜索要排的數在前面的位置,然後將比它大的數往後移動,然後插入即可。我們可以從這一過程中,可以看到三個過程,即搜索-移動-插入。移動和插入很難有所改變,那可以在搜索上下功夫了。我們知道搜索有順序搜索和二分搜索這兩個基本的方法。
插入排序約平均使用N^2/4次比較,N^2/4次半交換(移動),在最壞的情況下,比較與交換次數加倍。從算法思想上很容易看出比較與移動的次數是相同,對於隨機輸入,每個元素平均移動一半路程就折回,因此,應當計算對角線之下一半的元素。
對於順序搜索,我們可以將搜索和移動合併同時進行,即從後往前搜索,比較一次就移動一次,直到找到爲止,然後插入,即代碼爲:
//插入排序
void InsertSort(int *d,int n)
{
int i,j;
int tmp;
for(i=1;i<n;i++){
j=i;tmp=d[i];//複製爲哨兵,即存儲待排序元素
while(j>0&&d[j-1]>tmp)//從後往前找,邊找邊移動
d[j--]=d[j-1];
d[j]=tmp;//找到後就插入
}
}
最壞時間複雜度:1+2...+n-1=n(n-1)/2
時間複雜度:O(n^2).
1.2二分插入排序
二分插入排序,實際上是將前面的搜索用二分搜索,其它是一樣的,其代碼爲:
void insertDichotomySort(int *d,int n)
{
int i,m,a,b;//a,b爲二分法中的頭和尾,m爲中間索引值
int tmp;//保存要插入的值
for(i=1;i<n;i++){
tmp=d[i];//保存要插入的值
a=0;b=i;//在[0,i]之內搜索,不是[0,i-1]的原因是,可能這個範圍內找不到位置,
//但如果包含i,則必定會找到相應的位置,即本身
while(b>a){
m=(a+b)/2;
if(d[m]<d[i])
a=m+1;//由於要插入的值比中間值要大,那中間值必定不是要插入的地方,那至少在這點後面一個點
//同時也避免了最後a與b只相差1時的無窮循環了
else
b=m;//因爲這裏還包含等於
}
m=i;
while(m>=a)//移動
d[m--]=d[m-1];
d[a]=tmp;//插入
}
}
其實二分搜索插入並不佔多少優勢,主要是它依然要移動,同時在for循環中加入了if條件判斷,使之性能下降。但如果量很大的時候,二分法的優勢可能會體現出來
1.3 二路插入排序
void insert2Sort(int *d,int n)
{
int i,j;
int first=0,//只是記錄左右端點的索引,而不是個數
last=n;
int *t=new int[n+1];//多申請一個空間,讓t[0]和t[n],這樣就不必用求餘符號,減小計算量
memset(t,0,sizeof(int)*(n+1));
t[0]=d[0];t[n]=d[0];
for(i=1;i<n;i++){
if(d[i]>d[0]){//t[0]的右邊插入排序
j=++first;//記錄最右邊的索引
while(j>0&&t[j-1]>d[i])//從後往前找,邊找邊移動
t[j--]=t[j-1];
t[j]=d[i];
}
else{
j=--last;
while(j<n&&t[j+1]<d[i])////從前往後找,邊找邊移動
t[j++]=t[j+1];
t[j]=d[i];
}
}
// for(i=0;i<n;i++)//由於從last到first本身就是由小到大的順序,所以只需要將t中last到first平移到0到n-1即可
// d[i]=t[(i+last)%n];
memcpy(d,t+last,sizeof(int)*(n-last+1));//利用系統函數平移
memcpy(d+n-last,t,sizeof(int)*(first+1));
}
1.3希爾排序
選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;我們可以簡單地設增量序列dk = {n/2 ,n/4, n/8 .....1},也可以是其它方式
按增量序列個數k0,對序列進行k0 趟排序;
每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
void shellSort(int *d,int n)
{
//每次步長變化爲前一次的一半
for(int dk=n/2;dk>0;dk/=2){
//起始索引每次要加1,但不能超過步長dk,
//即起始索引爲0~dk-1,共移動dk次,即要排dk次直接插入排序
for(int k=0;k<dk;k++){
//每一組的直接插入
for(int i=k+dk;i<n;i+=dk){
int tmp=d[i];
int j=i;
while(j>k&&d[j-dk]>tmp){
d[j]=d[j-dk];
j-=dk;
}
d[j]=tmp;
}
}
}
}
2.交換排序
2.1冒泡法
2.1.1基本冒泡法
冒泡法是最簡單的排序算法之一,但不是最有效的排序算法之一。從名字上看,就知道和冒泡有關。在水裏,大的往下沉,小的往上冒。如果我們把一組數豎着看,小的數跑着前面,大的往後面跑。其實際做法就是,拿出數組中任意一個數,與下一個數比較,如果大,則往後移動;如果小,自身則不動,讓下一個值進行比較。若假設a是所有中最大的數,索引到它後,由於它後面的所有數都要比a小,故會一起交換下去,這樣一次的排序,就把最大值排到最後。當第二循環時,把第二大的數排到倒數第二位上。依此類推,就排好序了。同時也告訴我們怎麼找第n大的數。
時間複雜度:O(n^2).冒泡排序在最壞情況下,約平均使用N^2/2次細瘦,N^2/2次交換。
具體流程如下:
其代碼爲:
void bubbleSort(int *d,int n)
{
for(int i=0;i<n-1;i++)//要進行n-1次循環比較
for(int j=0;j<n-i-1;j++)//每一次冒泡
if(d[j]>d[j+1])//前一個和後一個比較,如果大於後面那個數,則交換其值
swap(d[j],d[j+1]);
}
2.2標誌冒泡法
void bubbleFlagSort(int *d,int n)
{
int pos;
int i=n-1;
while(i>0){//因爲我們不能確定循環次數,所以我們用while
pos=0;
for(int j=0;j<i;j++){
if(d[j]>d[j+1]){//記錄最後一次交換的位置
swap(d[j],d[j+1]);
pos=j;
}
}
i=pos;
}
}
2.3正反兩趟標記冒泡法
前面的兩種冒泡只是從一邊開始,我們可以像二路插入排序一樣,考慮兩頭冒泡。從左往右時,把大的往後移,然後從右往左移時,把小的往前移,同時採用標記減少循環次數。具體過程如下:
從上面的過程可以看到,首先是將最大值放入最右邊,然後把最小值放入最左邊,當然這個過程中還有別的交換。圖中的R表示從左往右掃,L表示從右往左掃,其值代表最後一
void bubbleFlagTwoSideSort(int *d,int n)
{
int beforePos=0,
afterPos=n-1;//設置前後標誌
int ib,ia;//用於前後標誌的中間變量
int i;
while(beforePos<afterPos){//如果左標誌大於右標誌,說明循環已完
ib=afterPos;ia=beforePos;//左右標誌的初始值的原則是,如果從頭到尾都沒有交換的話,那值是多少,很明顯
for(i=beforePos;i<afterPos;i++){
if(d[i]>d[i+1]){
swap(d[i],d[i+1]);
ia=i;//其值代表交換中的前一個值,以致afterPos不需要減一了
}
}
afterPos=ia;
// cout<<afterPos<<"R: ";
// Print(d,n);
for(i=afterPos;i>beforePos;i--){
if(d[i-1]>d[i]){
swap(d[i-1],d[i]);
ib=i;//其值代表交換中的後一個值,以致beforePos不需要加一了
}
}
beforePos=ib;
// cout<<beforePos<<"L: ";
// Print(d,n);
}
}
次交換的位置。結合上面兩種方法,可以寫出如下程序:
2.2快速排序
3 選擇排序
3.1 簡單選擇排序
3.2 堆排序
4 並歸排序
5 基數排序
網絡資源:
http://kb.cnblogs.com/page/210687/