快速排序
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为2个子序列,然后递归地排序两个子序列。
快速排序是一个不稳定的算法,在经过排序之后,可能会对相同值的元素的相对位置造成改变。
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中会消耗一定的空间,在一般情况下的空间复杂度为
O(logn)
,在最差的情况下,若每次只完成了一个元素,那么空间复杂度为O(n)
。所以我们一般认为快速排序的空间复杂度为O(logn)
。
方法一:遍历交换法。
取一个key元素,我一般取数组的最后一个元素。定义一个指针index从头向后遍历,再定义一个指针位pre于第一个元素之前。如果index元素比key小,pre向前移动一位,交换index和key的元素,把小的放前、大的放后。index遍历到最后,将pre+1和key进行交换,这样比key小的都在它前面,比key大的都在它后面了。再继续对左右两边进行递归运算。
#include<iostream>
#include<vector>
using namespace std;
void Swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
// int Partition(int data[], int start, int end){
int Partition(vector<int>& data, int start, int end){
cout << start << " " << end << endl;
int pre = start-1;
for(int index = start; index < end; ++index){
if(data[index] < data[end]){
++pre;
if(pre != index){
Swap(data[index], data[pre]);
}
}
}
++pre;
Swap(data[pre], data[end]);
return pre ;
}
// void QuickSort(int data[], int start, int end){
void QuickSort(vector<int>& data, int start, int end){
if(start < end){
int index = Partition(data, start, end);
if(index > start){
QuickSort(data, start, index-1);
}
if(index < end){
QuickSort(data, index+1, end);
}
}
}
int main(){
int a[7] = {5,3,2,8,1,4,9};
vector<int> input(a, a+7);
int size = input.size();
cout << "before:";
for(int i=0; i<size; ++i){
cout <<input[i] << " ";
}
cout << endl;
QuickSort(input, 0, 6);
cout << "after :";
for(int i=0; i<size; ++i){
cout << input[i] << " ";
}
cout << endl;
return 0;
}
方法二:填坑法
取一个p,我们取第一个元素。将start作为一个坑位,从后向前找,比p大的end拿过来放在坑里,同时end作为一个坑位。再从前向后找,比p小的start拿过来放在坑里,同时start作为一个坑位,直到start=end,将p放在最后的位置。这样比p小的都在它的左边,比p大的都在它的右边,再继续对左右两侧进行递归。
int partition(int a[], int start, int end){
int p = a[start];
while(start < end){
while(a[end] >= p && start < end) end--;
a[start] = a[end];
while(a[start] < p && start < end) start++;
a[end] = a[start];
}
a[start] = p;
return start;
}