快速排序
- 快速排序是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.選擇一個基準值,遍歷整個排序數組.把比基準值小的放到基準值前面,把比基準值大的放到基準值後面.多次排序(遞歸、堆、棧都可以實現)後使得數組變的有序。
- 難點:基準值的選擇和元素與基準值比較後的交換.