算法设计第三次作业

6.7.1.2

以下是使用C++语言描述矩阵连乘最优次序备忘录方法的代码:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

const int MAX_N = 100;
int dp[MAX_N][MAX_N];

// 计算矩阵i到j的最小乘次数
int matrixChainMultiplication(vector<int>& matrix, int i, int j) {
    if (i == j) {
        return 0;
    }
    if (dp[i][j] != -1) {
        return dp[i][j];
    }
    int res = INT_MAX;
    for (int k = i; k < j; k++) {
        int leftCost = matrixChainMultiplication(matrix, i, k);
        int rightCost = matrixChainMultiplication(matrix, k + 1, j);
        int currCost = matrix[i - 1] * matrix[k] * matrix[j];
        res = min(res, leftCost + rightCost + currCost);
    }
    dp[i][j] = res;
    return res;
}

int main() {
    int n = 5;
    vector<int> matrix = {10, 30, 5, 60, 20};
    memset(dp, -1, sizeof(dp));
    cout << "The minimum cost of matrix chain multiplication is: " << matrixChainMultiplication(matrix, 1, n - 1) << endl;
    return 0;
}
在上述代码中,矩阵的维度信息保存在向量matrix中,matrix[i]表示第i个矩阵的行数,同时也是第i-1个矩阵的列数。备忘录数组dp用于保存已经计算过的结果,避免重复计算,初始化为-1。

该方法的时间复杂度为O(n^3),其中n为矩阵数量。这是因为在计算矩阵i到j的最小乘次数时,需要枚举k,因此总共有n^2个子问题需要计算,每个子问题需要O(n)的时间复杂度来计算,因此总时间复杂度为O(n^3)。同时,由于使用了备忘录方法,避免了重复计算,因此空间复杂度也为O(n^2)。

6.7.1.3

以下是使用C++语言描述矩阵连乘最优次序动态规划算法的代码:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

const int MAX_N = 100;
int dp[MAX_N][MAX_N];

// 计算矩阵连乘的最小乘次数
int matrixChainMultiplication(vector<int>& matrix) {
    int n = matrix.size() - 1;
    for (int i = 1; i <= n; i++) {
        dp[i][i] = 0;
    }
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i <= n - len + 1; i++) {
            int j = i + len - 1;
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; k++) {
                int currCost = matrix[i - 1] * matrix[k] * matrix[j];
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + currCost);
            }
        }
    }
    return dp[1][n];
}

int main() {
    int n = 5;
    vector<int> matrix = {10, 30, 5, 60, 20};
    cout << "The minimum cost of matrix chain multiplication is: " << matrixChainMultiplication(matrix) << endl;
    return 0;
}
在上述代码中,矩阵的维度信息保存在向量matrix中,matrix[i]表示第i个矩阵的行数,同时也是第i-1个矩阵的列数。动态规划数组dp用于保存子问题的最优解,其中dp[i][j]表示从第i个矩阵到第j个矩阵的最小乘次数。

该方法的时间复杂度为O(n^3),其中n为矩阵数量。在计算矩阵连乘的最小乘次数时,需要枚举子问题的长度len、起点i和终点j,因此总共有n^3个子问题需要计算,每个子问题需要O(1)的时间复杂度来计算,因此总时间复杂度为O(n^3)。同时,由于使用了动态规划方法,不需要递归,因此空间复杂度为O(n^2)。

Like
Dislike
Share

6.7.1.4

以下是使用C++语言描述任意顶点间最短路径动态规划算法的代码:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

const int MAX_V = 100;
int dp[MAX_V][MAX_V];

// 计算任意顶点i到j的最短路径
void floydWarshall(vector<vector<int>>& graph) {
    int V = graph.size();
    // 初始化dp数组
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            dp[i][j] = graph[i][j];
        }
    }
    // 计算任意两点之间的最短路径
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
            }
        }
    }
}

int main() {
    int V = 4;
    vector<vector<int>> graph = {
        {0, 5, INT_MAX, 10},
        {INT_MAX, 0, 3, INT_MAX},
        {INT_MAX, INT_MAX, 0, 1},
        {INT_MAX, INT_MAX, INT_MAX, 0}
    };
    floydWarshall(graph);
    cout << "The shortest path between any two vertices is: " << endl;
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dp[i][j] == INT_MAX) {
                cout << "INF ";
            } else {
                cout << dp[i][j] << " ";
            }
        }
        cout << endl;
    }
    return 0;
}
在上述代码中,图的邻接矩阵保存在二维向量graph中,graph[i][j]表示从顶点i到顶点j的边的权重,如果没有边相连,则为INT_MAX。动态规划数组dp用于保存任意两点之间的最短路径,其中dp[i][j]表示从顶点i到顶点j的最短路径长度。

该方法的时间复杂度为O(V^3),其中V为图的顶点数量。在计算任意两点之间的最短路径时,需要枚举中间节点k、起点i和终点j,因此总共有V^3个子问题需要计算,每个子问题需要O(1)的时间复杂度来计算,因此总时间复杂度为O(V^3)。同时,由于使用了动态规划方法,不需要递归,因此空间复杂度为O(V^2)。	

6.7.1.5

以下是使用C++语言描述多段图动态规划算法的代码:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

const int MAX_N = 100;
const int MAX_M = 100;
const int INF = INT_MAX;

// 计算多段图的最短路径
int multiStageGraph(vector<vector<int>>& graph, int n, int m) {
    int dp[MAX_N];
    // 初始化dp数组
    for (int i = 0; i <= n; i++) {
        dp[i] = INF;
    }
    // 最后一段到终点的距离为0
    dp[n] = 0;
    // 从后往前计算每个阶段的最短路径
    for (int i = n - 1; i >= 1; i--) {
        for (int j = 0; j < m; j++) {
            if (graph[i][j] != INF) {
                dp[i] = min(dp[i], graph[i][j] + dp[j + 1]);
            }
        }
    }
    return dp[1];
}

int main() {
    int n = 4, m = 3;
    vector<vector<int>> graph = {
        {INF, INF, INF, INF},
        {INF, INF, 2, 1},
        {INF, 5, INF, 4},
        {INF, INF, INF, INF},
        {INF, INF, INF, INF}
    };
    cout << "The shortest path in the multi-stage graph is: " << multiStageGraph(graph, n, m) << endl;
    return 0;
}
在上述代码中,多段图的邻接矩阵保存在二维向量graph中,graph[i][j]表示从第i段的顶点到第i+1段的顶点j的边的权重,如果没有边相连,则为INF。算法中使用一个一维数组dp来保存从当前阶段到终点的最短路径长度。

该方法的时间复杂度为O(nm),其中n为多段图的段数,m为每段中的顶点数。在计算每个阶段的最短路径时,需要枚举该阶段中的所有顶点,并在下一阶段中枚举所有可能的到达顶点,因此总共有nm个子问题需要计算,每个子问题需要O(1)的时间复杂度来计算,因此总时间复杂度为O(nm)。同时,由于使用了动态规划方法,不需要递归,因此空间复杂度为O(n)。

6.7.2.1

以下是使用C++语言描述求任意两个有限序列的最长公共子序列动态规划算法的代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 计算最长公共子序列的长度
int longestCommonSubsequence(string s1, string s2) {
    int m = s1.size(), n = s2.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s1[i - 1] == s2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}

int main() {
    string s1 = "AGGTAB";
    string s2 = "GXTXAYB";
    cout << "The length of the longest common subsequence is: " << longestCommonSubsequence(s1, s2) << endl;
    return 0;
}
在上述代码中,字符串s1和s2为要比较的两个序列。算法中使用一个二维向量dp来保存计算结果,其中dp[i][j]表示s1的前i个字符和s2的前j个字符的最长公共子序列的长度。在计算dp[i][j]时,如果s1[i-1]等于s2[j-1],那么最长公共子序列中会包含s1[i-1]和s2[j-1],因此dp[i][j]等于dp[i-1][j-1]加上1;否则,最长公共子序列中不包含s1[i-1]和s2[j-1],因此dp[i][j]等于dp[i-1][j]和dp[i][j-1]中的较大值。

该方法的时间复杂度为O(mn),其中m和n分别为s1和s2的长度。在计算dp[i][j]时,需要枚举s1的前i个字符和s2的前j个字符,因此总共有mn个子问题需要计算,每个子问题需要O(1)的时间复杂度来计算。同时,由于使用了动态规划方法,不需要递归,因此空间复杂度为O(mn)。

6.7.2.2

以下是使用C++语言描述0/1背包问题动态规划算法的代码:

#include <iostream>
#include <vector>
#include <map>
using namespace std;

// 计算0/1背包问题的最大价值
int knapsack(vector<int>& w, vector<int>& v, int W) {
    int n = w.size();
    map<pair<int, int>, int> dp;
    for (int i = 0; i <= n; i++) {
        dp[make_pair(i, 0)] = 0;
    }
    for (int j = 1; j <= W; j++) {
        dp[make_pair(0, j)] = 0;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= W; j++) {
            if (w[i - 1] <= j) {
                dp[make_pair(i, j)] = max(dp[make_pair(i - 1, j)], dp[make_pair(i - 1, j - w[i - 1])] + v[i - 1]);
            } else {
                dp[make_pair(i, j)] = dp[make_pair(i - 1, j)];
            }
        }
    }
    return dp[make_pair(n, W)];
}

int main() {
    vector<int> w = {1, 2, 3};
    vector<int> v = {6, 10, 12};
    int W = 5;
    cout << "The maximum value of 0/1 knapsack problem is: " << knapsack(w, v, W) << endl;
    return 0;
}
在上述代码中,向量w和v分别表示物品的重量和价值,W表示背包的容量。算法中使用一个mapdp来保存计算结果,其中dp[make_pair(i, j)]表示前i个物品放入容量为j的背包中能够获得的最大价值。在计算dp[make_pair(i, j)]时,如果第i个物品的重量不超过j,那么可以选择将该物品放入背包中,此时dp[make_pair(i, j)]等于不放该物品的最大价值dp[make_pair(i-1, j)]和放该物品的最大价值dp[make_pair(i-1, j-w[i-1])] + v[i-1]中的较大值;否则,不能放该物品,此时dp[make_pair(i, j)]等于不放该物品的最大价值dp[make_pair(i-1, j)]。

该方法的时间复杂度为O(nW),其中n和W分别为物品的数量和背包的容量。在计算dp[make_pair(i, j)]时,需要枚举前i个物品和容量为j的背包,因此总共有nW个子问题需要计算,每个子问题需要O(1)的时间复杂度来计算。同时,由于使用了动态规划方法,不需要递归,因此空间复杂度为O(nW)。

6.7.2.3

以下是改写后的Floyd算法程序,计算有向图的自反传递闭包RTC的邻接矩阵:

#include <iostream>
#include <vector>
using namespace std;

void transitiveClosure(vector<vector<int>>& graph, vector<vector<int>>& rtc) {
    int n = graph.size();
    rtc = graph; // 初始化RTC矩阵为原图的邻接矩阵
    for(int k=0; k<n; k++) {
        for(int i=0; i<n; i++) {
            if(rtc[i][k] == 1) { // 如果rtc[i][k]为1,表示i到k有路径
                for(int j=0; j<n; j++) {
                    rtc[i][j] = rtc[i][j] || rtc[k][j]; // 更新rtc[i][j],如果rtc[k][j]为1,表示k到j有路径,则i到j也有路径
                }
            }
        }
    }
}

int main() {
    vector<vector<int>> graph = {
        {0, 1, 0, 0},
        {0, 0, 1, 1},
        {1, 0, 0, 0},
        {0, 0, 1, 0}
    };
    vector<vector<int>> rtc;
    transitiveClosure(graph, rtc);
    cout << "The RTC matrix is:" << endl;
    for(int i=0; i<rtc.size(); i++) {
        for(int j=0; j<rtc[i].size(); j++) {
            cout << rtc[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
在上述代码中,邻接矩阵graph表示有向图的邻接矩阵,rtc表示自反传递闭包的邻接矩阵。初始时,将rtc矩阵赋值为graph矩阵,然后使用Floyd算法计算rtc矩阵。在Floyd算法的每一次迭代中,如果rtc[i][k]为1,表示i到k有路径,那么就遍历所有的j,如果rtc[k][j]为1,表示k到j有路径,则i到j也有路径,因此将rtc[i][j]更新为1。

该方法的时间复杂度为O(n^3),其中n为图的顶点数。在Floyd算法中,需要进行n次迭代,每次迭代需要遍历所有的i、j和k,因此总共需要进行O(n^3)次基本操作。空间复杂度为O(n^2),需要使用一个n×n的矩阵来保存rtc矩阵。

7.8.1

1

以下是使用递归方法生成含n个分量的所有排列的程序:

#include <iostream>
#include <vector>
using namespace std;

// 递归函数,生成所有排列
void generatePermutations(vector<int>& nums, int start, vector<vector<int>>& result) {
    if(start == nums.size() - 1) { // 当start指向最后一个元素时,生成一个排列
        result.push_back(nums);
        return;
    }
    // 从start到最后一个元素,分别与start交换位置,递归生成排列
    for(int i=start; i<nums.size(); i++) {
        swap(nums[start], nums[i]);
        generatePermutations(nums, start+1, result);
        swap(nums[start], nums[i]);
    }
}

// 生成所有排列的函数
vector<vector<int>> generatePermutations(int n) {
    vector<vector<int>> result;
    vector<int> nums(n);
    for(int i=0; i<n; i++) {
        nums[i] = i+1; // 初始化nums数组为1~n
    }
    generatePermutations(nums, 0, result);
    return result;
}

int main() {
    int n = 3;
    vector<vector<int>> permutations = generatePermutations(n);
    cout << "All permutations of " << n << " numbers are:" << endl;
    for(int i=0; i<permutations.size(); i++) {
        for(int j=0; j<permutations[i].size(); j++) {
            cout<< permutations[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
在上述代码中,generatePermutations函数是递归函数,用于生成所有排列。参数nums表示待排列的数组,start表示当前处理的位置,result表示所有排列的结果。当start指向最后一个元素时,生成一个排列,将其加入到结果中。否则,从start到最后一个元素,分别与start交换位置,递归生成排列。在递归结束后,需要再将nums数组恢复为原来的顺序,以便下一次递归。

generatePermutations函数是生成所有排列的主函数。它首先初始化nums数组为1~n,然后调用generatePermutations函数生成所有排列。最后,将所有排列返回。

该方法的时间复杂度为O(n!),其中n为待排列的数组长度。在递归函数generatePermutations中,每次需要枚举从start到最后一个元素的所有元素,因此总共需要进行O(n!)次操作。空间复杂度为O(n^2),需要使用一个n长度的数组和一个n×n的矩阵来保存所有排列。

2

以下是使用递归方法生成n个元素的所有子集的程序:

#include <iostream>
#include <vector>
using namespace std;

// 递归函数,生成所有子集
void generateSubsets(vector<int>& nums, int start, vector<int>& subset, vector<vector<int>>& result) {
    result.push_back(subset); // 将当前子集加入到结果中
    // 从start到最后一个元素,分别加入到子集中,递归生成子集
    for(int i=start; i<nums.size(); i++) {
        subset.push_back(nums[i]);
        generateSubsets(nums, i+1, subset, result);
        subset.pop_back();
    }
}

// 生成所有子集的函数
vector<vector<int>> generateSubsets(int n) {
    vector<vector<int>> result;
    vector<int> nums(n);
    for(int i=0; i<n; i++) {
        nums[i] = i+1; // 初始化nums数组为1~n
    }
    vector<int> subset;
    generateSubsets(nums, 0, subset, result);
    return result;
}

int main() {
    int n = 3;
    vector<vector<int>> subsets = generateSubsets(n);
    cout << "All subsets of " << n << " numbers are:" << endl;
    for(int i=0; i<subsets.size(); i++) {
        for(int j=0; j<subsets[i].size(); j++) {
            cout<< subsets[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
在上述代码中,generateSubsets函数是递归函数,用于生成所有子集。参数nums表示待生成子集的数组,start表示当前处理的位置,subset表示当前生成的子集,result表示所有子集的结果。每次将当前子集加入到结果中,然后从start到最后一个元素,分别加入到子集中,递归生成子集。在递归结束后,需要将最后一个加入的元素弹出,以便下一次递归。

generateSubsets函数是生成所有子集的主函数。它首先初始化nums数组为1~n,然后调用generateSubsets函数生成所有子集。最后,将所有子集返回。

该方法的时间复杂度为O(2^n),其中n为待生成子集的数组长度。在递归函数generateSubsets中,每次需要将当前子集加入到结果中,并枚举从start到最后一个元素的所有元素,因此总共需要进行O(2^n)次操作。空间复杂度为O(n^2),需要使用一个n长度的数组和一个n×n的矩阵来保存所有子集。

3

以下是一种Python语言描述的旅行商问题的回溯算法:

import sys

def tsp(graph, visited, curr_pos, n, count, cost, ans):
    # 如果已经遍历完所有城市,更新最优解
    if count == n:
        ans[0] = min(ans[0], cost + graph[curr_pos][0])
        return

    # 遍历所有未访问的城市
    for i in range(n):
        if visited[i] == False:
            visited[i] = True
            tsp(graph, visited, i, n, count+1, cost+graph[curr_pos][i], ans)
            visited[i] = False

# 旅行商问题的主函数
def solve_tsp(graph):
    n = len(graph)
    visited = [False] * n
    visited[0] = True
    ans = [sys.maxsize]
    tsp(graph, visited, 0, n, 1, 0, ans)
    print("The optimal cost is:", ans[0])

# 示例
graph = [[0, 10, 15, 20],
         [10, 0, 35, 25],
         [15, 35, 0, 30],
         [20, 25, 30, 0]]

solve_tsp(graph)
在上述代码中,tsp函数是旅行商问题的回溯函数。参数graph表示城市之间的距离矩阵,visited表示是否访问过某个城市,curr_pos表示当前所在的城市,n表示城市总数,count表示已经访问的城市数,cost表示当前路径的总花费,ans表示最优解。每次遍历所有未访问的城市,对于每个未访问的城市,先将其标记为已访问,然后递归调用tsp函数继续搜索,最后将其标记为未访问。如果已经遍历完所有城市,更新最优解。

solve_tsp函数是旅行商问题的主函数。它首先初始化visited数组,将起始城市标记为已访问,然后调用tsp函数求解最优解。

该算法的时间复杂度为O(n!),其中n为城市总数。在回溯函数tsp中,每次需要遍历所有未访问的城市,因此总共需要进行O(n!)次操作。空间复杂度为O(n),需要使用一个n长度的数组来保存visited数组。

4

以下是使用回溯方法求解子集和问题,并只输出一个满足条件的子集,或报告不存在满足条件的子集的Python代码:

def subset_sum(nums, target):
    n = len(nums)
    subset = []
    def backtrack(start, cur_sum):
        # 如果当前子集的和等于目标值,返回True
        if cur_sum == target:
            return True
        # 如果当前子集的和大于目标值,返回False
        if cur_sum > target:
            return False
        # 枚举从start到n-1的所有元素,依次加入到子集中
        for i in range(start, n):
            subset.append(nums[i])
            # 递归搜索,如果找到一个满足条件的子集,则返回True
            if backtrack(i+1, cur_sum+nums[i]):
                return True
            # 如果搜索失败,则弹出最后一个元素,继续搜索
            subset.pop()
        # 如果枚举完所有元素,仍然没有找到满足条件的子集,则返回False
        return False

    # 从0开始搜索
    if backtrack(0, 0):
        return subset
    else:
        return "No subset found."

# 示例
nums = [3, 1, 5, 9, 12]
target = 8
print(subset_sum(nums, target))
在上述代码中,subset_sum函数是子集和问题的回溯函数。参数nums表示待搜索的数组,target表示目标值,subset表示当前生成的子集。每次枚举从start到n-1的所有元素,依次加入到子集中。如果当前子集的和等于目标值,返回True;如果当前子集的和大于目标值,返回False。如果搜索失败,则弹出最后一个元素,继续搜索。如果枚举完所有元素,仍然没有找到满足条件的子集,则返回False。

该算法在最坏情况下的时间复杂度为O(2^n),其中n为待搜索的数组长度。在回溯函数subset_sum中,每次需要枚举从start到n-1的所有元素,因此总共需要进行O(2^n)次操作。空间复杂度为O(n),需要使用一个n长度的数组来保存当前子集。

5

以下是使用回溯方法判断一个无向图是否可m着色,并记录一种着色方案的Python代码:

def is_graph_colorable(graph, m):
    n = len(graph)
    colors = [-1] * n  # 初始将所有节点的颜色设置为-1,表示未着色

    def is_valid(node, color):
        for i in range(n):
            if graph[node][i] == 1 and colors[i] == color:
                return False
        return True

    def backtrack(node):
        # 如果所有节点都已经着色,返回True
        if node == n:
            return True
        # 枚举所有可行的颜色
        for color in range(m):
            # 如果当前颜色可行,着色并继续搜索下一个节点
            if is_valid(node, color):
                colors[node] = color
                if backtrack(node+1):
                    return True
                # 如果搜索失败,撤销当前着色
                colors[node] = -1
        # 如果所有颜色都失败,则返回False
        return False

    # 从第一个节点开始搜索
    if backtrack(0):
        return colors
    else:
        return "Graph is not m-colorable."

# 示例
graph = [[0, 1, 1, 1],
         [1, 0, 1, 0],
         [1, 1, 0, 1],
         [1, 0, 1, 0]]
m = 3
print(is_graph_colorable(graph, m))
在上述代码中,is_graph_colorable函数是判断一个无向图是否可m着色的回溯函数。参数graph表示待着色的无向图,m表示着色的颜色数,colors表示当前的着色方案。首先将所有节点的颜色设置为-1,表示未着色。然后枚举所有可行的颜色,如果当前颜色可行,着色并继续搜索下一个节点。如果所有节点都已经着色,返回True;如果搜索失败,撤销当前着色。如果所有颜色都失败,则返回False。

该算法的时间复杂度为O(m^n),其中n为无向图的节点数。在回溯函数is_graph_colorable中,每次需要枚举m个颜色,因此总共需要进行O(m^n)次操作。空间复杂度为O(n),需要使用一个n长度的数组来保存当前着色方案。

7.8.2

1

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 20; // 最大顶点数
int n; // 顶点数
int graph[MAXN][MAXN]; // 图
int path[MAXN]; // 路径
bool used[MAXN]; // 记录顶点是否被使用过

void dfs(int cur, int depth) {
    path[depth] = cur;
    used[cur] = true;

    if (depth == n - 1) { // 找到一条Hamilton回路
        if (graph[cur][0]) { // 如果最后一个顶点与起点相连,则输出路径
            for (int i = 0; i < n; i++) {
                cout << path[i] << " ";
            }
            cout << endl;
        }
    } else {
        for (int i = 0; i < n; i++) {
            if (!used[i] && graph[cur][i]) { // 如果顶点未被使用过且与当前顶点相连
                dfs(i, depth + 1);
            }
        }
    }

    used[cur] = false;
}

void findHamiltonCycles() {
    dfs(0, 0);
}

int main() {
    cin >> n;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> graph[i][j];
        }
    }

    findHamiltonCycles();

    return 0;
}
时间复杂度分析:

最坏情况下,我们需要遍历所有的路径,因此时间复杂度为O(n!),其中n为顶点数。

2

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 20; // 最大顶点数
int n; // 顶点数
int graph[MAXN][MAXN]; // 图
int path[MAXN]; // 路径
bool used[MAXN]; // 记录顶点是否被使用过
bool found; // 是否找到Hamilton回路

void dfs(int cur, int depth) {
    path[depth] = cur;
    used[cur] = true;

    if (depth == n - 1) { // 找到一条Hamilton回路
        if (graph[cur][0]) { // 如果最后一个顶点与起点相连,则输出路径
            found = true;
            for (int i = 0; i < n; i++) {
                cout << path[i] << " ";
            }
            cout << endl;
        }
    } else {
        for (int i = 0; i < n; i++) {
            if (!used[i] && graph[cur][i]) { // 如果顶点未被使用过且与当前顶点相连
                dfs(i, depth + 1);
                if (found) {
                    return;
                }
            }
        }
    }

    used[cur] = false;
}

void findHamiltonCycle() {
    found = false;
    dfs(0, 0);
    if (!found) {
        cout << "不存在Hamilton回路" << endl;
    }
}

int main() {
    cin >> n;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> graph[i][j];
        }
    }

    findHamiltonCycle();

    return 0;
}
时间复杂度分析:

由于我们只需要找到一条Hamilton回路或报告不存在Hamilton回路,因此在找到一条Hamilton回路后直接返回即可,时间复杂度为O(n!),其中n为顶点数。

3

#include <iostream>
#include <vector>

using namespace std;

int max_clique_size = 0;
vector<int> max_clique;

void max_clique(vector<vector<int>> &graph, vector<int> &clique, int pos) {
    if (pos == graph.size()) {
        if (clique.size() > max_clique_size) {
            max_clique_size = clique.size();
            max_clique = clique;
        }
        return;
    }
    bool can_add = true;
    for (int i = 0; i < clique.size(); i++) {
        if (graph[pos][clique[i]] == 0) {
            can_add = false;
            break;
        }
    }
    if (can_add) {
        clique.push_back(pos);
        max_clique(graph, clique, pos + 1);
        clique.pop_back();
    }
    if (graph.size() - pos > max_clique_size - clique.size()) {
        max_clique(graph, clique, pos + 1);
    }
}

vector<int> maximum_clique(vector<vector<int>> &graph) {
    max_clique_size = 0;
    vector<int> clique;
    max_clique(graph, clique, 0);
    return max_clique;
}
在上述代码中,maximum_clique函数是求解最大团问题的回溯函数。参数graph表示待搜索的无向图,max_clique_size表示当前最大的团的大小,max_clique表示当前最大的团。首先将max_clique_size初始化为0,max_clique初始化为空集。然后枚举所有可行的节点,如果当前节点能够与clique中所有节点相连,则将其添加到clique中,并继续搜索下一个节点。如果所有节点都已经添加到clique中,判断clique的大小是否大于当前最大的团的大小,如果是,则更新max_clique_size和max_clique。如果当前节点不可行或搜索失败,撤销当前添加的节点。如果剩余的节点数加上clique的大小仍然小于当前最大的团的大小,则不必继续搜索下去。

该算法的时间复杂度为O(2^n),其中n为无向图的节点数。在回溯函数maximum_clique中,每次需要枚举2个选择,因此总共需要进行O(2^n)次操作。空间复杂度为O(n),需要使用一个n长度的数组来保存当前搜索的团。

4

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 20; // 最大物品数
int n; // 物品数
int w[MAXN], v[MAXN]; // 物品的重量和价值
int c; // 背包容量
int ans; // 最大价值
int cur_w, cur_v; // 当前背包中物品的重量和价值
bool used[MAXN]; // 记录物品是否被使用过
vector<int> best_path, cur_path; // 最优解和当前解

void dfs(int depth) {
    if (depth == n) { // 搜索到叶子节点
        if (cur_v > ans) { // 更新最优解
            ans = cur_v;
            best_path = cur_path;
        }
        return;
    }

    // 不选当前物品
    dfs(depth + 1);

    // 选当前物品
    if (cur_w + w[depth] <= c) { // 如果不超过背包容量
        cur_w += w[depth];
        cur_v += v[depth];
        cur_path.push_back(depth);

        dfs(depth + 1);

        cur_path.pop_back();
        cur_v -= v[depth];
        cur_w -= w[depth];
    }
}

void knapsack() {
    dfs(0);

    cout << "最大价值为:" << ans << endl;
    cout << "最优解为:";
    for (int i = 0; i < best_path.size(); i++) {
        cout << best_path[i] << " ";
    }
    cout << endl;
}

int main() {
    cin >> n >> c;

    for (int i = 0; i < n; i++) {
        cin >> w[i] >> v[i];
    }

    knapsack();

    return 0;
}
时间复杂度分析:

最坏情况下,我们需要遍历所有的状态,因此时间复杂度为O(2^n),其中n为物品数。

5

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 20; // 最大元素个数
int n; // 元素个数
int a[MAXN]; // 元素值
int target; // 目标值
vector<int> cur_path; // 当前解

void dfs(int depth, int cur_sum) {
    if (depth == n) { // 搜索到叶子节点
        if (cur_sum == target) { // 如果当前和等于目标和,则输出当前解
            for (int i = 0; i < cur_path.size(); i++) {
                cout << cur_path[i] << " ";
            }
            cout << endl;
        }
        return;
    }

    // 不选当前元素
    dfs(depth + 1, cur_sum);

    // 选当前元素
    cur_path.push_back(a[depth]);
    dfs(depth + 1, cur_sum + a[depth]);
    cur_path.pop_back();
}

void subsetSum() {
    dfs(0, 0);
}

int main() {
    cin >> n >> target;

    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    subsetSum();

    return 0;
}
时间复杂度分析:

最坏情况下,我们需要遍历所有的状态,因此时间复杂度为O(2^n),其中n为元素个数。

6

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 20; // 最大顶点数
int n, m; // 顶点数和边数
int color[MAXN]; // 顶点的颜色
vector<int> adj[MAXN]; // 邻接表表示的图
int ans; // 最少需要的颜色数
vector<int> best_color; // 最优解

bool is_valid(int v, int c) {
    for (int i = 0; i < adj[v].size(); i++) { // 遍历v的所有邻居
        if (color[adj[v][i]] == c) { // 如果v的邻居已经使用了颜色c
            return false;
        }
    }
    return true;
}

void dfs(int depth) {
    if (depth == n) { // 搜索到叶子节点
        int cnt = 0; // 统计颜色数目
        for (int i = 0; i < n; i++) {
            cnt = max(cnt, color[i]);
        }
        if (cnt < ans) { // 更新最优解
            ans = cnt;
            best_color = vector<int>(color, color + n);
        }
        return;
    }

    for (int i = 1; i <= ans + 1; i++) { // 枚举所有可能的颜色
        if (is_valid(depth, i)) { // 如果颜色i可以使用
            color[depth] = i;
            dfs(depth + 1);
            color[depth] = 0;
        }
    }
}

void graphColoring() {
    dfs(0);

    cout << "最少需要的颜色数:" << ans << endl;
    cout << "最优解为:";
    for (int i = 0; i < best_color.size(); i++) {
        cout << best_color[i] << " ";
    }
    cout << endl;
}

int main() {
    cin >> n >> m;

    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    ans = n; // 初始时,最多需要n种颜色
    graphColoring();

    return 0;
}
时间复杂度分析:

最坏情况下,我们需要遍历所有的状态,因此时间复杂度为O(k^n),其中k为最大可能的颜色数。在实际运行中,k的值通常比较小,因此该算法的时间复杂度通常比较低。

猜你喜欢

转载自blog.csdn.net/CSH__/article/details/130192757