小田田的算法笔记——回溯法

回溯法

背景
寻找问题的解比较直观的方法是穷举法,但是有时候候选解的数量非常大,通常是指数级别,甚至是阶乘级别的,即使使用最快的计算机,也只能解决规模很小的问题。因此需要一种系统化检查候选解的方法,借助这种方法,将搜索空间减少到最低的程度。这种系统并有组织的搜索方法,称为回溯法。

基本思想
(1)针对所给问题,定义问题的解空间。
(2)确定易于搜索的解空间结构。
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

|| 图的三着色问题

问题描述:
给出一个无向图G = (V,E),需要三种颜色之一为图中每个结点着色,三种颜色分别用1、2、3来表示。若没有两个邻接节点有相同的颜色,则称着色是不合法的。具有n个结点的图的一种着色,可以用向量(c1,c2,…cn)来表示。

在这里插入图片描述

递归算法-ColorRec
输入:无向图G = (V,E)
输出:图的结点3着色向量 c[1...n] , 1 <=c[k]<= 3
      1. GraphColorRec (k)
过程 GraphColorRec (k)
for color<-1 to 3
     c[k]<-color
	 if c[1...k]为合法着色 then //部分解或解 
	     if k=n then
		    output c[1...n] and exit //解 
		 else GraphColorRec(k+1)  //进入下一个结点 
		 end if
	 end if
end for

//

非递归算法
c[1...n]<-0
k<-1
while k > 0
   while c[k] < 3
      c[k]<-c[k]+1
	  if c[1...n]为合法着色 then
	      if k=n then 
		    output c[1...n] and exit
		  else k<-k+1
		  end if
	  end if
	end while //试完三种颜色后回退 
	c[k]<-0 , k<-k-1
end while 

下面展示 合法性检验算法

建立邻接矩阵A[][]
bool CheckColor()
{
	for(int i=1;i<=k-1;i++)
	{
		if(A[k][i]==1&&c[i]==c[k])
		  return false;
	}
	return true;
}

|| 八皇后问题

问题描述:
在8*8的国际象棋棋盘上安排8个皇后,使得没有两个皇后相互攻击。也就是说,没有两个皇后处于同一行、同一列或同一对角线上。为了简化讨论,我们先讨论4皇后问题。在此基础上,可简单地将研究结果推广到n为任意值的情况。

穷举法
for c[1]=1 to 4
  for c[2]=1 to 4
    for c[3]=1 to 4
	  for c[4]=1 to 4
	     if c[1..4]是解 then
		   output c[1..4]and exit
		  end if
	  end for
	end for
  end for
end for

下面展示 回溯法

递归解
输入:空
输出:对应于4皇后问题的向量c[1..4]
    1.advanced(1)
过程advanced(k)
for col=1 to 4
    c[k]=col
	if c[1..k]是解 then
	   if k=4 then
	      output c[1..4] and exit
	   else advanced(k+1)
	   end if
	end if
end for

//

非递归
c[1..4]=0
k=1
while k>0
     while c[k]<4
	    c[k]=c[k]+1
	    if c[1..k]是解 then
		   if k=4 then
		      output c[1..4]and exit
		   else k=k+1
		   end if 
		end if
	 end while
	 c[k]=0
	 k=k-1  //清零后退 
end while

下面展示判断冲突函数


//检查放置第k个皇后时是否产生冲突
bool check(int c[],int k)
{
	if(k===1) return true;
    for(int i=1;i<k;i++) //同列 
	{
	    if(c[i]==c[k]) return true;
	}
	for(int i=1;i<k;i++) //同一对角线 
	{
		if( (k-i)==fabs(c[k]-c[i]) )
		   return false;
	}
	return true;
} 

|| 实战演练

例 1:现在有一个候选数组C,一个目标值T,从C中选取部分数字(不可重复)使其和为T,使用回溯法求所有可能的情形,给出伪代码和时间复杂度。

基本思想:

首先对数组进行排序提纯筛去重复值,再深度优先遍历数组。复杂度为O( n! )

imput: candidates target
output: result

result={} //存放所有结果
curr={} // 存放暂时解
sort and pure(candidates)
dfs(candidates,start,target,curr,result)

   1.dfs(num,0,target,curr,result)
   
if target==0
   result.add(curr) 
   return
end if // success
if start>= num.length||num[start]>target
   return;
end if // error
i = start;
while(i<num.length&&num[i]<=target) //头元素尝试n种可能
     curr.add(num[i])
     dfs(num,i+1,target-num[i],curr,result) //子树
     curr.remove(curr.size()-1) // back ,删去根
     i++ // 指向下一头元素作为根
end while

例 2: 用回溯法输出1-N(N个不重复正整数)的所有全排列(如N=3,则1 2 3;1 3 2;2 3 1;3 2 1…均为它的全排列)

(1)用自然语言描述算法思想。
(2)以N=3为例,画出问题的解空间树。
(3)给出算法的伪代码程序。

解:

(1)该题采用回溯方法,对问题进行深度优先遍历,对于当前已得的排列,判 断该排列是否已经是一个完整的全排列,如果是则输出,否则,不断检测出下一 个整数(保证不与前面的重复),以此作为下一个元素继续进行回溯遍历。

(2)
在这里插入图片描述

(3)伪代码如下

c[1...n] 存储得到的一个全排列
result{} 所有全排列的集合
    
	1. solve(k)
for num=1 to N
   c[k]=num
   if c[1...k] legal then
       if k=n then
	      result.add(c[1...n])
		else solve(k+1)
		end if
	end if
end for

例 3: 现在有n种纸币,V1, V2, V3…Vn,每种面值的纸币有3个,现在甲需要给乙T元,请利用回溯法设计一个算法,判断甲能否利用这n种纸币恰好凑齐T元。如果能凑齐,返回凑齐方法(每个面值钱币选择的个数);如果不能,返回“no solution”.

c[1...k] 存储得到的一种解 
result{} 所有解的集合 
	调用 solve(1)
solve(k)
{
	  if(k<=n)
      {
	      for i=0 to 3
             c[k] = i;
             if(c[1...k]是最终解)
                result.add(c[1...k])
	         else if(c[1...k]是部分解)  solve(k+1)
	         end if
          end for
      }
}
判断子程序
FinalSolution(c[1...k]) // 只要满足和为T就是最终解 
{
	sum = 0;
	for i=1 to k
	   sum = sum + Vi*c[i]
	end for
	if(sum = T) return true
	else return false
} 

PartialSolution(c[1...k])
{
	sum = 0
	for i=1 to k
	   sum = sum + Vi*c[i]
	end for
	if(sum<T) return true
	else return false
}


// 最后一步 如果result为 ∅ ,返回 no solution
原创文章 12 获赞 7 访问量 892

猜你喜欢

转载自blog.csdn.net/tian__si/article/details/105626706