數據結構 || 常見的排序算法 @ 快速排序

快速排序

  • 快速排序是Hoare於1962年提出的一種二叉樹結構的交換排序方法.
  • 基本思想:任取待排序元素序列中的某元素作爲基準值,按照該排序碼將待排序集合分割成兩子序列,
  • 特點:左子序列中所有元素均小於基準值,右子序列中所有元素均大於基準值。
  • 結束條件:最左右子序列重複該過程,直到所有元素都排列在相應位置上爲止。

快速排序的流程

  • 這裏假設以數組最右面的值做基準值
    在這裏插入圖片描述
  • 通過上圖我們可以看到,我可以通過遞歸的思想不斷的將大區間分成小區間.
  • 那麼其終止條件是什麼呢?
  • 其實我們可以發現當區間不可再分的時候,遞歸也就無法再繼續下去了.
  • 那麼區間不可再分的條件是什麼呢?
  • 那就是當區間大小爲0 或者 區間大小爲1的時候(既left >= right 的時候)

遞歸的代碼實現

void _QuickSort(int *array,int left,int right){
 //終止條件:size==0 || size==1
 //left ==right 區間內還剩一個數
 //left > right 區間內沒有數
  if(left>=right){
    return ;
  }
  int div;//比基準值小的放其左邊,大的放到後面去,基準值所在的下標
  div=Partion(array,left,right);//遍歷array[left,right]區間,把比其小的放左面,大的放右面
  _QuickSort(array,left,div-1);//分治解決左邊的小區間
  _QuickSort(array,div+1,right);//分治解決右邊的小區間
}

那麼問題來了,非遞歸的方式可不可以實現這個過程呢?

  • 當然可以了,我們可以利用棧的後進先出 思想來完成.也可以通過隊列 先進先出的思想來完成.
  • 具體步驟如下
  • 通過上述遞歸的方式我們發現,要完成排序,我們要先拿到區間兩端數值的下標,再進行排序

說明:先拿到左邊區間兩端的下標值還是右邊區間兩端的下標值都可以,但是必須是同一區間兩端的下標值

使用棧的思想拿到區間兩端下標的實現方式
特點:後進先出

void _QuickSort(int* array,int left,int right){
  stack<int> s;
  s.push(right);
  s.push(left);

  while(!s.empty()){
    int _left=s.top();
    s.pop();
    int _right=s.top();
    s.pop();

    if(_left>=_right){
      continue; 
    }

    int div=Partition(array,_left,_right);

    //div+1 right
    s.push(_right);
    s.push(div+1);
    //_left,div -1
    s.push(div-1);
    s.push(_left);
  }
}

通過隊列拿到區間兩端下標值的實現方式
特點:先進先出

void _QuickSort(int *array,int left,int right){

  
  queue<int> q;
  q.push(left);
  q.push(right);

  while(!q.empty()){
    int _left=q.front();
    q.pop();
    int _right=q.front();
    q.pop();

    if(_left>=_right){
      continue;
    }

    int div=Partition(array,_left,_right);
    //[left,div-1]
    q.push(_left);
    q.push(div-1);
    //[div+1,_right]
    q.push(div+1);
    q.push(_right);   

  } 
}

將區間按照基準值劃分爲左右兩半部分的常見方法

  • 函數Pation的作用說明
  • 1.將區間分成比基準值大的一部分與比基準值小的一部分
  • 2.返回選定的基準值

【說明:下面方式都是將區間最右邊的數作爲基準值】

1.hoare版本
  • 算法思想
  • 1.標記區間中需要排序的開始和結束位置
  • 2.從兩端開始遍歷整個排序區間,將兩端比基準值小的值和比基準值大的值進行交換,重複該步驟
  • 3.最後將標準值(待排序區間的最右邊)與begin停止處的值交換
  • 4.返回選定的標準值
int Partion(int* array,int left,int right){
  int begin=left;
  int end=right;  //不能從right-1開始

  while(begin<end){
    while(begin<end && array[begin]<=array[right]){
      begin++;
      //在遍歷過程中對大於基準值的元素進行定位
      //此處的array[begin]<=array[right]中的"=" 必須存在
      //如果不存在的話會造成基準值右邊區間的死循環
    }
    

    while(begin<end && array[end]>=array[right]){
      end--;
      //在遍歷過程中對小於基準值的元素進行定位
    }

    if(begin!=end){  
      Swap(array+begin,array+end); 
    }//當begin和end兩個數相同的時候,兩者指向相同的值,所以不需要交換
  }

  	Swap(array+begin,array+right);
    //返回當前基準值所在位置,
    return begin;
}
2.挖坑法版本
  • 算法思想
  • 1.標記區間中需要排序的開始和結束位置
  • 2.保存基準值(避免填坑造成的數據覆蓋,導致標準值丟失),挖出填坑的空間
  • 3.從待排序區間前端開始找比標準值大的數,去填右邊區間的坑
  • 4.從待排序區間後端開始找比基準值小的數,去填左邊區間的坑
  • 5.用標準值去填最後一個坑
  • 6.返回標準值
  • 代碼實現
int  Partion_wakeng(int *array,int left,int right){
  int begin=left;
  int end=right;

  int flag=array[right]; //保存基準值,給挖坑目標騰空間
  while(begin<end){

    while(begin<end && array[begin]<= flag){
      begin++;
    }
    //右側坑
    array[end]=array[begin];

    while(begin<end && array[end] >= flag ){
      end--;
    }
   //左側坑
   array[begin]=array[end];
  } 
  array[begin]=flag;
  return begin;
}
3.前後標兵法
  • 算法思想
  • 1.使用標兵指向數組前端,
  • 2.遍歷整個數組,將標兵指向的數據元素和符合條件的元素進行交換,重複該步驟
  • 3.最後交換標兵和標準值
  • 4.返回標準值
int Partion_Point(int *array,int left,int right){
  int d=left;

  for(int i=left;i<right;i++){
    if(array[i]<array[right]){
    if(d!=i){
      Swap(array+d,array+i);
     }
      d++;
    }
  }
  Swap(array+d,array+right);
  return d;
}
我的實現方式
//挖坑法測試
//自己實現的版本
//在遍歷過程中,將基準值和不符合條件的值進行交換
//並不斷更新標準值的下標數據
int  Partion_wakeng(int *array,int left,int right){
  int begin=left;
  int end=right;

  int flag=right; //保存基準值,用來挖坑騰空間
  while(begin<end){

    while(begin<end && array[begin]<= array[flag]){
      begin++;
    }

    if(array[begin]>array[flag]){
      Swap(array+begin,array+flag);
      flag=begin;
    }
    while(begin<end && array[end] >= array[flag] ){
      end--;
    }

    if(array[end]<array[flag]){
      Swap(array+end,array+flag);
      flag=end;
    }
  }
  Swap(array+begin,array+flag);
  return begin;
}

總結

特性總結
  • 1.時間複雜度:O(N*lonN)
  • 2.空間複雜度:O(logN)
  • 3.穩定性:不穩定
個人快排思想:(個人理解)
  • 1.選擇一個基準值,遍歷整個排序數組.把比基準值小的放到基準值前面,把比基準值大的放到基準值後面.多次排序(遞歸、堆、棧都可以實現)後使得數組變的有序。
  • 難點:基準值的選擇和元素與基準值比較後的交換.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章