快速排序
一.思路:
1.选择一个基准值(以最右边为例)
2.分割区间:分割区间的结果是
小于等于基准值的 基准值 大于等于基准值的
3.分治:递归处理左右两个区间。
递归边界:
(1)区间内没有数字了
(2)区间内只有一个数字——即有序了。
二.分割区间的三种方法
1.Hover法:
代码:
//1.Hover法
int partition1(int arr[], int left, int right) {
int begin = left;
int end = right;
while (begin < end) {
while (begin < end && arr[begin] <= arr[right]) {
begin++;
}
//此时,该循环走完,begin下标所对应的数字就是大于基准值的
//开始走end
while (begin < end && arr[end] >= arr[right]) {
end--;
}
Swap(arr, begin, end);
}
//大循环走完,begin与end相遇,将begin/end下标所对应的数字与基准值交换
Swap(arr, begin, right);
return begin;
}
2.挖坑法:思路与Hover法一模不一样。
代码:
//2.挖坑法
int partition2(int arr[], int left, int right) {
int begin = left;
int end = right;
int pivot = arr[right];
while (begin < end) {
while (begin < end&&arr[begin] <= pivot) {
begin++;
}
//此时begin下标锁定对应的数字一定大于基准值,直接填到end位置
arr[end] = arr[begin];
//开始走end
while (begin < end&&arr[end] >= pivot) {
end--;
}
//此时,end下标所对应的值一定小于基准值,直接填到begin位置
arr[begin] = arr[end];
}
//此时,begin与end相遇,将基准值填到此处即可
arr[begin] = pivot;
return begin;
}
3.前后下标法
代码:
//3.前后下标法
int partition3(int arr[], int left, int right) {
int d = left;
for (int i = left; i < right; i++) {
if (arr[i] < arr[right]) {
Swap(arr, i, d);
d++;
}
}
Swap(arr, d, right);
return d;
}
三.快排代码
void Swap(int arr[], int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
//2.挖坑法
int partition2(int arr[], int left, int right) {
int begin = left;
int end = right;
int pivot = arr[right];
while (begin < end) {
while (begin < end&&arr[begin] <= pivot) {
begin++;
}
//此时begin下标锁定对应的数字一定大于基准值,直接填到end位置
arr[end] = arr[begin];
//开始走end
while (begin < end&&arr[end] >= pivot) {
end--;
}
//此时,end下标所对应的值一定小于基准值,直接填到begin位置
arr[begin] = arr[end];
}
//此时,begin与end相遇,将基准值填到此处即可
arr[begin] = pivot;
return begin;
}
void QuickSort(int arr[], int left, int right) {
//递归边界:区间内部没有数字或者区间内只有一个数字
if (left > right || left == right) {
return;
}
//1.分割区间
int d = partition2(arr, left, right);
//2.分治左右两个区间
QuickSort(arr, left, d - 1);
QuickSort(arr, d + 1, right);
}
四.存在问题
问题一:
1.问题:当选取的基准值是数组的最值时,就会出现最坏情况,即分割的一半区间没有数字,另一半区间是数组的所有或绝大部分元素。
2.解决:三数取中法:
(1)取区间中间下标的数字,将左右边界与其比较,返回中间大小数字的下标
(2)将选取的基准值与其交换即可
3.代码:
int getmidindex(int* arr,int left,int right){
int mid = left + (right-left) >> 1;
if(arr[left] < arr[right]){
if(arr[mid] < arr[left]){
return left;
}
else if(arr[mid] < arr[right]){
return mid;
}
else{
return right;
}
}
else{
if(arr[mid] < arr[right]){
return right;
}
else if(arr[mid] < arr[left]){
return mid;
}
else{
return left;
}
}
}
最后只要在partition里面先将得到的中间数字与要选择的基准值进行交换即可。
问题二:当区间内部数字过多时,递归的层次较深,性能大打折扣。可以判断如果区间内部数字较少的情况下,可以选择插入排序。
if(right - left <= 16){
insert(arr + left, right - left);
}
五.非递归
1.实现:
(1)将左右边界入栈
(2)取栈顶,出栈,拿到左右边界
(3)判断是否需要对区间进行分割--判断区间内部元素的数目
(4)栈非空,就循环判断分割区间,直到栈空,就代表排序完毕
2.代码:
void QuickSortNor(int* arr, int size) {
int left = 0;
int right = size - 1;
stack<int> s;
s.push(right);
s.push(left);
//栈如果不空,就让其一直循环
while (!s.empty()) {
//先取到栈顶元素
left = s.top();
s.pop();
right = s.top();
s.pop();
//判断是否需要分割区间
if (right - left > 1) {
int mid = partition(arr, left, right);
//处理左侧区间
s.push(mid);
s.push(left);
//右侧区间
s.push(right);
s.push(mid + 1);
}
}
}
六.总结:
1.选择基准值
2.分割区间
3.分治处理左右两个小区间直到区间不必在分割--区间内部没有数字,或只有一个数字