#include <stdio.h>
#include <string.h>
//将读入字符转换为数字存储,判断是否有王
//DFS回溯遍历,剪枝函数 约束 used 限界 pos
//确定总的DFS深度 pos 4 每个深度上的分叉数+-*/,pos==0一定是+;
//用cal 记录数组和符号;
//将数组转化为字符串输出
int cal_num[4];
char cal_signal[4];
int flag;
int used[4] = {0};
void compute(int num[], int res, int pos);
int main()
{
char card[10];
int num[4];
int joker_exist = 0;
int i;
for (i = 0; i < 4; i++)
{
scanf("%s", card);
if (strstr(card, "joker") != NULL || strstr(card, "JOKER") != NULL)
joker_exist = 1;
else if (strstr(card, "10") != NULL)
num[i] = 10;
else if (card[0] == 'J')
num[i] = 11;
else if (card[0] == 'Q')
num[i] = 12;
else if (card[0] == 'K')
num[i] = 13;
else if (card[0] == 'A')
num[i] = 1;
else if (card[0] >= '0' && card[0] <= '9')
num[i] = card[0] - '0';
}
int res = 0;
int pos = 0;
compute(num, res, pos);
if (joker_exist == 1)
{
printf("ERROR\n");
}
else if (flag == 0)
{
printf("NONE\n");
}
else if (flag == 1)
{
//cal_num
char res_card[4] = {'\0'};
for (int i = 0; i < 4; i++)
{
switch (cal_num[i])
{
case 11:
res_card[i] = 'J';
break;
case 12:
res_card[i] = 'Q';
break;
case 13:
res_card[i] = 'K';
break;
case 1:
res_card[i] = 'A';
break;
default:
res_card[i] = cal_num[i] + '0';
break;
}
}
printf("%c", res_card[0]);
for (int i = 1; i < 4; i++)
{
printf("%c%c", cal_signal[i], res_card[i]);
}
}
return 0;
}
//pos 计算到哪一个数 0 -3
void compute(int num[], int res, int pos)
{
if (pos == 4)
{
if (res == 24)
flag = 1;
else
flag = 0;
return;
}
for (int i = 0; i < 4; i++)
{
if (used[i] == 0)
{
if (pos == 0)
{
used[i]++;
res += num[i];
cal_num[pos] = num[i];
compute(num, res, pos + 1);
if (flag == 1)
break;
res -= num[i];
used[i]--;
}
else
{
used[i]++;
res += num[i];
cal_num[pos] = num[i];
cal_signal[pos] = '+';
compute(num, res, pos + 1);
if (flag == 1)
break;
res -= num[i];
used[i]--;
used[i]++;
res -= num[i];
cal_num[pos] = num[i];
cal_signal[pos] = '-';
compute(num, res, pos + 1);
if (flag == 1)
break;
res += num[i];
used[i]--;
used[i]++;
res *= num[i];
cal_num[pos] = num[i];
cal_signal[pos] = '*';
compute(num, res, pos + 1);
if (flag == 1)
break;
res /= num[i];
used[i]--;
used[i]++;
int temp = res;
res /= num[i];
cal_num[pos] = num[i];
cal_signal[pos] = '/';
compute(num, res, pos + 1);
if (flag == 1)
break;
res = temp;
used[i]--;
}
}
}
}
回溯递归相关笔记
剪枝函数:约束函数和限界函数 n个数中取r个数
#include <stdio.h>
#include <string.h>
#define MAX 100
static int n = 10, r = 5;
static int used[MAX] = {0}; //标记是否使用
static int p[MAX]; //输出的排列
static int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //当前数组
int count=0;
void permute(int pos);
int main()
{
permute(0);
printf("%d",count);
return 0;
}
void permute(int pos)
{
//pos==r排列形成并输出
if (pos == r)
{
for (int i = 0; i < r; i++)
printf("%d", p[i]);
printf("\n");
count++;
return;
}
//已经选了pos个数
//否则匹配第pos+1个数,对应p数组位置pos;
for (int i = 0; i <n; i++)
{
//这里用used数组进行剪枝 这里是约束函数剪去的重复使用一个数组位置的可能结果(即保证这r个数没有从n中重复取用)
//那么pos也可以算是限界函数,限制结果必须是r个数字的组合
if (used[i] == 0)
{
used[i]++;
p[pos] = a[i];
permute(pos+1); //递归 下一个数 直到pos+1-> pos==r输出一个排列
//递归后在pos到r后输出排列,同时需要将当前使用a的数对应的used数组归位
used[i]--;
}
}
}
无相同元素排列重排列 如1221中(1,2)1,2 (1,3)1,2算作同一排列 1.去重后进行排列 2.对上面函数进行修改
n个数中取r个数 不考虑顺序 无重组合
#include <stdio.h>
#include <string.h>
#define MAX 100
static int n = 10, r = 5;
static int used[MAX] = {0}; //标记是否使用
static int C[MAX]; //输出的排列
static int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //当前数组
int count = 0;
void combine(int pos, int h);
int main()
{
combine(0, 0);
printf("%d", count);
return 0;
}
void combine(int pos, int h)
{
if (pos == r)
{
for (int i = 0; i < r; i++)
printf("%d", C[i]);
printf("\n");
count++;
return;
}
//调整后续取数的遍历范围,顺序取数,开始位置依次后移
//例如长10取5 当前pos==0时,从0- 5(5=10-5+pos)都是pos==0时可取值
for (int i = h; i <= n - r + pos; i++)
if (used[i] == 0)
{
C[pos] = a[i];
used[i]++;
combine(pos + 1, i + 1);
used[i]--;
}
}
剪枝
● 如何剪枝:使用used数组还是begin变量?都是用来剪重复数字的枝,着这两个有什么区别呢?
used数组:
■ 一个boolean数组,用来标记nums中对应元素是否已经使用过。
■ 用于全排列问题中,对于这一类问题,我们是考虑数字顺序的,即[1,2,3]和[3,2,1]是两个不同的排列。它剪去的其实是当前层数以上的祖先节点,比如当我们第一位取了2时,2、3位只能取1和3,不能再取1了。
begin变量
■ 一个int类型,在递归时根据题意来决定下一层函数的选择列表中元素的起始位置,如果可重复即为begin,不可重复为begin+1
■ 用于组合问题中,对于这一类问题,我们是考虑数字顺序的,即[1,2,3]和[3,2,1]是两个相同的组合。它相对于used数组,剪去了更多的枝,这也就是我们都知道的排列的个数比组合个数多。因为它是控制每层对选择列表的起始位置,所以它不仅剪去了当前节点的祖先节点,还剪去了当祖先节点的左兄弟,这就是begin在起作用。(对应排列中就是这个h head变量也即bigin位置在限制)
● begin
○ 剪父节点和父节点的兄弟节点,排在它前面的元素都被剪掉
● used
○ 剪父节点和兄弟节点
● 重复节点两者都可
● 剪枝前需要考虑是否要对nums进行排序,剪重复节点是必须要进行排序的。
总结:两层
● 确定总的DFS深度,每个深度上进行剪枝和遍历,
● 在每个深度上确定分叉数,对每个子节点进行剪枝和遍历
如火车进站,火车序列已知,深度为火车数量n,每个深度上分叉为进站和出站两个
如全排列,数组已知,用used剪枝,深度为数组元素数量n,每个深度上有n个分叉(即取用n个元素中的一个),用used剪枝
递归后要还原操作回溯到前一深度状态