【经典算法】回朔法

  回溯是遍历所有搜索空间所有可能组态的方法。这些组态也许代表对象的所有排列或这是构建对象集合的所有可能的方法(子集)。其他情况包括列举一个图的所有生成树,两个节点的所有路径或是把节点分类成不同颜色的所有不同的方式。

  这些问题有一个共同的难点就是我们必须每次产生一个可能的组态。避免重复或遗漏组态的方法就是我们必须定义一个系统性的产生组态的顺序。我们把组合搜索解作为一个向量a=(a1,a2,...,an),向量元素ai来自有限子集S。这样的向量也许表示一个序列,元素ai是排列的第i个元素。或者,向量表示的是一个子集S,ai代表空间中的第i个元素是否在子集S中。

  在回溯算法的每一步,我们都试图通过在末尾添加其他元素来扩展一个给定的部分解a=(a1,a2,...,ak)。在扩展之后,我们必须检验目前的部分解是不是符合条件的完整解:如果是,我们应该输出或将解的数量加一(计数)。如果不是完整解,那么我们应该验证这个部分解有没有可能扩扩展成为完整解。

  回溯算法构造了一颗部分解的树,树中每一个节点代表了部分解。如果节点y是从节点x构造而来,那么x与y之间就存在一条边。这棵树为思考回溯算法提供了另一个角度,构造解的过程实际上就是在这棵树上进行深度优先搜索遍历。回溯算法的结构如下所示:

 1 bool finished = FALSE; /* 是否获得全部解? */
 2 backtrack(int a[], int k, data input)
 3 {
 4     int c[MAXCANDIDATES]; /*这次搜索的候选 */
 5     int ncandidates; /* 候选数目 */
 6     int i; /* counter */
 7     if (is_a_solution(a,k,input))
 8     process_solution(a,k,input);
 9     else {
10         k = k+1;
11         construct_candidates(a,k,input,c,&ncandidates);
12         for (i=0; i<ncandidates; i++) {
13             a[k] = c[i];
14             make_move(a,k,input);
15             backtrack(a,k,input);
16             unmake_move(a,k,input);
17             if (finished) return; /* 如果符合终止条件就提前退出 */
18         }
19     }
20 }

  这个算法的应用程序部分包括5个子程序:

  1  is a solution(a,k,input)  -这个布尔函数验证向量a的前k个元素能否构成一个给定问题的完整解。最后一个参数,input,允许我们像函数中传递其他必要的信息。我们可以用它指定n——目标解的大小。

  2  construct candidates(a,k,input,c,ncandidates)  根据目前状态,构造这一步可能的选择,存入c[]数组,其长度存入ncandidates

  3  process_solution(a,k,input)  对于符合条件的解进行处理,通常是输出、计数等

   4  make_move(a,k,input)和unmake_move(a,k,input)  前者将采取的选择更新到原始数据结构上,后者把这一行为撤销。

  

求一个集合的所有子集

  一个关键问题是,当指定表示组合对象的状态空间时,这个空间需要表示多少对象。比如,对于序列{1,...,n}这样一个n元素集合,有多少个子集存在呢?当n=1时,只有两个子集存在,即{}和{1}。当n=2时,有4个子集存在,当n=3时有8个子集。所以对于n,有2n个子集存在。

  每一个子集可以用元素是否在其中来表示。为了构造所有2n个集合,我们建立一个n元素的集合,其中ai的值表示第i个元素是否在给定的子集中。在一般的回溯算法机制中,Sk=(true, false)并且只要k=n时a就是一个解。现在我们可以通过简单的实现is_a_solution(), construct_candidates()以及process_solution().

由于每次for循环中a[k]=c[i],这是唯一的改动,并且在下次循环时会被覆盖,不需要专门编写make_move()和make_unmove()。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define MAXCANDIDATES   100

bool is_a_solution(int *a, int k, int n) {
    return k == n;
}

void process_solution(int* a, int k, int n) {
    int i;

    printf("{");

    for(i = 1; i <= k; ++i) {
        if (a[i] == true) printf("%d", i);
    }

    printf("}\n");
}

void construct_candidate(int a[], int k, int n, int c[], int *ncandidates) {
    c[0] = false;
    c[1] = true;
    *ncandidates = 2;
}

void backtrack(int* a, int k, int n) {
    int c[MAXCANDIDATES];
    int ncandidates;
    int i;

    if (is_a_solution(a, k, n)) {
        process_solution(a, k ,n);
    } else {
        k = k + 1;
        construct_candidate(a, k, n, c, &ncandidates);
        for (i = 0; i < ncandidates; i++) {
            a[k] = c[i];
            backtrack(a, k, n);
        }
    }
}

void generate_subsets(int n) {
    int *a = (int*)malloc(sizeof(int)*n);
    memset(a, 0, sizeof(int) * n);
    for(int i = 0; i < n; i++)
        a[i] = i + 1;

    backtrack(a, 0, n);
}

int main()
{
    generate_subsets(3);

    return 0;
}

参考资料:

  1.  《算法设计手册》  

  2.  http://www.cnblogs.com/wuyuegb2312/p/3273337.html#intro

时间: 2024-10-01 02:57:28

【经典算法】回朔法的相关文章

回朔法/KMP算法-查找字符串

回朔法:在字符串查找的时候最容易想到的是暴力查找,也就是回朔法.其思路是将要寻找的串的每个字符取出,然后按顺序在源串中查找,如果找到则返回true,否则源串索引向后移动一位,再重复查找,直到找到返回true,或者源串查找完也没有找到返回false:这种方法简单粗暴,但思路清晰.又因为每次查找失败,源串需要回到起始位置的下一个位置开始查找,所以该算法也称为回朔法. KMP算法:先对要查找的字符串进行分析,找出其中重复的子字符串.然后将目标串与源串比较,一旦比较失败,则不用回朔,而是根据目标串子串的

回朔法求数独

你一定听说过"数独"游戏. 如[图1.png],玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行.每一列.每一个同色九宫内的数字均含1-9,不重复. 数独的答案都是唯一的,所以,多个解也称为无解. 本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目.但对会使用计算机编程的你来说,恐怕易如反掌了. 本题的要求就是输入数独题目,程序输出数独的唯一解.我们保证所有已知数据的格式都是合法的,并且题目有唯一的解. 格式要求,输入9行,每行9个数字,0代表未知

五大经典算法之回溯法

一.基本概念 ??回溯法,又称为试探法,按选优条件向前不断搜索,以达到目标.但是当探索到某一步时,如果发现原先选择并不优或达不到目标,就会退回一步重新选择,这种达不到目的就退回再走的算法称为回溯法. 与穷举法的区别和联系: 相同点:它们都是基于试探的. 区别:穷举法要将一个解的各个部分全部生成后,才检查是否满足条件,若不满足,则直接放弃该完整解,然后再尝试另一个可能的完整解,它并没有沿着一个可能的完整解的各个部分逐步回退生成解的过程.而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某

翻牌回朔法解题

#include <cstdlib> #include <iostream> using namespace std; class card{ private: int a[5][5];//原题 int b[5][5];//解决方案 int c[5][5];//桥 int s[5][5];//结果串 int counts;//解数目 public: card (char x[25]); void Backtrack (int t); bool Constraint(int t);

递归之回朔算法应用----八皇后问题

从前,有个皇帝,取了八个皇后,由此产生一系列乱七八糟的问题,八皇后问题由此产生.哈哈 开个玩笑~~~~ 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 高斯认为有76种方案.1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果.计算机发明后,有多种方法可以解决此问题.

回文数系列题目(经典算法)

回文数 时间限制:1000 ms  |  内存限制:65535 KB 难度:0 描述 请寻找并输出1至1000000之间的数m,它满足m.m^2和m^3均为回文数.回文数大家都知道吧,就是各位数字左右对称的整数,例如121.676.123321等.满足上述条件的数如m=11,m^2=121,m^3=1331皆为回文数. 输入 没有输入 输出 输出1至1000000之间满足要求的全部回文数,每两个数之间用空格隔开,每行输出五个数 解析:这道题直接模拟就好了,算是回文数中最简单的题了,直接写个判断回

【转】聚类分析经典算法讲解及实现

本文将系统的讲解数据挖掘领域的经典聚类算法,并给予代码实现示例.虽然当下已有很多平台都集成了数据挖掘领域的经典算法模块,但笔者认为要深入理解算法的核心,剖析算法的执行过程,那么通过代码的实现及运行结果来进行算法的验证,这样的过程是很有必要的.因此本文,将有助于读者对经典聚类算法的深入学习与理解. 4 评论 杨 翔宇, 资深软件工程师, IBM 段 伟玮, 在读博士, IBM 2016 年 7 月 18 日 内容 在 IBM Bluemix 云平台上开发并部署您的下一个应用. 开始您的试用 前言

经典算法大全

原文地址:经典算法大全 作者:liurhyme 经                                                                    典                                                                    算                                                                    法                  

Java经典算法案例

笔试中的编程题3 JAVA经典算法40例[程序1] 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 1.程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21.... public class exp2{public static void main(String args[]){int i=0;for(i=1;i<=20;i++)System.out.println(f(i));}pu