回溯法 -数据结构与算法

1.回溯法算法思想:

 

定义:

        回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

1、回溯法适用:有许多问题,当需要找出它的解集(全部解)或者要求回答什么解是满足某些约束条件的最优解时,往往要使用回溯法。

2、有组织的穷举式搜索:回溯法的基本做法是搜索或者有的组织穷尽搜索。它能避免搜索所有的可能性。即避免不必要的搜索。这种方法适用于解一些组合数相当大的问题。

3、搜索解空间树:回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含(剪枝过程),则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

为了实现回溯,我们先弄明白以下两个问题:

1)首先应该明确问题的解空间。

2)其次是组织解空间以便它能用以被搜索到。

 

 

2. 问题的解空间 和空间树

 

        这个空间必须至少包含一个解(可能是最优的)。 一个复杂问题的解决往往由多部分构成,即,一个大的解决方案可以看作是由若干个小的决策组成。很多时候它们构成一个决策序列。解决一个问题的所有可能的决策序列构成该问题的解空间。解空间中满足约束条件的决策序列称为可行解。一般说来,解任何问题都有一个目标,在约束条件下使目标值达到最大(或最小)的可行解称为该问题的最优解。在解空间中,前k项决策已经取定的所有决策序列之集称为k定子解空间。0定子解空间即是该问题的解空间。    

      问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。

    解空间的确定与我们对问题的描述有关。如何组织解空间的结构会直接影响对问题的求解效率。这是因为回溯方法的基本思想是通过搜索解空间来找到问题所要求的解。一般地,可以用一棵树来描述解空间,称为解空间树
       当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集合树。此时,解空间有个元素,遍历子集树的任何算法均需的计算时间。

如例:定和子集问题: 已知一个正实数的集合P= {W1,w2, ... Wn}和另一个正实数M.试求P的所有子集S,使得S中的数之和等于M。这个问题的解可以表

示成0/1数组{x1,x2,…,xn},依据W1是否属于S, X1分别取值1或0。故解空间中共有个元素。它的树结构是一棵完整二叉树。 

当所给的问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树,此时,解空间有个元素。遍历排列树的任何算法均需的计算时间,均需的计算时间。

我们把这个例子逐一解析:

问题的解向量:问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。

显约束:对分量xi的取值限定。

隐约束:为满足问题的解而对不同分量之间施加的约束。

解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。

注意:同一个问题可以有多种表示,有些表示方法更简单,所需表示的状态空间更小(存储量少,搜索方法简单)。

下面是n=3时的0-1背包问题用完全二叉树表示的解空间:

 

       为了叙述方便,引进一些关于解空间树结构的术语。解空间树上的每个节点确定求解问题的一个问题状态,它由一条从根到该节点的路径描述。由根到所有其它节点的路径描述了这个问题的状态空间。解状态是这样一些问题状态S,对于这些问题状态,由根到S的那条路径确定了解空间的一个元组。即答案状态是这样的一些解状态S,对于这些解状态而言,由根到S的这条路径确定了这个问题的一个解(即可行解),解空间的树结构称为状态空间树。

      确定了解空间的组织结构后,回溯法就从初始节点(解空间树的根节点)出发,以深度优先的方式搜索整个解空间。这个开始节点就成为一个活节点,同时也成为当前的扩展节点。在当前扩展节点处,搜索向纵深方向移至一个新节点。这个新节点就成为一个新的活节点,并且成为当前的扩展节点。如果在当前的扩展节点处不能再向纵深方向搜索,则当前的扩展节点就成为死节点。此时应往回移动(回溯)至最近一个活节点处,并使这个活节点成为当前扩展节点。如此继续。回溯法就是以这种工作方式递归地在解空间中搜索,直至找到要求的解或解空间中已无活节点时为止。 

       事实上,当我们将问题的有关数据以一定的数据结构存储好以后(例如,旅行商问题存储赋权图的邻接矩阵、定和子集问题是存储已知的n+1个数、4皇后问题用整数对(i,j)表示棋盘上各个位置,不必先建立一个解空间树),就搜索生成解空间树的一部分或全部,并寻找所需要的解。也就是说,对于实际问题不必生成整个状态空间树,然后在整个解空间中搜索,我们只需有选择地搜索。为了使搜索更加有效,常常在搜索过程中加一些判断以决定搜索是否该终止或改变路线。通常采用两种策略来避免无效的搜索,提高回溯法的搜索效率:

其一是使用约束函数,在扩展节点处剪去不满足约束的子树;

其二是用限界函数, “剪去”不能达到最优解的子树。

这两种函数统称为剪枝函数。

总结:

 

扩展结点:一个正在产生儿子的结点称为扩展结点

活结点:一个自身已生成但其儿子还没有全部生成的节点称做活结点

死结点:一个所有儿子已经产生的结点称做死结点

深度优先的问题状态生成法:如果对一个扩展结点R,一旦产生了它的一个儿子C,就把C当做新的扩展结点。在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展结点,继续生成R的下一个儿子(如果存在)

宽度优先的问题状态生成法:在一个扩展结点变成死结点之前,它一直是扩展结点。

回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function)来处死(剪枝)那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。(回溯法 = 穷举 + 剪枝)。

 

 

 

3.回溯法的思路

 

描述问题:

定义可用回溯法求解的问题P:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。

基本思路:

若已有满足约束条件的部分解,不妨设为(x1,x2,x3,……xi),I<n,则添加x(i+1)属于s(i+2),检查 (x1,x2,……,xi,x(i+1))是否满足条件,满足了就继续添加x(i+2)、s(i+2),若所有的x(i+1)属于s(i+1)都不能得到 部分解,就去掉xi,回溯到(xi,x2,……x(i- 1)),添加那些未考察过的x1属于s1,看其是否满足约束条件,为此反复进行,直至得到解或证明无解。

这个回溯法明显提高算法效率。

 

4.回溯法的步骤

 

总结起来,运用回溯法解题通常包括以下三个步骤 
1).确定问题的解空间 :针对所给问题,定义问题的解空间; 

 子集树问题:装载问题、符号三角形问题、0-1背包问题、最大团问题
排列树问题:批处理作业调度、n后问题、旅行售货员问题、圆排列问题、电路板排列问题
其他:图的m着色问题

2).确定易于搜索的解空间结构:

找出适当的剪枝函数,约束函数和限界函数。

3).以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效的搜索。

递归回溯

迭代回溯

4)利用限界函数避免移动到不可能产生解的子空间

 

三. 

5.算法框架

 1. 递归回溯:

回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。

void backtracking (int t)

{

    if (t > n) {

       // 到达叶子结点,将结果输出

       output (x);

    }

    else {

       // 遍历结点t的所有子结点,即枚举t所有可能的路径   

      // f(n,t)=下界;g(n,t)=上界;

       for (int i = f(n,t); i <= g(n,t); i ++ ) {//

           x[t] = h[i];//满足界限函数和约束函数

           // 如果不满足剪枝条件,则继续遍历,进入下一层

           if (constraint (t) && bound (t)) 

              backtrack (t + 1);

       }

    }

}

t是递归深度;
n是深度控制,即解空间树的的高度;
可行性判断有两方面的内容:不满约束条件则剪去相应子树;若限界函数越界,也剪去相应子树;两者均满足则进入下一层;

2. 迭代回溯

 

采用树的非递归深度优先遍历算法,可将回溯法表示为一个非递归迭代过程。

// 针对N叉树的迭代回溯方法

void iterativeBacktrack ()

{

    int t = 1;

    while (t > 0) { //有路可走

       if (f(n,t) <= g(n,t)) {

           //  遍历结点t的所有子结点

           for (int i = f(n,t); i <= g(n,t); i ++) {

              x[t] = h(i);

              // 剪枝

              if (constraint(t) && bound(t)) {

                  // 找到问题的解,输出结果

                  if (solution(t)) {

                     output(x);

                  }

                  else // 未找到,向更深层次遍历

                     t ++;

              }

           }

       }

       else {

           t--;

       }

    }

}

 

 

 

6. 回溯法依赖的两种数据结构

回溯法通常在解空间树上进行搜索,一般依赖的两种数据结构:子集树和排列树

子集树(遍历子集树需O(2^n)计算时间):

一般有装载问题、符号三角形问题、0-1背包问题、最大团问题

void backtrack (int t)

{

    if (t > n)

       // 到达叶子结点

       output (x);

    else

       for (int i = 0;i <= 1;i ++) {

           x[t] = i;

           // 约束函数

           if ( legal(t) )

              backtrack( t+1 );

       }

}

排列树(遍历排列树需要O(n!)计算时间):

一般有批处理作业调度、n后问题、旅行售货员问题、圆排列问题、电路板排列问题


 

void backtrack (int t)

 

{

    if (t > n)

       output(x);

    else

       for (int i = t;i <= n;i++) {

           // 完成全排列

           swap(x[t], x[i]);

           if (legal(t))

              backtrack(t+1);

           swap(x[t], x[i]);

       }

 

}

其中f(n,t),g(n,t)表示当前扩展结点处未搜索过的子树的起始标号和终止标号, h(i)表示当前扩展节点处,x[t]第i个可选值constraint(t)和bound(t)是当前 扩展结点处的约束函数和限界函数。constraint(t)返回true时,在当前扩展结点 x[1:t]取值满足约束条件,否则不满足约束条件,可减去相应的子树。bound(t)返 回的值为true时,在当前扩展结点x[1:x]处取值未使目标函数越界,还需要由backtrack(t+1) 对其相应的子树进一步搜索。

 

7.回溯法的应用

 

应用回溯法有:

  • 1)装载问题
  • 2)批处理作业调度
  • 3)符号三角形问题
  • 4)n后问题
  • 5)0-1背包问题
  • 6)最大团问题
  • 7)图的m着色问题
  • 8)旅行售货员问题
  • 9)圆排列问题
  • 10)电路板排列问题
  • 11)连续邮资问题

 

n皇后问题:

1.问题表述:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。求不同的解的个数。

复杂问题从简单问题入手,我们先分析四皇后的问题,四叉树展示了求解的过程:

 

2. 问题分析: 

(1) 解空间:一组n元一维向量(x1, x2, x3, ... , xn),搜索空间是:1<=xi<=n, i=1,2,3,...,n

(2) 约束条件:

1)不同列:xi != xj

2)不处于同一正、反对角线:|i-j| != |x(i)-x(j)|

 

3. 代码实现:

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//  

#pragma once  

#include <stdio.h>
#include "stdlib.h"
#include <iostream>
using namespace std;  

//宏定义
#define TRUE   1
#define FALSE   0
#define OK    1
#define ERROR   0
#define INFEASIBLE -1
#define OVERFLOW -2    

typedef int Status;
typedef int ElemType;   
// Test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"    

#include <vector>  

class queen
{
    // 皇后在棋盘上的位置
    struct q_place {
        int x;
        int y;
        q_place ()
            : x(0),y(0)
        {}
    };  

public:
    queen(int qc)
        : q_count (qc), sum_solution (0) {
            curr_solution.resize (q_count);
    }  

    void backtrack () {
        _backtracking (0);
    }  

private:
    /************************************************************************/
    /*  判断对应的位置是否存在当前的方案中
    */
    /************************************************************************/
    bool _isCoordinate(int x, int y)
    {
        for (size_t i = 0;i < curr_solution.size(); ++ i) {
            if (curr_solution[i].x ==x &&  curr_solution[i].y == y) {
                return true;
            }
        }
        return false;
    }
    /************************************************************************/
    /*  打印当前的位置
    */
    /************************************************************************/
    void _printResult()
    {
        for (size_t i = 0;i < curr_solution.size(); ++ i) {
            for(size_t j = 0;j < curr_solution.size(); ++j) {
                if (_isCoordinate(i, j)) {
                    cout<<"1 ";
                }else{
                    cout<<"0 ";
                }
            }
            cout<< endl;  

        }
        cout << "sum_solution = " << sum_solution << endl;
    }  

    /************************************************************************/
    /*  现在从第i行算起继续为后续的棋子选择合适的位置
    */
    /************************************************************************/
    void _backtracking (int i)
    {
        if (i >= q_count) { //找到一个解决方案,将结果输出
            ++ sum_solution ;
            _printResult();
        }
        else {
            for (int j = 0;j < q_count; ++ j) {
                //将第i行第j列放置一个棋子
                curr_solution[i].x = j;
                curr_solution[i].y = i;
                if (isOk(i)) { //当前布局合法
                    _backtracking (i + 1);
                }
            }
        }
    }  

    /************************************************************************/
    /*  判断第k个皇后的位置是否与前面的皇后相冲突
    */
    /************************************************************************/
    bool isOk(int k)
    {
        for (int i = 0; i < k; ++ i) {
            if ((abs(curr_solution[i].x - curr_solution[k].x) == abs(curr_solution[i].y - curr_solution[k].y))
                || curr_solution[i].x == curr_solution[k].x) {
                    return false;
            }
        }
        return true;
    }  

private:
    vector<q_place> curr_solution;    // 当前解决方案
    const int q_count;              // 皇后个数
    int sum_solution;               // 当前找到的解决方案的个数
};  

int main()
{
    queen q(5);
    q.backtrack ();
    return 0;
}  

定和0/1背包问题

问题表述:给定n种物品和一背包。第i件物品的重量是wi,其价值为pi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?  

0-1背包问题是一个数规划问题:确定一个向量:x=(x1,x2,...,xn)满足:

例如:n=3但是时候:

W = (10, 8,5)

p = (5,5,1)

C = 16;

最优解为:(1,01),此时价值为:6

0/1背包问题用完全二叉树表示的解空间:

 

 

 

 

问题分析:

 

(1) 解空间:一组n元一维向量(x1, x2, x3, ... , xn),搜索空间是:1<=xi<=n, i=1,2,3,...,n

 

(2) 约束条件:

 可行性约束函数:

 

 

 

上界函数:
考虑一个右子树的时候,设
r:是当前未考虑的剩余物品的总价值(remainder)
cp:是当前的价值(current price)
bestp:是当前得到的最优价值(best price)

 

 

那么,满足:

但是,上界r太松。
一个更加紧的上界:
将剩余物品按照单位重量价值排序,然后依次装入物品,直到装不下,再将剩余物品的一部分放入背包。(r_n  <=  r)

 

c语言实现:

// TestWin32.cpp : Defines the entry point for the console application.
//  

#include "stdafx.h"  

int curr_weight = 0;        //当前重量
int curr_value = 0;     //当前价值
int bestv = 0;          //最优解
int x_length = 0;           //
/************************************************************************/
/*  将物品按单位价格降序排序                                                */
/************************************************************************/
void sortItem(itemGoods *item, int n){
    itemGoods temp;
    for(int i = 0; i < n-1; ++i){
        for(int j = i+1; j < n; ++j){
            if((item[i].v/item[i].w) < (item[j].v/item[j].w)){
                temp = item[i];
                item[i] = item[j];
                item[j] = temp;
            }
        }
    }
}
/************************************************************************/
/*  边界函数 : 计算上界
@int C, 背包容量
@int i     第i个物品
@int n     物品个数
*/
/************************************************************************/
int bound(itemGoods *item, int capacity, int i, int n){
    int capacity_left = capacity - curr_weight;
    int value_left = curr_value;
    // 按物品单位价值递减序装入物品
    while(i <= n && item[i].w <= capacity_left){
        capacity_left -= item[i].w;
        value_left += item[i].v;
        ++i;
    }
    //装满背包
    if(i <= n)
        value_left += item[i].v * capacity_left / item[i].w;
    return value_left;
}
/************************************************************************/
/* 递归回溯
@int capacity,  背包容量
@int i              第i个物品
@int n              物品个数
*/
/************************************************************************/
void backtrack(itemGoods *item, int capacity, int i, int n, int *bestX){
    if(i >= n){
        //到达叶子结点,更新最优价值
        if(bestv < curr_value){
            bestv = curr_value;
            x_length = 0;
            for(int i = 0; i < n; ++i)
                if(item[i].visited){
                    bestX[x_length] = item[i].id;
                    ++x_length;
                }
        }
        return;
    }
    //搜索左子树:左剪枝,能放的下的物品
    if(curr_weight + item[i].w <= capacity){
        curr_weight += item[i].w;
        curr_value += item[i].v;
        item[i].visited = true;
        backtrack(item,capacity,i+1,n,bestX);
        curr_weight -= item[i].w;
        curr_value -= item[i].v;  

    }
    //搜索右子树:放不下的物品
    if(bound(item,capacity,i,n) > bestv)
        item[i].visited = false;
        backtrack(item,capacity,i+1,n,bestX);
}
int Knapsack(itemGoods *item, int n, int capacity, int *bestX){
    sortItem(item,n);
    backtrack(item,capacity,0,n,bestX);
    return bestv;
}
void initGoods(itemGoods *item,int n){  

    cout << "物品信息:" << endl;
    for(int i = 0; i < n; ++i){
        item[i].id = i;
        item[i].visited = false;
        cout << "物品" <<i<<"重量:";
        cin >> item[i].w;
        cout << "物品" <<i<<"价值:";
        cin >> item[i].v;
    }  

}
void printKnapsack(int *bestX, int max_value){
    cout << "背包的物品id:" << endl;
    for(int i = 0; i < x_length; ++i)
        cout << bestX[i]+1 << "\t";
    cout << endl;
    cout << "最大价值: " << max_value << endl;  

}
int main(){
    int n;
    cout << "物品数量:" << endl;
    cin >> n;
    int capacity;
    cout << "背包容量:" << endl;
    cin >> capacity;
    itemGoods *item = new itemGoods[n];
    initGoods(item, n);
    int *bestX = new int[n];  //当前最优解
    int max_value = Knapsack(item,n,capacity, bestX);
    printKnapsack(bestX, max_value);
    return 0;
}  

C++

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//  

#pragma once  

#include "targetver.h"
#include <stdio.h>
#include "stdlib.h"
#include <iostream>
using namespace std;  

//宏定义
#define TRUE   1
#define FALSE   0
#define OK    1
#define ERROR   0
#define INFEASIBLE -1
#define OVERFLOW -2    

typedef int Status   ;
typedef int ElemType ;  

typedef struct itemGoods{
    int id;
    bool visited;
    int w;
    int v;
}itemGoods ;  

class knapsack{
private:
    itemGoods *item ;
    int capacity;   //背包容量
    int n;          //物品数
    int curr_weight;//当前重量
    int curr_value; //当前价值
    Status bestV;   //当前最优值
    int *bestX;     //当前最优解
    int x_length;   //最优解的数量
private:  

    void _sortItem();
    int  _bound(int i);
    void _backtrack(int i); //递归回溯函数  

public:
    knapsack (itemGoods *item, int c,int n)
    :capacity(c),   n(n), curr_value(0), bestV (0), curr_weight(0),x_length(0),item(item)
    {
        bestX = new int[n];
        bestX[0]=0;
    }
    int backtrack () ;
    void printKnapsack();
};  

Test.cpp

// Test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"   

/************************************************************************/
/*  边界函数 : 计算上界
*/
/************************************************************************/
int knapsack::_bound(int i)
{
    //计算上界
    int cleft = capacity - curr_weight;
    int value_left = curr_value;
    //以物品单位重量价值递减序装入物品
    while(i < n && item[i].w <= cleft) {
        cleft         -= item[i].w;
        value_left    += item[i].v;
        i++;
    }
    //装满背包
    if(i< n)
        value_left += item[i].v/item[i].w * cleft;
    return value_left;  

}  

/************************************************************************/
/*    递归回溯
*/
/************************************************************************/
void knapsack::_backtrack(int i)
{
    if(i>=n) {
        if(bestV < curr_value) {
            bestV = curr_value;
            x_length = 0;
            for(int j = 0;j < n;j++)
                if(item[j].visited) {
                    bestX[j] = item[j].id;
                    ++x_length;
                }
        }
        return;
    }
    if(curr_weight + item[i].w <= capacity)  {  //搜索左子树
        item[i].visited = TRUE;
        curr_weight += item[i].w;
        curr_value  += item[i].v;
        _backtrack(i+1);
        curr_weight -= item[i].w;
        curr_value  -= item[i].v;
    }
    if(_bound(i+1)>bestV) { //搜索右子树
        item[i].visited = FALSE;
        _backtrack(i+1);
    }
}  

/************************************************************************/
/*  排序  :将物品按单位价格降序排序
*/
/************************************************************************/
void knapsack::_sortItem(){
    itemGoods temp;
    for(int i = 0; i < n-1; ++i){
        for(int j = i+1; j < n; ++j){
            if((item[i].v/item[i].w) < (item[j].v/item[j].w)){
                temp = item[i];
                item[i] = item[j];
                item[j] = temp;
            }
        }
    }
}
int knapsack::backtrack () {
    _sortItem();
    _backtrack(0);
    return  bestV;
}  

void knapsack::printKnapsack(){
    cout << "背包的物品id:" << endl;
    for(int i = 0; i < x_length; ++i)
            cout << bestX[ i] << "\t";
    cout << endl;
    cout << "最大价值: " << bestV << endl;  

}  

int main(){
    int n = 3;
    cout << "物品数量:" << endl;
    //cin >> n;
    int capacity = 5;
    cout << "背包容量:" << endl;
    //cin >> capacity;
    itemGoods *item = new itemGoods[n];
    //初始化物品
    //cout << "物品信息:" << endl;
    //for(int i = 0; i < n; ++i){
    //  item[i].id = i;
    //  item[i].visited = FALSE;
    //  cout << "物品" <<i<<"重量:";
    //  cin >> item[i].w;
    //  cout << "物品" <<i<<"价值:";
    //  cin >> item[i].v;
    //}
    item[0].id = 0;
    item[0].visited = FALSE;
    item[0].w =2;
    item[0].v = 2;  

    item[1].id = 1;
    item[1].visited = FALSE;
    item[1].w = 2;
    item[1].v = 2;  

    item[2].id = 2;
    item[2].visited = FALSE;
    item[2].w  =4;
    item[2].v = 10;  

    knapsack ks(item,capacity,n);
    int max_value = ks.backtrack();
    ks.printKnapsack();
    return 0;  

}  

 

时间: 2024-09-20 17:49:19

回溯法 -数据结构与算法的相关文章

读书时间--回溯法浅析:逆向思维领略算法之美

下面将使用回溯思想解决若干经典问题并通过它们来说明使用回溯的基本思路 什么叫回溯法 回溯是一种比较简单.比较常用的搜索策略. 它的基本思想是假设某问题的解决步骤可能有N步,且每一步的解决方法又可能有M种,那么就按照某种顺序依次试探每一步中的各种方法,一旦某一步的所有方法都失效,那么就返回上一步继续试探上一步骤的其他M−1种方法.简而言之就是从一条路往前走,能进则进,不能进则退回来,换一条路再试. 通常用回溯法解决问题的一般步骤为:首先,定义一个解空间,它包含问题的解,也就是每一步所采用的各种方法

五大常用算法——分治法,动态规划,回溯法,分支界限法,贪心算法

分治算法一.基本概念    在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)--     任何一个可以用计算机求解的问题所需的计算时间都与其规模有关.问题的规模越小,越容易直接求解,解题所需的计算时间也越少.例如,对于n个元素

常用算法:C#用回溯法找出n个自然数中取r个数的全排列

回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验.在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯. 本实例是用回溯法输出n个自然数中以r个数全排列.代码如下: <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />public void Arrange(int n, int r) int i = 0, j; st

优化-01背包回溯法计算起来非常慢,有木有算法大大帮忙看看

问题描述 01背包回溯法计算起来非常慢,有木有算法大大帮忙看看 #include #include #include #include #include #include using namespace std; int n;//物品数量 double c;//背包容量 double v[9000];//各个物品的价值 double w[9000];//各个物品的重量 double cw = 0.0;//当前背包重量 double cp = 0.0;//当前背包中物品价值 double best

算法——回溯法

回溯法 回溯法有"通用的解题法"之称.用它可以系统地搜索一个问题的所有解或任一解.回溯法是一种即带有系统性又带有跳跃性的搜索算法.它在问题的解空间树中,按深度优先策略,从根节点出发搜索解空间树.算法搜索至解空间树的任一结点时,先判断该节点是否包含问题的解.如果不包含,则跳过对以该节点为根的子树的搜索,逐层向其它祖先节点回溯.否则,进入该子树,继续按照深度优先策略搜索.回溯法求问题的所有解时,要回溯到根,且根节点的所有子树都已被搜索遍才结束.回溯法求问题的一个解时,只要搜索到问题的一个解

算法详解之回溯法具体实现_C 语言

理论辅助: 回溯算法也叫试探法,它是一种系统地搜索问题的解的方法.回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试.用回溯算法解决问题的一般步骤为: 1.定义一个解空间,它包含问题的解. 2.利用适于搜索的方法组织解空间. 3.利用深度优先法搜索解空间. 4.利用限界函数避免移动到不可能产生解的子空间. 问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性. 还是那个基调,不喜欢纯理论的东西,喜欢使用例子来讲诉理论,在算法系列总结:动态规划(

《数据结构与算法:Python语言描述》一1.3算法和算法分析

1.3算法和算法分析 本节集中讨论算法的问题,特别是算法的性质及其分析技术. 1.3.1问题.问题实例和算法 在考虑计算问题时,需要清晰地区分问题.问题实例和算法三个概念,并理解它们之间的关系,这就是本小节讨论的内容.三个基本概念考虑一个计算问题时,需要注意到三个重要概念:问题:一个问题W是需要解决(需要用计算求解)的一个具体需求.例如判断任一个正整数N是否为素数,求任一个方形矩阵的行列式的值等.虽然可以严格定义"问题"的概念,但在这里还是想依靠读者的直观认识.总而言之,现实世界中存在

数据结构与算法系列(2)基础排序算法

前言 在计算机中实现存储数据最普遍的两种操作就是排序和查找.这是从计算机产业初始就已经确认的 了.这意味着排序和查找也是计算机科学领域最值得研究的两种操作.本书提到的许 多数据结构的主要设计目的就是为了使排序和/或查找更加简单,同时也是为了数据在结构内的存 储更加有效. 本章会介绍有关数据排序和查找的基础算法.这些算法仅依赖数组作为数据结构,而且所采用的 "高级"编程技术只是递归.本章还介绍了用来非正式分析不同算法之间速度与效率的方 法,此方法贯穿全书. 1.排序算法 人们在日常生活中

数据结构与算法系列(1)时间测试

前言 好久都把数据结构和算法的东西忘完了,最近想重温下这些知识.因此就写了<<数据结构与 算法系列>的文章,希望能和大家共同学习,共同探讨这方面的知识.希望大家多多指异. 1.时间测试 由于本部分内容采用了一种实用的方法来分析数据结构与算法检测,所以在这里避开了使用大O分 析法,而采用运行简单基准测试的方法来代替.这种测试将会说明运行一段代码需要多少秒数(或者 无论什么时间单位). 基准法测试是使用时间测试的方法来衡量运行完整算法所花费的时间长度.如同科学一样,基准测 试也像是一门艺术,