我最爱的冯老师在离散课上出了一个附加题。
为了能够加更多的学分也是出于自己的好奇,我决定尝试一下。
思路:二进制枚举+逆波兰表达式
#include<stdio.h>
#include<string.h>
#define T 1
#define F 0
struct Stack{
int top;
char arr[80];
}number={-1},symbol={-1};
//number储存后缀表达式结果
//symbol 操作符栈,用来保存操作符
int book[26];//记录变元元素和个数
//a对应下标为0
//b对应下标为1
//c对应下标为2
//以此类推
int num=0;//记录变元的个数
int alphabet_true_false[26];
/*
上面的book数组每个元素存放的是
该下标对应的字母是否存在
类似的这个alphabet_true_false存放的是
该下标对应的字母变元的真值(true or false)
当然,只有book[i]不为零的情况下该数组才会奏效
*/
int enum_result[80];
/*
如果二进制值为110110010
那么该数组从前往后数每个元素分别为:
1 1 0 1 1 0 0 1 0
说白了就是把十进制转为二进制01值并存到enum_result里罢了
*/
char str_copy[80];//输入的字符串的副本
void count_num(char str[]){//记录变元的个数,用全局变量num储存
int i,len=strlen(str);
for(i=0;i<len;i++){
if(str[i]>='a'&&str[i]<='z'){
if(book[str[i]-'a']==0){
book[str[i]-'a']=1;
++num;
}
}
}
}
void scan_string(char str[]){
//输入小写字符
//例如!(p|q)>(q&!r|(r>p))
int i;
gets(str);
strcpy(str_copy,str);
count_num(str);//记录变元的个数,用全局变量num储存
int len=strlen(str);
for(i=0;i<len;i++){
//对操作符进行优先级处理
//把字符数组的操作符改为计算机可判断优先级的特别字符
switch(str[i]){
case '!':str[i]=')'+5;break;
case '&':str[i]=')'+4;break;
case '|':str[i]=')'+3;break;
case '>':str[i]=')'+2;break;
case '=':str[i]=')'+1;break;
}
}
}
void reverse_polish(char s[]){
//把命题公式改为后缀表达式
//并把后缀处理的结果保存在number结构体里
//number结构体里原本存放的是变元,就是那些字母。
//symbol里存放的是操作符, 经过后缀处理完后symbol里应该为空。
//经过后缀处理完后就会把symbol里的操作符也转移到了number里
int i,len=strlen(s);
for(i=0;i<len;i++){
if(s[i]>='a'&&s[i]<='z'){//如果是命题变元就直接入number栈
number.arr[++number.top]=s[i];
}else if(s[i]>')'&&s[i]<=5+')'){
if(symbol.top==-1||symbol.arr[symbol.top]==')'){
++symbol.top;
symbol.arr[symbol.top]=s[i];
}else if(s[i]>=symbol.arr[symbol.top]){
++symbol.top;
symbol.arr[symbol.top]=s[i];
}else{
while(symbol.top!=-1&&s[i]<symbol.arr[symbol.top]){
++number.top;
number.arr[number.top]=symbol.arr[symbol.top];
--symbol.top;
}
--i;
}
}
else if(s[i]=='('||s[i]==')'){
if(s[i]=='('){
++symbol.top;
symbol.arr[symbol.top]=s[i];
}else{
while(symbol.arr[symbol.top]!='('){
++number.top;
number.arr[number.top]=symbol.arr[symbol.top];
--symbol.top;
}
if(symbol.top!=-1)--symbol.top;
}
}
}
while(symbol.top!=-1){
++number.top;
number.arr[number.top]=symbol.arr[symbol.top];
--symbol.top;
}
}
int check(int num){//判断enum_result[]的二进制情况组成的是极小值(return 1)还是极大值(return 0)
struct Stack ans={-1};
int i,k=0,len=0,temp;
for(i=0;i<26;i++){
if(book[i]){
++len;
alphabet_true_false[i]=enum_result[k++];//enum_result里只有真或假(1,0)
//这一步类似于变元p被赋值为false(0)
//或者t被赋值为true(1)
}
}
for(i=0;i<=number.top;i++){
if(number.arr[i]>='a'&&number.arr[i]<='z'){
++ans.top;
ans.arr[ans.top]=alphabet_true_false[number.arr[i]-'a'];
}else{
switch(number.arr[i]){
case '.':
ans.arr[ans.top]=!ans.arr[ans.top];
break;
case '-':
ans.arr[ans.top-1]=ans.arr[ans.top-1]&ans.arr[ans.top];
--ans.top;
break;
case ',':
ans.arr[ans.top-1]=ans.arr[ans.top-1]|ans.arr[ans.top];
--ans.top;
break;
case '+':
ans.arr[ans.top-1]=!ans.arr[ans.top-1]|ans.arr[ans.top];
--ans.top;
break;
case '*':
ans.arr[ans.top-1]=
(!ans.arr[ans.top-1]|ans.arr[ans.top])
&(!ans.arr[ans.top]|ans.arr[ans.top-1]);
--ans.top;
break;
}
}
}
printf("%4d\n",ans.arr[0]);
return ans.arr[0];
}
void binary_enumeration(int num){
//二进制枚举 2^num次 遍历所有情况
//
int i,j;
int conjunction_top=-1,disjunction_top=-1;/*也把下面的两个数组看作栈,
使用这两个top变量指向的就是栈顶元素的下标 */
int result_conjunction[80]={0};//储存的是主合取结果
int result_disjunction[80]={0};//储存的是主析取结果
for(i=0;i<26;i++)
if(book[i])printf("%4c|",i+'a');
putchar(' '),puts(str_copy);
//二进制枚举用的是两层循环
//最外面的一层循环如果num是4
//则1<<num的二进制就为(1111),十进制就是15
//说白了就是循环15次,让变量i自增15次
//而里面的循环则是:
//读取变量i的二进制中的第j位比特值
//假如变量i位5,则i的二进制就是101
//
for(i=0;i<(1<<num);i++){//
memset(enum_result,0,num);
for(j=0;j<num;j++){//这一层的循环说白了就是把变量j的值转换为对应的二进制,并把二进制值储存到enum_result数组里
if(i&(1<<(num-j-1))){
enum_result[j]=T;
}else{
enum_result[j]=F;
}
printf("%4d|",enum_result[j]);
}
//里层循环完之后现在的enum_result每一位里存放的就是j的二进制对应的值
if(check(num)){
result_disjunction[++disjunction_top]=i;//主析取十进制下标
}else{
result_conjunction[++conjunction_top]=i;//主合取十进制下标
}
}
puts("\n主析取范式为:");
for(i=0;i<=disjunction_top;i++){
printf("m%d%s",result_disjunction[i],i^disjunction_top?"&":"");
}
puts("\n主合取范式为:");
for(i=0;i<=conjunction_top;i++){
printf("M%d%s",result_conjunction[i],i^conjunction_top?"|":"");
}
}
void show(char str[]){
int i;
puts("\n后缀表达式为:");
for(i=0;i<=number.top;i++){
switch(number.arr[i]){
case ')'+5:number.arr[i]='!';break;
case ')'+4:number.arr[i]='&';break;
case ')'+3:number.arr[i]='|';break;
case ')'+2:number.arr[i]='>';break;
case ')'+1:number.arr[i]='=';break;
}
printf("%c",number.arr[i]);
}
puts("\n使用的命题变项:");
for(i=0;i<26;i++)
if(book[i])printf("%c ",i+'a');
}
/*
优先级
非 :! 5
合取:& 4
析取:| 3
蕴涵:> 2
等价:= 1
*/
int main()
{
int i=0;
char str[80];
scan_string(str);//输入小写字符
reverse_polish(str);//逆波兰表达式 去括号
binary_enumeration(num);//把变元个数num传入函数
//有num个变元就有2^num个情况,对每一个二进制情况经进行heck(),
show(str);
return 0;
}
大一,寒假时为了蓝桥杯学了不少算法和数据结构的知识。
写了将近4个小时。又复习了一遍后缀表达式。
算法方面没有什么不理解的地方,大部分时间都是细节上的错误,
比如
for(i=0;i<=number.top;i++){
switch(number.arr[i]){
...
}
}
写成
for(i=0;i<=number.top;i++){
switch(number.arr[number.top]){//循环的是i,我却循环成了栈顶元素。这个地方卡了差不多一个小时。可能是因为太晚了脑子有点糊涂
...
}
}
程序跑通了调的没bug了之后拿给老师看,老师理所当然的给出了肯定的评价;
就是不知道她什么时候能把我这个分给加上。
以下是运行结果:
为了配的上我心中的那个执念
lonely but only study
lonely but only suppress