Bootstrap

day01.快排+归并+二分

一、快速排序(分治)

1.思想

①确定分界点:x=q[l]或mid=q[r]或mid=q[(l+r)/2]
x所取并非索引,而是确确实实的值。
②调整区间。
③递归处理左右区间。

时间复杂度:O(nlogn)

2.模板:

void quick_sort(int q[], int l, int r) {
	if (l >= r) //数组无元素 或 只有一个元素,则直接返回
		return;
	int x = q[l]; //①确定分界点
	int 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);
}

注意:
分界点若选择q[l],则递归时应为 quick_sort(q, l, j); quick_sort(q, j + 1, r);
反之,若分界点为q[r],则递归应为 quick_sort(q, l, i-1); quick_sort(q, i, r);

3.题目(785.快速排序)

785.快速排序

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10; //常量N,为数组分配足够空间
int n;
int q[N];

void quick_sort(int q[], int l, int r) {
	if (l >= r) //数组无元素 或 只有一个元素,则直接返回
		return;
	int x = q[l]; //①确定分界点
	int 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);
}

int main() {
	scanf("%d", &n); //整数个数
	for (int i = 0; i < n; i++)
		scanf("%d", &q[i]); //scanf速度比cin快

	quick_sort(q, 0, n - 1);

	for (int i = 0; i < n; i++)
		printf("%d ", q[i]);
	return 0;
}

二、归并排序(分治)

1.思想

①确定分界点:mid=(l+r)/2
②递归排序left,right
③归并,合二为一

时间复杂度:O(nlogn)

2.模板

void merge_sort(int q[],int l,int r){
	if(l>=r) return;
	int mid=l+(r-l)/2;  //①确定分界点
	merge_sort(q,l,mid); //②递归
	merge_sort(q,mid+1,r);
	
	int k=0;  //存储位置的指针
	int 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];	
	}	
}

3.题目(787.归并排序)

在这里插入图片描述

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10; //常量N,为数组分配足够空间
int n;
int q[N],tmp[N]

void merge_sort(int q[],int l,int r){
	if(l>=r) return;
	int mid=l+(r-l)/2;  //①确定分界点
	merge_sort(q,l,mid); //②递归
	merge_sort(q,mid+1,r);
	
	int k=0;  //存储位置的指针
	int 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]); //scanf速度比cin快

	merge_sort(q, 0, n - 1);

	for (int i = 0; i < n; i++)
		printf("%d ", q[i]);
	return 0;
}

三、二分查找

1.整数二分

整数二分通常是指在一个整数区间中进行二分查找。与二分查找的区别在于,它通常用于解决一些需要求解整数或离散问题的场景,而不一定是查找一个目标元素。

整数二分的典型应用场景:
①求解函数的零点:例如,给定一个函数 f(x),我们想通过二分法来找到 f(x)=0 的解。
②求解最小/最大满足条件的整数值:例如,给定一个区间和一些条件,找到一个满足条件的最小或最大整数值。

整数二分的步骤:
(1)初始化左右边界:给定一个整数区间 [low, high],初始化 low 和 high 的值。
(2)计算中间元素:计算中点 mid = (low + high) / 2,并通过某些条件来判断是否找到了目标解,或者是否应该调整搜索区间。
(3)根据条件调整搜索区间:类似于二分查找,比较 mid 位置的值,并根据条件来调整 low 或 high。
(4)结束条件:满足某个精度或条件时停止。

//模板一、寻找符合左区间条件的数。
//比如:查询符合条件的最小值
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l,int r){
	while(l<r){
		int mid=l+(r-l)/2;
		if(check(mid))
			r=mid;  //true---左区间
		else l=mid+1;  //false--右区间
	}
	return l;
}

//模板二、寻找符合右区间条件的数。
//比如:查询符合条件的最大值
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l,int r){
	while(l<r){
		int mid = (l+r+1)/2;
		if(check(mid))
			l=mid;  //true---右区间
		else r=mid-1;  //false--左区间
	}
	return 1;
}

题目(789.数的范围)
在这里插入图片描述

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;

const int N=1e6+10;
int n, m, x;
int q[N]

int main() {
	scanf("%d %d", &n, &m)
	for (int i = 0; i < n; i++)  //输入数组
		scanf("%d", &q[i])

	while (m--) {  //检索m次元素
		scanf("%d", &x);  //每输入一个检索一次存在范围
		
		int l=0,r=n-1;
		while (l < r) {   //整数二分,查找最小索引
			int mid = l + (r - l) / 2;
			if (q[mid] >= m)
				r = mid;
			else
				l = mid + 1;
		}
		
		//return l; 模板中按理返回l为最小索引,下行直接将l带入不单独返回
		if(q[l]!=x)  //判断该元素是否在数组内存在
			printf("-1 -1");
	
		else{
			printf("%d ",l)  //输出最大索引
			int l=0,r=n-1;
			while (l < r) {   //整数二分,查找最大索引
				int mid = (l+r+1)/2;
				if (q[mid] <= m)
					l = mid;
				else
					r = mid - 1;
			}
			cout<<l<<endl;				
		}
	}
	return 0;
}

2.浮点数二分

实现方式:类似于整数二分查找,但是因为浮点数的精度限制,通常需要设定一个误差范围(例如 ε)。在每一步中,通过对区间的中点进行计算,根据函数值的大小关系来缩小区间,直到区间的宽度小于设定的误差阈值。

结束条件:因为浮点数无法完全精确表示,所以通常使用一个预设的误差阈值(如 1e-6)来判断收敛。(一般将误差范围设置为比小数位数多2)

循环条件是重点 while(r-l>eps)

double bsearch_3(double l,double r){
	const double eps=1e-6;  //定义常数存储 误差;
	while(r-l>eps){   //注意循环条件
		double mid = (l+r)/2;
		if (check(mid))
			r=mid;
		else
			l=mid;
	}
	return l;

题目
求平凡根。

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;

int main() {
	double x;
	scanf("%lf", &x);   //注意格式问题:double→%lf
	double l = 0, r = x, eps = 1e-6;
	while (r - l > eps) {
		double mid = (l + r) / 2;
		if (mid * mid >= x)
			r = mid;
		else
			l = mid;
	}
	printf("%lf", l);
	return 0;
}

四、总结

①快排 与 归并 的实现思想有相似之处,三步走。
②整数二分与平常理解的二分查找(分三类)实现有差别,注意理解。整数二分的两种模板按需使用。
③浮点数二分通过确定误差实现精度要求。

;