素数环问题剪枝技巧

题目描述:对于正整数n,对1~n进行排列,使得相邻两个数之和均为素数,输出时从整数1开始,逆时针排列。同一个环应恰好输出一次
n<=16
如输入:6
输出:
1 4 3 2 5 6
1 6 5 2 3 4

思路分析:这道题目第一个整数1已确定,第2个整数可以选择(2~N)间的任意一个数,第3个整数可以在第2个整数选择后余下的N-1个数里任意一个数,后面依此类推,直到选择完N个数。N个数N个选择,我们首先想到用DFS去解决。
第一个数已确定。因此可以从第二个数开始进行选择。DFS(2)选择第二个数,并标记为已选,防止后续重复选择。依次进行DFS(3),DFS(4)…DFS(N)完成N个数的选择。

在这里插入图片描述
选择完N个数后,需要设置出口判断条件。该问题需要判断 相邻2个数相加是否素数,如果DFS(N)的时候再进行判断需要再次读取前N个数进行判断,效率非常低。因此我们可以每次调用DFS的时候就进行判断。有如下两种剪枝判断方法:

dfs1: 在每次选取数的时候就进行判断,是否满足与前一个数相加是素数的条件。只选取符合条件的数。

void dfs1(int cur) {
	if (cur == N + 1) {
		if (IsPrime(gResult[1] + gResult[N])) {
			for (int i = 1; i <= N; i++) {
				printf("%d", gResult[i]);
			}
			printf("\n");
		}
		return;

	}

	//剪枝在此进行
	for (int i = 2; i <= N; i++) {
		if (gVi[i] == 0 && IsPrime(gResult[cur-1] + i)) {
			gVi[i] = 1;
			gResult[cur] = i;
			dfs1(cur + 1);
			gVi[i] = 0;
		}
	}
	return;
}

dfs2:在dfs入口处进行判断,选数环节只负责选数,不做任何处理。选好数后,在下次的dfs调用中判断前两次的数是否符合条件。

void dfs2(int cur) {
	//剪枝在此进行
	if (!IsPrime(gResult[cur - 1] + gResult[cur - 2]) && gResult[cur - 1] + gResult[cur - 2]!=1) {
		return;
	}
	if (cur == N + 1) {
		if (!IsPrime(gResult[cur - 1] + gResult[1])) {
			return;
		}
		else {
			for (int i = 1; i <= N; i++) {
				printf("%d ", gResult[i]);
			}
			printf("\n");
		}
	}

	for (int i = 2; i <= N; i++) {
		if (gVi[i] == 0) {
			gVi[i] = 1;
			gResult[cur] = i;
			dfs2(cur + 1);
			gVi[i] = 0;
		}
	}
	return;
}

完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

#define MAX_N 21

int N;
int gResult[MAX_N];
int gVi[MAX_N];

bool IsPrime(int num) {
	if (num < 2) return false;
	for (int i = 2; i * i <= num; i++) {
		if (num % i == 0) return false;
	}
	return true;
}

void dfs1(int cur) {
	if (cur == N + 1) {
		if (IsPrime(gResult[1] + gResult[N])) {
			for (int i = 1; i <= N; i++) {
				printf("%d", gResult[i]);
			}
			printf("\n");
		}
		return;

	}

	//剪枝在此进行
	for (int i = 2; i <= N; i++) {
		if (gVi[i] == 0 && IsPrime(gResult[cur-1] + i)) {
			gVi[i] = 1;
			gResult[cur] = i;
			dfs1(cur + 1);
			gVi[i] = 0;
		}
	}
	return;
}

void dfs2(int cur) {
	//剪枝在此进行
	if (!IsPrime(gResult[cur - 1] + gResult[cur - 2]) && gResult[cur - 1] + gResult[cur - 2]!=1) {
		return;
	}
	if (cur == N + 1) {
		if (!IsPrime(gResult[cur - 1] + gResult[1])) {
			return;
		}
		else {
			for (int i = 1; i <= N; i++) {
				printf("%d ", gResult[i]);
			}
			printf("\n");
		}
	}

	for (int i = 2; i <= N; i++) {
		if (gVi[i] == 0) {
			gVi[i] = 1;
			gResult[cur] = i;
			dfs2(cur + 1);
			gVi[i] = 0;
		}
	}
	return;
}

int main() {
	int T, test_case;
	freopen("1.txt", "r", stdin);
	setbuf(stdout, NULL);
	scanf("%d", &T);
	for (test_case = 1; test_case <= T; test_case++) {
		scanf("%d", &N);
		printf("#%d:\n", test_case);
		if (N == 1) {
			printf("%d\n", 1);
		}
		else {
			gResult[1] = 1;
			gVi[1] = 1;
			dfs2(2);
		}
	}
	return 0;
}

输入:

2
6
8

输出:

#1:
1 4 3 2 5 6
1 6 5 2 3 4
#2:
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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值