DFS
在分酒问题上面,用DFS解决我个人认为是不太好的。主要是因为无法很好地控制一个往回代的过程,也就是如果不仔细加以控制,甚至于即便加以控制,总是有那么一点不确定性将会让程序崩溃。而且在求出解的方面可能会出现比较多的重复解。。我研究了几天的DFS,由于水平原因,还是不得不放弃。改用BFS。
BFS
整体来讲,BFS的算法还算简单化,并且比较容易理解。。废话不多说直接上代码了
(ps:我里面使用了比较多的看似很没必要的操作,比如最重要的“存在替换”函数,它的目的其实是为了减少内存冗余和稍稍提高算法的执行速度,即使它在根本的复杂度上没有改变。因为我很不喜欢在内存空间上改来改去,所以写东西的时候都会很在意这些东西)
#include <iostream>
#include <time.h>
typedef struct state_already state_already;
typedef state_already *ptrto_state_already;
typedef struct state_tree state_tree;
typedef state_tree *ptrto_state_tree;
typedef struct q_ queue;
typedef queue *ptrtoqueue;
typedef struct Q_ Queue;
typedef Queue *ptrtoQueue;
struct state_already{
int num; //统计一共有的不重合的状态数
ptrto_state_tree *state_all; //状态集合,状态集合中的每一个值都是状态结点
};
struct state_tree{ //记录状态的结点,其中state记录的是酒杯的状态情况,up指向上一个状态
int *state; //状态数组,是有序的
ptrto_state_tree up;
int step; //代表走到这个结点从初始态要转移多少次状态
};
struct q_{ //一个队列
ptrto_state_tree state_q;
ptrtoqueue next;
};
struct Q_{
ptrtoqueue head;
ptrtoqueue tail;
int num;
};
//solution不一定能找出所有解,但一定能找到最优解或者最优解集合
ptrto_state_tree *solution(ptrto_state_tree last, int *standard, int cup_num, ptrto_state_already S, ptrtoQueue Q, int num); //处理函数
int existence_replace(ptrto_state_tree state_next, int cup_num, ptrto_state_already S); //存在替换函数,比较重要
int match(ptrto_state_tree last, ptrto_state_tree state_next, int cup_num); //匹配函数,让状态与末状态相匹配
ptrto_state_tree out_queue(ptrtoQueue Q);
void in_queue(ptrtoQueue Q, ptrto_state_tree element);
void output(ptrto_state_tree result, int cup_num, FILE *fp); //输出到文件(一般解)
void output(ptrto_state_tree *result, int cup_num, FILE *fp); //这个重载是输出最优解集到文件
FILE *fp=fopen("result.txt", "w");
int optimal_num=0; //最优解数目 /*static*/
ptrto_state_tree *optimal_solution; //最优解集
int main(int argc, char **argv)
{
clock_t start, end;
int num; //选择解的数量
int cup_num; //酒瓶个数
int *standard; //这代表每个酒瓶标准称量的数组
ptrto_state_tree first; //酒瓶问题的初状态结点
ptrto_state_tree last; //酒瓶问题的末状态结点
ptrto_state_already S=(ptrto_state_already)malloc(sizeof(state_already));
S->state_all=(ptrto_state_tree*)malloc(sizeof(ptrto_state_tree)*100000); //预装一万种状态,出错再加
S->num=0;
printf("输入酒瓶个数:");
scanf("%d", &cup_num);
first=(ptrto_state_tree)malloc(sizeof(state_tree)); //对初状态结点初始化
first->state=(int*)malloc(sizeof(int)*cup_num);
first->up=nullptr;
first->step=0;
S->state_all[S->num++]=first;
last=(ptrto_state_tree)malloc(sizeof(state_tree)); //对末状态结点初始化
last->state=(int*)malloc(sizeof(int)*cup_num);
last->up=nullptr;
ptrtoQueue Q=(ptrtoQueue)malloc(sizeof(Queue));
ptrtoqueue q=(ptrtoqueue)malloc(sizeof(queue));
q->state_q=first;
q->next=nullptr;
Q->head=q;
Q->tail=Q->head;
Q->num=1;
printf("输入酒瓶标准:");
standard=(int*)malloc(sizeof(int)*cup_num);
for (int i=0 ; i<cup_num ; i++){
scanf("%d", &standard[i]);
}
printf("输入酒瓶初状态:");
for (int i=0 ; i<cup_num ; i++){
scanf("%d", &first->state[i]);
}
printf("输入酒瓶末状态:");
for (int i=0 ; i<cup_num ; i++){
scanf("%d", &last->state[i]);
}
printf("尽可能多的解吗?(1:yes, 0:no)");
scanf("%d", &num);
fprintf(fp, "找到的解为:");
start=clock();
optimal_solution=solution(last, standard, cup_num, S, Q, num);
end=clock();
fprintf(fp, "最优解集(%d个最优解), 最小步数是(%d):\n", optimal_num, optimal_solution[0]->step);
output(optimal_solution, cup_num, fp);
fprintf(fp, "\n用时%f秒", (double)(end-start)/CLOCKS_PER_SEC);
printf("\n用时%f秒,结果已输出到文件(result.txt)中", (double)(end-start)/CLOCKS_PER_SEC);
return 0;
}
ptrto_state_tree *solution(ptrto_state_tree last, int *standard, int cup_num, ptrto_state_already S, ptrtoQueue Q, int num){
clock_t the=clock(), next;
int solution_num=0; //解的数目 /*static*/
ptrto_state_tree *optimal_solution=(ptrto_state_tree*)malloc(sizeof(ptrto_state_tree)*50); //最优解集
ptrto_state_tree state_; //当前正在处理的状态结点
int min_step=999; //处理时得到的最优解的最少步数
num==1 ? num=1 : num=0;
while ( (state_=out_queue(Q))){
if (num || state_->step < min_step){ //右边意思是只有当前结点的步数比已知最优解小的时候,才有可能在下一态找出更优解或者同优解。而左边的意思是尽可能多的解
for (int index_=0, st=true ; index_<cup_num && st ; index_++){ //第一个for循环寻找符合要求的可以倒出酒的酒瓶,即当前容量不为0
if (state_->state[index_]!=0){
for (int i=0; i<cup_num && st ; i++){ //第二个for循环寻找符合要求的可以被倒进酒的酒瓶,即被倒进酒的酒瓶不能是满的
ptrto_state_tree state_next=(ptrto_state_tree)malloc(sizeof(state_tree)); //初始化下一状态结点
state_next->up=state_;
state_next->state=(int*)malloc(sizeof(int)*cup_num);
state_next->step=state_->step+1;
if (i!=index_ && state_->state[i]<standard[i]){ //避免出现自己倒自己和要被倒酒的酒瓶已被盛满的情况
for (int j=0 ; j<cup_num ; j++){ //开始分别向其他杯子倒酒,即第三个for循环分别处理酒瓶的改变
if (j==index_){ //此条件说明要改变的酒瓶是倒出酒的那个酒瓶,条件表达式的意思是判断倒出的酒够不够盛满待倒酒瓶。
state_->state[j]-(standard[i]-state_->state[i]) <0 ? state_next->state[j]=0 : state_next->state[j]=state_->state[j]-(standard[i]-state_->state[i]);
}else if(j==i){ //此条件说明要改变的酒瓶是被倒进酒的酒瓶,条件表达式的意思是判断待倒酒瓶中已有的酒加上倒进去的酒会不会溢出。
state_->state[j]+state_->state[index_]> standard[j] ? state_next->state[j]=standard[j] : state_next->state[j]=state_->state[j]+state_->state[index_];
}else{ //剩余不变的酒瓶,直接将前一状态复制过去.
state_next->state[j]=state_->state[j];
}
}
//output(state_next, cup_num, fp);
if (match(last, state_next, cup_num)){ //匹配到对应的末状态,即找到解
next=clock();
solution_num++;
state_next->up=state_;
if (state_next->step < min_step){ //检查当前最优解要求,重新构造最优解集
optimal_num=1; //注意这是全局变量在上方
free(optimal_solution);
optimal_solution=(ptrto_state_tree*)malloc(sizeof(ptrto_state_tree)*50);
optimal_solution[0]=state_next;
min_step=state_next->step;
}else if (state_next->step == min_step){
optimal_solution[optimal_num++]=state_next;
}
output(state_next, cup_num, fp);
printf("用时%f秒,已找到%d个解\n", (double)(next-the)/CLOCKS_PER_SEC, solution_num);
st=false; //结束当前双循环,防止继续下去,因为不可能在这个结点之后,再找到比当前找到的路径更短的路径,因而没必要继续进行下去
break;
}
int er=existence_replace(state_next, cup_num, S);
if (er){
if (er==99999){
S->state_all[S->num++]=state_next; //约定er为99999时添加next
in_queue(Q, state_next);
}else{
S->state_all[er]=state_next; //用next替换下标为er的状态结点
in_queue(Q, state_next);
}
}else{ //不用入队
free(state_next);
}
}else{
free(state_next); //具体到寻找那个被倒进酒的酒瓶时,如果暂时没找到就把上面申请的内存释放掉.否则state_next重定向之后,可能会造成内存冗余
}
}
}
}
}
}
return optimal_solution;
}
/*这个函数的主要作用就是用来省时间.假设state_next是步数为5的结点,但是如果数组S中已经存在和state_next同态的结点,且步数比state_next少,那么state_next
* 之后的结点对求最优解没有什么帮助,可以不用入队
*/
int existence_replace(ptrto_state_tree state_next, int cup_num, ptrto_state_already S){
int i=0;
for (int k=0 ; i<S->num ; i++, k=0){
for (int j=0 ; j<cup_num ; j++){
if (state_next->state[j]==S->state_all[i]->state[j]){
k++;
}
}
if (k==cup_num){ //这说明状态组中存在一个相同的状态,继续判断next的步数是否比原来存在的状态少
if (state_next->step <= S->state_all[i]->step){ //如果next步数小于原数组中和它同态的状态的步数,则用next代替这个同态结点
return i;
}else{ //如果满足同一态却不满足步数条件而不能替换,则无需再继续搜索S数组,因为S数组中的每个值的步数都是代表同一态的最小步数,所以直接返回
return 0;
}
}
}
return 99999; //没有搜索到同一态,则添加这个next
}
int match(ptrto_state_tree last, ptrto_state_tree state_next, int cup_num){
int k=0;
for (int j=0 ; j<cup_num ; j++){
if (state_next->state[j]==last->state[j]){
k++;
}
}
return k==cup_num ? 1 : 0;
}
ptrto_state_tree out_queue(ptrtoQueue Q){
if (Q->num>0){
ptrto_state_tree element=Q->head->state_q;
ptrtoqueue rubbish=Q->head;
Q->head=Q->head->next;
Q->num--;
free(rubbish);
return element;
}else{
printf("队列空不能再出队,返空指针");
return nullptr;
}
}
void in_queue(ptrtoQueue Q, ptrto_state_tree element){
if (Q->num==0){
ptrtoqueue q=(ptrtoqueue)malloc(sizeof(queue));
q->state_q=element;
q->next=nullptr;
Q->head=q;
Q->tail=q;
}else{
ptrtoqueue q=(ptrtoqueue)malloc(sizeof(queue));
q->next=nullptr;
q->state_q=element;
Q->tail->next=q;
Q->tail=Q->tail->next;
}
Q->num++;
}
void output(ptrto_state_tree result, int cup_num, FILE *fp){
int **out=(int**)malloc(sizeof(int*)*result->step+1);
int step=result->step;
for (int i=step ; i>=0 ; i--, result=result->up){
out[i]=result->state;
}
for (int i=0 ; i<step+1 ; i++){
fprintf(fp, "(");
for (int j=0 ; j<cup_num ; j++){
fprintf(fp, "%d, ", out[i][j]);
}
fprintf(fp, ")->");
}
fprintf(fp, "完成\n");
}
void output(ptrto_state_tree *result, int cup_num, FILE *fp){
if (optimal_num==0){
fprintf(fp, "该状态转移情形无解!");
}else{
for (int j=0 ; j<optimal_num ; j++){
ptrto_state_tree tree=result[j];
int **out=(int**)malloc(sizeof(int*)*tree->step+1); //数量为步数+1
int step=tree->step;
for (int i=step ; i>=0 ; i--, tree=tree->up){
out[i]=tree->state;
}
for (int i=0 ; i<step+1 ; i++){
fprintf(fp, "(");
for (int j=0 ; j<cup_num ; j++){
fprintf(fp, "%d, ", out[i][j]);
}
fprintf(fp, ")->");
}
fprintf(fp, "\n");
}
}
}
思路
说一下思路,就部分代码说明:
步数step:指的是当前状态到初状态距离。
- 整体思路就是,利用BFS,每每出队一个状态,就将这个状态可能达到的所有下一步状态都穷举出来(排除掉重复的和步数太大的),然后这些下一步状态都一一入队,以此类推。如果某一个状态的下一个状态是要求的末状态(match函数),那么这条路径算一个解,将这个解的末端的地址记录在设好的一个数组里;如果某一个状态在之前都没有出现过,那么毫无疑问必须将这个状态入队;如果某个状态在以前出现过,但是由于这个状态比之前已经出现过的那个状态步数少,那么应该将之前出现过的那个状态释放掉,然后将现在这个替换掉它的位置。(existence_replace函数)。这样一来,在程序执行下取之后,总会有那么一个时刻,之后的所有状态都在这个时刻之前出现过,并且之后的状态步数无法代替之前这个相等状态的步数,那么主循环结束,算法也就结束了。
运行结果