• 回溯算法四:子集树、排列树以及组合优化


    一、子集树

    1、子集树:若一个组合问题的解释给定集合的子集,则解向量<x1, x2,...,xn>可以表示为分量取值为{0,1}的比特串,解空间可以组成一颗完全二叉树,称这棵搜索树为一棵子集树;

    2、由于解向量的每个分量均取0或1,因此可以省略解集合处理过程;

    3、子集问题示例,可以参考:回溯算法三:经典问题实现(m-着色、n-皇后、Hamilton回路、子集和)

    二、排列树

    1、把解向量是n个元素排列的组合问题的搜索树,称为排列树,将这类组合问题成为排列树问题;

    2、由于排列树问题的特殊性,可以将n-叉完全二叉树简化,以提高效率;

    3、简化步骤:

    EXPLORE(P, K)
    0 n = length(x)
    1 if IS-COMPLATE(X)                  // 判断解向量是否完全
    2    then flag = true                // 若为完全解,则置解标志,输出解信息,并返回
    3         PRINT-SOLUTION(X)
    4         return		             // 需要分析,是否需要输出所有的完整解
    5 if k > n			                 // k为当前解向量长度,n为解向量的最大长度
    6    then return                     // 若k>n,表示当前分支遍历完全且无解,直接返回
    7
    8 for i=(k,...,n)                    // 对当前第k个分量,逐一检测各种可能的取值
    10    do x[k] <=> x[i]               // 遍历x[k]的有效取值(非完全遍历),且交换后,维持x的有效性       
    11        if IS-PARTIAL(x, k)        // 确定是否为部分解
    12            then 	EXPLORE(P, k+1)  // 继续递归下一步探索过程
    13    x[k] <=> x[i]                  // 还原x,继续进行for中的下一个排列
    
    

    4、n-皇后问题示例如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // x[i]表示每行棋子的列位置,k表示最新添加的棋子索引,即当前处理的第k行的棋子位置问题(k从0开始)
    int isPartial(int n, int *x, int k)
    {
        int diff;
        for (int i = 0; i < k; i++) {
            // 判断新增的位置与历史位置是否满足规则:不同行、不同列、不同斜线
            // 遍历判断新增棋子与之前棋子的位置差异
            diff = x[i] - x[k];
            // 列位置相同——同列;列号之差等于行号之差——对角线;遍历处理[0,k-1]行,不可能同行
            if (diff == 0 || (diff == i - k) || (diff == k - i)) {
                return 0;
            }
        }
        return 1;
    }
    
    void swap(int *x, int k, int i)
    {
        int temp = x[k];
        x[k] = x[i];
        x[i] = temp; 
    }
    // 递归过程,x与k同时变化,其他均为定值
    void nQueensPlus(int n, int *x, int k)
    {
        int i;
        // 完全解判断:k为当前解长度,n为完整解的最大长度
        if (k >= n) {
            for (int i = 0; i < n; i++) {
                printf("%d ", x[i]);
            }
            printf("
    ");
            return;
        }
    
        // 递归遍历,回溯过程
        for (int i = k; i < n; i++) {
            // 针对x[k]的遍历值,取值范围为x[k,,,n],有效减小搜索空间
            swap(x, k, i);
            // 判断部分解逻辑复杂,建议抽取函数
            if (isPartial(n, x, k)) {
                nQueensPlus(n, x, k + 1);
            }
            // 恢复x,进行下一轮
            swap(x, i, k);
        }
    }
    
    int main(void)
    {
        // n为棋盘规模,x为解向量空间,k为当前解的个数[0, n-1]
        int n = 4;
        int k = 0;
        // 初始化解向量
        int *x = (int*)malloc(n * sizeof(int));
        for (int i = 0; i < n; i++) {
            x[i] = i;
        }
        
        // n为棋盘规模,locations为棋盘位置集合(列),x为解向量空间,k为当前解的个数[0, n-1]
        nQueensPlus(n, x, k);
        while(1);
        return 0;
    }
    
    

    三、组合优化

    1、简要说明

    (1)组合优化问题需要在解空间中找到最优者:在回溯过程中,需要跟踪目前为止最优合法解的目标值most,与当前搜索到的合法解current的目标值进行比较,确认是否需要更新most。

    (2)可以利用当前最优值most,对不可能成为最优解的子树进行剪枝(isPartial步骤)。

    2、旅行商问题

    (1)问题描述:商人从n个城市中的一个出发,希望走遍每个城市且每个城市只经过一次,然后回到出发的城市。如果有这样的路径,要求找到里程最短的路径。(最短的Hamilton回路)

    输入:带权无向图G=<V, E>,权函数w:E->R,起始点s。

    输出:如果存在Hamilton回路,输出权值最小的路径,否则输出无解信息。

    (2)分析过程:

    (3)算法实现:

    (4)测试结果:

  • 相关阅读:
    配置 L3 agent
    Why Namespace?
    虚拟 ​router 原理分析
    创建 router 连通 subnet
    用 config drive 配置网络
    cloud
    写在最前面
    使用apktool工具遇到could not decode arsc file的解决办法
    php-fpm优化
    解决官网下载jdk只有5k大小的错误
  • 原文地址:https://www.cnblogs.com/HZL2017/p/14679407.html
Copyright © 2020-2023  润新知