1. 程式人生 > >幾種常用的排序演算法(快速排序,希爾排序,堆排序,選擇排序,氣泡排序)

幾種常用的排序演算法(快速排序,希爾排序,堆排序,選擇排序,氣泡排序)

1、歸併排序
      基本原理:歸併排序也稱合併排序,其演算法思想是將待排序序列分為兩部分,依次對分得的兩個部分再次使用歸併排序,之後再對其進行合併。操作步驟如下。
(1)將所要進行的排序序列分為左右兩個部分,如果要進行排序的序列的起始元素下標為first,最後一個元素的下標為last,那麼左右兩部分之間的臨界點下標mid=(first+last)/2,這兩部分分別是A[first … mid]和A[mid+1 … last]。
(2)將上面所分得的兩部分序列繼續按照步驟(1)繼續進行劃分,直到劃分的區間長度為1。
(3)將劃分結束後的序列進行歸併排序,排序方法為對所分的n個子序列進行兩兩合併,得到n/2或n/2+l個含有兩個元素的子序列,再對得到的子序列進行合併,直至得到一個長度為n的有序序列為止。

//歸併排序
void merge(int arr[], int low, int mid, int high)
{
int i, k;
int *tmp = (int *)malloc((high-low+1)*sizeof(int));
//申請空間,使其大小為兩個
int left_low = low;
int left_high = mid;
int right_low = mid + 1;
int right_high = high;
for(k=0; left_low<=left_high && right_low<=right_high; k++){  // 比較兩個指標所指向的元素
if(arr[left_low]<=arr[right_low]){

tmp[k] = arr[left_low++];
}else{
tmp[k] = arr[right_low++];
}
}
if(left_low <= left_high){  //若第一個序列有剩餘,直接複製出來粘到合併序列尾
//memcpy(tmp+k, arr+left_low, (left_high-left_low+l)*sizeof(int));
for(i=left_low;i<=left_high;i++)
tmp[k++] = arr[i];
}
if(right_low <= right_high){
//若第二個序列有剩餘,直接複製出來粘到合併序列尾
//memcpy(tmp+k, arr+right_low, (right_high-right_low+1)*sizeof(int));

for(i=right_low; i<=right_high; i++)
tmp[k++] = arr[i];
}
for(i=0; i<high-low+1; i++)
arr[low+i] = tmp[i];
free(tmp);
return;
}

void merge_sort(int arr[],  int first,  int last){
int mid = 0;
if(first<last){
mid = (first+last)/2;
merge_sort(arr, first, mid);
merge_sort(arr, mid+1,last);
merge(arr,first,mid,last);
}
return;
}


2.快速排序
      快速排序演算法 的基本思想是:將所要進行排序的數分為左右兩個部分,其中一部分的所有資料都比另外一 部分的資料小,然後將所分得的兩部分資料進行同樣的劃分,重複執行以上的劃分操作,直 到所有要進行排序的資料變為有序為止。




(1)定義兩個變數low和high,將low、high分別設定為要進行排序的序列的起始元素和最後一個元素的下標。第一次,low和high的取值分別為0和n-1,接下來的每次取值由劃分得到的序列起始元素和最後一個元素的下標來決定。
(2)定義一個變數key,接下來以key的取值為基準將陣列A劃分為左右兩個部分,使陣列平均分為兩個長度相等的可以最理想,但不易實現,通 常,key值為要進行排序序列的第一個元素值。第一次的取值為A[0],以後毎次取值由要劃 分序列的起始元素決定。
(3)從high所指向的陣列元素開始向左掃描,掃描的同時將下標為high的陣列元素依次與劃分基準值key進行比較操作,直到high不大於low或找到第一個小於基準值key的陣列元素,然後將該值賦值給low所指向的陣列元素,同時將low右移一個位置。
(4)如果low依然小於high,那麼由low所指向的陣列元素開始向右掃描,掃描的同時將下標為low的陣列元素值依次與劃分的基準值key進行比較操作,直到low不小於high或找到第一個大於基準值key的陣列元素,然後將該值賦給high所指向的陣列元素,同時將high左移一個位置。
(5)重複步驟(3) (4),直到low的植不小於high為止,這時成功劃分後得到的左右兩部分分別為A[low……pos-1]和A[pos+1……high],其中,pos下標所對應的陣列元素的值就是進行劃分的基準值key,所以在劃分結束時還要將下標為pos的陣列元素賦值 為 key。
(6)將劃分得到的左右兩部分A[low……pos-1]和A[pos+1……high]繼續採用以上操作步驟進行劃分,直到得到有序序列為止。
//快速排序
int partition(int arr[], int low, int high){
int key;
key = arr[low];
while(low<high){
while(low <high && arr[high]> key )
high--;
if(low<high)
arr[low++] = arr[high];
while( low<high && arr[low]<key )
low++;
if(low<high)
arr[high--] = arr[low];
}
arr[low] = key;
return low;
}
void quick_sort(int arr[], int start, int end){
int pos;
if (start<end){
pos = partition(arr, start, end);
quick_sort(arr,start,pos-1);
quick_sort(arr,pos+1,end);
}
return;
}
3.氣泡排序
  氣泡排序的基本思想就是不斷比較相鄰的兩個數,讓較大的元素不斷地往後移。經過一輪比較,就選出最大的數;經過第2輪比較,就選出次大的數,以此類推。
//氣泡排序
//一般實現
void bubble_sort(int a[],int n)//n為陣列a的元素個數
{
//一定進行N-1輪比較
for(int i=0; i<n-1; i++)
{
//每一輪比較前n-1-i個,即已排序好的最後i個不用比較
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
}
}
//優化實現  在陣列已經排序好的情況下,會提前退出迴圈,減小了演算法的時間複雜度。
void bubble_sort_better(int a[],int n)//n為陣列a的元素個數
{
//最多進行N-1輪比較
for(int i=0; i<n-1; i++)
{
bool isSorted = true;
//每一輪比較前n-1-i個,即已排序好的最後i個不用比較
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
isSorted = false;
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
if(isSorted) break; //如果沒有發生交換,說明陣列已經排序好了
}
}
4.選擇排序
選擇排序(從小到大)的基本思想是,首先,選出最小的數,放在第一個位置;然後,選出第二小的數,放在第二個位置;以此類推,直到所有的數從小到大排序。
//選擇排序實現
void select_sort(int a[],int n)//n為陣列a的元素個數
{
//進行N-1輪選擇
for(int i=0; i<n-1; i++)
{
int min_index = i; 
//找出第i小的數所在的位置
for(int j=i+1; j<n; j++)
{
if(a[j] < a[min_index])
{
min_index = j;
}
}
//將第i小的數,放在第i個位置;如果剛好,就不用交換
if( i != min_index)
{
int temp = a[i];
a[i] = a[min_index];
a[min_index] = temp;
}
}
}


5.插入排序
    插入排序的基本思想是,將元素逐個新增到已經排序好的陣列中去,同時要求,插入的元素必須在正確的位置,這樣原來排序好的陣列是仍然有序的。
//插入排序實現,這裡按從小到大排序
void insert_sort(int a[],int n)//n為陣列a的元素個數
{
//進行N-1輪插入過程
for(int i=1; i<n; i++)
{
//首先找到元素a[i]需要插入的位置
int j=0;
while( (a[j]<a[i]) && (j<i))
{
j++;
}
//將元素插入到正確的位置
if(i != j)  //如果i==j,說明a[i]剛好在正確的位置
{
int temp = a[i];
for(int k = i; k > j; k--)
{
a[k] = a[k-1];
}
a[j] = temp;
}
}
}
6.希爾排序
基本思想:先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
//希爾排序
void shellsort(int a[], int n)
{
int i, j, gap;




for (gap = n / 2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
{
int temp = a[j];
a[j] = a[j + gap];
a[j + gap] = temp;
}
}
7.堆排序
     堆排序是利用堆的性質進行的一種選擇排序。
1.堆
  堆實際上是一棵完全二叉樹,其任何一非葉節點滿足性質:
  Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
  即任何一非葉節點的關鍵字不大於或者不小於其左右孩子節點的關鍵字。
  堆分為大頂堆和小頂堆,滿足Key[i]>=Key[2i+1]&&key>=key[2i+2]稱為大頂堆,滿足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱為小頂堆。由上述性質可知大頂堆的堆頂的關鍵字肯定是所有關鍵字中最大的,小頂堆的堆頂的關鍵字是所有關鍵字中最小的。
2.堆排序的思想
   利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。
    其基本思想為(大頂堆):
    1)將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆為初始的無須區;
    2)將堆頂元素R[1]與最後一個元素R[n]交換,此時得到新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且滿足R[1,2...n-1]<=R[n]; 
    3)由於交換後新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,......Rn-1)調整為新堆,然後再次將R[1]與無序區最後一個元素交換,得到新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數為n-1,則整個排序過程完成。


//堆排序
void HeapAdjust(int *a, int i, int size)  //調整堆 
{
int lchild = 2 * i;       //i的左孩子節點序號 
int rchild = 2 * i + 1;     //i的右孩子節點序號 
int max = i;            //臨時變數 
if (i <= size / 2)          //如果i不是葉節點就不用進行調整 
{
if (lchild <= size&&a[lchild]>a[max])
{
max = lchild;
}
if (rchild <= size&&a[rchild]>a[max])
{
max = rchild;
}
if (max != i)
{
int temp = a[max];
a[max] = a[i];
a[i] = temp;
HeapAdjust(a, max, size);    //避免調整之後以max為父節點的子樹不是堆 
}
}
}




void BuildHeap(int *a, int size)    //建立堆 
{
int i;
for (i = size / 2; i >= 0; i--)    //非葉節點最大序號值為size/2 
{
HeapAdjust(a, i, size);
}
}




void HeapSort(int *a, int size)    //堆排序 
{
int i;
BuildHeap(a, size);
for (i = size-1; i >=0; i--)
{
int temp = a[0];
a[0] = a[i];
a[i] = temp;           //交換堆頂和最後一個元素,即每次將剩餘元素中的最大者放到最後面 
//BuildHeap(a,i-1);        //將餘下元素重新建立為大頂堆 
HeapAdjust(a, 0, i - 1);      //重新調整堆頂節點成為大頂堆
}
}b