分类
Level5

基础算法模板

冒泡排序 核心思想是相邻元素之间两两比较,将较大(或较小)的元素逐渐“冒泡”到待排序序列的末尾。具体来说,在第i轮排序中,从下标1开始遍历整个数组,对相邻的元素a[j]和a[j+1]进行比较,如果a[j]>a[j+1],则交换它们的位置。循环执行n-1轮即可完成排序。
需要注意的,在冒泡排序中,如果在某一轮排序中没有进行任何元素交换,说明当前数组已经有序,可以直接退出循环,避免不必要的比较

void bubbleSort(int a[],int n){//从小到大对数组a进行排序
    for(int i=1;i<n; i++) {//比较n-1轮
        bool flag=0; //标记本轮是否进行过交换
        for(int j=1;j<=n-i;j++) { //第i轮比较n-i次
            if(a[j]>a[j+1]){ //相邻元素比较
                swap(a[j],a[j+1]); // 元素交换
                flag=1; //进行了交换,则标记为1
            }
        }
        if(flag==0) break;//本轮没有交换,则数组已经有序
    }
}

选择排序 核心思想是每次在待排序序列中找到最大(或小)值,将其放到有序区间的末尾。

void selectSort(int a[],int n){//从小到大对数组a进行排序
    for(int i=1;i<n;i++) {  
        for(int j=i+1;j<=n;j++){//每轮下来,a[i]是最小值
            if(a[i]>a[j]){  
                swap(a[i],a[j]);//元素交换
            }
        }
    }
}

插入排序 核心思想是将待排序序列分成前后2块,前一块:有序区间;后一块:无序区间。每次从无序区间中选择第一个元素,插入到有序区间中的合适位置,使得插入后有序区间仍然有序。

void insertSort(int a[],int n){
    for(int i=2;i<=n;i++){  
        int curr=a[i]; //选择无序区间的第一个元素
        for(int j=i-1;j>=1;j--){//找到有序区间的合适位置
            if(a[j]<curr){  
                a[j+1]=curr;//插入到合适位置
                break;
            }
            a[j+1]=a[j];
            if(j==1){//如果到了数组头部,不再往前比较
		a[1]=tmp;break;
	    }
        }
    }
}

桶排序 桶排序也叫箱排序。工作原理是:统计每个元素出现的次数,然后利用桶的下标已经有序的原理,按照每个数出现的次数循环输出,因此,桶排序要求数组元素数值必须是小范围内的数

for(int i=1;i<=n;i++){ //假设输入n个数字
    cin>>x;
    a[x]++; //统计x出现的次数
    maxNum=max(a[x],maxNum);//找到最大的数字
}
for(int i=0;i<=maxNum;i++){//maxNum是输入的最大数字
    while(a[i]--){
        cout<<i<<' '; //排序结果
    }
}

计数排序 计数排序的思想:1、计算每个数出现了几次;2、求出每个数出现次数的前缀和;3、利用出现次数的前缀和从右边至左计算每个数的排名。

for(int i=1;i<=n;i++){ //假设输入n个数字
    cin>>a[i];
    cnt[a[i]]++; //统计a[i]出现的次数
    maxNum=max(a[i],maxNum);//找到最大的数字
}
for(int i=1;i<=maxNum;i++){//maxNum是输入的最大数字
    cnt[i]=cnt[i-1]+cnt[i]; //出现次数的前缀和
}
for(int i=n;i>=1;i--){//逆序求出每个数正确的排序位置
    b[cnt[a[i]]--]=a[i]; //逆序是为了保证稳定性
}
for(int i=1;i<=n;i++) cout<<b[i]<<' ';//排序结果

快速排序 选择一个参考值,将数组一分为二,使得数组的左侧都<=mid,数组的右侧都>=mid;然后继续对每个区间重复上述步骤,直到整个数组有序。

void qsort(int a[],int l,int r){ //快速排序
    if(l>=r) return;
    int mid=a[(l+r)/2],i=l,j=r;//mid是参考值选取
    while(i<=j){
        while(a[i]<mid) i++;
        while(a[j]>mid) j--;
        if(i<=j){
            swap(a[i],a[j]);
            i++;j--;
        }
    }
    qsort(l,j);
    qsort(i,r);
}

快排可以直接调用<algorithm>中的sort()函数。
sort(begin(), end(), cmp);
begin() 是待排序数组的首地址
end() 是待排序数组的尾地址的下一个
cmp 是排序规则,默认是升序

归并排序 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

void msort(int a[],int b[],int l,int r){//归并排序
    if(l>=r)return;
    int mid=(l+r)/2,i=l,j=mid+1,k=l;//将数组一分为二
    msort(l,mid);
    msort(mid+1,r);
    while(i<=mid&&j<=r){ //合并左、右区间
        if(a[i]<=a[j]) b[k++]=a[i++];
        else b[k++]=a[j++];
    }
    while(i<=mid)b[k]=a[i],k++,i++;
    while(j<=r)b[k]=a[j],k++,j++;
    for(int i=l;i<=r;i++)a[i]=b[i];
}

堆排序 堆排序是指利用堆这种数据结构所设计的一种排序算法。
(1)将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
(2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
(3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

#include<iostream>
using namespace std;
const int N=1e6+10;
int n,a[N],b[N],k=0; //k当前的堆的长度
void push(int x){ //插入数字 
    a[++k]=x;
    int i=k; 
    while(i>1&&a[i]<a[i/2]){ //小于父节点 
        swap(a[i],a[i/2]);
        i=i/2;
    }
} 
void pop(){ //删除最小数 
    a[1]=a[k];
    k--;
    int i=1;
    while(i<=k&&2*i<=k){ //存在左孩子 
        if(2*i+1<=k&&a[2*i+1]<a[2*i]&&a[2*i+1]<a[i]){//右孩子更小 
            swap(a[i],a[2*i+1]); 
            i=2*i+1; //继续处理右孩子
        }else if(a[2*i]<a[i]){ //只有左孩子更小 
            swap(a[i],a[2*i]); 
            i=2*i; //继续处理左孩子 
        }else{
            break; //如果不比孩子小 
        } 
    }
}
void heapsort(){ //堆排序:从小到大
    for(int i=1;i<=n;i++){
	b[i]=a[1]; //获取队首
	pop();//队首出队
    }
}
int main(){
    scanf("%d",&n); 
    for(int i=1;i<=n;i++){
    	int x;
        scanf("%d",&x); 
        push(x); //插入元素到优先队列 
    }
    heapsort(); 
    for(int i=1;i<=n;i++) printf("%d\n",b[i]);
    return 0;
}

整数二分

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;//check()判断mid是否满足性质
        else r=mid-1;
    }
    return l;
}

浮点数二分

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

高精度加法 以字符串形式读入高精度数字,分别逆序存储到vector<int>中,通过高精度加法得到结果。

//C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A,vector<int> &B){
    if(A.size()<B.size()) return add(B, A);
    vector<int> C;
    int t=0;
    for(int i=0;i<A.size();i++ ){
        t+=A[i];
        if(i<B.size()) t+=B[i];
        C.push_back(t%10);
        t/=10;
    }
    if(t) C.push_back(t);
    return C;
}

高精度减法

// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B){
    vector<int> C;
    for (int i=0,t=0;i<A.size();i++){
        t=A[i]-t;
        if(i<B.size()) t-=B[i];
        C.push_back((t+10)%10);
        if(t<0) t=1;
        else t=0;
    }
    while(C.size()>1&&C.back()==0) C.pop_back();//去前导0
    return C;
}

高精度乘低精度

// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b){
    vector<int> C;
    int t=0;
    for(int i=0;i<A.size()||t;i++ ){
        if(i<A.size()) t+=A[i]*b;
        C.push_back(t%10);
        t/=10;
    }

    while(C.size()>1&&C.back()==0) C.pop_back();//去前导0
    return C;
}

高精度除以低精度

// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A,int b,int &r){
    vector<int> C;
    r=0;
    for(int i=A.size()-1;i>=0;i-- ){
        r=r*10+A[i];
        C.push_back(r/b);
        r%=b;
    }
    reverse(C.begin(),C.end());//反转
    while(C.size()>1&&C.back()==0) C.pop_back();
    return C;
}

一维前缀和

S[i]=a[1]+a[2]+...+a[i]
a[l]+...+a[r]=S[r]-S[l-1] //区间[[l,r]的和

一维差分

给区间[l, r]中的每个数加上c:B[l]+=c, B[r+1]-=c

位运算

求n的第k位数字: n>>k&1
返回n的最后一位1:lowbit(n)=n&-n

双指针算法

for(int i=0,j=0;i<n;i++ ){
    while(j<i&&check(i,j)) j++ ;
    //具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作