【算法】基础算法模板

文章目录

  • 一、快速排序
  • 二、归并排序
  • 三、二分
    • 1. 二分的本质
    • 2. 整数二分
    • 3. 实数二分
  • 四、前缀和
    • 1. 一维前缀和
    • 2. 二维前缀和
  • 五、差分
    • 1. 一维差分
    • 2. 二维差分
  • 六、常用位运算
    • 1. 求二进制的第 k 位
    • 2. lowbit
  • 七、其他常用算法
    • 1. 去重
    • 2. 表达式求值
    • 3. 单调栈
    • 4. 单调队列
    • 5. 并查集

一、快速排序

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

应用:快速选择 第k个数

//如果第k个数在左就在左区间找,在右就在右区间找
//由此保证答案在区间中
int quick_sort(int a[], int l, int r, int k)
{//区间长度为1时就是答案if(l == r) return a[l];int i = l - 1, j = r + 1, x = a[l + r >> 1];while(i < j){while(a[++i] < x);while(a[--j] > x);if(i < j) swap(a[i], a[j]);}//一趟快排后 前j个的数有哪些已经确定int lcnt = j - l + 1;if(k <= lcnt) return quick_sort(a, l, j, k);return quick_sort(a, j + 1, r, k - lcnt);
}

二、归并排序

int tmp[N];
void merge_sort(int a[], int l, int r)
{//递归出口 区间长度为0或1时已经有序if(l >= r) return;//先把左右区间都排好序int mid = (l + r) / 2;merge_sort(a, l, mid);merge_sort(a, 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 tmp[k++] = a[j++];}//扫尾while(i <= mid) tmp[k++] = a[i++];while(j <= r) tmp[k++] = a[j++];//再把辅助数组拷贝给原数组for(int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
}

应用:逆序对的个数

int tmp[N];
int merge_sort(int a[], int l, int r)
{//递归出口 区间长度为0或1时逆序对个数为0if(l >= r) return 0;int mid = l + r >> 1;//先分别求左右区间内部的逆序对个数int res = merge_sort(a, l, mid) + merge_sort(a, 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{tmp[k++] = a[j++];res += mid - i + 1;}}while(i <= mid) tmp[k++] = a[i++];while(j <= r) tmp[k++] = a[j++];for(int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];//最后返回总共的逆序对个数return res;
}

三、二分

1. 二分的本质

根据某种性质,将一段区间分成有这个性质和没有这个性质的两段,二分出的就是这两段的边界。

因此有单调性一定可以二分,没单调性也可能可以二分。

2. 整数二分

  1. 先确定答案所在区间 [ L , R ] [L, R] [L,R]
  2. 考虑用什么性质来二分
  3. 每次更新区间都要包含答案
  4. L = = R L == R L==R 时,区间长度为 1 1 1,就是答案
//第一种写法
while(l < r)
{int mid = (l + r) / 2;if(check(mid)) r = mid;else l = mid + 1;
}//第二种写法
while(l < r)
{int mid = (l + r + 1) / 2;//(1)if(check(mid)) l = mid;//(2)else r = mid - 1;
}
//注意(1)(2)
//当 r = l + 1 时,如果 mid = (l + r) / 2 = l => l = mid = l,就会死循环
//因此改为 mid = (l + r + 1) / 2

二分一定有答案,可以根据二分答案判断题目是否有解。

3. 实数二分

double eps = 1e-8;//经验值,一般比题目保留位数多两位
while(r - l > eps)
{double mid = (l + r) / 2;if(check(mid)) l = mid;else r = mid;
}
//r - l <= eps 时,就认为 l 或 r 是答案了

四、前缀和

建议下标从 1 1 1 开始

1. 一维前缀和

//定义
a[1] + ... + a[i] = s[i]//核心操作
s[i] = s[i - 1] + a[i]
a[l] + ... + a[r] = s[r] - s[l - 1]

2. 二维前缀和

//定义
s[i][j] = 第i行j列格子左上部分所有元素的和//以(x1, y1)为左上角,(x2, y2)为右下角的矩阵的所有元素的和
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]

五、差分

建议下标从 1 1 1 开始

1. 一维差分

//差分就是前缀和的逆运算
b[i] = a[i] - a[i - 1]//核心操作
//给区间[l, r]中的每个数加上c
//只需对差分数组b这样操作
b[l] += c, b[r + 1] -= c
//然后对b求前缀和就是操作后的原数组

2. 二维差分

//给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;

六、常用位运算

1. 求二进制的第 k 位

设最低位为第 0 0 0

//右移 k 位,再 & 1
x >> k & 1

2. lowbit

返回二进制最后一个1

//eg: x = 1110 => -x = 0010
//x & -x = 0010
int lowbit(int x)
{return x & -x;
}

七、其他常用算法

1. 去重

vector<int> v;sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());

2. 表达式求值

#include<bits/stdc++.h>
using namespace std;stack<int> num;//操作数栈
stack<char> op;//运算符栈void eval()
{int b = num.top(); num.pop();int a = num.top(); num.pop();char c = op.top(); op.pop();int res;if(c == '+') res = a + b;else if(c == '-') res = a - b;else if(c == '*') res = a * b;else res = a / b;num.push(res);
}int main()
{//运算符优先级表unordered_map<char, int> p{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};string s; cin >> s;for(int i = 0; i < s.size(); i++){if(isdigit(s[i])){int x = 0, j = i;while(j < s.size() && isdigit(s[j]))x = x * 10 + s[j++] - '0';i = j - 1;num.push(x);}else if(s[i] == '(') op.push(s[i]);else if(s[i] == ')'){while(op.top() != '(') eval();op.pop();}else{while(!op.empty() && op.top() != '(' && p[op.top()] >= p[s[i]]) eval();op.push(s[i]);}}while(!op.empty()) eval();cout << num.top() << '\n';
}

3. 单调栈

常见题型:

  1. 求每个数左边第一个比它小的数
  2. 求每个数左边第一个比它大的数
  3. 求每个数右边第一个比它小的数
  4. 求每个数右边第一个比它大的数

以求每个数左边第一个比它小的数为例。我们只要维护一个栈,每枚举一个数入栈之前,都把栈里不小于它的数弹出,这样每次求一个数左边第一个小于它的数,就只需要取栈顶元素。

题目链接

参考代码:

#include<bits/stdc++.h>
using namespace std;int main()
{int n; cin >> n;stack<int> stk;for(int i = 1; i <= n; i++){int x; cin >> x;while(!stk.empty() && stk.top() >= x) stk.pop();if(stk.empty()) cout << -1 << ' ';else cout << stk.top() << ' ';stk.push(x);}
}

4. 单调队列

常见题型:滑动窗口求最值

首先,滑动窗口可以用一个队列来维护:滑动窗口每次向右走一步,队尾就插入一个数,由于滑动窗口的长度是定值,如果此时队头不合法就要弹出一个数。
暴力的做法是,滑动窗口每走一步,都扫描一遍滑动窗口的区间求最值。显然这种做法是 O ( N 2 ) O(N^2) O(N2) 的。
如何优化呢?以求最大值为例,每枚举一个数入队之前,都把队列里不大于它的数弹出,再将这个数入队,以此来维护一段单调递减的区间。这样滑动窗口每次求最大值,就只需要取队头元素。

题目链接

参考代码:

#include<bits/stdc++.h>
using namespace std;const int N = 1e6 + 10;
int a[N];int main()
{int n, k; cin >> n >> k;for(int i = 1; i <= n; i++) cin >> a[i];deque<int> dq;//队列存下标,才能判断队头合法性//滑动窗口最小值for(int i = 1; i <= n; i++){//判断队头合法性: 右端为i,长度为k的区间:[i - k + 1, i]if(!dq.empty() && dq.front() < i - k + 1) dq.pop_front();//维护队列单调递增while(!dq.empty() && a[dq.back()] >= a[i]) dq.pop_back();dq.push_back(i);//滑动窗口最小值取队头即可if(i >= k) cout << a[dq.front()] << ' ';}cout << '\n';//清空队列dq.clear();//滑动窗口最大值for(int i = 1; i <= n; i++){//判断队头合法性: 右端为i,长度为k的区间:[i - k + 1, i]if(!dq.empty() && dq.front() < i - k + 1) dq.pop_front();//维护队列单调递减while(!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();dq.push_back(i);//滑动窗口最大值取队头即可if(i >= k) cout << a[dq.front()] << ' ';}
}

5. 并查集

题目链接

参考代码:

#include<bits/stdc++.h>
using namespace std;const int N = 1e5 + 10;
int p[N];
//p[x]是x的父亲
//p[x]==x表示x是根//返回根
int find(int x)
{if(p[x] != x) p[x] = find(p[x]);//路径压缩return p[x];
}int main()
{int n, m; cin >> n >> m;//初始化for(int i = 1; i <= n; i++) p[i] = i;while(m--){string s;int a, b;cin >> s >> a >> b;if(s == "M") p[find(a)] = find(b);//合并else{if(find(a) == find(b))//查询cout << "Yes" << '\n';elsecout << "No" << '\n';}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/677094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言安全编码:crypto/sha1库全面解析

Go语言安全编码&#xff1a;crypto/sha1库全面解析 简介SHA-1基础原理和特点SHA-1与其他哈希算法的比较代码示例&#xff1a;基本的SHA-1哈希生成 使用crypto/sha1处理数据处理字符串和文件的SHA-1哈希代码示例&#xff1a;为文件生成SHA-1哈希 常见错误和最佳实践 在实际项目中…

leetcode(双指针)283.移动零(C++详细题解)DAY3

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 示例 1: 输入…

fpga 需要掌握哪些基础知识?

个人根据自己的一些心得总结一下fpga 需要掌握的基础知识&#xff0c;希望对你有帮助。 1、数电&#xff08;必须掌握的基础&#xff09;&#xff0c;然后进阶学模电&#xff0c; 2、掌握HDL&#xff08;verilog或VHDL&#xff09;一般建议先学verilog&#xff0c;然后可以学…

【笔记】Helm-5 Chart模板指南-9 在模板内部访问文件

在模板内部访问文件 在上一节中&#xff0c;我们研究了几种创建和访问模板的方法。这样可以很容易从一个模板导入到另一个模板中。但有时想导入的是不是模板的文件并注入其内容&#xff0c;而无需通过模板渲染发送内容。 Helm提供了通过.Files对象访问文件的方法。不过&#x…

【Make编译控制 06】CMake初步使用

目录 一、概述与安装 二、编译源文件 三、无关文件管理 一、概述与安装 CMake是一个跨平台的项目构建工具&#xff0c;相比于Makefile&#xff0c;CMake更加高级&#xff0c;因为CMake代码在执行的时候是会先翻译生成Makefile文件&#xff0c;再调用Makefile文件完成项目构…

辅警考试怎么搜答案?这4款足够解决问题 #微信#知识分享

对于大学生来说&#xff0c;每天面对各式各样的学习任务和问题&#xff0c;寻找合适的学习资源和工具成了我们的迫切需求。幸运的是&#xff0c;现如今有许多高效且实用的日常搜题和学习软件可以满足我们的需求&#xff0c;助力我们取得更好的学习成果。 1.颐博查题 这是一个…

Django问题报错:TypeError: as_view() takes 1 positional argument but 2 were given

一、错误位置 from django.urls import pathfrom users_app.views import RegisterView, LoginView, LogoutViewapp_name users urlpatterns [path("register/", RegisterView.as_view, name"register"),path("login/", LoginView.as_view, n…

Netty中的常用组件(三)

ChannelPipeline 基于Netty的网路应用程序中根据业务需求会使用Netty已经提供的Channelhandler 或者自行开发ChannelHandler&#xff0c;这些ChannelHandler都放在ChannelPipeline中统一 管理&#xff0c;事件就会在ChannelPipeline中流动&#xff0c;并被其中一个或者多个Chan…

VED-eBPF:一款基于eBPF的内核利用和Rootkit检测工具

关于VED-eBPF VED-eBPF是一款功能强大的内核漏洞利用和Rootkit检测工具&#xff0c;该工具基于eBPF技术实现其功能&#xff0c;可以实现Linux操作系统运行时内核安全监控和漏洞利用检测。 eBPF是一个内核内虚拟机&#xff0c;它允许我们直接在内核中执行代码&#xff0c;而无…

什么是Java中的NIO(New I/O)?与传统的I/O有什么不同?

什么是Java中的NIO&#xff08;New I/O&#xff09;&#xff1f;与传统的I/O有什么不同&#xff1f; Java NIO&#xff08;New I/O&#xff09;是Java 1.4引入的一组用于非阻塞I/O操作的API。与传统的I/O&#xff08;即普通的阻塞I/O&#xff09;相比&#xff0c;Java NIO提供…

CVE-2022-0760 漏洞复现

CVE-2022-0760 NSS [HNCTF 2022 WEEK2]ohmywordpress 【CVE-2022-0760】 题目描述&#xff1a;flag在数据库里面。 开题&#xff1a; 顺着按钮一直点下去会发现出现一个按钮叫安装WordPress 安装完之后的界面&#xff0c;有一个搜索框。 F12看看network。 又出现了这个Wor…

华为数通方向HCIP-DataCom H12-821题库(单选题:461-480)

第461题 以下关于路由策略特点的描述,错误的是哪一项? A、能够修改路由属性,但是不能改变网络流量经过的路径 B、能通过控制路由器的路由表规模,来节约系统资源 C、能通过控制路由的接收、发布和引入,以提高网络的安全性 D、能通过修改路由属性,对网络数据流量可以合理规…

Python爬虫之关系型数据库存储#5

关系型数据库是基于关系模型的数据库&#xff0c;而关系模型是通过二维表来保存的&#xff0c;所以它的存储方式就是行列组成的表&#xff0c;每一列是一个字段&#xff0c;每一行是一条记录。表可以看作某个实体的集合&#xff0c;而实体之间存在联系&#xff0c;这就需要表与…

Java异常的处理 try-catch-finally

目录 什么是异常通过if-else处理异常用if-else堵漏洞的缺点 try-catch例第一种处理第二种处理第三种处理第四种处理 try-catch-finally例 System.exit(0);//终止当前的虚拟机执行 什么是异常 Exception&#xff1a;在程序的运行过程中&#xff0c;发生了不正常的现象&#xff0…

SpringCloud-Ribbon实现负载均衡

在微服务架构中&#xff0c;负载均衡是一项关键的技术&#xff0c;它可以确保各个服务节点间的负载分布均匀&#xff0c;提高整个系统的稳定性和性能。Spring Cloud 中的 Ribbon 就是一种负载均衡的解决方案&#xff0c;本文将深入探讨 Ribbon 的原理和在微服务中的应用。 一、…

12 ABC串口接收原理与思路

1. 串口接收原理 基本原理&#xff1a;通过数据起始位判断要是否要开始接收的数据&#xff0c;通过采样的方式确定每一位数据是0还是1。 如何判断数据起始位到来&#xff1a;通过边沿检测电路检测起始信号的下降沿 如何采样&#xff1a;一位数据采多次&#xff0c;统计得到高…

curl8.6.0 - CURLE_PEER_FAILED_VERIFICATION

文章目录 curl8.6.0 - CURLE_PEER_FAILED_VERIFICATION概述笔记END curl8.6.0 - CURLE_PEER_FAILED_VERIFICATION 概述 在看一个开源工程, 里面用到了curl和openssl, 但是工程使用vcpkg来管理的包, 用CMake来编译 依赖太多了, win10 编译选项为 vs2019 x64/Win32(或者Ninja)…

从零开始:用 Rust 编写你的第一个 Web 服务

Rust 是一种现代、高性能的编程语言&#xff0c;近年来在 Web 开发领域也有了一席之地。本文将介绍如何使用 Rust 编写一个简单的 Web 程序&#xff0c;从搭建开发环境到创建第一个 Web 页面。 1. 开发环境搭建 首先&#xff0c;确保你已经安装了 Rust 工具链。你可以通过在终…

肯尼斯·里科《C和指针》第12章 使用结构和指针(1)链表

只恨当时学的时候没有读到这本书&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; 12.1 链表 有些读者可能还不熟悉链表&#xff0c;这里对它作一简单介绍。链表(linked list)就一些包含数据的独立数据结构&#xff08;通常称为节点&#xff09;的集…

有关网络安全的课程学习网页

1.思科网络学院 免费学习skillsforall的课程 课程链接&#xff1a;Introduction to Cybersecurity by Cisco: Free Online Course (skillsforall.com) 2.斯坦福大学计算机和网络安全基础 该证书对于初学者来说最有价值&#xff0c;它由最著名的大学之一斯坦福大学提供。您可…