void Quick_Sort(int *arr, int len)
{
Quick(arr, 0, len-1);
}
void Quick(int *arr, int left, int right)
{
int par = Partition(arr, left, right);//par是这一趟分割函数中基准值最终所在的下标
//处理基准值左半部分
if(left < par-1)//保证基准值左半部分 至少有两个值
{
Quick(arr, left, par-1);//这时,左半部分有必要进行再次划份
}
//处理基准值右半部分
if(par+1 < right)//保证基准值右半部分 至少有两个值
{
Quick(arr, par+1, right);//这时,右半部分有必要进行再次划份
}
}
//快速排序的分割函数,划份函数 时间复杂度O(n) 空间复杂度O(1)
int Partition(int *arr, int left, int right)
{
//1.将第一个值看做基准值,用tmp保存
int tmp = arr[left];
while(left < right)//只要left和right两个指针没有相遇,则继续往复循环
{
while(left<right && arr[right] > tmp)//如果两个指针还未相遇,且right指向的值大于tmp,则right--
{
right--;
}
//此时while循环结束,只有两个可能:1.两个指针相遇 2.找到了小于tmp的值,由right指向
if(left == right)
{
/*arr[left] = tmp; //arr[right] = tmp; //left == right
return left; //return right;*/
break;
}
arr[left] = arr[right];
while(left<right && arr[left] <= tmp)//如果两个指针还未相遇,且left指向的值小于等于于tmp,则left++
{
left++;
}
//此时while循环结束,只有两个可能:1.两个指针相遇 2.找到了大于tmp的值,由left指向
if(left == right)
{
arr[left] = tmp; //arr[right] = tmp; //left == right
return left; //return right;
//break;
}
arr[right] = arr[left];
}
//此时,代码指向到这一行,代表着最大的while结束了,也就是说两个指针相遇了
//将tmp的值,重新放回来,然后返回基准值的下标
arr[left] = tmp; //arr[right] = tmp; //left == right
return left; //return right;
}
int Partition(int* arr, int left, int right)
{
int temp = arr[left];
while(left < right)
{
while (left<right && arr[right]>temp)
{
right--;
}
if (left == right)
{
break;
}
arr[left] = arr[right];
while (left<right && arr[left]<=temp)
{
left++;
}
if (left == right)
{
break;
}
arr[right] =arr[left];
}
arr[left] = temp;
return left;
}
void Quick(int* arr, int left, int right)
{
int par = Partition(arr, left, right);
if (left < par - 1)
{
Quick(arr, left, par-1);
}
if (right > par + 1)
{
Quick(arr,par+1, right);
}
}
void Quick_sort(int* arr, int len)
{
Quick(arr, 0, len - 1);
}
因为快排数据越乱跑起来越快(数据越乱,就有更大的可能将数据均分为两个部分,时间复杂度接近 n*logn),所以优化的目的都是将数据尽可能地弄乱
当数据量较小时,例如 len<=100,可以不调用快排,直接调用直接插入排序或者冒泡排序(n比较小,n^2也不会太大)
void Quick_Sort(int *arr, int len)
{
//优化1:len如果小于100, 1000, 10000
if(len <= 100)
{
Bubble_Sort(arr, len);
return;
}
Quick(arr, 0, len-1);
}
找到数组中最左边的数,最右边的数,和中间的数,将其按照下面顺序交换位置,目的还是为了让有序的数组变得无序。在调用Quick函数时先调用三数取中函数即可。
//优化2:三数取中法(写法非常的多)
void Get_ThreeNum_Mid(int *arr, int left, int right)
{
int mid = (left+right)/2;
if(arr[left] > arr[mid])//如果左值大于中值
{
int tmp = arr[left];
arr[left] = arr[mid];
arr[mid] = tmp;
}
//此时可以保证左值和中间的值 他俩较小的值 在左边
if(arr[mid] < arr[right])//前两个数的较大值和最右边的值比较
{
int tmp = arr[mid];
arr[mid] = arr[right];
arr[right] = tmp;
}
//此时可以保证 3个数的最大值 在中间
//而我们要的是 不大不小的值 (要么在左边,要么在右边)
if(arr[left] < arr[right])
{
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
//此时,就可以保证 不大不小的值在左边 而且还可以保证最大值在中间, 最小值在右边
}
利用随机数函数,将数组中的元素随机打乱,也是一种思路,了解即可。
利用到了栈这一种数据结构,合理利用栈的特点,将每一次的 left 和 right 入栈,然后出栈调用分割函数。
void Quick_Sort_Stack(int *arr, int len)
{
std::stack<int> st;
st.push(0);
st.push(len-1);
int left = 0;
int right = 0;
while(!st.empty())
{
right = st.top();
st.pop();
left = st.top();
st.pop();
int par = Partition(arr, left, right);
if(left < par-1)//左边部分至少有两个值
{
st.push(left);
st.push(par-1);
}
if(par+1 < right)//右边部分至少有两个值
{
st.push(par+1);
st.push(right);
}
}
}
快排效率较高,应用十分广泛,所以面试官经常考查快排,我们一定要掌握其原理和思想,了解其优化方法能熟练写出递归与非递归的代码。