栈
栈定义:只允许在表的末端进行插入和删除的线性表。栈是后进先出。
栈的抽象数据类型有两种典型的存储表示:基于数组的存储表示的顺序栈和基于链表的存储表示的链式栈。
栈的类定义:
const int maxSize=50;
enum bool{false,true};
template<class T>
class Stack{
public:
Stack(){};
virtual void Push(const T& x)=0;
virtual bool Pop(T& x)=0;
virtual bool getTop(T& x) const=0;
virtual bool IsEmpty() const=0;
virtual bool IsFull() const=0;
virtual int getSize() const=0;
}
顺序栈
在顺序栈的声明中用顺序表定义它的存储空间。当前栈顶位置由数组下标指针top指示(这里的指针其实就是标记数组下标的一种表示)。
顺序栈的类定义:
#include <assert.h>
#include <iostream.h>
#include "stack.h"
const int stackIncerament = 20;//栈溢出时扩展空间的增量
template <class T>
class SeqStack: public Stack<T>{
public:
SeqStack();
~SeqStack(){delete[]elements;}
void Push(const T& x);
bool Pop(T& x);
bool getTop(T& x);
bool IsEmpty()const {return (top==-1)?true:false;}
bool IsFull()const {return (top==maxSize-1)?true:false;}
int getSize() const {return top+1;}
void MakeEmpty() {top=-1;}
friend ostream& operator<<(ostream& os,SeqStack<T>& s){...}
private:
T *elements;
int top;
int maxSize;
void overflowProcess();
};
链式栈
链式栈是线性表的链接存储表示。采用链式栈来表示一个栈,便于结点的插入与删除。在程序中同时使用多个栈的情况下,用链接表示不仅能够提高效率,还可以达到共享存储空间的目的。
链式栈的栈顶在链表的表头,因此,新结点的插入和栈顶结点的删除在链表的表头,即栈顶进行。
栈的应用——表达式的计算
算术表达式的后缀表示:<操作数><操作数><操作符>例如 AB+。只需要一个栈,而前缀和后缀需要用两个栈。
C++中操作符的运算优先级
优先级 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
操作符 | 单目—,! | *,/,% | +,- | <,<=,>,>= | ==,!= | && | || |
利用后缀表示求解表达式的值时,从左向右顺序地扫描表达式,并使用一个栈暂存扫描到的操作数或计算结果。
后缀表示计算表达式值的过程:顺序扫描表达式的每一项,然后根据它的类型做如下相应操作:如果该项是操作数,则将其压入栈中;如果该项是操作符<op>
,则连续从栈中退出两个操作数Y和X,形成运算指令X< op >Y,并将计算结果重新压入栈中。当表达式的所有项都扫描并处理完后,栈顶存放的就是最后的计算结果。
利用栈将中缀表示转换为后缀表示
操作符在栈内和栈外优先数表(isp是栈内优先数,icp是栈外优先数)
操作符ch | # | ( | *,/,% | +,- | ) |
---|---|---|---|---|---|
isp | 0 | 1 | 5 | 3 | 2 |
icp | 0 | 6 | 4 | 2 | 1 |
算法描述:
-
操作符栈初始化,将结束符#进栈。然后读入中缀表达式字符流的首字符ch。
-
重复执行以下操作,直到遇到ch=‘#’,同时栈顶的操作符也是‘#’,停止循环。
a. 若ch是操作数直接输出,读入下一个字符ch。
b. 若ch是操作符,判断ch的优先级icp和当前位于栈顶的操作符op的优先级isp:- 若icp(ch) > isp(op),令ch进栈,读取下一个字符ch。
- 若icp(ch)<isp(op),退栈并输出。
- 若icp(ch)==isp(op),退栈但不输出,若退出的是’('号读入下一个字符ch。
c. 算法结束
算法实现:
void postfix(expression e){
Stack<char> s;
char ch = '#',ch1,op;
s.Push(ch);
cin.Get(ch);//读入一个字符
while(s.IsEmpty()==false && ch!='#')
if(isdigit(ch) {cout<<ch; cin.Get(ch);}
else{
s.getTop(ch1);
if(isp(ch1)<icp(ch)){
s.Push(ch);
cin.Get(ch);}
else if(isp(ch1)>icp(ch)){
s.Pop(op);
cout<<op;}
else{
s.Pop(op);//优先数相等出现在括号配对或者栈底‘#’与输入流最后的‘#’匹配
if(op=='(') cin.Get(ch);
}
}
}
栈与递归
3种用到递归的方法:
- 定义时递归的。例如阶乘
- 数据结构是递归的。例如链表
- 问题的解法是递归的。例如汉诺塔
汉诺塔
将n个盘子从A移到C中。如果n=1,则将这个盘子直接从A柱移到C柱,否则,执行以下3步:
- 用C柱做过渡,将A柱上的(n-1)个盘子移到B柱上;
- 将A柱上最后一个盘子直接移到C柱上;
- 用A柱当做过渡,将B柱上的(n-1)个盘子移到C柱上。
算法:
#include<iostream.h>
#include<string.h>
void Hanoi(int n,String A,string B,string C){//将n个盘子从A移到C
if(n==1)
cout<<“move top disk from peg” << A << "to peg" << C <<end;
else{
Hanoi(n-1,A,C,B);//将n-1个盘子从A移到B,借助C柱
cout<<"Move top disk from peg" << A <<"to peg" << C <<endl;//最后一个移到C柱
Hanoi(n-1,B,A,C);//将B柱n-1个盘子移到C柱
}
};
用栈实现递归过程的非递归算法
斐波那契数列。
递归过程:
long Fib(long n){
if(n<=1) return n;
else return Fib(n-1)+Fib(n-2);
用栈帮助求解斐波那契的非递归算法:
struct Node{//用栈记录回退路径,tag=1是向左递归,tag=2是向右递归
long n;
int tag;
};
long Fibnacci(long n){
Stack<Node> S; Node *w; long sum=0;
do{
while(n>1){w->n = n; w->tag=1; S.push(w); n--;}
sum = sum+n;
while(S.IsEmpty()==false){
S.Pop(w);
if(w->tag==1){
w->tag=2;S.push(w);n=w->n-2;break;}
}
}while(S.IsEmpty()==false);
return sum;
};
迭代法计算斐波那契数列:
long FibIter(long n){//计算第n项斐波那契数列
if(n<=1) return n;
long twoback=0; oneback=1; Current;
for (int i=2; i<=n; ++i){
Current=twoback+oneback;
twoback=oneback;
oneback=Current;
}
return Current;
};
队列
队列是另一种限定存取位置的线性表。只允许在表的一端插入,在另一端删除,允许插入的一端叫做队尾,允许删除的一端叫做队头,具有先进先出特点。
循环队列
基于数组的存储表示。设置两个指针front和rear,分别指示队列的队头和队尾位置。
每当加入一个新元素时,先将新元素添加到rear所指位置,再让队尾指针rear进1;如果要退出队头元素,应当首先把front所指位置上的元素值记录下来,再让队头指针front进1,最后把记录下的元素值返回。
为了避免假溢出,利用循环队列。当front和rear进到maxSize-1,再前进一个位置就自动到0,这可以利用除法取余的运算来实现。
队头指针进1:front=(front+1)%maxSize;
队尾指针进1:rear=(rear+1)%maxSize;
判断队空条件,用(rear+1)%maxSize == front,让rear指到front的前一位置就认为队已满。所以在队满情形实际空了一个元素位置。在循环队列中,最多只能存放maxSize-1个元素。
链式队列
基于单链表的一种存储表示。队列的队头指针指向单链表的第一个结点,队尾指针指向单链表的最后一个结点。
应用:杨辉三角等
优先队列
每次从队列中取出的应是具有最高优先权元素。
双端队列
可以在队列的两端进行插入和删除。可以基于数组或者链表表示。