Acwing——算法基础课

第一讲 基础算法

快速排序

快速排序的主要思想基于分治

规定:待排序数组为q,第一个数组元素下标是L,最后一个数组元素下标是R

快速排序的原理:

1.确定分界点。分界点可以是q[L]、q[(L+R)/2]、q[R]或者一个随机的数组元素
2.调整范围,挑选出x(x是个值),使得第一个区间里的所有数都小于等于x,第二个区间里的所有数都大于等于x
3.递归处理左右两个区间

暴力解法:

①开辟额外的数组空间a[]和b[]。

②随后遍历q数组,将所有小于q[x]的元素全部放入a[]中,将所有大于q[x]的元素全部放入b[]中,再把a[]和b[]里的每个元素重新赋值给数组q
 

更优解:

①声明两个指针i、j。i最开始指向第一个元素,j最开始指向最后一个元素。

②先从i开始,如果 q[i] < q[x],那么i向后移动一位(i++),直到 q[i] >= q[x]。

转换到q[j],如果q[j] > q[x],那么j向前移动一位(j–),直到 q[j] <= q[x],swap q[i]和q[j]。

这样,在任意时刻,i前面的所有数都是小于等于x 的,j右边的所有数都是大于等于x的。

两个指针相遇或者穿过时,结束循环。

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5+7;

int q[N];

//快速排序模板
void quickSort(int q[], int l, int r){
	if(l>=r) return;
	int i = l -1,j = r + 1,x=q[l+r>>1];
	while(i < j){
		do i++; while(x > q[i]);
		do j--; while(x < q[j]);
		if(i<j) swap(q[i],q[j]);//容易忽略if条件
	}
	quickSort(q,l,j);
	quickSort(q,j+1,r);
}
int main(){
	int n;
	cin>>n;
	for(int i = 0;i<n;i++) scanf("%d",&q[i]);
	quickSort(q,0,n-1);
	for(int i = 0;i<n;i++) printf("%d ",q[i]);
	return 0;
}

归并排序

归并排序也是分治的思想。

规定:待排序数组为q,第一个数组元素下标是L,最后一个数组元素下标是R

归并排序的原理:

  1. 确定分界点:mid=(l+r)/2
  2. 递归处理左右两边

使用双指针法来把两个有序的数组合并成一个有序数组

#include <iostream>
using namespace std;

const int N=100000;
int n;
int q[N],temp[N];

void merge_sort(int q[],int l,int r){
    if(l>=r) return;
    int mid=(l+r)>>1,k=0,i=l,j=mid+1;
    merge_sort(q,l,mid);
    merge_sort(q,mid+1,r);
    //用双指针法将左右两个有序区间合并
    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];
    return;
}

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;
}

归并排序是稳定的。

稳定:如果原序列里两个数的值是相同的,排序后位置如果不发生变化,那么这个排序就是稳定的。

 整数二分

tips:二分的本质并不是单调性。如果有单调性,就一定可以二分;没有单调性,未必不能二分。

假设有一个已经存在的性质,在左半边区间不满足这个性质,在右半边区间满足这个性质,那么就可以将整个区间一分为二。

那么二分就可以寻找性质的边界,既可以找到左半边靠右的边界点也可以找右半边靠左的边界点。

二分寻找左半边靠右

mid=(l+r+1)/2

if(check(mid)) 来判断mid是否满足性质
如果true,mid的左边都满足左半边性质,说明答案(左半边靠右边界点)在[mid,r](闭区间 包含mid) 更新方式为l=mid,r不变

如果false,mid的右边都不满足左半边性质,说明答案(左半边靠右边界点)在[l,mid-1] 更新方式为r=mid-1,l不变

二分寻找右半边靠左边界点

mid=(l+r)/2
if(check(mid))
如果true,mid的右边都满足右半边性质,说明答案(右半边靠左边界点)在[l,mid],更新方式为r=mid,l不变
如果false,mid的左边都不满足右半边性质,说明答案(右半边靠左边界点)在[mid+1,r],更新方式为l=mid+1,r不变

//整数二分法模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质

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

#include <iostream>

using namespace std;

const int N = 100010;

int q[N],n,m;

int main(){
	cin>>n>>m;
	for(int i = 0; i < n ; i++) cin>>q[i];
	while(m--){
		int x;
		cin>>x;
		int l = 0,r= n-1;
		while(l<r){
			int mid=l+r>>1;
			if(x<=q[mid]) 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;
				if(x>=q[mid]) l = mid;
				else r = mid-1;
			}
			cout<<l<<endl;		
		}
	}
	return 0;
}

浮点二分 

//浮点数二分模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}
#include<iostream>

using namespace std;

int main(){
	double n;
	cin>>n;
	double l = -10000,r=10000;
	while(r-l>1e-8){
		double mid = (l+r)/2;
		if(n<= mid *mid *mid) r=mid;
		else l= mid;
	}
	printf("%lf\n",l);
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值