算法笔记:DFS(基础)

专题:DFS(基础)

一、基础知识

1. 引入

搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。回溯是搜索算法中的一种控制策略。

2. 基本思想

1)为了求得问题的解,先选择某一种可能情况向前探索;

2)在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索;

3)如此反复进行,直至得到解或证明无解。

    举例:迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。

二、典例

1. 素数环

给定一个N,求1——N组成的环,使得环上相邻的元素和为素数。

输入描述:

一个整数N1<=N<=10

输出描述:

1放在第一位置,按照字典顺序不重复地输出所有解(顺时针,逆时针算不同的两种),相邻两数之间严格用一个空格隔开,每一行的末尾不能有多余的空格。如果无解,则输出“no”。

样例输入:

8

样例输出:

1 2 3 8 5 6 7 4

1 2 5 8 3 4 7 6

1 4 7 6 5 8 3 2

1 6 7 4 3 8 5 2

【分析】环中只确定起点为1,而其它位置上的数不确定,也就是说有多种可能的组合。遇到此种情境时要考虑搜索+回溯求解。

在这里,除去环的起点,每个位置上的数有N种可能。只要满足以下3点:

1)填进去的数合法(2~N

2)与前面的数不相同;

3)与左边相邻的数的和是一个素数,特别注意环的终点与环的起点上的数之和也是一个素数。

就可确定所求的环是素数环。

过程如下:

1)使用两个数组ringvis。其中ring保存所求素数环中的各个数,vis记录1~N N个数是否已使用(因为要保证不重复)

2)数据初始化;

3)递归填数:判断第i个数填入是否合法;

①合法:填数,并判断是否已经到达环的终点。如果到达终点,打印结果;否则,继续填下一个数;

②不合法:选择下一种可能。

#include <stdio.h>
#include <math.h>
#include <string.h>
#define maxn 15
int N;
int ans=0;       //ans记录解的数目,ans=0时表示无解,输出"no" 
int ring[maxn];  //ring数组保存素数环中的数 
int vis[maxn];   //vis记录1~N 这N个数是否已填入素数环 
int is_Prime(int n)  //判素数 
{
	int i;
	if(n<=1)     //负数.0和1都不是素数 
		return 0;
	for(i=2;i<=sqrt(n);i++)
	{
		if(n%i==0)
			return 0;
	}
	return 1;
}
void dfs(int cur)  //从第2个数开始,向后搜索符合条件的素数环 
{
	int i;
	if(cur==N)     //搜索结束 
	{
		if(is_Prime(ring[0]+ring[cur-1]))  //判断素数环首尾数之和是否为素数,如果是,则找到解 
		{
			for(i=0;i<cur-1;i++)
				printf("%d ",ring[i]);
			printf("%d\n",ring[cur-1]);
			ans++;
		}
		return; 
	}
	for(i=2;i<=N;i++)   //试探2~N中的每个数 
	{
		//当i未被使用且i与环中最后一个数之和为素数时,将其填入素数环 
		if(!vis[i] && is_Prime(ring[cur-1]+i))
		{
			vis[i]=1;   //置已使用标记 
			ring[cur]=i;//填充 
			dfs(cur+1); //继续搜索 
			vis[i]=0;   //回溯-清除已使用标记 
		}
	}
}
int main()
{
	scanf("%d",&N);
	memset(vis,0,sizeof(vis));
	if(N==1)    //N为1时,因为1不是素数,所以可直接判断无解 
		printf("no\n");
	else        //其它情况:搜索 
	{
		ring[0]=1;  //先将1填入,作为素数环的起点 
		dfs(1);     //从环的第2个数开始搜素 
		if(ans==0)  //搜索结束后,如果无解,输出"no" 
			printf("no\n");
	}
	return 0;
}
2. 排列组合问题

(Ⅰ)设有n个整数的集合{1,2,,n},从中取出任意r个数进行排列(1<=r<n<=10),试列出所有的排列。

【分析】元素的排列问题

总体思路同上,这里某个元素按不同次序出现的组合应视为不同的排列。例如:1 2 32 1 3,元素均为1.2.3,只是排列顺序不同,因此应视为元素1.2.3的不同排列。

特别地,当n=r时,称为n的全排列。实现时只需把下面程序的终点改为cur==n即可。

#include <stdio.h>
#define maxn 12
int n,r;
int ans=0;         //符合要求的排列总数 
int ret[maxn];     //保存产生的排列 
int vis[maxn];     //记录1~n是否已使用 1-是 0-否 
void dfs(int cur)  //从{1,2,...,n}中取r个数构成的排列
{
	int i;
	if(cur==r)     //r个数已经选完,输出选择方案 
	{
		for(i=0;i<cur-1;i++)
			printf("%d ",ret[i]);
		printf("%d\n",ret[cur-1]);
		ans++; 
		return;
	}
	//试探1~n n个数 
	for(i=1;i<=n;i++)
	{
		if(!vis[i])     //i未被使用 
		{
			vis[i]=1;   //置已使用标记 
			ret[cur]=i; //将i填入ret[cur] 
			dfs(cur+1); //继续搜索 
			vis[i]=0;   //回溯:清除标记 
		}
	}
} 
int main()
{
	scanf("%d %d",&n,&r);
	dfs(0);
	printf("count=%d\n",ans);
	return 0;
}

(Ⅱ)组合的输出

    排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r<n),我们可以简单地将n个元素理解为自然数12,…,n,从中任取r个数。

    现要求你用递归的方法输出所有组合。

    例如n=5r=3,所有组合为:

    1 2 3   1 2 4   1 2 5   1 3 4   1 3 5   1 4 5   2 3 4   2 3 5   2 4 5   3 4 5

【输入】

    一行两个自然数nr(1<n<211<r<n)

【输出】

    所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

【样例输入】

  5 3

【样例输出】

  1 2 3

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值