Acwing-基础算法课笔记之搜索与图论
一、BFS
1、根据字符串中得位置,求在矩阵中的位置
给出一个长度为9的字符串一维数组,计算出x字符在
3
×
3
3\times3
3×3矩阵二维数组当中的坐标
例如:字符串1234x5678
设t为x在字符串的所在位置,即
t
=
4
t=4
t=4,利用
x
=
t
/
3
x=t / 3
x=t/3,
y
=
t
%
3
y=t\% 3
y=t%3,
求得
x
=
1
,
y
=
1
x=1,y=1
x=1,y=1,即:
1
2
3
4
x
5
6
7
8
\def\arraystretch{1.5} \begin{array}{c:c:c} 1 & 2 & 3 \\ \hline 4 & x & 5 \\ \hdashline 6 & 7 & 8 \end{array}
1462x7358
2、运用到的工具-d.count()
以下是关于 unordered_map 和 unordered_set 中的 count 成员函数的简要说明:
( 1 ) u n o r d e r e d _ m a p (1)unordered\_map (1)unordered_map
#include <unordered_map>
#include <iostream>
using namespace std;
int main() {
std::unordered_map<int, int> myMap;
myMap[1] = 10;
myMap[2] = 20;
myMap[3] = 30;
int keyToFind = 2;
// 使用 count 函数检查特定键是否存在于 map 中
if (myMap.count(keyToFind) > 0) {
std::cout << "Key " << keyToFind << " exists in the map." << std::endl;
} else {
std::cout << "Key " << keyToFind << " does not exist in the map." << std::endl;
}
return 0;
}
在上面的例子中,myMap.count(keyToFind) 返回键 keyToFind 在 unordered_map 中出现的次数,因为在 unordered_map 中每个键都是唯一的,所以该函数实际上只能返回 1 或 0。
( 2 ) u n o r d e r e d _ s e t (2)unordered\_set (2)unordered_set
#include <unordered_set>
#include <iostream>
using namespace std;
int main() {
std::unordered_set<int> mySet = {1, 2, 3, 4, 5};
int elementToFind = 3;
// 使用 count 函数检查特定元素是否存在于 set 中
if (mySet.count(elementToFind) > 0) {
std::cout << "Element " << elementToFind << " exists in the set." << std::endl;
} else {
std::cout << "Element " << elementToFind << " does not exist in the set." << std::endl;
}
return 0;
}
在上面的例子中,mySet.count(elementToFind) 返回元素 elementToFind 在 unordered_set 中出现的次数,因为在 unordered_set 中每个元素都是唯一的,所以该函数实际上只能返回 1 或 0。
注意:在C++中,d.count(t) 的语法并不是标准 C++ 的一部分。可能是出现在个特定的上下文或特定的类(例如自定义的类)中。然而,如果你是在讨论标准库中的 unordered_map 或 unordered_set 的成员函数count,那么这个函数用于检查容器中特定元素的数量。
3、运用到的工具-t.find()
在C++中,t.find(‘x’)通常用于在字符串 t 中查找字符 ‘x’ 的位置。这是string类的成员函数,它返回字符 ‘x’ 在字符串中第一次出现的位置(索引)。
#include <iostream>
#include <string>
using namespace std;
int main() {
std::string t = "Hello, world!";
size_t position = t.find('x');
if (position != std::string::npos) {
std::cout << "Found 'x' at position: " << position << std::endl;
} else {
std::cout << "'x' not found in the string." << std::endl;
}
return 0;
}
在这个例子中,如果字符串 “Hello, world!” 中包含字符 ‘x’,则输出找到 ‘x’ 的位置;否则,输出 “‘x’ not found in the string.”。注意:t.find()不是STL当中的工具
4、从二维数组的位置变成一维数组的位置
例如:
二维数组:
1
2
3
4
x
5
6
7
8
\def\arraystretch{1.5} \begin{array}{c:c:c} 1 & 2 & 3 \\ \hline 4 & x & 5 \\ \hdashline 6 & 7 & 8 \end{array}
1462x7358
当中字符’x’的坐标为
x
=
1
,
y
=
1
x=1,y=1
x=1,y=1
利用公式:
t
=
x
×
3
+
y
t=x\times3+y
t=x×3+y得到字符’x’在一维数组中的位置
如下:
1234
x
5678
1234x5678
1234x5678
当中字符’x’的坐标为
t
=
4
t=4
t=4
二、树与图的深度优先遍历
1、树的重心
如图所示:
∙
\bullet
∙任取一点u,若以u为重心,则分为两类:
一类是u的子树,一类是u上面的部分。
∙
\bullet
∙需要算出u的最大子树的节点数和u上面的部分的节点数,然后取二者的最大值即可。
⋆
\star
⋆size:记录u的最大子树的节点数;
⋆
\star
⋆sum:记录以u为根的子树的节点数;
⋆
\star
⋆n-sum:u上面的部分的节点数;
⋆
\star
⋆ans=max(size,n-sum);
注意:
n
为节点的个数
注意:n为节点的个数
注意:n为节点的个数
2、邻接表模板
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;
// 添加一条边a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
// 初始化
idx = 0;
memset(h, -1, sizeof h);
3、深度优先遍历模板
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
三、树与图的广度优先遍历
1、宽度优先遍历模板
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
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; // 表示点j已经被遍历过
q.push(j);
}
}
}
四、拓扑排序
1、拓扑排序的概念
给定一张有向无环图,排出所有顶点的一个序列
A
A
A满足:
对于图中的每一条有向边
(
x
,
y
)
(x,y)
(x,y),
x
x
x在
A
A
A中都出现在
y
y
y之前,则称
A
A
A,是该图的顶点的一个拓扑序。
如图,
{
2
,
3
,
5
,
1
,
7
,
4
,
6
}
\{ 2,3,5,1,7,4,6\}
{2,3,5,1,7,4,6},
{
3
,
2
,
1
,
5
,
7
,
6
,
4
}
\{ 3,2,1,5,7,6,4\}
{3,2,1,5,7,6,4}都是合法的拓扑序。
拓扑排序可以判断有向图中是否有环,可以生成拓扑序列。
B站董晓算法-拓扑排序的详细讲解
注意:
拓扑序列一定是针对有向无环图而言的,并且一个有向无环图的拓扑序不是唯一的。
注意:\textcolor{red}{拓扑序列一定是针对有向无环图而言的,并且一个有向无环图的拓扑序不是唯一的。}
注意:拓扑序列一定是针对有向无环图而言的,并且一个有向无环图的拓扑序不是唯一的。
2、拓扑排序模板
时间复杂度 O ( n + m ) O(n+m) O(n+m), n n n表示点数, m m m表示边数
bool topsort()
{
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
1、朴素版Dijkstra算法
如下图所示:
∙
\bullet
∙一开始距离出发点的距离都设为
∞
\infty
∞
节点 | 距离出发点 | 前面点 |
---|---|---|
1 | ∞ \infty ∞ | |
2 | ∞ \infty ∞ | |
3 | ∞ \infty ∞ |
⇓
\Downarrow
⇓
∙
\bullet
∙一开始节点 1 的距离出发点距离是自己,所以距离出发点距离为 0,标记节点 1 已被经过
节点 | 距离出发点 | 前面点 |
---|---|---|
1 ✓ \checkmark ✓ | 0 | |
2 | ∞ \infty ∞ | |
3 | ∞ \infty ∞ |
⇓
\Downarrow
⇓
∙
\bullet
∙然后节点 1 接下来有两个路径分别是节点 2 和节点 3,填写距离两个节点的路径权值,并填写前面节点的记号,因为距离节点 2 最近,所以选择节点 2 的路径,并标记节点 2 已被经过
节点 | 距离出发点 | 前面点 |
---|---|---|
1 ✓ \checkmark ✓ | 0 | |
2 ✓ \checkmark ✓ | 2 | 1 |
3 | 4 | 1 |
⇓
\Downarrow
⇓
∙
\bullet
∙因为接下来是走节点 2 的路径,所以距离节点 3 的距离为 1
节点 | 距离出发点 | 前面点 |
---|---|---|
1 ✓ \checkmark ✓ | 0 | |
2 ✓ \checkmark ✓ | 2 | 1 |
3 | 1 | 2 |
⇓
\Downarrow
⇓
∙
\bullet
∙接下来是走从节点 1
→
\rarr
→节点 3 的路径,由于节点 3 距离节点 1 的距离 4,明显大于路径节点 1
→
\rarr
→节点 2
→
\rarr
→节点 3 的距离,所以不用在更新,直接标记节点 3 已被经过
节点 | 距离出发点 | 前面点 |
---|---|---|
1 ✓ \checkmark ✓ | 0 | |
2 ✓ \checkmark ✓ | 2 | 1 |
3 ✓ \checkmark ✓ | 1 | 2 |
∙ \bullet ∙所以最终得最短距离为 3
注意: 对于边数很多的稠密图用邻接矩阵来存,变数不多的稀疏图用邻接表来存。 注意:\textcolor{red}{对于边数很多的稠密图用邻接矩阵来存,变数不多的稀疏图用邻接表来存。} 注意:对于边数很多的稠密图用邻接矩阵来存,变数不多的稀疏图用邻接表来存。
2、朴素Dijkstra算法模板
时间复杂度 O ( n 2 + m ) O(\gdef\foo#1{#1^2} \foo{n} + {m}) O(n2+m), n n n表示点数, m m m表示边数
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
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;
// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
2、堆优化版dijkstra算法
(1)优先队列的使用-priority_queue(也称堆)
∙
\bullet
∙用数组来模拟的一棵完全二叉树
∙
\bullet
∙操作:第一种是入队,第二种是出队
∙
\bullet
∙这是B站某博主对堆的详细讲解视频
∙
\bullet
∙priority_queue的基本使用代码如下:
// 升序队列,小顶堆,大于当前节点要下沉
priority_queue <int, vector<int>, greater<int>> q;// top最小
// 降序队列,大顶堆,小于当前节点要下沉
priority_queue <int, vector<int>, less<int>> q;// top最大
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
// 默认是less,就是降序队列
priority_queue <int> q;
(2)堆优化版dijkstra算法模板
时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn), n n n表示点数, m m m表示边数
typedef pair<int, int> PII;
int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
// 求1号点到n号点的最短距离,如果不存在,则返回-1
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({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}