回溯法实验详解

一、素数环问题

找出长为n的,由1,2,…,n组成的一个环,环中所有相邻的数的和都为素数的环个数。(注意,如1,2,3,4和2,3,4,1是同一个环,只计算一次,但是1,2,3,4和1,4,3,2却不算是同一个环)

  • 解空间:对于该问题来说 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an)为问题的解空间,该问题求有多少个解。所以只要遍历解空间即可。
  • 约束条件:
    1. 相邻数和为素数2
    2. 首位数和为素数
    3. 首位为1

采用回溯法解决问题,显然由于数不能重复,定义一个visit数组存储是否已经访问过的信息即可。

#include<iostream>
using namespace std;
int visit[20]={0};
int n;
int count=0;
int isprime(int n){
	if(n<2||(n>2&&n%2==0)) return 0;
	for(int i=3;i*i<=n;i+=2){
		if(n%i==0) return 0;
	}
	return 1;
}
void dfs(int k,int cnt)//k表示当前数,cnt 表示累计数
{
	if(cnt==n&&isprime(k+1))//最后一个数和第一个数和是素数
	{
		count++;
		return;
	}
	for(int i=2;i<=n;i++){
		if(!visit[i]&&isprime(k+i))//当前数和下一个数是素数
		{
			visit[i]=1;
			dfs(i,cnt+1);
			visit[i]=0;
		}
	}
}
int main(){
	cin>>n;
	visit[1]=1;
	dfs(1,1);
	cout<<count;
	return 0;
}

时间复杂度,emmm,怎么说也有 O ( n ! ) O(n!) O(n!)吧,难顶

二、分糖果

菜菜最近上网课,非常无聊,天天在家和一群小朋友玩,但是菜菜是个不好玩的人,小朋友都不愿意和他玩,菜菜最近想到了一个好办法,他想给小朋友买糖果,但是小朋友对每种糖果的需求是不同的,也就是说,每个小朋友的口味不同,他想在满足小朋友的同时,使得自己买的糖果数量最少,但是同时不能买相同的糖果,因为这样小朋友会觉得糖果一样的,没什么意思,菜菜是个穷鬼,你能写个程序帮助他使得他买的糖果数量最少吗?

这题用回溯法也好解决,先定义一个矩阵a[i][j],代表i个孩子对j号糖果的需求。然后每层选一个进行试探(注:不能重复选一个列)

#include<iostream>
#define INF 10000000
using namespace std;

int a[12][12];
int visit[12]={0};
int minx = INF;
int n;
void dfs(int i,int count){
    if(i==n){
        if(minx>count)
            minx = count;
        return;
    }
    for(int j=0;j<n;j++)
        if(visit[j]==0){
            visit[j]=1;
            dfs(i+1,count+a[i][j]);
            visit[j]=0;
        }

}
int main(){
  	cin>>n;
  	for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            cin>>a[i][j];

    dfs(0,0);
    cout<<minx<<endl;
	return 0;
}

时间复杂度还是 O ( n ! ) O(n!) O(n!),暂时还没想到是否可以优化。有想到的可以@我 hhh

三、假·完美数

我们将一个十进制正整数N定义为完美数当且仅当 任意两个相邻的数位的 差的绝对值不超过1.形式化定义为: 对于 N = d1d2d3d4…dm. 对于 ∀i∈[1 , m) 有 |di - d(i + 1)| <= 1.
例如: 111 . 777 . 123. 322 都是完美数,而 115 . 224. 124 都不是完美数.
现在想知道,第K小完美数是多少.(1 <= k <= 100000)

这个题用回溯法解决,其实这个题回溯解决还需要知道几个条件,我也是后来才知道的,确定条件是最大的数有多少位,对于本题来说第十万小的数实际上已经到了10位数了,无疑int是装不下的

回溯法
  • 对于第i数位x来说,为保持完美数状态,必须满足i+1位数在x-1,x,x+1。
  • 边界条件:除了第1数位不能取到零外,其余数位的范围均在 0 < = x < = 9 0<=x<=9 0<=x<=9

实际上回溯的思路就是搜索出所有可能的值,然后排序。emmm,有些无脑。没什么好讲的,源代码如下

#include<cstdio>
#include<iostream>
#include <algorithm>
using namespace std;
long long b[200000]={0};
int c=1; //计数,目前初始化为1
void dfs(long long n,int k){
    if(k==0) return;
    b[c++]=n;//记录当前值
    int m=n%10;//取当前数位
    for(int j=m-1;j<=m+1;j++){
        if(j>=0&&j<=9)
            dfs(n*10+j,k-1);
    }
}
int main()
{
	//初始值1~9的回溯
    for(int i=1;i<=9;i++){
        dfs(i,10);
    }
    sort(b+1,b+c);//将搜索结果进行排序
    int n,k;
    scanf("%d",&n);
    while(n--){
        scanf("%d",&k);
        printf("%lld\n",b[k]);
    }
    return 0;
}


对于回溯算法不停的调用递归函数,然后将得出的结果储存起来,时间大头花排序上,在时间效率上还是有点低下。还有一种方法也可以做出此题,大思路都是一样的,直接列出所有结果,然后直接读答案,区别大概在于不需要回溯,也不需要排序,因为计算过程中结果已经排好了。

优化算法

对于该假完美数来说,第kn位数的循序必定依照n-1位的顺序构建,例如

  • 1,2,3,4,5,6,7,8,9
  • 10,11,20,21,22,32,33,34…
  • 100,101,110,111,…

现在很明显了,对于首位1的2位完美数,比首位2的2位完美数都要小。这时候,我们只需要依次将完美数填入即可。
首先构建一个完美数表 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] [1,2,3,4,5,6,7,8,9] [1,2,3,4,5,6,7,8,9],然后将1的2位填入, [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 ] [1,2,3,4,5,6,7,8,9,10,11] [1,2,3,4,5,6,7,8,9,10,11],2的2位填入 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 21 , 22 , 23 ] [1,2,3,4,5,6,7,8,9,10,11,21,22,23] [1,2,3,4,5,6,7,8,9,10,11,21,22,23],依次类推,即可
代码如下.

#include<stdio.h>
long long a[100001]={0,1,2,3,4,5,6,7,8,9};
int q=1,p=10;//队首,队尾
int n,k;
int main()
{
    while(p!=100001){//队尾已经符合要求
        int m=a[q]%10;//取当前数位
        for(int i=m-1;i<=m+1;i++){
            if(i>=0&&i<=9){
                a[p++]=a[q]*10+i;
            }
        }
        q++;
    }
    scanf("%d",&n);
    while(n--){
        scanf("%d",&k);
        printf("%ld\n",a[k]);
    }
    return 0;
}

时间复杂度 O ( n ) O(n) O(n)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值