基本的算法(续 1)之图算法


前言


一、深度优先搜索(DFS)

深度优先搜索(Depth-First Search,DFS)是一种图遍历算法,用于在图或树等数据结构中进行遍历和搜索。它的原理是从一个起始节点开始,沿着路径尽可能深地探索,直到达到最深的节点,然后回溯到上一层。DFS 通过栈或递归实现。

深度优先搜索算法的原理如下:

1. 选择一个起始节点。
2. 访问该节点,并标记为已访问。
3. 从该节点出发,选择一个相邻且未访问的节点,继续深入探索。
4. 如果没有未访问的相邻节点,则回溯到上一层节点,继续探索其他未访问的节点。
5. 重复步骤 3-4,直到所有节点都被访问或没有可访问的节点为止。

深度优先搜索算法的特点是会尽可能深入地搜索每条分支,直到无法再继续下去才回溯。它可以用于求解图的连通性、路径查找、拓扑排序等问题。由于使用栈或递归来存储访问路径,DFS 的空间复杂度较高,对于大规模的图可能会消耗较多的内存。

以下是用数值表示深度优先搜索算法的步骤:

1. 创建一个标记数组,用于记录节点是否被访问过。
   - 用一个长度为 n 的布尔数组 `visited` 表示,初始值全为 `false`。
   - `visited[i]` 表示第 i 个节点是否被访问过。
2. 从起始节点开始,将其标记为已访问,并进行相应的操作。
   - `visited[start] = true`,表示起始节点 `start` 被访问过。
   - 进行其他操作,例如打印节点值。
3. 遍历起始节点的所有相邻节点:
   - 如果相邻节点未被访问过,则递归调用深度优先搜索算法,以该相邻节点为起始节点。
     - `if (!visited[adjacent])`
     - `dfs(adjacent)`,递归调用以相邻节点 `adjacent` 为起始节点。
4. 重复步骤 3,直到没有未访问的相邻节点为止。
   - 考虑使用循环或递归来遍历相邻节点。
5. 若还有未被访问的节点,则选择其中一个作为新的起始节点,并重复步骤 2-4。
   - 可以使用循环遍历所有节点,如果有节点未被访问,则将该节点作为新的起始节点,并重复步骤 2-4。
6. 当所有节点都被访问过时,搜索结束。
   - 可以根据标记数组中的值来判断是否所有节点都被访问过,如果全为 `true`,则搜索结束。

1.C++ 编写的深度优先搜索算法

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> graph; // 图的邻接表表示
vector<bool> visited;      // 标记数组,记录节点是否被访问过

void dfs(int node) {
    visited[node] = true;  // 标记当前节点为已访问
    cout << node << " ";   // 打印当前节点(可根据需求进行其他操作)

    // 遍历当前节点的所有邻接节点
    for (int i = 0; i < graph[node].size(); i++) {
        int adjacent = graph[node][i];
        if (!visited[adjacent]) {
            dfs(adjacent);  // 递归调用深度优先搜索以邻接节点为起始节点
        }
    }
}

int main() {
    int numNodes, numEdges;
    cout << "请输入节点数和边数:" << endl;
    cin >> numNodes >> numEdges;

    graph.resize(numNodes);     // 调整邻接表大小
    visited.resize(numNodes);   // 调整标记数组大小

    cout << "请输入每条边的连接关系:" << endl;
    for (int i = 0; i < numEdges; i++) {
        int u, v;
        cin >> u >> v;
        // 无向图,需要在两个节点的邻接列表中分别添加对方
        graph[u].push_back(v);
        graph[v].push_back(u);
    }

    int startNode;
    cout << "请输入起始节点:" << endl;
    cin >> startNode;

    cout << "深度优先搜索结果:" << endl;
    dfs(startNode);

    return 0;
}

代码实现步骤:

1. 首先,程序使用 `vector` 头文件包含 `<iostream>` 和 `<vector>`。
2. 接着定义了两个全局变量:`graph` 表示图的邻接表,`visited` 是一个标记数组用于记录节点是否被访问过。
3. 然后,定义了一个深度优先搜索函数 `dfs`,它的参数是一个节点 `node`。
4. 在 `dfs` 函数内部,将当前节点标记为已访问,并将该节点的值输出到控制台。
5. 使用 `for` 循环遍历当前节点的邻接节点列表。
6. 如果邻接节点未被访问过,则递归调用 `dfs` 函数以该邻接节点为起始节点进行深度优先搜索。
7. `main` 函数开始,先定义了两个变量 `numNodes` 和 `numEdges` 用于存储用户输入的节点数和边数。
8. 输出提示信息,要求用户输入节点数和边数。
9. 调用 `cin` 对象对用户输入进行读取,并将结果存储到 `numNodes` 和 `numEdges` 变量中。
10. 调用 `resize` 函数,调整 `graph` 和 `visited` 的大小,使其能够容纳要输入的节点数。
11. 输出提示信息,要求用户输入每条边的连接关系。
12. 使用 `for` 循环和 `cin` 对象,读取并存储每条边的连接关系到邻接表 `graph` 中。
13. 由于是无向图,所以需要在两个节点的邻接列表中分别添加对方。
14. 输出提示信息,要求用户输入起始节点。
15. 使用 `cin` 对象读取用户输入的起始节点,并存储到变量 `startNode` 中。
16. 输出提示信息,告知深度优先搜索的结果将被打印。
17. 调用深度优先搜索函数 `dfs`,并将起始节点 `startNode` 作为参数传入进行深度优先搜索。
18. 程序执行完毕,返回 0 结束。

2.以下是C和Python的深度优先搜索算法代码示例!

#include <stdio.h>

#define MAX_NODES 100

int graph[MAX_NODES][MAX_NODES];
int visited[MAX_NODES];

void dfs(int node, int numNodes) {
    visited[node] = 1;
    printf("%d ", node);

    for (int i = 0; i < numNodes; i++) {
        if (graph[node][i] && !visited[i]) {
            dfs(i, numNodes);
        }
    }
}

int main() {
    int numNodes, numEdges;
    printf("请输入节点数和边数:\n");
    scanf("%d %d", &numNodes, &numEdges);

    printf("请输入每条边的连接关系:\n");
    for (int i = 0; i < numEdges; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        graph[u][v] = 1;
        graph[v][u] = 1;
    }

    int startNode;
    printf("请输入起始节点:\n");
    scanf("%d", &startNode);

    printf("深度优先搜索结果:\n");
    dfs(startNode, numNodes);

    return 0;
}
def dfs(node, graph, visited):
    visited[node] = True
    print(node, end=" ")

    for adjacent in graph[node]:
        if not visited[adjacent]:
            dfs(adjacent, graph, visited)

numNodes, numEdges = map(int, input("请输入节点数和边数:").split())

graph = [[] for _ in range(numNodes)]
visited = [False] * numNodes

print("请输入每条边的连接关系:")
for _ in range(numEdges):
    u, v = map(int, input().split())
    graph[u].append(v)
    graph[v].append(u)

startNode = int(input("请输入起始节点:"))

print("深度优先搜索结果:")
dfs(startNode, graph, visited)

二、广度优先搜索(BFS

广度优先搜索算法(BFS)是一种用于图的遍历的算法。它从指定的起始节点开始,逐层遍历图的节点,直到找到目标节点或遍历完整个图。BFS 通常使用队列来保存待访问的节点。

function BFS(graph, startNode):
    queue = []                    # 创建一个空队列
    queue.append(startNode)       # 将起始节点放入队列中
    visited = {}                  # 创建一个字典用于标记节点是否已访问(1表示已访问)
    visited[startNode] = 1        # 将起始节点的标记设为1
    
    while queue:                  # 当队列不为空时执行以下操作
         currentNode = queue.pop(0)         # 从队列中取出队首的节点编号作为当前节点
         process(currentNode)               # 访问当前节点,并执行相应的操作
         
         for neighbor in graph[currentNode]:         # 遍历当前节点的邻接节点
             if neighbor not in visited:             # 如果邻接节点未被访问过
                 visited[neighbor] = 1                # 将其标记设为1
                 queue.append(neighbor)               # 并将其编号放入队列中
         
         visited[currentNode] = 1          # 将当前节点标记为已处理(或已访问)
    
    for node in graph:                    # 如果图中还有未被访问的节点
        if node not in visited:           # 选择一个未被访问的节点作为新的起始节点
            queue.append(node)            # 并将其放入队列中
            visited[node] = 1             # 将其标记为1
    
            while queue:                  # 重复步骤1-4
                currentNode = queue.pop(0)
                process(currentNode)
                
                for neighbor in graph[currentNode]:
                    if neighbor not in visited:
                        visited[neighbor] = 1
                        queue.append(neighbor)
                
                visited[currentNode] = 1

    # 结束算法

1.C++ 编写的广度优先搜索算法

#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>

void BFS(std::unordered_map<int, std::vector<int>>& graph, int startNode) {
    std::queue<int> queue;                          // 创建一个空队列
    queue.push(startNode);                           // 将起始节点放入队列中
    std::unordered_map<int, bool> visited;           // 创建一个unordered_map用于标记节点是否已访问
    visited[startNode] = true;                       // 将起始节点的标记设为true
    
    while (!queue.empty()) {                         // 当队列不为空时执行以下操作
        int currentNode = queue.front();             // 从队列中取出队首的节点编号作为当前节点
        queue.pop();
        process(currentNode);                         // 访问当前节点,并执行相应的操作
        
        for (int neighbor : graph[currentNode]) {     // 遍历当前节点的邻接节点
            if (!visited[neighbor]) {                 // 如果邻接节点未被访问过
                visited[neighbor] = true;              // 将其标记设为true
                queue.push(neighbor);                  // 并将其编号放入队列中
            }
        }

        visited[currentNode] = true;                  // 将当前节点标记为已处理(或已访问)
    }
    
    for (auto iter : graph) {                          // 如果图中还有未被访问的节点
        int node = iter.first;
        if (!visited[node]) {                          // 选择一个未被访问的节点作为新的起始节点
            queue.push(node);                           // 并将其放入队列中
            visited[node] = true;                       // 将其标记为true
    
            while (!queue.empty()) {                    // 重复步骤1-4
                int currentNode = queue.front();
                queue.pop();
                process(currentNode);
                
                for (int neighbor : graph[currentNode]) {
                    if (!visited[neighbor]) {
                        visited[neighbor] = true;
                        queue.push(neighbor);
                    }
                }
                
                visited[currentNode] = true;
            }
        }
    }
}

int main() {
    std::unordered_map<int, std::vector<int>> graph;
    // 构建图的过程,将节点及其邻接节点加入graph中
    
    // 调用BFS函数,传入graph和起始节点
    BFS(graph, startNode);
    
    return 0;
}

2.C 编写的广度优先搜索算法

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define MAX_NODES 100

// 邻接链表节点
typedef struct Node {
    int vertex;
    struct Node* next;
} Node;

// 邻接链表
typedef struct AdjList {
    Node* head;
} AdjList;

// 图
typedef struct Graph {
    int numNodes;
    AdjList* array;
} Graph;

// 创建一个节点
Node* createNode(int vertex) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->vertex = vertex;
    newNode->next = NULL;
    return newNode;
}

// 创建一个图
Graph* createGraph(int numNodes) {
    Graph* graph = (Graph*)malloc(sizeof(Graph));
    graph->numNodes = numNodes;
    graph->array = (AdjList*)malloc(numNodes * sizeof(AdjList));

    for (int i = 0; i < numNodes; ++i)
        graph->array[i].head = NULL;

    return graph;
}

// 添加边
void addEdge(Graph* graph, int src, int dest) {
    Node* newNode = createNode(dest);
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;

    newNode = createNode(src);
    newNode->next = graph->array[dest].head;
    graph->array[dest].head = newNode;
}

// 广度优先搜索
void BFS(Graph* graph, int startNode) {
    bool visited[MAX_NODES] = { false };
    visited[startNode] = true;

    // 创建队列
    int queue[MAX_NODES];
    int front = 0, rear = 0;
    queue[rear++] = startNode;

    while (front != rear) {
        int currentNode = queue[front++];

        printf("%d ", currentNode); // 执行相应的操作,这里仅输出节点编号

        Node* temp = graph->array[currentNode].head;
        while (temp != NULL) {
            int neighbor = temp->vertex;
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                queue[rear++] = neighbor;
            }
            temp = temp->next;
        }
    }
}

int main() {
    int numNodes = 6;
    Graph* graph = createGraph(numNodes);

    // 添加边
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 2, 3);
    addEdge(graph, 2, 4);
    addEdge(graph, 3, 4);
    addEdge(graph, 3, 5);

    int startNode = 0;
    printf("BFS starting from node %d: ", startNode);
    BFS(graph, startNode);
    printf("\n");

    return 0;
}

三.最小生成树

最小生成树(Minimum Spanning Tree,简称MST)是一种在带权无向图中生成一棵树的算法。这棵树包含了图中的所有节点,并且将这些节点通过边连接起来,使得整个树的总权重最小。

最小生成树具有以下特点:

1. 包含所有的节点:最小生成树包含了图中的所有节点。
2. 无回路:最小生成树是一棵树,因此不存在回路。
3. 边的最小权重:最小生成树的边具有最小的总权重,即树上所有边的权重之和最小。

最小生成树有多种算法可用于求解,其中最流行的算法是Prim算法和Kruskal算法。

- Prim算法:从一个初始节点开始,逐步选择与当前生成树相邻且权重最小的边,直到将所有节点都包含在生成树中为止。
- Kruskal算法:按照边的权重从小到大的顺序逐步添加边,如果该边连接的两个节点不在同一个连通分量中,则加入生成树。

通过最小生成树可以找到一个连接图中所有节点且总权重最小的方式,应用广泛,如网络设计、电力传输、城市规划等领域。

1.Prim算法

Prim算法是一种用于求解最小生成树(Minimum Spanning Tree,简称MST)的贪心算法。Prim算法从一个起始节点开始,逐步选择与当前生成树相邻且权重最小的边,并将相邻节点加入生成树的集合中,直到将所有节点都包含在生成树中为止。

下面是Prim算法的基本步骤:

1. 随机选择一个节点作为起始节点。
2. 初始化一个空的生成树和一个辅助的集合用于存储已访问的节点。
3. 将起始节点加入已访问的节点集合中。
4. 对于已访问的节点,找到与其相邻且权重最小的边。
5. 选择权重最小的边所连接的节点,将其加入生成树,并将其加入已访问节点集合中。
6. 重复步骤4和步骤5,直到生成树包含了图中的所有节点。
7. 输出最小生成树。

Prim算法的核心思想是每次都选取和当前生成树连接的边中最小权重的边,并将其连接的节点加入生成树。通过不断选择权重最小的边来构建生成树,最终得到权重之和最小的最小生成树。

Prim算法的时间复杂度通常为 O(V^2),其中 V 表示节点的数量。但是通过使用优先队列(例如二叉堆)来存储边的权重,可以将时间复杂度优化到 O(E log V),其中 E 表示边的数量。这样可以提升Prim算法的效率,并适用于大规模图的求解。

1.1Prim算法c++代码示例

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

typedef pair<int, int> pii;   // 表示边的权重和终点的 pair

// 用于表示图的邻接列表
vector<vector<pii>> graph;

// Prim算法函数
void prim(int startNode) {
    int numNodes = graph.size();
    vector<bool> visited(numNodes, false);  // 用于标记节点是否已经访问
    vector<int> minWeight(numNodes, INT_MAX);  // 用于存储每个节点到生成树的最小权重
    priority_queue<pii, vector<pii>, greater<pii>> pq; // 最小堆,用于选择当前权重最小的边

    minWeight[startNode] = 0;  // 起始点到生成树的权重为0
    pq.push({0, startNode});  // 将起始点加入优先队列

    while (!pq.empty()) {
        int currentNode = pq.top().second;  // 当前权重最小的节点
        pq.pop();

        visited[currentNode] = true;  // 标记当前节点已访问

        // 遍历当前节点的所有邻接边
        for (auto edge : graph[currentNode]) {
            int weight = edge.first;
            int neighbor = edge.second;

            // 如果邻接节点未被访问且当前边的权重小于其到生成树的最小权重
            if (!visited[neighbor] && weight < minWeight[neighbor]) {
                minWeight[neighbor] = weight;  // 更新最小权重
                pq.push({weight, neighbor});  // 将邻接节点加入优先队列
            }
        }
    }

    // 输出生成树的信息
    cout << "Prim's Algorithm\n";
    cout << "Minimum Spanning Tree Edges: \n";
    for (int i = 1; i < numNodes; i++) {
        cout << i << " - " << (i < startNode ? (i + 1) : (i + 2)) << " (weight: " << minWeight[i] << ")\n";
    }
}

int main() {
    int numNodes = 6;
    graph.resize(numNodes);

    // 添加边
    graph[0].push_back({2, 1});
    graph[0].push_back({3, 2});
    graph[1].push_back({2, 0});
    graph[1].push_back({5, 2});
    graph[1].push_back({1, 3});
    graph[2].push_back({3, 0});
    graph[2].push_back({5, 1});
    graph[2].push_back({3, 3});
    graph[2].push_back({1, 4});
    graph[3].push_back({1, 1});
    graph[3].push_back({3, 2});
    graph[3].push_back({4, 4});
    graph[3].push_back({5, 5});
    graph[4].push_back({1, 2});
    graph[4].push_back({4, 3});
    graph[4].push_back({2, 5});
    graph[5].push_back({5, 3});
    graph[5].push_back({2, 4});
    
    int startNode = 0;
    prim(startNode);

    return 0;
}

该代码示例首先定义了一个用于表示图的邻接列表 graph,然后实现了 prim 函数进行 Prim 算法。在 prim 函数中,使用了优先队列 pq 存储当前权重最小的边,使用了 visited 数组记录节点是否已经访问过,使用了 minWeight 数组保存每个节点到生成树的最小权重。

具体实现步骤如下:

  1. 初始化节点是否已访问的标记 visited 和最小权重数组 minWeight,将起始节点加入优先队列 pq
  2. 当优先队列不为空时,取出当前权重最小的节点 currentNode
  3. 标记当前节点为已访问。
  4. 遍历当前节点的所有邻接边,如果邻接节点未被访问且当前边的权重小于其到生成树的最小权重,更新最小权重并将邻接节点加入优先队列。
  5. 重复步骤2到4,直到所有节点都被访问完毕。
  6. 输出生成树的信息,即最小生成树的边和对应的权重。

最后,在主函数中构建了一个包含6个节点的图,并设置了相应的边权重。通过调用 prim 函数,以起始节点为参数,执行 Prim 算法并输出最小生成树的边及对应的权重。

1.2Prim算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define MAX_NODES 6

// 邻接矩阵表示图
int graph[MAX_NODES][MAX_NODES];

// 找到图中未包含在生成树中的最小权重的节点
int findMinWeight(bool visited[], int minWeight[], int numNodes) {
    int minWeightVal = INT_MAX;
    int minWeightNode;

    for (int i = 0; i < numNodes; i++) {
        if (!visited[i] && minWeight[i] < minWeightVal) {
            minWeightVal = minWeight[i];
            minWeightNode = i;
        }
    }

    return minWeightNode;
}

// 执行Prim算法
void prim(int startNode, int numNodes) {
    bool visited[MAX_NODES];
    int minWeight[MAX_NODES];
    int parent[MAX_NODES];

    // 初始化visited、minWeight和parent数组
    for (int i = 0; i < numNodes; i++) {
        visited[i] = false;
        minWeight[i] = INT_MAX;
        parent[i] = -1;
    }

    // 起始节点到自身的权重为0
    minWeight[startNode] = 0;
    parent[startNode] = -1;

    // 找到最小生成树中的所有边
    for (int i = 0; i < numNodes - 1; i++) {
        int currentNode = findMinWeight(visited, minWeight, numNodes);
        visited[currentNode] = true;

        // 更新邻接节点的最小权重和父节点
        for (int j = 0; j < numNodes; j++) {
            if (graph[currentNode][j] != 0 && !visited[j] && graph[currentNode][j] < minWeight[j]) {
                minWeight[j] = graph[currentNode][j];
                parent[j] = currentNode;
            }
        }
    }

    // 输出生成树的信息
    printf("Prim's Algorithm\n");
    printf("Minimum Spanning Tree Edges:\n");
    for (int i = 1; i < numNodes; i++) {
        printf("%d - %d (weight: %d)\n", parent[i] + 1, i + 1, minWeight[i]);
    }
}

int main() {
    int numNodes = 6;

    // 构建邻接矩阵表示的图
    graph[0][1] = 2;
    graph[0][2] = 3;
    graph[1][0] = 2;
    graph[1][2] = 5;
    graph[1][3] = 1;
    graph[2][0] = 3;
    graph[2][1] = 5;
    graph[2][3] = 3;
    graph[2][4] = 1;
    graph[3][1] = 1;
    graph[3][2] = 3;
    graph[3][4] = 4;
    graph[3][5] = 5;
    graph[4][2] = 1;
    graph[4][3] = 4;
    graph[4][5] = 2;
    graph[5][3] = 5;
    graph[5][4] = 2;

    int startNode = 0;
    prim(startNode, numNodes);

    return 0;
}
import sys

def findMinWeight(visited, minWeight, numNodes):
    minWeightVal = sys.maxsize
    minWeightNode = -1

    for i in range(numNodes):
        if not visited[i] and minWeight[i] < minWeightVal:
            minWeightVal = minWeight[i]
            minWeightNode = i

    return minWeightNode

def prim(startNode, numNodes):
    visited = [False] * numNodes
    minWeight = [sys.maxsize] * numNodes
    parent = [-1] * numNodes

    minWeight[startNode] = 0

    for _ in range(numNodes - 1):
        currentNode = findMinWeight(visited, minWeight, numNodes)
        visited[currentNode] = True

        for j in range(numNodes):
            if graph[currentNode][j] != 0 and not visited[j] and graph[currentNode][j] < minWeight[j]:
                minWeight[j] = graph[currentNode][j]
                parent[j] = currentNode

    print("Prim's Algorithm")
    print("Minimum Spanning Tree Edges:")
    for i in range(1, numNodes):
        print(f"{parent[i] + 1} - {i + 1} (weight: {minWeight[i]})")

numNodes = 6

graph = [
    [0, 2, 3, 0, 0, 0],
    [2, 0, 5, 1, 0, 0],
    [3, 5, 0, 3, 1, 0],
    [0, 1, 3, 0, 4, 5],
    [0, 0, 1, 4, 0, 2],
    [0, 0, 0, 5, 2, 0]
]

startNode = 0
prim(startNode, numNodes)

2.Kruskal算法

Kruskal算法是一种用于求解最小生成树的贪心算法。最小生成树是指在一个连通带权无向图中,选择一棵包含所有顶点且边权重之和最小的子图。Kruskal算法的目标是找到最小生成树。

Kruskal算法的基本思想是从图中的边集合中按照权重递增的顺序选择边,并将其逐步加入到最小生成树的集合中。在选择每条边之前,检查是否加入该边会导致出现环路,如果不会,就将该边加入到最小生成树的集合中。

下面是Kruskal算法的基本步骤:

1. 初始化一个空的最小生成树集合。
2. 对图的所有边根据权重进行排序。
3. 依次遍历排序后的边集合,对于每条边,如果加入该边不会导致出现环路(即不与已有的边构成环路),则将其加入到最小生成树集合中。
4. 继续遍历,直到最小生成树的边数达到图的顶点数减1为止。

Kruskal算法的时间复杂度取决于排序边的时间复杂度,常见的实现方式是使用并查集(Union-Find)数据结构来判断是否形成环路,并使用快速排序或堆排序对边进行排序。总体而言,Kruskal算法的时间复杂度为O(ElogE),其中E为边的数量。

2.1Kruskal算法c++代码示例

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct Edge {
    int src, dest, weight;
};

// 按照边的权重进行排序的比较函数
bool compareEdges(Edge a, Edge b) {
    return a.weight < b.weight;
}

// 查找节点所属的集合
int findSet(vector<int>& parent, int i) {
    if (parent[i] == -1)
        return i;
    return findSet(parent, parent[i]);
}

// 合并两个集合
void unionSets(vector<int>& parent, int x, int y) {
    int xset = findSet(parent, x);
    int yset = findSet(parent, y);
    parent[xset] = yset;
}

// Kruskal算法实现
void kruskalMST(vector<Edge>& edges, int numVertices) {
    vector<Edge> minimumSpanningTree; // 存储最小生成树的边
    vector<int> parent(numVertices, -1); // 用于存储节点的父节点,初始为-1
    int numEdges = 0; // 记录加入最小生成树的边数

    // 按照边的权重进行排序
    sort(edges.begin(), edges.end(), compareEdges);

    for (auto edge : edges) {
        // 检查边的两个节点是否属于同一集合(是否形成环路)
        int x = findSet(parent, edge.src);
        int y = findSet(parent, edge.dest);

        if (x != y) {
            // 边不会导致环路,将其加入最小生成树,并合并两个集合
            minimumSpanningTree.push_back(edge);
            unionSets(parent, x, y);
            numEdges++;

            if (numEdges == numVertices - 1)
                break; // 达到最小生成树的边数要求,结束算法
        }
    }

    // 输出最小生成树的边和权重
    cout << "Kruskal's Algorithm" << endl;
    cout << "Minimum Spanning Tree Edges:" << endl;
    for (auto edge : minimumSpanningTree) {
        cout << edge.src << " - " << edge.dest << " (weight: " << edge.weight << ")" << endl;
    }
}

int main() {
    int numVertices = 6;
    vector<Edge> edges;

    // 添加图的边信息
    edges.push_back({0, 1, 2});
    edges.push_back({0, 2, 3});
    edges.push_back({1, 2, 5});
    edges.push_back({1, 3, 1});
    edges.push_back({2, 3, 3});
    edges.push_back({2, 4, 1});
    edges.push_back({3, 4, 4});
    edges.push_back({3, 5, 5});
    edges.push_back({4, 5, 2});

    kruskalMST(edges, numVertices);

    return 0;
}

Kruskal算法通过维护一个最小生成树集合和一个并查集,按照边的权重递增的顺序选择边。对于每条边,它首先通过并查集判断该边的两个节点是否属于同一个集合(是否形成环路),如果不是,则将该边加入最小生成树集合,并将两个集合合并。最终得到的最小生成树就是所求。

该代码使用了一个结构体Edge来表示图的边,包括起始节点、目标节点和权重。compareEdges函数用于比较边的权重大小,findSet函数用于查找节点所属的集合,unionSets函数用于合并两个集合。kruskalMST函数是Kruskal算法的主要实现部分,它按照边的权重进行排序,然后依次遍历每条边,并根据并查集判断是否加入该边到最小生成树中。最后,输出最小生成树的边和权重。

这段代码运行的时间复杂度取决于边的数量和顶点的数量。在排序边的步骤上,使用了快速排序算法,其时间复杂度为O(ElogE),其中E为边的数量。在循环内部的并查集操作上,时间复杂度近似为O(logV),其中V为顶点的数量。因此,Kruskal算法的总体时间复杂度为O(ElogE + ElogV)。

2.2Kruskal算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define MAX_NODES 6

// 邻接矩阵表示图
int graph[MAX_NODES][MAX_NODES];

// 找到图中未包含在生成树中的最小权重的节点
int findMinWeight(bool visited[], int minWeight[], int numNodes) {
    int minWeightVal = INT_MAX;
    int minWeightNode;

    for (int i = 0; i < numNodes; i++) {
        if (!visited[i] && minWeight[i] < minWeightVal) {
            minWeightVal = minWeight[i];
            minWeightNode = i;
        }
    }

    return minWeightNode;
}

// 执行Prim算法
void prim(int startNode, int numNodes) {
    bool visited[MAX_NODES];
    int minWeight[MAX_NODES];
    int parent[MAX_NODES];

    // 初始化visited、minWeight和parent数组
    for (int i = 0; i < numNodes; i++) {
        visited[i] = false;
        minWeight[i] = INT_MAX;
        parent[i] = -1;
    }

    // 起始节点到自身的权重为0
    minWeight[startNode] = 0;
    parent[startNode] = -1;

    // 找到最小生成树中的所有边
    for (int i = 0; i < numNodes - 1; i++) {
        int currentNode = findMinWeight(visited, minWeight, numNodes);
        visited[currentNode] = true;

        // 更新邻接节点的最小权重和父节点
        for (int j = 0; j < numNodes; j++) {
            if (graph[currentNode][j] != 0 && !visited[j] && graph[currentNode][j] < minWeight[j]) {
                minWeight[j] = graph[currentNode][j];
                parent[j] = currentNode;
            }
        }
    }

    // 输出生成树的信息
    printf("Prim's Algorithm\n");
    printf("Minimum Spanning Tree Edges:\n");
    for (int i = 1; i < numNodes; i++) {
        printf("%d - %d (weight: %d)\n", parent[i] + 1, i + 1, minWeight[i]);
    }
}

int main() {
    int numNodes = 6;

    // 构建邻接矩阵表示的图
    graph[0][1] = 2;
    graph[0][2] = 3;
    graph[1][0] = 2;
    graph[1][2] = 5;
    graph[1][3] = 1;
    graph[2][0] = 3;
    graph[2][1] = 5;
    graph[2][3] = 3;
    graph[2][4] = 1;
    graph[3][1] = 1;
    graph[3][2] = 3;
    graph[3][4] = 4;
    graph[3][5] = 5;
    graph[4][2] = 1;
    graph[4][3] = 4;
    graph[4][5] = 2;
    graph[5][3] = 5;
    graph[5][4] = 2;

    int startNode = 0;
    prim(startNode, numNodes);

    return 0;
}
import sys

def findMinWeight(visited, minWeight, numNodes):
    minWeightVal = sys.maxsize
    minWeightNode = -1

    for i in range(numNodes):
        if not visited[i] and minWeight[i] < minWeightVal:
            minWeightVal = minWeight[i]
            minWeightNode = i

    return minWeightNode

def prim(startNode, numNodes):
    visited = [False] * numNodes
    minWeight = [sys.maxsize] * numNodes
    parent = [-1] * numNodes

    minWeight[startNode] = 0

    for _ in range(numNodes - 1):
        currentNode = findMinWeight(visited, minWeight, numNodes)
        visited[currentNode] = True

        for j in range(numNodes):
            if graph[currentNode][j] != 0 and not visited[j] and graph[currentNode][j] < minWeight[j]:
                minWeight[j] = graph[currentNode][j]
                parent[j] = currentNode

    print("Prim's Algorithm")
    print("Minimum Spanning Tree Edges:")
    for i in range(1, numNodes):
        print(f"{parent[i] + 1} - {i + 1} (weight: {minWeight[i]})")

numNodes = 6

graph = [
    [0, 2, 3, 0, 0, 0],
    [2, 0, 5, 1, 0, 0],
    [3, 5, 0, 3, 1, 0],
    [0, 1, 3, 0, 4, 5],
    [0, 0, 1, 4, 0, 2],
    [0, 0, 0, 5, 2, 0]
]

startNode = 0
prim(startNode, numNodes)

四.单源最短路径

单源最短路径是图论中的一个经典问题,它求解的是从图中的一个指定源节点到图中的其他各节点的最短路径。

在一个加权有向图或加权无向图中,每条边都有一个关联的权重或距离。单源最短路径问题的目标是找到从给定的源节点到其余节点的最短路径。其中,最短路径可以根据两个节点之间边的权重之和来衡量。

有几种常见的算法可以解决单源最短路径问题,其中最著名的算法包括Dijkstra算法和Bellman-Ford算法。

- Dijkstra算法:适用于图中不存在负权边的情况。它以贪心的方式逐步构建最短路径树,通过选择当前节点距离最短的邻接节点来不断扩展最短路径。
- Bellman-Ford算法:适用于存在负权边的情况。该算法通过进行多轮的松弛操作,逐步减小从源节点到其他节点的估计距离,直到达到最短路径。

这些算法中,Dijkstra算法对于非负权边的图有较好的性能,而Bellman-Ford算法则可以处理存在负权边的情况。这些算法的实现方式多种多样,可以使用不同的数据结构来表示图和距离信息,具体的实现可能会有所不同。

1.Dijkstra算法

Dijkstra算法是一种用于求解单源最短路径问题的贪心算法。它能够找到有向图或无向图中,从给定源节点到各个节点的最短路径。

Dijkstra算法的基本思想是逐步构建最短路径树,通过选择当前源节点到其他节点距离最短的节点来不断扩展最短路径。

下面是Dijkstra算法的基本步骤:

1. 初始化距离数组,用于存储源节点到各个节点的距离。将源节点的距离设为0,其他节点的距离设为无穷大(表示尚未确定)。
2. 选择距离最短的节点作为当前节点,并将其标记为已访问。
3. 对于当前节点的每个邻接节点,计算通过当前节点到达该邻接节点的距离,如果该距离小于邻接节点的当前距离,则更新邻接节点的距离。
4. 重复步骤2和步骤3,直到所有节点都被访问过或者没有可以扩展的节点为止。

在上述步骤中,通过使用距离数组和一个标记数组来追踪节点距离和访问状态。在每一轮的选择节点时,都会选择当前距离最短且未被访问过的节点。通过不断更新节点的距离,可以得到源节点到所有其他节点的最短路径。

Dijkstra算法适用于非负权边的图,但对负权边的图无法正确处理,因为它无法处理已经确定最短路径的节点的更新。如果图中存在负权边,可以考虑使用其他算法如Bellman-Ford算法来解决单源最短路径问题。

1.1Dijkstra算法c++代码示例

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

// 定义无穷大表示距离为未确定状态
const int INF = INT_MAX;

// Dijkstra算法实现
void dijkstra(vector<vector<int>>& graph, int source) {
    int numVertices = graph.size();

    // 创建距离数组,用于存储源节点到各个节点的最短距离
    vector<int> distance(numVertices, INF);

    // 创建标记数组,用于记录节点的访问状态
    vector<bool> visited(numVertices, false);

    // 设置源节点的距离为0
    distance[source] = 0;

    // 循环遍历所有节点
    for (int count = 0; count < numVertices - 1; ++count) {
        int minDistance = INF;
        int minDistanceVertex = -1;

        // 选择距离最短的节点作为当前节点
        for (int v = 0; v < numVertices; ++v) {
            if (!visited[v] && distance[v] <= minDistance) {
                minDistance = distance[v];
                minDistanceVertex = v;
            }
        }

        // 标记当前节点为已访问
        visited[minDistanceVertex] = true;

        // 更新当前节点的邻接节点的距离
        for (int v = 0; v < numVertices; ++v) {
            if (!visited[v] && graph[minDistanceVertex][v] != 0 &&
                distance[minDistanceVertex] != INF &&
                distance[minDistanceVertex] + graph[minDistanceVertex][v] < distance[v]) {
                distance[v] = distance[minDistanceVertex] + graph[minDistanceVertex][v];
            }
        }
    }

    // 输出节点距离
    cout << "Dijkstra's Algorithm" << endl;
    cout << "Shortest Distance from Source Node to Each Node:" << endl;
    for (int v = 0; v < numVertices; ++v) {
        cout << "Node " << v << ": " << distance[v] << endl;
    }
}

int main() {
    int numVertices = 9;
    vector<vector<int>> graph = {
        {0, 4, 0, 0, 0, 0, 0, 8, 0},
        {4, 0, 8, 0, 0, 0, 0, 11, 0},
        {0, 8, 0, 7, 0, 4, 0, 0, 2},
        {0, 0, 7, 0, 9, 14, 0, 0, 0},
        {0, 0, 0, 9, 0, 10, 0, 0, 0},
        {0, 0, 4, 14, 10, 0, 2, 0, 0},
        {0, 0, 0, 0, 0, 2, 0, 1, 6},
        {8, 11, 0, 0, 0, 0, 1, 0, 7},
        {0, 0, 2, 0, 0, 0, 6, 7, 0}
    };

    dijkstra(graph, 0);

    return 0;
}

Dijkstra算法的实现步骤如下:

  1. 创建距离数组distance和标记数组visited,分别用于存储源节点到各个节点的最短距离和节点的访问状态。初始时,所有节点的距离设为无穷大(表示未确定)。
  2. 将源节点的距离设为0,并依次遍历所有节点。
  3. 在遍历的过程中,选择距离最短且未被访问过的节点作为当前节点。
  4. 标记当前节点为已访问,然后更新当前节点的邻接节点的距离。如果通过当前节点到达邻接节点的距离小于邻接节点的当前距离,则更新邻接节点的距离。
  5. 重复步骤3和步骤4,直到所有节点都被访问过或者没有可以扩展的节点为止。
  6. 输出节点距离,即源节点到每个节点的最短距离。

这段代码使用了一个二维向量graph来表示图的邻接矩阵,其中graph[i][j]表示从节点i到节点j的边的权重。dijkstra函数是Dijkstra算法的主要实现部分,它通过遍历所有节点,并利用最小堆(使用线性搜索)找到当前距离最短的节点。然后,它将该节点标记为已访问,并更新其邻接节点的距离。最后,输出节点的最短距离。

这段代码的运行时间复杂度取决于节点的数量和边的数量。在每一轮的选择最短距离节点时,需要进行线性搜索,时间复杂度为O(V)。在每一轮的更新邻接节点距离时,需要遍历所有节点,时间复杂度为O(V^2)。综合起来,Dijkstra算法的总体时间复杂度为O(V^2),其中V为节点的数量。

1.2Dijkstra算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define V 9

int minDistance(int dist[], bool visited[]) {
    int min = INT_MAX;
    int minIndex;

    for (int v = 0; v < V; v++) {
        if (visited[v] == false && dist[v] <= min) {
            min = dist[v];
            minIndex = v;
        }
    }

    return minIndex;
}

void printSolution(int dist[]) {
    printf("Dijkstra's Algorithm\n");
    printf("Shortest Distance from Source Node to Each Node:\n");
    for (int v = 0; v < V; ++v) {
        printf("Node %d: %d\n", v, dist[v]);
    }
}

void dijkstra(int graph[V][V], int source) {
    int dist[V];
    bool visited[V];

    for (int v = 0; v < V; ++v) {
        dist[v] = INT_MAX;
        visited[v] = false;
    }

    dist[source] = 0;

    for (int count = 0; count < V - 1; ++count) {
        int u = minDistance(dist, visited);
        visited[u] = true;

        for (int v = 0; v < V; ++v) {
            if (!visited[v] && graph[u][v] && dist[u] != INT_MAX &&
                dist[u] + graph[u][v] < dist[v]) {
                dist[v] = dist[u] + graph[u][v];
            }
        }
    }

    printSolution(dist);
}

int main() {
    int graph[V][V] = {
        { 0, 4, 0, 0, 0, 0, 0, 8, 0 },
        { 4, 0, 8, 0, 0, 0, 0, 11, 0 },
        { 0, 8, 0, 7, 0, 4, 0, 0, 2 },
        { 0, 0, 7, 0, 9, 14, 0, 0, 0 },
        { 0, 0, 0, 9, 0, 10, 0, 0, 0 },
        { 0, 0, 4, 14, 10, 0, 2, 0, 0 },
        { 0, 0, 0, 0, 0, 2, 0, 1, 6 },
        { 8, 11, 0, 0, 0, 0, 1, 0, 7 },
        { 0, 0, 2, 0, 0, 0, 6, 7, 0 }
    };

    dijkstra(graph, 0);

    return 0;
}
import sys

V = 9

def minDistance(dist, visited):
    min = sys.maxsize
    minIndex = -1

    for v in range(V):
        if visited[v] == False and dist[v] <= min:
            min = dist[v]
            minIndex = v

    return minIndex

def printSolution(dist):
    print("Dijkstra's Algorithm")
    print("Shortest Distance from Source Node to Each Node:")
    for v in range(V):
        print("Node", v, ":", dist[v])

def dijkstra(graph, source):
    dist = [sys.maxsize] * V
    visited = [False] * V
    dist[source] = 0

    for count in range(V - 1):
        u = minDistance(dist, visited)
        visited[u] = True

        for v in range(V):
            if (not visited[v] and graph[u][v] and dist[u] != sys.maxsize and
                    dist[u] + graph[u][v] < dist[v]):
                dist[v] = dist[u] + graph[u][v]

    printSolution(dist)

graph = [
    [0, 4, 0, 0, 0, 0, 0, 8, 0],
    [4, 0, 8, 0, 0, 0, 0, 11, 0],
    [0, 8, 0, 7, 0, 4, 0, 0, 2],
    [0, 0, 7, 0, 9, 14, 0, 0, 0],
    [0, 0, 0, 9, 0, 10, 0, 0, 0],
    [0, 0, 4, 14, 10, 0, 2, 0, 0],
    [0, 0, 0, 0, 0, 2, 0, 1, 6],
    [8, 11, 0, 0, 0, 0, 1, 0, 7],
    [0, 0, 2, 0, 0, 0, 6, 7, 0]
]

dijkstra(graph, 0)

2.Bellman-Fird算法

2.1Bellman-Fird算法c++代码示例

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

// 定义无穷大表示距离为未确定状态
const int INF = INT_MAX;

// Bellman-Ford算法实现
void bellmanFord(vector<vector<int>>& graph, int source) {
    int numVertices = graph.size();

    // 创建距离数组,用于存储源节点到各个节点的最短距离
    vector<int> distance(numVertices, INF);

    // 设置源节点的距离为0
    distance[source] = 0;

    // 对每条边进行V-1次松弛操作
    for (int i = 0; i < numVertices - 1; ++i) {
        // 遍历所有边,并进行松弛操作
        for (int u = 0; u < numVertices; ++u) {
            for (int v = 0; v < numVertices; ++v) {
                if (graph[u][v] != 0 && distance[u] != INF &&
                    distance[u] + graph[u][v] < distance[v]) {
                    distance[v] = distance[u] + graph[u][v];
                }
            }
        }
    }

    // 检测是否存在负权回路
    for (int u = 0; u < numVertices; ++u) {
        for (int v = 0; v < numVertices; ++v) {
            if (graph[u][v] != 0 && distance[u] != INF &&
                distance[u] + graph[u][v] < distance[v]) {
                cout << "Negative cycle detected!" << endl;
                return;
            }
        }
    }

    // 输出节点距离
    cout << "Bellman-Ford Algorithm" << endl;
    cout << "Shortest Distance from Source Node to Each Node:" << endl;
    for (int v = 0; v < numVertices; ++v) {
        cout << "Node " << v << ": " << distance[v] << endl;
    }
}

int main() {
    int numVertices = 5;
    vector<vector<int>> graph = {
        {0, -1, 4, 0, 0},
        {0, 0, 3, 2, 2},
        {0, 0, 0, 0, 0},
        {0, 1, 5, 0, 0},
        {0, 0, 0, -3, 0}
    };

    bellmanFord(graph, 0);

    return 0;
}

2.2Bellman-Fird算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define INF INT_MAX

struct Edge {
    int source, destination, weight;
};

void bellmanFord(struct Edge graph[], int numVertices, int numEdges, int source) {
    int distance[numVertices];

    // 初始化所有节点的距离为无穷大
    for (int i = 0; i < numVertices; ++i) {
        distance[i] = INF;
    }

    // 设置源节点的距离为0
    distance[source] = 0;

    // 进行V-1次松弛操作
    for (int i = 0; i < numVertices - 1; ++i) {
        // 遍历所有边,并进行松弛操作
        for (int j = 0; j < numEdges; ++j) {
            int u = graph[j].source;
            int v = graph[j].destination;
            int weight = graph[j].weight;

            if (distance[u] != INF && distance[u] + weight < distance[v]) {
                distance[v] = distance[u] + weight;
            }
        }
    }

    // 检测是否存在负权回路
    for (int i = 0; i < numEdges; ++i) {
        int u = graph[i].source;
        int v = graph[i].destination;
        int weight = graph[i].weight;

        if (distance[u] != INF && distance[u] + weight < distance[v]) {
            printf("Negative cycle detected!\n");
            return;
        }
    }

    printf("Bellman-Ford Algorithm\n");
    printf("Shortest Distance from Source Node to Each Node:\n");
    for (int i = 0; i < numVertices; ++i) {
        printf("Node %d: %d\n", i, distance[i]);
    }
}

int main() {
    int numVertices = 5;
    int numEdges = 7;
    struct Edge graph[] = {
        {0, 1, -1},
        {0, 2, 4},
        {1, 2, 3},
        {1, 3, 2},
        {1, 4, 2},
        {3, 1, 1},
        {4, 3, -3}
    };
    int source = 0;

    bellmanFord(graph, numVertices, numEdges, source);

    return 0;
}
def bellmanFord(graph, numVertices, numEdges, source):
    distance = [float('inf')] * numVertices
    distance[source] = 0

    for i in range(numVertices - 1):
        for j in range(numEdges):
            u = graph[j][0]
            v = graph[j][1]
            weight = graph[j][2]

            if distance[u] != float('inf') and distance[u] + weight < distance[v]:
                distance[v] = distance[u] + weight

    for i in range(numEdges):
        u = graph[i][0]
        v = graph[i][1]
        weight = graph[i][2]

        if distance[u] != float('inf') and distance[u] + weight < distance[v]:
            print("Negative cycle detected!")
            return

    print("Bellman-Ford Algorithm")
    print("Shortest Distance from Source Node to Each Node:")
    for i in range(numVertices):
        print("Node", i, ":", distance[i])

numVertices = 5
numEdges = 7
graph = [
    [0, 1, -1],
    [0, 2, 4],
    [1, 2, 3],
    [1, 3, 2],
    [1, 4, 2],
    [3, 1, 1],
    [4, 3, -3]
]
source = 0

bellmanFord(graph, numVertices, numEdges, source)

五.最大流最小割

最大流最小割(Max-flow min-cut)是网络流问题中的一个经典概念和定理。

在一个网络流问题中,我们有一个有向图,其中每条边都有一个容量限制,表示该边可以传输的最大流量。网络中有一个源节点和一个汇节点,我们的目标是从源节点将尽可能多的流量送到汇节点。最大流最小割定理指出,最大流的值等于最小割的值。

割(cut)是指将网络图中的节点分为两部分的一条边集。一个割将源节点和汇节点分别放在两侧。割的容量(cut capacity)是指这些边的容量之和,也就是从源节点指向割的边的容量之和。最小割(minimum cut)是指割的容量最小的割。

最大流最小割定理的关键观察是:在最大流情况下,流量从源节点到汇节点会被割截成不同的路径,并且割的容量等于最大流的值。换句话说,最大流的值是网络中任何割的容量的下限。这也意味着想要找到最大流,只需要找到任何一个割,使得割的容量等于最大流的值。

最大流最小割算法是一类用于求解网络流问题的算法,其中最著名的算法是Edmonds-Karp算法和Ford-Fulkerson算法,它们都基于不断寻找增广路径来求解最大流问题。这些算法在实际应用中被广泛使用,例如在网络通信、交通流量调度和图像分割等领域。

1.Edmonds-Karp算法

Edmonds-Karp算法是用于解决最大流最小割问题的一个经典算法。它是基于最大流最小割定理的一种实现方法。

最大流最小割定理指出,一个图的最大流的值等于图中任意一个割的容量。割将图的节点分为两个部分,并且割的容量等于从源节点指向割的边的容量之和。因此,为了找到图的最大流,我们只需找到任意一个割,使其容量等于最大流的值。

Edmonds-Karp算法利用广度优先搜索(BFS)来寻找增广路径,并通过不断更新剩余容量来增加流量。它遍历图中的所有增广路径,每次寻找到一条从源节点到汇节点的增广路径,就将该路径上的最小剩余容量作为流量增加到当前的最大流中。这个过程不断重复,直到不存在从源节点到汇节点的增广路径为止。最终得到的最大流就是图的最大流。

因此,可以说Edmonds-Karp算法是一种用于求解最大流最小割问题的具体算法实现。它是一种有效而且常用的算法,在实际应用中广泛被使用。

1.1Edmonds-Karp算法c++代码示例

#include <iostream>
#include <queue>
#include <limits.h>

#define V 6 // 定义图的节点数量

// 使用邻接矩阵来表示图的边
int graph[V][V] = {
    {0, 16, 13, 0, 0, 0},
    {0, 0, 10, 12, 0, 0},
    {0, 4, 0, 0, 14, 0},
    {0, 0, 9, 0, 0, 20},
    {0, 0, 0, 7, 0, 4},
    {0, 0, 0, 0, 0, 0}
};

bool bfs(int residualGraph[V][V], int source, int sink, int parent[]) {
    bool visited[V];
    memset(visited, 0, sizeof(visited));

    std::queue<int> q;
    q.push(source);
    visited[source] = true;
    parent[source] = -1;

    while (!q.empty()) {
        int u = q.front();
        q.pop();

        for (int v = 0; v < V; ++v) {
            if (!visited[v] && residualGraph[u][v] > 0) {
                q.push(v);
                parent[v] = u;
                visited[v] = true;
            }
        }
    }

    return visited[sink];
}

int edmondsKarp(int source, int sink) {
    int residualGraph[V][V];
    for (int u = 0; u < V; ++u) {
        for (int v = 0; v < V; ++v) {
            residualGraph[u][v] = graph[u][v];
        }
    }

    int maxFlow = 0;
    int parent[V];

    while (bfs(residualGraph, source, sink, parent)) {
        int pathFlow = INT_MAX;

        // 找到增广路径上的最小剩余容量
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            pathFlow = std::min(pathFlow, residualGraph[u][v]);
        }

        // 更新增广路径上的边的剩余容量
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            residualGraph[u][v] -= pathFlow;
            residualGraph[v][u] += pathFlow;
        }

        maxFlow += pathFlow;
    }

    return maxFlow;
}

int main() {
    int source = 0;
    int sink = 5;
    int maxFlow = edmondsKarp(source, sink);

    std::cout << "Max Flow: " << maxFlow << std::endl;

    return 0;
}

这段C++代码实现了Edmonds-Karp算法来求解最大流问题。算法的关键在于不断寻找增广路径并更新剩余容量。

首先,定义了一个邻接矩阵graph来表示图的边,其中graph[u][v]表示节点u到节点v之间的容量限制。

然后,在bfs函数中使用广度优先搜索(BFS)来寻找增广路径。该函数维护一个队列,并记录每个节点是否被访问过。当存在未访问节点且存在剩余容量的边时,将该节点加入队列,并标记为已访问。同时,记录节点的父节点,以便构造增广路径。

edmondsKarp函数中,创建一个初始的剩余图residualGraph,并初始化最大流为0。然后,利用bfs函数寻找增广路径,如果找到了从源节点到汇节点的路径,则计算该路径上的最小剩余容量。接着,更新增广路径上的边的剩余容量,并将最小剩余容量累加到最大流中。重复以上操作,直到不存在从源节点到汇节点的增广路径。

最后,在main函数中设置源节点和汇节点,并调用edmondsKarp函数来计算最大流。打印结果即为最大流的值。

该算法的时间复杂度为O(V * E^2),其中V是节点数量,E是边数量。算法使用BFS来寻找增广路径,因此最坏情况下每次BFS操作的时间复杂度为O(E)。在最多O(V)次BFS操作中,可能会进行O(E)次更新操作。因此,总体时间复杂度为O(V * E^2)。

1.2Edmonds-Karp算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define V 6  // 定义图的节点数量

// 使用邻接矩阵来表示图的边
int graph[V][V] = {
    {0, 16, 13, 0, 0, 0},
    {0, 0, 10, 12, 0, 0},
    {0, 4, 0, 0, 14, 0},
    {0, 0, 9, 0, 0, 20},
    {0, 0, 0, 7, 0, 4},
    {0, 0, 0, 0, 0, 0}
};

bool bfs(int residualGraph[V][V], int source, int sink, int parent[]) {
    bool visited[V];
    memset(visited, 0, sizeof(visited));

    int queue[V];
    int front = 0, rear = 0;
    queue[rear++] = source;
    visited[source] = true;
    parent[source] = -1;

    while (front != rear) {
        int u = queue[front++];

        for (int v = 0; v < V; ++v) {
            if (!visited[v] && residualGraph[u][v] > 0) {
                queue[rear++] = v;
                parent[v] = u;
                visited[v] = true;
            }
        }
    }

    return visited[sink];
}

int edmondsKarp(int source, int sink) {
    int residualGraph[V][V];
    for (int u = 0; u < V; ++u) {
        for (int v = 0; v < V; ++v) {
            residualGraph[u][v] = graph[u][v];
        }
    }

    int maxFlow = 0;
    int parent[V];

    while (bfs(residualGraph, source, sink, parent)) {
        int pathFlow = INT_MAX;

        // 找到增广路径上的最小剩余容量
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            pathFlow = (pathFlow < residualGraph[u][v]) ? pathFlow : residualGraph[u][v];
        }

        // 更新增广路径上的边的剩余容量
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            residualGraph[u][v] -= pathFlow;
            residualGraph[v][u] += pathFlow;
        }

        maxFlow += pathFlow;
    }

    return maxFlow;
}

int main() {
    int source = 0;
    int sink = 5;
    int maxFlow = edmondsKarp(source, sink);

    printf("Max Flow: %d\n", maxFlow);

    return 0;
}
from collections import deque

V = 6  # 定义图的节点数量

# 使用邻接矩阵来表示图的边
graph = [
    [0, 16, 13, 0, 0, 0],
    [0, 0, 10, 12, 0, 0],
    [0, 4, 0, 0, 14, 0],
    [0, 0, 9, 0, 0, 20],
    [0, 0, 0, 7, 0, 4],
    [0, 0, 0, 0, 0, 0]
]

def bfs(residualGraph, source, sink, parent):
    visited = [False] * V
    queue = deque()
    queue.append(source)
    visited[source] = True
    parent[source] = -1

    while queue:
        u = queue.popleft()

        for v in range(V):
            if not visited[v] and residualGraph[u][v] > 0:
                queue.append(v)
                parent[v] = u
                visited[v] = True

    return visited[sink]

def edmondsKarp(source, sink):
    residualGraph = [row[:] for row in graph]

    maxFlow = 0
    parent = [None] * V

    while bfs(residualGraph, source, sink, parent):
        pathFlow = float('inf')

        # 找到增广路径上的最小剩余容量
        v = sink
        while v != source:
            u = parent[v]
            pathFlow = min(pathFlow, residualGraph[u][v])
            v = parent[v]

        # 更新增广路径上的边的剩余容量
        v = sink
        while v != source:
            u = parent[v]
            residualGraph[u][v] -= pathFlow
            residualGraph[v][u] += pathFlow
            v = parent[v]

        maxFlow += pathFlow

    return maxFlow

source = 0
sink = 5
maxFlow = edmondsKarp(source, sink)

print("Max Flow:", maxFlow)

2.Ford-Fulkerson算法

Ford-Fulkerson算法是一种用于解决最大流问题的经典算法。该算法通过不断寻找增广路径,并更新路径上的边的流量来逐步增加流量,直到找不到更多的增广路径为止。最终得到的流量即为图的最大流。

算法的基本思想是从初始流量为0的图开始,重复以下步骤:

1. 使用深度优先搜索(DFS)或广度优先搜索(BFS)在残余图中寻找一条从源节点到汇节点的增广路径。增广路径是指路径上所有边的剩余容量大于0,并且路径中的最小剩余容量定义了该路径的瓶颈。

2. 如果找到增广路径,则通过该路径上的瓶颈值来增加流量。具体做法是遍历增广路径上的每条边,将瓶颈值加到该边的流量上,并且将瓶颈值作为反向边的流量进行减少。

3. 重复步骤1和步骤2,直到找不到增广路径。此时,算法终止,并且得到的最大流即为图的最大流。

Ford-Fulkerson算法的关键在于如何选择增广路径。在每次搜索增广路径时,可以使用DFS或BFS来寻找路径,具体取决于选择哪种搜索策略。此外,也可以使用不同的策略来选择增广路径,例如随机选择或优先选择剩余容量最大的路径。

需要注意的是,Ford-Fulkerson算法的时间复杂度取决于增广路径的选择策略。在最坏的情况下,其时间复杂度可能会非常高,但通过使用一些优化技巧,如Edmonds-Karp算法中的BFS,可以将时间复杂度限制在O(V^2E),其中V是节点数量,E是边数量。

Ford-Fulkerson算法是解决最大流问题的经典算法,对于许多流网络应用都是有效并可行的。

2.1Ford-Fulkerson算法c++代码示例

#include <iostream>
#include <vector>
#include <queue>
#include <limits.h>

using namespace std;

// 定义图的边的数据结构
struct Edge {
    int v;          // 边的终点
    int capacity;   // 边的容量
    int flow;       // 边的当前流量
    int reverse;    // 反向边在邻接表中的下标
};

// 使用邻接表来表示图
class Graph {
private:
    int V;                              // 图的节点数量
    vector<vector<Edge>> adjacencyList; // 邻接表

public:
    Graph(int V) {
        this->V = V;
        adjacencyList.resize(V);
    }

    // 添加边到邻接表中
    void addEdge(int u, int v, int capacity) {
        Edge directEdge = { v, capacity, 0, adjacencyList[v].size() };
        Edge reverseEdge = { u, 0, 0, adjacencyList[u].size() };
        adjacencyList[u].push_back(directEdge);
        adjacencyList[v].push_back(reverseEdge);
    }

    // 使用BFS寻找从源节点到汇节点的增广路径,并返回是否找到路径
    bool bfs(int source, int sink, vector<int>& parent) {
        vector<bool> visited(V, false);
        queue<int> q;

        q.push(source);
        visited[source] = true;
        parent[source] = -1;

        while (!q.empty()) {
            int u = q.front();
            q.pop();

            for (const auto& edge : adjacencyList[u]) {
                int v = edge.v;
                int capacity = edge.capacity;
                int flow = edge.flow;

                if (!visited[v] && capacity > flow) {
                    q.push(v);
                    parent[v] = u;
                    visited[v] = true;
                }
            }
        }

        return visited[sink];
    }

    // 使用DFS遍历增广路径,并更新路径上的边的流量
    int dfs(int u, int sink, int minCapacity, vector<int>& parent) {
        if (u == sink)
            return minCapacity;

        for (auto& edge : adjacencyList[u]) {
            int v = edge.v;
            int capacity = edge.capacity;
            int flow = edge.flow;
            int reverse = edge.reverse;

            if (capacity > flow && parent[v] == u) {
                int updatedFlow = dfs(v, sink, min(minCapacity, capacity - flow), parent);

                if (updatedFlow > 0) {
                    edge.flow += updatedFlow;
                    adjacencyList[v][reverse].flow -= updatedFlow;
                    return updatedFlow;
                }
            }
        }

        return 0;
    }

    // 使用Ford-Fulkerson算法求解最大流
    int fordFulkerson(int source, int sink) {
        vector<int> parent(V, -1);  // 存储路径的父节点
        int maxFlow = 0;

        while (bfs(source, sink, parent)) {
            int minCapacity = INT_MAX;

            // 在增广路径上找到瓶颈值(最小剩余容量)
            for (int v = sink; v != source; v = parent[v]) {
                int u = parent[v];
                for (auto& edge : adjacencyList[u]) {
                    if (edge.v == v) {
                        minCapacity = min(minCapacity, edge.capacity - edge.flow);
                        break;
                    }
                }
            }

            // 更新增广路径上的边的流量
            for (int v = sink; v != source; v = parent[v]) {
                int u = parent[v];
                for (auto& edge : adjacencyList[u]) {
                    if (edge.v == v) {
                        edge.flow += minCapacity;
                        adjacencyList[v][edge.reverse].flow -= minCapacity;
                        break;
                    }
                }
            }

            maxFlow += minCapacity;
        }

        return maxFlow;
    }
};

int main() {
    int V = 6; // 图的节点数量
    Graph graph(V);

    // 添加边到图中
    graph.addEdge(0, 1, 16);
    graph.addEdge(0, 2, 13);
    graph.addEdge(1, 2, 10);
    graph.addEdge(1, 3, 12);
    graph.addEdge(2, 1, 4);
    graph.addEdge(2, 4, 14);
    graph.addEdge(3, 2, 9);
    graph.addEdge(3, 5, 20);
    graph.addEdge(4, 3, 7);
    graph.addEdge(4, 5, 4);

    int source = 0;
    int sink = 5;

    int maxFlow = graph.fordFulkerson(source, sink);
    cout << "Max Flow: " << maxFlow << endl;

    return 0;
}

该代码使用了邻接表来表示图,使用了Edge结构体来存储边的信息,包括终点、容量、流量和反向边的下标。Graph类实现了添加边、使用BFS查找增广路径、使用DFS遍历增广路径和主要的Ford-Fulkerson算法函数。在main函数中创建了一个Graph对象,并添加了边。然后调用fordFulkerson函数来计算最大流,并输出结果。

Ford-Fulkerson算法的核心思想就是不断寻找增广路径并更新路径上的边的流量,直到找不到增广路径为止。其中,BFS用于寻找增广路径,DFS用于遍历增广路径。通过不断地更新路径上的边的流量,最终得到的流量即为图的最大流。

2.2Ford-Fulkerson算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define V 6

// 边的数据结构
struct Edge {
    int v;          // 边的终点
    int capacity;   // 边的容量
    int flow;       // 边的当前流量
    int reverse;    // 反向边的下标
};

// 图的数据结构
struct Graph {
    int numNodes;                   // 节点数量
    struct Edge *adjacencyList[V];  // 邻接表
};

// 创建边
struct Edge *createEdge(int v, int capacity, int reverse) {
    struct Edge *edge = (struct Edge *)malloc(sizeof(struct Edge));
    edge->v = v;
    edge->capacity = capacity;
    edge->flow = 0;
    edge->reverse = reverse;
    return edge;
}

// 添加边到图中
void addEdge(struct Graph *graph, int u, int v, int capacity) {
    graph->adjacencyList[u] = createEdge(v, capacity, graph->adjacencyList[v] ? graph->adjacencyList[v]->reverse : 0);
    graph->adjacencyList[v] = createEdge(u, 0, graph->adjacencyList[u] ? graph->adjacencyList[u]->reverse : 0);
}

// 使用BFS寻找从源节点到汇节点的增广路径,并返回是否找到路径
bool bfs(struct Graph *graph, int source, int sink, int parent[]) {
    bool visited[V] = { false };
    int queue[V];
    int front = 0, rear = 0;

    visited[source] = true;
    parent[source] = -1;
    queue[rear++] = source;

    while (front < rear) {
        int u = queue[front++];

        for (struct Edge *edge = graph->adjacencyList[u]; edge != NULL; edge = graph->adjacencyList[u]) {
            int v = edge->v;
            int capacity = edge->capacity;
            int flow = edge->flow;

            if (!visited[v] && capacity > flow) {
                parent[v] = u;
                visited[v] = true;
                queue[rear++] = v;
            }

            u = edge->v;
        }
    }

    return visited[sink];
}

// 使用DFS遍历增广路径,并更新路径上的边的流量
int dfs(struct Graph *graph, int u, int sink, int minCapacity, int parent[]) {
    if (u == sink)
        return minCapacity;

    for (struct Edge *edge = graph->adjacencyList[u]; edge != NULL; edge = graph->adjacencyList[u]) {
        int v = edge->v;
        int capacity = edge->capacity;
        int flow = edge->flow;
        int reverse = edge->reverse;

        if (capacity > flow && parent[v] == u) {
            int updatedFlow = dfs(graph, v, sink, min(minCapacity, capacity - flow), parent);

            if (updatedFlow > 0) {
                edge->flow += updatedFlow;
                graph->adjacencyList[v]->flow -= updatedFlow;
                return updatedFlow;
            }
        }

        u = edge->v;
    }

    return 0;
}

// 使用Ford-Fulkerson算法求解最大流
int fordFulkerson(struct Graph *graph, int source, int sink) {
    int parent[V];
    int maxFlow = 0;

    while (bfs(graph, source, sink, parent)) {
        int minCapacity = INT_MAX;

        // 在增广路径上找到瓶颈值(最小剩余容量)
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            struct Edge *edge = graph->adjacencyList[u];

            while (edge && edge->v != v) {
                edge = graph->adjacencyList[u];
            }

            if (edge) {
                minCapacity = min(minCapacity, edge->capacity - edge->flow);
            }
        }

        // 更新增广路径上的边的流量
        for (int v = sink; v != source; v = parent[v]) {
            int u = parent[v];
            struct Edge *edge = graph->adjacencyList[u];

            while (edge && edge->v != v) {
                edge = graph->adjacencyList[u];
            }

            if (edge) {
                edge->flow += minCapacity;
                graph->adjacencyList[v]->flow -= minCapacity;
            }
        }

        maxFlow += minCapacity;
    }

    return maxFlow;
}

int main() {
    struct Graph graph;
    graph.numNodes = V;

    for (int i = 0; i < V; i++) {
        graph.adjacencyList[i] = NULL;
    }

    // 添加边到图中
    addEdge(&graph, 0, 1, 16);
    addEdge(&graph, 0, 2, 13);
    addEdge(&graph, 1, 2, 10);
    addEdge(&graph, 1, 3, 12);
    addEdge(&graph, 2, 1, 4);
    addEdge(&graph, 2, 4, 14);
    addEdge(&graph, 3, 2, 9);
    addEdge(&graph, 3, 5, 20);
    addEdge(&graph, 4, 3, 7);
    addEdge(&graph, 4, 5, 4);

    int source = 0;
    int sink = 5;

    int maxFlow = fordFulkerson(&graph, source, sink);
    printf("Max Flow: %d\n", maxFlow);

    return 0;
}
from collections import defaultdict

class Graph:
    def __init__(self, numNodes):
        self.numNodes = numNodes
        self.adjacencyList = defaultdict(list)

    def addEdge(self, u, v, capacity):
        directEdge = {"v": v, "capacity": capacity, "flow": 0, "reverse": len(self.adjacencyList[v])}
        reverseEdge = {"v": u, "capacity": 0, "flow": 0, "reverse": len(self.adjacencyList[u])}
        self.adjacencyList[u].append(directEdge)
        self.adjacencyList[v].append(reverseEdge)

    def bfs(self, source, sink, parent):
        visited = [False] * self.numNodes
        queue = []

        queue.append(source)
        visited[source] = True
        parent[source] = -1

        while queue:
            u = queue.pop(0)

            for edge in self.adjacencyList[u]:
                v = edge["v"]
                capacity = edge["capacity"]
                flow = edge["flow"]

                if not visited[v] and capacity > flow:
                    queue.append(v)
                    parent[v] = u
                    visited[v] = True

        return visited[sink]

    def dfs(self, u, sink, minCapacity, parent):
        if u == sink:
            return minCapacity
        
        for edge in self.adjacencyList[u]:
            v = edge["v"]
            capacity = edge["capacity"]
            flow = edge["flow"]
            reverse = edge["reverse"]

            if capacity > flow and parent[v] == u:
                updatedFlow = self.dfs(v, sink, min(minCapacity, capacity - flow), parent)

                if updatedFlow > 0:
                    edge["flow"] += updatedFlow
                    self.adjacencyList[v][reverse]["flow"] -= updatedFlow
                    return updatedFlow
        
        return 0

    def fordFulkerson(self, source, sink):
        parent = [-1] * self.numNodes
        maxFlow = 0

        while self.bfs(source, sink, parent):
            minCapacity = float("inf")

            for v in range(sink, source, -1):
                u = parent[v]
                for edge in self.adjacencyList[u]:
                    if edge["v"] == v:
                        minCapacity = min(minCapacity, edge["capacity"] - edge["flow"])
                        break

            for v in range(sink, source, -1):
                u = parent[v]
                for edge in self.adjacencyList[u]:
                    if edge["v"] == v:
                        edge["flow"] += minCapacity
                        self.adjacencyList[v][edge["reverse"]]["flow"] -= minCapacity
                        break

            maxFlow += minCapacity

        return maxFlow

if __name__ == "__main__":
    V = 6 # 节点数量
    graph = Graph(V)

    # 添加边到图中
    graph.addEdge(0, 1, 16)
    graph.addEdge(0, 2, 13)
    graph.addEdge(1, 2, 10)
    graph.addEdge(1, 3, 12)
    graph.addEdge(2, 1, 4)
    graph.addEdge(2, 4, 14)
    graph.addEdge(3, 2, 9)
    graph.addEdge(3, 5, 20)
    graph.addEdge(4, 3, 7)
    graph.addEdge(4, 5, 4)

    source = 0
    sink = 5

    maxFlow = graph.fordFulkerson(source, sink)
    print("Max Flow:", maxFlow)

六.拓扑排序

拓扑排序是一种对有向无环图(DAG)中的节点进行排序的算法。在拓扑排序中,节点表示任务或事件,有向边表示任务之间的依赖关系。拓扑排序的目的是将任务按照依赖关系的顺序进行排序,使得任何一个任务的前置任务都排在它的前面。

具体来说,拓扑排序的过程如下:
1. 找到图中入度为0的节点(即没有依赖的节点),将其加入结果列表中。
2. 删除该节点,并更新与该节点相邻的节点的入度,即将它们的入度减1。
3. 重复步骤1和步骤2,直到图中所有节点都被处理过。

如果拓扑排序能够成功完成,那么结果列表中的节点顺序就是一种满足依赖关系的排序。拓扑排序常用于任务调度、编译顺序等场景。请注意,拓扑排序只适用于有向无环图,若图中存在环路,则无法进行拓扑排序。

1.拓扑c++代码示例

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

vector<int> topologicalSort(vector<vector<int>>& graph) {
    int numNodes = graph.size();
    
    // 统计每个节点的入度
    vector<int> inDegrees(numNodes, 0);
    for (int i = 0; i < numNodes; i++) {
        for (auto neighbor : graph[i]) {
            inDegrees[neighbor]++;
        }
    }
    
    // 使用队列来存储入度为0的节点
    queue<int> q;
    for (int i = 0; i < numNodes; i++) {
        if (inDegrees[i] == 0) {
            q.push(i);
        }
    }
    
    // 进行拓扑排序的结果数组
    vector<int> result;
    
    // 开始拓扑排序
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        result.push_back(node); // 将当前节点加入结果数组
        
        // 更新相邻节点的入度
        for (auto neighbor : graph[node]) {
            inDegrees[neighbor]--;
            
            // 如果入度为0,则将其加入队列中
            if (inDegrees[neighbor] == 0) {
                q.push(neighbor);
            }
        }
    }
    
    return result;
}

int main() {
    vector<vector<int>> graph = {{1, 2}, {3}, {3, 4}, {4}, {}};
    
    vector<int> result = topologicalSort(graph);
    
    cout << "拓扑排序结果: ";
    for (int node : result) {
        cout << node << " ";
    }
    
    return 0;
}

这段代码首先统计了每个节点的入度,然后利用队列存储入度为0的节点。然后从队列中取出一个节点,并将其加入结果数组中。接着遍历该节点的相邻节点,将相邻节点的入度减1,并将入度减为0的节点加入队列。如此循环,直到队列为空,所有节点都按照拓扑排序的顺序加入到结果数组中。

最后,将结果数组输出即可得到拓扑排序的结果。

2.拓扑排序的c代码和python代码示例

#include <stdio.h>
#include <stdlib.h>

#define MAX_NODES 100

// 定义图的数据结构
struct Graph {
    int numNodes;
    int adjacencyMatrix[MAX_NODES][MAX_NODES];
};

// 初始化图
void initGraph(struct Graph* graph, int numNodes) {
    graph->numNodes = numNodes;
    for (int i = 0; i < numNodes; i++) {
        for (int j = 0; j < numNodes; j++) {
            graph->adjacencyMatrix[i][j] = 0;
        }
    }
}

// 添加边
void addEdge(struct Graph* graph, int start, int end) {
    graph->adjacencyMatrix[start][end] = 1;
}

// 拓扑排序
void topologicalSort(struct Graph* graph, int node, int* visited, int* result, int* index) {
    visited[node] = 1;
    
    for (int i = 0; i < graph->numNodes; i++) {
        if (graph->adjacencyMatrix[node][i] == 1 && visited[i] == 0) {
            topologicalSort(graph, i, visited, result, index);
        }
    }
    
    result[*index] = node;
    (*index)--;
}

// 调用拓扑排序算法进行排序
void performTopologicalSort(struct Graph* graph) {
    int numNodes = graph->numNodes;
    int visited[MAX_NODES] = {0};
    int result[MAX_NODES];
    int index = numNodes - 1;
    
    for (int i = 0; i < numNodes; i++) {
        if (visited[i] == 0) {
            topologicalSort(graph, i, visited, result, &index);
        }
    }
    
    printf("拓扑排序结果: ");
    for (int i = 0; i < numNodes; i++) {
        printf("%d ", result[i]);
    }
    printf("\n");
}

int main() {
    // 创建一个图对象,并初始化
    struct Graph graph;
    initGraph(&graph, 5);
    
    // 添加边
    addEdge(&graph, 0, 1);
    addEdge(&graph, 0, 2);
    addEdge(&graph, 1, 3);
    addEdge(&graph, 2, 3);
    addEdge(&graph, 2, 4);
    addEdge(&graph, 3, 4);
    
    // 进行拓扑排序
    performTopologicalSort(&graph);
    
    return 0;
}
from collections import deque

# 拓扑排序
def topologicalSort(graph):
    numNodes = len(graph)
    
    # 统计每个节点的入度
    inDegrees = [0] * numNodes
    for neighbors in graph.values():
        for neighbor in neighbors:
            inDegrees[neighbor] += 1
    
    # 使用队列来存储入度为0的节点
    queue = deque()
    for node in range(numNodes):
        if inDegrees[node] == 0:
            queue.append(node)
    
    # 进行拓扑排序的结果列表
    result = []
    
    # 开始拓扑排序
    while queue:
        node = queue.popleft()
        result.append(node)  # 将当前节点加入结果列表
        
        # 更新相邻节点的入度
        for neighbor in graph.get(node, []):
            inDegrees[neighbor] -= 1
            
            # 如果入度为0,则将其加入队列中
            if inDegrees[neighbor] == 0:
                queue.append(neighbor)
    
    return result

def main():
    # 创建一个有向图
    graph = {
        0: [1, 2],
        1: [3],
        2: [3, 4],
        3: [4],
        4: []
    }
    
    # 进行拓扑排序
    result = topologicalSort(graph)
    
    # 输出拓扑排序结果
    print("拓扑排序结果:", result)
    
if __name__ == "__main__":
    main()

六.图的匹配

图的匹配是图论中的一个重要概念,用于描述在一个图中找到一组边,使得这些边互不相邻。这组边被称为匹配。

具体来说,对于一个无向图,图的匹配是指选择图中的一些边,使得这些边没有公共顶点。换句话说,对于任意两条边来说,它们都不相邻。

匹配在很多实际应用中都有重要的作用。例如,在社交网络中,可以利用匹配来寻找用户之间的潜在关系;在电子电路设计中,可以利用匹配来寻找电路中满足特定条件的连接方式。

在图论中,有很多算法可以用来寻找图的匹配,其中最著名的算法之一是匈牙利算法(Hungarian algorithm)。匈牙利算法可以在二分图中寻找最大的匹配,即找到尽可能多的不相邻的边。其他常用的匹配算法还包括最大流算法(如Edmonds-Karp算法(5.1.2))和深度优先搜索算法(1.1)。

1.匈牙利算法

匈牙利算法(Hungarian algorithm),也称为增广路径算法,是解决二分图最大匹配问题的经典算法之一。匈牙利算法的目标是找到一个最大的匹配,即找到尽可能多的互不相邻的边。

具体来说,匈牙利算法是通过不断寻找增广路径来不断扩大匹配的。增广路径是指一条路径,路径的起点和终点都是未匹配的顶点,且路径上的边交替属于匹配和非匹配边。通过寻找增广路径,可以将原有的匹配中的某些边移除,并加入新的边,从而使匹配数量增加。

匈牙利算法的基本步骤如下:
1. 初始化一个空的匹配集合。
2. 对于每个未匹配的顶点,尝试寻找一条增广路径。
3. 如果找到增广路径,则将原匹配中的边移除,并加入新的边。
4. 如果无法找到增广路径,则结束算法,得到最大匹配。

匈牙利算法通过使用深度优先搜索或广度优先搜索来查找增广路径。它可以在多项式时间内解决二分图最大匹配问题,时间复杂度为O(V^3),其中V是顶点的数量。

匈牙利算法的应用非常广泛,比如在任务分配、稳定婚姻匹配、社交网络分析等领域都有应用。此外,匈牙利算法也是其他一些更复杂算法的基础,如KM算法(也称为Kuhn-Munkres算法),它可以在带权二分图上求解最大权匹配问题。

1.1匈牙利算法c++代码示例

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

#define MAX_NODES 100 // 最大节点数

// 图的数据结构
vector<int> graph[MAX_NODES];

// 顶点的匹配关系数组
int match[MAX_NODES];

// 记录顶点是否已被访问过的数组
bool visited[MAX_NODES];

// 初始化图
void initGraph(int numNodes) {
    for (int i = 0; i < numNodes; i++) {
        graph[i].clear();
    }
}

// 添加边
void addEdge(int u, int v) {
    graph[u].push_back(v);
}

// 在当前匹配中寻找增广路径
bool findAugmentingPath(int node) {
    for (int i = 0; i < graph[node].size(); i++) {
        int neighbor = graph[node][i];

        // 如果邻居节点未被访问过,则将其标记为已访问
        if (!visited[neighbor]) {
            visited[neighbor] = true;

            // 如果邻居节点未匹配或能在与其相邻的节点中找到增广路径,则进行匹配
            if (match[neighbor] == -1 || findAugmentingPath(match[neighbor])) {
                match[neighbor] = node;
                return true;
            }
        }
    }
    
    return false;
}

// 使用匈牙利算法找到最大匹配
int maximumMatching(int numNodes) {
    // 初始化顶点的匹配关系
    memset(match, -1, sizeof(match));
    
    int count = 0; // 记录匹配的边的数量
    
    for (int i = 0; i < numNodes; i++) {
        // 初始化记录顶点是否已被访问过的数组
        memset(visited, false, sizeof(visited));
        
        // 在当前顶点的邻居中寻找增广路径
        if (findAugmentingPath(i)) {
            count++;
        }
    }
    
    return count;
}

int main() {
    int numNodes = 4;
    
    // 初始化图
    initGraph(numNodes);
    
    // 添加边
    addEdge(0, 1);
    addEdge(1, 2);
    addEdge(2, 0);
    addEdge(2, 3);
    
    // 使用匈牙利算法找到最大匹配
    int maxMatching = maximumMatching(numNodes);
    
    cout << "最大匹配数: " << maxMatching << endl;
    
    return 0;
}

这段代码首先定义了一个全局的图数据结构,使用邻接表表示图的连接关系。然后定义了顶点的匹配关系数组match和记录顶点是否已被访问过的数组visited

匈牙利算法采用递归的方式,在每一次递归中查找增广路径来扩大匹配。函数findAugmentingPath用于在当前匹配中寻找增广路径。在该函数中,遍历当前节点的邻居节点,如果邻居节点未被访问过,则继续递归地寻找增广路径。如果邻居节点未匹配或能在与其相邻的节点中找到增广路径,则进行匹配,将邻居节点与当前节点进行匹配。

函数maximumMatching用于使用匈牙利算法找到最大匹配。在该函数中,遍历所有节点,对于每个节点,使用findAugmentingPath来查找增广路径,并进行匹配。

最后,在main函数中,初始化图,并添加边。然后使用匈牙利算法找到最大匹配,并将结果输出。

该代码的实现基于匈牙利算法的思想,通过不断寻找增广路径来扩大匹配集合。通过递归和回溯的方式,在遍历图中的顶点时,不断尝试寻找增广路径,并进行匹配,直到无法再找到增广路径为止。这样就可以得到最大的匹配数。

1.2匈牙利算法的c代码和python代码示例

#include <stdio.h>
#include <stdbool.h>

#define MAX_NODES 100 // 最大节点数

// 图的数据结构
int graph[MAX_NODES][MAX_NODES];

// 顶点的匹配关系数组
int match[MAX_NODES];

// 记录顶点是否已被访问过的数组
bool visited[MAX_NODES];

// 初始化图
void initGraph(int numNodes) {
    for (int i = 0; i < numNodes; i++) {
        for (int j = 0; j < numNodes; j++) {
            graph[i][j] = 0;
        }
    }
}

// 添加边
void addEdge(int u, int v) {
    graph[u][v] = 1;
}

// 在当前匹配中寻找增广路径
bool findAugmentingPath(int node, int numNodes) {
    for (int i = 0; i < numNodes; i++) {
        if (graph[node][i] && !visited[i]) {
            visited[i] = true;

            if (match[i] == -1 || findAugmentingPath(match[i], numNodes)) {
                match[i] = node;
                return true;
            }
        }
    }

    return false;
}

// 使用匈牙利算法找到最大匹配
int maximumMatching(int numNodes) {
    // 初始化顶点的匹配关系
    for (int i = 0; i < numNodes; i++) {
        match[i] = -1;
    }

    int count = 0;

    for (int i = 0; i < numNodes; i++) {
        // 初始化记录顶点是否已被访问过的数组
        for (int j = 0; j < numNodes; j++) {
            visited[j] = false;
        }

        if (findAugmentingPath(i, numNodes)) {
            count++;
        }
    }

    return count;
}

int main() {
    int numNodes = 4;

    // 初始化图
    initGraph(numNodes);

    // 添加边
    addEdge(0, 1);
    addEdge(1, 2);
    addEdge(2, 0);
    addEdge(2, 3);

    // 使用匈牙利算法找到最大匹配
    int maxMatching = maximumMatching(numNodes);

    printf("最大匹配数: %d\n", maxMatching);

    return 0;
}
def find_augmenting_path(node, graph, match, visited):
    num_nodes = len(graph)
    for i in range(num_nodes):
        if graph[node][i] and not visited[i]:
            visited[i] = True

            if match[i] == -1 or find_augmenting_path(match[i], graph, match, visited):
                match[i] = node
                return True

    return False

def maximum_matching(graph):
    num_nodes = len(graph)

    match = [-1] * num_nodes
    count = 0

    for i in range(num_nodes):
        visited = [False] * num_nodes

        if find_augmenting_path(i, graph, match, visited):
            count += 1

    return count

def main():
    num_nodes = 4

    # 初始化图
    graph = [[0] * num_nodes for _ in range(num_nodes)]

    # 添加边
    graph[0][1] = 1
    graph[1][2] = 1
    graph[2][0] = 1
    graph[2][3] = 1

    # 使用匈牙利算法找到最大匹配
    max_matching = maximum_matching(graph)

    print("最大匹配数:", max_matching)

if __name__ == "__main__":
    main()

总结

像最小费用流、割边和割点、有向无环图的计数等等图算法,还可以参考一些算法教育网站和在线资源,例如GeeksforGeeks、Stack Overflow、算法导论等,它们提供了大量的算法学习资料、教程和示例代码。

后续还会出一期关键字的使用

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
以下是一个简单的城乡居民养老保险算法的代码示例,仅供参考: ``` # 定义基本养老金标准和年龄、性别等因素的影响系数 basic_pension = 1000 age_factor = 10 gender_factor = {'male': 1.2, 'female': 0.8} payment_factor = 0.5 # 计算个人应该获得的基本养老金标准 def calculate_basic_pension(age, gender, payment_years): return basic_pension + age_factor * age * gender_factor[gender] * payment_years # 计算个人实际应该获得的养老金数额 def calculate_actual_pension(age, gender, payment_years, payment_amount): return payment_factor * payment_amount + (1 - payment_factor) * calculate_basic_pension(age, gender, payment_years) # 计算国家应该给予的补贴 def calculate_subsidy(age, gender, payment_years, payment_amount): basic_pension = calculate_basic_pension(age, gender, payment_years) actual_pension = calculate_actual_pension(age, gender, payment_years, payment_amount) if actual_pension < basic_pension: return basic_pension - actual_pension else: return 0 # 计算个人实际获得的养老金数额 def calculate_final_pension(age, gender, payment_years, payment_amount): return calculate_actual_pension(age, gender, payment_years, payment_amount) + calculate_subsidy(age, gender, payment_years, payment_amount) # 示例:计算一个年龄为60岁、男性、缴纳养老保险金10年、每年缴纳养老保险金1000元的居民应该获得的养老金数额 final_pension = calculate_final_pension(age=60, gender='male', payment_years=10, payment_amount=1000) print(final_pension) ``` 需要注意的是,上述代码仅是一个简单的示例,实际的城乡居民养老保险算法需要考虑更多的因素和细节,并且可能需要结合具体的政策和法规进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fuxi-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值