1.基本算法

基本算法



排序算法

  1. 快速排序
  2. 归并排序

查找算法

  1. 整数二分
  2. 浮点数二分

高精度算法

  1. 高精度加法
  2. 高精度减法
  3. 高精度乘法
  4. 高精度除法

前缀和和差分

  1. 一维前缀和
  2. 二维前缀和
  3. 一维差分
  4. 二维差分

双指针算法

位运算

离散化

区间合并


排序算法

快速排序

**思想:确定一个分界点(分界点可以是左端点,右端点,中点和随机),然后用两个指针从头和尾分别朝中间遍历数据直到两指针相遇,将数据按照与分界点的相对大小放在指针与端点构成的区间内,并递归此操作。
代码:

#include<iostream>
//创建数组
const int N = 100010;
int q[N];

using namespace std;
void quick_sort(int q[] ,int l ,int r)
{
    if( l >= r) return;
    int mid = q[l + r >> 1];//确定分界点
    int i = l - 1 ,j = r + 1;
    //用双指针遍历
    while(i < j)
    {
    	//注意这里要用dowhile语句,否则这里会出现死循环(左右边界问题)
        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] << ' ';
}

归并排序

思想:将一堆数据分成两个部分,将两个部分给重新合并为一个有序的数组并重复该操作。
代码:

#include<iostream>
using namespace std;
const int N = 100010;
int q[N] ,b[N];
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 i = l ,j = mid + 1 ,k = 0 ;
    //将两组数据合成一组有序的数据
    while(i <= mid && j <= r)
    {
        if(q[i] <= q[j])
        {
            b[k ++] = q[i ++];
        }else{
            b[k ++] = q[j ++];
        }
    }
    while(i <= mid) b[k ++] = q[i ++];
    while(j <= r) b[k ++] = q[j ++];
    //把有序的数据放回原数组中
    for(int i = l,j = 0;i <= r;i ++ ,j ++) q[i] = b[j];
}
int main()
{
    int  n;
    cin >> n;
    for(int i = 0;i < n;i ++) cin >> q[i];
    merge_sort(q ,0 ,n  - 1);
    for(int i = 0;i < n;i ++) cout << q[i] << ' ';
}


二分法

二分的实质就是在一堆有一定次序的数中找到目标,查找的方法是通过用一个给定的条件来缩小查找的范围,直到查找到目标或满足退出的条件。
我们可以通过二分法来在给定的答案数范围中寻找正确的答案,即二分答案。

整数二分

代码:

bool check(int x) {/* ... */} // 条件函数



//这两种方法划分的实质就是mid这一个数被分在左区间还是右区间



// 区间[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;
}

浮点数二分

代码:

bool check(double x) {/* ... */} // 条件函数

//因为没有了整数删去小数的限制,这里左右边界都可以是mid

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)//这里也可以让查找循环100次
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}


高精度算法

高精度算法目的是防止因为数据类型的限制让答案出现误差。它的实质就是用数组存储一个较大数的每一位数,用算竖式的方法来进行计算

高精度加法

代码:

#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int> add(vector<int> &a,vector<int> &b)
{
    if(a.size() < b.size()) return add(b ,a);
    vector<int> c;
    int t = 0;
    //t 是进位
    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;
}
int main()
{
    string s1 ,s2;
    vector<int> a ,b;
    cin >> s1 >> s2;
    //因为列竖式是从个位开始算的,所以把个位存在最前面,函数内比较好算。
    for(int i = s1.size() - 1;i >= 0;i --) a.push_back(s1[i] - '0');
    for(int i = s2.size() - 1;i >= 0 ;i --) b.push_back(s2[i] - '0');
    auto  c = add(a ,b);
    for(int i = c.size() - 1;i >= 0;i --) cout << c[i];
}

高精度减法

代码:

#include<iostream>
#include<string>
#include<vector>
using namespace std;

//判断两种情况:1.被减数和减数的长度大小,2.同长度下a和b谁大。

bool check(vector<int> a,vector<int> b)
{
    if(a.size() != b.size()) return a.size() > b.size();
    for(int i = a.size() - 1; i >= 0; i --)
    {
        if(a[i] != b[i]) return a[i] > b[i];
    }
    return true;
    //全部条件都相同就是大小一样
}

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);
        //这样可以避免t是负数时的计算,可以理解成约瑟夫环
        if(t >= 0) t = 0;
        else t = 1;
        //存在借位时 t 借一位
    }
    while(c.back() == 0 && c.size() > 1) c.pop_back();
    //删去前置0
    return c;
}

int main()

{
    vector<int> a ,b ,c;
    string s1 ,s2;
    cin >> s1 >> s2;
    for(int i = s1.size() - 1;i >= 0;i --) a.push_back(s1[i] - '0');
    for(int i = s2.size() - 1;i >= 0;i --) b.push_back(s2[i] - '0');
    if(check(a ,b)) c = sub(a ,b);
    else
    {
        c = sub(b , a);
        cout << '-';
    }
    for(int i = c.size() - 1;i >= 0;i --) cout << c[i];

}
    

高精度乘法

代码:

#include<iostream>
#include<string>
#include<vector>

using namespace std;

vector<int> mul(vector<int> a,int b)
{
    vector<int> c;
    int  t = 0;//t 是进位
    for(int i = 0 ;i < a.size(); i ++)
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    if(t) c.push_back(t);
    while(c.size() > 1 && c.back() == 0) c.pop_back();
    //删去前置0  
    return c;
}

int main()
{
    string s1;
    vector<int> a;
    int b;
	//注意这里只讨论了a 较大 ,b 较小的情况 
    cin >> s1 >> b;
    for(int i = s1.size() - 1; i >= 0; i --) a.push_back(s1[i] - '0');
    auto c = mul(a , b);
    for(int i = c.size() - 1; i >= 0; i --) cout << c[i];
}

高精度除法

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

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;
    }
    
    //这里的反转是为了除掉前置0 , 如果不反转的话输出的数前面会多出0
    reverse(C.begin(), C.end());
    //除去前置0
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    
    return C;
}

int main()
{
    string a;
    vector<int> A;

    int B;
    //注意这里只考虑A 是大数据 ,B是小数据且不考虑正负号。
    cin >> a >> B;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');

    int r;
    auto C = div(A, B, r);

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];

    cout << endl << r << endl;

    return 0;
}


前缀和算法

前缀和的目的是 求出这一个区间内的所有数的和 ,和遍历后求和的方法不同的是这可以用O(1)的时间复杂度来求出值,这在大数据量的情况下非常好用
前缀和

一维前缀和

代码:

#include<iostream>

using namespace std;

const int N = 10e6 + 10;
int a[N] , s[N];
//a是原数组 ,s是前缀和数组
int main()
{
    int n , m , x;
    cin >> n >>m;
    //注意这里的循环是从1开始的 , 目的是 1.防止算前缀和数组的时候下标越界 2.符合前缀和数组的规则	   (就是是第一个数前面的数的和默认是0)。
    for(int i = 1;i <= n;i ++ ) cin >> a[i] , s[i] += s[i - 1] + a[i];
    int l ,r ;
    while(m -- )
    {
        cin >> l >> r;
        cout << s[r] - s[l - 1] << endl;
    }
    
}

二维前缀和

二维前缀和其实是求出一个大矩阵中的子矩阵的和,这更加能够体现前缀和算法在时间复杂度上的优势
二维前缀和
代码:

#include<iostream>
using namespace std;
const int N =1010;
int a[N][N], s[N][N];
int main()
{
    int r , c;
    cin >> r >> c;
    
    for(int x = 1;x <= r; x ++)
    {
        for(int y = 1;y <= c; y ++)
        {
            cin >> a[x][y];
            s[x][y] = s[x - 1][y] + s[x][y - 1] - s[x - 1][y - 1] + a[x][y];
        }
    }
    
    int x1 ,y1 ,x2 ,y2;
    while(cin >> x1 >> y1 >> x2 >> y2) 
    cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
}


差分算法

差分就是前缀和的逆运算,也就是说一个区间内差分数组的前缀和就是它本身
在这里插入图片描述
查分数组的构造:
在这里插入图片描述

一维差分

代码:

#include<iostream>
using namespace std;
const int N = 100010;
int a[N] , b[N];
void insert(int l ,int r , int x)
{
    b[l] += x;
    b[r + 1] -= x;
}
int main()
{
    int m , n ;
    cin >> n >> m;
    for(int i = 1;i <= n; i ++) cin >> a[i] ,  insert(i , i , a[i]);
    //这里的insert(i , i , a[i])是在构造最初的差分数组 ,就是在a[i]这个位置上加上一个值,
    //并使后面的值不变
    
    int l , r ,x;
    
    while(cin >> l >> r >> x) insert(l , r , x);
    //在l 到 r 这个区间上加上一个数 x
    for(int i = 1;i <= n;i ++)
    {
        b[i] += b[i - 1];
        cout << b[i] << ' ';
    }
    
}

二维差分

二维差分就是让一个大矩阵的一个子矩阵中的所有元素同时改变。
我们以一个全为0的矩阵来举例在这里插入图片描述
完成后的结果是红色子矩阵内所有的元素都加上了c。
代码:

#include<iostream>

using namespace std;
const int N = 1010;
int a[N][N] ,b[N][N];

//b是差分数组 , 把一个子矩阵中的元素加上一个数

void insert(int x1 ,int y1 ,int x2 ,int y2 ,int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1] [y2 + 1] += c;
}

int main()
{
    
    int n ,m ,q;
    cin >> n >> m >> q;
    for(int i = 1 ;i <= n;i ++)
    {
        for(int j = 1 ;j <= m;j ++)
        {
            cin >> a[i][j];
            insert(i ,j ,i ,j ,a[i][j]);
        }
    }
    
    int x1 ,y1 ,x2 ,y2 ,c;
    while(cin >> x1 >> y1 >> x2 >> y2 >> c) insert(x1 ,y1 ,x2 ,y2 ,c);

    for(int i = 1;i <= n;i ++)
    {
        for(int j = 1;j <= m;j ++)
        {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
            //就是前缀和,输出修改后的矩阵
            cout << b[i][j] << ' ';
        }
        cout << endl;
    }
    
    return 0;
    
}


双指针算法

双指针算法指的是在遍历对象的过程中,是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的的一种方法。
双指针算法的最终目的是节省程序运行的时间,也就是把暴力解法进行优化。

比如下面的例题

给定一个`长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出其长度

我们可以用暴力的方法来搜索有效的子序列

int main()
{
    int n , maxn = 0;
    cin >> n;
    for(int i = 0;i < n;i ++ ) cin >> q[i];
    for(int i = 0 ,j = 0;i < n ;i ++ )
    {
        for(j = 0;j <= i;j ++)
        {
            if(check(i , j)) maxn = max(maxn , i - j + 1);
            //check函数目的是判断它是不是无重复的区间
        }
    }
    cout << maxn << endl;
}

但这两重循环带来的是极长的运行时间
我们可以看见 i指针 一定在 j指针 之后。双指针算法就是在优化 j指针 的移动上来节省时间。

在运行时我们发现在区间时不满足条件时,j指针一定会向后移动,直至区间满足条件,且下一次区间不满足条件时一定是i指针所在的数出现了重复。

在这里插入图片描述

所以就有了以下解法:

#include<iostream>
using namespace std;
const int N = 100010;
int q[N] ,s[N];
//s数组记录了每一个数出现的个数
int main()
{
    int n;
    cin >> n;
    for(int i = 0;i < n ;i ++) cin >>  q[i];
    int max1 = -1;
    for(int i = 0 ,j = 0; i < n; i ++)
    {
        s[q[i]] ++;
        while(s[q[i]] > 1)
        //就是消除i所在的数的重复
        {
            s[q[j]] --;
            j ++;
        }
        max1 = max(j - i + 1, max1);
    }
    cout << max1 << endl;
}


离散化

离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小来进行对数据的操作
离散化的目的是降低算法的时间复杂度
eg:
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首 先进行 n 次操作,每次操作将某一位置 x 上的数加 c。接下来,进行 m 次询 问,每个询问包含两个整数 lr,你需要求出在区间 [l,r] 之间的所有数的 和。

这种情况下的数据大小是无限的,但他们有相对的大小,就是这组数具有单调性。但是我们用到的数是有限的。我们要节省算法的时间就要把没用的数给剔除。
所以我们就可以创造以下的映射:
在这里插入图片描述
用这种方法(把有改动的数和查询范围的左右边界重新组成一个数组)就可以有效地提高搜索的效率。

代码:

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

typedef pair<int , int> PII;

const int N = 1000010;
vector<PII> add , query;
vector<int> all;
int a[N] , s[N];

int find(int x)
{
    int l = 0,r = all.size() - 1;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(all[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1;
    //我们让映射后的下标从 1 开始,因为我们是用前缀和来求值的
}

int main()
{
    int n , m;
    cin >> n >> m;
    for(int i = 0;i < n;i ++)
    {
    	int x , c;
        cin >> x >> c;
        add.push_back({x , c});
        all.push_back(x);
        //把改动的数放入映射后的数组
    }
    for(int i = 0;i < m;i ++)
    {
        cin >> l >> r;
        query.push_back({l , r});
        all.push_back(l);
        all.push_back(r);
        //把查询范围的左右边界放入映射后的数组
    }
    
    sort(all.begin() , all.end());
    all.erase(unique(all.begin() , all.end()) , all.end());
    //对数据进行排序和去重
    // 结果是让 alls 内的数据按照一定的顺序,下标从 0 开始排成离散化的数字
    
     for(auto item : add)
    {
        int x = find(item.first) , c = item.second;
        a[x] += c;
        //将某一位置 x 上的数加 c
    }

    for(int i = 1;i <= all.size(); i ++) s[i] = s[i - 1] + a[i];
    //前缀和求值
    
    for(auto item : query)
    {
        int l = find(item.first) , r = find(item.second);
        cout << s[r] - s[l - 1] << endl;
    }
    
}


位运算

求n的第k位数字:

x = n >> k & 1

lowbit操作(返回n的最后一位1)
比如 n = 10001000
返回值是1000

lowbit(n) = n & -n


区间合并

区间合并就是把几个区间按照某种性质合并成一个区间
在区间合并时我们会先把区间按照左端点进行排序,然后处理右边界的情况
在这里插入图片描述
代码如下

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

typedef pair<int , int> PII;

vector<PII> segs , res;
//segs 是原来的区间 ,res 是合并后的区间
void merge(vector<PII> segs)
{
    int l = -2e9 , r = -2e9;
    //这里的-2e9是负无穷的意思,可以用其他的大数代替
    sort(segs.begin() , segs.end());
    for(auto seg : segs)
    {
        if(r < seg.first)
        {
            if(l != -2e9) res.push_back({l , r});
            // l != -2e9 的目的是防止把无穷给录进去
            l = seg.first , r = seg.second;
            //更新区间
        }else r = max(r , seg.second);
        //合并区间
    }
    if(l != -2e9) res.push_back({l , r});
    // 1.防止把无穷给录进去 2.把最后一个区间补录进去
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值