数制转换
十进制数N和其他d进制数的转换是计算机实现计算的基本问题,其解决方法很多,简单算法基于下列原理:
N=(N div d)*d+N mod d(其中:div为整除运算,mod为求余运算)
例如(1348)10=(2504)8
计算过程如下图:
(这算法很简单,在此不做解释)
我们发现到,对于任意一个非负整数,它的计算是从低位开始,然后才是高位。所以可以用栈先进后出的规则,下面是代码:
算法3.1:
void conversion (int Num) {
// 对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数
ElemType e;
SqStack S;
InitStack(S); // 构造空栈
while (Num) {
Push(S, Num % 8);
Num = Num/8;
}
while (!StackEmpty(S)) {
Pop(S,e);
printf ("%d", e);
}
printf("\n");
} // conversion
现在来分析下:
1.这里面Num是输入的任意一个非负十进制整数。
2.这里push就是入栈,Pop就是出栈,每次把num除以8的余数入栈,入栈后再把Num除以8,这和上面的算法一样。
3.StackEmpty()顾名思义,就是是不是空栈,他的返回值应该是0和非0(c语言没有bool类型)。
4.随后用先进后出的思想,就可以实现输出了。
行编辑程序
当用户输入错误用“#”表示退格,用“@”表示行无效。
如下图所示:
下面编写一个程序:如果他既不是退格符也不是换行符,则将该字符压入栈顶;如果是一个退格符,则从栈顶删去一个字符;如果它是一个退行符,则将字符栈清空为空栈。
算法如下:
void LineEdit() {
//利用字符栈S,从终端接收一行并传送至调用过程的数据区。
char ch,*temp;
SqStack S;
InitStack(S); //构造空栈S
printf("请输入一行(#:退格;@:清行):\n");
ch = getchar(); //从终端接收第一个字符
while (ch != EOF) { //EOF为全文结束符
while (ch != EOF && ch != '\n') {
switch (ch) {
case '#': Pop(S, ch); break; // 仅当栈非空时退栈
case '@': ClearStack(S); break; // 重置S为空栈
default : Push(S, ch); break; // 有效字符进栈,未考虑栈满情形
}
ch = getchar(); // 从终端接收下一个字符
}
temp=S.base;
while(temp!=S.top) {
printf("%c",*temp);
++temp;
}
// 将从栈底到栈顶的栈内字符传送至调用过程的数据区;
ClearStack(S); // 重置S为空栈
printf("\n");
if (ch != EOF) {
printf("请输入一行(#:退格;@:清行):\n");
ch = getchar();
}
}
DestroyStack(S);
}
下面来分析下:
1.这里有个EOF,注释为全文结束符,这是一个宏,在此程序里面,我们不必要高清这个宏具体指什么,只需要知道,有那么一个东西即可。
2.getchar这个函数只能接受一个字符,比如,当你从键盘上输入hjuijij这几个字母后,只有h被接受,如下图所示:
3.这个DestroyStack就是销毁栈,C/C++的特点就是要及时销毁不用内存。
4.这个程序的思路就是从键盘输入字符,判断是什么字符,如果是‘#’则栈顶元素出栈,如果是‘@’则清空栈,如果是其他字符,则入栈。
迷宫求解
为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。
下面说明一个迷宫:
可以写出如下算法:
下面说明下这个栈的元素类型的结构体:
typedef struct{
int ord; //通道块在路径上的“序号”
PosType seat; //通道块在迷宫中的“坐标位置”
int di; //从此通道块走向下一通道块的“方向”
}SElemType;
算法3.2:迷宫maze中存在从入口start到出口end的通道,则求的一条存放在栈中。
下面是代码:
Status MazePath(MazeType &maze, PosType start, PosType end) {
// 若迷宫maze中从入口 start到出口 end的通道,则求得一条存放在栈中
// (从栈底到栈顶),并返回TRUE;否则返回FALSE
Stack S;
PosType curpos;
int curstep;
SElemType e;
InitStack(S);
curpos = start; // 设定"当前位置"为"入口位置"
curstep = 1; // 探索第一步
do {
if (Pass(curpos)) { // 当前位置可通过,即是未曾走到过的通道块
FootPrint(curpos); // 留下足迹
e.di =1;
e.ord = curstep;
e.seat= curpos;
Push(S,e); // 加入路径
if (curpos == end)
return (TRUE); // 到达终点(出口)
curpos = NextPos(curpos, 1); // 下一位置是当前位置的东邻
curstep++; // 探索下一步
} else { // 当前位置不能通过
if (!StackEmpty(S)) {
Pop(S,e);
while (e.di==4 && !StackEmpty(S)) {
MarkPrint(e.seat);
Pop(S,e); // 留下不能通过的标记,并退回一步
} // while
if (e.di<4) {
e.di++;
Push(S, e); // 换下一个方向探索
curpos = NextPos(e.seat, e.di); // 当前位置设为新方向的相邻块
} // if
} // if
} // else
} while (!StackEmpty(S) );
return FALSE;
} // MazePath
下面来分析下代码:
表达式求值这个程序的思路如下,PosType e,这个是定义了一个坐标类型,变量名为e,并且初始化了栈S,在程序开始(未循环时)把坐标设置为迷宫开始的位置。并且用了一个int型的curstep来说明当前的步骤(开始时设置为1)。在do循环中,可以看到一个叫Pass的函数,传递了一个参数,为当前位置。
如果可以通过则留下足迹,并且给e进行赋值(e为上面那个结构体SElemType)。这里我们看到了一个if(curpos==end)这个,然后我们到倒数第7行看到cusps=NextPos(e.seat,e.di)这个if就是根据他来的。当if(curpos==end)不满足时,curpos后移,并且cursetp++。
如果不可以通过时,则要先看这个S栈是不是空,不为空这出栈,随后判断e.ei==4(这个,其实是标记,上下左右的标记,当此位置上下左右都被标记后就是4)。
差不多就这样了,大家熟悉下思路就可以了。官方也没有提供全代码,也不好具体分析。
这里只介绍算符优先(编译原理也有相关介绍):
如对下面的算术表达式求值:
4+2*3-10/5
这个表达式的计算顺序为:4+2*3-10/5=4+6-10/5=10-10/5=10-2=8
算符优先法就是根据这个运算优先关系的规定来实现对表达式的编译或解释执行的。
我们知道算符只有三种关系:
1.低于2.等于3.高于
下面定义了算符之间的这种优先关系。
#是表达式结束符。
为了实现算符优先算法,可以使用两个工作栈。一个叫OPTR,用以寄存器运算,一个叫OPND用以寄存操作数或运算结果。思路如下:
1.首先置操作数栈为空栈,表达式起始符“#”为运算符栈底元素;
2.依次读入表达式中每个字符,若是操作数则进OPND栈,若是运算符则和OPTR栈的栈顶元素比较优先级后做相应操作,直至整个表达式求值完毕(即OPTR栈的栈顶元素和当前读入的字符均为“#”)
先看一个例子,再来看代码,例子如下:
下面是代码:
这代码我修改了下,与书上有些地方不同(个人觉得书上的不太好)
OperandType EvaluateExpression() {
//算术表达式求值的算符优先算法。设OPTR和OPND分别为运算符和运算数栈,
//OP为运算符集合
StackChar OPTR; // 运算符栈,字符元素
StackFloat OPND; // 运算数栈,实数元素
float a,b;
char theta,c;
InitStack(OPTR);
Push(OPTR,'#');
InitStack(OPND);
c=Nextchar();
while(c!='#'||GetTop(OPTR)!='#') //OP为运算符集合
{
if(!In(c,OP))
{
Push(OPND,c); //操作数静OPND
c=Nextchar();
}
else
{
switch(Precede(GetTop(OPTR),c))
{
case '<': //栈顶元素优先权低
Push(OPTR,c);
c=Nextchar();
break;
case '=': //脱括号并接受下一字符
Pop(OPTR,c);
c=Nextchar();
break;
case '>': //退栈并将运算结果入栈
Pop(OPTR,theta);
Pop(OPND,b);
Pop(OPND,a);
Push(OPND,Operate(a,theta,b));
break;
}
}
}
return GetTop(OPND);
} // EvaluateExpression
下面是分析:
这里两个栈,OPTR和OPND,OPTR存运算符,OPND存数和结果。这里用Nextchar来获取表达式的字符,这里有个In函数,其中里面有个OP,就是判断获得的这个字符,是不是运算符。这里还有个Operate,这里的Operate不是C++里面的Operate,这个大家就把他理解成一个运算的函数,他可以把数a和运算符theta和数b进行处理,最后得出结果
思路每次读取字符,当发现是运算符时放入OPTR,当是数时放入OPND,还要判断优先级,当栈顶优先级低,则入栈,当相等时(只有栈顶为左括号,待入栈为右括号时才会相等)当栈顶优先级大的时后着出栈,运算。