今天来进行分治算法的学习~
问题引入:
题目描述:
请在一个有序数组中,找出值x的位置,如果x不在数组中,输出-1。
输入:
第一行,一个整数n,代表数组元素个数第二行,n个数,代表数组的n个元素(n<=1000000)第三行,一个整数x,代表要查找的数
输出:
x在数组中的位置
由于数组过大,使用循环进行遍历并不高效,于是我们考虑如下思想:
对于一个按照升序排列的数组,优先规定其左边界为left,右边界为right,中间mid=(left+right)/2 ,若A[mid]>x则说明mid大了,将右边界移动到mid左边一位。vice versa若A[mid]<x 则说明mid小了,左边界移动到mid右一位。而若A[mid]==x 就是找到啦!
下面进行代码实现,首先我们尝试使用while循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include<iostream> using namespace std; int M[1000000]; int main(){ int n,post=-1; cin>>n; for(int i=0;i<n;i++){ cin>>M[i]; } int x; cin>>x; int left=0,right=n-1; while(left<=right){ int mid=(left+right)/2; if(M[mid]==x){ post=mid+1; break; } else if(M[mid]>x){ right=mid-1; } else if(M[mid]<x){ left=mid+1; } } cout<<post;
return 0; }
|
同样的,我们意识到递归也能解决同样的问题,下面我们试图使用递归来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include<iostream> using namespace std; int M[1000000]; int x; int search(int left,int right){ int mid=(left+right)/2; if(left>right){ return -1; } if(M[mid]==x){ return mid+1; } if(M[mid]>x){ return search(left,mid-1); } if(M[mid]<x){ return search(mid+1,right); } } int main(){ int n; cin>>n; for(int i=0;i<n;i++){ cin>>M[i]; } cin>>x; cout<<search(0,n-1);
return 0; }
|
在完成了基本二分思想的实现之后,我们尝试解决P2249 【深基13.例1】查找 - 洛谷
P2249 【深基13.例1】查找
题目描述
输入 $n$ 个不超过 $10^9$ 的单调不减的(就是后面的数字不小于前面的数字)非负整数 $a1,a_2,\dots,a{n}$,然后进行 $m$ 次询问。对于每次询问,给出一个整数 $q$,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 $-1$ 。
输入格式
第一行 $2$ 个整数 $n$ 和 $m$,表示数字个数和询问次数。
第二行 $n$ 个整数,表示这些待查询的数字。
第三行 $m$ 个整数,表示询问这些数字的编号,从 $1$ 开始编号。
输出格式
输出一行,$m$ 个整数,以空格隔开,表示答案。
输入输出样例 #1
输入 #1
1 2 3
| 11 3 1 3 3 3 5 7 9 11 13 15 15 1 3 6
|
输出 #1
说明/提示
数据保证,$1 \leq n \leq 10^6$,$0 \leq a_i,q \leq 10^9$,$1 \leq m \leq 10^5$
本题输入输出量较大,请使用较快的 IO 方式。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<iostream> using namespace std; int n,m; int M[1100000],N[110000]; int search(int left,int right,int i){ int mid=(left+right)/2; if(left>right){ return -1; } if(M[mid]==N[i]){ if(mid!=0){ if(M[mid-1]==N[i]) return search(left-1,right-1,i); return mid+1; } else return mid+1;
} else if(M[mid]<N[i]){ return search(mid+1,right,i); } else if(M[mid]>N[i]){ return search(left,mid-1,i); } } int main(){ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); cin>>n>>m; for(int i=0;i<n;i++){ cin>>M[i]; } for(int i=0;i<m;i++){ cin>>N[i]; } for(int i=0;i<m;i++){ cout<<search(0,n-1,i); if(i!=m) cout<<" "; }
return 0; }
|
可以看到,即便关闭了iostream的同步,最后一个节点还是会TLE,于是我们需要试图继续提高io的速度,如果io速度提高也不行,我们需要重构算法
改为使用printf和scanf,仍然tle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<cstdio> int n,m; int M[1100000],N[110000]; int search(int left,int right,int i){ int mid=(left+right)/2; if(left>right){ return -1; } if(M[mid]==N[i]){ if(mid!=0){ if(M[mid-1]==N[i]) return search(left-1,right-1,i); return mid+1; } else return mid+1;
} else if(M[mid]<N[i]){ return search(mid+1,right,i); } else if(M[mid]>N[i]){ return search(left,mid-1,i); } } int main(){ scanf("%d %d", &n,&m); for(int i=0;i<n;i++){ scanf("%d",&M[i]); } for(int i=0;i<m;i++){ scanf("%d",&N[i]); } for(int i=0;i<m;i++){ printf("%d",search(0,n-1,i)); if(i!=m-1) printf(" "); }
return 0; }
|
那么只剩下重构算法了,我们不再尝试利用递归,改为重新用loop,刚刚使用递归只是为了熟悉下递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<cstdio> int n,m; int M[1100000],N[110000]; int search(int left,int right,int i){ int post=-1; while(left<=right){ int mid=(left+right)/2; if(M[mid]==N[i]){ post=mid+1; right=mid-1; } else if(M[mid]>N[i]){ right=mid-1; } else if(M[mid]<N[i]){ left=mid+1; } } return post;
} int main(){ scanf("%d %d", &n,&m); for(int i=0;i<n;i++){ scanf("%d",&M[i]); } for(int i=0;i<m;i++){ scanf("%d",&N[i]); } for(int i=0;i<m;i++){ printf("%d",search(0,n-1,i)); if(i!=m-1) printf(" "); }
return 0; }
|
成功AC