栈的说明
-
表和数组:表是动态的数据结构,因为它的大小能够改变,而数组是静态的数据结构,因为它有固定的大小。大小可变的表在计算机中可以作为占据固定长度的数组的一部分实现,而数组中可能留有一部分未被使用
-
栈:一种形式的表。在栈这种数据结构中,所有的元素插入和删除都在表的一端进行,这一段称为栈顶 (top);当向栈中增加一项时,称之为入栈 (push),当从栈中删除一项时称之为出栈 (pop);最后压入栈中的项总是最先从栈中弹出的项,这种特征称为LIFO(last in, first out)
-
标准模板库STL:C++的标准模板库(通常称为STL)提供一个类来实现栈,该标准库包含了各种有用的信息、函数和类,STL是C++标准库的一部分。可以用命令
#include <stack>
将STL栈包含到程序当中,一旦包含了该库就可以定义并初始化置空的栈对象,并且运用push
,pop
,top
和empty
方法。 -
反转表:
int main()
/*Pre: The user supplies an integer n and n decimal numbers.
Post: The numbers are printed in reverse order.
Uses: The STL class stack and its methods. */
{
int n;
double item;
stack<double>numbers; //declares and initializes a stack of numbers
cout<<"Type in an integer n followed by n decimal numbers."<<endl<<"The numbers will be printed in reverse order."<<endl;
cin>>n;
for (int i=0; i<n;i++){
cin>>item;
numbers.push(item);
}
cout<<endl<<endl;
while(!numbers.empty()){
cout<<numbers.top<<" ";
numbers.pop();
}
cout<<endl;
}
STL栈实现是一个类模板,它的的一个重要特征是用户可以在<>之间说明类模板的参数(特定栈中存放的数据类型),正确地选择将哪些项放在栈中,如语句stack<double>numbers;
栈的实现
利用Stack类来实现数据结构,其基本操作有empty()
,top()
,push()
,pop()
以及设置空栈的初始化操作Stack::Stack()
- 泛型:对不同的元素类型使用同样的基本数据结构操作的能力称为泛型 (generic),使用
typedef
语句选择栈Stack里的元素类型即是实现C++的泛型数据结构的一种简单方法,如语句typedef char Stack_entry;
,typedef double Stack_entry;
- 错误代码:用诊断性的错误代码,指明客户程序非法使用Stack方法这样的问题。枚举类型Error_code是实用程序包的一部分,在实现Stack方法时将使用的 3 个Error_code值有
success
(成功),overflow
(上溢),underflow
(下溢)。 - 异常处理:C++提供了异常处理技术,当检测到一个错误即抛出一个异常时,客户程序能够捕获这个异常。栈和其他类的标准库实现使用异常处理去处理错误条件,简单起见,本书中所有异常处理的实现改为返回错误代码。
- 封装:对数据私有可见性的声明使得客户程序除了用正式的方法push(),pop(),top()以外不能够以其他方法获得或修改栈中存储的数据,数据完整性将得到保护,而给予Stack对象的这种保护可以概括的说它们被封装。一般说来,如果数据只由受控的函数集进行访问,则说它受到封装。对于一个被封装的类,不必担心非法的数据值,且其所有方法的前置条件都是“无”,这就意味着客户程序不需要在使用公共栈方法之前检查任何特殊条件,比如未初始化的栈。
顺序实现
Stack类及其操作的实现
- Stack的定义构成了文件
stack.h
const int maxstack=10; //small value for testing
class Stack{
public:
Stack;
bool empty()const;
Error_code pop();
Error_code top(Stack_entry &item)const;
Error_code push(const Stack_entry &item);
private:
int count;
Stack_entry entry[maxstack];
};
注意:int maxstack 前用 const 修饰,表示常量
- 入栈、出栈以及其它方法
Error_code Stack::push(const Stack_entry &item)
/*Pre: None.
Post: If the Stack is not full, item is added to the top of the Stack. If the Stack is full, an Error_code of overflow is returned and the Stack is left unchanged. */
{
Error_code outcome = success;
if (count>=maxstack)
outcome = overflow;
else
entry[count++]=item;
return outcome;
}
Error_code Stack::pop()
/*Pre: None.
Post: If the Stack is not empty, the top of the Stack is removed. If the Stack is empty, an Error_code of underflow is returned. */
{
Error_code outcome = success;
if (count==0)
outcome = underflow;
else
count--;
return outcome;
}
Error_code Stack::top(Stack_entry &item)const
/*Pre: None.
Post: If the stack is not empty, the top of the Stack is returned in item. If the stack is empty, an Error_code of underflow is returned. */
{
Error_code outcome = success;
if(count==0)
outcome = underflow;
else
item = entry[count-1];
return outcome;
}
bool Stack::empty()const
/*Pre: None.
Post: If the Stack is empty, true is returned. Otherwise, false is returned. */
{
bool outcome = true;
if(count>0)
outcome = false;
return outcome;
}
Stack::Stack()
/*Pre: None.
Post: The Stack is initialized to be empty. */
{
count = 0;
}
顺序栈练习2.2
- 为类Extended_stack的方法clear,full,size编写代码(使用私有数据成员)
class Extended_stack{
public:
Extended_stack();
Error_code pop();
Error_code push(Stack_entry item);
Error_code top(Stack_entry &item)const;
bool empty() cosnt;
void clear(); //Reset the stack to be empty.
bool full() const; //If the stack is full, return true; else return false.
int size() const; //Return the number of entries in the stack.
private:
int top;
Stack_entry entry[maxstack];
}
为clear,full,size方法编写代码(使用私有数据成员):
void Extended_stack::clear()
/*Post: reset the stack to be empty */
{
top=-1;
}
bool Extended_stack::full() const
/*Post: If the stack is full, return true; else return false. */
{
bool outcome = true;
if(top < maxstack-1)outcome = false;
return outcome;
}
int Extended_stack::size()const
/*Post: return the number of entries in the stack */
{
return top+1;
}
- 采用栈的方法为下面
Pre
和Post
的说明编写 3 个版本的函数copy_stack,并考虑在这 3 个版本中:
哪一个最容易编写?栈接近满时,哪一个运行最快?栈接近空时,哪一个运行最快?如果实现可被改变,哪一个是最好的方法?哪一个版本的函数可以将source作为一个常量引用传递?
Error_code copy_stack(stack &dest, Stack &source);
Pre: None.
Post: Stack dest has become an exact copy of Stack source; source is unchanged. If an error is detected, an appropriate code is returned; otherwise, a code of success is returned.
(1) 仅使用一个简单的赋值语句:
dest = source;
(2) 使用Stack方法和一个临时的Stack,从Stack source
中抽取元素并将每个元素加入到Stack dest
中,并恢复Stack scurce
(3) 为Stack类编写一个友元 (friend) 函数
(友元函数可以访问C++类中包括私有成员在内的的所有成员),使用Stack的私有数据成员并且写一个循环将元素从source
复制到dest
Error_code copy_stack(stack &dest, Stack &source)
{
Error_code outcome = success;
Stack_entry t;
outcome = dest.top(t);
outcome = dest.push(t);
dest.pop();
dest = source;
return outcome;
}
Error_code copy_stack(stack &dest, Stack &source)
{
Error_code outcome = success;
Stack temp;
Stack_entry t;
outcome = dest.top(t)
outcome = dest.push(t)
dest.pop();
while(!source.empty()){
source.top(t);
dest.push(t);
temp.push(t);
source.pop();
}
while(!temp.empty()){
temp.top(t);
source.push(t);
temp.pop();
}
return outcome;
}
Error_code copy_stack(stack &dest, Stack &source)
{
Error_code outcome = success;
if(source.count>maxstack)outcome = overflow;
if(source.count<0)outcome = underflow;
for (int i=1; i<=count ;i++){
dest.entry[count-i] = source.entry[count-i];
}
return outcome;
}
- 为下列函数编写代码(必须使用栈方法,但不用假设栈及其方法是如何真正实现的,对于一些函数可以声明和使用另一个临时的Stack对象)
①函数bool full(Stack &s)
没有改变Stack s并且根据Stack s是否已满而返回true或false值
bool full(Stack &s){
bool outcome = true;
int cnt=0;
Stack t;
Stack_entry temp;
while(!s.empty){
cnt++;
s.top(temp);
s.pop();
t.push(temp);
}
while(!t.empty()){
t.top(temp);
t.pop();
s.push(temp);
}
if(cnt<maxstack)outcome = false;
return outcome;
}
②函数Error_code pop_top(Stack &s, Stack_entry &t)
从Stack s中删除栈顶元素并且将它的值返回给输出参数 t
Error_code pop_top(Stack &s, Stack_entry &t)
{
Error_code outcome = true;
outcome = s.top(t);
outcome = s.pop();
return outcome;
}
③函数void clear(Stack &s)
删除所有元素
void clear(Stack &s)
{
while(!s.empty())s.pop();
}
④函数int size(Stack &s)
不改变栈Stack s并返回栈Stack中的元素个数
int size(Stack &s)
{
int cnt=0;
Stack t;
Stack_entry temp;
while(!s.empty){
cnt++;
s.top(temp);
s.pop();
t.push(temp);
}
while(!t.empty()){
t.top(temp);
t.pop();
s.push(temp);
}
return cnt;
}
⑤函数void delete_all(Stack &s, Stack_entry x)
从s中删除所有的x,并保持s中其他元素的相对次序不变
void delete_all(Stack &s, Stack_entry x)
{
Stack t;
Stack_entry y;
while(!s.empty()){
s.top(y);
s.pop();
if(y!=x)
t.push(y);
}
while(!t.empty()){
t.top(y);
t.pop();
s.push(y);
}
}
- 有时一个程序需要两个包含同样数据类型的栈(一般是一大一小),可令一个栈从数组的一段增长,另一个栈从另一端以相反的方向增长,以避免空间的浪费(直到空间真正用完)。定义
类Double_stack
,包含(作为私有数据成员)数组
和两个符号top_a
和top_b
,并且为方法Double_stack()
,push_a()
,push_b()
,pop_a()
,pop_b()
编写代码
const int maxstack = 15;
class Double_stack{
public:
Double_stack();
Error_code push_a(const Stack_entry &item);
Error_code push_b(const Stack_entry &item);
Error_code pop_a();
Error_code pop_b();
private:
Stack_entry entry[maxstack];
top_a;
top_b;
};
//push_a函数
Error_code Double_stack::push_a(const Stack_entry &item)
{
Error_code outcome = success;
if(top_a>=top_b-1)outcome = overflow;
else{
top_a+=1;
entry[top_a]=item;
}
return outcome;
}
//push_b函数
Error_code Double_stack::push_b(const Stack_entry &item)
{
Error_code outcome = success;
if(top_b<=top_a+1)outcome = overflow;
else{
top_b-=1;
entry[top_b]=item;
}
return outcome;
}
//pop_a函数
Error_code Double_stack::pop_a()
{
Error_code outcome = success;
if(top_a<0)outcome = underflow;
else top_a-=1;
return outcome;
}
//pop_b函数
Error_code Double_stack::pop_b()
{
Error_code outcome = success;
if(top_b>maxstack)outcome = underflow;
else top_b+=1;
return outcome;
}
应用1:桌面计算器
typedef double Stack_entry;
# include "stack.h"
int main(){
Stack stored_numbers;
introduction();
instructions();
while(do_command(get_command(),stored_numbers));
}
利用栈实现逆波兰计算:?
表示读取一个操作数并将其入栈,+
,-
,*
,/
表示算术运算符,=
表示打印栈顶命令
char get_command(){
char command;
bool waiting=true;
cout<<"Select command and press<Enter>:";
while(waiting){
cin>>command;
command = tolower(command);
if(command=='?'||command=='='||command=='+'||command=='-'||command=='*'||command=='/'||command=='q')waiting=false;
else{
cout<<"Please enter a valid command:"<<endl<<"[?]push to stack [=]print top"<<endl<<"[+][-][*][/] are arithmetic operations"<<endl<<"[Q]uit."<<endl;
}
}
return command;
}
辅助函数get_command()
从用户那里得到一个命令 (char),检查其是否合法并将它转化为小写。字符串函数tolower()
可以将字符传转化为小写,在标准头文件cctype
中定义。
bool do_command(char command, Stack &numbers)
/*Pre: The first parameter specifies a valid calculator command.
Post: The command specified by the first parameter has been applied to the Stack of numbers given by the second parameter. A result of true is returned unless command=='q'.
Uses: The class Stack. */
{
double p,q;
switch(command){
case'?':
cout<<"Enter a real number:"<<flush;
cin>>p;
if(numbers.push(p)==overflow)
cout<<"Warning: Stack full, lost number"<<endl;
break;
case'=':
if(number.top(p)==underflow)
cout<<"Stack empty"<<endl;
else
cout<<p<<endl;
break;
case'+':
if(numbers.top(p)==underflow)
cout<<"Stack empty"<<endl;
else{
numbers.pop();
if(numbers.top(q)==underflow){
cout<<"Stack has just one entry"<<endl;
numbers.push(p);
}
else{
numbers.pop();
if(numbers.push(q+p)==overflow)
cout<<"Warning: Stack full, lost result"<<endl;
}
}
break;
case'-':
if(numbers.top(p)==underflow)
cout<<"Stack empty"<<endl;
else{
numbers.pop();
if(numbers.top(q)==underflow){
cout<<"Stack has just one entry"<<endl;
numbers.push(p);
}
else{
numbers.pop();
if(numbers.push(q-p)==overflow)
cout<<"Warning: Stack full, lost result"<<endl;
}
}
break;
case'*':
if(numbers.top(p)==underflow)
cout<<"Stack empty"<<endl;
else{
numbers.pop();
if(numbers.top(q)==underflow){
cout<<"Stack has just one entry"<<endl;
numbers.push(p);
}
else{
numbers.pop();
if(numbers.push(q*p)==overflow)
cout<<"Warning: Stack full, lost result"<<endl;
}
}
break;
case'/':
if(numbers.top(p)==underflow)
cout<<"Stack empty"<<endl;
else if(p==0)
cout<<"The top of the stack equals to 0, division cannot be performed."<<endl;
else{
numbers.pop();
if(numbers.top(q)==underflow){
cout<<"Stack has just one entry"<<endl;
numbers.push(p);
}
else{
numbers.pop();
if(numbers.push(q/p)==overflow)
cout<<"Warning: Stack full, lost result"<<endl;
}
}
break;
case'q':
cout<<"Calculation finished.\n";
return false;
}
return true;
}
当且仅当用户输入为q
时,do_command
函数返回值为false,此时while循环终止。
应用2:括号的匹配
检查一个输入文本文件中的括号是否正确匹配。
——如何进行括号的匹配?
首先声明一个用于存储前括号的栈openings
,以及用于判断是否匹配的布尔型变量is_matched
,随后在while循环中判断客户端输入的字符symbol
:
如果是前括号则将它压入到栈中push()
;如果是后括号,先判断栈是否为空empty()
,如果空说明不匹配,如果不空就得到一个栈顶元素top()
并将栈顶元素弹出pop()
,通过逻辑运算符判断栈顶元素与用户输入的字符是否相匹配,若匹配则继续循环,直到括号不相匹配或用户输入字符为回车键(symbol=cin.get())!='\n'
为止。在跳出循环之后判断栈是否为空,若非空表示栈中还有前括号未被匹配完,则可以输出提示。
int main()
/*Post: The program has notified the user of any bracket mismatch in the standard input file.
Uses: The class Stack. */
{
Stack openings;
char symbol;
bool is_matched=true;
//若已有的后括号(closing)全部匹配,则继续循环直到输入回车
//若有出现没有前括号或与前括号不匹配,则立即退出循环
while(is_matched && (symbol=cin.get())!='\n'){
if(symbol=='{'||symbol=='['||symbol=='(')
openings.push(symbol);
if(symbol=='}'||symbol==']'||symbol==')'){
if(openings.empty()){
cout<<"Unmatched closing bracket"<<symbol<<"detected."<<endl;
is_matched=false;
}
else{
char match;
oenings.top(match);
openings.pop();
is_matched=(symbol=='}'&&match=='}'||symbol==']'&&match=='['||symbol==')'&&match=='(');
if(!is_matched)
cout<<"Bad match"<<match<<symbol<<endl;
}
}
}
//若在前括号(opening)全部匹配完之前输入回车,则输出提示
if(!openings.empty())
cout<<"Unmatched opening bracket(s) detected."<<endl;
}
抽象数据类型及其实现
- 原子类型:原子类型的值仅为单个元素,不会进行再分,如
int
,float
,char
… - 结构化类型:C++提供了如
数组
、类
和指针
这些工具,可以用于建立新的类型,称之为结构化类型,其值包括两种成分:它由组元元素构成,并且有一个结构来提供将这些组元元素放在一起的规则集合。 - 连续 VS 顺序:连续意味着元素从逻辑上形成一个序列,顺序意味着元素在内存中有相邻的地址。
- 抽象数据类型:Abstract Data Type,ADT. 任何抽象数据类型的定义包括两部分:第一是分量间彼此相关的方式的描述,第二是能对抽象数据类型的元素执行的操作的说明。
- 栈的抽象数据类型的定义(形式化定义):
元素类型为T的栈是T的元素的一个有限序列,同时带有如下操作:
1.创建一个栈并使它为空(create)
2.测试栈是否为空(empty)
3.倘若栈不满,则将一个新元素压入栈顶(push)
4.倘若栈不空,则从栈顶弹出一个元素(pop)
5.倘若栈不空,则检索栈顶元素(top)