數據結構—各類‘排序算法’實現(上)


      數據結構中的排序算法分爲比較排序,非比較排序。比較排序有插入排序、選擇排序、交換排序、歸併排序,非比較排序有計數排序、基數排序。下面是排序的具體分類:

wKiom1dBI3ew4ev-AABO_8JtbaE299.png

1.直接排序

         主要思想:使用兩個指針,讓一個指針從開始,另一個指針指向前一個指針的+1位置,兩個數據進行比較


void InsertSort(int* a, size_t size)
{
     assert(a);
     for (size_t i = 0; i < size - 1; i++)
     {
          int end = i;
          int tmp = a[end + 1];
          while (end >= 0 && a[end] > tmp)
          {
               a[end + 1] = a[end];
               --end;
          }
          a[end + 1] = tmp;   //當進行到a[end]>tmp的時候,將tmp插入到a[end+1]的位置上
     }
}


2.希爾排序

       主要思想:給定間距gap,將間距上的數據進行排序,然後將間距進行縮小,當間距爲1時,就相當於進行直接插入排序,這就避免了,直接排序有序的情況,提高排序的效率


void shellSort(int* a, size_t size)
{
     assert(a);
     int gap = size;
     while (gap > 1)    //當gap爲2或者1時,進入循環中gap = 1,相當於進行直接插入排序 
     {
          gap = gap / 3 + 1;
          for (size_t i = 0; i < size - gap; i++)
          {
               int end = i;
               int tmp = a[end + gap];
               while (end >= 0 && a[end] > tmp)
               {
                    a[end + gap] = a[end];
                    end -= gap;
               }
               a[end + gap] = tmp;
          }
     }
}


3.選擇排序

        主要思想: 每一次從所要排序的數據中選出最大的數據,放入到數組的最後位置上,用數組的下標來控制放置的位置,直到排好順序


void selectSort(int* a, size_t size)
{
     assert(a);
     for (size_t i = 0; i < size - 1; i++)
     { 
          int max = a[0];
          int num = 0;
          for (size_t j = 0; j < size - i; j++)
          {
               if (max < a[j])
               {
                    max = a[j];
                    num = j;
               }
          }
          swap(a[num], a[size - i - 1]);
     }
}


     選擇排序優化後的思想:每一次可以選兩個數據,最大數據和最小數據,將最大數據從數組的最大位置開始放置,最小數據從數組的最小位置開始放置,能夠提高排序的效率


void selectSort(int* a, size_t size)
{
     int left = 0;
     int right = size - 1;
     
     while (left < right)
     { 
          for (int i = left; i < right; i++)
          {
               int min = a[left];
               int max = a[right];
               if (a[i] < min)
               {
                    min = a[i];
                    swap(a[i], a[left]);
               }
               if (a[i] > max)
               {
                    max = a[i];
                    swap(a[i], a[right]);
               }
          }
          ++left;
          --right;
     }
}


4.堆排序

        主要思想:創建一個大堆進行排序,堆排序只能排序數組,通過數組的下表來計算數據在堆中的位置,將大堆的根節點與最後一個葉子節點進行交換,然後對堆中剩下的數據進行調整,直到再次成爲大堆。


void AdjustDown(int* a, size_t root, size_t size)
{
     assert(a);
     size_t parent = root;
     size_t child = parent * 2 + 1;
     while (child < size)
     {
          if (child + 1 < size && a[child + 1] > a[child])
          {
               ++child;
          }
          if (a[child] > a[parent])
          {
               swap(a[child], a[parent]);
               parent = child;
               child = parent * 2 + 1;
          }
          else
          {
               break;
          }
     }
}

void HeapSort(int* a, size_t size)
{
     for (int i = (size - 2) / 2; i >= 0; --i)   
      //從最後一個父親節點開始建堆(使用i>=0時,必須使用int類型)
     {
          AdjustDown(a, i, size);
     }
     for (size_t i = 0; i < size; i++)     //進行排序,最大的數往下放
     {
          swap(a[0], a[size - i - 1]);
          AdjustDown(a, 0, size - i - 1);
     }
}


5.快速排序

方法一:

        主要思想:先選定一個key值(一般是數組的頭元素或者尾元素),這裏選定數組的尾元素,給定兩個指針begin和end,begin指針指向數組的頭位置,end指針指向倒數第二個位置,begin指針找比key值大的數據,end指針找較key值小的數據,如果begin指針還沒有和end相遇,則將a[begin]和a[end]數據進行交換。當begin和end指針相遇,則將key值和a[begin]進行交換。

wKioL1dBO1fymxxXAAAQ1C5bu2M958.png

int partSort(int* a, int left, int right)
{
     assert(a);
     int key = a[right];    //選最右端的數據作爲key
     int begin = left;
     int end = right - 1;
     while (begin < end)
     {
          while (begin < end && a[begin] <= key)    //begin找比key大的數據
          {
               ++begin;
          }
          while (begin < end && a[end] >= key)    //end找比key小的數據
          {
               --end;
          }
          if (begin < end)
          {
               swap(a[begin], a[end]);
          }
     }
     if (a[begin] > a[right])      //只有兩個元素的情況
     {
          swap(a[begin], a[right]);
          return begin;
     }
     else
     {
          return right;
     }
     return begin;
}

void QuickSort(int* a, int left, int right)
{
     assert(a);
     if (left >= right)
     {
          return;
     }
     int point = partSort(a, left, right);
     QuickSort(a, left, point-1);
     QuickSort(a, point+1, right);
}


方法二:

        主要思想:挖坑法實現,將最右邊的數據用key進行保存,可以說這時候最後的位置相當於一個坑,能夠對數據進行任意的更改,將左指針找到的較key值大的數據賦值到key的位置上,這時候左指針指向的位置可以形成一個坑,這時再用右指針找較key值小的數據,將其賦值到剛纔的坑中,這時右指針指向的位置也就行成坑。最後當兩個指針相遇時,將key值賦值到坑中,這時左邊的數據都小於key值,右邊的數據都大於key值。

wKioL1dBPljS1-iCAAAR7xFNHBY455.png

         其中,若選取數組中最大或者最小的數據爲key值,這是快速排序的最壞情況,利用三數取中的方法可以解決這種問題,取數組中頭元素、尾元素、和中間元素的最中間大小的數據作爲key值,就能夠避免這樣的情況。

//三數取中法
int GetMidIndex(int* a, int left, int right)
{
     assert(a);
     int mid = (left + right) / 2;
     if (a[left] < a[right])
     {
          if (a[mid] < a[left])    //a[mid] < a[left] < a[right]
          {
               return left;
          }
          else if (a[mid] > a[right])   //a[left] < a[right] < a[mid]
          {    
               return right;
          }
          else     //a[left] < a[mid] <  a[right]
          {
               return mid;
          }
     }
     else
     {
          if (a[mid] < a[right])       //a[left] > a[right] > a[mid]
          {
               return right;
          }
          else if (a[mid] > a[left])    //a[right] < a[left] < a[mid] 
          {
               return left;
          }
          else    //a[right] < a[mid] < a[left]
          {
               return mid;
          }
     }
}

int partSort1(int* a, int left, int right)
{
     int index = GetMidIndex(a, left, right);
     swap(a[index], a[right]);    //將中間的數據與最右邊的數據進行交換,然後將最右邊數據賦值給key
     int key = a[right];  //首先將最右邊的位置作爲第一個坑
     int begin = left;
     int end = right;
     while (begin < end)
     {
          while (begin < end && a[begin] <= key)  //從左往右找較key大的數據
          {
               ++begin;
          }
          a[end] = a[begin];   //將第一個坑進行覆蓋,同時空出新的坑
          while (begin < end && a[end] >= key)   //從右往左查找較key小的數據
          {
               --end;
          }
          a[begin] = a[end];   //將第二個坑進行覆蓋,同時空出新的坑
     }
     if (begin == end)
     {
          a[end] = key;   //key現在的位置,左邊的數據都較key值小,右邊的數據豆角key值大
          return begin;
     }
}

void QuickSort1(int* a, int left, int right)
{
     assert(a);
     if (left > right)
     {
          return;
     }
     int ret = partSort1(a, left, right);
     QuickSort1(a, left, ret - 1);
     QuickSort1(a, ret + 1, right);
}


方法三:

        主要思想:選定最右邊的數據爲key,將cur指針指向數組的頭元素,cur指針找較key值小的數據,prev指針指向cur-1的位置,當cur找到較小的數據,先進行prev++,若此時cur=prev,cur繼續找較小的數據,直到cur!=prev,就將a[prev]和a[cur]進行交換,直到cur指向數組的倒數第二個元素,這時將key值和a[++prev]進行交換。


int partSort2(int* a, int left, int right)
{
     int key = a[right];
     int cur = left;
     int prev = left - 1;
     while (cur < right)
     {
          if (a[cur] < key && ++prev != cur)
          {
               swap(a[cur], a[prev]);
          }
          ++cur;
     }
     swap(a[right], a[++prev]);
     return prev;
}

void QuickSort2(int* a, int left, int right)
{
     assert(a);
     if (left > right)
     {
          return;
     }
     int ret = partSort2(a, left, right);
     QuickSort2(a, left, ret - 1);
     QuickSort2(a, ret + 1, right);
}


        優化:當區間gap<13,採用直接排序效率會高於都採用快速排序,能夠減少程序壓棧的開銷

//核心代碼
void QuickSort1(int* a, int left, int right)
{
     assert(a);
     if (left > right)
     {
          return;
     }
     int gap = right - left + 1;
     if (gap < 13)
     {
          InsertSort(a, gap);
     }
     int ret = partSort1(a, left, right);
     QuickSort1(a, left, ret - 1);
     QuickSort1(a, ret + 1, right);
}

 

——如果不使用遞歸,那應該怎樣實現快速排序算法呢?(利用棧進行保存左邊界和右邊界)

 

//核心代碼
void QuickSort_NonR(int* a, int left, int right)
{
     assert(a);
     stack<int> s;
     if (left < right)    //當left < right證明需要排序的數據大於1
     {
          s.push(right);
          s.push(left);
     }
     while (!s.empty())
     {
          left = s.top();
          s.pop();
          right = s.top();
          s.pop();
          if (right - left < 13)
          {
               InsertSort(a, right - left + 1);
          }
          else
          {
               int div = partSort1(a, left, right);
               if (left < div - 1)
               {
                    s.push(div - 1);
                    s.push(left);
               }
               if (div + 1 < right)
               {
                    s.push(right);
                    s.push(div + 1);
               }
          }
     }
}


6.歸併排序

        主要思想:與合併兩個有序數組算法相似,需要藉助一塊O(N)的空間,將一個數組中的元素分爲兩部分,若這兩個部分都能夠有序,則利用合併的思想進行合併,過程一直進行遞歸


void _Merge(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
     int index = begin1;       //用來標記tmp數組的下標
     while (begin1 <= end1 && begin2 <= end2)
      //先判斷begin1和begin2的大小,然後將小的數據從begin到end拷貝到tmp上,
      //出循環的條件begin1>=end1 || begin2 >= end2
     {
          if (a[begin1] < a[begin2])  
          {
               tmp[index++] = a[begin1++];
          }
          else
          {
               tmp[index++] = a[begin2++];
          }
     }
     //將剩下的begin1或者begin2在進行拷貝
     while (begin1 <= end1)   
     {
          tmp[index++] = a[begin1++];
     }
     while (begin2 <= end2)
     {
          tmp[index++] = a[begin2++];
     }
}

void _MergeSort(int* a, int* tmp, int left, int right)
{
     if (left < right)
     {
          int mid = (right + left) / 2;
          _MergeSort(a, tmp, left, mid);
          _MergeSort(a, tmp, mid + 1, right);
          _Merge(a, tmp, left, mid, mid + 1, right);   //藉助tmp進行排序
          //將tmp上面排序後的數據再拷貝到上層的數組中
          for (int i = left; i <= right; ++i)
          {
               a[i] = tmp[i];
          }
     }
}

void MergeSort(int* a, int size)   //歸併排序數組
{
     assert(a);
     int* tmp = new int[size];    //開闢N個空間
     int left = 0;
     int right = size - 1;
     _MergeSort(a, tmp, left, right);
     delete[] tmp;
}


       上面主要介紹的爲各個比較排序的算法實現,非比較排序(計數、基數)在下篇:“數據結構—各類‘排序算法’實現(下)”


本文出自 “無心的執着” 博客,請務必保留此出處http://10740590.blog.51cto.com/10730590/1775856

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章