数据结构
6.栈
从数据结构角度看,栈和队列仍属于线性结构,具有线性结构的共同特征;
其个性是:栈和队列是操作受限的线性结构;
后进先出(LIFO,Last In First Out)或先进后出(FILO,First In Last Out)结构,最先(晚)到达栈的结点将最晚(先)被删除。
栈顶(top):表中允许进行插入和删除操作的一端称为栈顶;
栈底(bottom):表的另一端叫做栈底;
进栈(Push):在栈顶位置插入元素的操作叫进栈,也叫入栈、压栈;
出栈(Pop):删除栈顶元素的操作叫出栈,也叫弹栈、退栈;
栈溢出:当栈满时,若再有元素进栈则发生上溢;当栈空时,若再出栈则发生下溢。
空栈:不含元素的空表称为空栈。
1.2顺序栈
常用数组的后端表示栈顶,top常被称为栈顶指针;
栈空时,栈顶指针top==-1;
入栈时,栈顶指针加1,即++top;
出栈时,栈顶指针减1,即top–。
顺序栈的类型定义
template <class T>
class seqStack
{
private:
T* data; //存放栈中元素的数组
int top; //栈顶指针,指向栈顶元素
int maxSize; //栈的大小
public:seqStack(int initSize=100);
~seqStack(){delete[]data;}
void clear(){top=-1;} //清空栈
bool empty()const{return top==-1;l}//判空
int size()const{return top+1;} //求长度
void push(const T&value); //进栈
T pop(); //出栈
T getTop()const; //取栈顶元素
};
类内声明函数名,类外定义函数内具体的代码
初始化一个空的顺序栈,置栈顶指针top为-1
template <class T>
seqStack<T>::seqStack(int initSize=100){
if(initSize<=0) throw badSize();
data=new T[initSize];
maxSize = initSize;
top=-1;
}
时间复杂度O(1),空间复杂度O(1)
1.进栈
template <class T>
void seqStack<T>s::push(const T&value){
if(top == maxSize-1)
{
cout<<“栈满\n”;
return;
}
data[++top] = value; //++top!!!!!!!!!!
时间复杂度O(1),空间复杂度O(1)
2.出栈
template <class T>
T seqStack<T>::pop(){
if(empty())throw outOfRange();//空栈无法弹栈
return data[top--]; //top--!!!!!!!!!!!
}
时间复杂度O(1),空间复杂度O(1)
应该指出:
“出栈”并不一定要带回元素,带回元素是“副产品”,
出栈的主要目的是下移指针,
是否要返回栈顶元素取决于题目要求。
3.取栈顶元素
template <class T>
T seaStack-T>::getTop()const{
if(empty())throw outOfRange();
return data[top];
}
时间复杂度O(1),空间复杂度O(1)
1.3 链栈的表示和实现
栈的操作都是在栈顶进行的,用不带头结点的单链表就足够了
只需要考虑栈顶元素的插入删除,可将单链表的头指针指向栈顶。
链栈的类型定义
template <class T>
class linkStack{
private:
struct Node{
T data;
Node* next;
Node(){next=NULL;}
Node(const T&value,Node *p = NULL)
{data=value;next=p;}
};
Node* top; //栈顶指针
public:
linkStack(){top = NULL;} //初始化空栈
~linkStack(){clear();}
void clear(); //清空
bool empty()const{return top == NULL;}//判空
int size()const; //求长度
void push(const T&value); //压栈
T pop(); //弹栈
T getTop()const; //取栈顶元素
};
1.进栈
template <class T>
void linkStack<T>::push(const T&value){//在栈顶插入元素
Node *p = new Node();
p->data = value;
p->next = top;
top = p; //p成为新的栈顶元素
}
时间复杂度O(1),空间复杂度O(1)
2.出栈
template <class T>
T linkStack<T>::pop(){
if(empty())throw outOfRange();//empty这个函数里可以写top==NULL
Node *p = top;
T value = p->data;//value保存栈顶元素的值
top = top->next; //top指向向后移动
delete p; //删除栈顶元素
return value;
}
时间复杂度O(1),空间复杂度O(1)
3.取栈顶元素
template <class T>
T linkStack<T>::getTop()const{
if(empty())throw outOfRange();
return top->data;
}
时间复杂度O(1),空间复杂度O(1)
4.清空栈
template <class T>
void linkStack<T>::clear(){
Node *p;
while(top != NULL){
p = top;//p指向当前栈顶元素
top=top->next;//top指针移向次栈顶元素
delete p;//释放p指向的当前栈顶元素
}
}
时间复杂度O(n),空间复杂度O(1)
5.求栈中元素的个数
template <class T>
int linkStack<T>::size()const{
Node *p = top;
int count = 0;//计数器
while(p){ //遍历栈,统计元素总数
count++;
p=p->next;
}
return count;
}
时间复杂度O(n),空间复杂度O(1)
总结
1.栈是一种后进先出的线性表
2.栈可以用顺序实现,也可以用链接实现
3.大部分操作只需常数时间,顺序栈和链式栈在时间效率上难分伯仲
4.实际应用中,顺序栈比链栈用得更广泛些,因为顺序栈存储开销较低,并且能够以O(1)的时间复杂度快速定位并读取栈中元素,而在链栈中读取第i个元素时需要沿着指针查状,时间复杂度O(n)。
1.4栈的应用
中缀式求值
(中缀式指运算符在操作数中间的式子,例如a+b;后缀式顾名思义,例如ab+)
假定表达式中只有运算符和操作数两类成分,运算符有加+,减-,乘*,除/,以及圆括号()。
设置两个栈:
运算符栈(optr)(全名operator)、操作数栈(opnd)(全名operand)
算法思想:
首先置操作数栈opnd为空,将表达式起始符‘=’压入运算符栈optr作为栈底元素,然后从左向右扫描用于存储中缀表达式的infix数组,读入字符infix[i],直至表达式结束,
1.若infix[i]是数字字符就拼成数字,然后压入opnd栈,
2.若infix[i]为运算符,则按以下规则进行:
(1)若infix[i]的优先级高于optr栈顶的运算符,则infix[i]入optr栈;
(2)若infix[i]的优先级低于optr栈顶的运算符,则弹出optr栈顶的运算符,并从opnd栈弹出两个操作数,进行相应算数运算后,结果压入opnd栈;
中缀转后缀
后缀表达式的优点是计算机处理过程非常简单方便,这主要是因为:
(1)后缀表达式中不包含括号;
(2)不必考虑运算符的优先规则;
(3)运算符放在两个运算对象的后面,按照运算符出现的顺序,从左向右进行计算。
必会手工模拟
设一字符栈optr,并设中缀表达式和后缀表达式分别存放在字符数组infix和postfix中。从左向右扫描infix:
(1)若infix[i]是‘(’,压入栈optr中;
(2)若infix[i]是操作数,将infix[i]直接送入postfix;
(3)若infix[i]是运算符,且其级别比栈顶元素的级别高,则infix[i]入栈;否则,栈顶元素退栈,进入postfix,infix[i]再与新栈顶元素比较,重复步骤(3);
(4)若infix[i]为‘)’,则将栈中元素依次送postfix,直至碰到‘’,然后‘(’入栈,消掉一对括号。
(5)当遇到infix的结束符‘=‘时,开始执行退栈操作,并将退栈元素直接存入postfix,直至栈空。最后在postfix中存入\0’作为后缀表达式结束标志。
后缀式求值
对后缀表达式求值
(1)设置一操作数栈opnd;
(2)从左到右扫描后缀表达式,直到遇到结束标志‘\0’;
a)若读到的是操作数,则将其进栈;
b)若读到的是运算符,则将栈顶的两个操作数出栈,后弹出的操作数为被操作数,先弹出的为操作数,将得到的操作数完成运算符所规定的运算,并将结果进栈;
c)若读到的是空格,则跳过它;
(3)表达式扫描完毕,栈中剩一个数,即表达式的值。
附:中缀表达式转为后缀表达式——简便方法
**(1)加括号:**将中缀表达式中所有的子表达式按计算规则用嵌套括号括起来;
**(2)移运算符:**将每对括号中的运算符移到相应括号的后面;
**(3)删括号:**删除所有括号。
例如,将中缀表达式12*(6/2-0.5)转为后缀表达式:
步骤(1):(12*((6/2)-0.5));
步骤(2):(12((6 2)/0.5)-)*;
步骤(3):12 6 2 / 0.5 - *,即为所求后缀表达式。
可用类似方法将中缀表达式转为前缀表达式。
递归
递归是一种特殊的函数调用,是在一个函数中又调用了函数本身。
在系统内部,函数调用是用栈来实现,如果程序员可以自己控制这个栈,就可以消除递归调用。
递归过程的应用:
问题的定义是递归的:f(n)=n*f(n-1)
数据结构是递归的:链表
问题的解法是递归的:Hanoi塔问题
递归求解n的阶乘
int Fact(int n){
if(n==0||n==1) return 1;
else return (n * Fact(n-1));
}
//使用循环迭代方法,计算阶乘n!的程序
int fact(int n){
int m=1;
int i;
if(n>1)
for(i=1;i<=n;i++)
m=m*i;
return m;
}