二、堆栈
堆栈是一组元素的集合,类似于数组,不同之处在于,数组可以按下标随机访问,这次访问a[5]
下次可以访问a[1]
,但是堆栈的访问规则被限制为Push和Pop两种操作,Push(入栈或压栈)向栈顶添加元素,Pop(出栈或弹出)则取出当前栈顶的元素,也就是说,只能访问栈顶元素而不能访问栈中其他元素。如果所有元素的类型相同,堆栈的存储也可以用数组来实现,访问操作可以通过函数接口提供,观察如下的示例程序:
例12.1 用堆栈实现倒序打印
#include<stdio.h>
char stack[512];
int top=0;
void push(char c){
stack[top++]=c;
}
char pop(void){
return stack[--top];
}
int is_empty(void){
return top==0;
}
int main(void){
push('a');
push('b');
push('c');
while(!is_empty())
putchar(pop());
putchar('\n');
return 0;
}
输出结果是cba
,运行过程如下所示:
这里数组stack是堆栈的存储空间,变量top总是保存数组中栈顶的下一个元素的下标,我们说“top总是指向栈顶的下一个元素”,或者把top叫做栈顶指针(Pointer)。在插入排序部分介绍了Loop Invariant的概念,我们可以用它检验循环的正确性,这里的”top总是指向栈定元素的下一个元素“其实也是一种Invariant,Push和Pop操作总是维持这个条件不变,这种Invariant描述的对象是一个数据结构而不是一个循环,在DbC中称为Class Invariant。Pop操作的语义是取出栈顶元素,但上例的实现其实没有清除原来的栈顶元素,只是把top指针移动了以下,原来的栈顶元素依然存在那里,这就足够了,因为此后通过Push和Pop操作不可能再访问到已经取出的元素,下次Push操作就会覆盖它。
堆栈这种数据结构的特点可以概括为LIFO(Last In First Out,后进先出)。我们也可以通过一个递归函数倒序打印,利用函数调用栈帧实现后进先出:
// 例12.2 用递归实现倒序打印
#include<stdio.h>
#define LEN 3
char buf[LEN]={'a','b','c'};
void print_backward(int pos){
if(pos==LEN)
return;
print_backward(pos+1);
putchar(buf[pos]);
}
int main(void){
print_backward(0);
putchar('\n');
return 0;
}