栈基础
比较基础,但很重要,一般写程序时,都会用到或多或少的数据结构。
栈
栈(Stack):是限定在表的一端进行插入和删除运算的线性表,通常将插入、删除的一端称为栈项(top),另一端称为栈底(bottom)。不含元素的空表称为空栈。
栈的修改是按后进先出的原则进行的,因此,栈又称为后进先出(Last In First Out)的线性表,简称为LIFO表。
栈的基本运算
(1)置空栈InitStack(&S):构造一个空栈S。
(2)判栈空StackEmpty(S):若栈S为空栈,则返回TRUE,否则返回FALSE。
(3)判栈满StackFull(S):若栈S为满栈,则返回TRuE,否则返回FALSE。
(4)进栈(又称入栈或插入):Push(&S,x):将元素x插入S栈的栈顶。
(5)退栈(又称出栈或删除)Pop(&s):若栈S为非空,则将S的栈顶元素删除,并返回栈顶元素。
(6)取栈顶元素GetTop(S):若S栈为非空,则返回栈顶元素,但不改变栈的状态。
顺序栈
栈的顺序存储结构称为顺序栈。顺序栈也是用数组实现的,栈底位置是固定不变的,将栈底位置设置在数组的最低端(即下标为0);栈顶位置是随着进栈和退栈操作而变化的,一个整型量top来指示当前栈顶位置,通常称top为栈顶指针,是一个假指针。
顺序栈数据结构描述
#define stackSize 100 //栈空间的大小应根据实际需要来定义,这里假设为100
tvpedef char DataType;//DataType的类型可根据实际情况而定,这里假设为char
typedef Struct {
DataType data[stackSize]; //数组data用来存放表结点
int top; //表示栈顶指针
} SeqStack; //栈的数据类型
SeqStack s; //s为栈类型的变量
顺序栈运算实现
置空栈
void InitStack(SeqStack *s)
{
s->top=-1;//也就是数组下标0,-1代表了数组不可能的元素。
}
判栈空
int StackEmpty(SeqStack *S)
{
return S->top==-1;
)
判栈满
int StackFull(SeqStack *S)
{
return S->top==StackSize-1;//如果栈满则S->top== StackSize -1的值为1,返回1,反之,返回0
}
入栈
void Push(SeqStack *S,DataType x)
{
if (StackFull(S)) //调用判满函数
printf("stack overflow");
else {
S->top = S->top + 1; //栈顶指针加1
S->data[S->top] = x; //将x入栈
}
}
出栈
DataType Pop(SeqStack *S)
{
if (StackEmpty(S)) { //调用判空函数
printf("Stack underflow");
exit(0); //出错退出处理
} else
return S->data[S->top--];//返回栈顶元素,栈顶指针减1
}
取栈顶元素
DataType GetTop(SeqStack *S)
{
if (StackEmpty(S)) { //调用判空函数
printf("stack empty");
exit(0); //出错退出处理
} else
return S->data[S->top] //返回栈顶元素,注意:栈顶指针不动
}
设计技巧
可以一个数组空间装两个栈,栈底在数组两端,栈顶各自向中间移动。
链栈
栈的链式存储结构称为链栈,它是运算受限的单链表,其插入和删除操作仅限制在表头位置上(栈顶)进行,因此不必设置头结点,将单链表的头指针head改为栈顶指针top即可。
链栈数据结构描述
typedef struct stacknode {
DataType data;
struct stacknode *next;
} StackNode;
定义结点
typedef StackNode *Linkstack;
LinkStack top;//定义栈顶指针top
链栈上运算实现
判栈空
int StackEmpty(LinkStack top)
{
return top == NULL ; // 栈空top==NULL的值为1,返回1,否则返回0
}
入栈
LinkStack Push(LinkStack top,DataType x) //将x插入栈顶
{
//无需判满
StackNode *P;
p = (StackNode *)malloc(Sizeof(StackNode)); //申请新的结点
p->data = x; //申请新的结点
p->next = top; //新结点*p插入栈顶
top = p; //更新栈顶指针top
return top;
}
出栈
LinkStack Pop(LinkStack top, DataType *x)
{
StackNode *p = top; //保存栈顶指针
if (StackEmpty(top)) { //栈为空
printf("stack empty"); //出错退出处理
exit(0);
} else {
*x = p->data; //保存删除结点值,并带回
top = p->next; //栈顶指针指向下一个结点
free(p); //删除P指向的结点
return top; //并返删除后的栈顶指针
}
}
取栈顶元素
DataType GetTop(LinkStack top)
{
if (StackEmpty(top)) { //栈为空
printf("stack empty");
exit(0); //出错退出处理
}
else
return top->data; //返回栈项结点值
}
应用
圆括号匹配的检验
对于输入的一个算术表达式字符串,试写一算法判断其中圆括号是否匹配,若匹配则返回TRUE,否则返回FALSE。
分析
利用栈的操作来实现:循环读入表达式中的字符,如遇左括号"(“就进栈;遇右括号”)"则判断栈是否为空,若为空,则返回FALSE,否则退栈;循环结束后再判断栈是否为空,若栈空则说明括号匹配,否则说明不匹配。
算法实现
int Expr()
{
SeqStack S;
DataType ch, x;
InitStack(&S); //初始化栈S
ch = getchar();
while (ch != '\n') {
if (ch == '(')
Push(&S, ch); //遇左括号进栈
else if (ch == ')')
if (StackEmpty(&S)) //遇右括号如果栈空,说明不匹配,返回0
return 0;
else
x = Pop(&s); //遇右括号退栈
ch = getchar(); //读入下一个字符
}//end of while
if (StackEmpty(&S)) return 1; //最后栈空,说明匹配,返回1
else return 0;
}
字符串回文的判断
利用顺序栈的基本运算,试设计一个算法,判断一个输入字符串是否具有中心对称(也就是所谓的"回文",即正读和反读均相同的字符序列),例如ababbaba和abcba都是中心对称的字符串
分析
从中间向两头进行比较,若完全相同,则该字符串是中心对称,否则不是。这就要首先求出字符串串的长度,然后将前一半字符入栈,再利用退栈操作将其与后一半字符进行比较。
算法实现
int symmetry(char str[ ])
{
SeqStack S;
int j, k, i = 0;
InitStack(&S);
while (Str[i] != '\0') i++; //求串长度
for (j = 0; j < i / 2; j++)
Push(&s, str[j]); //前一半字符入栈
k = (i + 1)/2; //后一半字符在串中的起始位置,下标从0开始的话,奇数的话就是中间,两个字符个数相等
//特别注意这条命令怎么处理奇偶数个字符的。 偶数的话,中间位置下一个位置,所以这个(i+1)/2很关键
for (j = k; j < i; j++) //后一半字符与栈中字符比较
if (str[j] != Pop(&s))
return 0; //有不相同字符,即不对称
return 1; //完全相同,即对称
}
将一个非负的十进制整数N转换成d进制
分析
将一个非负的十进制整数N转换成d进制的方法:N除以d,求出每一步所得的余数,然后将所有余数逆序书写就是十进制整数N对应的d进制数。
算法实现
void conversion(int N, int d)
{
//将一个非负的十进制数N转换成任意的d进制数
SeqStack S;
InitStack(&S)
while (N) {
Push(&S, N % d); //N除以d的余数入栈
N = N / d; //N除以d的商
}
while (!StackEmpty(&S)) {
i = Pop(&S);
printf("%d", i); //出栈完成余数倒序输出
}
}
递归与栈
栈还有一个非常重要的应用就是在程序设计语言中实现递归。一个直接调用自己或间接调用自己的函数,称为递归函数。
递归是程序设计中一个强有力的工具,递归算法有两个关键条件:一是有一个递归公式;二是有终止条件。
阶乘
求n的阶乘可递归地定义为
算法实现
#include <stdio.h>
long int fact(int n)
{
int temp;
if (n == 0)
return 1; //递归终止条件
else
temp = n * fact(n - 1); //递归公式
r12:
return temp;
}
//void main() //主函数
int main(int argc, char const *argv[])
{
long int n;
n = fact(5); //调用fact()函数求5!
r11:
printf("5!=%ld", n);
return 0;
}
过程分析
调用层次 | 调用 | 参数n | 返回地址 | temp结果 | 退栈时计算结果 |
---|---|---|---|---|---|
↑5 | fact(0) | 0 | r12 | 1 | ↓ |
↑4 | fact(1) | 1 | r12 | 1* fact(0) | 1*1=1↓ |
↑3 | fact(2) | 2 | r12 | 2*fact(1) | 2*1=2↓ |
↑2 | fact(3) | 3 | r12 | 3*fact(2) | 3*2=6↓ |
↑1 | fact(4) | 4 | r12 | 4*fact(3) | 4*6=24↓ |
↑0 | fact(5) | 5 | r11 | 5*fact(4) | 5*24=120返回 |
进栈 | 主函数打印5!=120 退栈 |