【数据结构(三)】栈和队列

三. 栈和队列

1 栈(stack)

1.1 栈的基本概念

1.1.1 栈的定义
  • 栈是特殊的线性表:只允许在一端进行插入或删除操作, 其逻辑结构与普通线性表相同;
  • 栈顶(Top):允许进行插入和删除的一端 (最上面的为栈顶元素);
  • 栈底(Bottom):固定的,不允许进行插入和删除的一端 (最下面的为栈底元素);
  • 空栈:不含任何元素的空表;
  • 特点:后进先出(后进栈的元素先出栈);
  • 缺点:栈的大小不可变,解决方法——共享栈;
1.1.2 栈的基本操作
  • “创建&销毁”

    • InitStack(&S) 初始化栈:构造一个空栈S,分配内存空间;
    • DestroyStack(&S) 销毁栈:销毁并释放栈S所占用的内存空间;
  • “增&删"

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

    • GetTop(S, &x) 读取栈顶元素:若栈S非空,则用x-返回栈顶元素;(栈的使用场景大多只访问栈顶元素);
    • StackEmpty(S) 判空: 断一个栈S是否为空,若S为空,则返回true,否则返回false;

1.2 栈的顺序存储

1.2.1 顺序栈的定义
#define MaxSize 10         			//定义栈中元素的最大个数

typedef struct{
    ElemType data[MaxSize];       	//静态数组存放栈中元素
    int top;                      	//栈顶元素
}SqStack;

void testStack(){
    SqStack S;       				//声明一个顺序栈(分配空间)
                     				//连续的存储空间大小为 MaxSize*sizeof(ElemType)
}

1.2.2 顺序栈的基本操作
1.2.2.1 栈初始化
//初始化栈
void InitStack(SqStack &S){
    S.top = -1;                   	//初始化栈顶指针
}

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

1.2.2.3 进栈
bool Push(SqStack &S, ElemType x){
    if(S.top == MaxSize - 1)        	//栈满
        return false;
    
    S.top = S.top + 1;    				//指针先加1
    S.data[S.top] = x;    				//新元素入栈

    /*
    S.data[++S.top] = x;
    */
    return true;
}

1.2.2.4 出栈
bool Pop(SqStack &S, ElemType &x){
    if(S.top == -1)          				//栈空
        return false;
    
    x = S.data[S.top];       				//先出栈
    S.top = S.top - 1;       				//栈顶指针减1
    return true;

    /*
    x = S.data[S.top--];
    */

    //只是逻辑上的删除,数据依然残留在内存里

}

1.2.2.5 读栈顶元素
bool GetTop(SqStack S, ElemType &x){
    if(S.top == -1)
        return false;
    
    x = S.data[S.top];      				//x记录栈顶元素
    return true; 
}

注:也可以初始化时定义 S.top = 0 :top指针指向下一个可以插入元素的位置(栈顶元素的后一个位置);

  • 进栈操作 :栈不满时,栈顶指针先加1,再送值到栈顶元素。S.data[S.top++] = x;
  • 出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减1。x = S.data[–S.top];
  • 栈空条件:S.top==-1
  • 栈满条件:S.top==MaxSize-1
  • 栈长:S.top+1

1.2.3 共享栈

定义:利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。

  • 存取数据的时间复杂度均为O(1)
#define MaxSize 10         			//定义栈中元素的最大个数

typedef struct{
    ElemType data[MaxSize];       	//静态数组存放栈中元素
    int top0;                     	//0号栈栈顶指针
    int top1;                     	//1号栈栈顶指针
}ShStack;

//初始化栈
void InitSqStack(ShStack &S){
    S.top0 = -1;        //初始化栈顶指针
    S.top1 = MaxSize;   
}

栈满条件:top1-top0=1


1.3 栈的链式存储

1.3.1 定义:

采用链式存储的栈称为链栈。


1.3.2 优点:

链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。


1.3.3 特点:
  • 进栈和出栈都只能在栈顶一端进行(链头作为栈顶)

  • 链表的头部作为栈顶,意味着:

    1. 在实现数据"入栈"操作时,需要将数据从链表的头部插入;
    2. 在实现数据"出栈"操作时,需要删除链表头部的首元节点;
      因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;

栈的链式存储结构可描述为:

typedef struct Linknode{
    ElemType data;              			//数据域
    struct Linknode *next;      			//指针域
}Linknode,*LiStack;                      	//栈类型的定义

1.3.4 栈的基本操作:
1.3.4.1 带头结点的链栈基本操作
1.3.4.1.1 初始化
#include<stdio.h>

struct Linknode{          		//节点结构体
    int data;             		//数据域
    Linknode *next;       		//指针域
}Linknode,*LiStack;   

typedef Linknode *Node;   		//结点结构体指针变量
typedef Node List;        		//结点结构体头指针变量

								//初始化
void InitStack(LiStack &L){   	//L为头指针
    L = new Linknode; 	  		//把结点结构体的内存首地址赋值给头指针变量
    L->next = NULL;
}

1.3.4.1.2 判断栈是否为空
bool isEmpty(LiStack &L){
    if(L->next == NULL){
        return true;
    }
    else
        return false;
}

1.3.4.1.3 进栈

链栈基本上不会出现栈满的情况

void pushStack(LiStack &L, int x){
    Linknode *s;          		//创建存储新元素的结点
    s = new Linknode;
    s->data = x;

    //头插法
    s->next = L->next;
    L->next = s;
}

1.3.4.1.4 出栈
bool popStack(LiStack &L, int &x){
    Linknode *s;
    if(L->next == NULL) 				//栈空不能出栈
        return false;
    
    //删除操作
    s = L->next;
    x = s->data;
    L->next = L->next->next;
    delete(s);

    return true;
}

1.3.4.2 不带头结点的链栈基本操作
1.3.4.2.1 初始化
#include<stdio.h>

struct Linknode{
    int data;             				//数据域
    Linknode *next;       				//指针域
}Linknode,*LiStack;   

typedef Linknode *Node;   				//结点结构体指针变量
typedef Node List;        				//结点结构体头指针变量

										//初始化 
void initStack(LiStack &L){
    L=NULL;
}

1.3.4.2.2 栈判断是否为空
bool isEmpty(LiStack &L){
    if(L == NULL)
        return true;
    else
        teturn false;
}

1.3.4.2.3 进栈
void pushStack(LiStack &L, int x){
    Linknode * s;          //创建存储新元素的结点
    s = new Linknode;
    s->data = x;
    
    //插入操作
    s->next = L;
    L = s;
}

1.3.4.2.4 出栈
bool popStack(LiStack &L, int &x){
    //Linknode s; 
    Node s;
    if(L = NULL)     					//栈空不出栈
        return false;

    s = L;
    x = s->data;
    L = L->next;
    delete(s);
    
    return true;
}


2 队列(Queue)

2.1 队列的基本概念

2.1.1 定义

队列(Queue)简称队,是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。


2.1.2 特点
  • 队列是操作受限的线性表,只允许在一端进行插入 (入队),另一端进行删除 (出队)
  • 操作特性:先进先出 FIFO
  • 队头:允许删除的一端
  • 队尾:允许插入的一端
  • 空队列:不含任何元素的空表

2.1.3 队列的基本操作

“创建&销毁”

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

“增&删”

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

“查&其他”

  • GetHead(Q,&x): 读队头元素,若队列Q非空,则将队头元素赋值给x
  • QueueEmpty(Q): 判队列空,若队列Q为空,则返回

2.2 队列的顺序存储结构

  • 队头指针:指向队头元素
  • 队尾指针:指向队尾元素的下一个位置
2.2.1 队列存储的基本操作
//队列的顺序存储类型
# define MaxSize 10;     //定义队列中元素的最大个数
typedef struct{
    ElemType data[MaxSize];   //用静态数组存放队列元素
                              //连续的存储空间,大小为——MaxSize*sizeof(ElemType)
    int front, rear;          //队头指针和队尾指针
}SqQueue;

//初始化队列
void InitQueue(SqQueue &Q){
    //初始化时,队头、队尾指针指向0
    Q.rear = Q.front = 0;
}

void test{
    SqQueue Q;                //声明一个队列
    InitQueue(Q);
    //...
}

// 判空
bool QueueEmpty(SqQueue 0){
    if(Q.rear == Q.front)    //判空条件后
        return true;
    else 
        return false;
}

2.2.2 循环队列

定义:将循环队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。

a%b == a除以b的余数

初始:Q.front = Q.rear = 0;

队首指针进1:Q.front = (Q.front + 1) % MaxSize

队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize —— 队尾指针后移,当移到最后一个后,下次移动会到第一个位置

队列长度:(Q.rear + MaxSize - Q.front) % MaxSize

区分队空还是队满的情况:
方案一: 牺牲一个单元来区分队空和队满

队尾指针的再下一个位置就是队头,即 (Q.rear+1)%MaxSize == Q.front

  • 循环队列——入队:只能从队尾插入(判满使用方案一)
bool EnQueue(SqQueue &Q, ElemType x){
    if((Q.rear+1)%MaxSize == Q.front)        //队满
        return false;
    Q.data[Q.rear] = x;                      //将x插入队尾
    Q.rear = (Q.rear + 1) % MaxSize;         //队尾指针加1取模

    return true;
}
  • 循环队列——出队:只能让队头元素出队
//出队,删除一个队头元素,用x返回
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;
}
方案二: 不牺牲存储空间,设置size

定义一个变量 size用于记录队列此时记录了几个数据元素,初始化 size = 0,进队成功 size++,出队成功size--,根据size的值判断队满与队空

队满条件:size == MaxSize

队空条件:size == 0

# define MaxSize 10;     
typedef struct{
    ElemType data[MaxSize];   
    int front, rear;        
    int size;               //队列当前长度
}SqQueue;

//初始化队列
void InitQueue(SqQueue &Q){
    Q.rear = Q.front = 0;
    size = 0;
}
方案三: 不牺牲存储空间,设置tag

定义一个变量 tag,tag = 0 最近进行的是删除操作;tag = 1 最近进行的是插入操作;

每次删除操作成功时,都令tag = 0;只有删除操作,才可能导致队空;
每次插入操作成功时,都令tag = 1;只有插入操作,才可能导致队满;
队满条件:Q.front == Q.rear && tag == 1

队空条件:Q.front == Q.rear && tag == 0

# define MaxSize 10;     
typedef struct{
    ElemType data[MaxSize];   
    int front, rear;        
    int tag;               //最近进行的是删除or插入
}SqQueue;

2.3 队列的链式存储结构

2.3.1 定义:

队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表

队列的链式存储类型可描述为:

typedef struct LinkNode{      //链式队列结点
    ElemType data;
    struct LinkNode *next;
}

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

2.3.2 链式队列的基本操作

带头结点

  • 初始化 & 判空
void InitQueue(LinkQueue &Q){
    //初始化时,front、rear都指向头结点
    Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
    Q.front -> next = NULL;
}

//判断队列是否为空
bool IsEmpty(LinkQueue Q){
    if(Q.front == Q.rear)     //也可用 Q.front -> next == NULL
        return true;
    else
        return false;
}
  • 入队操作
//新元素入队 (表尾进行)
void EnQueue(LinkQueue &Q, ElemType x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); //申请一个新结点
    s->data = x;
    s->next = NULL;     //s作为最后一个结点,指针域指向NULL
    Q.rear->next = s;   //新结点插入到当前的rear之后
    Q.rear = s;         //表尾指针指向新的表尾
}
  • 出队操作
//队头元素出队
bool DeQueue(LinkQueue &Q, ElemType &x){
    if(Q.front == Q.rear)
        return false;                    //空队
    
    LinkNode *p = Q.front->next;         //p指针指向即将删除的结点 (头结点所指向的结点)
    x = p->data;
    Q.front->next = p->next;             //修改头结点的next指针
    if(Q.rear == p)                      //此次是最后一个结点出队
        Q.rear = Q.front;                //修改rear指针
    free(p);                             //释放结点空间

    return true;
}
  • 队列满的条件

    • 顺序存储:预分配存储空间
    • 链式存储:一般不会队满,除非内存不足
  • 计算链队长度 (遍历链队)

    • 设置一个int length 记录链式队列长度
  • 初始化 & 判空
  • 入队操作
//新元素入队 (表尾进行)
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;           //新结点插入到rear结点之后
        Q.rear = s;                 //修改rear指针指向新的表尾结点
    }
}

2.4 双端队列

定义:双端队列是指允许两端都可以进行入队和出队操作的队列

  • 双端队列允许从两端插入、两端删除的线性表;
  • 如果只使用其中一端的插入、删除操作,则等同于栈;
  • 输入受限的双端队列:允许一端插入,两端删除的线性表;
  • 输出受限的双端队列:允许两端插入,一端删除的线性表;

3 栈的应用

3.1 栈在括号匹配中的应用

用栈实现括号匹配

  • ((())) 最后出现的左括号最先被匹配 (栈的特性—LIFO);
  • 遇到左括号就入栈;
  • 遇到右括号,就“消耗”一个左括号 (出栈);
    匹配失败情况:
  • 扫描到右括号且栈空,则该右括号单身;
  • 扫描完所有括号后,栈非空,则该左括号单身;
  • 左右括号不匹配;
#define MaxSize 10   

typedef struct{
    char data[MaxSize];
    int top;
} SqStack;

//初始化栈
InitStack(SqStack &S)

//判断栈是否为空
bool StackEmpty(SqStack &S)

//新元素入栈
bool Push(SqStack &S, char x)

//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)



bool bracketCheck(char str[], int length){
    SqStack S;      //声明
    InitStack(S);   //初始化栈

    for(int i=0; i<length; i++){
        if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
            Push(S, str[i]);       //扫描到左括号,入栈
        }else{
            if(StackEmpty(S))      //扫描到右括号,且当前栈空
                return false;      //匹配失败
            
            char topElem;          //存储栈顶元素
            Pop(S, topElem);       //栈顶元素出栈

            if(str[i] == ')' && topElem != '(' )
                return false;

            if(str[i] == ']' && topElem != '[' )
                return false;

            if(str[i] == '}' && topElem != '{' )
                return false;       
        }
    }

    StackEmpty(S);                //栈空说明匹配成功
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eiker_3169

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值