八大排序——快速排序/快排优化
八大排序——快速排序/快排优化
目录
一、快速排序定义以及实现
1.1 定义
1.2 代码
1.3 快速排序特点
二、快速排序的优化
2.1 三数取中法
2.2 随机数法
2.3 调用冒泡/直接插入
三、非递归怎么实现快排
一、快速排序定义以及实现
1.1 定义
分治的思想。每一趟将待排序的第一个值看作基准值,通过一趟排序,可以将排序数据分成俩半,左边这一半都小于等于基准值,右边这一半都大于基准值,然后对左右俩半分别递归进行排序,直接数据全部有序(基准值俩边划分好之后,自身是有序的,他就在最终排序好所在的位置上)
1. 选择第一个值作为基准值 从右向左找比基准值小的数 谁不空挪谁
2. 从右向左找比基准值小的数,往左扔,86大于基准值不动,right向前一位,47比基准值小,挪到空位里
3. 右边有空位,从左往右找比基准值大的数往右扔。
4. 只要指针没有相遇就一直这样操作。直到指针相遇,把基准值tmp中的值放到当前空位中
1.2 代码
Partition函数实现方法
int Partition(int arr[], int left, int right)
{int tmp = arr[left];while (left < right)//没相遇{//从右向左找,找一个小于等于基准值的值while (left<right && arr[right] > tmp){right--;}//while结束 有两种可能:1.两个指针相遇了// 2.找到一个小于等于tmp的值if (left == right)//处理情况1{break;}arr[left] = arr[right];//从左向右找,找大于基准值的值while (left<right && arr[left] <= tmp){left++;}//while结束 有两种可能:1.两个指针相遇了// 2.找到一个大于tmp的值if (left == right)//处理情况1{break;}arr[right] = arr[left];}//当最外层while结束,代表两个指针相遇arr[left] = tmp;//== arr[right] = tmp;return left;//== return right
}
void Quick(int arr[], int left, int right)
{int par = Partition(arr, left, right);//3,找第一个找到的基准值下标//最少两个值才需要递归处理if (left < par - 1)//保证左半边至少两个值{Quick(arr, left, par - 1);}if (par + 1 < right)//保证右半边至少两个值{Quick(arr, par + 1, right);}}
1.3 快速排序特点
数据越乱越有序
二、快速排序的优化
快速排序在最坏情况下(例如数组已经有序)时间复杂度会退化为 O(n2),为了降低这种最坏情况出现的概率,可采用以下方法来选择基准元素。
优化思路:把数据打乱,越乱越好,越乱越有可能以基准值为中心均分
2.1 三数取中法
最左端/中间值/最右端,找其中不大不小的那个值放到最左端,最大值放最右端。
三数取中法是指在进行分区操作前,从数组的起始位置、中间位置和末尾位置选取三个元素,然后对这三个元素进行排序,取排序后中间的那个元素作为基准元素。这种方法能让基准元素更接近数组的中位数,使分区后的两个子数组规模更均衡,进而减少最坏情况发生的可能性。
#include <stdio.h>void Swap(int* pa, int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}void Three_Nums_Get_Mid(int arr[], int left, int right)
{//arr[left] arr[mid] arr[right]int mid = (left + right) / 2;if (arr[left] > arr[mid])//保证前两个位置的较大的值放到中间中间{Swap(&arr[left], &arr[mid]);}if (arr[mid] > arr[right])//再比较前两个位置的较大值和最右端的值进行比较,目的是让三个值里面的最大值,放到最右端去{Swap(&arr[mid], &arr[right]);}if (arr[left] < arr[mid])//此时,不大不小的那个值,肯定在前两个位置{Swap(&arr[left], &arr[mid]);}
}int Partition(int arr[], int left, int right)
{int tmp = arr[left];while (left < right)//没相遇{//从右向左找,找一个小于等于基准值的值while (left<right && arr[right] > tmp){right--;}//while结束 有两种可能:1.两个指针相遇了// 2.找到一个小于等于tmp的值if (left == right)//处理情况1{break;}arr[left] = arr[right];//从左向右找,找大于基准值的值while (left<right && arr[left] <= tmp){left++;}//while结束 有两种可能:1.两个指针相遇了// 2.找到一个大于tmp的值if (left == right)//处理情况1{break;}arr[right] = arr[left];}//当最外层while结束,代表两个指针相遇arr[left] = tmp;//== arr[right] = tmp;return left;//== return right
}
void Quick(int arr[], int left, int right)
{if (left < right) {Three_Nums_Get_Mid(arr, left, right);int par = Partition(arr, left, right);//最少两个值才需要递归处理if (left < par - 1)//保证左半边至少两个值{Quick(arr, left, par - 1);}if (par + 1 < right)//保证右半边至少两个值{Quick(arr, par + 1, right);}}
}// 测试代码
int main() {int arr[] = {3, 6, 8, 10, 1, 2, 1};int n = sizeof(arr) / sizeof(arr[0]);Quick(arr, 0, n - 1);for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");return 0;
}
1. swap函数
此函数的作用是交换两个整数的值。它借助一个临时变量
tmp
来完成交换操作
2. Three_Nums_Get_Mid
函数该函数实现了三数取中法。它从数组的起始位置
left
、中间位置mid
和末尾位置right
选取三个元素,经过一系列比较和交换操作,把这三个元素中的中位数放到arr[left]
位置,从而将其作为后续分区操作的基准元素。
3. Partition
函数此函数实现了分区操作。它把
arr[left]
(也就是三数取中法选出的基准元素)作为基准,通过双指针法,从数组的两端向中间遍历,把小于等于基准的元素放到左边,大于基准的元素放到右边,最后将基准元素放到正确的位置,并返回该位置的索引。
4. Quick
函数该函数是快速排序的核心递归函数。它先调用
Three_Nums_Get_Mid
函数选择基准元素,接着调用Partition
函数进行分区操作,最后递归地对分区后的左右子数组进行排序。
2.2 随机数法
2.3 调用冒泡/直接插入
开始时就:当数据量不大时,可以在快速排序中进行判断,转头直接调用冒泡/直接插入
进行partition函数后:判断基准值左右俩部分的数据个数,如果小于一定量,转头直接调用冒泡/直接插入
三、非递归怎么实现快排
#include <iostream>
#include <stack>// 分区函数
int Partition(int arr[], int left, int right)
{int tmp = arr[left];while (left < right) // 没相遇{// 从右向左找,找一个小于等于基准值的值while (left < right && arr[right] > tmp){right--;}// while结束 有两种可能:1.两个指针相遇了// 2.找到一个小于等于tmp的值if (left == right) // 处理情况1{break;}arr[left] = arr[right];// 从左向右找,找大于基准值的值while (left < right && arr[left] <= tmp){left++;}// while结束 有两种可能:1.两个指针相遇了// 2.找到一个大于tmp的值if (left == right) // 处理情况1{break;}arr[right] = arr[left];}// 当最外层while结束,代表两个指针相遇arr[left] = tmp; // == arr[right] = tmp;return left; // == return right
}// 快速排序非递归函数
void Quick_No_Recursion(int arr[], int left, int right)
{std::stack<int> st;st.push(left);st.push(right);while (!st.empty()){int tmp_right = st.top();st.pop();int tmp_left = st.top();st.pop();int par = Partition(arr, tmp_left, tmp_right);// 正确使用当前子数组的边界if (tmp_left < par - 1) {st.push(tmp_left);st.push(par - 1);}if (par + 1 < tmp_right) {st.push(par + 1);st.push(tmp_right);}}
}// 打印数组函数
void printArray(int arr[], int size)
{for (int i = 0; i < size; i++){std::cout << arr[i] << " ";}std::cout << std::endl;
}int main()
{int arr[] = {3, 6, 8, 10, 1, 2, 1};int n = sizeof(arr) / sizeof(arr[0]);std::cout << "排序前的数组: ";printArray(arr, n);Quick_No_Recursion(arr, 0, n - 1);std::cout << "排序后的数组: ";printArray(arr, n);return 0;
}
快速排序非递归函数
Quick_No_Recursion
- 功能:该函数使用栈来模拟递归调用的过程,实现快速排序的非递归版本。
- 实现步骤:
- 创建一个栈
st
,并将整个数组的左右边界left
和right
压入栈中。- 当栈不为空时,从栈中弹出两个元素,分别作为当前子数组的左右边界
tmp_left
和tmp_right
。- 调用
Partition
函数对当前子数组进行分区操作,得到基准值的最终位置par
。- 如果基准值左边的子数组长度大于 1,则将其左右边界压入栈中。
- 如果基准值右边的子数组长度大于 1,则将其左右边界压入栈中。
- 重复步骤 2 - 5,直到栈为空。