基础算法Part.1

快速排序

引入视频

基本步骤:选择分界点、调整区间、递归处理左右两段

例.AcWing785 快速排序

给定你一个长度为 $n$ 的整数数列。

请你使用快速排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 $n$。

第二行包含 $n$ 个整数(所有整数均在 $1 \sim 10^9$ 范围内),表示整个数列。

输出格式

输出共一行,包含 $n$ 个整数,表示排好序的数列。

数据范围

$1 \le n \le 100000$

输入样例

3 1 2 4 5

输出样例

1 2 3 4 5

题解

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int q[N];

void quick_sort(int q[],int l,int r)
{
    if(l>=r) return;
    
    int i=l-1,j=r+1,mid=q[l+r>>1];
    while(i<j)
    {
        do i++;while(q[i]<mid);
        do j--;while(q[j]>mid);
        
        if(i<j) swap(q[i],q[j]);
    }
    
    quick_sort(q,l,j);
    quick_sort(q,j+1,r);
}

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++) cin>>q[i];
    
    quick_sort(q,0,n-1);
    
    for(int i=0;i<n;i++) cout<<q[i]<<' ';
    
    return 0;
}

模板

void quick_sort(int q[], int l, int r)//需要三个参数,需要排序的数组 左端点 右端点
{
    if (l >= r) return;//如果递归处理的数组长度为1,停止递归

    int i = l - 1, j = r + 1, x = q[l + r >> 1];//找到设定左端点右端点,找到中间位置值
    while (i < j)//左指针小于右指针时不断循环
    {
        do i ++ ; while (q[i] < x);//在应该比x小的x的左侧找到比x大的值
        do j -- ; while (q[j] > x);//在应该比x大的x的右侧找到比x小的值
        if (i < j) swap(q[i], q[j]);//交换数字
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);//递归处理左右两段
}

归并排序

基本步骤:确定分界点、递归排序、合二为一

例.AcWing787 归并排序

给定你一个长度为 $n$ 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 $n$。

第二行包含 $n$ 个整数(所有整数均在 $1 \sim 10^9$ 范围内),表示整个数列。

输出格式

输出共一行,包含 $n$ 个整数,表示排好序的数列。

数据范围

$1 \le n \le 100000$

输入样例

5
3 1 2 4 5

输出样例

1 2 3 4 5

题解

除了排序部分,主体思路与上一题一致

模板

int tmp[N];//提前申请全局变量用于临时存放归并排序的数据

void merge_sort(int q[], int l, int r)//需要三个参数,需要排序的数组 左端点 右端点
{
    if (l >= r) return;//如果递归处理的数组长度为1,停止递归

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);//对左右两遍进行递归

    int k = 0, i = l, j = mid + 1;
    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 ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];//左右两个数组,可能存在一个数组走完,另一个数组还没走完的情况,将剩余所有数字放入tmp

    for(int i=0;i<k;i++) q[l+i]=tmp[i];//将tmp这段有序序列替换掉原数组里对应位置的数字
}

例.AcWing788 逆序对的数量

给定一个长度为 $n$ 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 $i$ 个和第 $j$ 个元素,如果满足 $i < j$ 且 $a[i] > a[j]$,则其为一个逆序对;否则不是。

输入格式

第一行包含整数 $n$,表示数列的长度。

第二行包含 $n$ 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

$1 \le n \le 100000$,
数列中的元素的取值范围 $[1,10^9]$。

输入样例

6
2 3 4 5 6 1

输出样例

5

题解

主要思路:利用归并排序的特性,在放入tmp前检查两指针位置,就可以得到该对数字中大的数字的逆序对在本段数组的数量

#include <iostream>

using namespace std;

const int N = 100010;

int q[N],tmp[N];
long long ans;

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;
    while(i<=mid&&j<=r)
    {
        if(q[i]<=q[j]) tmp[k++]=q[i++];
        else
        {
            tmp[k++]=q[j++];
            ans+=mid-i+1;
        }
    }
    
    while(i<=mid) tmp[k++]=q[i++];
    while(j<=mid) tmp[k++]=q[j++];
    
    for(i=0;i<k;i++) q[l+i]=tmp[i];
}

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++) cin>>q[i];
    
    merge_sort(q,0,n-1);
    
    cout<<ans;
    
    return 0;
}

二分查找(循环版,非递归)

注意:

1、二分查找为了方便可以直接写在main函数里

2、check()函数应当把区间划为两个且仅有两个完整的部分

3、check()应是对含mid变量的一个表达式进行判定

整数型二分

模板1.找到第一个满足check()条件的位置

把[l,r]划分为[l,mid]与[mid+1,r]

这意味着检查mid,若合法,区间变为[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;
        else l = mid + 1;
    }
    return l;
}

模板2.找到最后一个满足check()条件的位置

把[l,r]划分为[l,mid-1]与[mid,r]

一定要注意r的设置,设置错范围会出问题

这意味着检查mid,若合法,区间变为[mid,r],若不合法,区间变为[l,mid-1]

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

实数型二分

用于求反函数值,或者解方程,取决于check()
比整数型二分简单,不用考虑边界
下面以一例来说明实数型二分

例.AcWing790 数的三次方根

给定一个浮点数 $n$,求它的三次方根。

输入格式

共一行,包含一个浮点数 $n$。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 $6$ 位小数。

数据范围

$-10000 \le n \le 10000$

输入样例

1000.00

输出样例

10.000000

题解

#include <iostream>
using namespace std;

int main()
{
	double x;
	cin >> x;
	double l = -1e4-10,r = 1e4+10;//这里的上下限根据题目的数据范围来
	while (r - l > 1e-6)//可以换成for(int i=0;i<100;i++),因为二分100次是2的100次方,精度足够/更高
	{
		double mid = (l + r)/2;
		if (mid * mid <= x) l = mid; 
		else r = mid;
	}
	cout << l;

	return 0;
}
posted @ 2022-01-01 10:08  泝涉江潭  阅读(104)  评论(0)    收藏  举报