栈定义
栈是只允许在表的一段进行插入、删除操作的线性表,
特点:后进先出、先进后出
顺序栈
定义:利用顺序存储结构实现的栈。
元素是用一个一维数组来存储,用一个整型变量top存放栈顶元素的位置,top称为栈顶指针。
初始时,top=-1,表示栈为空;进栈加一退栈减一。
若现在有一个栈,StackSize是5,则栈的普通情况、空栈、满栈的情况分别如下图所示:
顺序栈的类型描述如下:
#ifndef _SEQSTACK_H_
#define _SEQSTACK_H_
#include "Stack.h"
template <class T>
class seqStack : public Stack<T> {
private:
T * data; //存放栈中元素的数组
int top; //栈顶指针,指向栈顶元素,保存栈顶指针下标
int maxSize; //栈的大小
void resize(); //扩大栈空间
public:
seqStack(int initSize = 100);
~seqStack(){ delete [] data;}
bool empty() const{ return top == -1;} //判空
int size() const{ return top+1; } //求长度
void clear() { top = -1; } // 清空栈内容
void push(const T &value); //进栈
T pop(); //出栈
T getTop() const; //取栈顶元素
};
出来进栈和扩大栈空间,所有的时间复杂度都是O(1).
进栈
先判断栈是否已满,若已满则重新申请空间,否则将出现上溢出错误。
template <class T>
void seqStack<T>::push(const T &value) {
if (top == maxSize - 1) //检查顺序栈是否已满
resize();
data[++top] = value; //修改栈顶指针,新元素入栈
}
出栈
先判断栈是否为空,若空栈去操作会产生下溢错误。
出栈不一定要返回元素,主要目的是下移指针。
template <class T>
T seqStack<T>::pop(){
if (empty()) //空栈无法弹栈
throw outOfRange();
return data[top--]; //修改栈顶指针,返回栈顶元素
}
取栈顶元素
若允许修改返回的栈顶元素,则考虑使用引用作为函数的返回值
template <class T>
T seqStack<T>::getTop() const{
if (empty())
throw outOfRange();
return data[top];
}
其他
构造函数
初始化一个空的顺序栈,置栈顶指针top为-1
template <class T>
seqStack<T>::seqStack(int initSize = 100) {
if (initSize <= 0)
throw badSize();
data = new T[initSize];
maxSize = initSize;
top = -1;
}
扩大表空间
template <class T>
void seqStack<T>::resize(){
T* tmp = data;
data = new T[2 * maxSize];
for (int i = 0;i < maxSize;i++) {
data[i] = tmp[i];
}
maxSize *= 2;
delete [] tmp;
}
共享栈
利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示:
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;
仅当两个栈顶指针相邻(top0+1=top1)时,判断为栈满。
当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减一再赋值出栈时则刚好相反。
/*两栈共享空间结构*/
#define MAXSIZE 50 //定义栈中元素的最大个数
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
/*两栈共享空间结构*/
typedef struct{
ElemType data[MAXSIZE];
int top0; //栈0栈顶指针
int top1; //栈1栈顶指针
}SqDoubleStack;
进栈
对于两栈共享空间的push方法,我们除了要插入元素值参数外,还需要有一个判断是栈0还是栈1的栈号参数stackNumber。
/*插入元素e为新的栈顶元素*/
Status Push(SqDoubleStack *S, Elemtype e, int stackNumber){
if(S->top0+1 == S->top1){ //栈满
return ERROR;
}
if(stackNumber == 0){ //栈0有元素进栈
S->data[++S->top0] = e; //若栈0则先top0+1后给数组元素赋值
}else if(satckNumber == 1){ //栈1有元素进栈
S->data[--S->top1] = e; //若栈1则先top1-1后给数组元素赋值
}
return OK;
}
出栈
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqDoubleStack *S, ElemType *e, int stackNumber){
if(stackNumber == 0){
if(S->top0 == -1){
return ERROR; //说明栈0已经是空栈,溢出
}
*e = S->data[S->top0--]; //将栈0的栈顶元素出栈,随后栈顶指针减1
}else if(stackNumber == 1){
if(S->top1 == MAXSIZE){
return ERROR; //说明栈1是空栈,溢出
}
*e = S->data[S->top1++]; //将栈1的栈顶元素出栈,随后栈顶指针加1
}
return OK;
}
链栈
定义:用链式结构实现的栈
结点的结构与单链表中的结点结构相同。链栈由栈顶指针top唯一确定。
链栈通常不带头结点.top指针直接指向栈顶元素,当top==NULL时为空栈。
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
#include "Stack.h"
template <class T>
class linkStack : public Stack<T> {
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; //去栈顶元素
};
入栈、出栈、取栈顶元素、判空都是在栈顶进行的操作,与栈中的元素个数无关,这些操作的时间复杂性均为O(1)
清空和求长度操作需要遍历链表中所有元素,时间复杂度是O(n)
进栈
压入元素E的操作图如下:
template <class T>
void linkStack<T>::push(const T &value) {
Node* p = new Node(value,top); //在栈顶插入元素
top = p; //p成为新的栈顶元素
}
出栈
弹出栈顶元素D的操作图如下:
template <class T>
T linkStack<T>::pop() {
if (empty())
throw outOfRange();
Node* p = top;
T value = p->data; //value保存栈顶元素的值
top = top->next; //top指针向后移动
delete p ; //删除栈顶元素
return value;
}
取栈顶元素
template <class T>
T linkStack<T>::getTop() const {
if (empty())
throw outOfRange();
return top->data;
}
其他
清空栈
template <class T>
void linkStack<T>::clear() {
Node* p;
while (top != NULL) {
p = top; //p指向当前栈顶元素
top = top->next; //top指针移向次栈顶元素
delete p; //释放p指向的当前栈顶元素
}
}
求栈中元素个数
template <class T>
int linkStack<T>::size()const {
Node* p = top;
int count = 0; //计数器
while (p) { //遍历栈,统计元素总数
count++;
p = p->next;
}
return count;
}
应用举例(待补充)
可以先去这里看看:数据结构:栈的应用举例(严蔚敏版)
栈的小结
顺序栈和链栈的比较
时间上:大部分只需常数时间
空间上:(1)顺序:要说明一个固定长度; (2)链式:长度可变,增加结构性开销
队列定义
队列是一种只允许在表的一端插入,在另一端删除的,操作受限的线性表,
特点:先进先出
队尾(rear):允许插入的一端
队头(front):允许删除的一端
循环队列
队列的顺序存储结构,包含一个能容纳数据元素的数组空间 和 两个分别指向队头元素前一个位置的队头指针和指向队尾的队尾指针(一般是这么约定)
#ifndef _SEQQUEUE_H_
#define _SEQQUEUE_H_
#include "queue.h"
template <class T>
class seqQueue : public Queue<T>{
private:
T *data; //指向存放元素的数组
int maxSize; //队列的大小
int front, rear; //队头和队尾指针
void resize(); //扩大队列空间
public:
seqQueue(int initsize = 100);
~seqQueue(){ delete [] data; }
void clear(){ front = rear = -1; } //清空队列
bool empty()const { return front == rear; } //判空
bool full()const { return (rear + 1) % maxSize == front; } //判满
int size()const{ return (rear-front+maxSize)%maxSize; } //队列长度
void enQueue(const T &x); //入队
T deQueue(); //出队
T getHead()const; //取队首元素
};
”假溢出“
设maxSize=10,在图中,随着入队、出队的进行 ,会使整个队列整体向后移动,单队尾指针移动到最后的rear=maxSize-1,若再有元素入队就会出现溢出。但事实上,此时并未队满,队列前端还有很多空的位置。
用循环队列解决”假溢出“
由于这时候的循环队列的队空和队满都是front==rear
,我们通过牺牲一个存储空间的方式区分队列空和满,常用的表达式如下:
队列为满的条件:(rear + 1) % MaxSize == front
队列为满的条件:front == rear
队列中元素个数:( rear - front + MaxSize ) % MaxSize
入队:rear = ( rear + 1 ) % MaxSize
出队:front = ( front + 1 ) % MaxSize
入队
template <class T>
void seqQueue<T>::enQueue(const T &x){
if ((rear + 1) % maxSize == front) //若队满,则扩大队列
resize();
rear = (rear + 1) % maxSize; //移动队尾指针
data[rear] = x; //x入队
}
出队
template <class T>
T seqQueue<T>::deQueue(){
if (empty()) //若队列为空,则抛出异常
throw outOfRange();
front = (front + 1) % maxSize; //移动队首指针
return data[front]; //返回队首元素
}
取队首元素
template <class T>
T seqQueue<T>::getHead()const{
if (empty())
throw outOfRange();
return data[(front + 1) % maxSize];
}
其他
构造函数
template <class T>
seqQueue<T>::seqQueue(int initsize){
if (initsize <= 0)
throw badSize();
data = new T[initsize];
maxSize = initsize;
front = rear = 0;
}
扩大队列空间
template <class T>
void seqQueue<T>::resize(){
T* p = data;
data = new T[2 * maxSize];
for (int i = 1;i <= size() ;i++)
data[i] = p[(front + i) % maxSize]; //复制元素
front = 0; //设置队首指针
rear = size(); 设置队尾指针
maxSize *= 2;
delete p;
}
链队列
定义:用链式存储结构表示的队列
用无头结点的单链表表示队列,表头为队头,表尾为队尾。一个链队列需要两个指针front和rear分别指向队头元素和队尾元素(分别成为头指针和尾指针)。
#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include "queue.h"
template <class T>
class linkQueue: public Queue<T>{
private:
struct node {
T data;
node *next;
node(const T &x, node *N = NULL){ data = x; next = N; }
node():next(NULL) {}
~node() {}
};
node *front, *rear; //队头指针,队尾指针
public:
linkQueue(){ front = rear = NULL; }
~linkQueue();
void clear(); //清空队列
bool empty()const{ return front == NULL;} //判空
int size()const; //队列长度
void enQueue(const T &x); //入队
T deQueue(); //出队
T getHead()const; //取队首元素
};
入队
template <class T>
void linkQueue<T>::enQueue(const T &x) {
if (rear == NULL) { //若列队为空
front = rear = new node(x); //入队元素既是队首又是队尾
} else {
rear->next = new node(x); //在队尾入队
rear = rear->next; //修改队尾指针
}
}
出队
template <class T>
T linkQueue<T>::deQueue() {
if (empty()) //队列空,抛出异常
throw outOfRange();
node* p = front; //保存队首元素
T value = front->data; //保存队首元素
front = front->next; //在队首出队
if (front == NULL) { //原来只有一个元素,出队后队列为空
rear = NULL; //修改队尾指针
}
delete p;
return value;
}
取队首长度
template <class T>
T linkQueue<T>::getHead()const {
if (empty()) //队列空,抛出异常
throw outOfRange();
return front->data; //返回队首元素
}
其他
清空队列
template <class T>
void linkQueue<T>:: clear(){
node* p;
while (front != NULL) { //释放队列中所有结点
p = front;
front = front->next;
delete p;
}
rear = NULL; //修改尾指针
求队列长度
template <class T>
int linkQueue<T>::size()const {
node* p =front;
int count = 0;
while(p) {
count++;
p = p->next;
}
return count;
}
小结
习题
- 元素a,b,c,d,e依次进入初始为空的栈中,若元素进栈后可停留、可出栈,知道所有元素都出栈,则在所有可能的出栈序列中,以元素d为开头的序列个数是()
解析:当栈中只有a,b,c,d时,d可出栈,出栈序列时d,,,c,,b,,a,_,,其中" _
"处均是元素e可能出现的位置,故本题是4;
- 循环队列存储在数组A[0…m]中,则入队的操作是()
解析:数组A[0…m]共有m+1个元素,故入队rear = (rear + 1) mod (m+1)
- 循环队列的引入,目的是___的现象
解析:假溢出时大量移动数据元素的假象
- 若以1,2,3,4作为双端队列的输入队列,则既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出队列()
A.1,2,3,4
B.4,1,3,2
C.4,2,3,1
D.4,2,1,3
双向队列:两端都可输入输出
解析:选C