1. 程式人生 > >各種常規排序演算法總結

各種常規排序演算法總結

1.插入排序
        基本思想是:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子表中的適當位置,直到全部記錄插入完成為止。常規插入排序分兩種,即直接插入排序和希爾排序。
1.1直接插入排序
        假設待排序的記錄放在陣列R[0...n-1]中,排序過程的某一中間時刻,R被劃分成兩個子區間R[0..i-1]和R[i..n-1],其中:前一個區間是已經排好序的有序區,後一個子區間是當前未排序的部分,不妨稱為無序區。直接插入排序的基本操作是將當前無序區的第一個記錄R[i]插入到R[0..i-1]中適當的位置,使R[0..i]變為新的有序區-------演算法時間複雜度o(n^2)。

    // 插入排序      void insertSort(int array[], int length)      {          int i, j, key;          for (i = 1; i < length; i++)          {              key = array[i];              // 把i之前大於array[i]的資料向後移動              for (j = i - 1; j >= 0 && array[j] > key; j--)              {                  array[j + 1] = array[j];              }              // 在合適位置安放當前元素              array[j + 1] = key;          }      }

1.2shell排序
        希爾排序也是一種插入排序方法,實際上是一種分組插入方法。基本思想是:先取定一個小於n的整數d1作為第一個增量,把表的全部記錄分成d1個組,所有距離為d1的倍數的記錄放在同一個組中,在各組內進行直接插入排序;然後取第二個增量d2(<d1),重複上述的分組和排序,直至所取的增量dt=1(dt<dt-1<..d2<d1),及所有記錄放在同一組中進行直接插入排序為止。演算法最好時間複雜度o(nlogn)。

// shell排序      void shellSort(int array[], int length)      {          int key;          // 增量從陣列長度的一半開始,每次減小一倍          for (int increment = length / 2; increment > 0; increment /= 2)              for (int i = increment; i < length; ++i)              {                  key = array[i];                  // 對一組增量為increment的元素進行插入排序                  for (int j = i - increment; j >= 0 && array[j] > key; j -= increment)                  {                      array[j+increment] = array[j];                  }                  // 在合適位置安放當前元素                  array[j+increment] = key;              }      }

2.交換排序
          基本思想:兩兩比較待排序記錄的關鍵字,發現兩個記錄的次序相反時,即進行交換,直到沒有反序的記錄為止。
2.1氣泡排序
         基本思想是,通過無序區中相鄰記錄關鍵字間的比較和位置的交換,使關鍵字最小的記錄如氣泡一般逐漸往上“漂浮”直至“水面”。整個演算法是從最後面的記錄開始,對每兩個相鄰的關鍵字進行比較,且使關鍵字較小的記錄換至關鍵字較大的記錄之上,使得經過一趟氣泡排序後,關鍵字最小的記錄到大最上端,接著,再在剩下的記錄中找到關鍵字次小的記錄,並把它換在第二個位置上,依次類推,一直到所有記錄都有序為止。演算法時間複雜度o(n^2)。

    //氣泡排序      void bubble_sort(int a[], int length)      {          int i, j, tag;                for(i = length - 1; i > 0; i--)//不斷把前面無序區最大元素移到後面有序區          {              tag = 1;//標誌位              for(j = 0; j < i; j++)//前面無序區最大元素不斷向後移              {                  if(a[j] > a[j + 1])                  {                      a[j] ^= a[j + 1];                      a[j + 1] ^= a[j];                      a[j] ^= a[j+1];                      tag = 0;                  }              }              if(tag == 1)              {                  break;              }          }      }

2.2快速排序
         快速排序是由氣泡排序改進而得的,它的基本思想是:在待排序的n個記錄中任取一個記錄(通常取第一個記錄),把該記錄放入適當位置後,資料序列被此記錄劃分成兩部分。所有關鍵字比該記錄關鍵字小的記錄放置在前一部分,所有比它大的記錄放置在後一部分,並把該記錄排在這兩部分的中間(稱為該記錄歸位),這個過程稱作一趟快速排序。之後對所有的兩部分分別重複上述過程,直至每個部分內只有一個記錄或為空為止。簡單的說,每趟使表的第一個元素放入適當位置,將表一分為二,對子表按遞迴方法繼續這種劃分,直至劃分的子表長為1或0.-------演算法最好時間複雜度o(nlog2n)

 // 對一個給定範圍的子序列選定一個樞紐元素,執行完函式之後返回分割元素所在的位置,      // 在分割元素之前的元素都小於樞紐元素,在它後面的元素都大於這個元素      int partition(int array[], int low, int high)      {          // 採用子序列的第一個元素為樞紐元素          int pivot = 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;          // 返回樞紐元素所在的位置          return low;      }      // 快速排序      void quickSort(int array[], int low, int high)      {          if (low < high)          {              int n = partition(array, low, high);              quickSort(array, low, n);              quickSort(array, n + 1, high);          }      }

3.選擇排序

         基本思想:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子表的最後,直到全部記錄排序完畢。由於選擇排序方法每一趟總是從無序區中選出全域性最小(或最大)的關鍵字,所以適合於從大量的記錄中選擇一部分排序記錄,如,從10000個記錄中選擇出關鍵字前10位的記錄,就適合使用選擇排序法。
3.1直接選擇排序
         直接選擇排序法:第i趟排序開始時,當前有序區和無序區分別為R[0..i-1]和R[i..n-1](0≤ i≤n-1),該趟排序則是從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第一個記錄R[i]交換,使得R[0..i]和R[i+1..n-1]分別為新的有序區和新的無序區

 //直接選擇排序      void selectSort(int array[],int nLength)      {          int i,j,k;          for(i = 0; i < nLength - 1; i++)          {              k = i;              for(j = i+1; j < nLength; j++)              {                  if(array[j] < array[k])                      k = j;              }              if(k != i)                  swap(&array[k],&array[i]);          }      }

3.2堆排序
         n個關鍵字序列Kl,K2,…,Kn稱為堆,當且僅當該序列滿足如下性質(簡稱為堆性質):
          (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤)
         若將此序列所儲存的向量R[1……n]看做是一棵完全二叉樹的儲存結構,則堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。
         堆的這個性質使得可以迅速定位在一個序列之中的最小(大)的元素。
         堆排序演算法的過程如下:1)得到當前序列的最小(大)的元素 2)把這個元素和最後一個元素進行交換,這樣當前的最小(大)的元素就放在了序列的最後,而原先的最後一個元素放到了序列的最前面 3)交換可能會破壞堆序列的性質(注意此時的序列是除去已經放在最後面的元素),因此需要對序列進行調整,使之滿足於上面堆的性質。重複上面的過程,直到序列調整完畢為止。

// array是待調整的堆陣列,i是待調整的陣列元素的位置,length是陣列的長度      void heapAdjust(int array[], int i, int nLength)      {          int nChild, nTemp;          for (nTemp = array[i]; 2 * i + 1 < nLength; i = nChild)          {              // 子結點的位置是 父結點位置 * 2 + 1              nChild = 2 * i + 1;              // 得到子結點中較大的結點              if (nChild != nLength - 1 && array[nChild + 1] > array[nChild])                  ++nChild;              // 如果較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點              if (nTemp < array[nChild])                  array[i] = array[nChild];              else// 否則退出迴圈                  break;          }          // 最後把需要調整的元素值放到合適的位置          array[i] = nTemp;      }      // 堆排序演算法      void heapSort(int array[], int length)      {          //建立大頂堆          for (int i = length / 2 - 1; i >= 0; --i)              heapAdjust(array, i, length);          // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素          for (int j = length - 1; j > 0; --j)          {              // 把第一個元素和當前的最後一個元素交換,              // 保證當前的最後一個位置的元素都是在現在的這個序列之中最大的              swap(&array[0], &array[j]);              // 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值              heapAdjust(array, 0, j);          }      }

4.歸併排序
       歸併排序是多次將兩個或兩個以上的有序表合併成一個有序表。最簡單的歸併是直接將兩個有序的子表合併成一個有序的表。
       兩個有序表直接合併成一個有序表的演算法Merge().設兩個有序表存放在同一陣列中相鄰的位置上:R[low..mid],R[mid+1..high],先將他們合併到一個區域性的暫存陣列R1中,待合併完成後將R1複製到R中。每次從兩個段中取出一個記錄進行關鍵字的比較,將較小者放入R1中,最後將各段中餘下的部分直接複製到R1中。

 void merge(int array[],int start,int mid,int end)      {          int i,j,k;          i = start;          j = mid + 1;          k = 0;          int * p = (int *)malloc((end - start + 1) * sizeof(int));                while((i <= mid) && (j <= end))          {              if(array[i] < array[j])                  p[k++] = array[i++];              else                  p[k++] = array[j++];          }          while(i <= mid)              p[k++] = array[i++];          while(j <= end)              p[k++] = array[j++];                k = 0;          while(start <= end)              array[start++] = p[k++];                free(p);      }      //歸併排序      void mergeSort(int array[],int start,int end)      {          if(start < end)          {              int i = (start + end) / 2;              mergeSort(array,start,i);              mergeSort(array,i+1,end);              merge(array,start,i,end);          }      }

5.計數排序和基數排序     
        在之前介紹過的排序方法,都是屬於[比較性]的排序法,也就是每次排序時,都是比較整個關鍵字的大小以進行排序。計數排序是一個非基於比較的線性時間排序演算法。
        計數排序演算法的基本思想是對於給定的輸入序列中的每一個元素x,確定該序列中值小於x的元素的個數。一旦有了這個資訊,就可以將x直接存放到最終的輸出序列的正確位置上。例如,如果輸入序列中只有17個元素的值小於x的值,則x可以直接存放在輸出序列的第18個位置上。

//計數排序      //nMaxValue為陣列array中最大值      void countingSort(int array[],int nMaxValue,int nLength)      {          int i;          int *pTemp = (int *)malloc((nMaxValue + 1) * sizeof(int));          int *pResult = (int *)malloc((nLength + 1) * sizeof(int));                for(i = 0; i <= nMaxValue; i++)              pTemp[i] = 0;          // 此過程記錄每一個元素的個數          for(i = 0; i < nLength; i++)              pTemp[array[i]]++;          // 此過程確定小於元素x的元素的個數          for(i = 1; i <= nMaxValue; i++)              pTemp[i] += pTemp[i-1];          for(i = nLength-1; i >= 0; i--)          {              pResult[pTemp[array[i]]] = array[i];              pTemp[array[i]]--;          }          for(i = 1; i <= nLength; i++)              array[i-1] = pResult[i];      }

         基數排序的主要思路是,將所有待比較數值(注意,必須是正整數)統一為同樣的數位長度,數位較短的數前面補零. 然後, 從最低位開始, 依次進行一次穩定排序.這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列.
         比如這樣一個數列排序: 342 58 576 356, 以下描述演示了具體的排序過程
第一次排序(個位):
3 4 2
5 7 6
3 5 6
0 5 8
第二次排序(十位):
3 4 2
3 5 6
0 5 8
5 7 6
第三次排序(百位):
0 5 8
3 4 2
3 5 6
5 7 6
結果: 58 342 356 576

//基數排序      //nMaxBit為數字中的最高位數,如最大數為235,則nMaxBit為3      void radixSort(int *array, int nLength ,int nMaxBit)      {          int i,j;          int temp[10];          int nTmp;          int nRadix = 1;          int *pResult = (int *)malloc(nLength * sizeof(int));                     for(j = 1; j <= nMaxBit; j++)          {              // 以下和計數排序一樣              for(i = 0; i < 10; i++)                  temp[i] = 0;              // 此過程記錄每一個元素的個數              for(i = 0; i < nLength; i++)              {                  nTmp = (array[i] / nRadix) % 10;                  temp[nTmp]++;              }              // 此過程確定小於元素x的元素的個數              for(i = 1; i < 10; i++)                  temp[i] += temp[i-1];              for(i = nLength-1; i >= 0; i--)              {                  nTmp = (array[i] / nRadix) % 10;                  temp[nTmp]--;                  pResult[temp[nTmp]] = array[i];              }              for(i = 0; i < nLength; i++)                  array[i] = pResult[i];              nRadix *= 10;          }      }