快排原理——分治
1、從數列中取出一個數作爲基準數(樞軸,pivot)。
2、將數組進行劃分(partition),將比基準數大的元素都移至樞軸右邊,將小於等於基準數的元素都移至樞軸左邊。
3、再對左右的子區間重複第二步的劃分操作,直至每個子區間只有一個元素。
快排最重要的一步就是劃分了。劃分的過程用通俗的語言講就是“挖坑”和“填坑”。
快速排序時間複雜度
快速排序的時間複雜度在最壞情況下是O(N^2),平均的時間複雜度是O(N*lgN)。
這句話很好理解:假設被排序的數列中有N個數。遍歷一次的時間複雜度是O(N),需要遍歷多少次呢?至少lg(N+1)次,最多N次。
(01) 爲什麼最少是lg(N+1)次?快速排序是採用的分治法進行遍歷的,我們將它看作一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹的定義,它的深度至少是lg(N+1)。
因此,快速排序的遍歷次數最少是lg(N+1)次。
(02) 爲什麼最多是N次?這個應該非常簡單,還是將快速排序看作一棵二叉樹,它的深度最大是N。因此,快讀排序的遍歷次數最多是N次。
快速排序穩定性
快速排序是不穩定的算法,它不滿足穩定算法的定義。
算法穩定性 -- 假設在數列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;並且排序之後,a[i]仍然在a[j]前面。則這個排序算法是穩定的!
快排快速原因
快速排序之所比較快,因爲相比冒泡排序,每次交換是跳躍式的。每次排序的時候設置一個基準點,將小於等於基準點的數全部放到基準點的左邊,將大於等於基準點的數全部放到基準點的右邊。這樣在每次交換的時候就不會像冒泡排序一樣每次只能在相鄰的數之間進行交換,交換的距離就大的多了。因此總的比較和交換次數就少了,速度自然就提高了。當然在最壞的情況下,仍可能是相鄰的兩個數進行了交換。因此快速排序的最差時間複雜度和冒泡排序是一樣的都是O(N2),它的平均時間複雜度爲O(NlogN)。
快排示意圖
代碼實現
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
//從小到大排序
//最左邊數字爲pivot
void quickSort(vector<int>& arr, int left, int right){
if(left > right)
return ;
int i, j, pivot, temp;
i = left;
j = right;
pivot = arr[left];
while(i<j){
while(arr[j] >= pivot && i < j)
j--;//從後往前找小於pivot
while(arr[i] <= pivot && i < j)
i++;//從前往後找大於pivot
if(i < j){//交換兩者位置
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//pivot復位
arr[left] = arr[i];
arr[i] = pivot;
quickSort(arr, left, i-1);//遞歸左邊
quickSort(arr, i+1,right);//遞歸右邊
}
void printArr(vector<int>& arr, int len){
for(int i = 0; i < len; i++){
cout<<arr[i]<< " ";
}
cout<<endl;
}
int main(int argc, char** argv) {
const int N = 10;
vector<int> arr = {N,0};
// srand(unsigned int)time(nullptr);
int k;
for( k = 0; k < N; k++){
arr[k] = rand()%10000 + 1;
}
cout<<"排序前"<<endl;
printArr(arr, N);
cout<<"排序後"<<endl;
quickSort(arr, 0, N-1);
printArr(arr, N);
return 0;
}