排序与二分

一、排序

1、快速排序——分治法——时间复杂度nlog2n(平均时间复杂度,最坏情况为n2

1、思路:

1、确定分界点,q[l]、q[r]、q[(l+r)/2]
2、调整区间,调整完后分界点左边都是小于等于x的数,右边都是大于等于x的数
3、递归处理左右两段


用i指针指向大于x的数,j指针指向小于x的数。

代码模板:

#include<iostream>
using namespace std;
const int N=1e6+10;
int n,q[N];
void quick_sort(int q[],int l,int r)
{
	if(l>=r)//只有一个元素 
		return ;
	int x=q[l],i=l-1,j=r+1;//x的位置看情况选,由于ij每次交换完都会向x移动一次,所以先让i=l-1,j=r+1 
	while(i<j)
	{
		do i++; while(q[i]<x);//找到大于x的值的位置 
		do j--; while(q[j]>x);//找到小于x的值的位置 
		if(i<j)	/*如果i在j的左边,即两个指针没有相遇,
			此时i指的数应该放在右边,j指的数应该放在左边,交换两个数的值*/ 
			swap(q[i],q[j]);//没有swap可以手写交换函数 
	} 
	quick_sort(q,l,j);//写j的话x不能取q[r],要不然也会无限递归			
	quick_sort(q,j+1,r);
  /*也可以写 quick_sort(q,l,i-1);						
			 quick_sort(q,i,r);
    但是这也写x不能等于q[l]会有边界问题,无限递归 
  */
}
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;
}

2、归并排序——分治法——时间复杂度为nlog2n

思路:

1、先确定分界点,只能是中间值,即(l+r)/2
2、然后左右两边分别排序形成两个序列
3、然后归并合二为一成新序列res。这一步时间复杂度是O(n)的
左右两个序列排完后,
用两个指针分别指向两个序列第一个元素,然后比较,将较小的元素输到新序列res中
较小元素的那个序列指针后移一位,继续比较,将较小的元素输到新序列res中,重复
如果遇到元素相等,将左边序列后移。

代码模板:

#include<iostream>
using namespace std;
const int N=1e6+10;
int n,q[N],tem[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;					//k计数,i为左边序列指针,j为右边序列指针 
	while(i<=mid&&j<=r)
	{
		if(q[i]<=q[j])						//左边序列值更小或者相等 
			tem[k++]=q[i++];				//把左边序列的值放入新数列 
		else								//右边序列值更小
			tem[k++]=q[j++];				//把右边序列的值放入新数列 
	}
	while(i<=mid)							//如果左边序列没有放完,右边放完了,把左边剩下的依次输入到新数列里 
		tem[k++]=q[i++];
	while(j<=r)								//同理右边 
		tem[k++]=q[j++];
	for(i=l,j=0;i<=r;i++,j++)				//用新数列给原数列赋值 
		q[i]=tem[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;
}

二、二分

总说

原理:如果确定f(x)在区间[a, b]内连续,且f(a) ∙ f(b) < 0,则至少有一个实根。二分法的操作,就是把[a, b]逐次分半,检查每次分半后区间两端点函数值符号的变化,确定有根的区间。
一般来说什么情况下用二分?
两个条件:上下界[a, b]确定、函数在[a, b]内单调。

1、整数二分

思路:

通过选定一个性质,把一个序列分为两部分,一边满足这个性质,另一边不满足这个性质,然后就可以找到这两个边界点。
第一种情况,要找红色部分的右分界点。
1、先取mid=(l+r+1)/2                                                        //如果不加1,当r=l+1时,会出现死循环
2、写check函数
3、判断mid是否在红色区域里,
    如果在红色区域,将区间缩小为[mid,r],通过l=mid来更新
       如果不在,将区间缩小为[l,mid-1],通过r=mid-1更新
第二种情况,要找绿色部分的左分界点
1、先取mid=(l+r)/2
2、写check函数
3、判断mid是否在绿色区域里
       如果在绿色区域,将区间缩小为[l,mid],通过r=mid来更新
       如果不在,将区间缩小为[mid+1,r],通过l=mid+1更新

代码模板:


#include<iostream>
using namespae std;
const int N=1e5+10;
int n,m,q[N];
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
		scanf("%d",&q[i]);
	while(m--)								//m次询问 
	{
		int x;								//x代表询问的值 
		scanf("%d",&x);
		int l=0,r=n-1;						//区间范围刚开始为整个区间 
		while(l<r)							//当l与r没有相遇时一直进行二分,先求左分界点,这点的后面都是大于等于x的 
		{
			int mid=(l+r)>>1;				//位运算>> 向右移动一位相当于除2 
			if(q[mid]>=x)					//当所取的中间值大于等于x时, 
				r=mid;						//缩小右边 
			else
				l=mid+1;					//缩小左边 
		}
		if(q[l]!=x)							//当所求出来的点并不等于x时,代表不存在, 
			cout<<"-1 -1"<<endl;
		else								//左分界点存在,接下来求右分界点 
		{
			cout<<l<<" ";
			int 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<<l<<endl;
		}
	}
	return 0;
}

2、浮点数二分

思路:

与整数二分差不多,但是由于浮点数没有明确的界限,所以当我们的区间长度小于10-6的时候,可以认为是一个数,就代表找到了。

代码模板:

求一个数的平方根

#include<iostream>
using namespace std;
int main()				
{
	double x;
	cin>>x;
	int l=0,r=x;
	while(r-l>1e-8)						//如果输出的答案要保留a位小数,那么精度至少要到1e-(a+2)
        //或者直接for(int i=0;i<=100;i++)                         循环100次也行 
	{
		double mid=(l+r)/2;
		if(mid*mid>=x)
			r=mid;
		else
			l=mid;
	}
	printf("%lf\n",l);
	return 0;
} 

全部评论

相关推荐

谁知道呢_:bro不如吃顿疯狂星期四
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务