Algorithm Design and Analysis Final Summary

0000 Preface: It is basically for me to read some things that I tend to forget, to prepare for the exam, mainly for the second half of the knowledge, for the first half, please see the summary of the algorithm design and analysis stage exam

chapter Five

A backtracking algorithm is a method of systematically searching for a solution to a problem. All possible solutions of a problem are called the solution space of the problem . If the solution space is limited, the solution space can be mapped into a tree structure . The basic idea of ​​the backtracking algorithm is: go forward from one road, advance if you can, go back if you can't, and try another road.

Related concepts 1

  • extension node
  • Slip node
  • dead node

Related concepts 2

  • Subset tree: O(2n) computing time is required to traverse the subset tree
  • Permutation tree: O(n!) calculation time is required to traverse the permutation tree

Chapter Six

The branch and bound method is an algorithm for solving optimization problems. It usually searches the solution space tree of the problem in a breadth-first or minimum-cost (maximum benefit)-first manner. The basic idea is to expand the feasible solutions of the problem, and then find the best solution from each branch .

In the branch and bound method, the branch is to use the breadth-first strategy to generate all the branches of the extended node in turn. The limit is to calculate the upper bound of the node during the node expansion process, and cut off some branches while searching.


Difference from Backtracking

The solution goals are different

  • The backtracking method is to find all solutions that satisfy the constraints
  • The branch and bound method is to find a solution that satisfies the conditions, or the optimal solution in a certain sense

search differently

  • Backtracking: Depth First
  • Branch and bound method: breadth first or least cost first

Related concepts

  • Slip node
  • live node table PT
  • extension node
  • son node
  • queued branch and bound
  • priority queue branch and bound

Chapter VII

Randomization algorithms fall broadly into four categories:

  • Numerical randomization algorithm
  • Monte Carlo algorithm
  • Las Vegas Algorithm
  • Sherwood Algorithm

Input to the randomization algorithm

  • input to the original problem
  • A sequence of random numbers chosen at random

Numerical stochastic algorithms are often used to solve numerical problems. These algorithms often obtain approximate solutions, and the accuracy of the approximate solutions increases with the increase of computing time. In many cases, it is impossible or unnecessary to calculate the exact solution of the problem, and the numerical tax base algorithm can be used to obtain a quite satisfactory solution.

Numerical problems are commonly used in various integral and differential, mathematical calculations.

Monte Carlo algorithm is used to find the exact solution of the problem. For many problems, approximate solutions are meaningless. A solution to the problem can be found with the Monte Carlo algorithm, but the solution may not be correct. The probability of finding the correct solution depends on the time taken by the algorithm. The more time the algorithm takes, the higher the probability of getting the correct solution. The main disadvantage of the Monte Carlo algorithm is also here. In general, it is impossible to effectively judge whether the obtained solution is determinable and correct. (Non-general cases can be judged!)

The Las Vegas algorithm does not get incorrect solutions. Once a solution is found using the Las Vegas algorithm, it must be the correct solution. But sometimes the Las Vegas algorithm can't find a solution. The probability that the Las Vegas algorithm finds the correct solution increases as the computation time it takes increases.

The Sherwood algorithm  can always find a correct solution to the problem, eliminating the correlation between the worst-case behavior of the algorithm and a specific instance does not improve the average performance, nor does it deliberately avoid the worst-case behavior of the algorithm

Precautions

  • The result of the randomization algorithm cannot be guaranteed to be correct, but its error probability can be limited;
  • The randomization algorithm can have different results for the same input instance in different runs, so the execution time of the randomization algorithm can be different for the same input instance.

Analysis of Algorithms

Backtracking

Batch job scheduling (guess)

Given a set of n jobs {J1,J2,...,Jn}. Each job must be processed first by machine 1 and then by machine 2. Job Ji requires processing time tij on machine j. For a definite job schedule, let Fij be the time when job i completes processing on machine j. The sum of the processing times of all jobs on machine 2 is called the sum of the completion times of the job schedule. The batch job scheduling problem requires that for a given n jobs, to formulate the best job scheduling scheme to minimize the sum of the completion time.

Questions after N (omitted)

Place n queens who are not attacked by each other on an n×n grid board. According to the rules of chess, a queen can attack a piece that is on the same row or column or on the same diagonal as the queen. The n-queen problem is equivalent to placing n queens on an n×n grid, and any two queens are not placed on the same row or column or on the same slash. The problem has a solution if and only if n = 1 or n ≥ 4.

train of thought

Four post-problem solutions

Symbolic triangle problem (omitted)

 train of thought

Use the n-tuple x[1:n] to represent the n symbols of the first row of the symbol triangle. When x[i] is equal to 1, it means that the i-th symbol of the first row of the symbol triangle is "+"; when x When [i] is equal to 0, it means that the i-th symbol in the first row of the symbol triangle is "-"; 1<=i<=n. Since x[i] is 2-valued. Therefore, when using the backtracking method to solve the symbolic triangle problem, a complete binary tree can be used to represent its solution space. After the first i symbols x[1:i] in the first row of the symbol triangle are determined, a symbol triangle consisting of i*(i+1)/2 symbols is determined.

(i*(i+1)/2 comes from the summation formula of the arithmetic sequence whose first item is 1 and the tolerance is 1)

 Unsolvable judgment: n*(n+1)/2 is an odd number

0-1 knapsack problem

 Exercise: An example of the 0-1 knapsack problem is: n=4, c=16, p=[22,21,16,7], w=[11,10,8,7]. To solve this problem according to the backtracking method, try to answer the following questions:

(1) What is the constraint function for this problem?

(2) Please draw the solution space tree for finding the optimal solution. Nodes that are required to be discarded in the middle (solutions that do not meet the constraint conditions) are marked with ×, nodes that obtain intermediate solutions are framed with a single circle ○, and the optimal solution is framed with a double circle ◎.

Answer: (1) The constraint function is: ∑wixi≤C, that is, the backpack can hold items

(2) The solution space tree is shown in the figure below.

Largest group exercises (guess)

 Feasibility constraint function: every vertex from the current vertex to the selected vertex set is connected by an edge.

(2) Bounding function: There are enough optional vertices to make it possible for the algorithm to find a larger clique in the right subtree.

branch and bound

0-1 knapsack problem

Consider the following instance of the 0-1 knapsack problem: n=3, c=30, w=[16,15,15], v=[45,25,25]

branch and bound

Traveling salesman problem (the same idea is only put in PPT) (omitted)

queued branch and bound

priority queue branch and bound

The loading problem is the same

queued branch and bound

Priority queue branch and bound method (guess)

When the branch-and-bound method of priority queue is given to solve this example, the change process of the live node table (priority is the sum of the weight of the container in the current ship plus the weight of the remaining container). Description method: [A,B,C]FGF(40), where [A,B,C] is the active node table, A is the current expanded node, FG is generated by A, and G does not meet the constraints and is cut Off, 40 means the priority of node F 

Examples of Wiring Problems

randomization algorithm

Calculating Definite Integral by Random Number  Casting Point Method

Monte Carlo randomization algorithm

programming

Backtracking

loading problem

Problem Description

There is a batch of n containers to be loaded on 2 ships with loads C1 and C2 respectively, where the weight of container i is wi, and ∑wi ≤ C1+C2 The loading problem requires determining whether there is a reasonable loading scheme that can load this The containers were loaded onto the 2 ships. If so, find a loading scheme. It is easy to prove that if a given loading problem has a solution, the optimal loading scheme can be obtained by adopting the following strategy: (1) First, fill the first ship as full as possible; (2) Load the remaining containers on the second ship ship.

problem analysis

key code

// 搜索到叶子节点
    if (i > n) {
        // 如果找到更优解,则更新最优解
        if (cw > bestw) {
            bestw = cw;
            for (int j = 1; j <= n; j++) {
 
                best[j] = x[j];
            }
        }
        return;
    }
    // 搜索左子树
    r -= w[i];
    if (cw + w[i] <= c) {
        x[i] = 1;
        cw += w[i];
        backtrack(i + 1);
        cw -= w[i];
    }
    r += w[i];
    // 搜索右子树
    if (cw + r > bestw) {
        x[i] = 0;
        backtrack(i + 1);
    }



detailed code

#include <stdio.h>
#define MAX_N 20
 
int n;                      // 货物数量
int c;                      // 车的载重量
int w[MAX_N],x[MAX_N];               // 每个货物的重量
int best[MAX_N];            // 最优解
int cw;                     // 当前载重量
int bestw;                  // 最优载重量
int r;                      // 剩余物品重量和
 
// 搜索装载方案
void backtrack(int i)
{
    // 搜索到叶子节点
    if (i > n) {
        // 如果找到更优解,则更新最优解
        if (cw > bestw) {
            bestw = cw;
            for (int j = 1; j <= n; j++) {
 
                best[j] = x[j];
            }
        }
        return;
    }
    // 搜索左子树
    r -= w[i];
    if (cw + w[i] <= c) {
        x[i] = 1;
        cw += w[i];
        backtrack(i + 1);
        cw -= w[i];
    }
    r += w[i];
    // 搜索右子树
    if (cw + r > bestw) {
        x[i] = 0;
        backtrack(i + 1);
    }
}
 
int main()
{
    //printf("请输入货物数量和车的载重量(用空格分隔):\n");
    scanf("%d%d", &n, &c);
    //printf("请输入每个货物的重量:\n");
    for (int i = 1; i <= n; i++) {
        scanf("%d", &w[i]);
        r += w[i];
    }
    backtrack(1);
    printf("最优装载方案为:\n");
    for (int i = 1; i <= n; i++) {
        if (best[i]) {
            printf("%d ", i);
        }
    }
    printf("\n最优载重量为:%d\n", bestw);
    return 0;
}

0-1 Backpack ( The PPT is ugly and watery, recommended for the exam · the following code)

PPT code please refer to the blog Backtracking Algorithm Design Experiment

where the key code is


int bound(int t)
{
    int cleft = C - CurWeight;//剩余容量
    int b = CurValue;//现阶段背包内物品的价值
    while (t <= n && w[t] <= cleft)//以物品重量价值递减装入物品
    {
        cleft = cleft - w[t];
        b = b + v[t];
        t++;
    }
    if (t <= n)//装满背包
        b = b + v[t] * cleft / w[t];//计算t号物品的单位价值装满剩余空间
    return b;
}
void backtrack(int t)
{
    if (t > n)//到达叶子节点了
    {
        if (CurValue > BestValue)//已经搜寻完一次了,把现有的最大值赋值;
        {
            BestValue = CurValue;
            for (int i = 1; i <= n; i++)
                BestX[i] = X[i];
        }
        return;
    }
    if (CurWeight + w[t] <= C)//不到背包最大容量进入左子树
    {
        X[t] = 1;//记录是否装入
        CurWeight += w[t];
        CurValue += v[t];
        backtrack(t + 1);//回溯
        CurWeight -= w[t];
        CurValue -= v[t];
    }
    if (bound(t + 1) > BestValue)//进入右子树
    {
        X[t] = 0;//他自己没有后面物品合适
        backtrack(t + 1);//判断
    }
}


 Problem Description

There are n items, each of which has its own volume and value. With a backpack of given capacity, how to maximize the total value of the items loaded in the backpack?
Note : Different from the ordinary knapsack problem, in the 0-1 knapsack problem, the items appear as a whole, and you can only choose to put the whole in the knapsack or not put the whole in the knapsack.

#include <stdio.h>
#include <stdlib.h>
#define N 100
int n, c, maxValue = 0; // 物品数量,背包容量,最大价值
int w[N], v[N]; // 物品重量,物品价值
int path[N];
int path0[N];
void backtrack(int i, int res, int value) {
    if (i == n) {
        if (value > maxValue) {
                maxValue = value;
                for (int i = 0; i < n; i++) path0[i] = path[i];
        }
        return;
    }
    path[i] = 1;
    if (res >= w[i]) {
            backtrack(i + 1, res - w[i], value + v[i]); // 考虑第i个物品放入背包
    }
    path[i] = 0;
    backtrack(i + 1, res, value); // 不考虑第i个物品放入背包
}
 
int main() {
    scanf("%d%d", &n, &c);
    for (int i = 0; i < n; i++) scanf("%d%d", &w[i], &v[i]);
    backtrack(0, c, 0);
    printf("%d\n", maxValue);
    for (int i = 0; i < n; i++) printf("%d ", path0[i]);
    return 0;
}

The traveling salesman problem (omitted)

problem analysis

 detailed code

#include <stdio.h>
#include <stdbool.h>
#define MAXN 100 // 最大城市数
 
int n;             // 城市数
int graph[MAXN][MAXN]; // 图的邻接矩阵
int path[MAXN],bestPath[MAXN];    // 保存当前路径
bool visited[MAXN]; // 标记城市是否访问过
int minDist = 0x7fffffff; // 保存最短路径的长度
 
void backtracking(int cur, int dist) {
    if (cur == n) {  // 所有城市都已经走过了
        if (dist + graph[path[n - 1]][0] < minDist) {
            minDist = dist + graph[path[n - 1]][0]; // 更新最短路径
            for(int i = 0;i < n;i++){
                bestPath[i] = path[i];
            }
        }
        return;
    }
    for (int i = 1; i < n; i++) { // 枚举下一个城市
        if (!visited[i]) {        // 如果这个城市还没有访问过
            path[cur] = i;         // 选择这个城市
            visited[i] = true;     // 标记这个城市已经访问过
            backtracking(cur + 1, dist + graph[path[cur - 1]][i]); // 递归到下一层
            visited[i] = false;    // 回溯,撤销选择
        }
    }
}
 
int main() {
    scanf("%d", &n); // 输入城市数
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d", &graph[i][j]); // 输入邻接矩阵
        }
    }
    path[0] = 0;       // 起点是城市0
    visited[0] = true; // 标记起点已经访问过
    backtracking(1, 0); // 从第2个城市开始递归
    printf("%d\n", minDist); // 输出最短路径长度
    for(int i = 0;i < n;i++){
        printf("%d ",bestPath[i]+1);
    }
    return 0;
}

 0-1 backpack

 

branch and bound

loading problem

// 定义MaxLoading子函数,用来求解装载问题
// 输入参数:w是一个整型数组,表示集装箱的重量;c是一个整型变量,表示船舶的载重量;n是一个整型变量,表示集装箱的数量;bestx是一个整型数组,用来存储最优解方案
// 返回值:bestw是一个整型变量,表示最优解值
int MaxLoading(int* w, int c, int n, int* bestx) {
    // 初始化变量
    int i = 1; // 当前扩展节点所在层次
    int j; // 循环计数器
    int bestw = 0; // 最优解值
    Heap h; // 优先队列
    h.length = 0; // 优先队列的长度初始化为0
    int* r = new int[n + 1]; // 剩余集装箱重量之和
    r[n] = 0;
    for (j = n - 1; j > 0; j--)
        r[j] = r[j + 1] + w[j + 1];
    Node* p = new Node; // 当前扩展节点
    p->weight = 0;
    p->level = i;
    p->parent = NULL;
    Node* q; // 新生成节点

    while (i != n + 1) { // 当还未到达叶子节点时循环
        if (p->weight + w[i] <= c) { // 进入左子树,即选择第i个集装箱
            q = new Node; // 创建新节点
            q->LChild = 1; // 标记为左子树
            q->level = p->level + 1; // 层次加一
            q->parent = p; // 父节点指向当前扩展节点
            q->weight = p->weight + w[i]; // 节点重量等于父节点重量加上第i个集装箱重量
            q->uweight = q->weight + r[i]; // 节点上界等于节点重量加上剩余集装箱重量之和
            if (q->level == n + 1 && q->weight > bestw) { // 找到更好解
                bestw = q->weight; // 更新最优解值
                for (j = n; j > 0; j--) { // 更新最优解方案
                    bestx[j] = q->LChild;
                    q = q->parent;
                }
            }
            else { // 将新生成节点插入优先队列
                HeapInsert(h, q);
            }
        }
        if (p->weight + r[i] > bestw) { // 进入右子树,即不选择第i个集装箱,并且满足剪枝条件
            q = new Node; // 创建新节点
            q->LChild = 0; // 标记为右子树
            q->level = p->level + 1; // 层次加一
            q->parent = p; // 父节点指向当前扩展节点
            q->weight = p->weight; // 节点重量等于父节点重量
            q->uweight = q->weight + r[i]; // 节点上界等于节点重量加上剩余集装箱重量之和
            if (q->level == n + 1 && q->weight > bestw) { // 找到更好解
                bestw = q->weight; // 更新最优解值
                for (j = n; j > 0; j--) { // 更新最优解方案
                    bestx[j] = q->LChild;
                    q = q->parent;
                }
            }
            else { // 将新生成节点插入优先队列
                HeapInsert(h, q);
            }
        }
        delete p; // 删除当前扩展节点
        if (!h.empty()) { // 取堆顶元素作为下一个扩展节点,并且堆不为空时继续循环
            HeapDelete(h, p);
            i = p->level;
        }
        else { // 堆为空则结束循环
            break;
        }
        
    }
    delete[] r; // 删除动态数组r
    return bestw; // 返回最优解值
}

maximum clique problem

 

0-1 backpack

randomization

Randomized quicksort: A quicksort algorithm that randomly selects pivot points

core code

void quickSort(int r[], int low, int high)
{
	srand(time(0));
	int i, k;
	if (low<high)
	{
		i=randomNum(low, high); //在区间[low,high]中随机选取一个元素,下标为i
		r[low]←→r[i]; //交换r[low]和r[i]的值
		k=partition(r, low, high); //进行一次划分,得到轴值的位置k
		quickSort(r, low, k-1);//在前半部分继续查找
		quickSort(r, k+1, high);//在后半部分继续查找
	}
}

full code

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
//舍伍德(Sherwood)型随机化算法 随机快速排序:随机选择枢点的快速排序算法
 
//在区间[low,high]中随机选取一个元素,下标为i
int randomNum(int low, int high){
  return low + rand() % (high - low + 1);
}
 
//交换两个元素的值
void swap(int *a, int *b){
  int temp = *a;
  *a = *b;
  *b = temp;
}
 
//进行一次划分,得到轴值的位置k
int partition(int r[], int low, int high){
  int pivot = r[low]; //选取第一个元素作为轴值
  while(low < high){ //循环直到low和high相遇
    while(low < high && r[high] >= pivot) high--; //从右向左找到第一个小于轴值的元素
    swap(&r[low], &r[high]); //交换r[low]和r[high]的值
    while(low < high && r[low] <= pivot) low++; //从左向右找到第一个大于轴值的元素
    swap(&r[low], &r[high]); //交换r[low]和r[high]的值
  }
  return low; //返回轴值的位置
}
 
//快速排序函数
void quickSort(int r[], int low, int high)
{
	srand(time(0));
	int i, k;
	if (low<high)
	{
		i=randomNum(low, high); //在区间[low,high]中随机选取一个元素,下标为i
		swap(&r[low], &r[i]); //交换r[low]和r[i]的值
		k=partition(r, low, high); //进行一次划分,得到轴值的位置k
		quickSort(r, low, k-1);//在前半部分继续查找
		quickSort(r, k+1, high);//在后半部分继续查找
	}
}
 
//打印数组
void printArray(int arr[], int n){
  int i;
  for(i=0; i<n; i++){
    printf("%d ", arr[i]);
  }
  printf("\n");
}
 
//主函数
int main(){
  //定义一个数组,表示10个待排序的数
  int arr[10] = {23, 45, 12, 67, 89, 34, 56, 78, 90, 11};
  //打印原始数组
  printf("原始数组:\n");
  printArray(arr, 10);
  //调用快速排序函数
  quickSort(arr, 0, 9);
  //打印排序后的数组
  printf("排序后的数组:\n");
  printArray(arr, 10);
 
  return 0;
}

Eight queens problem (ppt has no code, I feel that the probability of the test is not high)

(1) Initialize the array x[8] to 0; initialize the trial times count to 0;

(2)for (i=1; i<=8; i++)     

 2.1 Generate a random number j of [1, 8];   

 2.2 count=count+1, conduct the countth trial;     

 2.3 If queen i (fixed in row i) is placed in column j without conflict, then x[i]=j; count=0; go to step (2) (the for loop continues to run) to place the next queen;     

  2.4 If (count==8), queen i cannot be placed, and the algorithm fails.       

 Go to step 2.1 to reposition queen i;

(3) Output the elements x[1]~x[8] as a solution to the eight queens problem.

#include <iostream>
#include <cstdlib>
#include <ctime>
 
using namespace std;
 
bool isSafe(int x[], int row, int col) {
    // 检查当前位置是否与之前放置的皇后冲突
    for (int i = 1; i < row; i++) {
        if (x[i] == col || abs(i - row) == abs(x[i] - col)) {
            return false;
        }
    }
    return true;
}
 
void solveEightQueens(int x[], int row) {
    if (row > 8) {
        // 所有皇后都放置完成,打印解
        for (int i = 1; i <= 8; i++) {
            cout << x[i] << " ";
        }
        cout << endl;
    } else {
        for (int j = 1; j <= 8; j++) {
            if (isSafe(x, row, j)) {
                x[row] = j;
                solveEightQueens(x, row + 1);
            }
        }
    }
}
 
int main() {
    srand(time(0));
 
    int x[9] = {0}; // 数组从下标 1 开始使用,初始化为0
 
    solveEightQueens(x, 1);
 
    return 0;
}

The meaning of the output of the code is as follows:

  • Each row represents a solution, that is, a queen placement scheme that satisfies the conditions.
  • There are eight numbers in each row, which respectively indicate the column numbers of the queens in the first to eighth rows.
  • For example, the output of the first row is 4 2 7 3 6 8 5 1, which means that the queen of the first row is in the fourth column, the queen of the second row is in the second column, and so on.
  • In total, there are 92 possible solutions, that is, 92 different queen placement schemes.

main element problem

core code


bool isMajority(int arr[], int n, int x) {
    int count = 0; // 记录x出现的次数
    for (int i = 0; i < n; i++) {
        if (arr[i] == x) count++;
    }
    return count > n / 2; // 如果x出现次数超过一半,返回true
}

optimization

full code

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
// 在区间[low,high]中随机选取一个整数
int randomNum(int low, int high) {
    return low + rand() % (high - low + 1);
}
 
// 判断一个元素是否是主元素,即出现次数超过一半
bool isMajority(int arr[], int n, int x) {
    int count = 0; // 记录x出现的次数
    for (int i = 0; i < n; i++) {
        if (arr[i] == x) count++;
    }
    return count > n / 2; // 如果x出现次数超过一半,返回true
}
 
// 蒙特卡罗函数,返回数组中的一个主元素,如果不存在,返回-1
int monteCarlo(int arr[], int n) {
    srand(time(0)); // 设置随机数种子
    int k = 10; // 设置最大尝试次数
 
    // 候选主元素初始化为数组的第一个元素
    int candidate = arr[0];
    int count = 1; // 记录候选主元素的计数
 
    for (int i = 1; i < n; i++) {
        if (arr[i] == candidate) {
            count++;
        } else {
            count--;
            if (count == 0) {
                // 当前候选主元素计数为0,更新候选主元素为当前元素
                candidate = arr[i];
                count = 1;
            }
        }
    }
 
    // 最后确定的候选主元素需要再次验证
    if (isMajority(arr, n, candidate)) {
        return candidate; // 如果是,返回该元素
    }
 
    return -1; // 如果不存在主元素,返回-1
}
 
// 打印数组
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}
 
// 主函数
int main() {
    // 定义一个数组,表示n个待查找的数
    int arr[10] = {3, 3, 4, 4, 2, 4, 2, 4, 4,4};
 
    // 打印原始数组
    cout << "原始数组:\n";
    printArray(arr, 10);
 
    // 调用蒙特卡罗函数,返回数组中的一个主元素
    int result = monteCarlo(arr, 10);
 
    // 打印结果
    if (result == -1) {
        cout << "不存在主元素" << endl;
    } else {
        cout << "一个主元素是:" << result << endl;
    }
 
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_62377885/article/details/130832631