后进先出(LIFO)的线性序列被称为栈。栈也是一种线性表,不过是操作受限的线性表,只能在一端进出。进出的一端被称为栈顶。另一端被称为栈底。栈可以采用顺序存储,也可以采用链式存储,分别被称为顺序栈和链栈。

一:栈

1.顺序栈

栈的顺序存储方式如下图:(见视频)
显然,顺序栈需要两个指针,base指向栈底,top指向栈顶。顺序栈的数据结构定义(动态分配)如下:

  1. typedef struct SQStack{
  2. ElemType *base;
  3. ElemType *top;
  4. }SQStack;

在栈定义好后,还要定义一个最大的分配空间,顺序结构都是这个要求,需要预先分配空间,因而可以采用宏定义或常量。

  1. #definr Maxsize 100
  2. //或者
  3. const int Maxsize = 100;

上面的结构体定义采用了动态分配形式,也可以采用静态分配形式,使用一个定长数组来存储数据元素,使用一个整形下标记录栈顶元素的位置。顺序栈的数据结构定义(静态分配)如下图所示。

  1. typedef struct SQStack{
  2. ElemType data[Maxsize];
  3. int top;
  4. }SQStack;

需要注意的是,栈只能在一端操作,后进先出的特性是人为规定的,也就是说不允许在中间进行查找取值插入删除等操作,但顺序栈本身是按顺序存储的,确实能从中间取出一个元素,但这样就不是栈了。
顺序栈的基本操作包括初始化,入栈,出栈和取栈顶元素等。这里以动态分配空间及int类型的元素为例进行详解。

  1. 初始化。

初始化一个空栈,动态分配Maxsize大小的空间,S.top和S.base指向该空间的基地址。

  1. bool InitStack(SQStack &s){
  2. S.base = new int [Maxsize];
  3. if(!S.base)
  4. return false;
  5. S.top = S.base;
  6. return true;
  7. }
  1. 入栈。

入栈前判断栈是否已满,如果栈已满,则入栈失败;否则将元素放入栈顶,栈顶指针向上移动一个位置(top++)。依次输入1,2,入栈,如下图所示。

  1. bool Push(SQStack &S,int e){
  2. if(S.top-S.base==Maxsize)
  3. return false;
  4. *S.top++=e //等价于*S.top = e;S.top++;
  5. return true;
  6. }
  1. 出栈

出栈前要判断栈是否已空,如果栈已空,则出栈失败;否则就将栈顶元素暂存到一个变量中。栈顶指针向下移动一个空间(top—)。栈顶元素所在的位置实际上是S.top-1,因此把该元素取出来,暂存在变量e中,然后S.top指针向下移动一个位置。因此可以先移动一个位置,即—S.top,然后取元素。
注意,空间未销毁。只是下次元素进栈进行原有值的覆盖。

  1. bool pop(SQStack &s,int &e){
  2. if(S.base==S.top)
  3. return false;
  4. e = *--S.top;
  5. return true;
  6. }
  1. 取栈顶元素

取栈顶元素和出栈不同,取栈顶元素只是把栈顶元素复制一次,栈顶指针未移动,栈内元素的个数未变。而出栈指栈顶指针向下移动一个位置,栈内不再包含该元素。

int GetTop(SQStack S){
    if(S.top!=S.base)
        return *(S.top-1);
    else
        return -1;
}

2:链栈

和线性表一样,栈可以采用顺序存储,也可以采用链式存储。顺序栈和链栈如下图所示:(视频)
顺序栈是分配一段连续空间,需要两个指针,base指向栈底,top指向栈顶。而链栈每个节点地址都是不连续的,只需一个栈顶指针即可。链栈的节点和单链表节点一样,包含两个域:数据域和指针域。可以把链栈看做一个不带头结点的单链表,但只能在头部进行插入删除取值等操作,无法在中间和尾部操作。
链栈的数据结构定义如下:

typedef struct StackNode
{
ElemType data;
struct StackNode *next;
}StackNode ,*LinkStack;

可以看出,链栈的节点定义和单链表一样,只不过它只能在栈顶那一端操作。
链栈的基本操作包括初始化,入栈,出栈,取栈顶元素等(以int为例)

  1. 初始化

初始化一个空栈,链栈不需要头结点,因此只需要让栈顶指针为空指针即可

bool InitStack(LinkStack &S){
    S=NULL;
    return true;
}
  1. 入栈

入栈就是把新节点压入栈顶,也就是将新节点放在第一个节点前面,然后把栈顶指针指向新节点即可。

p = new Snode;
p->data = e;

->是指针的指向运算符,通常与结构体一起使用。

  1. 出栈

出栈就是把栈顶指针删除,栈顶指针指向下一个节点,然后释放该节点空间。

bool Pop(LinkStack &S,int &e){
    LinkStack p;
    if(S==NULL)
    {
        return false;
    }
    e = S->data;
    p=S;
    S = S->next;
    delete p;
return true;
}
  1. 取栈顶元素

和出栈不同,取栈顶元素就是把栈顶元素复制一份,栈顶指针不改变。而出栈是删除栈顶元素,栈顶指针指向下一个元素。

int GetTop(LinkStack S){
    if(S!=NULL)
        return s->data;
    else
        return -1;
}
#include<iostream>
using namespace std;

#define Maxsize 100  //预先分配空间,这个数值根据实际需要预估确定;

typedef struct SqStack {
    int *base; //栈底指针
    int *top; //栈顶指针
}SqStack;

bool InitStack(SqStack &S) //构造一个空栈S
{
    S.base = new int[Maxsize];//为顺序栈分配一个最大容量为Maxsize的空间
    if (!S.base)    //空间分配失败
        return false;
    S.top=S.base;  //top初始为base,空栈
    return true;
}

bool Push(SqStack &S, int e) // 插入元素e为新的栈顶元素
{
    if (S.top-S.base == Maxsize) //栈满
        return false;
    *(S.top++) = e; //元素e压入栈顶,然后栈顶指针加1,等价于*S.top=e; S.top++;
    return true;
}

bool Pop(SqStack &S, int &e) //删除S的栈顶元素,暂存在变量e中
{
    if (S.base == S.top) //栈空
        return false;
    e = *(--S.top); //栈顶指针减1,将栈顶元素赋给e
    return true;
}

int GetTop(SqStack S) //返回S的栈顶元素,栈顶指针不变
{
    if (S.top != S.base)  //栈非空
        return *(S.top - 1); //返回栈顶元素的值,栈顶指针不变
    else
        return -1;
}

int main()
{
    int n,x;
    SqStack S;
    InitStack(S);//初始化一个顺序栈S
    cout <<"请输入元素个数n:" <<endl;
    cin>>n;
    int &m=n; 
    cout<<&n<<endl;
    cout <<"请依次输入n个元素,依次入栈:" <<endl;
    while(n--)
    {
        cin>>x; //输入元素
        Push(S, x);
    }
    cout <<"元素依次出栈:" <<endl;
    while(S.top!=S.base)//如果栈不空,则依次出栈
    {
        cout<<GetTop(S)<<"\t";//输出栈顶元素
        Pop(S, x);   //栈顶元素出栈
    }
    return 0;
}

三:队列

后进先出(FILO)是栈,队列的特点是先进先出(FIFO)。队列也是一种线性表,不过他也是操作受限的线性表,只能在两端操作:从一端进,从另一端出。进的一端被称作队尾(rear),出的一端被称作队头(front)。同样,队列可以采用顺序存储,也可以采用链式存储。

1,顺序队列

队列的顺序存储指用一段连续的空间存储数据元素,用两个整型变量记录队头和队尾元素的下标。如图(视频)。
顺序队列的数据结构定义(动态分配)如下:

struct SqQueue{
    int *base;//基地址
    int front,rear;//头索引 尾索引
};

顺序结构定义好之后,还要先定义一个最大分配空间,顺序结构都是如此。

#define Maxsize 100

顺序队列的静态分配数据结构定义如下

typedef struct SqQueue{
ElemType data[Maxsize];
    int front,rear;
}SqQueue;

与栈同理,队列只能从一端进一端出,不允许在中间查找取值替换等,先进先出也是人为规定的,若破坏此规则就不是队列了。
图解如视频。

2.循环队列

简单讲解下循环队列队空,队满的判定条件,以及入队,出队,队列元素个数计算等基本操作方法。
由于只是小的改动,大部分和顺序队列相同,只在视频中讲解,此处只写几个知识点。
(1)队空判断条件即Q.front ==Q.rear。
(2)判断队满(Q.rear+1)%Maxsize= Q.front。
(3)入队

Q.base[Q.rear]=x;
Q.rear = (Q.rear+1)%Maxsize;

(4)出队

e = Q.base[Q.front];
Q.front = (Q.front+1)%Maxsize;

(5)计算队列中元素个数

(Q.rear - Q.front+Maxsize)%Maxsize;

3.链队列

数据结构定义

typedef struct QNode    /* 结点结构 */
{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;

链队列结构体定义

typedef srtuct{
    Qnode *front;
    Qnode *rear;
}LinkQueue

各操作相同。

#include <iostream>
using namespace std;
#define Maxsize 100

typedef  struct SqQueue{
  int *base; //基地址
  int front,rear; //头指针,尾指针
}SqQueue;

//循环队列的初始化
bool InitQueue(SqQueue &Q)//注意使用引用参数,否则出了函数,其改变无效
{
    Q.base=new int[Maxsize];//分配空间
    if(!Q.base) return false;
    Q.front=Q.rear=0; //头指针和尾指针置为零,队列为空
    return true;
}

//循环队列的入队
bool EnQueue(SqQueue &Q,int e)//将元素e放入Q的队尾
{
    if((Q.rear+1)%Maxsize==Q.front) //尾指针后移一位等于头指针,表明队满
        return false;
    Q.base[Q.rear]=e; //新元素插入队尾
    Q.rear=(Q.rear+1)%Maxsize; //队尾指针加1
    return true;
}

//循环队列的出队
bool DeQueue(SqQueue &Q, int &e) //删除Q的队头元素,用e返回其值
{
    if (Q.front==Q.rear)
        return false; //队空
    e=Q.base[Q.front]; //保存队头元素
    Q.front=(Q.front+1)%Maxsize; //队头指针加1
    return true;
}

//取循环队列的队头元素
int GetHead(SqQueue Q)//返回Q的队头元素,不修改队头指针
{
    if (Q.front!=Q.rear) //队列非空
        return Q.base[Q.front];
    return -1;
}
//循环队列的长度
int QueueLength(SqQueue Q)
{
    return (Q.rear-Q.front+Maxsize)%Maxsize;
}

int main()
{
    SqQueue Q;
    int n,x;
    InitQueue(Q);//初始化队列(一定要初始化,否则后面存储出错)
    cout <<"请输入元素个数n:" <<endl;
    cin>>n;
    cout <<"请依次输入n个整型数,依次入队:" <<endl;
    while(n--)
    {
           cin>>x;
        EnQueue(Q,x);//入队
    }
    cout<<endl;
    cout <<"队列内元素个数,即长度:"<<QueueLength(Q)<<endl;
    cout <<"队头元素:" <<GetHead(Q)<<endl;
    cout <<"元素依次出队:" <<endl;
    while(true)//如果栈不空,则依次出栈
    {
        if(DeQueue(Q,x))
            cout<<x<<"\t";//出队元素
        else
            break;
    }
    cout <<endl;
    cout <<"队列内元素个数,即长度:"<<QueueLength(Q)<<endl;
    return 0;
}

训练,洛谷p1739—链接