1. 程式人生 > >八種常用內部排序演算法總結

八種常用內部排序演算法總結

在公司實習了,由於公司的性質,以後的工作中用到演算法&資料結構的知識可能會很少,很想把以前學的資料結構&演算法的知識系統的回憶一下,但由於時間的原因,再加上我一直都很喜歡排序演算法的思想。近期主要就排序這個問題做一個系統的介紹:氣泡排序,簡單選擇排序,直接插入排序,希爾排序,快速排序,歸併排序,堆排序,基數排序。

排序的穩定性:假設 ,在排序前的序列中第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)從方法的穩定性來比較,基數排序是穩定的,幾種簡單排序演算法均是穩定性排序演算法,而對於改進的演算法,比如希爾排序,快速排序,堆排序均是不穩定排序。

綜上所述:對於所有的排序演算法,沒有哪種演算法是最優的,在實用是要根據不同的情況適當選擇,甚至可以多種方法綜合實用(比如快速排序可以和直接插入排序(對於基本有序的序列效能較佳))。