第七章:(暴力求解法)
枚举全排列:(包括可重集)

/* //枚举排列:(递归) int f[101]; void Print1(int n,int *A,int cur){ //生成全排列: int i,j; if(cur==n){ for(i=0;i<n;i++) printf("%d ",A[i]); printf("\n"); } else{ for(i=0;i<n;i++){ int ok=1; for(j=0;j<cur;j++) if(A[j]==f[i]){ok=0;break;} if(ok){ A[cur]=f[i]; Print1(n,A,cur+1); } } } } void Print2(int n,int *A,int cur){ //生成可重集全排列: int i,j; if(n==cur){ for(i=0;i<n;i++) printf("%d ",A[i]);printf("\n"); } else{ int c1,c2; for(i=0;i<n;i++){ if(!i || f[i]!=f[i-1]){ c1=c2=0; for(j=0;j<cur;j++) if(f[i]==A[j]) c1++; for(j=0;j<n;j++) if(f[i]==f[j]) c2++; if(c1<c2){ A[cur]=f[i]; Print2(n,A,cur+1); } } } } } void text(){ int i,j,k,n;int A[101]; while(scanf("%d",&n)!=EOF){ for(i=0;i<n;i++) scanf("%d",&f[i]); sort(f,f+n);//排列 for(i=0;i<n;i++) printf("%d ",f[i]); //Print1(n,A,0); Print2(n,A,0); } } */
下一个排列:
while(next_permutation(p,p+n)){
printf(...) //当不存在下一个排列的时候,跳出while循环。
}
枚举子集:

int f[1010];int A[1010]; void Print1(int n,int *A,int cur){ //法一:;增量构造法! 需要排序!依次从集合中取出元素! 注意解答树情况! int i,j,s; if(cur){ for(i=0;i<cur;i++) printf("%d ",A[i]); printf("\n"); } s = cur ? A[cur-1] : 0 ;//注意这里要加1 ! //s这里代表的是 A[cur-1]在原数组中第几位! for(i=s;i<n;i++){ A[cur]=i+1; Print1(n,A,cur+1); } } void Print2(int n,int *B,int cur){ //法二:位向量法! 思路: 设定一个数组B,其中 B[i] 为1,代表第i个元素被选中。 int i,j; if(cur==n){ for(i=0;i<n;i++) if(B[i]) printf("%d ",f[i]);//特点之一,不需要按照顺序来计算了! printf("%\n"); } else{ B[cur]=1; Print2(n,B,cur+1); B[cur]=0; Print2(n,B,cur+1); } } void PP(int n,int s){ for(int i=0;i<n;i++) if(s&1<<i) printf("%d ",f[i]); //else printf("0 "); printf("\n"); } void Print3(int n){ //法三:二进制法! 使用二进制代表子集。 //化成二进制形式,其中从右往左第i位表示元素i是否在集合中。(1表示在,0表示不在) int i,j; for(i=0;i<(1<<n);i++){ PP(n,i); } } void text(){ int i,j,k; int n; while(scanf("%d",&n)!=EOF){ int A[1010]; //Print1(n,A,0); //输出1-7的全排列; int B[101];memset(B,0,sizeof(B)); for(i=0;i<n;i++) scanf("%d",&f[i]); //Print2(n,B,0); Print3(n); } }
这两者的解答树是稍有不同的。但由于特性使然:最后一层节点数目 约等于 其上所有节点总和, 两者解答树节点数目相差2倍。
回溯:也即递归枚举算法! + 适当地减少枚举量。
进一步减少解答树节点数目,减少时间空间开支,剪枝,加快算法。
核心:生成和检查有机地结合起来! 从而减少不必要的枚举量

/* //八皇后:回溯法的实例应用 int tot=0; void Print(int n,int cur,int *C){ int i,j; if(n==cur) tot++; else{ for(i=0;i<n;i++){ int ok=1; for(j=0;j<cur;j++) if(i==C[j] || cur-i==j-C[j] || cur+i==j+C[j]) { ok=0; break;} if(ok){C[cur]=i;Print(n,cur+1,C);} } } } void text(){ //回溯法:核心:生成和检查有机地结合起来! 从而减少不必要的枚举量 //7.4.1八皇后问题 //初步判定: 皇后的排列问题,相当于一个组合选择全排列问题: (8行8列,每列选择位置不同,可以看成一个全排列。) //但经过枚举过程中进一步的将: 生成和检查 结合起来,可以进一步减少枚举量!!---> 回溯 int i,j,k; int n; while(scanf("%d",&n)!=EOF){ tot=0;int C[15];memset(C,0,sizeof(C)); Print(n,0,C); printf("共有%d种放法。\n",tot); } } //注意: 一般地,如果在回溯递归过程中,改变了函数的局部变量,尤其是函数参数,可能需要将他们恢复原状!! */
注意恢复原状的问题,一般也是在“出口”处回复原状,也即:函数调用结束的地方。
素数环:

/* //7.4.2素数环:回溯 int vis[101]; int isp(int x){ if(x==2|| x== 3|| x==5||x==7||x==11||x==13||x==17||x==19||x==23) return 1; else return 0; } void Print(int n,int cur,int *A){ int i,j; if(n==cur){ if(isp(A[cur-1]+A[0])) { for(i=0;i<n;i++) printf("%d ",A[i]); printf("\n"); } } else{ for(i=2;i<=n;i++){ if(!vis[i] && isp(i+A[cur-1])){ //回溯的分支判断; A[cur]=i;vis[i]=1; Print(n,cur+1,A); //!!! 记得还原! vis[i]=0; } } } } void text(){ int i,j,k,n;int A[101]; while(scanf("%d",&n)!=EOF){ memset(vis,0,sizeof(vis)); A[0]=1;vis[1]=1; //放置第一个数字; Print(n,1,A); } } //总结:记得初始化放置第一个数字的时候,一定要对A和vis数组进行第一步的处理。 * 还有一个还原vis数组的问题!! 同时,因为cur(在同一个函数中,cur的值不变)的存在,所以A数组不需要进行还原处理! * 这也是vis数组与构造的A数组的不同之处。 */
困难的串:

/* //6.4.3困难的串 int cnt; int Print(int n,int L,int cur,int *A){ int i,j,k; if(cnt==n){ for(i=0;i<cur;i++) printf("%c ",A[i]+'A'); printf("\n"); return 0; } else{ for(i=0;i<L;i++){ int ok=1; int flag; A[cur]=i;//为数组赋值。 for(j=1;2*j<=cur+1;j++){ //字符串长度从2j开始枚举 flag=1; for(k=0;k<j;k++) if(A[cur-k]!=A[cur-j-k]){flag=0;break;} if(flag){ok=0;break;} //判断当前i已经不行了! } if(ok){ cnt++;if(!Print(n,L,cur+1,A)) return 0; } } } return 1; } void text(){ int i,j,n,L; while(scanf("%d %d",&n,&L)!=EOF){ cnt=0;//计数器,第n个困难的串; //按照字典序的顺序! int A[101];memset(A,0,sizeof(A)); Print(n,L,0,A); } } //总结: if(!Print(n,L,cur+1,A)) return 0; 可以很好地在得到一组解之后就跳出循环!! //--->bjfu_oj上木棒那题可以应用 */
//注意最后一点总结注释!可以跳出整个递归!
带宽:
/*
与八皇后问题不同之处:
八皇后问题,在得到完整解之前,可以避免拓展一些不可行的节点!
但是此题每种情况都是可行解。那么就没有可以优化的方式了么?
当然不是! 我们可以采用一个在平常最常使用的方法:维护最大值! 只不过这里我们是在递归的过程中维护,用来剪枝!
通俗讲:我们可以记录下目前已经找到的最小带宽k,在枚举其它路子树结构时,如果发现已经有某两个节点的距离大于等于k,那么我们考虑将这一路“剪掉”!那么可以优化很多!(就像一个数组,维护最大值最小值等问题,思想类似,只不过应用在了不同的地方。)
*/