八種常用內部排序演算法總結
在公司實習了,由於公司的性質,以後的工作中用到演算法&資料結構的知識可能會很少,很想把以前學的資料結構&演算法的知識系統的回憶一下,但由於時間的原因,再加上我一直都很喜歡排序演算法的思想。近期主要就排序這個問題做一個系統的介紹:氣泡排序,簡單選擇排序,直接插入排序,希爾排序,快速排序,歸併排序,堆排序,基數排序。
排序的穩定性:假設 ,在排序前的序列中第i記錄領先於第j個記錄。如果排序後第i記錄仍領先於第j個記錄,則稱所用的排序演算法是穩定的;反之,所用的排序的演算法是不穩定的。
對於內排序來說,演算法的效能主要受3個方面影響:時間效能(衡量排序演算法好壞的最重要的標誌);輔助空間
交換兩個資料主要有三種方法,下面演算法實現:
int swap(int array[],int m,int n)//實現兩個元素的交換的三種方法 { //第一種方法需要一個額外空間儲存 int temp; temp=array[m]; array[m]=array[n]; array[n]=temp; //第二種方法容易發生溢位現象 /*array[m]=array[m]+array[n]; array[n]=array[m]-array[n]; array[m]=array[m]-array[n];*/ //第三種方法採用位異或的資訊儲存方法 /*array[m]=array[m]^array[n]; array[n]=array[m]^array[n]; array[m]=array[m]^array[n];*/ return 0; }
1、氣泡排序
1、思想描述:一種交換排序,兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。
2、演算法複雜度及穩定性:
平均時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定性演算法
3、演算法實現:
/**************************************************************/
/********************氣泡排序演算法******************************/
void BubbleSort(int array[],int length)
{
int i,j;
for(i=0;i<length-1;i++)
{
for(j=length-2;j>=i;j--)//從後朝前比較,將較小的放置前面
{
if(array[j]>array[j+1])
swap(array,j,j+1);
}
}
}
上述演算法能否進一步改進呢,我們發現對於已經有序的序列來說,外部仍然需要進行length-1次,顯然這個時候不需要再繼續後面的迴圈。為了實現這種想法,可以增加一個標誌位來標記陣列是否已經有序。
改進演算法:
void BubbleSortImprove(int array[],int length)
{
int i,j;
boolflag=true;
for(i=0;i<length-1&&flag;i++)
{
flag=false;//初始化為false
for(j=length-2;j>=i;j--)//從後朝前比較,將較小的放置前面
{
if(array[j]>array[j+1])
{
swap(array,j,j+1);
flag=true; //此時發生資料交換,序列不是有序序列,標記為true
}
}
}
}
2、簡單選擇排序
1、演算法思想描述:通過 次的關鍵字比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第 個記錄交換。
2、演算法複雜度及穩定性:
平均時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定性演算法
3、演算法的具體實現:
/**************************************************************/
/********************簡單選擇排序******************************/
void SelectSort(int array[],int length)
{
inti,j,min;
for(i=0;i<length-1;i++)
{
min=i; //記錄length-i最小元素
for(j=i+1;j<length;j++)
{
if(array[j]<array[min])
min=j;
}
if(i!=min)
swap(array,i,min);
}
}
3、直接插入排序
1、演算法思想:假設前 個記錄已經有序,將第 個記錄插入到已經已經排好序的有序表中,從而得到一個有 個記錄的新的有序表。
2、演算法複雜度及穩定性
平均時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定性演算法
3、演算法實現:
/**************************************************************/
/********************直接插入排序******************************/
void InsertSort(int array[],int length)
{
inti,j,temp;
for(i=1;i<length;i++)
{
if(array[i]<array[i-1])//需要將array[i]插入到有序陣列中
{
temp=array[i];
for(j=i;j>0&&temp<array[j-1];j--)
{
array[j]=array[j-1];//記錄後移
}
array[j]=temp;//將array[i]插入到正確位置
}
}
}
4、希爾排序
1、演算法思想:實質上就是直接插入排序的改進,先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。
2、演算法複雜度及穩定性
平均時間複雜度:O(nlogn)-O(n^2)
空間複雜度:O(1)
穩定性:不穩定性演算法
3、演算法實現
/**************************************************************/
/********************希爾排序******************************/
void ShellSort(int array[],int length)
{
inti,j,temp,gap;
for(gap=length/2;gap>0;gap=gap/2)//增量序列
{
for(i=gap;i<length;i++)
{
if(array[i]<array[i-gap])//將array[i]插入到增量子序列中
{
temp=array[i];
for(j=i;j>0&&temp<array[j-gap];j=j-gap)
{
array[j]=array[j-gap];
}
array[j]=temp;
}
}
}
}
5、堆排序
1、演算法思想:將待排序的序列構造成一個大頂堆。此時整個序列的最大值就是堆頂的根節點。將其移走(就是講其與堆陣列的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的 個序列重新構造堆,這樣就會得到n個元素中的次大元素,如此反覆,最終就會得到一個有序序列了。
2、演算法複雜性及穩定性
平均時間複雜度:O(nlogn)
空間複雜度:O(1)
穩定性:不穩定性演算法
3、演算法的實現:
/**************************************************************/
/********************堆排序******************************/
void HeapAdjust(int array[],int local,int length)
{
inti,temp;
temp=array[local];
for(i=2*local+1;i<length;i=2*local+1)//從該節點的兩個孩子節點開始
{
if((i<length-1)&&array[i]<array[i+1])//比較local的兩個孩子的大小,保留較大的下標
i++;
if(temp>=array[i])//應插入位置
break;
array[local]=array[i];
local=i;
}
array[local]=temp;//插入
}
void HeapSort(int array[],int length)//堆排序
{
int i;
for(i=length/2-1;i>=0;i--)//從第一個非葉子節點開始自上而下的調整每一個子樹
{
HeapAdjust(array,i,length);
}
for(i=length-1;i>=0;i--)
{
swap(array,0,i); //將堆頂記錄和當前未排序的子序列的最後一個記錄交換
HeapAdjust(array,0,i);//將剩下的記錄重新調整為大頂堆
}
}
6、歸併排序
1、演算法思想:歸併排序就是利用歸併的思想,假設初始含有n個記錄,把其看成是n個有序的子序列,每個子序列的長度為1,然後兩兩歸併,得到 個長度為1或2的有序子序列;然後在兩兩合併……,如此重複,直至得到一個長度為n的有序序列為止,成為2-路歸併排序。
2、演算法複雜度及穩定性:
平均時間複雜度:O(nlogn)
空間複雜度:O(n)
穩定性:穩定性演算法
3、演算法實現:
/**************************************************************/
/********************歸併排序的遞迴形式************************/
void Merge(int array[],int tm[],int start,int mid,intend)//將有序的array[start...mid]和array[mid+1...end]合併為有序的tm[start...end]
{
int i,j,k;
k=start;
i=start;
j=mid+1;
while(i<=mid&&j<=end)//將array中的記錄有小到大歸併入tm中
{
if(array[i]<=array[j])
tm[k++]=array[i++];
else
tm[k++]=array[j++];
}
if(i<=mid)//將剩餘的array[i...mid]複製到tm中
{
while(i<=mid)
tm[k++]=array[i++];
}
if(j<=end)//將剩餘的array[j...end]複製到tm中
{
while(j<=end)
tm[k++]=array[j++];
}
}
void MSort(int array[],int tm[],int start,int end)
{
inttm2[MAXSIZE+1];
if(start==end)//如果就一個記錄可以直接歸併
{
tm[start]=array[end];
}
else
{
intmid=(start+end)/2;
MSort(array,tm2,start,mid);//首先將array[start...mid]歸併為一個有序陣列,並放在tm2[start..mid]中
MSort(array,tm2,mid+1,end);//然後將array[mid+1...end]歸併為一個有序陣列,並放在tm2[mid+1...end]中
Merge(tm2,tm,start,mid,end);//最後將tm2[start...mid]和tm2[mid+1...end]合併成一個有序記錄,並放在tm[start...end]中
}
}
void MergeSort(int array[],int length)
{
MSort(array,array,0,length-1);//歸併排序的遞迴形式
}
7、快速排序
1、演算法思想:通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字記錄均比另一部分的關鍵字小,然後分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的。
2、演算法複雜度及穩定性分析:
平均時間複雜度:O(nlogn)
空間複雜度:O(logn)-O(n)
穩定性:不穩定性演算法:
3、演算法實現:
/**************************************************************/
/***********************快速排序******************************/
int Partition(int array[],int low, int high)//交換順序表array中字表的記錄,使樞軸到位,並返回其所在的位置
{
intmid=low+(high-low)/2;//計算陣列中間的元素的下標
if(array[low]>array[high])//交換左端和右端資料,保證左端資料較小
swap(array,low,high);
if(array[high]<array[mid])//交換右端和中間資料,保證中間資料較小
swap(array,mid,high);
if(array[low]>array[mid])//交換左端和中間資料,保證左端資料較小
swap(array,low,mid);
intpivot=array[low];//用第一個記錄作為樞軸的記錄
while(low<high)
{
while(low<high&&array[high]>=pivot)
high--;
array[low]=array[high];//直接替換,不需要交換,將比樞軸小的記錄放到低端
while(low<high&&array[low]<=pivot)
low++;
array[high]=array[low];//直接替換,不需要交換,將比樞軸大的記錄放到高階
}
array[low]=pivot;
returnlow;//返回樞軸的位置
}
void QSort(int array[],int low,int high)
{
if(low<high)
{
intpartition=Partition(array,low,high);//將array[low...high]一分為二,算出樞軸的位置
QSort(array,low,partition-1);
QSort(array,partition+1,high);
}
}
void QuictSort(int array[],int length)
{
QSort(array,0,length-1);
}
改進主要有優化樞軸記錄的選取,一種取中間,隨機選取;用替換替代交換;對於記錄較少的可以直接使用直接插入排序;尾遞迴的方法縮減堆疊的深度。
void QSort1(int array[],int low,int high)//尾遞迴
{
if(low<high)
{
intpartition=Partition(array,low,high);//將array[low...high]一分為二,算出樞軸的位置
QSort1(array,low,partition-1);
low=partition+1;//尾遞迴
}
}
8、基數排序
1、演算法思想:n個記錄的關鍵字進行排序,每個關鍵字看成一個d元組: ,分為兩種方式:LSD(由數值的最右邊(低位)開始)和MSD(由數值的最左邊(高位)開始)。只講述LSD,排序時先按 的值,從小到大將記錄分到r(稱為基數)個盒子中,一次手機;然後在按 的值繼續。
2、演算法複雜度及穩定性分析:
平均時間複雜度:O(d(n+rd))
空間複雜度:O(rd)
穩定性:穩定性演算法
基數排序可參考:http://blog.csdn.net/cjf_iceking/article/details/7943609
八種常見內部排序演算法總結回顧
本文將常見的8種內部排序演算法做一個總結,排序的重要性不言而喻,主要從演算法思想,演算法複雜度及穩定性(排序穩定對於某些特殊需求來說是不言而喻的),演算法的具體實現(VC++6.0下C語言實現)。下面用一個表格就這八種演算法做一個彙總:
簡單排序:氣泡排序,簡單選擇排序,直接插入排序(1)從演算法的性質上來看,八種演算法分為兩類:
改進演算法排序:希爾排序,堆排序,歸併排序,快速排序,基數排序。
(2)從平均時間效能而言,快速排序最佳,其所需時間最省,但快速排序在最壞的情況下的時間效能不如堆排序和歸併排序。但對於後兩者而言,在n較大時,歸併所需時間較省,但所需輔助儲存量最多。
(3)基數排序最適用於n值很大而關鍵字較小的序列。而對於基本有序的序列,冒泡和插入效能較佳。
(4)從方法的穩定性來比較,基數排序是穩定的,幾種簡單排序演算法均是穩定性排序演算法,而對於改進的演算法,比如希爾排序,快速排序,堆排序均是不穩定排序。
綜上所述:對於所有的排序演算法,沒有哪種演算法是最優的,在實用是要根據不同的情況適當選擇,甚至可以多種方法綜合實用(比如快速排序可以和直接插入排序(對於基本有序的序列效能較佳))。