常用数据结构——栈及其应用

来源:互联网 发布:温莎公爵间谍 知乎 编辑:程序博客网 时间:2024/05/10 09:05

队列和栈作为一种最简单最基本的常用数据结构,可以说在许多方面都应用广泛。在程序运行时,他们可以保存程序运行路径中各个点的信息,以便用于回溯操作或其他需要访问已经访问过的节点信息的操作。这里对栈的特点、作用做出描述、并简单地用不同途径实现了栈的基本功能。本文的实现分别用了C语言(不是纯C,仍为cpp文件,仅仅只是采用了C语言中的部分思想)和C++,代码均在GCC编译器下验证通过。



什么是栈?


是一种先进后出(FILO)的数据结构,与上一篇中的队列一样,同为最基础的数据结构之一。队列请参考用数据结构——队列及其应用。详细定义请参考教科书。栈同样作为一种限制性的线性表。操作只能在一端进行,即栈顶。另一端为栈底,不进行操作。在栈顶插入元素的操作称为压栈或进栈(push),删除元素的操作称为出栈(pop)


栈的结构类似于一个箱子,装入东西只能从上面加在最顶上,取出时也只能先取出箱子中最顶上的东西。


基本操作除了出栈,压栈外,还有取栈顶元素,初始化,销毁,判空,判满,清空,遍历等操作,可根据需求定义。类似于队列,栈对应于顺序存储结构和链式存储结构分别为顺序栈和链式栈


C实现


顺序栈在C语言中的实现很简单:

其中栈顶指针top指向栈顶元素。

#include<iostream>#include<stdlib.h>#define MaxSize 100using namespace std;typedef struct//栈的定义 {char data[MaxSize];//默认字符数组 int top;//指针 }SqStack;void InitStack(SqStack *&s);//初始化 void DestroyStack(SqStack *&s); //销毁栈 bool StackEmpty(SqStack *s);//判空 bool Push(SqStack *&s,char e);//压栈 bool Pop(SqStack *&s,char &e); //出栈 bool GetTop(SqStack *s,char &e);//取得栈顶元素 int main(void){SqStack *s;InitStack(s);DestroyStack(s);system("pause");return 0;}void InitStack(SqStack *&s){s = (SqStack *)malloc(sizeof(SqStack));s->top = -1;}void DestroyStack(SqStack *&s)//销毁栈 {free(s);}bool StackEmpty(SqStack *s)//判空 {return s->top == -1;}bool Push(SqStack *&s,char e)//压栈 {if(s->top == MaxSize-1)//栈满情况 return false;s->top ++;s->data[s->top] = e;//e赋给栈顶 return true;}bool Pop(SqStack *&s,char &e)//出栈 {if(s->top == -1)//为空 return false;e = s->data[s->top];s->top --;return true;}bool GetTop(SqStack *s,char &e)//取栈顶元素 {if(s->top == -1)return false;e = s->data[s->top];return true;}


链式栈在C语言中,采用带头节点的链表实现如下:
#include<iostream>#include<stdlib.h>using namespace std;typedef struct SNode{int data;SNode *next; }LinkedStack;//链式栈节点定义 void InitStack(LinkedStack *&s);//初始化void DestroyStack(LinkedStack *&s);//销毁栈 bool StackEmpty(LinkedStack *s);//判空 void Push(LinkedStack *&s,int e);//压栈/进栈 bool Pop(LinkedStack *&s,int &e);//出栈 bool GetTop(LinkedStack *&s,int &e);//get栈顶元素 int main(void){LinkedStack *Q;InitStack(Q); Push(Q,1);Push(Q,2);Push(Q,3);int a,b;Pop(Q,a);Pop(Q,b);cout << a << endl << b << endl; system("pause");return 0;}void InitStack(LinkedStack *&s){s = (LinkedStack *)malloc(sizeof(SNode));s->next = NULL;//创建空栈 }void DestroyStack(LinkedStack *&s){LinkedStack *p =s, *q = s->next;while(q != NULL){free(p);p = q;q = q->next;}free(p);} bool StackEmpty(LinkedStack *s){return s->next == NULL;}void Push(LinkedStack *&s,int e)//链表头插法 {SNode *p = (LinkedStack *)malloc(sizeof(SNode));p->data = e;p->next = s->next;s->next = p;}bool Pop(LinkedStack *&s,int &e){//首先判空if(s->next == NULL)return false;SNode *p = s->next;e = p->data;s->next = p->next;free(p); return true;}bool GetTop(LinkedStack *&s,int &e){if(s->next == NULL)return false;e = s->next->data;return true;}



C++实现


后面的几个例子主要采用C++实现。

C++中与C语言中区别:C++中操作作为类的成员函数,默认传入this指针,少了一个参数。且初始化和销毁分别用构造函数和析构函数实现。利用C++中的类模板技术可以实现栈的重用,可以实例化出能装入不同类型对象的栈。这里主要实现顺序栈,而且栈顶指针指向的是栈顶的下一个位置。

顺序栈实现:
#include<iostream>using namespace std;class MyStack{public:MyStack(int size);//分配初始空间,设置栈容量,栈顶 ~MyStack();//析构函数,销毁栈bool StackEmpty();//判空bool StackFull();//判满void ClearStack();//清空int StackLength();//求长度bool Push(char elem);//压栈 bool Pop(char &elem);//出栈 void Traverse_Stack(bool IsFromButtom);//遍历,private:char *m_pBuffer;//栈空间指针int m_iSize;//栈最大容量int m_iTop;//栈顶 }; int main(void){MyStack *pStack = new MyStack(5);pStack->Push('h');pStack->Push('e');pStack->Push('l');pStack->Push('l');pStack->Push('o');pStack->Traverse_Stack(1);pStack->Traverse_Stack(0);cout << pStack->StackLength() << endl;char elem = 0;pStack->Pop(elem);cout << elem << endl;pStack->Traverse_Stack(1);pStack->Traverse_Stack(0);pStack->ClearStack();if(pStack->StackEmpty()){cout << "The stack is empty!" << endl;}if(pStack->StackFull()){cout << "The stack is full!" << endl;} delete pStack;//在堆中分配内存一定要释放内存 pStack = NULL;return 0;}MyStack::MyStack(int size){m_iSize = size;m_pBuffer = new char[size];m_iTop = 0;//空栈 }MyStack::~MyStack(){delete []m_pBuffer;}bool MyStack::StackEmpty(){return m_iTop == 0;}bool MyStack::StackFull(){//栈顶指针m_iTop,栈空为0,压栈累加 if(m_iTop == m_iSize)//>={return true;}elsereturn false;}void MyStack::ClearStack(){m_iTop = 0;//栈中值无效 }int MyStack::StackLength(){return m_iTop;//m_iTop即为栈高度 }bool MyStack::Push(char elem){if(StackFull())//栈满,入栈失败,处理方法,bool类型返回值或者抛异常 {return false;}m_pBuffer[m_iTop] = elem;m_iTop++;return true;}bool MyStack::Pop(char &elem){if(StackEmpty()){return false;}m_iTop--;elem = m_pBuffer[m_iTop];return true;}void MyStack::Traverse_Stack(bool IsFromButtom)//visit(){if(IsFromButtom){//从栈底遍历 for(int i=0;i<m_iTop;i++){cout << m_pBuffer[i] << ",";}cout << endl;} else{//从栈顶遍历for(int i=m_iTop-1;i>=0;i--){cout << m_pBuffer[i] << ",";}cout << endl;}}

使用类模板改造上面的顺序栈:使之能够装入任意类型的数据元素。这里定义了坐标类Coordinate,包含两个数据成员横坐标x,和纵坐标y。并重载了<<运算符,使之能够直接使用cout对象输出Coordinate对象。并用这个类实例化的一个存放可以存放Coordinate类型数据元素的栈。

实现如下:
#include<iostream>using namespace std;/*************坐标类Coordinate**********/ class Coordinate{//<<运算符只能用友元函数重载 friend ostream &operator<<(ostream &out,Coordinate &coor);public: Coordinate(double x=0,double y=0){m_iX = x;m_iY = y;}void print_Coor(){cout << "(" << m_iX << "," << m_iY << ")" << endl;}private:double m_iX;double m_iY;};ostream &operator<<(ostream &out,Coordinate &coor){out << "(" << coor.m_iX << "," << coor.m_iY << ")" << endl;//coor.print_Coor();return out; }/***************类模板********************/template <typename T>class MyStack{public:MyStack(int size);//分配初始空间,设置栈容量,栈顶 ~MyStack();bool StackEmpty();bool StackFull();void ClearStack();int StackLength();bool Push(T elem);//压栈 bool Pop(T &elem);//出栈 void Traverse_Stack(bool IsFromButtom);private:T *m_pBuffer;//栈空间指针int m_iSize;int m_iTop;//栈顶 }; int main(void){MyStack<Coordinate> *pStack = new MyStack<Coordinate>(5);pStack->Push(Coordinate(1,2));pStack->Push(Coordinate(3,4));pStack->Push(Coordinate(4,5));pStack->Traverse_Stack(1);pStack->Traverse_Stack(0);Coordinate c(0,0);pStack->Pop(c);cout << c << endl;cout << pStack->StackLength() << endl;pStack->ClearStack();if(pStack->StackEmpty()){cout << "The stack is empty!" << endl;}if(pStack->StackFull()){cout << "The stack is full!" << endl;} delete pStack;//在堆中分配内存一定要释放内存 pStack = NULL;return 0;}template <typename T>MyStack<T>::MyStack(int size){m_iSize = size;m_pBuffer = new T[size];m_iTop = 0;//空栈 }template <typename T>MyStack<T>::~MyStack(){delete []m_pBuffer;}template <typename T>bool MyStack<T>::StackEmpty(){return m_iTop == 0;}template <typename T>bool MyStack<T>::StackFull(){//栈顶指针m_iTop,栈空为0,压栈累加 if(m_iTop == m_iSize)//>={return true;}elsereturn false;}template <typename T>void MyStack<T>::ClearStack(){m_iTop = 0;//栈中值无效 }template <typename T>int MyStack<T>::StackLength(){return m_iTop;//m_iTop即为栈高度 }template <typename T>bool MyStack<T>::Push(T elem){if(StackFull())//栈满,入栈失败,处理方法,bool或者抛异常 {return false;}m_pBuffer[m_iTop] = elem;m_iTop++;return true;}template <typename T>bool MyStack<T>::Pop(T &elem){if(StackEmpty()){return false;}m_iTop--;elem = m_pBuffer[m_iTop];return true;}template <typename T>void MyStack<T>::Traverse_Stack(bool IsFromButtom)//visit(){if(IsFromButtom){//从栈底遍历 for(int i=0;i<m_iTop;i++){//对于传入模板类中的封装数据类型必须重载<<运算符 cout << m_pBuffer[i] << endl;}cout << endl;} else{//从栈顶遍历for(int i=m_iTop-1;i>=0;i--){cout << m_pBuffer[i] << endl;}cout << endl;}}


典型应用


栈的应用场景极其广泛,在程序运行时,使用new或者malloc动态分配的内存位于堆区,而栈区通常用来暂时存放为运行函数而分配的局部变量,函数参数,返回数据,返回地址等。下面主要介绍三个栈解决的很经典很简单的问题:进制转换、括号匹配、迷宫问题。

进制转换


问题描述:输入任意十进制整数,将其转换为任意进制的数。
方法:家喻户晓的除基数取余法。公式 N = (N div d) *d + N mod d。

这里的栈实现采用上面的类模板设计,不再重写。
实现算法如下:
char num[] = "0123456789ABCDEF" ;//定义全局数组,存放转换之后表示大于10进制的数字的字符, void RadixConvert(int n,int radix)//输入十进制数n和基数radix,打印出转换之后的数 {MyStack<int> *pStack = new MyStack<int>(32);int mod = 0;//余数while(n != 0){mod = n%radix; pStack->Push(mod);//余数进栈 n = n/radix;}int elem = 0;while(!pStack->StackEmpty())//也可将结果用字符串保存返回给函数 {pStack->Pop(elem);cout << num[elem];}cout << endl;delete pStack;//在堆中实例化对象一定要释放内存,也可以在栈中实例化 pStack = NULL;}



括号匹配


问题描述:输入字符串str,判断其中括号是否匹配。

类似于 ( [ { } [ ] ] )称之为匹配,类似于 ( [ ) ] 称之为不匹配。实现中只考虑英文半角括号 ()  [ ]  { },如若想考虑全角符号或者半角与全角匹配之类的问题,请自行编写。

思路:遇到左括号依次进栈,如果碰到右括号就将上一个元素出栈,并查看是否与之匹配,匹配则继续,不匹配则结束。这里遇到右括号时可能出现栈为空的情况,此时是不匹配的,出栈会不成功,所以需要在遇到右括号时增加一个判断站是否为空的条件语句。default情况下,不执行直接break则会跳过中间不是括号的字符

实现如下:
匹配则返回true,不匹配返回false。这里的栈实现依然采用上面的类模板。

bool BracketMatch(char *str){MyStack<char> *pStack = new MyStack<char>(30);bool matching = true;//表示当前是否匹配,如果不匹配,直接跳出for循环 for(int i=0;i<strlen(str);i++){char elem = 0;switch(str[i]){case '[':case '(':case '{':pStack->Push(str[i]);break;case ']':if(pStack->StackLength() == 0){matching = false;break;}pStack->Pop(elem); if(elem == '[')//同栈顶元素匹配则继续 break;else{matching = false;//不匹配 break; }case ')':if(pStack->StackLength() == 0){matching = false;break;}pStack->Pop(elem); if(elem == '(') break;else{matching = false;break; }case '}':if(pStack->StackLength() == 0){matching = false;break;}pStack->Pop(elem); if(elem == '{') break;else{matching = false;break; }default://包含异常字符,直接忽略,继续匹配,我们只管括号的!break; }if(!matching)break; }if(pStack->StackLength() == 0 && matching) {return true;//括号匹配 } else{return false;//括号不匹配 }}

如果main函数中执行以下代码,则会打印出括号匹配!
char str[] = "[(你)好][()( ) ]{[(hello,world)] } ([世界]!)";if(BracketMatch(str)){cout << "括号匹配!"<< endl; }else{cout << "括号不匹配!" << endl;}


迷宫问题


问题描述:给定给定一个M×N的迷宫图、入口与出口、行走规则。求一条从指定入口到出口的路径。
所求路径必须是简单路径,即路径不重复。

这个问题在上一篇队列中利用队列(常用数据结构——队列及其应用)求解过,而且利用队列求解出来的结果一定是最短路径中的一条。但这里用栈求解的结果不一定是最短路径

迷宫采用二维数组来表示,其中路用0表示,墙用1表示。为了求解问题的方便,通常在数组的周围加上围墙,即在周围加上两行和两列。形成M+2行,N+2列的迷宫数组。

思路:对于每一个方块都优先查找其方位0上的方块的路径,如果这个方向上没有路径,就依次查找1,2,3方向,如果都没有路径,就将其出栈,表明经过这个格点无法找到出口。0->3的方向分别为上右下左。最后找到出口时栈底到栈顶的序列即为路径。


主要思想是从入口开始按照上右下左的优先顺序(优先顺序可以自行设置)依次查找其相邻方块,直到查找到出口打印路径返回true,或者将所有方位都查找过后发现没有路径返回false,表示没有路径。在求解时同样要将查找过的方格值赋为-1,避免重复访问。这样求出来的路径一般情况下不是最短路径。比如以下程序输入起点坐标(1,1)终点坐标(2,1)时,输出路径顺序为(1,1),(1,2),(1,3),(2,3),(2,4),(3,4),(4,4),(4,3),(4,2),(3,2),(3,1),(2,1)。


注意这里的坐标为(行号,列号),均从0开始。


实现如下:


#include<iostream>#include<stdlib.h>using namespace std;const int MaxSize = 100;typedef struct{int i,j;//i表示行号,j表示列号 int di;//方位从0(上)开始,++顺时针旋转,即0->3为上右下左 }Box;typedef struct{Box data[MaxSize];int top;//指向栈顶元素 }Stack;//全局数组maze表示迷宫const int M=4,N=4;int maze[M+2][N+2] = { {1, 1, 1, 1, 1, 1}, //迷宫示例 {1, 0, 0, 0, 1, 1}, {1, 0, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 1}, {1, 1, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1}  };bool MazePath(int xi,int yi,int xe,int ye);int main(void){if(!MazePath(1,1,4,4)){cout << "There is no path !" << endl;}system("pause");return 0;}bool MazePath(int xi,int yi,int xe,int ye)//(xi,yi)入口(xe,ye)出口 {int i,j,di;bool find = false;//find为true表明找到了下一个可走方块 Stack St;St.top = -1;St.top ++;St.data[St.top].i = xi;//将入口进栈 St.data[St.top].j = yi;St.data[St.top].di = -1;maze[xi][yi] = -1;//将迷宫值赋为-1,避免重复访问while(St.top != -1)//栈不空时循环 {i = St.data[St.top].i;j = St.data[St.top].j;di = St.data[St.top].di;if(i==xe && j==ye)//找到出口{cout << "迷宫路径如下:" << endl;for(int k=0;k<=St.top;k++){cout << "("<< St.data[k].i << "," <<  St.data[k].j << ")" <<endl;}cout << endl;return true;}find = false; while(di<4 && !find)//找到下一个可走方块 {di ++;switch(di){case 0: i=St.data[St.top].i-1;j=St.data[St.top].j;break; case 1: i=St.data[St.top].i;j=St.data[St.top].j+1;break;case 2: i=St.data[St.top].i+1;j=St.data[St.top].j;break;case 3: i=St.data[St.top].i;j=St.data[St.top].j-1;break;}if(maze[i][j]==0)//找到了下一个可走方块 {find = true;}}if(find)//有路径可走 {St.data[St.top].di = di;St.top ++;St.data[St.top].i = i;St.data[St.top].j = j;St.data[St.top].di = -1;maze[i][j] = -1;}else//没有路径可走 {//让该位置变为其他路径可走方块 maze[St.data[St.top].i][St.data[St.top].j] = 0;St.top --;//将该方块出栈 }}return false;}




栈作为一种最简单最基础的家喻户晓的数据结构,是必须掌握的,后续应该会有树和图的文章,敬请期待。




原创粉丝点击