第一章 基础算法(一)快排、归并、二分

1. 排序

1.1 快速排序 —— 分治
(一)步骤
  1. 确定分界点 x :

(1)q[ l ]

(2)q[ ( l + r ) / 2 ]

(3)q[ r ]

(4)随机

  1. 调整区间 :保证区间左边的数小于等于 x , 区间右边的数大于等于 x

(1)暴力调整区间

​ ① 创建两个数组,a[ ] 、b[ ]

​ ② 扫描 q[ l ~ r ] ,

​ q [ i ] <= x , 放入 a[ ] 中

​ q [ i ] > x , 放入 b[ ] 中

​ ③ 先将 a[ ] 中数放入 q [ ] 中,再将 b[ ] 放入 q[ ] 中

(2)优美解法

​ 使用两个指针,i 指针在左边,j 指针在右边,两个指针同时往中间走。

​ 如果 i 指针指向的数小于 x,i 指针继续向中间(右)移动 ,直到某次 i 指针指向的数大于等于 x , 这个数应该放到区间右边,此时 i 停下,移动 j 。

​ 如果 j 指针指向的数大于 x,j 指针继续向中间(左)移动,直到 j 指针指向的数小于等于 x ,这个数应该放到左半边。

​ 此时,i 指向的数应该放到右边,j 指向的数应该放到左边,交换 i 、 j 指向的数。

​ 交换完后,i 、j 指针各向中间移动一位。

​ i ,j 继续往中间走,直到 i,j 相遇,区间一分为二。

​ (任意时刻都保证 i 指针左边的数小于 x, j 指针右边的数大于 x)

  1. 递归处理左右两端
(二)代码模板
#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 x = q[l],i = l - 1,j = 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);
	/* 
	分界点取左边界,后面的递归取j
	*/
}

int main()
{
	scanf("%d",&n);
	for(int i = 0;i < n;i++)
		scanf("%d",&q[i]);
	quick_sort(q, 0, n-1);
	
	for( i = 0;i < n; i++)
		printf("%d",q[i]);
	return 0;
}
(三)快速排序基本思想

​ 从待排序列中任意选择一个记录,以该记录的关键字作为“枢轴”,凡其关键字小于枢轴的记录均移至枢轴之前;凡关键字大于枢轴的记录均移至枢轴之后。

​ 使一趟排序之后,记录的无序序列q将分割成左右两个子序列,然后分别对分割所得到的两个子序列递归地进行快速排序,直至每个子序列中只含有一个记录为止。

1.2 归并排序——分治
(一)步骤
  1. 确定分界点,mid = (l+r)/2

  2. 先递归排序左右两边,排完序之后左右两边都是有序的序列

  3. 归并:将两个有序的数组合为一个有序的数组(合二为一)

​ 有两个数组a,b,a,b都按照从小到大的顺序排列,现在要将a,b合二为一,要求得到的新数组数据也是由小到大。

​ 创建一个新数组c,指针 i,j分别指向a,b的最左端,比较a[i],a[j],将最小的数放到c中,并将该数所在的数组指针继续向下移。当a,b中的任何一个数组遍历完毕,将另一个数组中的数据直接插到c的后边。

*O(logN)底数是多少?

​ 算法中log级别的时间复杂度都是由于使用了分治思想,这个底数直接由分治的复杂度决定。如果采用二分法,那么就会以2为底数,三分法就会以3为底数,其他亦然。

​ O(log n),而不论对数的底是多少,是对数时间算法的标准记法。

(二)代码模板
#include<iostream>

using namespace std;

const int N = 1000010;

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; // 1.确定分界点(l+r)/2
	merge_sort(q,l,mid);
	merge_sort(q,mid+1,r); // 2.递归排序左右两边(q 不是一个全局变量吗,为什么还要作为参数在这里传递)

	//3.合并
	int k=0; // tmp数组中元素个数
	int i=l,j=mid+1; // i指向左半边的起点,j指向右半边的起点
	while(i <= mid && j<=r)//i <= 左半边边界,j <= 右半边边界
	{
		if(q[i] <= q[j])tmp[k++] = q[i++];
		else tmp[k++] = q[j++];
	}
		while(i <= mid)tmp[k++]=q[i++]; // 左边没有取完(右边已取完)将左边所有元素直接加到tmp后边
		while(j <= r)tmp[k++] = q[j++];
		
		for(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( i = 0;i < n;i++) printf("%d",q[i]);

	return 0;
}
1.3 整数二分

​ 有单调性一定可以二分,可以二分的题目不一定非要有单调性。二分的本质并不是单调性。

​ 在一个区间中如果可以找到一个性质,使右半边满足,左半边不满足,(左右半边没有交点),我们可以将边界点二分出来。

​ 二分的本质是边界

​ 给定一个区间,在区间上定义某种性质,左半边满足性质,右半边不满足,左右两边没有交点。二分就可以寻找左右两边的边界。
在这里插入图片描述

(一)模板
  1. 如何二分出左边边界点:

    ​ 找中间值,判断中间值是否满足左半边性质。

    ​ mid=(l+r**+1**)/2

    ​ 满足:左边 边界点在[mid,r]中,更新:l=mid(mid在左半边区域,左半边区域分界点在mid右边)

    ​ 不满足:[l,mid-1],更新 r=mid-1

    【注】这里的mid为什么等于(l+r+1)/2?

    ​ 如果l=r-1,如果mid这里不加1,那么mid=(l+r)/2=(2l+1)/2=l,如果此时check(mid)结果为true,更新:l=mid,更新后的区间为[l,r],如此会死循环下去。

    ​ 如果mid这里加1,那么mid=(l+r+1)/2=(r-1+r+1)/2=r,更新后的区间为[r,r],循环停止。

  2. 如何二分出右边边界点:

    ​ 找中间值,判断中间值是否满足右半边性质。

    ​ mid=(l+r )/2

    ​ 满足:[l,mid] r=mid

    ​ 不满足:[mid+1,r] l=mid+1

给我们一个二分问题,如何选择用模板1还是2,mid应该如何写?

​ 二分问题先写一个 check函数,想一下check(mid)的true和false情况如何更新。根据l,r的更新情况选择mid。

(二)代码模板
#include<iostream>

using namespace std;

const int N = 100010;

int m,n;
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;  // check(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;
				if(q[mid] <= x)l = mid;
				else r = mid - 1;
			}
			cout<<l<<endl;
		}

	}

	return 0;
}
输入:
5 1
2 2 2 3 4
2
输出:
0 2

check(mid)应该如何写?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xcHA4a3l-1630485387696)(file:///C:\Users\86138\Documents\Tencent Files\2141034490\Image\C2C\8B336B0D2F3B63046C39E25133B454F1.jpg)]

1.4 浮点数二分
#include<iostream>

using namespace std;

int main()
{
	// 求平方根
	double x;
	cin>>x;

	double l=0,r=x;
	while(r-l>1e-8)
	{
		double mid = (l+r)/2;
		if(mid*mid >= x)r=mid;
		else l=mid;
	}
	printf("%lf",l);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值