栈与队列的经典问题(万字详细讲解+基础回顾+括号匹配\用队列实现栈\用栈实现队列\设计循环队列-用C语言描述)

文章介绍了栈和队列这两种基本数据结构,包括它们的工作原理和典型应用场景。栈遵循后进先出(LIFO)原则,常用于表达式求值和递归;队列遵循先进先出(FIFO)原则,常见于进程调度和缓存。文章提供了用C语言实现栈和队列的示例,如括号匹配问题、用队列实现栈和用栈实现队列,以及循环队列的创建和操作。
摘要由CSDN通过智能技术生成

一、概要

栈和队列都是常见的线性数据结构,但是它们之间有很大的区别。

栈(Stack)是一种后进先出(Last In First Out,LIFO)的数据结构。栈顶是最后一个入栈的元素,只能从栈顶进行插入和删除操作。栈的应用非常广泛,例如表达式求值、递归函数调用等。

队列(Queue)是一种先进先出(First In First Out,FIFO)的数据结构。队尾是最后一个入队的元素,队头是最先出队的元素。队列的应用也非常广泛,例如进程调度、缓存等。

在实际应用中,我们需要根据不同的需求选择使用栈或队列。如果需要按照后进先出的顺序进行处理,就应该使用栈;如果需要按照先进先出的顺序进行处理,就应该使用队列。当然,在某些情况下,栈和队列也可以通过一些特殊的实现方式来满足相反的需求。例如,双端队列(Deque)可以同时支持队列和栈两种操作。


二、基础回顾

1.数据结构-利用动态数组实现栈

https://team-hsyt.blog.csdn.net/article/details/129915447?spm=1001.2014.3001.5502https://team-hsyt.blog.csdn.net/article/details/129915447?spm=1001.2014.3001.5502

2.数据结构-利用链表实现队(包含源码+详细解析)

https://team-hsyt.blog.csdn.net/article/details/129929290?spm=1001.2014.3001.5502https://team-hsyt.blog.csdn.net/article/details/129929290?spm=1001.2014.3001.5502

3.指针的进阶

https://team-hsyt.blog.csdn.net/article/details/128548110?spm=1001.2014.3001.5502https://team-hsyt.blog.csdn.net/article/details/128548110?spm=1001.2014.3001.5502


 另外,大家还要复习一下结构体传参相关的知识点!

三、经典问题

1.括号的匹配

力扣icon-default.png?t=N2N8https://leetcode.cn/problems/valid-parentheses/

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

bool isValid(char * s){
    int len = strlen(s);
    char stack[len+1];  // 定义一个栈
    int top = -1;       // 栈顶指针初始化为-1
    for (int i = 0; i < len; i++) {
        if (s[i] == '(' || s[i] == '[' || s[i] == '{') {     // 左括号入栈
            stack[++top] = s[i];
        } else {    // 右括号出栈并判断是否匹配
            if (top == -1) {    // 栈为空,无法匹配
                return false;
            }
            if (s[i] == ')' && stack[top] == '(') {     // 括号匹配
                top--;
            } else if (s[i] == ']' && stack[top] == '[') {  
                top--;
            } else if (s[i] == '}' && stack[top] == '{') {
                top--;
            } else {    // 括号不匹配,返回 false
                return false;
            }
        }
    }
    
    if (top == -1) {    // 栈为空说明所有括号都匹配成功
        return true;
    } else {    // 栈不为空说明还有括号未匹配成功
        return false;
    }
}

 

这段代码实现了一个判断输入字符串中括号是否匹配的函数 isValid。

isValid 函数中,首先使用 strlen 函数获取字符串 s 的长度,然后定义了一个 char 类型的数组 stack,作为栈的存储结构,并初始化栈顶指针 top 为 -1。

接着,函数使用 for 循环遍历字符串 s 中的每个字符。当遇到左括号时,将其入栈;当遇到右括号时,从栈中取出栈顶元素并进行匹配。如果栈为空或者当前右括号与栈顶元素不匹配,则返回 false。否则,栈顶元素出栈,继续遍历字符串。

最后,判断栈是否为空。如果为空,说明所有括号都匹配成功,返回 true;否则,还有括号未匹配成功,返回 false。

2.用队列实现栈

力扣https://leetcode.cn/problems/implement-stack-using-queues/description/

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int QDatatype;

typedef struct QueueNode{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

void QueueInit(Queue*pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDatatype x);
void QueuePop(Queue* pq);
int  QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);

void QueueInit(Queue* pq) {
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq) {
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->tail = 0;
}

void QueuePush(Queue* pq, QDatatype x) {//向队列尾部插入元素
	QNode* newcode = (QNode*)malloc(sizeof(QNode));
	if (newcode==NULL)
	{
		perror("malloc fail");
		return;
	}

	newcode->data = x;
	newcode->next = NULL;

	if (pq->head == NULL) {
		assert(pq->tail == NULL);
		pq->head = pq->tail = newcode;
	}
	else {
		pq->tail->next = newcode;
		pq->tail = newcode;
	}
	pq->size++;
}


void QueuePop(Queue* pq) {
	assert(pq);
	assert(pq->head != NULL);
	QNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head== NULL)
		pq->tail = next;
	pq->size--;
}
//QueueInit:初始化队列
//QueueDestroy:销毁队列
//QueuePush:向队列尾部插入元素
//QueuePop:弹出队列头部元素
int  QueueSize(Queue* pq)//QueueSize:获取队列大小
{
	assert(pq);
	return pq->size;
}

bool QueueEmpty(Queue* pq)//QueueEmpty:判断队列是否为空
{
	assert(pq);
	return pq->size == 0;
}

QDatatype QueueFront(Queue* pq)//Queueont:获取队列头部元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QDatatype QueueBack(Queue* pq)//QueueBack:获取队列尾部元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
//用两个队列实现一个栈
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;


MyStack* myStackCreate() {
    MyStack*pst=(MyStack*)malloc(sizeof(MyStack));
    if(pst==NULL){
        perror("malloc fail");
        return NULL;
    }
    QueueInit(&pst->q1);
    QueueInit(&pst->q2);
    return pst;
}

void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) {
    Queue*emptyQ=&obj->q1;
    Queue*nonemptyQ=&obj->q2;
    if(!QueueEmpty(&obj->q1)){
    emptyQ=&obj->q2;
    nonemptyQ=&obj->q1;    
    }
    //倒数据
    while(QueueSize(nonemptyQ)>1){
        QueuePush(emptyQ,QueueFront(nonemptyQ));
        QueuePop(nonemptyQ);
    }
    int top=QueueFront(nonemptyQ);
    QueuePop(nonemptyQ);
    return top;
}

int myStackTop(MyStack* obj){
    if(!QueueEmpty(&obj->q1))
    {
    return QueueBack(&obj->q1);
    }
    else
    {
    return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}

这段代码实现了一个基于队列的栈,即使用两个队列 q1 和 q2 来模拟栈的操作。每次 push 元素时,将元素插入到非空的队列中,如果两个队列都为空,则默认将元素插入到 q1 中。pop 元素时,先将非空队列中的元素取出并压入另一个空队列,直到只有一个元素时弹出即可。该栈支持 push、pop、top 和 empty 四种操作。

具体来说,这个程序中定义了两个队列:Queue 类型,包括队列头部指针 head、队列尾部指针 tail、当前队列中元素个数 size,和节点结构体 QNode,包括数据 data 和下一个节点指针 next。程序中还定义了一些与队列相关的函数,具体如下:

  • QueueInit(Queue*pq):用来初始化队列,将队列头部指针、队列尾部指针和队列大小全部设为 0。
  • QueueDestroy(Queue* pq):用来销毁队列,释放队列中所有节点的内存空间,并将队列头部指针、队列尾部指针和队列大小全部设为初始值。
  • QueuePush(Queue* pq, QDatatype x):向队列尾部插入元素,即在队列末尾插入一个节点,并将队列尾部指针指向该节点。
  • QueuePop(Queue* pq):弹出队列头部元素,即将队列头部指针指向下一个节点,并释放原来头部节点的内存空间。
  • QueueSize(Queue* pq):获取队列大小,即返回当前队列中元素的个数。
  • QueueEmpty(Queue* pq):判断队列是否为空,当队列大小为 0 时返回 true。
  • QueueFront(Queue* pq):获取队列头部元素,即返回队列头部节点包含的数据 data。
  • QueueBack(Queue* pq):获取队列尾部元素,即返回队列尾部节点包含的数据 data。

此外,该程序还定义了 MyStack 结构体,包括两个 Queue 类型的变量 q1 和 q2。MyStack 结构体还实现了以下函数:

  • myStackCreate():用来创建基于队列的栈。在函数中,首先通过 malloc 分配内存空间,然后分别初始化两个 Queue 变量 q1 和 q2,并将它们作为成员变量保存在 MyStack 结构体中。
  • myStackPush(MyStack* obj, int x):用来向栈中压入一个元素。在函数中,首先判断 q1 是否为空,如果非空则将元素插入到 q1 中,否则插入到 q2 中。
  • myStackPop(MyStack* obj):用来从栈中弹出一个元素。在函数中,首先找出非空队列和空队列,然后将非空队列中的所有元素都取出并插入到空队列中,直到非空队列中剩余一个元素,最后将该元素弹出并返回。
  • myStackTop(MyStack* obj):用来获取栈顶元素,即返回 q1 或 q2 中的最后一项。
  • myStackEmpty(MyStack* obj):用来判断栈是否为空,当 q1 和 q2 队列的大小均为 0 时返回 true。
  • myStackFree(MyStack* obj):用来销毁基于队列的栈,释放 q1 和 q2 队列的内存空间,并将 MyStack 结构体指针设置为 NULL。

在实际使用过程中,需要注意以下几点:

  • 对 Queue 相关函数的理解和掌握程度可能会影响到该栈的正确性,需要非常熟悉这些函数的实现原理以及使用方式。
  • 内存管理非常重要,需要始终保持严谨的编码习惯,避免内存泄漏、野指针等问题的发生。
  • 可能会遇到由于队列操作不当而导致的性能问题,可以适时进行优化。

3.用栈实队列

力扣icon-default.png?t=N2N8https://leetcode.cn/problems/implement-queue-using-stacks/

#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>

typedef int STDataType;

typedef struct  Stack 
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);//初始化
void STDesroy(ST* ps);//销毁
void STPush(ST* ps,STDataType x);//插入
void STPop(ST* ps);//删除

int STSize(ST* ps);//数据的长度
bool STEmpty(ST* ps);//判断是否为空值
STDataType STTop(ST* ps);//取栈顶的数据

void STInit(ST* ps)//初始化
{
	assert(ps);
	ps->a =(STDataType*)malloc(sizeof(STDataType)*4);
	if (ps->a == NULL) {
		printf("malloc fail\n");
		return;
	}
	ps->capacity = 4;
	ps->top = 0;//ps->top = 1;
	//top=0,意味着top指向栈顶数据的下一个,top=-1指向的是栈顶数据
}

void STDesroy(ST* ps)//销毁
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

void STPush(ST* ps, STDataType x)//插入
{
	assert(ps);
	//判断空间是否满的
	if (ps->top == ps->capacity) {
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) *ps->capacity*2);
		if (tmp==NULL) {
			printf("relloc fail\n");
			return;
		}
		//把数据放到top的位置
		ps->a = tmp;
		ps->capacity*=2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)//删除
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}

int STSize(ST* ps) {
	assert(ps);
	return ps->top;

}

bool STEmpty(ST* ps)//判断是否为空值
{
	assert(ps);
	return ps->top == 0;

}

STDataType STTop(ST* ps)//取栈顶的数据
{
	assert(ps);
	assert(!STEmpty(ps));//加断言解释一下
	return ps->a[ps->top - 1];
}

typedef struct {
    ST pushst;
    ST popst;
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));
    if(obj==NULL){
        perror("malloc fail");
        return NULL;
    }
    STInit(&obj->pushst);
    STInit(&obj->popst);
    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->pushst,x);
}

int myQueuePop(MyQueue* obj) {
    //倒·数据
    if(STEmpty(&obj->popst))
    {
        while(!STEmpty(&obj->pushst)){
            STPush(&obj->popst,STTop(&obj->pushst));
            STPop(&obj->pushst);
        }
    }
    int front =STTop(&obj->popst);
    STPop(&obj->popst);
    return front;
}

int myQueuePeek(MyQueue* obj) {
        //倒·数据
    if(STEmpty(&obj->popst))
    {
        while(!STEmpty(&obj->pushst)){
            STPush(&obj->popst,STTop(&obj->pushst));
            STPop(&obj->pushst);
        }
    }
   return STTop(&obj->popst);

}

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst)&&STEmpty(&obj->popst);
}

void myQueueFree(MyQueue* obj) {
    STDesroy(&obj->pushst);
    STDesroy(&obj->popst);
    free(obj);
}

这是一个使用两个栈来实现队列的 C 语言代码,即 MyQueue 结构体中包含两个 ST(Stack)类型的栈 pushst 和 popst。其中,pushst 用于入队操作,popst 用于出队操作。该代码主要实现了以下几个函数:

  1. MyQueue* myQueueCreate():创建一个新的队列并返回指向该队列的指针。在该函数中,我们通过调用 STInit 函数初始化了 pushstpopst 两个栈,并将它们封装到 MyQueue 结构体中。

  2. void myQueuePush(MyQueue* obj, int x):将整数 x 入队。在该函数中,我们只需要调用 STPush 函数将 x 放入 pushst 栈顶即可。

  3. int myQueuePop(MyQueue* obj):将队首元素出队并返回该元素的值。为了实现这一功能,我们需要使用两个栈,将 pushst 中的所有元素倒序放入 popst 中,并从 popst 的栈顶取出队首元素。在此之后,我们可以通过将 popst 的元素再次倒序放入 pushst 中维护队列的状态。

  4. int myQueuePeek(MyQueue* obj):返回队首元素的值,但并不将其出队。与 myQueuePop 函数相似,我们需要先将 pushst 中的元素倒序放入 popst 中,并从 popst 的栈顶取出队首元素。但与 myQueuePop 函数不同的是,该函数并不会将元素从 popst 中删除。

  5. bool myQueueEmpty(MyQueue* obj):判断队列是否为空。在该函数中,我们只需要检查 pushstpopst 两个栈是否均为空即可。

  6. void myQueueFree(MyQueue* obj):释放队列的内存空间。在该函数中,我们通过调用 STDesroy 函数销毁 pushstpopst 两个栈,并通过 free 函数释放 obj 所指向的内存空间。

 3.设计循环队列

typedef struct {
    int*a;
    int front;
    int rear;
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->front=obj->rear=0;
    obj->a=(int*)malloc(sizeof(int)*(k+1));
    obj->k=k;
    return obj;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->rear;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1)%(obj->k+1)==obj->front;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    return false;
    obj->a[obj->rear++]=value;
    obj->rear%=(obj->k+1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    return false;

    ++obj->front;
    obj->front%=(obj->k+1);
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj)
{   
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        // int x =obj->rear==0?obj->k:obj->rear-1;
        // return obj->a[x];
        return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)];
}



void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

这是一个使用数组实现循环队列的 C 语言代码,即 MyCircularQueue 结构体中包含一个 int 类型的数组 a、两个整型变量 front 和 rear 分别表示队首和队尾的下标位置,以及变量 k 表示队列的容量大小。该代码主要实现了以下几个函数:

  1. MyCircularQueue* myCircularQueueCreate(int k):创建一个容量为 k 的循环队列并返回指向该队列的指针。在该函数中,我们通过调用 malloc 函数分配数组的内存空间,并初始化 front 和 rear 变量为 0。

  2. bool myCircularQueueIsEmpty(MyCircularQueue* obj):判断队列是否为空。在该函数中,我们只需要检查 front 和 rear 两个变量是否相等即可。

  3. bool myCircularQueueIsFull(MyCircularQueue* obj):判断队列是否已满。由于采用了循环队列的实现方式,因此队列最多只能存储 k 个元素。在该函数中,我们使用求模运算判断队列是否已满。

  4. bool myCircularQueueEnQueue(MyCircularQueue* obj, int value):将元素 value 入队。在该函数中,我们先判断队列是否已满,若已满则返回 false,否则将元素放入 rear 所指向的位置并更新 rear 的值。由于采用了循环队列的实现方式,因此我们需要对 rear 的值取模操作以满足队列“循环”的特性。

  5. bool myCircularQueueDeQueue(MyCircularQueue* obj):将队首元素出队。在该函数中,我们先判断队列是否为空,若为空则返回 false,否则更新 front 的值即可。同样由于采用了循环队列的实现方式,我们需要对 front 的值取模操作以满足队列“循环”的特性。

  6. int myCircularQueueFront(MyCircularQueue* obj):返回队首元素的值。在该函数中,我们先判断队列是否为空,若为空则返回 -1,否则返回数组 a 中 front 所指向的位置的元素。

  7. int myCircularQueueRear(MyCircularQueue* obj):返回队尾元素的值。在该函数中,我们先判断队列是否为空,若为空则返回 -1,否则返回数组 a 中 rear-1 所指向的位置的元素。此处注意由于采用了循环队列的实现方式,因此我们需要对 rear 进行一定的计算以获取准确的位置。

  8. void myCircularQueueFree(MyCircularQueue* obj):释放队列的内存空间。在该函数中,我们通过调用 free 函数释放数组所占用的内存空间,并通过 free 函数释放 obj 所指向的内存空间。

 return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)]

myCircularQueueRear 函数中,代码行 return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)] 用于返回循环队列的队尾元素。

对于该行代码,我们可以进行如下解释:

首先,由于采用的是循环队列的实现方式,因此队列的队尾位置可能会“循环”到数组的前面。因此,为了避免出现队尾元素先“循环”到数组前面再回到数组尾部的情况,我们需要对数组的下标进行取模运算,即 (obj->rear-1+obj->k+1)%(obj->k+1)

其次,由于队尾指针在入队时是指向队尾元素的下一个位置,因此我们需要将 rear 指针减 1。因此,代码中的 (obj->rear-1+obj->k+1) 表示计算出队尾元素在数组中的下标。

最后,通过 obj->a[...] 访问数组 a 中相应位置的元素并将它作为函数的返回值。即 obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)] 返回循环队列的队尾元素。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Tech行者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值