顺序栈
顺序栈的定义
typedef struct{//顺序栈结构体
int data[Maxsize];//静态数组存放栈中元素
int top;//栈顶指针
}Sqstack;
top指针指向栈顶元素
top指针初始化为-1,压入元素后指针指向栈顶空间
顺序栈的初始化
void InitStack(Sqstack& S)//初始化栈
{
S.top = -1;//初始化栈顶指针
}
顺序栈的判断空栈
bool StackEmpty(Sqstack S)//判断栈是否为空
{
if (S.top == -1)return 1;//判断指针是否为-1
else return 0;
}
顺序栈的压栈操作
bool Push(Sqstack& S, int x)//压栈操作
{
if (S.top == Maxsize - 1)//栈满返回失败
return 0;
S.data[++S.top] = x;//元素入栈
return 1;
}
顺序栈的弹栈操作
bool Pop(Sqstack& S, int& x)//弹栈 x为指针变量接收被弹出的元素
{
if (S.top == -1)//栈空报错
return 0;
x = S.data[S.top--];//栈顶指针减一弹出栈顶元素元素
return 1;
}
top指针指向下一个可以压栈的位置
顺序栈的初始化
void InitStack(Sqstack& S)//初始化栈
{
S.top = 0;//初始化栈顶指针为0
}
顺序栈的判断空栈
bool StackEmpty(Sqstack S)//判断栈是否为空
{
if (S.top == 0)return 1;//判断指针是否为-1
else return 0;
}
顺序栈的压栈操作
bool Push(Sqstack& S, int x)//压栈操作
{
if (S.top == Maxsize)//栈满返回失败
return 0;
S.data[S.top++] = x;//元素入栈(先入栈指针后加一)
return 1;
}
}
顺序栈的弹栈操作
bool Pop(Sqstack& S, int& x)//弹栈 x为指针变量接收被弹出的元素
{
if (S.top == 0)//栈空报错
return 0;
x = S.data[--S.top];//栈顶指针先减一 弹出栈顶元素元素
return 1;
}
共享栈
两个栈共享一片空间,两个栈分别以一个数组的0位和MAX位作为栈底
共享栈的定义
typedef struct {
int data[Maxsize];
int top0; //0号栈顶指针
int top1; //1号栈顶指针
}ShStack;
共享栈的初始化
void InitStack(ShStack& S)//初始化栈
{
S.top0 = -1; //0号栈顶指针指向-1
S.top1 = Maxsize; //1号栈顶指针指向Max
}
共享栈判满
共享栈判断满的条件:top0+1==top1
bool StackFull(ShStack& S)//判断共享栈是否满了
{
if (S.top0 + 1 == S.top1)return 1;
else return 0;
}
链栈
链栈的定义
typedef struct Linknode{//链栈(与链表定义相同)
int data;
Linknode* next;
}*Listack;
带头结点链栈
栈顶指针指向头结点
链栈初始化
bool InitListack(Listack& L)//初始化链表头结点
{
L = new Linknode;
if (L == NULL)return 0;//分配失败
L->next = NULL;
return 1;
}
链栈压栈
bool Push(Listack& L, int x)//压栈
{
Linknode* p = new Linknode;
if (p == NULL)return 0;//分配空间失败
p->next = NULL;
p->data = x;
if (L->next == NULL)//链表为空
L->next = p;
else //链表不为空
{
p->next = L->next;
L->next = p;
}
return 1;
}
链栈弹栈
bool Pop(Listack L, int& x)//弹栈 x指针接收被弹出的元素
{
Linknode* p = L->next;
if (L->next == NULL)return 0;//空链表返回错误
x = p->data;//x指向被弹出元素
L->next = p->next;
delete p;
}
不带头结点链栈
栈顶指针指向第一个结点
链栈初始化
bool InitNListack(Listack& L)//不带头结点的链表初始化
{
L = NULL;//空表防止脏数据
return 1;
}
链栈压栈
bool NPush(Listack& L, int x)//压栈 头插法
{
Linknode* p = new Linknode;
if (p == NULL)return 0;//分配空间失败
p->data = x;
p->next = NULL;
if (L == NULL)//空表
{
L = p;
}
else//头插法插入结点
{
p->next = L;
L = p;
}
return 1;
}
链栈弹栈
bool NPop(Listack& L, int& x)//弹栈 x接收弹出元素 删除首结点
{
if (L == NULL)return 0;//空表报错
Linknode* p = L;
x = L->data;
L = L->next;
delete p;
return 1;
}
栈的应用
括号匹配
遇到左括号依次入栈,遇到右括号则消耗一个左括号(栈内左括号出栈一个),出栈左括号与右括号匹配,若匹配成功继续,失败则匹配失败;遇到右括号而栈已经空则匹配失败;扫描完了所有的右括号而栈内仍存在左括号,则匹配失败
bool bracketcheck(char str[],int length)//括号匹配
{
Sqstack S;
InitStack(S);
for (int i = 0; i < length; i++)
{
if (str[i] == '(' || str[i] == '[' || str[i] == '{')//遇到左括号入栈
Push(S, str[i]);
else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {//遇到右括号
if (StackEmpty(S))return 0;//扫描到右括号时栈空返回0
char Topelem;
Pop(S, Topelem);//扫描到右括号栈不为空出栈
if (str[i] == '(' && Topelem != ')')return 0;//括号类型匹配失败返回0;
if (str[i] == '[' && Topelem != ']')return 0;
if (str[i] == '{' && Topelem != '}')return 0;
}
}
return StackEmpty(S);//匹配完所有右括号最后扫描栈是否为空
}
表达式求值问题
中缀表达式
必须要有界限符
中缀转后缀的手算方法:
1.确定中缀表达式各个运算符的运算顺序
2.选择下一运算符,按照【左操作数、右操作数、运算符】的方式组合成一个新的操作数
3.如果还有运算符没被处理,继续2
手算时为了保证与计算结果保持一致采取左优先原则:只要左边的运算能先计算就先算左边的
中缀表达式转后缀表达式机算:
初始化一个栈用于保存暂时还不能确定运算顺序的元素符从左到右处理各个元素,直到末尾,可能遇到三种情况
1.遇到操作数,直接接入后缀表达式
2.遇到界限符‘(’直接入栈;遇到‘)’则依次弹出栈内运算符并加入后缀表达式,直到弹出‘(’或者栈空为止 注意:‘(’不加入后缀表达式
3。遇到运算符:依次弹出栈内优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到‘(’或者栈空则停止。之后再把当前运算符入栈。
按上诉方法处理完所有字符后,将栈种剩余运算符依次弹出,并加入后缀表达式。
中缀转前缀的手算方法:
1.确定中缀表达式中各个运算符的运算顺序
2.选择下一个运算符,按照【运算符 左操作数 右操作数】的方式组合成一个新的操作数
右优先原则:只要右边的运算能先计算就先算右边的
中缀表达式的机算:
初始化两个栈:操作数栈、运算符栈
若扫描到操作数则压入操作数栈,若扫描到运算符或则界限符则按照中缀转后缀相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
后缀表达式(逆波兰表达式 Reverse Polish notation)
可以不需要界限符
用栈实现后缀表达式的运算:
1.从左向右扫描所有元素直到扫描完所有元素
2.若扫描到操作数则压入栈并回到1;否则执行3
3.若扫描到运算符,则弹出两个栈顶元素,执行相应算法,运算结果压回栈顶,回到1.
前缀表达式(波兰表达式 Polish notation)
可以不需要界限符
用栈实现前缀表达式的计算:
1.从左往右扫描下一个元素,直到处理完所有元素
2.若扫描到操作数则压入栈,并返回到1;否则执行2
3.若扫描到运算符则弹出两个栈顶元素,执行相应运算,运算结果压回栈,回到1.
递归
递归调用的特点:最后调用的函数最先执行结束
适合用递归算法解决:可以把原始问题转换为属性相同,但规模较小的问题
缺点:太多层调用会导致栈溢出;可能会包含很多次的重复运算