第一课-快速排序,归并排序,二分

排序(快速排序和归并排序)
二分(整数和浮点数)

理解+背模板:
如何提高熟练度,对模板重复使用3~5次。

题目:
一道题目重复写3~5遍


简单模拟一下

 --------------------------------------
 r                                    l

快速排序-分治:
①:确定边界:左边界,右边界,中间边界
q[l] q[r] q[(r+l)/2]

:调整区间:随机取一个数,其实就是pivot,算是一个分界点,把所有比pivot≤的数放在左边,≥放在右边

③:递归处理左右两端,给左边和右边去进行排序


其实②是最重要,也是最难的。如何解决呢?
①:开辟两个数组 :a[ ], b[ ]
②:从q[l+r] 去找,如果
q[i]≤x,x插入a[ ]
q[i]≥x, x插入b[ ]
③:再先把a[ ]放入q[ ]中,然后把b[ ]放入q[ ]中
这种方法比较暴力。

双指针的做法比较巧妙,其实快排还是背模板比较好。
快排是不稳定的,但是可以变成稳定的

#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;
	//确定分界点:是可以随便取得,l,r,(l+r)/2 
	int pivot=q[l]; 
	int i=l-1,j=r+1;//双指针指向两侧 
	while(i<j){
		do i++;while(q[i]<pivot);
		do j--;while(q[j]>pivot);
		if(i<j){
			int t=q[i];
			q[i]=q[j];
			q[j]=t;
		}
	} 
	//递归处理两边 
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}
int main(){
	//当输入数据比较多的时候,建议使用scanf,不然容易阻塞 
	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;
} 

归并排序–分治:
①:确定分界点:mid=(l+r)/2
②:数组按照中间,划分成两半left和right。先递归处理两边,再去做其他操作
②:合二为一

序列一: 1 3 5 7 9

序列二: 2 4 6 8 10

设置一个双指针,分别指向序列一和序列二的起始端。通过比较去进行排序。
归并排序是稳定的
时间复杂度平均是O(nlogn),妥妥的nlogn,分析一定要会分析

模板如下:

#include <iostream>
using namespace std;
const int N=1e6+10;
int n,q[N];
int temp[N];//开辟一个临时数组,存放归并顺序后的数 
void merge_sort(int q[],int l,int r){
	//当前区间之间的只有一个数或者没有数 
	if(l>=r) return;
	//①:确定中点 
	int mid=r+l>>1;//右移一位就是表示除以2
	//②:左右两边去进行递归
	merge_sort(q,l,mid),merge_sort(q,mid+1,r);
	//③:归并去进行比较,双指针,开辟一个新的数组 
	int k=0;//表示临时数组里面有多少个数 
	int i=l,j=mid+1;// i指向左半边的起点,j指向右半边的起点
	while(i<=mid&&j<=r)
		if(q[i]<=q[j]) temp[k++]=q[i++];
		else temp[k++]=q[j++];
	while(i<=mid) temp[k++]=q[i++];
	while(j<=r) temp[k++]=q[j++];
	//把temp里面的数,放回q中
	for(i=l,j=0;i<=r;i++,j++){
		q[i]=temp[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;
}

二分算法:整数二分和浮点数二分
先介绍两个模板:

其中:check函数判断mid是否满足某种性质,和具体题目相关

//区间[l,r]被划分成[l,mid],[mid+1,r]时使用 
int bsearch_1(int l,int r){
	while(l<r){
		int mid=l+r>>1;
		if(cheak(mid)) r=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(cheak(mid)) l=mid;
		else l=mid-1;
	}
	return l;
}

题目有太多了,我以后会尽量去列举一下题目链接:

二分的本质是:(本质并不是单调性)
本质是:
如果有点单调性,一定是可以去进行二分的。
但是可以二分的题目,不一定是要有单调性。

那么如何考虑二分的模板选择?
先写一个cheak()函数,if()的true or false的情况,如何去对区间去进行更新。是
l=mid? 还是r=mid?

例子:

二分的使用 Acwing-789-数的范围
上面的简单应用 leetdode-69. x 的平方根
//二分:整数二分和浮点数二分 
#include <iostream>
using namespace std;
const int N=1e6+10;
int n,m,q[N];
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		scanf("%d",&q[i]);
	}
	while(m--){
		int x;
		cin>>x;
		int l=0,r=n-1;//左边界右边界 
		while(l<r){
			int mid=l+r>>1;//这里加不加一,需要去根据边界确定 
			//check函数判断mid是否满足某种性质,和具体题目相关
			//mid在右半边 
			if(q[mid]>=x) r=mid;//中间点在解的右边 
			else l=mid+1; //往左边退一个 
		}
		if(q[l]!=x) cout<<"-1 -1"<<endl;
		else{
			cout<<l<<' ';
			int l=0,r=n-1;//左边界右边界 
			while(l<r){
			int mid=l+r+1>>1;
			//check函数判断mid是否满足某种性质,和具体题目相关
			//mid在右半边 
			if(q[mid]<=x) l=mid;
			else r=mid-1; 
			}
			cout<<l<<endl;
		}
	}
	return 0;
} 

浮点数二分因为不要处理边界,边界处理上会稍微简单一点,上面给的第二题,就是有点浮点数二分的味道。

举例子:
求一个数的平方根吧。

#include <iostream>
using namespace std;
int main(){
	double x;
	cin>>x;
	double l=0.0,r=x;
	for(int i=0;i<=100;i++){
	//对区间进行了100次划分
		double mid=(l+r)/2;
		if(mid * mid>=x) r=mid; //范围从0~x缩小到[0,mid] 
		else  l=mid; 
	}
	printf("%lf\n",l);
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/weixin_44110100/article/details/106940990