当前位置: 首页 > news >正文

数据结构 学习 图 2025年6月14日 12点57分

搜索算法

深度优先搜索

一种用于遍历或搜索树或图的算法。它沿着树的深度遍历树的节点,尽可能深的搜索树的分支。

DFS核心思想

  • 深度优先:尽可能深地搜索树的分支

  • 回溯思想:当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点

  • 递归实现:通常用递归方式自然地实现DFS

  • void dfs(Node* node, vector<bool>& visited) {// 标记当前节点为已访问visited[node->val] = true;cout << node->val << " ";// 访问所有相邻节点for (Node* neighbor : node->neighbors) {if (!visited[neighbor->val]) {dfs(neighbor, visited);}}
    }递归实现

    经典运用

  • //图的连通分量
    int countComponents(int n, vector<vector<int>>& edges) {vector<vector<int>> graph(n);vector<bool> visited(n, false);int components = 0;// 构建邻接表for (auto& edge : edges) {graph[edge[0]].push_back(edge[1]);graph[edge[1]].push_back(edge[0]);}for (int i = 0; i < n; ++i) {if (!visited[i]) {components++;dfs(i, graph, visited);}}return components;
    }void dfs(int node, vector<vector<int>>& graph, vector<bool>& visited) {visited[node] = true;for (int neighbor : graph[node]) {if (!visited[neighbor]) {dfs(neighbor, graph, visited);}}
    }

    DFS是解决许多算法问题的强大工具,掌握其核心思想和各种优化技巧对算法能力提升至关重要。

广度优先搜索

一种用于遍历或搜索树或图的算法。它从根节点开始,先访问所有相邻节点,然后再依次访问这些节点的相邻节点,以此类推。

BFS核心思想

  • 层级遍历:按照距离起始节点的层级依次访问

  • 队列结构:使用队列来存储待访问节点

  • 最短路径:在无权图中能找到最短路径

#include <queue>
#include <vector>using namespace std;//标准实现 使用队列
void bfs(Node* start) {if (!start) return;queue<Node*> q;vector<bool> visited(NODES_SIZE, false);q.push(start);visited[start->val] = true;while (!q.empty()) {Node* current = q.front();q.pop();cout << current->val << " "; // 处理当前节点// 访问所有相邻节点for (Node* neighbor : current->neighbors) {if (!visited[neighbor->val]) {visited[neighbor->val] = true;q.push(neighbor);}}}
}

最短路径(无权图)

int shortestPath(Node* start, Node* end) {if (!start || !end) return -1;if (start == end) return 0;queue<Node*> q;unordered_map<Node*, int> distance;q.push(start);distance[start] = 0;while (!q.empty()) {Node* current = q.front();q.pop();for (Node* neighbor : current->neighbors) {if (!distance.count(neighbor)) {distance[neighbor] = distance[current] + 1;if (neighbor == end) {return distance[neighbor];}q.push(neighbor);}}}return -1; // 不可达
}

 

注意事项

  1. 访问标记:必须在入队时标记,而非出队时

  2. 队列大小:处理层级时需要先保存当前队列大小

  3. 边界检查:网格类问题注意边界条件

  4. 状态表示:复杂状态需要设计良好的哈希函数

BFS是解决最短路径问题和层级遍历问题的利器,掌握其核心思想和各种变种对算法能力提升至关重要。

图的概念

有向图 (强连通分量)

强连通分量是有向图中的一个重要概念,指有向图中任意两个顶点都互相可达的最大子图。理解强连通分量对于分析有向图的结构至关重要。

基本概念

1. 强连通分量定义

  • 强连通:在有向图中,如果从顶点u到v有一条路径,且从v到u也有一条路径,则称u和v强连通

  • 强连通分量:有向图的极大强连通子图

2. 相关性质

  • 每个顶点属于且仅属于一个强连通分量

  • 将每个强连通分量缩为一个顶点,得到的有向图是一个有向无环图(DAG)

  • 应用场景:编译器优化、社交网络分析、电路设计等

// Kosaraju算法(两次DFS) 实现
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>using namespace std;class Graph {int V;vector<vector<int>> adj;vector<vector<int>> revAdj;void fillOrder(int v, vector<bool>& visited, stack<int>& st) {visited[v] = true;for (int u : adj[v]) {if (!visited[u]) {fillOrder(u, visited, st);}}st.push(v);}void DFSUtil(int v, vector<bool>& visited, vector<int>& component) {visited[v] = true;component.push_back(v);for (int u : revAdj[v]) {if (!visited[u]) {DFSUtil(u, visited, component);}}}public:Graph(int V) : V(V), adj(V), revAdj(V) {}void addEdge(int v, int w) {adj[v].push_back(w);revAdj[w].push_back(v);}vector<vector<int>> findSCCs() {stack<int> st;vector<bool> visited(V, false);// 第一次DFS,填充栈for (int i = 0; i < V; i++) {if (!visited[i]) {fillOrder(i, visited, st);}}// 重置visited数组fill(visited.begin(), visited.end(), false);vector<vector<int>> sccs;// 第二次DFS,按照栈的顺序处理逆图while (!st.empty()) {int v = st.top();st.pop();if (!visited[v]) {vector<int> component;DFSUtil(v, visited, component);sccs.push_back(component);}}return sccs;}
};
// Tarjan算法(一次DFS)
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>using namespace std;class Graph {int V;vector<vector<int>> adj;void tarjanSCCUtil(int u, vector<int>& disc, vector<int>& low, stack<int>& st, vector<bool>& inStack, vector<vector<int>>& sccs, int& time) {disc[u] = low[u] = ++time;st.push(u);inStack[u] = true;for (int v : adj[u]) {if (disc[v] == -1) { // v未被访问tarjanSCCUtil(v, disc, low, st, inStack, sccs, time);low[u] = min(low[u], low[v]);} else if (inStack[v]) { // v在栈中low[u] = min(low[u], disc[v]);}}// 发现强连通分量if (low[u] == disc[u]) {vector<int> component;while (st.top() != u) {int v = st.top();component.push_back(v);inStack[v] = false;st.pop();}component.push_back(u);inStack[u] = false;st.pop();sccs.push_back(component);}}public:Graph(int V) : V(V), adj(V) {}void addEdge(int v, int w) {adj[v].push_back(w);}vector<vector<int>> tarjanSCC() {vector<int> disc(V, -1), low(V, -1);vector<bool> inStack(V, false);stack<int> st;vector<vector<int>> sccs;int time = 0;for (int i = 0; i < V; i++) {if (disc[i] == -1) {tarjanSCCUtil(i, disc, low, st, inStack, sccs, time);}}return sccs;}
};

实际应用: 有向图的缩点(将SCC缩为单个顶点)

Graph buildCondensedGraph(Graph& g, vector<vector<int>>& sccs) {// 创建顶点映射:原始顶点 -> 缩点后的顶点编号vector<int> vertexToComponent(g.V);for (int i = 0; i < sccs.size(); i++) {for (int v : sccs[i]) {vertexToComponent[v] = i;}}// 构建缩点后的图Graph condensed(sccs.size());unordered_set<string> edges; // 避免重复边for (int u = 0; u < g.V; u++) {for (int v : g.adj[u]) {int compU = vertexToComponent[u];int compV = vertexToComponent[v];if (compU != compV) {string edge = to_string(compU) + "-" + to_string(compV);if (!edges.count(edge)) {condensed.addEdge(compU, compV);edges.insert(edge);}}}}return condensed;
}

强连通分量分析是图算法中的重要工具,掌握Kosaraju和Tarjan这两种经典算法,能够有效解决许多有向图相关问题。

无向图 (双连通分量)

双连通分量是无向图中的一个重要概念,指没有"关节点"(割点)的最大连通子图。理解双连通分量对于分析网络可靠性、电路设计等问题至关重要。

基本概念

1. 双连通分量定义

  • 关节点(割点):删除该顶点会增加图的连通分量数量

  • 桥(割边):删除该边会增加图的连通分量数量

  • 双连通分量:不含关节点的极大连通子图

2. 相关性质

  • 任意两个顶点之间至少存在两条不相交的路径

  • 双连通分量之间通过关节点连接

  • 应用场景:网络容错分析、交通网络规划、电路板设计

// Tarjan算法求双连通分量
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>using namespace std;class Graph {int V;vector<vector<int>> adj;void BCCUtil(int u, vector<int>& disc, vector<int>& low, stack<pair<int, int>>& st, vector<bool>& isAP,vector<vector<pair<int, int>>>& bccs, int& time, int parent = -1) {int children = 0;disc[u] = low[u] = ++time;for (int v : adj[u]) {if (disc[v] == -1) { // v未被访问children++;st.push({u, v});BCCUtil(v, disc, low, st, isAP, bccs, time, u);low[u] = min(low[u], low[v]);// u是关节点(两种情况)if ((parent == -1 && children > 1) || (parent != -1 && low[v] >= disc[u])) {isAP[u] = true;// 输出双连通分量vector<pair<int, int>> component;while (st.top() != make_pair(u, v)) {component.push_back(st.top());st.pop();}component.push_back(st.top());st.pop();bccs.push_back(component);}} else if (v != parent && disc[v] < disc[u]) { // 处理回边low[u] = min(low[u], disc[v]);st.push({u, v});}}}public:Graph(int V) : V(V), adj(V) {}void addEdge(int v, int w) {adj[v].push_back(w);adj[w].push_back(v);}pair<vector<bool>, vector<vector<pair<int, int>>>> findBCCs() {vector<int> disc(V, -1), low(V, -1);vector<bool> isAP(V, false);stack<pair<int, int>> st;vector<vector<pair<int, int>>> bccs;int time = 0;for (int i = 0; i < V; i++) {if (disc[i] == -1) {BCCUtil(i, disc, low, st, isAP, bccs, time);// 处理剩余边vector<pair<int, int>> component;bool hasEdge = false;while (!st.empty()) {hasEdge = true;component.push_back(st.top());st.pop();}if (hasEdge) {bccs.push_back(component);}}}return {isAP, bccs};}
};//查找桥(割边)算法
vector<pair<int, int>> findBridges(Graph& g) {vector<int> disc(g.V, -1), low(g.V, -1);vector<pair<int, int>> bridges;int time = 0;for (int i = 0; i < g.V; i++) {if (disc[i] == -1) {bridgeUtil(i, -1, disc, low, bridges, time, g);}}return bridges;
}void bridgeUtil(int u, int parent, vector<int>& disc, vector<int>& low,vector<pair<int, int>>& bridges, int& time, Graph& g) {disc[u] = low[u] = ++time;for (int v : g.adj[u]) {if (disc[v] == -1) { // v未被访问bridgeUtil(v, u, disc, low, bridges, time, g);low[u] = min(low[u], low[v]);// 发现桥if (low[v] > disc[u]) {bridges.push_back({u, v});}} else if (v != parent) { // 处理回边low[u] = min(low[u], disc[v]);}}
}

 无向图的双连通分量分析是图论中的重要工具,掌握Tarjan算法及其变种能够有效解决网络可靠性、关键节点识别等问题。理解算法的核心思想和实现细节对解决实际问题至关重要

        回路

欧拉回路

欧拉回路和欧拉路径是图论中的重要概念,分别指图中经过每条边恰好一次并回到起点的回路,和经过每条边恰好一次的路径。

基本概念

1. 定义

  • 欧拉回路:图中经过每条边恰好一次并回到起点的闭合路径

  • 欧拉路径:图中经过每条边恰好一次的路径(不一定闭合)

  • 欧拉图:存在欧拉回路的图

  • 半欧拉图:存在欧拉路径但不存在欧拉回路的图

2. 判定条件

对于无向图:
类型连通性顶点度数条件
欧拉回路连通所有顶点度数为偶数
欧拉路径连通恰好两个顶点度数为奇数(起点和终点)
对于有向图:
类型连通性顶点度数条件
欧拉回路强连通每个顶点入度等于出度
欧拉路径单向连通一个顶点出度=入度+1(起点),一个顶点入度=出度+1(终点),其余入度=出度

算法实现

//Hierholzer算法(寻找欧拉回路/路径)
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>using namespace std;class Graph {int V;vector<vector<int>> adj;public:Graph(int V) : V(V), adj(V) {}void addEdge(int u, int v) {adj[u].push_back(v);}void removeEdge(int u, int v) {auto it = find(adj[u].begin(), adj[u].end(), v);if (it != adj[u].end()) {adj[u].erase(it);}}vector<int> findEulerianCircuit() {vector<int> circuit;stack<int> currPath;int currVertex = 0; // 可以选择任意顶点作为起点currPath.push(currVertex);while (!currPath.empty()) {if (!adj[currVertex].empty()) {currPath.push(currVertex);int nextVertex = adj[currVertex].back();adj[currVertex].pop_back();currVertex = nextVertex;} else {circuit.push_back(currVertex);currVertex = currPath.top();currPath.pop();}}reverse(circuit.begin(), circuit.end());return circuit;}
};

欧拉回路和路径在DNA测序、网络路由、物流配送等领域有广泛应用。掌握Hierholzer算法及其实现细节,能够有效解决许多实际问题。理解欧拉图的性质和判定条件是应用这些算法的基础。 

哈米尔顿回路 

哈密尔顿回路是图论中的一个重要概念,指经过图中每个顶点恰好一次并最终回到起点的闭合路径。与欧拉回路(经过每条边一次)不同,哈密尔顿回路关注的是顶点的遍历。

基本概念

1. 定义

  • 哈密尔顿路径:经过图中每个顶点恰好一次的路径

  • 哈密尔顿回路:闭合的哈密尔顿路径(起点=终点)

  • 哈密尔顿图:包含哈密尔顿回路的图

2. 判定条件

哈密尔顿回路的判定是NP完全问题,没有已知的多项式时间算法。但有一些充分条件和必要条件:

充分条件:
  • Dirac定理:对于n≥3的简单图,若每个顶点度数≥n/2,则是哈密尔顿图

  • Ore定理:对于n≥3的简单图,若任意两个不相邻顶点u,v满足deg(u)+deg(v)≥n,则是哈密尔顿图

必要条件:
  • 图必须是连通的

  • 没有度数为1的顶点

  • 删除任意k个顶点后,剩余子图的连通分量不超过k个

算法实现

//回溯法(基础实现)
#include <iostream>
#include <vector>using namespace std;class Graph {int V;vector<vector<int>> adj;bool hamCycleUtil(vector<int>& path, int pos) {if (pos == V) {// 检查最后一个顶点是否与第一个顶点相连return adj[path[pos-1]][path[0]] == 1;}for (int v = 1; v < V; v++) {if (isSafe(v, path, pos)) {path[pos] = v;if (hamCycleUtil(path, pos+1))return true;path[pos] = -1; // 回溯}}return false;}bool isSafe(int v, vector<int>& path, int pos) {// 检查当前顶点是否与上一个顶点相连if (adj[path[pos-1]][v] == 0)return false;// 检查是否已经包含在路径中for (int i = 0; i < pos; i++)if (path[i] == v)return false;return true;}public:Graph(int V) : V(V), adj(V, vector<int>(V, 0)) {}void addEdge(int u, int v) {adj[u][v] = 1;adj[v][u] = 1;}bool hamCycle() {vector<int> path(V, -1);path[0] = 0; // 从顶点0开始if (!hamCycleUtil(path, 1)) {cout << "不存在哈密尔顿回路" << endl;return false;}printSolution(path);return true;}void printSolution(vector<int>& path) {cout << "哈密尔顿回路: ";for (int i = 0; i < V; i++)cout << path[i] << " ";cout << path[0] << endl;}
};

哈密尔顿回路问题在运筹学、电路设计、生物信息学等领域有重要应用。虽然它是NP难问题,但通过合理的算法选择和优化技巧,可以有效地解决中小规模的实际问题。

并查集

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。它支持两种基本操作:

  • 查找(Find):确定元素属于哪个子集

  • 合并(Union):将两个子集合并成一个集合

基本概念

1. 核心操作

操作功能描述
makeSet(x)创建仅包含x的新集合
find(x)返回x所在集合的代表元素
union(x, y)合并x和y所在的集合

2. 关键思想

  • 代表元:每个集合选择一个元素作为代表

  • 树形结构:用树表示集合,根节点为代表元

  • 路径压缩:优化查找操作

  • 按秩合并:优化合并操作

//基础实现(带路径压缩和按秩合并)
class DSU {
private:vector<int> parent;vector<int> rank; // 秩(树高度的上界)public:DSU(int n) {parent.resize(n);rank.resize(n, 0);// 初始化每个元素为自己的父节点for (int i = 0; i < n; i++) {parent[i] = i;}}// 查找根节点(带路径压缩)int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]); // 路径压缩}return parent[x];}// 合并两个集合(按秩合并)void unionSets(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX == rootY) return; // 已在同一集合// 按秩合并if (rank[rootX] < rank[rootY]) {parent[rootX] = rootY;} else if (rank[rootX] > rank[rootY]) {parent[rootY] = rootX;} else {parent[rootY] = rootX;rank[rootX]++;}}
};

 并查集是解决动态连通性问题的利器,在社交网络分析、图像处理、网络连接等领域有广泛应用。掌握其核心思想和优化技巧,能够高效解决许多实际问题。

相关文章:

  • linux开机原理以及如何开关机-linux023
  • 基于ssm专利服务系统微信小程序源码数据库文档
  • React 第三方状态管理库的比较与选择
  • Spring中观察者模式的应用
  • UE5反射系统分析(一)generated.h
  • uniapp 腾讯地图服务
  • 1.0 前言(Python系列教程)
  • 面试问题总结——关于C++(四)
  • spring如何处理bean的循环依赖
  • java面试总结-20250610
  • 开疆智能ModbusTCP转Devicenet网关连接FANUC机器人配置案例
  • Elasticsearch高效文章搜索实践
  • RLHF调参实战手册:实用Trick、现象排查与解决思路(持续更新)
  • 【CSS-14】深入解析CSS定位:从基础到高级应用
  • 数据库期末
  • 19 - SAFM模块
  • 共享项目中使用Wpf和Winform——c# CAD二次开发
  • 拓扑推理:把邻接矩阵和节点特征形式数据集转换为可以训练CNN等序列模型的数据集
  • 展开说说Android之Glide详解_源码解析
  • YOLOV8模型优化-选择性视角类别整合模块(SPCI):遥感目标检测的注意力增强模型详解
  • 网站建设合同 费用/域名收录查询工具
  • 外卖网站那家做的好/免费网络推广的方法
  • 毕业设计资料网站有哪些/怎么创建一个网站
  • 做网站背景/微信指数怎么看
  • 杭州租房网站建设/百度移动
  • 哈尔滨网站建设乙薇/seo项目是什么