星际旅行家(广度优先搜索+邻接表)
我对这两个其实都不是那么熟悉。
我们略微用一用。
1. 邻接表基本概念
邻接表(Adjacency List)是一种图的存储结构,它通过为每个顶点维护一个链表来存储该顶点的所有邻接顶点。
优点:
- 节省空间(特别是稀疏图)
- 容易找到某个顶点的所有邻接顶点
- 添加/删除边操作高效
缺点:
- 判断两个顶点是否相邻需要遍历链表
- 相比邻接矩阵,某些操作效率较低
C++中邻接表的实现方式
2.1 使用vector<vector<int>>
这是最常用的实现方式,简洁高效:
vector<vector<int>> adj(n); // n为顶点数// 添加边(无向图)
adj[u].push_back(v);
adj[v].push_back(u);// 添加边(有向图)
adj[u].push_back(v);
2.2 使用list<int>
数组
list<int> *adj = new list<int>[n];// 添加边
adj[u].push_back(v);
2.3 使用结构体/类(带权图)
struct Edge {int to;int weight;
};vector<vector<Edge>> adj(n);// 添加带权边
adj[u].push_back({v, w});
3. 邻接表的基本操作
3.1 初始化
int n = 5; // 顶点数
vector<vector<int>> adj(n);
3.2 添加边
// 无向图
void addEdge(int u, int v) {adj[u].push_back(v);adj[v].push_back(u);
}// 有向图
void addDirectedEdge(int u, int v) {adj[u].push_back(v);
}
3.3 删除边
// 无向图删除边
void removeEdge(int u, int v) {// 删除u->vadj[u].erase(remove(adj[u].begin(), adj[u].end(), v), adj[u].end());// 删除v->uadj[v].erase(remove(adj[v].begin(), adj[v].end(), u), adj[v].end());
}
3.4 遍历邻接顶点
// 遍历顶点u的所有邻接顶点
for(int v : adj[u]) {cout << v << " ";
}
4. 邻接表的常见应用
4.1 图的遍历
DFS实现:
vector<bool> visited(n, false);void dfs(int u) {visited[u] = true;for(int v : adj[u]) {if(!visited[v]) {dfs(v);}}
}
BFS实现:
void bfs(int start) {vector<bool> visited(n, false);queue<int> q;q.push(start);visited[start] = true;while(!q.empty()) {int u = q.front();q.pop();for(int v : adj[u]) {if(!visited[v]) {visited[v] = true;q.push(v);}}}
}
4.2 拓扑排序
vector<int> topologicalSort() {vector<int> inDegree(n, 0);vector<int> result;queue<int> q;// 计算入度for(int u = 0; u < n; u++) {for(int v : adj[u]) {inDegree[v]++;}}// 入度为0的节点入队for(int u = 0; u < n; u++) {if(inDegree[u] == 0) {q.push(u);}}// 拓扑排序while(!q.empty()) {int u = q.front();q.pop();result.push_back(u);for(int v : adj[u]) {if(--inDegree[v] == 0) {q.push(v);}}}return result;
}
5. 性能优化技巧
-
预分配空间:如果知道大概的边数,可以预先reserve空间
for(auto& list : adj) {list.reserve(expected_edges_per_vertex); }
-
使用emplace_back:避免临时对象构造
adj[u].emplace_back(v);
-
使用数组代替vector:对于固定大小的图,性能更好
vector<int> adj[MAX_NODES];
-
使用前向星:对于极大图,可以节省空间
struct Edge {int to, next; } edges[MAX_EDGES]; int head[MAX_NODES], edge_count;
6. 邻接表的变体
6.1 带权图的邻接表
struct Edge {int to;int weight;
};vector<vector<Edge>> adj(n);// 添加带权边
adj[u].push_back({v, w});
6.2 使用pair的邻接表
vector<vector<pair<int, int>>> adj(n); // pair<to, weight>// 添加带权边
adj[u].emplace_back(v, w);
6.3 链式前向星
const int MAXN = 100010;
const int MAXM = 200010;struct Edge {int to, next, w;
} edge[MAXM];int head[MAXN], cnt;void init() {memset(head, -1, sizeof(head));cnt = 0;
}void addEdge(int u, int v, int w) {edge[cnt].to = v;edge[cnt].w = w;edge[cnt].next = head[u];head[u] = cnt++;
}
7. 常见问题与解决方案
7.1 处理重复边
// 使用set代替vector
vector<set<int>> adj(n);// 添加边(自动去重)
adj[u].insert(v);
adj[v].insert(u);
7.2 处理重边(允许重复边)
// 直接使用vector即可
vector<vector<int>> adj(n);
adj[u].push_back(v); // 允许重复添加
7.3 快速查询边是否存在
// 使用unordered_set
vector<unordered_set<int>> adj(n);// 查询边u->v是否存在
if(adj[u].count(v)) {// 边存在
}
上面其实已经总结邻接表与DFS和BFS结合使用的实例了
那我们直接看题:
代码:
# include<iostream>
# include<vector>
# include<queue>using namespace std;int main()
{int n,m;cin>>n>>m;vector<vector<int>> arr(n+1);vector<int> visited(n+1,false);queue<int> q;int u,v;for(int i=0;i<m;i++){cin>>u>>v;arr[u].push_back(v);arr[v].push_back(u);}q.push(1);visited[1] = true;while(!q.empty()){int current = q.front();for(int i:arr[current]){if(!visited[i]){q.push(i);visited[i] = true;}}q.pop();}int unvisited = 0;for(int i=2;i<=n;i++){if(!visited[i]){unvisited++;}}cout<<unvisited<<endl;return 0;
}
ok,其实没有太大必要去详细讲解,你从头仔细看肯定可以看懂的,心静如水,相信自己!