排序:
1.快排
2.归并排序
二分:
1.整数二分【重点】
2.小数二分(浮点数二分)【相对简单】
快排的基本思想:基于分治
1.确定分界点 x【对于左右边界l和r】
一般四种方式都可以:q[l],q[(l+r)/2],q[r],随机
2.调整区间
使得左边的所有数据都<=x;右边的数据都>=x;
3.递归处理左右两段
采用双指针的方式,可以不用开辟额外的空间
上述过程会涉及到令人不爽的边界问题,一般这种情况常常背一个模板比较好!
#include <iostream> using namespace std; const int N = 1e6+10; int n; int q[N]; //代码模板精华如下: 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); } //至上 int main(){ scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d", &q[i]); quick_sort(q,0,n-1); for(int i = 0; i < n; i++) printf("%d ", q[i]); return 0; }
归并排序:也是分治,但是和快排有区别
快排是可以随机选一个数进行排序,但优先选择中间的数,因为效率相对更高。 然而归并是以整个数组的中间开始,先递归再处理,而快排是先处理再递归。归并排序是稳定的,相同的两个元素,并不改变其位置。
快排的平均复杂度是nlogn,最坏是n方
而归并排序是妥妥的nlogn
1.确定分界点: mid = (l+r)>>1;
2.递归排序left和right
3.归并(合二为一)
#include <iostream> using namespace std; const int N = 1e6+10; int n; int q[N], tmp[N]; //和快排的区别就是多用了一个辅助数组来进行二路归并 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(int i = l, j =0; i <=r; i++,j++) q[i] = tmp[j]; } int main(){ scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d", &q[i]); merge_sort(q,0,n-1); for(int i = 0; i < n; i++) printf("%d ", q[i]); return 0; }
二分算法:
整数二分:
有单调性的题目可以二分,但是可以二分的题目不一定都有单调性。所以二分的本质不是单调性,二是 “边界”。
二分其实是把区间分成两部分,一部分满足条件,一部分不满足。二分可以寻找满足性质的边界。
写算法时候的正常思路:
对于一个区间[l,r]
1.先求mid = l+r >>1;(这里先这么写,因为后面要调整,是否加一的问题)
2.然后写一个check函数,然后想一下这个check函数如果是true或者false的话该如何更新。
if(check(mid)) 看mid是否满足题目中的性质:看模板代码的详细内容。
(注意二分算法是一定有解的,而给的题目可能是无解的,如果无解的话,二分结束的位置,就是从左往右看,第一个大于这个数x的最小值。也就是说,定义了一个性质,性质是有边界的,二分算法是一定可以把这个边界找出来的,所以二分就不需要考虑有没有解的情况了,我们是根据二分出来的结果来判断题目是否有解)
#include <iostream> using namespace std; const int N = 100010; int n,m; int q[N]; int main(){ scanf("%d%d", &n, &m); for(int i = 0; i < n; i++) scanf("%d", &q[i]); while(m--){ int x; scanf("%d", &x); int l = 0, r = n-1; while(l < r){ int mid = l+r >>1; if(q[mid] >=x) r = mid; else l = mid +1; } if(q[l] != x) cout <<"-1 -1"<<endl; else{ cout<<l<<" "; l = 0, r = n-1; while(l < r){ int mid = l+r+1 >>1; if(q[mid] <=x) l = mid; else r = mid - 1; } cout<<r<<endl; } } return 0; }
整数二分的模板:
//区间[l,r]被分成[l,mid]和[mid+1, r]时使用 int bsearch(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(int l, int r){ while(l < r){ int mid = l+r+1>>1; if(check(mid)) l = mid; //check() 判断mid是否满足性质 else l = mid - 1; } return l; //都是返回l,因为while停止的条件就是l = r,所以是一样的 }
浮点数二分:本质上也是一个边界,因为没有整除的影响,所以区间长度可以严格的二分,不需要考虑边界,要时时刻刻保证答案在区间的内部。
举一个开平方的例子:
#include <iostream> using namespace std; int main(){ double x; cin >>x; double l = 0, r = x; while(r - l > 1e-6){ double mid = (l+r) /2; if(mid *mid >=x) r = mid; else l = mid; } printf("%lf\n", l); return 0; }