数据结构(三)----栈和队列

news2025/6/22 22:51:29

目录

一.栈

1.栈的基本概念

2.栈的基本操作

3.顺序栈的实现

•顺序栈的定义

•顺序栈的初始化

•进栈操作

  •出栈操作

  •读栈顶元素操作

  •若使用另一种方式:

4.链栈的实现

•链栈的进栈操作

•链栈的出栈操作

•读栈顶元素

二.队列

1.队列的基本概念

2.队列的基本操作

3.用顺序存储实现队列

•初始化

•入队操作

•出队操作

•获得队头元素的值

•判满/空方案

4.用链式存储实现队列

•初始化

•入队操作

•出队操作

•队列满的条件

三.双端队列


一.栈

1.栈的基本概念

栈(stack)是只允许在一端进行插入或删除操作的线性表。

栈顶:允许插入和删除的一端。最上面的元素被称为栈顶元素。

栈底:不允许插入和删除的一端。最下面的元素被称为栈底元素。

如下图所示:

进栈顺序:a1-->a2-->a3-->a4-->a5

出栈顺序:a5-->a4-->a3-->a2-->a1

所以栈的特点是后进先出(Last In First Out ( LIFO ))

补充:

n个不同元素进栈,出栈元素不同排列的个数为\frac{1}{n+1}C_{2n}^{n}

上述公式称为卡特兰(Catalan)数,可采用数学归纳证明。

2.栈的基本操作

•Initstack(&S):初始化栈。构造一个空栈S,分配内存空间。
•DestroyStack(&L):销毁栈。销毁并释放栈s所占用的内存空间。
•Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶。

•Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。
•GetTop(S,&x):读栈顶元素。若栈S非空,则用x返回栈顶元素。
其他常用操作:
StackEmpty(S):判断一个栈S是否为空。若S为空,则返回true,否则返回false。

3.顺序栈的实现
•顺序栈的定义
#define Maxsize 10    //定义栈中元素的最大个数
typedef struct{
    ElemType data[Maxsize];    //静态数组存放栈中元素
    int top;    //栈顶指针
} SqStack;

void testStack(){
    SqStack S;     //声明一个顺序栈(分配空间)
}
//这里使用声明的方式分配内存空间,并没有使用malloc函数。
//所以给这个栈分配的内存空间,会在函数结束之后又系统自动回收。

声明顺序栈后,就会给各个数据元素分配连续的存储空间,大小为MaxSize*sizeof(ElemType)的空间。

•顺序栈的初始化
#define Maxsize 10    //定义栈中元素的最大个数
typedef struct{
ElemType data[Maxsize];    //静态数组存放栈中元素
int top;    //栈顶指针
} SqStack;

//初始化栈
void Initstack(Sqstack &S){
    S.top=-1;    //初始化栈顶指针
}

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

void testStack(){
    SqStack S;
    InitStack(S);
}

•进栈操作
#define MaxSize 10
typedef struct{
    ElemType data[Maxsize];
    int top;
} Sqstack;

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

//或者写为
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize-1)    //栈满,报错
        return false;
    S.data[++S.top]=x;
    return true;
}
//不能写为
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize-1)    //栈满,报错
        return false;
    S.data[S.top++]=x;
    return true;
}

//这意味着
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize-1)    //栈满,报错
        return false;    
    S.data[s.top]=x;    //新进栈的元素会把以前的元素覆盖 
    S.top = S.top + 1;
    return true;
}

  •出栈操作
#define MaxSize 10
typedef struct{
    ElemType data[Maxsize];
    int top;
} Sqstack;

//出栈操作
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;
}
//删除操作中,top指针往下移,只是逻辑上被删除了,数据还残留在内存中。

//出栈操作也可写为
bool Pop(Sqstack &S,ElemType &x){
    if(S.top==-1)    //栈空,报错
        return false;
    x=S.data[S.top--];
    return true;
}
//不能写为
bool Pop(Sqstack &S,ElemType &x){
    if(S.top==-1)    //栈空,报错
        return false;
    x=S.data[--S.top];
    return true;
}

//这意味着
bool Pop(Sqstack &S,ElemType &x){
    if(S.top==-1)    //栈空,报错
        return false;
    S.top=S.top-1;
    x=S.data[S.top];
    return true;
}

 如果先减,再将top的值赋给x,那么x值就会返回"i",而不是"j"

  •读栈顶元素操作
#define Maxsize 10
typedef struct{
    ElemType data[Maxsize];
    int top;
} Sqstack;

//出栈澡作
bool Pop(Sqstack &S,ElemType &x){
    if(S.top==-1)    //栈空,报错
        return false;
    x=S.data[s.top--];    //先出栈,指针再减1
    return true;
}

//读栈顶元素
bool GetTop(Sqstack S,ElemType &x){
    if(S.top==-1)    //栈空,报错
        return false;
    x=S.data[s.top];    //x记录栈顶元素
    return true;
}
//可以看到,出栈操作和读栈顶元素非常类似。

  •若使用另一种方式:

将top指针刚开始指向0,判断栈是否为空,即判断S.top==0,这样设计是将top指针指向下一个能插入元素的位置。

若进行入栈操作时,需要先把x放到top指针指向的位置,再让top+1,和之前的方式相反。

出栈操作也是,需要先让top-1,再把top指向的数据元素传回去。

代码如下:

#define Maxsize 10
typedef struct{
    ElemType data[Maxsize];
    int top;
} SqStack;

//初始化栈
void Initstack(Sqstack &s){
    S.top=0;    //初始化指向0
}

bool StackEmpty(Sqstack S){
    if(S.top==0)    //栈空
        return true;
    else
        return false;
}

//入栈操作
bool Push(SqStack &S,ElemType x){
    if(S.top == MaxSize)    //栈满,报错
        return false;
    S.data[S.top++]=x;
    return true;
}

//出栈操作
bool Pop(Sqstack &S,ElemType &x){
    if(S.top==0)    //栈空,报错
        return false;
    x=S.data[--S.top];
    return true;
}

void testStack(){    判断栈空
    SqStacks;//声明一个顺序栈
    InitStack(S);
}

顺序栈的缺点是栈的大小不可变,可以在刚开始就给栈分配大片的内存空间,但这样会导致内存空间的浪费,可以使用共享栈提高内存空间的利用率。共享栈即两个栈共享同一片内存空间。

代码如下:

#define MaxSize 10
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

总结:

4.链栈的实现

对于链栈而言,其进栈操作其实对应于链表中对头结点的"后插"操作,出栈操作对应于链表中对头结点的"后删"操作,就是将链头的一端看作栈顶的一端

建议先看:http://t.csdnimg.cn/IknBJ

代码如下:

//链栈的定义和链表的定义是相同的,只是命名不同
typedef struct Linknode{
    ElemType data;
    struct Linknode *next;
}LiStack;    //栈类型定义

//带头结点
bool InitStack(LiStack &L){
    L=(Linknode *)malloc(sizeof(Linknode));
    if(L==NULL)
        return false;    //内存不足,分配失败
    L->next=NULL;
    return true;    
}

bool Empty(LinkList L){
    return(L->next == NULL);
}


//不带头结点
bool InitStack(LiStack &L){
    L=NULL;
    return true;
}

bool Empty(LinkList L){
    return(L=NULL);
}
•链栈的进栈操作
//带头结点
LiStack LiSPush(LiStack &L){
    Linknode *s;
    int x;
    L=(LiStack)malloc(size(Linknode));
    L->next=NULL;
    scanf("%d",&x);
    while(x!=9999){
        s=(Linknode *)malloc(sizeof(Linknode));
        s->data=x;
        s->next=L->next;
        L->next=s;
        scanf("%d",&x);
    }
    return L;
}

//不带头结点
LiStack LisPush(LiStack &L){
    Linknode *s;
    int x;
    L=NULL;
    scanf("%d",&x);
    while(x!=9999){
        s=(Linknode *)malloc(sizeof(Linknode));
        s->data=x;
        s->next=L;
        L=s;
        scanf("%d",&x);
    }
    return L;
}
•链栈的出栈操作
//带头结点
LiStack LisPop(LiStack &L, int &e) {
    if (L->next == NULL) {
        // 栈空,无法出栈
        return NULL;
    }
    Linknode *q = L->next;
    e = q->data;
    L->next = q->next;
    free(q);
    return L;
}

//不带头结点
LiStack LisPop(LiStack &L, int &e) {
    if (L == NULL) {
        // 栈空,无法出栈
        return NULL;
    }
    Linknode *q = L;
    e = q->data;
    L = L->next;
    free(q);
    return L;
}
•读栈顶元素
//带头结点
int GetTop(LiStack &L) {
    if (L->next == NULL) {
        // 栈为空
        return -1; // 或者抛出异常
    }
    return L->next->data;
}

//不带头结点
int GetTop(LiStack &L) {
    if (L == NULL) {
        // 栈为空
        return -1; // 或者抛出异常
    }
    return L->data;
}

二.队列

1.队列的基本概念

栈(stack)是只允许在一端进行插入或删除操作的线性表。队列(aueue)是只允许在一端进行插入,在另一端删除的线性表。

队头,队尾,空队列:

空队列:没有数据元素

队头:允许删除的一端

队尾:允许插入的一端

队列的特点:先进入队列的元素先出队,即先进先出(First In First Out,FIFO)。

2.队列的基本操作

Initaueue(&Q):初始化队列,构造一个空队列。

DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间。

EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾

DeQueue(&a,&x):出队,若队列Q非空,删除队头元素,并用x返回。
GetHead(a,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。

其他常用操作:

QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。

3.用顺序存储实现队列
•初始化
#define Maxsize 10    //定义队列中元素的最大个数
typedef struct{
    ElemType data[Maxsize];    //用静态数组存放队列元素
    int front,rear;    //队头指针和队尾指针
}SqQueue;

void testQueue(){
    SqQueue Q;//声明一个队列
}

声明一个队列后,系统会分配一片连续的存储空间,大小为MaxSize*sizeof(ElemType),如下图所示:

队头指针:指向队头元素。

队尾指针:指向队尾元素的后一个位置。

所以还没有插入元素时,队头指针与队尾指针同时指向data[0]:

#define Maxsize 10    //定义队列中元素的最大个数
typedef struct{
    ElemType data[Maxsize];    //用静态数组存放队列元素
    int front,rear;    //队头指针和队尾指针
}SqQueue;

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

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

void testQueue(){
    SqQueue Q;//声明一个队列
    InitQueue(Q);

}

•入队操作

只能从队尾入队

#define MaxSize 10
typedef struct{
    ElemType data[Maxsize];
    int front,rear;
} SqQueue;

//入队
bool EnQueue(SqQueue &Q,ElemType x){
    if(Q.rear==MaxSize)    
        return false;    //队满则报错
    Q.data[Q.rear]=x;    //新元素插入队尾
    Q.rear=(Q.rear+1);
    return true;
}

注:rear=MaxSize不能作为队列已满的判断条件,上面的写法是错误的。如下图所示,若前面的元素出队了,要再插入元素,可以从前面无数据元素的区域插入。

正确写法:

#define MaxSize 10
typedef struct{
    ElemType data[Maxsize];
    int front,rear;
} SqQueue;

//入队
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;
}

这里的Q.rear=(Q.rear)%MaxSize实现的效果是:当(Q.rear+1)%MaxSize==0时,即“队满”时,会将rear指针重新指向data[0]。

这样用模运算,将存储空间在逻辑上变成了“环状。

如下图所示,队满条件是:队尾指针的再下一个位置是队头,即(Q.rear+1)%MaxSize==Q.front

为什么需要不能再插入一个元素,并且使rear和front指向同一个元素呢?

因为初始化队列的时候,rear指针与front指针就是指向同一个位置,同时我们也是通过判断rear和front指针是否指向同一个位置,判断队列是否为空的。

如果再插入一个元素,rear和front指针指向同一个位置,这样,判满与判空条件就会混淆起来。

所以必须牺牲一个存储单元,以区分队列满还是空。

•出队操作

只能让队头元素出队:

//出队(删除一个队头元素,并用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;
}

•获得队头元素的值
//获得队头元素的值,用x返回
bool GetHead(SqQueue Q,ElemType &x){
    if(Q.rear==Q.front)    //队空则报错
        return false;
    x=Q.data[Q.front];
    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;
}
•判满/空方案

方案一:

以上方案中,判断队列已满的条件:队尾指针的再下一个位置是队头,即:

(Q.rear+1)%MaxSize==Q.front

队空条件:队头指针与队尾指针指向同一个地方,即:

Q.rear=Q.front

队列元素个数:

(rear+MaxSize-front)%MaxSize

例如下图,rear=2,front=3,那么队列元素个数就是:(2+10-3)%10 =9%10=9

其实也可以不用牺牲一个存储空间,下面两种方案可供参考。

方案二:

#define MaxSize 10
typedef struct{
    ElemType data[MaxSize];
    int front,rear;
    int size;    //用size表示当前队列的长度,当入队成功size++,出队成功size--
}SqQueue;

具体代码如下: 

#define MaxSize 10
typedef struct {
    ElemType data[MaxSize];
    int front, rear;
    int size; // 用size表示当前队列的长度,当入队成功size++,出队成功size--
} SqQueue;

// 初始化队列
void InitQueue(SqQueue &Q) {
    Q.front = Q.rear = 0;
    Q.size = 0;
}

// 判断队列是否为空
bool QueueIsEmpty(SqQueue Q) {
    return (Q.rear == Q.front) && (Q.size == 0);
}

// 判断队列是否已满
bool QueueIsFull(SqQueue Q) {
    return (Q.rear == Q.front) && (Q.size == MaxSize);
}

// 入队操作
bool EnQueue(SqQueue &Q, ElemType x) {
    if (QueueIsFull(Q))
        return false;
    Q.data[Q.rear] = x;
    Q.rear = (Q.rear + 1) % MaxSize;
    Q.size++;
    return true;
}

// 出队操作
bool DeQueue(SqQueue &Q, ElemType &x) {
    if (QueueIsEmpty(Q))    // 队列为空
        return false;
    x = Q.data[Q.front];
    Q.front = (Q.front + 1) % MaxSize;
    Q.size--;
    return true;
}

方案三:

#define Maxsize 10
typedef struct{
    ElemType data[Maxsize];
    int front,rear;
    int tag;    //记录最近进行的是删除/插入
//每次删除操作成功时,都令tag=0,每次插入成功时,都令tag=1;
} SqQueue;

只有删除操作,才能导致队空,只有插入操作,才能导致队满。所以:

具体代码如下: 

#define Maxsize 10
typedef struct{
    ElemType data[Maxsize];
    int front,rear;
    int tag;    //记录最近进行的是删除/插入
//每次删除操作成功时,都令tag=0,每次插入成功时,都令tag=1;
} SqQueue;

// 初始化队列
void InitQueue(SqQueue &Q) {
    Q.front = Q.rear = 0;
    Q.tag = 0; // 初始时没有进行过操作,设置tag为0
}

// 判断队列是否为空
bool QueueIsEmpty(SqQueue Q) {
    return Q.front == Q.rear && Q.tag == 0;
}

// 判断队列是否已满
bool QueueIsFull(SqQueue Q) {
    return Q.front == Q.rear && Q.tag == 1;
}

// 入队操作
bool EnQueue(SqQueue &Q, ElemType x) {
    if (QueueIsFull(Q)) {
        return false;
    }
    Q.data[Q.rear] = x;
    Q.rear = (Q.rear + 1) % Maxsize;
    Q.tag = 1; // 插入成功,设置tag为1
    return true;
}

// 出队操作
bool DeQueue(SqQueue &Q, ElemType &x) {
    if (QueueIsEmpty(Q)) {
        return false;
    }
    x = Q.data[Q.front];
    Q.front = (Q.front + 1) % Maxsize;
    Q.tag = 0; // 删除成功,设置tag为0
    return true;
}

在考试时,也可能出现rear指向队尾元素的情况,如下图所示:

//rear指向队尾元素的后一个位置时入队操作:
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxSize;

//rear指向队尾元素时入队操作:
Q.rear=(Q.rear+1)%MaxSize;
Q.data[Q.rear]=x;

初始化操作:

void InitQueue(SqQueue &Q){
    //初始时,队头和队尾指针指向0
    Q.front=0;
    Q.rear=MaxSize-1;
}

判空操作: 

//判空
bool QueueEmpty(SqQueue Q){
    if((Q.rear+1)%MaxSize==Q.front)    //队空条件
        return true;
    else
        return false;

判满操作:

判满也不能用与判空相同的条件了:

可以牺牲一个存储空间,即队空时,队尾指针在队头指针后面一个位置,队满时,队尾指针在队头指针后面两个位置。

或者向上面说的一样,增加辅助变量,如size,tag

这里只演示牺牲一个存储空间的情况:

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

// 初始化队列
void InitQueue(SqQueue &Q) {
    Q.front = 0;
    Q.rear =MaxSze-1;
    
}

//判空
bool QueueEmpty(SqQueue Q){
    if((Q.rear+1)%MaxSize==Q.front)    //队空条件
        return true;
    else
        return false;


//判满
bool QueueEmpty(SqQueue Q){
    if((Q.rear+2)%MaxSize==Q.front)    //队空条件
        return true;
    else
        return false;

// 入队操作
bool EnQueue(SqQueue &Q, ElemType x) {
    if (QueueIsFull(Q)) {
        return false;
    }
    Q.rear=(Q.rear+1)%MaxSize;    //先往后移一个存储空间,再赋值
    Q.data[Q.rear]=x;
    return true;
}

// 出队操作
bool DeQueue(SqQueue &Q, ElemType &x) {
    if (QueueIsEmpty(Q)) {
        return false;
    }
    x = Q.data[Q.front];    
    Q.front = (Q.front + 1) % Maxsize;
    return true;
}

总结:

4.用链式存储实现队列
•初始化
typedef struct LinkNode{        //链式队列结点
    ElemType data;
    struct LinkNode *next;
}LinkNode;

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

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 IsEmpty(LinkQueue Q){
    if(Q.front==Q.rear)
        return true;
    else
        return false;
}

void testLinkQueue(){
    LinkQueue Q;    //声明一个队列
    InitQueue(Q);   //初始化队列
}

//初始化队列(不带头结点)
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=(LinkNode *)malloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;    //新结点插入到rear之后    
    Q.rear->next=s;  //修改表尾指针
    Q.rear=s;        

首先申请一个新结点,并把数据元素放到这一新结点当中:s->data=x;

新插入的结点一定是队列的最后一个结点,所以该结点的next指针指向NULL:s->next=NULL

将rear指向的结点的next指针指向新申请的s结点:Q.rear->next=s;

最后表尾指针会指向新的表尾结点: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;    //新结点插入到 rear 结点之后
        Q.rear=s;    //修改 rear 指针
    }
}

•出队操作
//队头元素出队(带头结点)
bool DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front==Q.rear)
        return false;    //空队
    LinkNode *p=Q.front->next;
    x=p->data;    //用变量x返回队头元素
    Q.front->next=p->next;    //修改头结点的 next 指针
    if(Q.rear==p)    //此次是最后一个结点出队
        Q.rear=Q.front;    //修改rear指针
    free(p);    //释放结点空间
    return true;
}

首先用p指向要出队的结点,即头结点之后的结点:LinkNode *p=Q.front->next;

接着修改头结点的后项指针:Q.front->next=p->next;

最后释放结点p:free(p)

若此次出队的结点p是当前队列的最后一个元素,在修改完头结点的后项指针后:

还需要修改表尾指针,让其指向头结点:Q.rear=Q.front;

最后释放p:free(p)

对于不带头结点的队列:

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

每次出队的是front指针指向的结点:LinkNode *p=Q.front;

由于没有头结点,所以每一次队头出队时,就需要修改队头指针指向的结点:

Q.front=p->next;

最后一个结点出队后,将front和rear都指向NULL:Q.front=NULL;Q.rear=NULL;

•队列满的条件

对于顺序存储的队列,存储空间都是预分配的,预分配的存储空间耗尽,则队满。而对链式存储而言,一般不会对满,除非内存不足。

三.双端队列

之前学习的栈,只允许从一端插入和删除的线性表:

队列则只允许从一端插入,另一端删除的线性表:

双端队列则是允许从两端插入,也允许从两端删除的线性表:

若只使用其中一端的插入、删除操作,则效果等同于栈。所以,只要是栈能实现的功能,双端队列一定能够实现。

双端队列还可以分为:

输入受限的双端队列:只允许从一端插入、两端删除的线性表。

输出受限的双端队列:只允许从两端插入、一端删除的线性表。

对于而言,合法的出栈序列有14种,可用卡特兰数计算:

对于输入受限的双端队列:

在栈中合法的序列,在双端队列中一定合法,所以只需要看“在栈中不合法”的输出队列即可。

可以得到以下结果,划线的是在栈中不合法,而在输入受限的双端队列中合法的序列:

对于输出受限的双端队列,同理:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1584659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【TensorRT】TensorRT C# API 项目更新 (1):支持动态Bath输入模型推理(下篇)

4. 接口应用 关于该项目的调用方式在上一篇文章中已经进行了详细介绍,具体使用可以参考《最新发布!TensorRT C# API :基于C#与TensorRT部署深度学习模型》,下面结合Yolov8-cls模型详细介绍一下更新的接口使用方法。 4.1 创建并配…

Ubuntu配置VScode的C++环境

在Ubuntu系统下配置C环境,并运行helloworld 1. 下载VScode 我这里使用的是星火应用商店,在商店里面可以直接下载安装 http://spark-app.store/ 2.创建文件夹 3.启动VScode并打开该文件夹 4.安装以下几个扩展 PS:Clang这个插件别安装&…

Spark 应用程序优化和调优总结

文章目录 前言调整 Spark 默认配置查看和设置 Spark 配置信息动态扩展集群负载 数据的缓存和持久化DataFrame.cache()DataFrame.persist()何时缓存和持久化何时不缓存和持久化 Spark 中的 JOINs广播连接排序合并连接 总结 前言 本文总结了 Spark 中比较重要和常用的调优手段&a…

docker pull镜像的时候指定arm平台

指定arm平台 x86平台下载arm平台的镜像包 以mysql镜像为例 docker pull --platform linux/arm64 mysqldocker images查看镜像信息 要查看Docker镜像的信息,可以使用docker inspect命令。这个命令会返回镜像的详细信息,包括其元数据和配置。 docker i…

【重磅推荐】2024七大零售行业线下开店超全指南大全共452份

如需下载完整PPTX可编辑源文件,请前往星球获取:https://t.zsxq.com/19F4dDDrv 联华快客便利店的加盟手册.docx 好德便利店加盟手册.docx 超市&便利店守则:商品退换货管理.docx 赠品管理制度.doc 选址必看.doc 新人续签考核作业.doc 物流箱管理制度.d…

AugmentedReality之路-平面检测(5)

本文介绍通过AR检测水平平面和垂直平面,并将检测到的平面转化为Mesh 1、在首页添加功能入口 在首页添加一个按钮,命名为Start World Track 2、自定义ExecStartAREvent 创建ARSessionConfig并取名为ARSessionConfig_World 自定义ExecStartAREvent&…

C++ | Leetcode C++题解之第20题有效的括号

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isValid(string s) {int n s.size();if (n % 2 1) {return false;}unordered_map<char, char> pairs {{), (},{], [},{}, {}};stack<char> stk;for (char ch: s) {if (pairs.count(ch)) {if (…

SSH穿透ECS访问内网RDS数据库

处于安全考虑&#xff0c;RDS一般只会允许指定的IP进行访问&#xff0c;而我们开发环境的IP往往是动态的&#xff0c;每次IP变动都需要去修改RDS的白名单&#xff0c;为我们的工作带来很大的不便。 那么如何去解决这个问题&#xff1f; 假如我们有一台ESC服务器&#xff0c;E…

DVWA -File Upload-通关教程-完结

DVWA -File Upload-通关教程-完结 文章目录 DVWA -File Upload-通关教程-完结页面功能LowMediumHighImpossible 页面功能 此页面的功能为选择某个图片文件点击Upload按钮上传&#xff0c;上传成功后得知文件上传路径为DVWA\hackable\uploads。 Low 源码审计 这段 PHP 代码…

双云及多云融合(混合云)

背景&#xff1a;客户对于业务的高可用需求&#xff0c;当发生故障时&#xff0c;业务还能正常使用&#xff0c;如某云机房整体宕机&#xff0c;或云管理服务整体宕掉&#xff0c;导致客户业务不可用&#xff0c;此时&#xff0c;需有业务能顺利切换到灾备云上。 需求&#xf…

【八股】AOP

AOP(Aspect Oriented Programming)&#xff0c;面向切面编程&#xff0c;他是一种编程范式。 作用&#xff1a; 在不改变原始设计的的基础上对其进行功能增强。 几个基本概念&#xff1a; 连接点&#xff1a;所有的方法 切入点&#xff1a;追加功能的方法 通知&#xff1a;追加…

基于小程序实现的社区户口管理的系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;ssm 【…

vue2 二次封装element 组件,继承组件原属性,事件,插槽 示例

测试页面代码 这里主要记录如何封装element的el-input 并且封装后具有el-input原本的属性 事件 插槽 下面为测试页面即组件调用 <script> import CustomInput from /components/CustomInput.vue;export default {name: TestPage,components: { CustomInput },data() …

SpringCloud系列(2)--SpringCloud和SpringBoot技术选型

前言&#xff1a;SpringCloud是微服务架构的一揽子解决方案&#xff0c;SpringBoot是一种技术&#xff0c;要使用SpringCloud&#xff0c;也需要使用到SpringBoot&#xff0c;所以要使用SpringCloud时&#xff0c;必须也要考虑到SpringBoot的适配问题 1、查看SpringCloud和与之…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 4月11日,星期四

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年4月11日 星期四 农历三月初三 1、 中办、国办&#xff1a;加大从优秀社区工作者中招录公务员力度。 2、 即日起&#xff0c;全国铁路实行新的货物列车运行图。 3、 台湾花莲地震死亡人数上升至16人&#xff0c;强震后“地…

在渲染项目instant-ngp使用代码(run.py)实现的补充说明

0 引言 最近&#xff0c;在做一个项目中有需要使用渲染接口&#xff0c;需要使用代码来实现。详细的步骤在文章instant-ngp中run.py的使用_/instant-ngp/./scripts/run.py", line 25, in https://blog.csdn.net/fengbingchun/article/details/129770444?ops_request_misc…

新型[datahelper@onionmail.org].datah 勒索病毒来袭:如何筑起安全防线?

在数字化时代&#xff0c;网络安全问题日益凸显&#xff0c;其中勒索病毒成为了一种非常严重的威胁。[datahelperonionmail.org].datah勒索病毒就是其中的佼佼者&#xff0c;它以其复杂的加密手段和恶劣的勒索行为&#xff0c;给用户带来了巨大的损失。本文将从病毒的运行机制、…

电脑技巧:推荐一款护眼神器小智护眼宝,呵护你的眼睛

目录 一、软件简介 二、软件特点 三、 安装说明 四、使用说明 一、软件简介 小智护眼宝是一款专为广大电脑使用者设计的高效蓝光过滤软件。通过过滤蓝光&#xff0c;它可以有效防止长时间使用电脑对眼睛造成的伤害&#xff0c;保护用户的视力健康。除了基础的护眼功能外&a…

Qt plugin 开发UI界面插件

目录 1.创建接口 2.创建插件 3.创建插件界面 4.插件实现 5.创建应用工程 6.应用插件 1.创建接口 打开QtCreater&#xff0c;点击左上角“文件”->新建文件或项目&#xff0c;在弹窗中选择C/CHeader File。 输入文件名&#xff0c;选好路径&#xff08;可自行设置名称…

Gitlab全量迁移

Gitlab全量迁移 一、背景1.前提条件 一、背景 公司研发使用的Gitlab由于服务器下架需要迁移到新的Gitlab服务器上。Gitlab官方推荐了先备份然后再恢复的方法。个人采用官方的另外一种方法&#xff0c;就写这篇文章给需要的小伙伴参考。 源Gitlab: http://old.mygitlab.com #地…