數據結構和算法總結(二):排序

前言算法

複習各類排序算法,並記錄下。數組

正文數據結構

1.冒泡排序app

冒泡能夠說是最簡單的排序算法,它的排序過程就是每次遍歷數組將最大的那個數往前頂,就好像氣泡上浮同樣。優化

過程能夠參考以下圖網站

參考代碼ui

void bubbleSort(vector<int>& num)
{
    for(int i = num.size()- 1;i > 0;i--)
    {
       for(int j = 1;j <= i;j++)
       {
           if(num[j] < num[j-1])
            swap(num[j],num[j-1]);
       }
    }
}

複雜度分析spa

冒泡的最壞狀況下的時間複雜度爲:O(n2),平均複雜度:O(n2) 。.net

優化blog

咱們能夠稍微優化下冒泡排序,增長一個標識來肯定一個數組是否有序,若是是那麼能夠提早終止排序,參考代碼以下

//提早終止的冒泡排序
bool PreStopBubble(vector<int>& num,const int n)
{
   bool isSwapped = false;
   for(int i = 1;i < n;i++)
   {
       if(num[i - 1] > num[i])
       {
           isSwapped = true;
           cout << "Swapped!" << endl;
           swap(num[i - 1],num[i]);
       }
   }
   return isSwapped;
}

void PreStopBubbleSort(vector<int>&num)
{
  for(int i = num.size();i > 0 && PreStopBubble(num,i);i--); 
  //若是某一次冒泡過程未發生交換,那麼說明數組已經有序,因此提早終止
}

這種情形下,冒泡排序的最好複雜度能夠達到O(n)。

 

2.選擇排序

選擇排序和冒泡排序的思路相似,都是找到最大(或者最小)的數,只不過選擇排序須要額外的空間來存儲一次遍歷過程當中最大(或者最小)的數字和它的位置,而後將它與首位(或者末位)交換。

過程能夠參考以下圖

注:圖中的選擇排序每次是選擇的最小值,參考代碼每次是選取最大值。

參考代碼

void SelectionSort(vector<int>& num)
{
    for(int i = num.size();i > 0 ;i--)
    {
        int tmp = num[0],index = 0;
        for(int j = 1;j < i;j++)
        {
           if(num[j] > tmp)
           {
               tmp = num[j];
               index = j;
           }
        }
        swap(num[index],num[i - 1]);
    }
}

複雜度分析

選擇排序的最壞狀況下的時間複雜度爲:O(n2),平均複雜度:O(n2) 。可是每每選擇排序效率優於冒泡排序(若是不是提早終止的冒泡),由於每一次遍歷選擇排序只須要發生一次交換,而冒泡排序可能發生了屢次交換,然而,這是選擇排序犧牲了額外空間來存儲最值得來的。

 

3.計數排序

計數排序,也叫桶排序。這種排序方法適合已知必定範圍內的數字排序,每個數字都有一個對應的桶,一次遍歷將數組的數字放入到對應的桶中進行統計,而後遍歷每一個桶將其輸出便可。

過程能夠參考以下圖

參考代碼

void CountSort(vector<int>& num,const int maxnum)
{
   vector<int> countBox(maxnum + 1,0);
   for(int i =0;i < num.size();i++)
   {
       countBox[num[i]]++;
   }
   int j = 0;
   for(int i = 0;i < num.size();i++)
   {
      if(countBox[j]-- > 0)
        num[i] = j;
      else
      {
         j++;i--;
      }
   }
}

複雜度分析

計數排序的平均時間複雜度爲:O(n)。可是它的空間複雜度不好。這是一種犧牲空間換取時間的排序算法。

 

4.歸併排序

有序數組的合併

首先,咱們要知道如何將兩個有序數組合並。方法很簡單,從頭開始比較兩個數組的數字,用一個額外的數組tmp存儲一次比較時的較小數字,而後較小者所在數組的索引向後+1繼續和以前另外一個數組的較大者比較。若是其中一個數組遍歷到末尾,那麼把另外一個數組的剩餘元素依次添加到tmp數組末尾便可,這樣tmp就是一個合併後的有序數組。

分治

而對於一個無序數組,咱們能夠將它劃分爲兩個無序子數組,子數組又能夠不停劃分,直到當一個子數組只有一個數時,這時這個子數組確定是有序的,那麼咱們就能夠將一個個有序子數組合併成更大的有序子數組,直到最終合併成一個有序數組,這就是歸併排序的思想。

排序過程能夠參考以下圖

參考代碼

void MergeArray(vector<int>& num,int left,int right,int mid,vector<int>& tmp)
{ //合併兩個有序子數組
    int l = left,lm = mid;
    int mr = mid + 1,r = right;
    int k = 0;
    while(l <= lm && mr <= r)
    {
        if(num[l] < num[mr])
            tmp[k++] = num[l++];
        else
            tmp[k++] = num[mr++];
    }
    while(l <= lm) tmp[k++] = num[l++];
    while(mr <= r) tmp[k++] = num[mr++];
    for(int i = 0;i < k;i++)
    {
        num[left + i] = tmp[i];
    }
}

void MergeSort(vector<int>& num,int left,int right,vector<int>& tmp) 
{
   if(left >= right)
    return;
   int mid = (left + right)/2;
  //遞歸+分治
   MergeSort(num,left,mid,tmp);  
   MergeSort(num,mid + 1,right,tmp);
   MergeArray(num,left,right,mid,tmp);
}

void MergeSort_begin(vector<int>& num) //歸併排序入口
{
    if(num.empty())
        return ;
    vector<int> tmp(num.size());
    MergeSort(num,0,num.size() - 1,tmp);
}

複雜度分析

歸併排序的最壞時間複雜度爲:O(nlogn),平均複雜度爲:O(nlogn)。

 

5.快速排序

快速排序的思想從本質來講與歸併排序相似,也是分治。只不過快速排序是在數組中選定了一個軸點,比軸點小的數字劃分到左邊,比軸點大的劃分到右邊,這樣一個數組就被劃分紅了兩部分,而後在這兩部分基礎上繼續選擇一個軸點劃分,如此直到沒法劃分爲止。

排序過程能夠參考以下圖

參考代碼

注:這裏是選取每一個數組最左邊的數字爲軸點

int qs_partition(vector<int>& num,int left,int right)
{
  int pivot = num[left];
  int l = left,r =right;
  while(l < r)
  {
      while(l < r && num[r] >= pivot) r--;
      if(l < r)
        num[l++] = num[r];
      while(l < r && num[l] < pivot) l++;
      if(l < r)
        num[r--] = num[l];
  }
  num[l] = pivot;
  return l;
}

void quickSort(vector<int>& num,int left,int right)
{
  if(left > right)
    return;
  int k = qs_partition(num,left,right);
  quickSort(num,left,k - 1);
  quickSort(num,k + 1,right);
}

上述的爲遞歸的快速排序,若是數據量很是大可能會致使棧內存爆掉,因此能夠用一個棧來實現非遞歸的快速排序。

非遞歸的快速排序參考代碼

int qs_partition(vector<int>& num,int left,int right)
{
  int pivot = num[left];
  int l = left,r =right;
  while(l < r)
  {
      while(l < r && num[r] >= pivot) r--;
      if(l < r)
        num[l++] = num[r];
      while(l < r && num[l] < pivot) l++;
      if(l < r)
        num[r--] = num[l];
  }
  num[l] = pivot;
  return l;
}

void stack_quickSort(vector<int>& num)
{
    int left = 0,right = num.size() - 1;
    if(left > right)
        return;
    stack<int> stk;
    int l,r;
    stk.push(right);
    stk.push(left);
    while(!stk.empty())
    {
        l = stk.top();stk.pop();
        r = stk.top();stk.pop();
        if(l < r)
        {
            int k = qs_partition(num,l,r);
            stk.push(k - 1);stk.push(l);
            stk.push(r);stk.push(k + 1);
        }
    }
}

複雜度分析  

快速排序的最壞時間複雜度爲:O(n2),即軸點的左側或者右側沒有數字。最好的狀況是左右兩側數字大體相同,平均複雜度爲:O(nlogn)。

補充

快速排序的軸點選擇對於該排序算法的效率有很大的影響,最普通的選取最左或者最右的數字做爲軸點的方法其實不太穩定,經常使用的選取軸點的方法是隨機取值或者三值取中。

三值取中:顧名思義,在最左、最右、中間三個位置選取三個數字,而後在三個數字中選取值居於中間的那個數字做爲軸點。

 

參考資料

《數據結構、算法與應用——C++描述》   做者:【美】 薩特吉·薩尼       機械工業出版社

  Visualgo算法可視化網站

相關文章
相關標籤/搜索