堆栈的定义
- 堆栈(stack)是一个线性表,其插入和删除操作都在表的同一端进行,这端被称为栈顶(top),另一端被称为栈底(bottom)
- 后进先出
堆栈的描述
公式化描述
- 继承线性表
template<class T> class Stack : private LinearList <T> { //线性表的尾部作为栈顶 public: Stack(int MaxStackSize = 10): LinearList<T> (MaxStackSize){} //构造函数 bool IsEmpty() const{ return LinearList<T>::IsEmpty(); } bool IsFull() const{ return (Length() == GetMaxSize()); } T Top() const{ //提取栈顶元素 if (IsEmpty()) throw OutOfBounds(); T x; Find(Length(), x); return x; } Stack<T>& Push(const T& x){ //入栈 Insert(Length(), x); return *this; } Stack<T>& Pop(T& x){ //出栈 Delete(Length(), x); return *this; } }
- 自定义的stack类
class Stack{
public:
Stack(int MaxStackSize = 10);
~Stack() {delete [] stack;}
bool IsEmpty() const {return top == -1;}
bool IsFull() const {return top == MaxTop;}
T Top() const;
Stack<T>& Push(const T& x);
Stack<T>& Pop(T& x);
private:
int top; //栈顶的位置
int MaxTop; //栈顶的最大位置(数组大小)
T *stack; //一维数组实现栈
};
template<class T>
Stack<T>::Stack(int MaxStackSize) { //构造函数
MaxTop = MaxStackSize - 1;
stack = new T[MaxStackSize];
top = -1; //空栈
}
template<class T>
T Stack<T>::Top() const { //返回栈顶元素
if (IsEmpty())
throw OutOfBounds();
return stack[top];
}
template<class T>
Stack<T>& Stack<T>::Push(const T& x){ //入栈
if(IsFull())
throw OutOfBounds();
stack[++top]=x; //先加再存
return *this;
}
template<class T>
Stack<T>& Stack<T>::Pop(T& x){
if(IsEmpty())
throw OutOfBounds();
x=stack[top--]; //先取再减
return *this;
}
- 数组描述的缺陷:空间利用率低(要开一个很大的数组)
链表描述
- 首节点当成栈顶,尾节点当成栈底 ➡️ O(1)
- 继承链表
template<class T> class LinkedStack : private Chain<T> { //私有继承自链表类,栈顶在链表的首节点 public: bool IsEmpty() const{ return Chain<T>::IsEmpty(); } bool IsFull() const; T Top() const{ if (IsEmpty()) throw OutOfBounds(); T x; Find(1, x); return x; } LinkedStack<T>& Push(const T& x) { Insert(0, x); //在链表头前面插入数据 return *this; } LinkedStack<T>& Pop(T& x) { Delete(1, x); //删除链表头的数据 return *this; } } template<class T> bool LinkedStack<T>::IsFull() const { try { ChainNode<T> *p = new ChainNode<T>; delete p; return false; } catch (NoMem) { //如果抛出异常 return true; //栈已满 } }
- 自定义的链表实现
template <class T> class Node { friend LinkedStack<T>; //一定要声明友元,否则它无法调用node的私有成员 private: T data; Node<T> *link; }; template<class T> class LinkedStack { public: LinkedStack() {top = 0;} ~LinkedStack(); bool IsEmpty() const {return top == 0;} bool IsFull() const; T Top() const; LinkedStack<T>& Push(const T& x); LinkedStack<T>& Pop(T& x); private: Node<T> *top; //栈顶指针 }; template<class T> LinkedStack<T>::~LinkedStack() { //析构函数 Node<T> *next; while (top) { next = top->link; delete top; top = next; } } template<class T> bool LinkedStack<T>::IsFull() const { try { Node<T> *p = new Node<T>; delete p; return false; } catch (NoMem) { //如果抛出异常 return true; //栈已满 } } template<class T> T LinkedStack<T>::Top() const { //获取栈顶元素 if (IsEmpty()) throw OutOfBounds(); return top->data; } template<class T> LinkedStack<T>& LinkedStack<T>::Push(const T& x){//入栈 Node<T> *p = new Node<T>; p->data = x; p->link = top; //加在栈顶前面 top = p; //更新栈顶 return *this; } template<class T> LinkedStack<T>& LinkedStack<T>::Pop(T& x) { //出栈 if (IsEmpty()) throw OutOfBounds(); x = top->data; //把出栈的元素存在x中 Node<T> *p = top; top = top->link; //更新栈顶 delete p; //释放空间 return *this; }
- 小结:堆栈的两种实现方式比较
公式化 链表 Create() O(1) / O(MaxSize) O(1) Destroy() O(1) / O(MaxSize) O(n) IsEmpty() O(1) O(1) IsFull() O(1) O(1) Top() O(1) O(1) Push() O(1) O(1) Pop() O(1) O(1)
其中,堆栈最常用最重要的函数是Push()和Pop()
堆栈的应用
括号匹配
- 问题描述:括号必须成对出现,否则报错
- 应用:C++编译器、数学公式自动求解(句法分析)
- 算法设计思路:右括号和最近未匹配的左括号匹配
(1)从左向右解析表达式,遇到左括号就把它push入栈中,遇到右括号就把栈顶的左括号pop出来和它匹配
(2)如果有右括号但找不到左括号与之匹配,或遍历完了之后左括号的栈不为空,则报错#include <iostream.h> #include <string.h> #include <stdio.h> #include "stack.h" const int MaxLength = 100; //输入的表达式的最大长度 void PrintMatchedPairs(char *expr) { //括号匹配函数 Stack<int> s(MaxLength); //用堆栈实现 int j, length = strlen(expr); for (int i = 1; i <= length; i++) { //遍历字符串 if (expr[i - 1] == '(') //遇见左括号就入栈 s.Push(i); else if (expr[i - 1] == ')') //遇见右括号就出栈 try { s.Pop(j); cout << j << ' ' << i << endl; //输出哪两个字符是匹配的括号 } catch (OutOfBounds){ //如果抛出异常,证明有多余的右括号 cout << "No match for right parenthesis”<< " at " << i << endl; } } while (!s.IsEmpty()) { //当栈不为空时,即有多余的左括号 s.Pop(j); cout << "No match for left parenthesis at "<< j << endl; } } void main() { char expr[MaxLength]; cout << "Type an expression of length at most "<< MaxLength << endl; cin.getline(expr, MaxLength); //获取表达式 cout <<"The pairs of matching parentheses in”<< endl; puts(expr); cout <<"are" << endl; PrintMatchedPairs(expr); }
火车车厢重排
- 问题描述:重新排列杂序入轨的车厢,使得车厢按编号排列出轨
- 算法思路:通过栈构造缓冲轨
(1)入轨的车厢,判断按顺序是否该出轨
(2)如果不该出轨,放入缓冲轨中(越早进入缓冲轨越晚出轨)
(3)如果该出轨,直接出轨,然后判断缓冲轨中有没有应该出轨的
//返回能否将火车车厢重排 bool Railroad(int p[],int n, int k){ //k条缓冲轨 LinkedStack<int> *H; //创建用链表实现的堆栈数组来放k条缓冲轨 H = new LinkedStack<int> [k + 1]; int NowOut = 1; //记录接下来应该出轨的车厢号 int minH = n+1; //在所有缓冲轨上最小的车厢号 int minS; //拥有最小车厢号的缓冲轨 //重排 for(int I=1;i<=n;i++) if(p[I]==NowOut){ //如果入轨的车厢就是应该出轨的车厢 cout << “Move car ” << p[i] <<“ from input to output”<< endl; NowOut++; //更新应该出轨的车厢号 while(minH==NowOut){ //如果在缓冲轨中有接下来应该出轨的 Output(minH,minS,H,k,n); //从缓冲轨中出轨 NowOut++; //更新应该出轨的车厢号 } } else{ //如果入轨的车厢不应该出轨,那么让它进入缓冲轨 if(!Hold(p[I],minH,minS,H,k,n)) //如果进入缓冲轨不成功,报错 return false; } return true; } //O(k*n) //从缓冲轨中出轨 void Output(int& minH,int& minS,LinkedStack<int> H[],int k,int n){ int c; //记录车厢号 H[minS].Pop(c); //缓冲轨栈顶元素出栈 cout << "Move car " << minH << " from holding track “<< minS << " to output" << endl; minH = n + 2; //给缓冲轨中的最小车厢号设置一个不可能的数字,方便更新 for(int I=1;i<=k;i++) //遍历所有的缓冲轨 if(!H[I].IsEmpty()&&(c=H[I].Top())<minH){ //只需考虑每个缓冲轨的栈顶元素即可 minH=c; minS=I; } } //O(k) //让入轨的车厢进入缓冲轨,如果无法进行该操作,应返回错误 bool Hold(int c,int& minH,int& minS,LinkedStack<int> H[],int k,int n){ int BestTrack=0, //可以放进去的最好的缓冲轨号码 BestTop=n+1, //最好的缓冲轨上栈顶的车厢号 x; //记录车厢号 for(int I=1;i<=k;i++) //遍历所有的缓冲轨 if(!H[I].IsEmpty()){ //该缓冲轨是否为空 x=H[I].Top(); //该缓冲轨栈顶的车厢号 if(c<x&&x<BestTop){ //如果目标车厢比该栈顶车厢号小,且该栈顶车厢号比最好的栈顶车厢号小 BestTop=x; //更新最好的缓冲轨 BestTrack=I; } } else //如果该缓冲轨为空 if(!BestTrack) //如果还没找到可以放进去的缓冲轨 BestTrack=I; //最好的缓冲轨为该空轨 if(!BestTrack) //如果还没找到可以放进去的缓冲轨 return false; //返回无法放入缓冲轨,即重排失败 H[BestTrack].Push(c); //让车厢进入最好的缓冲轨 cout << "Move car " << c << " from input "<< "to holding track " << BestTrack << endl; if (c < minH) { minH = c; //更新缓冲轨中的最小车厢号 minS = BestTrack; } return true; } //O(k)
队列的定义
- 队列(queue)的插入和删除操作分别在表的不同端进行,队尾添加新元素,队首删除元素
- 先进先出
队列的描述
公式化描述
template<class T>
class Queue {
public:
Queue(int MaxQueueSize = 10);
~Queue() {delete [] queue;}
bool IsEmpty() const { //如果队首和队尾位置相同,队列即为空
return front == rear;
}
bool IsFull() const {
return (((rear + 1) % MaxSize == front) ? 1 : 0);
}
T First() const; //获取队首元素
T Last() const; //获取队尾元素
Queue<T>& Add(const T& x); //在队尾增加元素
Queue<T>& Delete(T& x); //删除队首元素
private:
int front; //队首的位置
int rear; //队尾的位置
int MaxSize; //队列的最大长度
T *queue; //一维数组
};
template<class T>
Queue<T>::Queue(int MaxQueueSize){ //构造函数
MaxSize = MaxQueueSize + 1; //留出一个额外空间
queue = new T[MaxSize];
front = rear = 0;
}
template<class T>
T Queue<T>::First() const{
if (IsEmpty())
throw OutOfBounds();
return queue[(front + 1) % MaxSize];
}
template<class T>
T Queue<T>::Last() const{
if (IsEmpty())
throw OutOfBounds();
return queue[rear];
}
template<class T>
Queue<T>& Queue<T>::Add(const T& x) {
if (IsFull())
throw NoMem();
rear = (rear + 1) % MaxSize;
queue[rear] = x;
return *this;
}
template<class T>
Queue<T>& Queue<T>::Delete(T& x){
if (IsEmpty())
throw OutOfBounds();
front = (front + 1) % MaxSize;
x = queue[front];
return *this;
}
链表描述
两种指针方向
- front ➡️ rear:入队和出队都是O(1)
- rear ➡️ front:入队是O(1),出队是O(n),因为出队要更新front指针,但要找到front指针的前一个位置,需要遍历队列
template<class T> class LinkedQueue {
public:
LinkedQueue() {front = rear = 0;} //构造函数初始化队列
~LinkedQueue();
bool IsEmpty() const{
return ((front) ? false : true);
}
bool IsFull() const;
T First() const;
T Last() const;
LinkedQueue<T>& Add(const T& x);
LinkedQueue<T>& Delete(T& x);
private:
Node<T> *front; //头指针
Node<T> *rear; //尾指针
};
template<class T> LinkedQueue<T>::~LinkedQueue(){ //析构函数
Node<T> *next;
while (front) {
next = front->link;
delete front;
front = next;
}
}
template<class T>
bool LinkedQueue<T>::IsFull() const {
Node<T> *p;
try {
p = new Node<T>;
delete p;
return false;
} catch (NoMem) { //若抛出异常,则队列已满
return true;
}
}
template<class T>
T LinkedQueue<T>::First() const{
if (IsEmpty())
throw OutOfBounds();
return front->data;
}
template<class T>
T LinkedQueue<T>::Last() const{
if (IsEmpty())
throw OutOfBounds();
return rear->data;
}
template<class T>
LinkedQueue<T>& LinkedQueue<T>::Add(const T& x){ //增加新元素
Node<T> *p = new Node<T>; //新建节点并赋值
p->data = x;
p->link = 0;
if (front) //如果队列已被创建
rear->link = p;
else
front = p;
rear = p; //更新尾节点
return *this;
}
template<class T>
LinkedQueue<T>& LinkedQueue<T>::Delete(T& x){
if (IsEmpty())
throw OutOfBounds();
x = front->data; //把要删除的元素存储在x中
Node<T> *p = front;
front = front->link; //更新首节点
delete p; //释放原队首的空间
return *this;
}
小结:堆栈的两种实现方式比较
公式化 | 链表 | |
Create() | O(1) / O(MaxSize) | O(1) |
Destroy() | O(1) / O(MaxSize) | O(n) |
IsEmpty() | O(1) | O(1) |
IsFull() | O(1) | O(1) |
Top() | O(1) | O(1) |
Push() | O(1) | O(1) |
Pop() | O(1) | O(1) |
队列的应用
火车车厢重排
bool RailRoad(int p[],int n,int k){
LinkedQueue<int> *H; //创建链表数组做缓冲轨
H = new LinkedQueue<int> [k];
k--; //第k个缓冲轨不用
int NowOut=1; //应该出轨的车厢号
int minH=n+1; //所有缓冲轨上最小的车厢号
int minQ; //拥有最小车厢号的缓冲轨
for(int i=1;i<=n;i++){ //遍历所有入轨的车厢
if(p[i]==NowOut){ //如果正好要出轨
cout << "Move car”<<p[i]<<" from input to output" << endl;
NowOut++; //更新下一个要出轨的车厢号
while(minH==NowOut){ //如果缓冲轨上有要出轨的车厢
Output(minH,minQ,H,k,n); //从缓冲轨上出轨
NowOut++;
}
}
else{ //如果入轨的车厢不应该出轨,则将其放入缓冲轨
if(!Hold(p[i],minH,minQ,H,k,n)) //如果放入失败,则重排失败
return false;
}
}
return true;
}
//从缓冲轨中出轨
void Output(int& minH,int& minQ,queue<int> H[],int k,int n){
int c; //记录车厢号
H[minQ].Delete(c); //出轨
minH=n+2; //准备重新寻找缓冲轨上最小车厢号
for(int i=1;i<=k;i++){ //遍历所有缓冲轨
if (!H[i].IsEmpty()&&(c = H[i].First()) < minH) {
minH = c; //更新最小车厢号的信息
minQ = I;
}
}
}
//从入轨中放入缓冲轨,如果放入失败要返回错误
bool Hold(int c,int& minH,int& minQ,queue<int> H[],int k,int n){
int BestTrack=0,BestLast=0,x;
for(int i=1;i<=k;i++) //遍历所有缓冲轨,寻找最好的缓冲轨
if(!H[I].IsEmpty()){ //缓冲轨是否为空
x=H[I].last();
if (c > x && x > BestLast) { //注意条件的设置,队列是队首先出
BestLast = x;
BestTrack = I;
}
}
else if (!BestTrack) //如果还没找到最好的缓冲轨
BestTrack = I;
if (!BestTrack) //如果所有缓冲轨都不能放入
return false;
H[BestTrack].Add(c); //放入缓冲轨
if (c < minH) { //更新缓冲轨上的最小车厢号
minH = c;
minQ = BestTrack;
}
return true;
}
作业4
Josephus问题
/*
8
5
5 2 8 7 1 4 6 3
5 2 8 7 1 4 6 3
*/
template <typename T>
class linearList{
public:
linearList(int n);
linearList<T>& append(T x);
linearList<T>& deleted(int k);
void josephus(int m);
private:
int length;
T *p;
};
template <typename T>
linearList<T>::linearList(int n){
length=0;
p=new T[n];
}
template <typename T>
linearList<T>& linearList<T>::append(T x){
int i=0;
for(;i<length;i++);
p[i]=x;
length++;
return *this;
}
template <typename T>
linearList<T>& linearList<T>::deleted(int k){
for(int i=k+1;i<length;i++)
p[i-1]=p[i];
length--;
return *this;
}
template <typename T>
void linearList<T>::josephus(int m){
int mark=0;
while(length!=1){
for(int i=1;i<m;i++){
mark++;
if(mark==length)
mark=0;
else if(mark>length)
mark=1;
}
cout<<p[mark]<<" ";
deleted(mark);
}
cout<<p[0]<<endl;
}
template <typename T>
class chain{
struct node{
T data;
node *next;
};
public:
node *head; //头节点
chain(); //构造函数
void append(T t); //将数据拼接在链表最后
bool empty(); //判断链表是否为空
void deleted(node *t); //删除元素
void josephus(int n,int m);
};
template <typename T>
chain<T>::chain(){
head=NULL;
}
template <typename T>
void chain<T>::append(T t){
node *p=new node;
p->data=t;
p->next=NULL;
if(head==NULL){ //如果链表还未被创建
head=p;
head->next=head; //头尾相连
return;
}
node *tail=head;
while(tail->next!=head) //找到尾节点所在
tail=tail->next;
tail->next=p; //将新节点接在最后面
p->next=head; //头尾相连
}
template <typename T>
bool chain<T>::empty(){
if(head)
return false;
else
return true;
}
template <typename T>
void chain<T>::deleted(node* t){
node* p=head;
while(p->next->data!=t->data)
p=p->next;
p->next=t->next;
}
template <typename T>
void chain<T>::josephus(int n,int m){
node *p=head;
while(n!=1){ //当链表中的元素大于一个时
for(int i=1;i<m;i++)
p=p->next; //找到第m个元素
node *t=p;
p=p->next; //指针继续寻找
cout<<t->data<<" "; //输出第m个元素
deleted(t); //删除该元素
n--;
}
cout<<p->data<<endl; //输出最后一个元素
}
int main(){
int n,m;
cin>>n>>m;
if(n>=3&&n<=100&&m>=1&&m<=n);
else{
cout<<"WRONG!";
return 0;
}
linearList<int> l(n);
for(int i=1;i<=n;i++)
l.append(i);
l.josephus(m);
chain<int> c;
for(int i=1;i<=n;i++)
c.append(i);
c.josephus(n,m);
cout<<endl;
return 0;
}
删除栈中所有等于x的数据项,保持其他数据项顺序不变
/*
a
b a t a a e c
c e t b
*/
template<typename T>
class Stack{ //虚基类栈定义
public:
virtual bool empty() const=0; //检查栈是否为空
virtual int getSize() const=0; //返回栈的大小
virtual T& getTop()=0; //返回栈顶指针
virtual void pop()=0; //出栈
virtual void push(const T& t)=0; //进栈
};
template<typename T>
class linkedStack:public Stack<T>{ //用链表实现栈
struct node{ //节点定义
T data;
node *next;
};
public:
linkedStack(int capacity=10){ //构造函数
top=NULL;
size=0;
}
bool empty() const{
return size==0;
}
int getSize() const{
return size;
}
T& getTop(){
if(size==0)
cout<<"The stack is empty!"<<endl;
return top->data;
}
void pop(){
if(size==0){
cout<<"The stack is empty!"<<endl;
return ;
}
cout<<top->data;
node *p=top->next;
top=p;
size--;
}
void push(const T& t){
node *p=new node;
p->data=t;
p->next=top;
top=p; //更新栈顶指针
size++;
}
node* find(int& k,T t){ //从第k个元素开始找数据为t的元素,返回其节点
node *p=top;
for(int i=1;i<k;i++)
p=p->next; //找到第k个元素
while(p){ //当p不为NULL时
if(p->data==t){
k--; //调整k的值,以便下一次寻找
return p;
}
p=p->next;
k++;
}
return p;
}
void deleted(node *t){ //删除节点
if(t==NULL)
return ;
if(t==top){ //删除栈顶,直接pop即可
pop();
return ;
}
node *p=top;
while(p->next!=t)
p=p->next; //找到节点的前一个
p->next=t->next;
size--;
}
private:
node *top; //栈顶指针
int size; //栈的大小
};
//删除栈中所有等于x的数据项,保持其他数据项顺序不变
template<typename T>
void delete_all(linkedStack<T>&s,const T&x){
for(int i=1;i<=s.getSize();i++)
if(s.find(i,x)) //从第i个元素开始寻找,如果有等于x的数据项
s.deleted(s.find(i,x)); //删除该数据项
else //返回节点为NULL,即栈中没有等于x的数据项
break; //跳出循环
}
int main(){
linkedStack<char> l;
char c;
cout<<"Please input data of the stack!"<<endl;
for(int i=1;i<=7;i++){ //获取数据
cin>>c;
l.push(c);
}
cout<<"Please input the element you want to delete: ";
cin>>c;
delete_all(l, c);
while(l.getSize()){ //依此出栈
l.pop();
if(l.getSize())
cout<<" ";
}
cout<<endl;
return 0;
}