目录
一、定义
首先,我们来介绍一下快速排序,
每一趟排序,将第一个值看作基准值,然后从右向左找一个比基准值小的值放在左边,然后再从左向右找一个比基准值大的值放在右边,直到left 与right相遇,(此时会发现,以基准值位分界线,将所有数据分为两个部分,左边部分的值都小于基准值,右边部分的值都大于基准值)
二、实现
2.1递归方式实现
递归实现图解
具体用代码实现起来就是
最重要的部分,划分函数,作用是每次将数组按以基准值位界,左右两部分并将两部分排序
int partition(int *arr,int left,int right) {//最重要部分,划分函数
int tmp = arr[left];
while (left<right) {//确保每次循环最少有两个值
while (left<right&&arr[right]>tmp) {//从右向左找
right--;
}
if (left==right) {//如果left与right相遇就结束循环
break;
}
arr[left] = arr[right];//否则交换两个值
while (left < right && arr[left] <= tmp) {//从左向右找
left++;
}
if (left == right) {//如果left与right相遇就结束循环
break;
}
arr[right] = arr[left];//否则交换两个值
}
arr[left] = tmp;//最后将tmp放回到空位置处
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);//调用Quick进行每一层排序
}
if (par+1<right) {//右部分
Quick(arr,par+1,right);//调用Quick进行每一层排序
}
}
void Quick_Sort(int *arr,int len) {
Quick(arr,0,len-1);
}
void show(int *arr,int len) {//打印函数,不用多说了。
for (int i = 0; i < len ;i++) {
printf("%3d",arr[i]);
}
}
int main() {
int arr[] = {1,5,3,4,8,6,7,5,4};
int len = sizeof(arr) / sizeof(arr[0]);
Quick_Sort(arr,len);
show(arr,len);
}
测试用例:
2.2非递归方式实现
非递归方式实现,就要用到队和栈了,由于之前的基数排序已经使用过队了,我们这里就以栈为例
这里也需要使用到这个划分函数
int partition(int *arr,int left,int right) {//最重要部分,划分函数
int tmp = arr[left];
while (left<right) {//确保每次循环最少有两个值
while (left<right&&arr[right]>tmp) {//从右向左找
right--;
}
if (left==right) {//如果left与right相遇就结束循环
break;
}
arr[left] = arr[right];//否则交换两个值
while (left < right && arr[left] <= tmp) {//从左向右找
left++;
}
if (left == right) {//如果left与right相遇就结束循环
break;
}
arr[right] = arr[left];//否则交换两个值
}
arr[left] = tmp;//最后将tmp放回到空位置处
return left;//返回已经确定好位置的下标
}
注意:这里的入栈顺序非常重要,直接关乎到出栈时左右边界,所以一定要注意,不能把顺序弄反了。
void Qucik_Stark(int *arr,int left,int right) {
stack<int> st;//定义栈
int par = partition(arr,left,right);//接收基准值下标(已经确定位置的)
if (left < par - 1) {//确保左边最少有两个值
st.push(left);//入栈
st.push(par-1);
}
if (right > par + 1) {//确保右边最少有两个值
st.push(par + 1);
st.push(right);
}
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 (right < par + 1) {
st.push(par + 1);
st.push(right);
}
}
}
void Quick_Sort(int *arr,int len) {
Qucik_Stark(arr,0,len-1);
}
void show(int *arr,int len) {
for (int i = 0; i < len ;i++) {
printf("%3d",arr[i]);
}
}
int main() {
int arr[] = {1,5,3,4,8,6,7,5,4};
int len = sizeof(arr) / sizeof(arr[0]);
Quick_Sort(arr,len);
show(arr,len);
}
测试用例:
三、优化
由于我们已经知道,快速排序的优点在于处理的数据越乱越快。所以我们的优化思路就是有三种
3.1 调用其他排序方式
我们知道,当数组越趋近于有序,快速排序的效率越低,所以我们可以直接在数据不多的情况下直接调用选择排序。
int partition(int *arr,int left,int right) {//最重要部分,划分函数
int tmp = arr[left];
while (left<right) {//确保每次循环最少有两个值
while (left<right&&arr[right]>tmp) {//从右向左找
right--;
}
if (left==right) {//如果left与right相遇就结束循环
break;
}
arr[left] = arr[right];//否则交换两个值
while (left < right && arr[left] <= tmp) {//从左向右找
left++;
}
if (left == right) {//如果left与right相遇就结束循环
break;
}
arr[right] = arr[left];//否则交换两个值
}
arr[left] = tmp;//最后将tmp放回到空位置处
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);//调用Quick进行每一层排序
}
if (par+1<right) {//右部分
Quick(arr,par+1,right);//调用Quick进行每一层排序
}
}
void Quick_Sort(int *arr,int len) {
if(len<=1000){//调用选择排序
return Intersect(arr,len)
}
Quick(arr,0,len-1);
}
void show(int *arr,int len) {//打印函数,不用多说了。
for (int i = 0; i < len ;i++) {
printf("%3d",arr[i]);
}
}
int main() {
int arr[] = {1,5,3,4,8,6,7,5,4};
int len = sizeof(arr) / sizeof(arr[0]);
Quick_Sort(arr,len);
show(arr,len);
}
3.2改变首元素(基准值)
我们知道,当数组越趋近于有序,快速排序的效率越低,所以我们设法打乱数据,即让首元素与中间一个差不多大小居中的值与首元素交换,以达到打乱数据的目的(最好的情况是每次归并将数组平均分为两组)
void Three_Num_Mid(int *arr,int left,int right) {//优化二,改变基准值
int mid = (left+right) / 2;
if (arr[left]>arr[mid]) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
if (arr[mid]>arr[right]) {
int tmp = arr[mid];
arr[mid] = arr[right];
arr[right] = tmp;
}
if (arr[left]<arr[mid]) {
int tmp = arr[mid];
arr[mid] = arr[left];
arr[left] = tmp;
}
}
int partition(int *arr,int left,int right) {//最重要部分,划分函数
int tmp = arr[left];
while (left<right) {//确保每次循环最少有两个值
while (left<right&&arr[right]>tmp) {//从右向左找
right--;
}
if (left==right) {//如果left与right相遇就结束循环
break;
}
arr[left] = arr[right];//否则交换两个值
while (left < right && arr[left] <= tmp) {//从左向右找
left++;
}
if (left == right) {//如果left与right相遇就结束循环
break;
}
arr[right] = arr[left];//否则交换两个值
}
arr[left] = tmp;//最后将tmp放回到空位置处
return left;//返回已经确定好位置的下标
}
void Quick(int *arr,int left,int right) {//递归
Three_Num_Mid(arr,left,right);//调用之前写好的改变首元素
int par = partition(arr,left,right);//接收基准值
if (left<par-1) {//左部分
Quick(arr,left,par-1);//调用Quick进行每一层排序
}
if (par+1<right) {//右部分
Quick(arr,par+1,right);//调用Quick进行每一层排序
}
}
void Quick_Sort(int *arr,int len) {
Quick(arr,0,len-1);
}
void show(int *arr,int len) {//打印函数,不用多说了。
for (int i = 0; i < len ;i++) {
printf("%3d",arr[i]);
}
}
int main() {
int arr[] = {1,5,3,4,8,6,7,5,4};
int len = sizeof(arr) / sizeof(arr[0]);
Quick_Sort(arr,len);
show(arr,len);
}
3.3随机种子法
与第二中方法相似,也是为了打乱数据,我们采用随机种子法,即让数组中的随机一个元素与首元素进行交换,以达到打乱数据的目的,与第二个方法过于相似,这里就不写啦,只需要将第二种方法中的改变基准值的那个函数改为求随机种子即可