基础算法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;
}

浙公网安备 33010602011771号