栈与队列

142 阅读7分钟

栈的定义和基本操作

Stack 只允许在一端(表尾)进行插入或删除操作的线性表(一堆,碟子)

  • 栈顶:表尾端top,允许插入和删除的一端
  • 栈底:表头端bottom,不允许插入和删除的一端
  • 空栈:不含元素的空表

后进先出 Last In First Out(LIFO)
常考:给定进栈顺序找出合法的出栈顺序
卡特兰数:n个不同元素进栈,出栈元素不同排列的个数

栈的基本操作

InitStack(&S):初始化栈,构造一个空栈S,分配存储空间
DestroyStack(&S):销毁栈。销毁并释放栈S所占用的内存空间

ClearStack(&S):将S清为空栈
StackEmpty(S):判空操作,若栈S为空栈,则返回TRUE,否则FALSE

StackLength(S):栈长,返回S的元素个数,即栈的长度
GetTop(S,&x):读取栈顶元素,用x返回S的栈顶元素(不删除栈顶元素)

Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶
Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回(删除栈顶元素)

StackTraverse(S,visit()):从栈底到栈顶依次对S的每个数据元素调用visit()visit()失败则操作失败

顺序栈

栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。

顺序栈结构体

#define MaxSize 10           //定义栈中元素的最大个数
typedef struct{
    ElemType data[MaxSize];  //静态数组存放栈中元素
    int top;                 //栈顶指针
}SqStack;                    //sequence--顺序
//顺序栈的大小不可变

顺序栈基本操作实现

//初始化栈
void InitStack(SqStack &S){
    S.top = -1;
    //top初始化指向的位置影响后续代码的书写,top是指向下一个可插入位置还是
}

//判断栈空
bool StackEmpty(SqStack S){
    if(S.top == -1)         //栈空
        return true;
    else                    //不空
        return false;
}

//新元素入栈
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize - 1)//栈满,报错
        return false;
    S.top = S.top + 1;
    S.data[S.top] = x;      //此两行等同于:S.data[++S.top] = x;
    return true;
}
//出栈:数据还残留在内存中,知识逻辑上被删除了
bool Pop(SqStack &S,ElemType &x){
    if(S.top == -1)
        return false;
    x = S.data[S.top];   //栈顶元素先出栈
    S.top = S.top - 1;   //指针再减1
    //x = S.data[S.top--];
    return true;
}
//
bool GetTop(SqStack,S,ElemType &x){
    if(S.top == -1)
        return false;
    x = S.data[S.top];
    return true;
}
/*
- 初始化时 top = 0(指向接下可以插入元素的位置)
- 入栈:S.data[S.top++] = x
- 出栈:x = S.data[--S.top]
- 获得栈顶元素:x = S.data[S.top - 1]
- 栈空/栈满条件:
*/

共享栈

两个栈共享同一片内存空间,从两边往中间增长

共享栈结构体

typedef struct{
    ElemType data[MaxSize];
    int top0;    //0号栈栈顶指针
    int top1;    //1号栈栈顶指针
}ShStack;

共享栈基本操作实现

//初始化栈
void InitStack(ShStack &S){
    S.top0 = -1;
    S.top1 = MaxSize;
}
//栈满条件:top0 + 1 = top1;

链栈

将链栈看成一个单链表,对其进行操作限制符合栈的结构。入栈:对头结点的后插;出栈:对头结点的后删

链栈结构体

typedef struct LinkNode{
    ElemType data;          //数据域
    struct LinkNode *next;  //指针域
}*LiStack;

链栈基本操作实现

//初始化
bool InitLinkStack(LiStack *S){
    S = (LiStack *)malloc(sizeof(LiStack));
    if(S == NULL)
        return false;
    S->data = 0;
    S->next = NULL;
    return true;
}
//入栈
bool Push(LiStack *S,ElemType e){
    LiStack *new;
    new = (LiStack *)malloc(sizeof(LiStack));
    if(new == NULL)
        return false;
    new->next = S->next;
    S->next = new;
    new->data = e;
    return true;
}
//出栈
bool Pop(LiStack *S,ElemType *e){
    if(S == NULL)
        return false;
    LiStack *new;
    /*new = (LiStack *)malloc(sizeof(LiStack));
    if(new == NULL)
        return false;
    */
    new = S->next;
    *e = new->data;
    S->next = new->data;
    free(new);
    return true;
}
//取栈顶元素
bool GetTop(LiStack &S,ElemType &e){
    if(!S->next)
        return false;
    *e = S->next->data;
    return true;
}
//输出栈元素,遍历即可
bool printStack(LiStack &S){
    LiStack *p = S;
    while(p){
        p = p->next;
        printf("%d ",p->data);
    }
    printf("\n");
}

队列

队列的定义和基本操作

只允许在一端进行插入,在另一端删除的线性表

  • 队头:允许删除的一端
  • 队尾:允许插入的一端
  • 空队列

先进先出First In First Out(FIFO)

队列的基本操作

InitQueue(&Q):初始化队列,构造一个空队列
DestroyQueue(&Q):销毁队列,销毁并释放队列Q所占用的内存空间

CleraQueue(&Q):将Q清为空队列
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false

QueueLength(Q):返回Q的元素个数,即队列的长度
GetHead(Q,&x):读队头元素,若队列Q非空,将队头元素赋值给x

EnQueue(&Q,x):入队,若队列Q未满,将x插入,使之成为新的队尾
DeQueue(&Q,&x):出队,若队列Q非空,删除队头元素,并用x返回

QueueTraverse(Q,visit()):队头到队尾依次调用visit()

队列的顺序实现

顺序队列结构体

#define MaxSize 10
typedef struct{
    ElemType data[MaxSize];
    int front;   //队头指针
    int rear;    //队尾指针
}SqQueue;

顺序队列基本操作实现

//基于fornt指向队头元素,rear指向队尾元素的后一个位置(下一个应该插入的位置)
void InitQueue(SqQueue &Q){
//初始时 队头、队尾指针指向0
    Q->rear = Q.front = 0;
}
bool QueueEmpty(SqQueue Q){
    if(Q.rear == Q.front)
        return true;
    else
        return false;
}
bool EnQueue(SqQueue &Q,ElemType x){
    //队尾指针的后一个位置是否等于队头指针指向的位置
    if((Q.rear+1)%MaxSize == Q.front)
        return false;
    Q.data[Q.rear] = x;   //新元素插入队尾
    Q.rear = (Q.rear+1)%MaxSize;  //队尾指针加1取模
    return true;
    //模运算将存储空间在逻辑上变成了"环状"
}
bool DeQueue(SqQueue &Q,ELemType &x){
    if(Q.rear == Q.front)
        return false;
    x = Q.data[Q.front];
    Q.front = (Q.front+1)%MaxSize; //队头指针后移
    return true;
}
bool GetHead(SqQueue Q,ELemType &x){
    if(Q.rear == Q.front)
        return false;
    x = Q.data[Q.front];
    return true;
}
//判断队列已满/已空
/*
    1、牺牲一个存储单元
    2、在结构体中添加size用来记录队列当前长度,初始化size = 0
    3、
    结构体中添加tag变量记录最近进行的时删除0/插入1操作
    只有删除操作,才可能导致队空
    只有插入操作,才可能导致队满
    队满条件:front == rear && tag == 1
    队空条件:front == rear && tag == 0
*/

队列的链式实现

链表表示的队列,需要头指针和尾指针才能唯一确定

链式队列结构体

typedef struct LinkNode{    //链式队列结点
    ElemType data;
    struct LinkNode *next;
}LinkNode;
typedef struct{
    LinkNode *front,*rear;  //队列的队头和队尾指针
}LinkQueue;

//链式存储一般不会队满,除非内存不足

基本操作实现

初始化及判空

//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
    //初始时 front、rear 都指向头结点
    Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
    Q.front->next = NULL;
}
//判断队列是否为空
bool QueueEmpty(LinkQueue Q){
    if(Q.front == Q.rear)
        return true;
    else
        return false;
}

//初始化队列(不带头结点)
void InitQueue(LinkQueue &Q){
    //初始时,front rear都指向NULL
    Q.front = NULL;
    Q.rear = NULL;
}
bool IsEmpty(LinkQueue Q){
    if(Q.front == NULL)
        return true;
    else
        return false;
}

入队

//带头结点
void EnQueue(LinkQueue &Q,ELemType x){
    LinkNode *s = (LinkQueue *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.rear->next = s;  //新结点插入到rear之后
    Q.rear = s;        //修改表尾结点
}
//不带头结点
void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    //不带头结点的队列,第一个元素入队时需要特别处理
    if(Q.front == NULL){   //在空队列中插入第一个元素
        Q.front = s;
        Q.rear = s;
    }else{
        Q.rear->next = s;
        Q.rear = s;
    }
}

出队

//队头元素出队,带头结点
bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front == Q.rear)
        return false;
    LinkNode *p = Q.front->next;
    x = p->data;
    Q.front->next = p->next;
    //此次是最后一个结点出队,修改rear指针
    if(Q.rear == p)
        Q.rear = Q.front;
    free(p);
    return true;
}
//不带头结点
bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front == NULL)
        return false;
    LinkNode *p = Q.front;  //p指向此次出对的结点
    x = p->data;
    Q.front = p->next;
    if(Q.rear == p){
        Q.front = NULL;
        Q.rear = NULL;
    }
    free(p);
    return true;
}

双端队列

只允许从两端插入、两端删除的线性表

  • 输入受限的双端队列:只允许一端插入,两端删除的线性表
  • 输出受限的双端队列:只允许两端插入,一端删除的线性表
问题

若数据元素输入序列为1.2.3.4,则哪些输出序列是合法的,哪些是非法的?