快排,归并排序及二分
一.快速排序
1.动态图演示
2.具体过程
3.主要思想
快排属于分治算法,分治算法都有三步:
- 分成子问题
- 递归处理子问题
- 子问题合并
快排一般分为三个步骤:
- 确定分界点,可以选择为最左端,最右端,中间点
- 调整区间,一次递归结束会将区间一分为二
- 递归处理左右两段
4.代码实现
//快排模版
void quick_sort(int q[], int l, int r){
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)//当左指针没有与右指针相遇
{
do i++; while(q[i]<x);
do j--; while(q[j]>x);
if (i < j) swap(q[i],q[j]);
}
quick_sort(q, l, j);//递归左边部分
quick_sort(q, j + 1, r);//递归右边部分
}
5.代码涉及知识点
1.while循环与do-while循环:我看别人博客时,看别人说while的运行时间是do-while的两倍,因此在考虑时间限制时需使用do-while循环,不然会超时。
2.移位运算符:<<与>>,<<为左移运算符,>>为右移运算符。
一个整数每次执行移位运算中的左运算
n
n
n次,相当于这个整数乘以
2
n
2^n
2n;
一个整数每次执行移位运算中的右运算
n
n
n次,相当于这个整数除以
2
n
2^n
2n;
不过这种方式只能用于乘以或除以
2
n
2^n
2n,但是它的效率比乘法运算要高;
我们可以简记为左乘右除。
二.归并排序
1.动态图演示
2.具体过程
3.主要思想
归并排序一般分为三个步骤:
1.确定分界点,数组中间
2.递归排序左边和右边,排序完后左右两边均为有序序列
3.归并——合二为一
4.代码实现
//归并排序模版
void merge_sort(int q[], int l, int r){
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j])
tmp[k ++ ] = q[i ++ ];
else
tmp[k ++ ] = q[j ++ ];
while (i <= mid)
tmp[k ++ ] = q[i ++ ];
while (j <= r)
tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ )
q[i] = tmp[j];
}
5.代码涉及知识点
排序的稳定性:通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。再简单形式化一下,如果 A i = A j A_i = A_j Ai=Aj, A i A_i Ai原来在 A j A_j Aj位置前,排序后 A i A_i Ai还是要在 A j A_j Aj位置前。
三.二分
1.整数二分
1.图片分析
第一种模版代表的是将区间[l,r]分为[l,mid]和[mid + 1,r]。这时我们进行判断时是确定mid是否位于灰色区域。也就是我们从左往右进行判断。
第二章模版代表的是将区间[l,r]分为[l,mid - 1]和[mid,r]。这时我们进行判断时是确定mid是否位于绿色区域。也就是我们从右往左进行判断。
2.两种情况
1.找不满足条件的边界值(左边的灰色区域的右边界)
- 找中间值 mid = (l + r)/2
- if(check(mid))等于true或者是false
check(mid)是检查mid是不是在不满足性质的区间(检查是不是在灰色区间) - 更新l或者r,若check(mid)=true,r=mid;若check(mid)=false,l=mid + 1
2.找满足条件的边界值(右边的绿色区域的左边界)
- 找中间值 mid = (l + r + 1)/2,加1是为了避免陷入死循环
- if(check(mid))等于true或者是false
check(mid)是检查mid是不是在满足性质的区间(检查是不是在绿色区间) - 更新l或者r,若check(mid)=true,l=mid;若check(mid)=false,r=mid - 1
3.主要步骤
- 先写一个check函数
- 判定在check的情况下(true和false的情况下),如何更新区间。
- 找不满足条件的边界值时(从左往右),中间点的更新方式是mid=(l + r)/2
找满足条件的边界值时(从右往左),中间点的更新方式是mid=(l + r + 1)/2
核心思想:假设目标值在闭区间[l,r],每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
4.代码实现
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r){
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r){
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
2.浮点数二分
1.基本思想
浮点数二分不需要考虑那么多情况,也不需要考虑边界问题,只需要每次将l,r进行更改就行。
2.主要步骤
- 先写一个check函数
- 判定在check的情况下(true和false的情况下),如何更新区间。
3.主要思想
浮点数二分最后确定取值不是通过判断相等,而是判断精度,即区间的长度足够小时,我们就认为找到了所求值。一般区间长度我们可以取1e-8。或者是循环进行100次左右,我们也可以认为找到了所求值。
4.代码实现
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}