基础算法1

基础算法1

排序

快排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void quick_sort(int a[],int begin,int end){
if(begin >= end) return ;

int i = begin - 1, j = end + 1, mid = a[ begin + end >> 1];

while(i<j){
do i++;while(a[i]<mid);
do j--;while(a[j]>mid);
if(i < j) swap(a[i],a[j]);
}

quick_sort(a,begin,j);
quick_sort(a,j+1,end); //由于递归时 在begin,j 和 j+1,end 两个区间递归 如果基准点选择了 end 可能会导致死循环 同理 对于 begin, i-1和 i,end 则不能选取 begin 为 基准点
}

归并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void merge_sort(int a[], int begin,int end){
if(begin >= end) return ;
int mid= begin + end >> 1;
merge_sort(a,begin,mid);
merge_sort(a,mid+1,end);

int k=0,i=begin,j=mid+1;
while( i<=mid && j<=end )
if(a[i]<=a[j]) tmp[k++]=a[i++];
else tmp[k++]=a[j++];

while(j<=end) tmp[k++]=a[j++];
while(i<=mid) tmp[k++]=a[i++];

for(i=begin,j=0;i<=end;j++,i++) a[i]=tmp[j];
}

二分

整数二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//区间被分为[l,mid-1],[mid,r]
int bsearch1(int a[],int l,int r){
while(l<r){
int mid= l+r+1 >> 1;
if(check(mid)) l=mid;
else r=mid-1;
}
return l;
}

//区间被分为 [l,mid]和[mid+1,r]
int bsearch2(int a[],int l,int r){
while(l<r){
int mid= l+r >> 1;
if(check(mid)) r=mid;
else l=mid+1;
}
return l;
}

数的范围:给定义一个有序数组,求出输入的数的起始下标和终止下标 若不存在则返回-1 -1。输入格式:先输入两个数,表示数组长度m和数组中被查找的元素的数目n,第二行输入数组各元素 共m个,后续的n行输入具体的被查找的元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <stdio.h>
using namespace std;
const int N=100000;
int a[N];

int main(){
int m,n;
scanf("%d%d",&m,&n);
for(int i=0;i<m;i++) scanf("%d",&a[i]);

while(n--){
int x;
cout<<endl;
scanf("%d",&x);

int l=0,r=m-1;
//寻找左边界 定义check为 a[mid]>= x
while(l<r){
int mid= l+r >>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
//倘若数组中不存在该数
if(a[l]!=x) cout<<"\n-1 -1";
else
{
cout << "\n"<<l<<" ";
//再寻找右边界 定义check为 a[mid]<= x
int l=0,r=m-1;
while(l<r){
int mid = l+r+1 >>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l;
}
}
return 0;
}

浮点数二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//相比整数二分 没那么多边界问题 因此会更加简单 更新区间只要 死板地l=mid 或者 r=mid
//例子:求平方根

int main(){
doub accuracy=1e-6; //两种方法,1.控制精度 2.直接循环N次
double x;
cin>>x;
double l=0,r=x;//此处错误 右端点应该是 max(1,x) 因为当0<x<1时 x的平方根应该是大于自身的 此时将会越界 因此 应该将r设置为max(1,x)
while(r-l>accuracy){
double mid= (l+r)/2;
if(mid*mid >= x) r=mid;
else l=mid;
}
cout<<l;
return 0;
}

习题

第K小数

输入一个序列,求这个序列的第K小数。输入:序列长度 序列各元素 K 输出:第K小数

用快排的思想可以较高效率地求出。有几个问题:

  1. 每一次递归时,子序列的K要不要改变,即K是全局的,还是序列局部的?
  2. 应该如何快速递归应该传入的参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N=100010;
int a[N];

//返回序列a的第K小数
int KthMin(int l,int r,int k){
if(l>=r) return a[l]; //此处用l==r即可 但是在快速排序中 必须是>=

int i=l-1,j=r+1,mid=a[ i+j >>1];

while(i<j){
do i++; while(a[i]<mid);
do j--; while(a[j]>mid);
if(i<j) swap(a[i],a[j]);
}
int leftTotal=j-l+1;
if(leftTotal >= k) return KthMin(l,j,k); //如果左侧的数数量大于等于K 则说明 第K小的数 就在左侧区间 并且 在左侧区间,也是第K小的数
return KthMin(j+1,r,k-leftTotal); //如果左侧的数的数量小于K 则说明 原序列第K小的数 就在右侧区间 并且 在右侧区间,应该是第K-leftTotal小的数
}

int main(){
//数据定义和初始化
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
//输出结果
printf("%d",KthMin(0,n-1,k));
return 0;
}

针对上面的问题,回答是:

K是局部的,需要在子序列中相应调整, 并且要根据子序列在基准的左侧or右侧,做出不同的K值调整,结合图形分析,同样,第二个问题也是通过画图的方式来帮助分析最好。
递归时不能写成:

1
2
3
4
5
if(leftTotal > k) return KthMin(l,j,k);
else if(leftTotal < k) return KthMin(j+1,r,k-leftTotal);
else return a[leftTotal-1];
//本意是想要在k==leftTotal时,就判断 左侧区间的最后一个数 就是要求的数,但是左侧整体不一定是有序的!!!
//由于 左侧的数并非有序的,很可能要求的值在左侧区间某个地方而并非端点,不能通过这种方式就判断出返回值。最终的结果只能是通过区间宽度逼近到1的返回,

逆序对数量

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

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

输入格式

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

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

分治(归并排序)的思想,取区间的中点,那么这个区间的逆序对的数量应该是,左侧区间逆序对的数量+右侧逆序对的数量+跨左右的逆序对的数量,然后对每个子区间按同样的方式来求,知道区间很小,只有两个数字,可以立马得出。然后得到上一层递归的结果,进而得到最终结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//超时代码:由于在比较跨左右的逆序对的数量的 时间性能太差 ,遍历了左右两侧,如果能对左右两侧的序列分别排序,从而找到某个左侧下标后,左侧后续的下标都不必再判断了,这样就能大大提高效率
#include <iostream>
#include <stdio.h>
using namespace std;
typedef long long LL;
const int N= 100010;
int a[N];

LL reversePair(int l,int r){
if(r-l==1) return a[l]>a[r];
if(r<=l) return 0;
int mid = l + r >> 1;
int leftPair=0,rightPair=0,betweenPair=0;
leftPair=reversePair(l,mid);
rightPair=reversePair(mid+1,r);
//跨越左右两侧的情况,依次考虑 右侧 中的每一个数,然后对每一个数 统计 左侧 有多少数大于这个数,然后全部加起来
for(int i=mid+1;i<=r;i++){
for(int leftIndex=l;leftIndex<=mid;leftIndex++){
if(a[leftIndex]>a[i]) {
betweenPair++;
}
}
}
return leftPair+rightPair+betweenPair;
}

int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);

printf("%d",reversePair(0,n-1));
return 0;
}

改进后的代码如下:

思路:仍然是基于分治,在处理跨左右的区域的部分时,会进行排序操作,将左右两端区域都有序化(自底向上的归并排序),再数据量极大时,由于左右两侧的数据都是有序的,因此当找到一个逆序对后,就可以判断左侧剩余的数据都是逆序对,也就是一旦满足a[i]>a[j] 则可以直接求出对于此时右侧那个元素的逆序对的数量了,为:mid-i+1,然后直接复制好元素后移动右侧指针即可。精髓是:一边排序,用来方便上一层(大规模数据)的求解的同时,求出此层的逆序对数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <stdio.h>
using namespace std;
typedef long long LL;
const int N= 100010;
int a[N],tmp[N];

LL reversePair(int l,int r){
if(r<=l) return 0;
int mid = l + r >> 1;
LL leftPair=0,rightPair=0,betweenPair=0;
leftPair=reversePair(l,mid);
rightPair=reversePair(mid+1,r);

int i=l,j=mid+1,k=0;
while(i<=mid && j<=r ){
if(a[i]<=a[j]) tmp[k++]=a[i++];
else{
betweenPair += mid - i + 1; //左侧此后一定都满足逆序对,记录好后 直接移动右侧指针
tmp[k++]=a[j++];
}
}

while(i<=mid) tmp[k++]=a[i++];
while(j<=r) tmp[k++]=a[j++];
for(i=l,j=0;i<=r;i++,j++) a[i]=tmp[j];
return leftPair+rightPair+betweenPair;
}

int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);

printf("%lld",reversePair(0,n-1));
return 0;
}

三次方根

求一个数的三次方根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <stdio.h>
using namespace std;

const double ACCURACY=1e-8;

int main(){
double n;
scanf("%lf",&n);
double l=-100,r=100;
while(r-l>ACCURACY){
double mid = (r+l)/2;
if(mid*mid*mid >= n) r=mid;
else l = mid;
}
printf("%.6lf",l);
}

基础算法1
http://springbeara.top/2024/01/17/算法/acwing_basic/基础算法1/
作者
zyq
发布于
2024年1月17日
许可协议