• 回溯算法二:算法框架与实现


    一、算法框架分析

    1、刻画组合各自特性的动态属性统一表示如下:
    (1)MAKE-ITEMS:生成当前节点的取值集合;
    (2)IS-PARTIAL:判断部分解;
    (3)IS-COMPLETE:判断完整解;
    (4)PRINT-SOLUTION:打印一个合法解,输出结果;
    静态属性:
    (5)问题的解向量长度:n
    (6)问题的解向量x

    2、组合问题抽象描述如下:

    输入:解向量的最大长度n,解向量x,产生解向量第k个分量取值集合items={items1,items2,...,itemsm}的过程MAKE-ITEMS,判断部分解规则IS-PARTIAL,判断完整解的规则IS-COMPLETE,打印合法解的方法PRINT-SOLUTION的组合问题P。

    输出:如果问题P有合法解,输出所有合法解,否则输出无解信息。

    3、算法框架如下:

    设解向量的分量取值集合items有m个元素,解向量的维数为n,则解空间可以组织为高度为n的m叉完全树。

    回溯算法框架:

    BACKTRACK(P)
    1 flag = false           // 是否有解标志
    2 为解向量x分配存储空间    // malloc
    3 EXPLORE(P, 1)          // 探索过程
    4 if flag == false       // 判断解标志
    5	then print_error "no solution"    // 打印无解信息
    
    

    探索过程EXPLORE:

    EXPLORE(P, K)
    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 items = MAKE-ITEMS(K)              // 生成当前节点的取值集合
    8 m = length(items)                  // 集合长度
    9 for i=(1,...,m)                    // 对当前第k个分量,逐一检测各种可能的取值
    10    do x[k] = items[i]            
    11        if IS-PARTIAL(x, k)        // 确定是否为部分解
    12            then 	EXPLORE(P, k+1)  // 继续递归下一步探索过程
    
    

    二、 框架代码实现

    结构体及变量定义:

    // 单链表定义
    struct List {
        void *data;
        struct List *next;
    };
    typedef struct List List;
    
    // 变量定义
    void *x;                                 // 解向量
    int n;                                   // 解向量长度
    int elesize;                             // 解向量元素存储长度
    int flag = 0;                            // 解标志
    
    int (*isComplete)(void *x, int k);       // 判断完整合法解
    void (*printSolution)(void *x, int k);   // 打印解向量
    List (*makeItems)(int k);                // 生成第k个分量的选项表
    int (*isPartial)(void *x, int k);        // 判断部分合法解
    
    

    算法框架实现:

    void backtrack(void(*explore)(int))
    {
        explore(0);
        if (!flag) {
            printf("no solution.
    ");
        }
    }
    
    void generalExplore(int k)
    {
        int i;
    	// step1: 完整解判断
        if (isComplete(x, k)) {
            flag = 1;
            printSolution(x, k);
            return;
        }
    	// step2: 无解退出
        if (k >= n) {
            return;
        }
    	// step3: 处理下一个节点
        // step3.1: 确定第k个节点的取值集合
        List *iterms = makeIterms(k);
        List *q = iterms;
        // step3.2: 遍历该节点的取值集合iterms
        while (q != NULL) {
    		// step4: 针对某个取值,将该节点的值加入解空间,确认是否为部分解
            memcpy(x + k * elesize, q->data, elesize);
            if (isPartial(x, k)) {
    			// step5: 若是部分解则处理下一个节点,直到完全解结束,或者无解退出
                generalExplore(k + 1);
            }    // step5.1: 此处省略的else语句,表明该取值不满足部分解,放弃该方案,不进行后续节点彷徨
    	
            // 步骤4、5可以处理一种取值方案, 遍历第k个节点的下一种取值方案,回溯的味道尽在于此
            // 本质上是,深度搜索所有解空间,然后在递归过程中,根据部分解要求及时剪枝处理;
            // 剪枝完成后回到上一层,再继续向后进行,即出现回溯;
            // 若是没有剪枝动作,兼职就是深度搜索,暴力解决
            q = q->next;
        }
    
        // step6: 完成第k个节点的处理
        listClear(iterms);
        free(iterms);
        iterms = NULL;
    }
    
    

    三、 m-着色问题:代码实现

    以下完整代码包括:全局变量定义,单链表操作实现,回溯法框架着色问题具体实现,测试代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /**************************************part1: 单链表处理**********************************************/
    // 单链表定义
    struct List {
        void *data;
        struct List *next;
    };
    typedef struct List List;
    
    // 链表新增节点:e为节点数据,l为链表头指针,将新增节点添加在链表头
    void listPushBegin(List **l, void *e)
    {
        // 节点内存申请
        List *p = (List*)malloc(sizeof(List));
        // 节点数据赋值
        p->data = e;
        // 将新节点插入链表头
        p->next = *l;
        // 更新链表头指针l
        *l = p;
    
        // printf("l= %d
    ", (*l)->data);
    }
    
    // 链表新增节点,并将节点添加到链表尾部,l为链表尾指针(单链表,需要另外保存头节点)
    void listPushBack(List **l, void *e)
    {
        // 节点内存申请
        List *p = (List*)malloc(sizeof(List));
        // 节点数据赋值,并将尾指针置空
        p->data = e;
        p->next = NULL;
        // 若表非空,则将新节点添加到表尾
        if (*l != NULL) {
            (*l)->next = p;
        }
        // 更新表尾指针
        *l = p;
    }
    
    // 删除链表头节点,l为链表头指针
    void *listDeleteBegin(List **l)
    {
        void *e = NULL;
        if (*l != NULL) {
            // 取出链表头数据
            e = (*l)->data;
            // 更新链表头指针,向后移位
            *l = (*l)->next;
        }
        // 将原链表头数据返回
        return e;
    }
    
    // 清除整个链表,l为链表头指针
    void listClear(List *l)
    {
        // 清理数据域,释放内存后置空
        if (l->data != NULL) {
            free(l->data);
            l->data = NULL; 
        }
        // 若链表无后继,则表明已经清理到链表尾部
        if (l->next == NULL) {
            return;
        }
        // 递归清理l的后继
        listClear(l->next);
        // 释放l的指针域,并置空
        free(l->next);
        l->next = NULL;
    }
    
    /**************************************part2: 变量定义**********************************************/
    
    // 变量定义
    int *x;                              // 解向量:类型需要根据问题确定
    int n;                               // 解向量长度
    int elesize;                         // 解向量元素存储长度:用于数据赋值
    int flag;                            // 解标志
    
    // int (*isComplete)(void *x, int k);       // 判断完整合法解
    // void (*printSolution)(void *x, int k);   // 打印解向量
    // List (*makeItems)(int k);                // 生成第k个分量的选项表
    // int (*isPartial)(void *x, int k);        // 判断部分合法解
    
    /**************************************part3: 着色问题**********************************************/
    // m为颜色种数,G为图的邻接矩阵(按行优先)
    int m;
    int *G;
    
    // 完全解判断:k为当前解长度,n为完整解的最大长度
    int isComplete(int *x, int k)
    {
        return k >= n;
    }
    
    // 打印完整合法解:x为解向量,n为完整解长度
    void printSolution(int *x, int k)
    {
        int i;
        printf("solution: ");
        for (i = 0; i < n; i++) {
            printf("%d ", x[i]);
        }
        printf("
    
    ");
    }
    
    // 创建解向量的的第k个分量的取值集合:根据具体问题确定,着色问题取值固定,可以简化
    List *makeIterms(int k)
    {
        List *iterms = NULL;
        int i;
        // 将各节点的颜色取值,插入集合iterms,颜色固定,可以简化
        for (i = 0; i < m; i++) {
            int *e = (int *)malloc(sizeof(int));
            // 插入链表时,是从头部插入,因此颜色插入顺序为3、2、1,则链表中最终顺序为1、2、3
            // 详细分析问题后,可以简化处理
            *e = m - i;
            listPushBegin(&iterms, e);
        }
        return iterms;
    }
    
    // 判断部分合法解:
    int isPartial(int *x, int k)
    {
        int i;
        for (i = 0; i < k; i++) {
            // 根据邻接表判断两个节点之间是否相邻,再进一步判断其配色是否相同,x中按顺序保存各节点的配色
            // 按行优先邻接矩阵,针对k列,按行i遍历时,若G[i][k]==1,表示第i个节点和第j个节点相邻;若x中对应的配色相同,则不满足解要求
            if ((G[i * n + k] == 1) && (x[i] == x[k])) {
                return 0;
            }
        }
        // 遍历完全:第k个节点的着色方案x[k]满足部分解要求
        return 1;
    }
    
    /**************************************part4: 基本框架**********************************************/
    void backtrack(void(*explore)(int))
    {
        explore(0);
    
        if (!flag) {
            printf("no solution.
    ");
        }
    }
    
    void generalExplore(int k)
    {
        int i;
        // 判断当前解是否完整,若是则输出
        if (isComplete(x, k)) {
            flag = 1;
            printSolution(x, k);
            return;
        }
    
        if (k >= n) {
            return;
        }
        // 继续确定第k个节点的解
        printf("drawing k=%d
    ", k);
        // 确定第k个节点的所有解的取值
        List *iterms = makeIterms(k);
        List *q = iterms;
        while (q != NULL) {
            // 遍历取值集合,判断x[k]加入解空间后,是否满足部分解要求
            memcpy(x + k * elesize, q->data, elesize);
            if (isPartial(x, k)) {
                // 若满足要求,则继续进行确定下一个节点,递归处理
                printf("x[%d] = %d is partial
    ", k, x[k]);
                generalExplore(k + 1);
            } else {
                printf("x[%d] = %d is not partial
    ", k, x[k]);
            }
            // 遍历第k个节点的下一种取值方案,回溯的味道尽在于此
            // 本质上是,深度搜索所有解空间,然后在递归过程中,根据部分解要求及时剪枝处理;
            // 剪枝完成后回到上一层,再继续向后进行,即出现回溯;
            // 若是没有剪枝动作,兼职就是深度搜索,暴力解决
            q = q->next;
        }
    
        listClear(iterms);
        free(iterms);
        iterms = NULL;
    }
    
    /**************************************测试程序**********************************************/
    int main(int argc, char ** argv)
    {
        // 按行优先邻接矩阵
        int a[25] = {0, 1, 1, 0, 0,
                     1, 0, 0, 1, 1,
                     1, 0, 0, 1, 1,
                     0, 1, 1, 0, 1,
                     0, 1, 1, 1, 0};
        // 以下变量均为全局变量,G-邻接矩阵,n-节点数,m-颜色种类,flag-解标志,elesize-解元素大小,x-解向量
        G = a;
        n = 5; 
        m = 3;
        flag = 0;
        // elesize = sizeof(int),会出现异常,很奇怪,可能与calloc有关???
        elesize = 1;
        // calloc(元素个数,单个元素长度-字节)
         x = (int*)calloc(5, sizeof(int));
        backtrack(generalExplore);
    
        while(1);
        return (EXIT_SUCCESS);
    }
    
    

    四、测试结果

    1、测试结果

    2、回溯过程分析

    drawing k=0
    x[0] = 1 is partial
    drawing k=1
    x[1] = 1 is not partial
    x[1] = 2 is partial
    drawing k=2
    x[2] = 1 is not partial
    x[2] = 2 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is partial
    solution: 1 2 2 1 3
    
    x[3] = 2 is not partial
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is partial
    solution: 1 2 2 3 1
    
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[2] = 3 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 2 is not partial
    x[3] = 3 is not partial
    x[1] = 3 is partial
    drawing k=2
    x[2] = 1 is not partial
    x[2] = 2 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 2 is not partial
    x[3] = 3 is not partial
    x[2] = 3 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is partial
    solution: 1 3 3 1 2
    
    x[4] = 3 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is partial
    solution: 1 3 3 2 1
    
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 3 is not partial
    x[0] = 2 is partial
    drawing k=1
    x[1] = 1 is partial
    drawing k=2
    x[2] = 1 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is partial
    solution: 2 1 1 2 3
    
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is partial
    solution: 2 1 1 3 2
    
    x[4] = 3 is not partial
    x[2] = 2 is not partial
    x[2] = 3 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 3 is not partial
    x[1] = 2 is not partial
    x[1] = 3 is partial
    drawing k=2
    x[2] = 1 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 3 is not partial
    x[2] = 2 is not partial
    x[2] = 3 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is partial
    solution: 2 3 3 1 2
    
    x[4] = 3 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is partial
    solution: 2 3 3 2 1
    
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[3] = 3 is not partial
    x[0] = 3 is partial
    drawing k=1
    x[1] = 1 is partial
    drawing k=2
    x[2] = 1 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is partial
    solution: 3 1 1 2 3
    
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is partial
    solution: 3 1 1 3 2
    
    x[4] = 3 is not partial
    x[2] = 2 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is not partial
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[2] = 3 is not partial
    x[1] = 2 is partial
    drawing k=2
    x[2] = 1 is partial
    drawing k=3
    x[3] = 1 is not partial
    x[3] = 2 is not partial
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[2] = 2 is partial
    drawing k=3
    x[3] = 1 is partial
    drawing k=4
    x[4] = 1 is not partial
    x[4] = 2 is not partial
    x[4] = 3 is partial
    solution: 3 2 2 1 3
    
    x[3] = 2 is not partial
    x[3] = 3 is partial
    drawing k=4
    x[4] = 1 is partial
    solution: 3 2 2 3 1
    
    x[4] = 2 is not partial
    x[4] = 3 is not partial
    x[2] = 3 is not partial
    x[1] = 3 is not partial
    
    
    
    
  • 相关阅读:
    OUT还开通博客!
    《超越想象——Windows_8应用设计与开发》
    kissy初体验(一)
    网页乱码!!!
    一起学习extjs()
    程序员你不懂爱,博客园就要倒下来。。
    如何用WordPress做网站?
    程序员,你懂的.
    Windows 8 应用开发技术资源
    开源easyui.selectdialog
  • 原文地址:https://www.cnblogs.com/HZL2017/p/14635547.html
Copyright © 2020-2023  润新知