C++基础算法题模板(未完结)
基础算法
快速排序
#include<iostream>
using namespace std;
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){
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j) swap(a[i],a[j]);
}
quick_sort(a,l,j),quick_sort(a,j+1,r);
}
归并排序
#include<iostream>
using namespace std;
void merge_sort(int a[],int l,int r){
if(l>=r) return ;
int mid=l+r>>1;
merge_sort(a,l,mid),merge_sort(a,mid+1,r);
int i=l,j=mid+1,k=0;
int temp[100001];
while(i<=mid && j<=r){
if(a[i]<a[j]) temp[k++]=a[i++];
else temp[k++]=a[j++];
}
while(i<=mid) temp[k++]=a[i++];
while(j<=r) temp[k++]=a[j++];
for(int i=l,j=0;i<=r;i++,j++){
a[i]=temp[j];
}
}
归并排序典型例题——求逆序对数量
#include<iostream>
using namespace std;
void merge_sort(int a[],int l,int r,long long &sum){//sum逆序对数量,初始化为0
if(l>=r) return ;
int mid=l+r>>1;
merge_sort(a,l,mid,sum),merge_sort(a,mid+1,r,sum);
int i=l,j=mid+1,k=0;
int temp[100001];
while(i<=mid && j<=r){
if(a[i]<a[j]) temp[k++]=a[i++];
else sum+=(mid-i+1), temp[k++]=a[j++];//核心点,注重理解
}
while(i<=mid) temp[k++]=a[i++];
while(j<=r) temp[k++]=a[j++];
for(int i=l,j=0;i<=r;i++,j++){
a[i]=temp[j];
}
}
二分
整数二分
给定一个按照升序排列的长度为 n 的整数数组,以及 q个查询。对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0开始计数)。如果数组中不存在该元素,则返回
-1 -1。输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。第二行包含 n个整数(均在 1∼10000范围内),表示完整数组。接下来 q行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q行,每行包含两个整数,表示所求元素的起始位置和终止位置。如果数组中不存在该元素,则返回
-1 -1。数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000输入样例:
6 3 1 2 2 3 3 4 3 4 5输出样例:
3 4 5 5 -1 -1
#include <iostream>
using namespace std;
//计算左边界
int bsearch1(int a[],int l,int r,int id){
while(l<r){
int mid =l+r>>1;
if(a[mid]>=id) r=mid;//核心向左收缩
else l=mid+1;
}
return l;
}
//计算右边界
int bsearch2(int a[],int l,int r,int id){
while(l<r){
int mid=l+r+1>>1;//若mid =l+r>>1,l=r-1,mid=l,下一步陷入死循环
if(a[mid]<=id) l=mid;//核心向右收缩
else r=mid-1;
}
return l;
}
int main(){
int n,q,a[1000001];
cin>>n>>q;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<q;i++){
int id;
cin>>id;
int m=bsearch1(a,0,n-1,id);
int k=bsearch2(a,0,n-1,id);
if(a[m]!=id) cout<<"-1 -1";
else cout<<m<<" "<<k;
cout<<endl;
}
}
浮点数二分
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;
}
高精度加法
#include<iostream>
using namespace std;
//计算a+b
vestor<int> add(vestor<int>&a,vestor<int>&b){
vestor<int> c;
int t=0;
for(int i=0;i<a.size()||i<b.size();i++){
if(i<a.size()) t+=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%10);//处理进位
t/=10;//处理进位
}
}
int main(){
string a,b;
vector<int> m,n;
cin>>a>>b;
for(int i=a.size()-1;i>=0;i--) m.push_back(a[i]-'0');
for(int j=b.size()-1;j>=0;j--) n.push_back(b[i]-'0');
auto c=add(m,n);
for(int i=c.size()-1;i>=0;i--) cout<<c[i];
}
高精度减法
#include<iostream>
#include<vector>
using namespace std;
//判断a>b,保证a-b>=0,若a<b,计算b-a结果加"-"即可
bool cmp(vector<int>&a,vector<int>&b){
if(a.size()!=b.size()) return a.size()>b.size();
else{
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;
int t=0;
for(int i=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;
}
}
高精度乘法
#include<iostream>
#include<vector>
using namespace std;
//计算a*b
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;
}
//清除前导零如123*0=0,c=[0,0,0],清除后c=[0]
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
高精度除法
#include<iostream>
#include<vector>
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;
}
reverse(c.begin(), c.end());
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
前缀和与差分
一维前缀和
s[i]=s[i-1]+a[i];//前缀和
a[l]+...+a[r]=s[r]-s[l-1];//区间和
二维前缀和
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]
一维差分
B[i]=a[i]-a[i-1]
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
a[i]=a[i-1]+B[i]
二维差分
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 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) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
单链表
实现一个单链表,链表初始为空,支持三种操作:
1.向链表头插入一个数;
2.删除第k个插入的数后面的一个数;
3.在第k个插入的数后插入一个数。
注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,..第n个插入的数。
#include<iostream>
using namespace std;
const int N=100010;
int head,idx,ne[N],e[N];
void init(){
head=-1;
idx=0;
}
void add_to_head(int x){
e[idx]=x;
ne[idx]=head;
head=idx++;
}
void add_to_k(int k,int x){
e[idx]=x;
ne[idx]=ne[k-1];
ne[k-1]=idx++;
}
void del_k(int k){
ne[k-1]=ne[ne[k-1]];
}
滑动窗口
给定一个大小为 n≤106的数组。 有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。 你只能在窗口中看到 k个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子:该数组为
[1 3 -1 -3 5 3 6 7],k为 3。你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
窗口位置 最小值 最大值 [1 3 -1] -3 5 3 6 7 -1 3 1 [3 -1 -3] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7
#include<iostream>
#include<deque>
#include<algorithm>
using namespace std;
const int N=1000010;
int a[N];
int main(){
int n,k;
deque<int> b;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
while( b && b.back()>a[i]) b.pop_back();
b.push_back(a[i]);
if(i-k>=1&&b.front()==a[i-k]) b.pop_front();
if(i>=k) cout<<b.front()<<" ";
}
b.clear();
cout<<endl;
for(int i=1;i<=n;i++){
while( b && b.back()<a[i]) b.pop_back();
b.push_back(a[i]);
if(i-k>=1&&b.front()==a[i-k]) b.pop_front();
if(i>=k) cout<<b.front()<<" ";
}
}
KMP字符串
#include<iostream>
using namespace std;
const int N=1000010;
char p[N],s[N];
int kmp[N];
int n,m;
void KMP(){//构建KMP数组
for(int i=1,j=0;i<n;i++){
while(j&&p[i]!=p[j]) j=kmp[j-1];
if(p[i]==p[j]) j++;
kmp[i]=j;
}
}
int main(){
cin>>n>>p>>m>>s;
KMP();
for(int i=0,j=0;i<m;i++){//匹配字符串
while(j&&s[i]!=p[j]) j=kmp[j-1];
if(s[i]==p[j]) j++;
if(j==n){
cout<<i-j+1<<" ";
j=kmp[j-1];
}
}
}
Trie字符串统计——字典树
维护一个字符串集合,支持两种操作:
I x向集合中插入一个字符串 xQ x询问一个字符串在集合中出现了多少次。共有 N个操作,所有输入的字符串总长度不超过 105,字符串仅包含小写英文字母。
输入格式
第一行包含整数 N,表示操作数。接下来 N行,每行包含一个操作指令,指令为
I x或Q x中的一种。输出格式
对于每个询问指令
Q x,都要输出一个整数作为结果,表示 x在集合中出现的次数。每个结果占一行。
#include<iostream>
using namespace std;
const int N=100010;
int son[N][26],cnt[N],idx;//idx其实就是节点标号,每次++idx就是创建一个新的节点标号
void insert(char *str){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u]) son[p][u]=++idx;//son存储的是当前节点标号p对应元素的下一个节点
p=son[p][u];//跳到当前节点的下一个节点标号
}
cnt[p]++;
}
int find(char *str){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}
int main(){
int n,sum;
char str[N],op;
cin>>n;
for(int i=0;i<n;i++){
cin>>op>>str;
if(op=='I') insert(str);
else if(op=='Q'){
sum=find(str);
cout<<sum<<endl;
}
}
}
字典树——最大异或对
在给定的 N 个整数 A1,A2……A**N 中选出两个进行 xor(异或)运算,得到的结果最大是多少?
输入格式
第一行输入一个整数 N。第二行输入 N个整数 A1~AN。
输出格式
输出一个整数表示答案。
#include<iostream>
#include<algorithm>
using namespace std;
int const N=100010;
int a[N];
int son[31*N][2],idx;
void insert(int x) {
int p = 0; // 根节点
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1; // 取第i位
if (!son[p][u]) son[p][u] = ++idx; // 创建新节点
p = son[p][u]; // 移动到子节点
}
}
int search(int x) {
int p = 0, res = 0;
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1;
if (son[p][!u]) { // 优先选相反的位
res=res*2+1;
p = son[p][!u];
} else {
p = son[p][u];
res=res*2+0;
}
}
return res;
}
int main(void)
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
insert(a[i]);
}
int res=0;
for(int i=0;i<n;i++) res=max(res,search(a[i]));
cout<<res<<endl;
}
并查集——合并集合
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。现在要进行 m个操作,操作共有两种:
M a b,将编号为 a和 b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;2.
Q a b,询问编号为 a和 b的两个数是否在同一个集合中;输入格式
第一行输入整数 n和 *m。接下来 m行,每行包含一个操作指令,指令为
M a b或Q a b中的一种。输出格式
对于每个询问指令
Q a b,都要输出一个结果,如果 a和 b在同一集合内,则输出Yes,否则输出No。每个结果占一行。
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
// 初始化并查集
void init() {
for (int i = 1; i < N; i++) {
a[i] = i; // 初始时每个元素的父节点是自己
}
}
// 查找根节点 + 路径压缩
int find(int x) {
if (x != a[x]) {
a[x] = find(a[x]); // 路径压缩
}
return a[x];
}
// 合并集合
void add(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
a[rootY] = rootX; // 将 rootY 的父节点设为 rootX
}
}
int main() {
init(); // 初始化并查集
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
char op;
cin >> op;
if (op == 'M') {
int b, c;
cin >> b >> c;
add(b, c);
} else if (op == 'Q') {
int b, c;
cin >> b >> c;
int rootB = find(b);
int rootC = find(c);
if (rootB == rootC) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
return 0;
}
堆——堆排序
输入一个长度为 n 的整数数列,从小到大输出前 m小的数。
输入格式
第一行包含整数 n和 m。第二行包含 n个整数,表示整数数列。
输出格式
共一行,包含 m个整数,表示整数数列中前 m 小的数。
#include <iostream>
using namespace std;
const int N=100010;
int a[N],m,n,r;
void down(int x){
int t=x;
if(2*x<=r&&a[x]>a[2*x]) t=2*x;
if(2*x+1<=r&&a[t]>a[2*x+1]) t=2*x+1;
if(x!=t){
swap(a[x],a[t]);
down(t);
}
}
int main(){
cin>>n>>m;
r=n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=n/2;i>=0;i--) down(i);
while(m--){
cout<<a[1]<<" ";
swap(a[1],a[r]);
r--;
down(1);
}
}
模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
- I x,插入一个数 x;
- PM,输出当前集合中的最小值;
- DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
- D k,删除第 k个插入的数;
- C k x,修改第 k个插入的数,将其变为 x ;
现在要进行 N次操作,对于所有第 2个操作,输出当前集合的最小值。
输入格式
第一行包含整数 N
接下来 N行,每行包含一个操作指令,操作指令为 I x,PM,DM,D k 或 C k x 中的一种。
输出格式
对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。
每个结果占一行。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010; // 定义堆的最大容量
int n, m; // n: 操作次数,m: 插入的数的编号
// h: 堆数组,ph: 插入顺序到堆下标的映射,
//hp: 堆下标到插入顺序的映射,size: 堆当前大小
int h[N], ph[N], hp[N], heap_size;
// 交换堆中的两个元素,并维护ph和hp数组
void heap_swap(int a, int b) {
swap(ph[hp[a]], ph[hp[b]]); // 交换ph数组中记录的插入顺序
swap(hp[a], hp[b]); // 交换hp数组中记录的堆下标
swap(h[a], h[b]); // 交换堆中的元素
}
// 下沉操作,维护小根堆性质
void down(int u) {
int t = u;
if (u * 2 <= heap_size && h[u * 2] < h[t]) t = u * 2; // 左子节点更小
if (u * 2 + 1 <= heap_size && h[u * 2 + 1] < h[t]) t = u * 2 + 1; // 右子节点更小
if (u != t) {
heap_swap(u, t); // 交换当前节点与更小的子节点
down(t); // 递归下沉
}
}
// 上浮操作,维护小根堆性质
void up(int u) {
while (u / 2 && h[u / 2] > h[u]) { // 父节点比当前节点大
heap_swap(u / 2, u); // 交换父子节点
u /= 2; // 继续向上检查
}
}
int main() {
scanf("%d", &n); // 读取操作次数
while (n--) {
char op[10];
int k, x;
scanf("%s", op); // 读取操作类型
if (!strcmp(op, "I")) { // 插入操作
scanf("%d", &x);
heap_size++; // 堆大小增加
m++; // 插入编号增加
ph[m] = heap_size; // 记录第m个插入的数在堆中的位置
hp[heap_size] = m; // 记录堆中位置对应的插入编号
h[heap_size] = x; // 将x放入堆中
up(heap_size); // 上浮维护堆
} else if (!strcmp(op, "PM")) { // 输出最小值
printf("%d\n", h[1]);
} else if (!strcmp(op, "DM")) { // 删除最小值
heap_swap(1, heap_size); // 将堆顶与堆尾交换
heap_size--; // 堆大小减少
down(1); // 下沉维护堆
} else if (!strcmp(op, "D")) { // 删除第k个插入的数
scanf("%d", &k);
k = ph[k]; // 找到第k个插入的数在堆中的位置
heap_swap(k, heap_size); // 交换到堆尾
heap_size--; // 堆大小减少
down(k), up(k); // 可能需要下沉或上浮
} else { // 修改第k个插入的数为x
scanf("%d%d", &k, &x);
k = ph[k]; // 找到第k个插入的数在堆中的位置
h[k] = x; // 修改值
down(k), up(k); // 维护堆
}
}
return 0;
}
哈希表——模拟散列表
维护一个集合,支持如下几种操作:
I x,插入一个整数 x;Q x,询问整数 x是否在集合中出现过;现在要进行 N次操作,对于每个询问操作输出对应的结果。
//开放寻址法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003; // 通常取一个远离2的幂的质数,减少冲突
const int null = 0x3f3f3f3f; // 定义一个不在数据范围内的数表示空位置
int h[N]; // 哈希表数组
// 寻找x的位置,如果x存在则返回其位置,否则返回应该插入的位置
int find(int x) {
int k = (x % N + N) % N; // 处理负数情况
while (h[k] != null && h[k] != x) {
k++;
if (k == N) k = 0; // 循环到表头
}
return k;
}
int main() {
int n;
scanf("%d", &n);
memset(h, 0x3f, sizeof h); // 初始化哈希表为空
while (n--) {
char op[2];
int x;
scanf("%s%d", op, &x);
int k = find(x);
if (*op == 'I') {
h[k] = x; // 插入x
} else {
if (h[k] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
//拉链法
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 100003; // 取一个质数,减少冲突概率
vector<int> h[N]; // 哈希表,每个槽位是一个链表
// 哈希函数
int hash_func(int x) {
return (x % N + N) % N; // 处理负数
}
void insert(int x) {
int k = hash_func(x);
for (auto num : h[k]) {
if (num == x) return; // 已经存在,无需重复插入
}
h[k].push_back(x); // 插入到链表尾部
}
bool query(int x) {
int k = hash_func(x);
for (auto num : h[k]) {
if (num == x) return true;
}
return false;
}
int main() {
int n;
scanf("%d", &n);
while (n--) {
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') {
insert(x);
} else {
if (query(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
字符串哈希
给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1] 和 [l2,r2],这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数 n和 m,表示字符串长度和询问次数。
第二行包含一个长度为 n的字符串,字符串中只包含大小写英文字母和数字。
接下来 m行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从 1开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No。
每个结果占一行。
#include <iostream>
using namespace std;
typedef unsigned long long ULL; // 使用无符号长整型存储哈希值,避免溢出
const int N = 100010, P = 131; // P是哈希的基数,通常取131或13331
int n, m;
char str[N]; // 存储输入的字符串
ULL h[N], p[N]; // h存储前缀哈希值,p存储P的幂次
// 计算子串[l, r]的哈希值
ULL get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1]; // 公式:h[r] - h[l-1] * p^(r-l+1)
}
int main() {
scanf("%d%d%s", &n, &m, str + 1); // 从索引1开始存储字符串
// 初始化p和h数组
p[0] = 1; // P^0 = 1
for (int i = 1; i <= n; i++) {
p[i] = p[i - 1] * P; // 计算P的i次幂
h[i] = h[i - 1] * P + str[i]; // 计算前缀哈希值
}
while (m--) {
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
// 比较两个子串的哈希值
if (get(l1, r1) == get(l2, r2)) {
puts("Yes");
} else {
puts("No");
}
}
return 0;
}
排列数字问题(DFS)
给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。
输入格式:共一行,包含一个整数 n。
输出格式:按字典序输出所有排列方案,每个方案占一行。
数据范围:1≤n≤7
输入样例:3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include <iostream>
using namespace std;
const int N=10;
int s[N];
bool sgn[N];
int n;
void dfs(int r){
if(idx==n){
for(int i=0;i<n;i++){
cout<<s[i]<<" ";
}
cout<<endl;
return ;
}
for(int i=1;i<=n;i++){
if(!sgn[i]){
s[idx]=i;
sgn[i]=1;
dfs(idx+1);
sgn[i]=0;
}
}
}
int main(){
cin>>n;
dfs(0);
return 0;
}
N-皇后问题
#include<iostream>
using namespace std;
const int N=10;
int p[N][N],col[N],dg[2*N],udg[2*N];
int n;
void dfs(int r){
if(r==n){
for(int i=0;i<n;i++){
for (int j=0;j<n;j++){
cout<<p[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
return;
}
for(int i=0;i<n;i++){
if(!col[i]&&!dg[r-i+n]&&!udg[r+i]){
p[r][i]='Q';
col[i]=dg[r-i+n]=udg[r+i];
dfs(r+1);
col[i]=dg[r-i+n]=udg[r+i]=0;
p[r][i]='.';
}
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){//😂
p[i][j]='.';
}
}
dfs(0);
}
走迷宫
#include<iostream>
#include<queue>
using namespace std;
const int N=110;
int s[N][N],dist[N][N];//dist记录步数
int m,n;
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
queue<pair<int,int>> q;
void bfs(){
q.push({1,1}); dist[1][1]=0;
while(!q.empty()){
auto cur=q.front(); q.pop();
int x=cur.first;
int y=cur.second;
for(int i=0;i<4;i++){
int nx=x+dir[i][0];
int ny=y+dir[i][1];
if(nx==n&&ny==m){
dist[n][m]=dist[x][y]+1;
return;
}
if(nx>=1 && nx<=n && ny>=1 && ny<=m && s[nx][ny] && dist[nx][ny]==-1){
dist[nx][ny]=dist[x][y]+1;
q.push({nx,ny});
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>s[i][j];
dist[i][j]=-1;//初始化距离为-1,表示未访问
}
}
bfs();
cout<<dist[n][m];
return 0;
}
八数码
#include<iostream>
#include<queue>
#include<unordered_map>
#include<algorithm>
using namespace std;
const string str="12345678x";
string s;
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
queue<string>q;
unordered_map<string,int> dist;
void bfs(){
q.push(s); dist[s]=0;
while(!q.empty()){
auto cur=q.front(); q.pop();
int step=dist[cur];
if(cur==str){
cout<<step<<endl; return;
}
int pos=cur.find('x');
int x=pos/3,y=pos%3;
for(int i=0;i<4;i++){
int nx=x+dir[i][0];int ny=y+dir[i][1];
if(nx>=0 && ny>=0 && nx<3 && ny<3){
string temp=cur;
swap(temp[pos],temp[nx*3+ny]);
if(!dist.count(temp)){
dist[temp]=step+1;
q.push(temp);
}
}
}
}
cout<<-1<<endl;
}
邻接表
int h[N],e[N],ne[N],w[N],idx;
void(int a,int b,int w){
e[idx]=b,w[idx]=w,ne[idx]=h[a],h[a]=idx++;
}
idx=0;
memset(h,-1,sizeof(h));
树与图的遍历O(m+n) ,n表示点数,m表示边数
(1)深度优先遍历——树的重心
int dfs(int u){
st[u]=true;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]) dfs(j);
}
}
(2)宽度优先遍历——图中点的层次
queue<int> q;
st[1]=true;//一号点已经遍历过
q.push(1);
while(q.size()){
int t=q.front();
q.pop();
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]){
st[j]=true;
q.push(j);
}
}
}
拓扑排序——有向图的拓扑排序
时间复杂度O(n+m),n表示点数,m表示边数
bool topsport(){
int hh=0,tt=-1;
//d[i]存储点i的入度
for(int i=1;i<=n;i++){
if(!d[i]){
q[++tt]=i;
}
}
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(--d[j]==0){
q[++tt]=j;
}
}
}
// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
return tt=n-1;
}
朴素dijkstra算法——Dijkstra求最短路Ⅰ
时间复杂度O(n**2+m),n 表示点数,m 表示边数
int g[N][N];//存储每条边
int dist[N];//存储1号点到每个点的最短距离
bool st[N];//存储每个点的最短路是否已经确定
//求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra(){
memset(dist,0x3f,sizeof dist);
for(int i=0;i<n-1;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j])){
t=j;
}
}
st[t]=true;
for(int j=1;j<=n;j++){
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
堆优化版dijkstra——Dijkstra求最短路Ⅱ
时间复杂度O(mlogn),n表示点数,m表示边数
typedef pair<int,int> PII;
int n;//点的数量
int h[N],w[N],e[N],ne[N],idx;// 邻接表存储所有边
int dist[N];
bool st[N];
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1}); //first存储距离,second存储节点编号
while(heap.size()){
auto t=heap.top(); heap.pop();
int ver=t.second,distance=t.first;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>distance+w[i]){
dist[j]=distance+w[i];
heap.push({diat[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
floyd算法——Floyd求最短路
时间复杂度O(n**3),n表示点数
//初始化
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) d[i][j]=0;
else d[i][j]=INF;
}
}
//算法结束后,d[a][b]表示a到b的最短距离
void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
Bellman-Ford算法——有边数限制的最短路
时间复杂度O(nm),n表示点数,m表示边数
int n,m;
int dist[N]; // dist[x]存储1到x的最短路距离
struct Edge{
int a,b,w;
}edges[M];
int bellman_ford(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
}
朴素版prim算法
int n;
int g[N][N];
int dist[N];
bool st[N];
int prim(){
memset(dist,0x3f,sizeof dist);
int res=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t=-1||dist[t]>dist[j])){
t=j;
}
}
if(i&&dist[t]==0) return -1;
if(i) res+=dist[t];
st[t]=true;
for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t,j]);
}
return res;
}
Kruskal算法 ——Kruskal算法求最小生成树
时间复杂度O(mlogm),n表示点数,m表示边数
int n,m;
int p[N];//并查集的父节点数组
struct Edge{
int a,b,w;
bool operator < (const Edge &W)const{//重载运算符
return w<W.w;
}
}edges[M];
int find(int x){ //并查集核心操作
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int kruskal(){
sort(edges,edges+m);
for(int i=1;i<=n;i++) p[i]=i; //初始化并查集
int res=0,cnt=0;
for(int i=0;i<m;i++){
int a=edges[i].a,b=edges[i].b,w=edges[i].w;
a=find(a),b=find(b);
if(a!=b){
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt<n-1) return INF;
return res;
}
浙公网安备 33010602011771号