【数据结构】从零到精通:图解栈和队列的基本操作与应用!

目录

一.引言

二.栈 (Stack):后进先出的世界

2.1 栈的顺序存储

2.1.1 顺序栈的结构定义

2.1.2 顺序栈的基本操作

2.2 栈的链式存储

2.2.1 链式栈的结构定义

2.2.2 链式栈的基本操作

三.队列 (Queue):先进先出的秩序

3.1 队列的链式存储

3.1.1 链队列的结构定义

3.2.2 链队列的基本操作

3.2 循环队列

3.2.1 循环队列的结构定义

3.2.2 循环队列的基本操作

四.栈与队列练习题

4.1 两个队列实现栈

4.2 两个栈实现队列

4.3 括号匹配问题


一.引言

       想象一下,你走进一家餐厅,服务员将你的菜品一个一个叠放在托盘上。当你取用时,最上面的菜总是最先被你拿走。又或者,你站在超市的结账队伍里,第一个排队的人总是第一个完成结账

       这两个简单的生活场景,恰好对应了计算机科学中最基础、最核心的两大数据结构:栈 (Stack)队列 (Queue)。它们结构简单,但却是实现递归、深度/广度优先搜索、程序内存管理、任务调度等复杂功能的基石。

二.栈 (Stack):后进先出的世界

       栈(Stack)是一种特殊的线性表,它所有的插入和删除操作都只能在一端进行,这一端被称为栈顶(Top)。它的核心原则是 后进先出 (Last In, First Out),简称 LIFO。栈的操作就像是一个羽毛球筒:你往里放球时,新球总是在最上面;当你取球时,也只能先取走最上面的球。

  • 栈顶 (Top):允许插入和删除的一端。

  • 栈底 (Bottom):固定的一端,不允许进行操作。

  • 压栈/入栈 (Push):向栈顶添加元素。

  • 弹栈/出栈 (Pop):从栈顶移除元素。

栈的实现方式主要有两种:顺序存储(数组或动态数组)链式存储(链表)

2.1 栈的顺序存储

2.1.1 顺序栈的结构定义

       栈的顺序存储,顾名思义,是利用一段连续的内存空间(如数组或动态数组)来存储栈中的元素。这种实现方式也被称为顺序栈

  • 实现特点: 它通过数组下标的移动来模拟元素的入栈(插入)和出栈(删除)操作。通常,我们用一个变量(如 top)来指示栈顶元素的位置下一个可插入数据的位置

需要注意二者的区别:当实现顺序栈时,如果Top 指向栈顶元素,则初始化时应设置为 −1。如果Top指向下一个可插入位置,则初始化时应设置为0。这样二者才可以区分开,同时确保栈空和栈中只有一个元素的状态都能正确识别!!!(重要)

  • 优点: 访问效率高,因为元素地址连续,存取速度快,时间复杂度为 O(1)。

  • 缺点: 必须预先分配内存。如果栈的空间不足(栈满),则需要进行动态扩容,这会带来一定的性能开销。

typedef int STDataType; // 假设栈存储的数据类型为 int

typedef struct Stack
{
	STDataType* a;  // 指向存储元素的动态数组
	int top;        // 指向下一个要插入的位置
	int capacity;   // 栈的容量
} ST;

注意:本文博客采用Top指向下一个要插入的位置,这样Top的值刚好也等于目前栈中存储的元素数量!!!(重要)

2.1.2 顺序栈的基本操作

        这里直接提供C语言程序,实现了栈的初始化、销毁、入栈、出栈、获取栈顶元素、判空和获取大小等核心功能。整个实现过程简单,思路清晰,并没有额外需要补充的点,不做过多解释。

// 初始化 (STInit)
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

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

// 入栈 (STPush):关键在于判断容量,并进行动态扩容
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->top == pst->capacity)
	{
		// 扩容:如果容量为0则初始化为4,否则加倍
		int newcapacity = pst->capacity == 0 ? 4 : (pst->capacity * 2);
		STDataType* temp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType)); // 注意:这里 sizeof(ST) 应该是 sizeof(STDataType)
		if (temp == NULL)
		{
			perror("realloc fail!\n");
			return;
		}
		pst->a = temp;
		pst->capacity = newcapacity;
	}
	// 插入元素并更新栈顶位置
	pst->a[pst->top] = x;
	pst->top++;
}

// 出栈 (STPop):移除栈顶元素,只需将 top 减一
void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0); // 断言:确保栈非空才能出栈
	pst->top--;
}

// 取栈顶元素 (STTop)
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0); // 断言:确保栈非空
	return pst->a[pst->top - 1]; // 栈顶元素位于 top-1 的位置
}

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

// 获取栈中元素个数 (STSize)
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

2.2 栈的链式存储

2.2.1 链式栈的结构定义

       与顺序栈使用连续内存空间不同,栈的链式存储是利用单链表来实现栈结构。这种实现方式被称为链式栈链式栈将栈中的每一个元素存储在一个独立的节点(Node)中,每个节点除了存储数据外,还包含一个指向下一个节点的指针

  • 实现特点: 栈顶操作(入栈和出栈)对应于链表的头部操作(头插和头删)。我们用一个头指针 (top) 来指向栈顶元素,也就是链表的第一个节点。

  • 优点:无需预先分配固定大小的内存,可以动态增长和缩小,不会有“栈满溢出”的风险。 入栈和出栈操作都只需要改变指针的指向,时间复杂度为 O(1)。

  • 缺点: 相比顺序栈,每个节点都需要额外的空间来存储指针,内存利用率稍低。

typedef int STDataType; // 假设栈存储的数据类型为 int

// 链表节点结构
typedef struct StackNode
{
	STDataType data;
	struct StackNode* next;
} Node;

// 栈结构体
typedef struct Stack
{
	Node* top;  // 栈顶指针 (指向链表的第一个节点)
	int size;   // 栈中元素的个数
} ST;

2.2.2 链式栈的基本操作

       这里同样直接提供C程序,实现初始化、销毁、入栈、出栈、获取栈顶元素、判空和获取大小等核心功能。整个过程容易理解,不做过多解释。

// 初始化 (STInit)
void STInit(ST* pst)
{
	assert(pst);
	pst->top = NULL; // 初始化时栈顶为空
	pst->size = 0;
}

// 销毁 (STDestroy):遍历链表,逐个释放节点
void STDestroy(ST* pst)
{
	assert(pst);
	while (pst->top)
	{
		Node* next = pst->top->next;
		free(pst->top);
		pst->top = next;
	}
	pst->size = 0;
}

// 入栈 (STPush):使用头插法 O(1)
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	// 1. 创建新节点
	Node* newnode = (Node*)malloc(sizeof(Node));
	if (newnode == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	// 2. 存入数据
	newnode->data = x;
	// 3. 新节点指向原栈顶
	newnode->next = pst->top;
	// 4. 更新栈顶指针
	pst->top = newnode;
	pst->size++;
}

// 出栈 (STPop):移除并释放栈顶节点 O(1)
void STPop(ST* pst)
{
	assert(pst && pst->top); // 断言:确保栈非空
	// 记录第二个节点,作为新的栈顶
	Node* next = pst->top->next;
	// 释放原栈顶节点
	free(pst->top);
	// 更新栈顶指针
	pst->top = next;
	pst->size--;
}

// 取栈顶元素 (STTop)
STDataType STTop(ST* pst)
{
	assert(pst && pst->top); // 断言:确保栈非空
	return pst->top->data;
}

// 判断栈是否为空 (STEmpty)
bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == NULL;
}

// 获取栈中元素个数 (STSize)
int STSize(ST* pst)
{
	assert(pst);
	return pst->size;
}

三.队列 (Queue):先进先出的秩序

       队列(Queue)是另一种重要的线性数据结构,它严格遵循 先进先出 (First In, First Out),简称 FIFO 的原则。想象一下排队购买电影票:第一个排队的人总是第一个获得服务并离开队伍。队列的操作就像是管道,数据从一端流入,从另一端流出。

  • 队头 (Front / Head):允许删除的一端,即元素离开队列的地方。

  • 队尾 (Rear / Tail):允许插入的一端,即元素进入队列的地方。

  • 入队 (Push / Enqueue):在队尾添加元素。

  • 出队 (Pop / Dequeue):从队头移除元素。

注意:在顺序存储(数组)结构中实现队列不仅较为复杂,还容易出现“假溢出”问题。更为关键的是,出队操作需要在数组头部删除元素,从而导致后续所有元素依次移动,其时间复杂度高达 O(N),效率较低。基于这一原因,队列通常更适合采用单链表来实现。

3.1 队列的链式存储

3.1.1 链队列的结构定义

       链队列是利用链表来实现队列结构。与链式栈只需要一个指针指向栈顶不同,高效的链队列通常需要两个指针队头指针 (phead):指向队列的第一个元素,用于执行出队操作。队尾指针 (ptail):指向队列的最后一个元素,用于执行入队操作。

  • 实现特点: 为了确保 O(1) 的操作效率,链队列通常采用:

    • 入队 (Enqueue): 在链表尾部插入节点(尾插法)。

    • 出队 (Dequeue): 在链表头部删除节点(头删法)。

typedef int QDataType; // 假设队列存储的数据类型为 int

// 链表节点结构
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
} QNode;

// 队列结构体
typedef struct Queue
{
	QNode* phead; // 队头指针
	QNode* ptail; // 队尾指针
	int size;     // 队列中元素个数
} Queue;

3.2.2 链队列的基本操作

       这部分程序也比较容易理解,就是单链表的基础操作,不做过多解释了。

// 初始化 (QueueInit)
void QueueInit(Queue* q)
{	
	assert(q);
	q->phead = NULL; // 队头指针置空
	q->ptail = NULL; // 队尾指针置空
	q->size = 0;
}

// 销毁 (QueueDestroy):遍历链表,释放所有节点
void QueueDestroy(Queue* q)
{
	assert(q);
	while (q->phead)
	{
		QNode* next = q->phead->next;
		free(q->phead);
		q->phead = next;
	}
	q->phead = q->ptail = NULL;
	q->size = 0;
}

// 入队 (QueuePush):尾插法实现 O(1)
void QueuePush(Queue* q, QDataType x)
{
	assert(q);
	// 1. 创建新节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL; 
	
	// 2. 判断队列是否为空
	if (q->ptail == NULL)
	{
		// 空队列时,新节点既是头又是尾
		q->phead = q->ptail = newnode;
	}
	else
	{
		// 非空时,原队尾节点的 next 指向新节点,并更新队尾指针
		q->ptail->next = newnode;
		q->ptail = newnode;
	}
	q->size++;
}

// 出队 (QueuePop):头删法实现 O(1)
void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q)); // 断言:确保队列非空
	
	// 处理只有一个元素的情况:出队后,头尾指针都置空
	if (q->phead == q->ptail)
	{
		free(q->phead);
		q->phead = q->ptail = NULL;
	}
	else
	{
		// 记录第二个节点,释放队头节点,并更新队头指针
		QNode* next = q->phead->next;
		free(q->phead);
		q->phead = next;
	}
	q->size--;
}

// 判断队列是否为空 (QueueEmpty)
bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->phead == NULL;
}

// 获取队头元素 (QueueFront)
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->phead->data;
}

// 获取队尾元素 (QueueBack)
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->ptail->data;
}

// 获取队列中元素个数 (QueueSize)
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

3.2 循环队列

3.2.1 循环队列的结构定义

       虽然链队列非常灵活,但在某些需要高性能和内存连续的场景,我们仍然希望使用数组来实现队列。为了解决传统数组队列出队后会产生闲置空间,导致“假溢出”(即数组中仍有空位但无法继续入队)的问题,我们引入了循环队列 (Circular Queue)。注:front指向队首元素,rear指向下一个可插入数据的位置。

       循环队列将数组的首尾看作是相连的,形成一个环状结构。当队尾指针到达数组末尾时,它会“回绕”到数组开头继续存储,从而有效地利用了数组的所有空间。

现在,我们就迎来了一个关键问题:循环队列如何来实现队头和队尾指针的移动?——通过取模运算!取模运算 (mod N) 的结果总是落在 0 到 N−1 的范围内,当指针达到数组末尾下标 N−1 后再加 1 时,取模运算会将其自动“回绕”到下标为 0 的位置,从而实现循环。

  • 初始时:Q->front = Q->rear = 0。

  • 队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。

  • 队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。

  • 队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。

此时,我们又引入了一个新的问题,无论是队空还是队满,front == rear 总是满足。那么循环队列如何判断队空和队满呢?——通常有三种解决思路!

  • 牺牲一个存储空间: 数组实际容量为 K+1,但最多只存储 K 个元素。此时,判满条件为 (rear + 1) % (K + 1) == front ,判空条件为 front == rear

  • 额外记录元素个数 (size): 不浪费空间,通过 size 或其他标志位来辅助判断。此时,判满条件为 size == Maxsize ,判空条件为 size == 0

  • 额外的状态标记(tag):通过记录队列上一次执行的操作来辅助判断。tag = 0 表示上一次操作是删除(出队),若此时 front = rear,则队列被清空。tag = 1 表示上一次操作是插入(入队),若此时 front = rear,则队列刚刚被填满。

那么在实际中最常用的方法是第一种,这里我们也以第一种方法为例,画图来解析:

// 结构体定义 (k 为用户要求的最大容量)
typedef struct 
{
    int* arr; // 存储数据的数组
    int front; // 队头指针 (指向队头元素)
    int rear; // 队尾指针 (指向下一个可插入的位置)
    int k;    // 用户要求的容量
}MyCircularQueue;

3.2.2 循环队列的基本操作

// 函数声明 (为保持完整性)
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);

// 创建 (myCircularQueueCreate):分配 k+1 空间
MyCircularQueue* myCircularQueueCreate(int k)
{
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    // 实际分配 k+1 的空间,用于牺牲一个位置来区分满和空
    obj->arr = (int*)malloc((k + 1) * sizeof(int)); 
    if(obj->arr == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    obj->front = 0; // 队头从0开始
    obj->rear = 0; // 队尾从0开始
    obj->k = k;
    return obj;
}

// 入队 (myCircularQueueEnQueue):O(1)
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
    if(myCircularQueueIsFull(obj)) // 先判满
    {
        return false;
    }
    obj->arr[obj->rear] = value;
    // 队尾指针循环后移
    obj->rear = (obj->rear + 1) % (obj->k + 1);
    return true;
}

// 出队 (myCircularQueueDeQueue):O(1)
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj)) // 先判空
    {
        return false;
    }
    // 队头指针循环后移
    obj->front = (obj->front + 1) % (obj->k + 1);
    return true;
}

// 获取队头元素 (myCircularQueueFront)
int myCircularQueueFront(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1; // 约定空队列返回-1或抛出错误
    }
    return obj->arr[obj->front];
}

// 获取队尾元素 (myCircularQueueRear)
int myCircularQueueRear(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1; // 约定空队列返回-1或抛出错误
    }
    // 队尾指向下一个可插入位置,所以队尾元素在它前面一个位置
    // 通过 (rear - 1 + k + 1) % (k + 1) 处理 rear = 0 时的取模负数问题
    return obj->arr[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];  
}

// 判断队列是否为空:头尾指针重合即为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
    return obj->front == obj->rear;
}

// 判断队列是否为满:队尾指针的下一个位置是队头指针,说明队尾和队头只隔着被牺牲的一个空间
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
    return (obj->rear + 1) % (obj->k + 1) == obj->front;    
}

// 释放资源
void myCircularQueueFree(MyCircularQueue* obj)
{
    free(obj->arr);
    free(obj);    
}

四.栈与队列练习题

       理论结合实践才能真正掌握数据结构。以下是三道经典练习题,它们能帮助你理解栈和队列在实际问题中的灵活应用。

4.1 两个队列实现栈

       请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

  • 入栈:入不为空的队列。

  • 出栈:将不为空的队列前n-1个元素倒到为空的队列,出不为空队列的最后一个元素。

typedef int QDataType;
typedef struct QListNode
{
	QDataType data;
	struct QListNode* next;
}QNode;
//声明队列
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

void QueueInit(Queue* q);	//队列初始化
void QueuePush(Queue* q, QDataType x);	//队尾入队列
void QueuePop(Queue* q);	//队头出队列
QDataType QueueFront(Queue* q);	//获取队列头部元素
QDataType QueueBack(Queue* q);	//获取队列尾部元素
bool QueueEmpty(Queue* q);	//判空
int QueueSize(Queue* q);	//获取队列中有效元素个数
void QueueDestroy(Queue* q);	//销毁队列

void QueueInit(Queue* q)
{	
	assert(q);
	q->phead = NULL;
	q->ptail = NULL;
	q->size = 0;
}

void QueuePush(Queue* q, QDataType x)
{
	assert(q);
	QNode* newcode = (QNode*)malloc(sizeof(QNode));
	if (newcode == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	newcode->data = x;
	newcode->next = NULL;
	if (q->ptail == NULL)
	{
		q->phead = q->ptail = newcode;
	}
	else
	{
		q->ptail->next = newcode;
		q->ptail = newcode;
	}
	q->size++;
}

bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->phead == NULL;
}

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	if (q->phead == q->ptail)
	{
		free(q->phead);
		q->phead = q->ptail = NULL;
	}
	else
	{
		QNode* next = q->phead->next;
		free(q->phead);
		q->phead = next;
	}
	q->size--;
}

QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->phead->data;
}

QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->ptail->data;
}

int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

void QueueDestroy(Queue* q)
{
	assert(q);
	while (q->phead)
	{
		QNode* next = q->phead->next;
		free(q->phead);
		q->phead = next;
	}
	q->phead = q->ptail = NULL;
	q->size = 0;
}

typedef struct 
{
    Queue q1;
    Queue q2;    
} MyStack;


MyStack* myStackCreate()
{
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&(obj->q1));
    QueueInit(&(obj->q2)); 
    return obj;   
}

void myStackPush(MyStack* obj, int x)
{
    //入数据入不为空的队列
    if(!QueueEmpty(&(obj->q1)))
    {
        QueuePush(&(obj->q1), x);
    }
    else
    {
        QueuePush(&(obj->q2), x);
    }
}

int myStackPop(MyStack* obj)
{
    //假设法
    Queue* Empty = &(obj->q1);
    Queue* NoneEmpty = &(obj->q2);
    if(!QueueEmpty(&(obj->q1)))
    {
        NoneEmpty = &(obj->q1);
        Empty = &(obj->q2);
    }
    //出数据不为空的倒一下
    while(QueueSize(NoneEmpty) > 1)
    {
        QueuePush(Empty, QueueFront(NoneEmpty));
        QueuePop(NoneEmpty);
    }
    //出不为空的最后一个数据
    int data = QueueBack(NoneEmpty);
    QueuePop(NoneEmpty);
    return data;
}

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);
}

4.2 两个栈实现队列

       请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

  • 入队:直接将元素压入输入栈。

  • 出队:如果输出栈有数据,说明这些数据已经按正确的队列顺序排列,直接弹出数据即可。如果没有数据,需要将输入栈的数据依次弹出并压入输出栈,再从输出栈弹出一个元素,完成出队操作。

//声明栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* pst);		//初始化
void STDestroy(ST* pst);	//销毁
void STPush(ST* pst, STDataType x);	//入栈
void STPop(ST* pst);	//出栈
STDataType STTop(ST* pst);	//取栈顶元素
bool STEmpty(ST* pst);	//判空
int STSize(ST* pst);	//获取数据个数

//top指向栈中数据的下一个位置
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : (pst->capacity * 2);
		STDataType* temp = (STDataType*)realloc(pst->a, newcapacity * sizeof(ST));
		if (temp == NULL)
		{
			perror("realloc fail!\n");
			return;
		}
		pst->a = temp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->a[pst->top - 1];
}

bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}

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

typedef struct 
{
    ST PushST;
    ST PopST;   
}MyQueue;


MyQueue* myQueueCreate()
{
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    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 data = STTop(&(obj->PopST));
    STPop(&(obj->PopST));
    return data;
}

int myQueuePeek(MyQueue* obj)
{
    if(STEmpty(&(obj->PopST)))
    {
        return obj->PushST.a[0];
    }
    return STTop(&(obj->PopST));
}

bool myQueueEmpty(MyQueue* obj) 
{
    return STEmpty(&(obj->PushST)) && STEmpty(&(obj->PopST));
}

void myQueueFree(MyQueue* obj)
{
    STDestroy(&(obj->PushST));
    STDestroy(&(obj->PopST));
    free(obj);
}

4.3 括号匹配问题

题目描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。①左括号必须用相同类型的右括号闭合。②左括号必须以正确的顺序闭合。③每个右括号都有一个对应的相同类型的左括号。

题目解析:用栈充当一个“等待匹配”的容器,完美保证匹配顺序的正确性。

  • 遍历字符串: 从左到右依次扫描字符串中的每个字符。

  • 遇到左括号: 如果字符是左括号(如 (, {, [),将其压入栈中。这表示我们期望在将来某个时刻遇到它的匹配右括号。

  • 遇到右括号: 如果字符是右括号(如 ), }, ]),此时必须立即检查栈顶元素:

    • 栈为空: 如果栈为空,说明在当前右括号之前没有匹配的左括号,匹配失败,字符串无效。

    • 栈非空: 弹出栈顶元素,检查它是否与当前右括号类型匹配。如果类型不匹配(例如,遇到 ),但栈顶是 [),匹配失败,字符串无效。

  • 遍历结束: 当整个字符串遍历完毕后,检查栈的状态:

    • 栈为空: 说明所有左括号都被成功匹配并弹出了,字符串有效

    • 栈非空: 说明栈中还残留有未匹配的左括号,字符串无效。


       请各位读者多多点赞,多多支持! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值