Algorithm Design and Analysis Fourth Assignment

8.8.1 Basic training questions

1 Write a program that uses the branch and bound method to generate all permutations with n components.

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

using namespace std;

void generatePermutations(vector<int>& permutation, vector<bool>& used, int depth) {
    if (depth == permutation.size()) {
        // 已经遍历到排列的末尾,输出排列
        for (int i = 0; i < permutation.size(); i++) {
            cout << permutation[i] << " ";
        }
        cout << endl;
    } else {
        // 对于每个未使用的数字,递归生成下一个数字的排列
        for (int i = 1; i <= permutation.size(); i++) {
            if (!used[i-1]) {
                used[i-1] = true;
                permutation[depth] = i;
                generatePermutations(permutation, used, depth+1);
                used[i-1] = false;
            }
        }
    }
}

int main() {
    int n;
    cout << "请输入排列的长度n: ";
    cin >> n;

    vector<int> permutation(n);
    vector<bool> used(n, false);

    generatePermutations(permutation, used, 0);

    return 0;
}

2 Write a program that uses the branch and bound method to generate all subsets of n elements

#include <iostream>
#include <vector>

using namespace std;

void generateSubsets(vector<int>& subset, vector<bool>& used, int depth) {
    if (depth == subset.size()) {
        // 已经遍历到子集的末尾,输出子集
        cout << "{ ";
        for (int i = 0; i < subset.size(); i++) {
            if (used[i]) {
                cout << subset[i] << " ";
            }
        }
        cout << "}" << endl;
    } else {
        // 对于每个元素,递归生成下一个元素的子集
        used[depth] = true;
        generateSubsets(subset, used, depth+1);
        used[depth] = false;
        generateSubsets(subset, used, depth+1);
    }
}

int main() {
    int n;
    cout << "请输入集合大小n: ";
    cin >> n;

    vector<int> subset(n);
    for (int i = 0; i < n; i++) {
        subset[i] = i+1;
    }

    vector<bool> used(n, false);

    generateSubsets(subset, used, 0);

    return 0;
}

3 Give the changing process of the solution space tree and live node queue for solving the traveling salesman problem (starting from 0) using the queue-type branch-and-bound method and the priority queue-type branch-and-bound method respectively


4 Give the solution space tree of the maximum clique problem as shown in the figure using the priority queue branch-and-bound method


5 Use a programming language to describe the branch and bound algorithm for solving the traveling salesman problem (output the optimal solution), and analyze the time complexity

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

const int N = 5;

struct Node {
    int level; // 当前所在的层数
    int path[N]; // 从根节点到当前节点的路径
    bool visited[N]; // 标记城市是否已经访问过
    int bound; // 当前节点的下界
    int cost; // 当前路径的总代价
};

// 定义比较函数,用于优先队列中节点的排序
struct cmp {
    bool operator()(const Node& a, const Node& b) {
        return a.bound > b.bound;
    }
};

int get_cost(int graph[][N], int path[], int n) {
    // 计算路径的总代价
    int cost = 0;
    for (int i = 0; i < n - 1; i++) {
        cost += graph[path[i]][path[i+1]];
    }
    cost += graph[path[n-1]][path[0]];
    return cost;
}

void init_node(Node& node, int level) {
    // 初始化节点
    node.level = level;
    for (int i = 0; i < N; i++) {
        node.visited[i] = false;
    }
}

void print_path(int path[], int n) {
    // 输出路径
    for (int i = 0; i < n; i++) {
        cout << path[i] << " ";
    }
    cout << endl;
}

void tsp_bnb(int graph[][N]) {
    // 初始化根节点
    Node root;
    init_node(root, 0);
    root.path[0] = 0; // 从城市0出发
    root.visited[0] = true;
    root.bound = get_cost(graph, root.path, 1); // 计算根节点的下界
    root.cost = 0;

    // 使用优先队列存储节点
    priority_queue<Node, vector<Node>, cmp> q;
    q.push(root);

    while (!q.empty()) {
        Node cur = q.top();
        q.pop();

        if (cur.bound >= INT_MAX) {
            // 当前节点的下界已经超过了当前的最优解,可以剪枝
            continue;
        }

        if (cur.level == N-1) {
            // 已经遍历了所有的城市,找到了一条完整的路径
            cur.path[cur.level] = 0; // 回到出发城市
            cur.cost = get_cost(graph, cur.path, N); // 计算路径的总代价
            if (cur.cost < root.cost) {
                // 更新最优解
                root = cur;
            }
            continue;
        }

        // 枚举下一步可以访问的城市
        for (int i = 0; i < N; i++) {
            if (cur.visited[i]) {
                // 当前城市已经访问过
                continue;
            }

            Node
        Node child;
        init_node(child, cur.level+1);
        for (int j = 0; j <= cur.level; j++) {
            child.path[j] = cur.path[j];
        }
        child.path[cur.level+1] = i; // 记录下一步访问的城市
        child.visited[i] = true; // 标记城市已经访问过
        child.cost = cur.cost + graph[cur.path[cur.level]][i]; // 更新路径的总代价

        // 计算节点的下界
        int min_cost = INT_MAX;
        for (int j = 0; j < N; j++) {
            if (!child.visited[j] && graph[i][j] < min_cost) {
                min_cost = graph[i][j];
            }
        }
        child.bound = child.cost + min_cost * (N - child.level - 1);

        if (child.bound < root.cost) {
            // 当前节点的下界小于当前的最优解,加入队列继续搜索
            q.push(child);
        }
    }
}

// 输出最优解
cout << "最优路径为:" << endl;
print_path(root.path, N);
cout << "路径的总代价为:" << root.cost << endl;
}

int main() {
int graph[N][N] = {
   
   {0, 10, 15, 20, 25},
{10, 0, 35, 25, 30},
{15, 35, 0, 30, 20},
{20, 25, 30, 0, 18},
{25, 30, 20, 18, 0}};
tsp_bnb(graph);
return 0;
}

mathematica
Copy code

时间复杂度分析:

- 对于每个节点,需要枚举剩余未访问的城市,因此每个节点的分支数为O(N),总共需要遍历O(N!)个节点。
- 对于每个节点,需要计算它的下界,需要遍历剩余未访问的城市,因此计算下界的时间复杂度为O(N^2)。
- 优先队列的插入和删除操作的时间复杂度为O(logN)。
- 因此,总时间复杂度为O(N! * N^2 * logN)。

6 Use a programming language to describe the branch and bound algorithm for solving the largest clique problem (output the optimal solution), and analyze the time complexity

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <climits>

using namespace std;

const int N = 100;
int graph[N][N]; // 图的邻接矩阵表示
bool visited[N]; // 当前团中的节点
int max_clique[N]; // 最大团
int max_size; // 最大团大小

struct Node {
    int level; // 当前搜索的深度
    int size; // 当前团的大小
    int bound; // 当前节点的下界
};

bool operator<(const Node& n1, const Node& n2) {
    // 优先队列中按照下界从小到大排序
    return n1.bound > n2.bound;
}

void init_node(Node& node, int level, int size) {
    node.level = level;
    node.size = size;
    node.bound = 0;
}

void dfs(int cur, int size) {
    // 在剩余节点中搜索当前团的最大扩展
    for (int i = cur + 1; i < N; i++) {
        if (visited[i]) continue;
        bool flag = true;
        for (int j = 0; j < size; j++) {
            if (!graph[i][visited[j]]) {
                flag = false;
                break;
            }
        }
        if (flag) {
            visited[i] = true;
            dfs(i, size+1);
            visited[i] = false;
        }
    }

    // 当前团是最大团,更新最大团和最大团大小
    if (size > max_size) {
        max_size = size;
        for (int i = 0; i < size; i++) {
            max_clique[i] = visited[i];
        }
    }
}

void maxclique_bnb() {
    max_size = 0;
    memset(visited, false, sizeof(visited));
    memset(max_clique, false, sizeof(max_clique));

    // 初始化根节点
    Node root;
    init_node(root, 0, 0);
    for (int i = 0; i < N; i++) {
        root.bound += graph[i][i+1];
    }

    // 优先队列存储活结点
    priority_queue<Node> q;
    q.push(root);

    while (!q.empty()) {
        Node cur = q.top();
        q.pop();

        if (cur.bound <= max_size) {
            // 当前节点的下界小于等于当前最大团大小,不需要搜索下去
            continue;
        }

        if (cur.level == N) {
            // 已经遍历完了所有节点,更新最大团
            max_size = cur.size;
            for (int i = 0; i < N; i++) {
                max_clique[i] = visited[i];
            }
            continue;
        }

        // 枚举当前节点的子节点
        Node child;
        init_node(child, cur.level+1, cur.size);
        for (int i = N-1; i >= cur.level; i--) {
            // 从N-1到cur.level,按照节点编号从大到小排序
            // 优先
        // 搜索的是当前节点的子节点,不包括父节点和父节点之前已经遍历过的兄弟节点
        if (graph[i][i+1] && !visited[i] &&
            i > cur.level && (cur.level == 0 || graph[i][visited[cur.level-1]])) {
            // 当前节点可行,计算子节点的下界
            child.bound = cur.bound - 1;
            for (int j = i+1; j < N; j++) {
                if (graph[i][j] && !visited[j]) {
                    child.bound--;
                }
            }

            if (child.bound > max_size) {
                // 子节点的下界大于当前最大团大小,将子节点加入优先队列
                q.push(child);
            }
        }
    }

    // 不选择当前节点
    Node child2;
    init_node(child2, cur.level+1, cur.size);
    child2.bound = cur.bound - graph[cur.level][cur.level+1];
    if (child2.bound > max_size) {
        // 将不选择当前节点的子节点加入优先队列
        q.push(child2);
    }

    // 标记当前节点
    visited[cur.level] = true;
    dfs(cur.level, cur.size+1);
    visited[cur.level] = false;
}
int main() {
// 构造图的邻接矩阵表示
memset(graph, 0, sizeof(graph));
graph[0][1] = graph[1][0] = 1;
graph[0][2] = graph[2][0] = 1;
graph[0][3] = graph[3][0] = 1;
graph[0][4] = graph[4][0] = 1;
graph[1][2] = graph[2][1] = 1;
graph[1][3] = graph[3][1] = 1;
graph[1][4] = graph[4][1] = 1;
graph[2][3] = graph[3][2] = 1;
graph[2][4] = graph[4][2] = 1;
graph[3][4] = graph[4][3] = 1;
    maxclique_bnb();

cout << "Max clique size: " << max_size << endl;
cout << "Max clique nodes: ";
for (int i = 0; i < N; i++) {
    if (max_clique[i]) {
        cout << i << " ";
    }
}
cout << endl;

return 0;
时间复杂度分析:

对于最大团问题的分枝限界算法,每次搜索时可以通过剪枝减少大量的搜索空间,因此时间复杂度不会很高,但仍然是指数级别的。

具体来说,如果使用深度优先搜索,时间复杂度为 $O(2^n)$,其中 $n$ 为节点数量,因为每个节点都有选或不选两种情况。

如果使用优先队列优化的分枝限界算法,时间复杂度可以得到一定程度的优化。假设优先队列

7 Use a programming language to describe the branch and bound algorithm for solving the subset sum problem (outputting all solutions), and analyze the time complexity

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

void printSolution(const vector<int>& sol) {
    for (int i = 0; i < sol.size(); i++) {
        if (sol[i] == 1) {
            cout << i + 1 << " ";
        }
    }
    cout << endl;
}

void subsetSum(vector<int>& nums, int targetSum, vector<int>& path, int depth, int remainingSum) {
    if (remainingSum < 0) {
        return;
    }
    if (depth == nums.size()) {
        if (remainingSum == 0) {
            printSolution(path);
        }
        return;
    }

    // include current element
    path.push_back(1);
    subsetSum(nums, targetSum, path, depth + 1, remainingSum - nums[depth]);
    path.pop_back();

    // exclude current element
    path.push_back(0);
    subsetSum(nums, targetSum, path, depth + 1, remainingSum);
    path.pop_back();
}

int main() {
    vector<int> nums = {10, 7, 5, 18, 12, 20, 15};
    int targetSum = 35;

    vector<int> path;
    int remainingSum = 0;
    for (int num : nums) {
        remainingSum += num;
    }

    subsetSum(nums, targetSum, path, 0, remainingSum);

    return 0;
}

8.8.2 Experimental questions

1 Use a programming language to describe the branch-and-bound algorithm for solving the Hamilton loop problem (outputting all Hamilton loops), and analyze the time complexity

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

const int MAXN = 20;

int n; // 图中顶点的数量
int graph[MAXN][MAXN]; // 图的邻接矩阵
vector<int> path; // 当前的解
bool visited[MAXN]; // 记录每个顶点是否被访问过

void printSolution(const vector<int>& sol) {
    for (int i = 0; i < sol.size(); i++) {
        cout << sol[i] << " ";
    }
    cout << endl;
}

bool isValid(int vertex, int pos) {
    // 如果当前顶点已经在解中出现过,则不合法
    if (visited[vertex]) {
        return false;
    }

    // 如果当前顶点和上一个顶点之间没有边相连,则不合法
    if (pos > 0 && graph[path[pos - 1]][vertex] == 0) {
        return false;
    }

    // 如果当前顶点是起点,但是还没有遍历完所有的顶点,则不合法
    if (pos == n - 1 && graph[vertex][path[0]] == 0) {
        return false;
    }

    return true;
}

void hamiltonianCircuit(int pos) {
    if (pos == n) {
        if (graph[path[pos - 1]][path[0]] == 1) {
            printSolution(path);
        }
        return;
    }

    for (int vertex = 1; vertex < n; vertex++) {
        if (isValid(vertex, pos)) {
            path.push_back(vertex);
            visited[vertex] = true;

            hamiltonianCircuit(pos + 1);

            path.pop_back();
            visited[vertex] = false;
        }
    }
}

int main() {
    // 初始化图的邻接矩阵
    memset(graph, 0, sizeof(graph));
    graph[0][1] = graph[1][0] = 1;
    graph[0][2] = graph[2][0] = 1;
    graph[1][2] = graph[2][1] = 1;
    graph[1][3] = graph[3][1] = 1;
    graph[2][3] = graph[3][2] = 1;
    graph[2][4] = graph[4][2] = 1;
    graph[3][4] = graph[4][3] = 1;
    graph[3][5] = graph[5][3] = 1;
    graph[4][5] = graph[5][4] = 1;

    n = 6;
    path.push_back(0);
    visited[0] = true;

    hamiltonianCircuit(1);

    return 0;
}
在这个算法中,我们使用回溯法实现了分枝限界算法。我们首先将起点加入到解中,并将起点标记为已访问。在搜索的过程中,我们维护一个 path 数组,表示当前的解。对于每个顶点,我们都有多个选择:将其包含在解中或不包含在解中。因此,我们可以使用回溯法来递归地搜索所有可能的解。在递归的过程中,我们可以使用剪枝技巧来提高搜索的效率。具体来说,如果当前的解已经包含了所有的顶点,则可以检查最后一个顶点是否和起点相邻,如果相邻,则找到一个 Hamilton 回路。如果当前的解已经超过了所有的顶点,则可以直接返回。如果当前的解已经不可能找到一个 Hamilton 回路,则可以剪枝回溯。

时间复杂性分析:Hamilton 回路问题是一个 NP 完全问题,因此不存在多项式时间算法可以解决该问题。分枝限界算法的时间复杂度取决于搜索树的大小。在最坏情况下,搜索树的大小为 n!,因此时间复杂度为 O(n!)。但是,在实际情况中,使用剪枝技巧可以将搜索树的大小大大减小,从而提高搜索效率。因此,分枝限界算法的时间复杂度是具有一定不确定性的。

2 Use the branch and bound method to solve the Hamilton circuit problem, requiring only one Hamilton circuit to be output, or reporting that there is no Hamilton circuit

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

const int MAXN = 20;

int n; // 图中顶点的数量
int graph[MAXN][MAXN]; // 图的邻接矩阵
vector<int> path; // 当前的解
bool visited[MAXN]; // 记录每个顶点是否被访问过

void printSolution(const vector<int>& sol) {
    for (int i = 0; i < sol.size(); i++) {
        cout << sol[i] << " ";
    }
    cout << endl;
}

bool isValid(int vertex, int pos) {
    // 如果当前顶点已经在解中出现过,则不合法
    if (visited[vertex]) {
        return false;
    }

    // 如果当前顶点和上一个顶点之间没有边相连,则不合法
    if (pos > 0 && graph[path[pos - 1]][vertex] == 0) {
        return false;
    }

    // 如果当前顶点是起点,但是还没有遍历完所有的顶点,则不合法
    if (pos == n - 1 && graph[vertex][path[0]] == 0) {
        return false;
    }

    return true;
}

void hamiltonianCircuit(int pos) {
    if (pos == n) {
        if (graph[path[pos - 1]][path[0]] == 1) {
            printSolution(path);
        }
        return;
    }

    for (int vertex = 1; vertex < n; vertex++) {
        if (isValid(vertex, pos)) {
            path.push_back(vertex);
            visited[vertex] = true;

            hamiltonianCircuit(pos + 1);

            path.pop_back();
            visited[vertex] = false;
        }
    }
}

int main() {
    // 初始化图的邻接矩阵
    memset(graph, 0, sizeof(graph));
    graph[0][1] = graph[1][0] = 1;
    graph[0][2] = graph[2][0] = 1;
    graph[1][2] = graph[2][1] = 1;
    graph[1][3] = graph[3][1] = 1;
    graph[2][3] = graph[3][2] = 1;
    graph[2][4] = graph[4][2] = 1;
    graph[3][4] = graph[4][3] = 1;
    graph[3][5] = graph[5][3] = 1;
    graph[4][5] = graph[5][4] = 1;

    n = 6;
    path.push_back(0);
    visited[0] = true;

    hamiltonianCircuit(1);

    return 0;
}

3 Use a programming language to describe the branch-and-bound algorithm for solving the 0/1 knapsack problem (output the optimal solution), and analyze the time complexity

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

struct Item {
    int weight; // 物品的重量
    int value; // 物品的价值
};

bool compareByRatio(const Item& a, const Item& b) {
    double ratio_a = (double)a.value / (double)a.weight;
    double ratio_b = (double)b.value / (double)b.weight;
    return ratio_a > ratio_b;
}

int knapsack(int capacity, const vector<Item>& items) {
    int n = items.size();

    // 根据单位价值排序
    vector<Item> sorted_items = items;
    sort(sorted_items.begin(), sorted_items.end(), compareByRatio);

    // 初始化最优解
    int best_value = 0;
    vector<bool> best_solution(n, false);

    // 初始化当前解
    int current_value = 0;
    int current_weight = 0;
    vector<bool> current_solution(n, false);

    // 初始化堆栈
    vector<int> stack;
    stack.push_back(-1);

    while (!stack.empty()) {
        int last_item_index = stack.back();
        stack.pop_back();

        // 遍历下一个物品
        int next_item_index = last_item_index + 1;
        if (next_item_index == n) {
            // 检查当前解是否是最优解
            if (current_value > best_value) {
                best_value = current_value;
                best_solution = current_solution;
            }
            continue;
        }

        // 不选下一个物品
        stack.push_back(next_item_index);

        // 选下一个物品
        if (current_weight + sorted_items[next_item_index].weight <= capacity) {
            current_weight += sorted_items[next_item_index].weight;
            current_value += sorted_items[next_item_index].value;
            current_solution[next_item_index] = true;

            double upper_bound = current_value;
            int remaining_capacity = capacity - current_weight;
            for (int i = next_item_index + 1; i < n; i++) {
                if (sorted_items[i].weight <= remaining_capacity) {
                    remaining_capacity -= sorted_items[i].weight;
                    upper_bound += sorted_items[i].value;
                } else {
                    upper_bound += (double)remaining_capacity / (double)sorted_items[i].weight * sorted_items[i].value;
                    break;
                }
            }

            if (upper_bound > best_value) {
                stack.push_back(next_item_index);
            }

            current_weight -= sorted_items[next_item_index].weight;
            current_value -= sorted_items[next_item_index].value;
            current_solution[next_item_index] = false;
        }
    }

    // 输出最优解
    for (int i = 0; i < n; i++) {
        if (best_solution[i]) {
            cout << i << " ";
        }
    }
    cout << endl;

    return best_value;
}

int main() {
    int capacity = 10;
    vector<Item> items = {
        {2, 6},
        {2, 10},
        {3, 12},
        {4, 14},
        {5, 18}
    };

    int value = knapsack(capacity, items);
    cout << "max value: " << value << endl;

    return 0;
}

4 Use the branch and bound method to solve the subset sum problem, requiring only one subset satisfying the condition to be output, or reporting that there is no subset satisfying the condition

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

bool subsetSum(const vector<int>& nums, int target, vector<int>& solution) {
    int n = nums.size();

    // 初始化堆栈
    vector<int> stack;
    stack.push_back(-1);

    while (!stack.empty()) {
        int last_index = stack.back();
        stack.pop_back();

        // 遍历下一个元素
        int next_index = last_index + 1;
        if (next_index == n) {
            // 检查当前解是否符合条件
            int sum = 0;
            for (int i = 0; i < n; i++) {
                if (solution[i] == 1) {
                    sum += nums[i];
                }
            }
            if (sum == target) {
                return true;
            }
            continue;
        }

        // 不选下一个元素
        stack.push_back(next_index);

        // 选下一个元素
        solution[next_index] = 1;
        stack.push_back(next_index);

        // 回溯
        solution[next_index] = 0;
    }

    return false;
}

int main() {
    vector<int> nums = {3, 1, 2, 8, 7, 5};
    int target = 14;

    vector<int> solution(nums.size(), 0);
    bool found = subsetSum(nums, target, solution);

    if (found) {
        cout << "subset found: ";
        for (int i = 0; i < nums.size(); i++) {
            if (solution[i] == 1) {
                cout << nums[i] << " ";
            }
        }
        cout << endl;
    } else {
        cout << "no subset found" << endl;
    }

    return 0;
}

5 Please use the branch and bound method to select a coloring scheme with the least number of colors for an undirected graph that needs to be colored

    请使用分枝限界方法为一个需要着色的无向图选取一种颜色数目最少的着色方案
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

// 无向图结构体
struct Graph {
    int V; // 顶点数
    vector<unordered_set<int>> adj; // 邻接表

    Graph(int V) : V(V), adj(V) {}
    void addEdge(int u, int v) {
        adj[u].insert(v);
        adj[v].insert(u);
    }
};

// 检查当前节点的颜色是否与相邻节点的颜色冲突
bool isSafe(const Graph& g, const vector<int>& colors, int node, int color) {
    for (int neighbor : g.adj[node]) {
        if (colors[neighbor] == color) {
            return false;
        }
    }
    return true;
}

// 使用分枝限界法为无向图着色
void colorGraph(const Graph& g, vector<int>& colors, int& minColors) {
    int n = g.V;

    // 初始化堆栈
    vector<pair<int, int>> stack; // (node, color)
    stack.push_back(make_pair(-1, 0));

    while (!stack.empty()) {
        pair<int, int> last_node_color = stack.back();
        stack.pop_back();

        // 遍历下一个节点
        int next_node = last_node_color.first + 1;
        if (next_node == n) {
            // 检查当前解是否符合条件
            int max_color = 0;
            for (int i = 0; i < n; i++) {
                max_color = max(max_color, colors[i]);
            }
            if (max_color < minColors) {
                minColors = max_color;
            }
            continue;
        }

        // 尝试每种颜色
        for (int color = 1; color <= n; color++) {
            if (isSafe(g, colors, next_node, color)) {
                colors[next_node] = color;
                stack.push_back(make_pair(next_node, color));
                colors[next_node] = 0;
            }
        }
    }
}

int main() {
    Graph g(5);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 2);
    g.addEdge(1, 3);
    g.addEdge(2, 3);
    g.addEdge(3, 4);

    vector<int> colors(g.V, 0);
    int minColors = g.V + 1;
    colorGraph(g, colors, minColors);

    cout << "Minimum number of colors required: " << minColors << endl;

    return 0;
}

9.6.1

1 Rewriting the quicksort program and the partition-based selection program with random methods

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

// 交换两个元素
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 选取基准元素并将其放在正确的位置上
int partition(int arr[], int low, int high) {
    // 选取随机位置上的元素作为基准元素
    int randomIndex = low + rand() % (high - low + 1);
    swap(arr[randomIndex], arr[high]);
 
    int pivot = arr[high];
    int i = low - 1;
 
    for (int j = low; j <= high - 1; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}

// 随机化快速排序
void quicksort(int arr[], int low, int high) {
    if (low < high) {
        int pivot = partition(arr, low, high);
        quicksort(arr, low, pivot - 1);
        quicksort(arr, pivot + 1, high);
    }
}

int main() {
    srand(time(NULL));
    int arr[] = {3, 5, 1, 7, 2, 8, 4, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    quicksort(arr, 0, n - 1);

    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}





#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

// 交换两个元素
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 选取基准元素并将其放在正确的位置上
int partition(int arr[], int low, int high) {
    // 选取随机位置上的元素作为基准元素
    int randomIndex = low + rand() % (high - low + 1);
    swap(arr[randomIndex], arr[high]);
 
    int pivot = arr[high];
    int i = low - 1;
 
    for (int j = low; j <= high - 1; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}

// 基于划分的选择算法
int select(int arr[], int low, int high, int k) {
    if (low == high) {
        return arr[low];
    }
    int pivot = partition(arr, low, high);
    int rank = pivot - low + 1;
    if (k == rank) {
        return arr[pivot];
    } else if (k < rank) {
        return select(arr, low, pivot - 1, k);
    } else {
        return select(arr, pivot + 1, high, k - rank);
    }
}

int main() {
    srand(time(NULL));
    int arr[] = {3, 5, 1, 7, 2, 8, 4, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    int k = 3;
    int kthSmallest = select(arr, 0, n - 1, k);

    cout << k << "-th smallest element is " << kthSmallest << endl;

    return 0;
}

2 Please write the program of random shuffling algorithm

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

void shuffle(vector<int>& cards) {
    int n = cards.size();
    for (int i = n - 1; i > 0; i--) {
        int j = rand() % (i + 1);
        swap(cards[i], cards[j]);
    }
}

int main() {
    srand(time(NULL));
    vector<int> cards = {1, 2, 3, 4, 5};
    shuffle(cards);
    for (int card : cards) {
        cout << card << " ";
    }
    cout << endl;
    return 0;
}

3 Please design a Las Vegas algorithm for the traveling salesman problem (whether there is a trip that costs no more than t)

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

const int INF = 1e9;

// 生成随机解
vector<int> randomSolution(int n) {
    vector<int> solution(n);
    for (int i = 0; i < n; i++) {
        solution[i] = i;
    }
    random_shuffle(solution.begin(), solution.end());
    return solution;
}

// 计算路径长度
int calculateDistance(const vector<vector<int>>& graph, const vector<int>& solution) {
    int distance = 0;
    int n = solution.size();
    for (int i = 0; i < n - 1; i++) {
        distance += graph[solution[i]][solution[i + 1]];
    }
    distance += graph[solution[n - 1]][solution[0]];
    return distance;
}

// 拉斯维加斯算法
vector<int> tspLasVegas(const vector<vector<int>>& graph, int t) {
    int n = graph.size();
    vector<int> bestSolution;
    int bestDistance = INF;
    int startTime = time(NULL);
    while (time(NULL) - startTime < t) {
        vector<int> solution = randomSolution(n);
        int distance = calculateDistance(graph, solution);
        if (distance < bestDistance) {
            bestDistance = distance;
            bestSolution = solution;
        }
    }
    return bestSolution;
}

int main() {
    srand(time(NULL));

    // 构造一个5个城市的图
    vector<vector<int>> graph = {
        {0, 2, 9, 10, 5},
        {2, 0, 6, 4, 8},
        {9, 6, 0, 7, 1},
        {10, 4, 7, 0, 3},
        {5, 8, 1, 3, 0}
    };
    int t = 1; // 时间限制为1秒

    vector<int> solution = tspLasVegas(graph, t);
    int distance = calculateDistance(graph, solution);

    cout << "Best solution: ";
    for (int city : solution) {
        cout << city << " ";
    }
    cout << endl;
    cout << "Distance: " << distance << endl;

    return 0;
}

4 Please design a Las Vegas algorithm for the 0/1 knapsack problem (whether there is a benefit and a packing method not less than t)

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

const int INF = 1e9;

// 生成随机解
vector<int> randomSolution(int n) {
    vector<int> solution(n);
    for (int i = 0; i < n; i++) {
        solution[i] = rand() % 2;
    }
    return solution;
}

// 背包问题的价值函数
int knapsackValue(const vector<int>& weights, const vector<int>& values, const vector<int>& solution, int capacity) {
    int n = weights.size();
    int value = 0;
    for (int i = 0; i < n; i++) {
        if (solution[i] == 1) {
            capacity -= weights[i];
            if (capacity < 0) {
                return -INF;
            }
            value += values[i];
        }
    }
    return value;
}

// 拉斯维加斯算法
vector<int> knapsackLasVegas(const vector<int>& weights, const vector<int>& values, int capacity, int t) {
    int n = weights.size();
    vector<int> bestSolution;
    int bestValue = -INF;
    int startTime = time(NULL);
    while (time(NULL) - startTime < t) {
        vector<int> solution = randomSolution(n);
        int value = knapsackValue(weights, values, solution, capacity);
        if (value > bestValue) {
            bestValue = value;
            bestSolution = solution;
        }
    }
    return bestSolution;
}

int main() {
    srand(time(NULL));

    // 构造一个5个物品的背包问题
    vector<int> weights = {2, 3, 4, 5, 6};
    vector<int> values = {3, 4, 5, 6, 7};
    int capacity = 10; // 背包容量为10
    int t = 1; // 时间限制为1秒

    vector<int> solution = knapsackLasVegas(weights, values, capacity,

5 Please use Fermat's little theorem to construct a probability algorithm for primality testing.

Fermat's little theorem is a method for determining whether a number is prime or not. The basic idea is that for any prime number p and integer a, a^p mod p is equal to a. That is to say, if for a number n, randomly select an integer a, if a^(n-1) mod n is not equal to 1, then n must not be a prime number; if a^(n-1) mod n is equal to 1, then n may be prime, but not necessarily prime.

Based on this idea, a probability algorithm can be constructed called Fermat-Miller Primality Test. The basic idea of ​​the algorithm is as follows:

  1. Randomly pick an integer a such that 1 < a < n-1.
  2. Computes the value of a^(n-1) mod n.
  3. If a^(n-1) mod n is not equal to 1, then n must not be a prime number, and the result can be returned directly.
  4. If a^(n-1) mod n is equal to 1, further testing is required.
  5. Choose an integer k such that n-1 can be expressed in the form of 2^r * k, where r and k are both positive integers, and k is an odd number.
  6. For i from 1 to r, compute the value of a (2 (i-1) * k) mod n.
  7. If there exists an i such that a (2 (i-1) * k) mod n is equal to n-1, then n may, but is not necessarily, be prime.
  8. If a (2 (i-1) * k) mod n is not equal to n-1 for all i , then n must not be prime.

The correctness of this algorithm is based on Fermat's little theorem and Euler's theorem. The time complexity of this algorithm is O(k * log^3(n)), where k is the number of tests. Generally speaking, a value of k ranging from 15 to 20 can meet the requirements.

The following is the C++ code implementation:

#include <iostream>
#include <cstdlib>
#include <cmath>

using namespace std;

int power(int x, int y, int p)
{
    int res = 1;
    x = x % p;

    while (y > 0)
    {
        if (y & 1)
            res = (res*x) % p;

        y = y>>1;
        x = (x*x) % p;
    }
    return res;
}

bool isPrime(int n, int k)
{
    if (n <= 1 || n == 4) return false;
    if (n <= 3) return true;

    while (k > 0)
    {
        int a = 2 + rand()%(n-4);

        if (power(a, n-1, n) != 1)
            return false;

        int d = n-1;
        while (d % 2 == 0)
            d /= 2;

        int x = power(a, d, n);

        if (x == 1 || x == n-1)
            continue;

        for (int i = 0; i < d-1; i++)
        {
            x = (x*x) % n;

            if (x == 1)
                return false;

            if (x == n-1)
                break;
        }

        if (x != n-1)
            return false;

        k--;
    }

    return true;
}

int main()
{
    int n = 23;
    int k = 15;

    if (isPrime(n, k))
        cout << n << " is prime" << endl;
    else
        cout << n << " is not prime" << endl;

    return 0;
}

In the above code, the isPrime function is used to judge whether a number is prime, n is the number to be judged, and k is the number of tests. The function first judges some special cases, then randomly selects an integer a, and uses Fermat's little theorem to judge whether n is a prime number. If it is not a prime number, the result is returned directly. If it is prime, a further test is done, which is to check if a (2 (i-1) * k) mod n is equal to n-1. If there exists an i such that a (2 (i-1) * k) mod n is equal to n-1, then n may, but is not necessarily, be prime. If a (2 (i-1) * k) mod n is not equal to n-1 for all i , then n must not be prime.

9.6.2

Experimental questions

1 Please design a Las Vegas algorithm for the Hamilton circuit problem (whether there is a Hamilton circuit)

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

using namespace std;

const int MAXN = 20; // 最大顶点数
int n; // 顶点数
int g[MAXN][MAXN]; // 邻接矩阵

// 随机生成一个顶点序列
vector<int> random_permutation() {
    vector<int> perm(n);
    for (int i = 0; i < n; i++) {
        perm[i] = i;
    }
    shuffle(perm.begin(), perm.end(), default_random_engine());
    return perm;
}

// 检查是否存在Hamilton回路
bool check_hamilton_loop(const vector<int>& perm) {
    for (int i = 0; i < n - 1; i++) {
        if (g[perm[i]][perm[i+1]] == 0) {
            return false;
        }
    }
    if (g[perm[n-1]][perm[0]] == 0) {
        return false;
    }
    return true;
}

// 拉斯维加斯算法
bool hamilton_loop() {
    // 随机生成多个顶点序列进行检查
    const int MAX_ITERATIONS = 1000;
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        vector<int> perm = random_permutation();
        if (check_hamilton_loop(perm)) {
            return true;
        }
    }
    return false;
}

int main() {
    // 读入图的邻接矩阵
    cin >> n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> g[i][j];
        }
    }

    // 检查是否存在Hamilton回路
    if (hamilton_loop()) {
        cout << "存在Hamilton回路" << endl;
    } else {
        cout << "不存在Hamilton回路" << endl;
    }

    return 0;
}

2 Please design a Las Vegas algorithm for the queen problem (whether there is a solution)

#include <iostream>
#include <vector>
#include <random>

using namespace std;

const int MAXN = 100; // 最大皇后数
int n; // 皇后数
int column[MAXN]; // 每行皇后所在的列

// 检查是否存在冲突
bool conflict(int row, int col) {
    for (int i = 0; i < row; i++) {
        if (column[i] == col || row - i == abs(col - column[i])) {
            return true;
        }
    }
    return false;
}

// 随机生成一个初始解
void random_initialization() {
    for (int i = 0; i < n; i++) {
        column[i] = rand() % n;
    }
}

// 拉斯维加斯算法
bool queens() {
    const int MAX_ITERATIONS = 1000;
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        random_initialization();
        bool is_valid = true;
        for (int j = 0; j < n; j++) {
            // 随机选择一列进行移动
            int col = rand() % n;
            if (!conflict(j, col)) {
                column[j] = col;
            } else {
                is_valid = false;
                break;
            }
        }
        if (is_valid) {
            return true;
        }
    }
    return false;
}

int main() {
    // 读入皇后数
    cin >> n;

    // 检查是否存在解
    if (queens()) {
        cout << "存在皇后放置方案" << endl;
    } else {
        cout << "不存在皇后放置方案" << endl;
    }

    return 0;
}

3 Please design a Las Vegas algorithm for the subset sum problem (whether there exists and is a subset of t)

#include <iostream>
#include <vector>
#include <random>

using namespace std;

const int MAXN = 100; // 最大元素数
int n; // 元素数
int a[MAXN]; // 元素值
int t; // 目标和

// 检查是否存在和为t的子集
bool check_subset_sum() {
    // 随机生成一个01序列,用于表示当前的子集
    vector<int> subset(n);
    for (int i = 0; i < n; i++) {
        subset[i] = rand() % 2;
    }

    // 检查当前子集的和是否为t
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (subset[i] == 1) {
            sum += a[i];
        }
    }
    return sum == t;
}

// 拉斯维加斯算法
bool subset_sum() {
    const int MAX_ITERATIONS = 1000;
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        if (check_subset_sum()) {
            return true;
        }
    }
    return false;
}

int main() {
    // 读入元素数和目标和
    cin >> n >> t;

    // 读入元素值
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    // 检查是否存在和为t的子集
    if (subset_sum()) {
        cout << "存在和为" << t << "的子集" << endl;
    } else {
        cout << "不存在和为" << t << "的子集" << endl;
    }

    return 0;
}

4 Please design a Las Vegas algorithm for the clique problem (whether there is a clique with the number of vertices not less than k)

#include <iostream>
#include <vector>
#include <random>

using namespace std;

const int MAXN = 100; // 最大顶点数
int n; // 顶点数
int g[MAXN][MAXN]; // 图的邻接矩阵
int k; // 团的大小下限

// 检查是否存在顶点数不小于k的团
bool check_clique() {
    // 随机选择一个顶点作为团的起点
    int start = rand() % n;
    
    // 初始化团
    vector<int> clique;
    clique.push_back(start);
    
    // 逐步扩展团
    for (int i = 0; i < n; i++) {
        if (i == start) {
            continue;
        }
        bool is_clique = true;
        for (int v : clique) {
            if (g[v][i] == 0) {
                is_clique = false;
                break;
            }
        }
        if (is_clique) {
            clique.push_back(i);
            if (clique.size() >= k) {
                return true;
            }
        }
    }
    return false;
}

// 拉斯维加斯算法
bool clique() {
    const int MAX_ITERATIONS = 1000;
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        if (check_clique()) {
            return true;
        }
    }
    return false;
}

int main() {
    // 读入顶点数和团的大小下限
    cin >> n >> k;

    // 读入图的邻接矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> g[i][j];
        }
    }

    // 检查是否存在顶点数不小于k的团
    if (clique()) {
        cout << "存在顶点数不小于" << k << "的团" << endl;
    } else {
        cout << "不存在顶点数不小于" << k << "的团" << endl;
    }

    return 0;
}

5 Please use the probability method to design a Monte Carlo algorithm that judges whether a given integer function f(x) is always equal to 0, and use a programming language to describe the algorithm, and its C/C++ function prototype is "bool iszero (int f(int), int n);", where n means to give the answer after at most n comparisons

#include <iostream>
#include <random>

using namespace std;

// 判断函数f是否恒等于0,最多进行n次比较
bool iszero(int (*f)(int), int n) {
    // 随机生成n个整数,作为函数的参数
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dis(-1000, 1000);
    for (int i = 0; i < n; i++) {
        int x = dis(gen);
        if (f(x) != 0) {
            return false;
        }
    }
    return true;
}

// 测试函数,判断一个整数是否为偶数
int is_even(int x) {
    return x % 2;
}

int main() {
    // 测试is_even函数是否恒等于0
    if (iszero(is_even, 1000)) {
        cout << "is_even函数恒等于0" << endl;
    } else {
        cout << "is_even函数不恒等于0" << endl;
    }

    return 0;
}

In the above code, we first randomly generate n integers as parameters of the function f. Then for each parameter, calculate the value of f(x). If there is a parameter that makes f(x) not equal to 0, it means that the function f is not equal to 0, and returns false. If all parameters satisfy that f(x) is equal to 0, it means that the function f is always equal to 0 and returns true. The number of repetitions n can be adjusted according to actual conditions.

The randomness of this algorithm is mainly reflected in the process of randomly generating the parameters of the function. The parameters generated each time are random, so the results of each algorithm run may be different. This randomness can increase the global search ability of the algorithm, thereby reducing the risk of the algorithm falling into a local optimal solution.

Guess you like

Origin blog.csdn.net/CSH__/article/details/130311080