栈和队列(2)

学习了上一节栈和队列的基本结构后,我们来做几道算法题巩固一下:

1.括号匹配问题

https://leetcode.cn/problems/valid-parentheses/

思路:

1.遍历字符串,遇到左括号直接入栈保存

2.遇到右括号就取出当前栈顶的左括号进行匹配

关于栈的定义与创建,前文已经详细讲解过,不熟悉的同学可以回顾这篇内容:

https://blog.csdn.net/gumidc/article/details/160866387?spm=1011.2124.3001.6209

主要代码如下:

易错点:

**·**右括号进行匹配前,必须先判断栈是否为空

· 遍历完全部字符后,必须检查栈的剩余状态。如果栈内还有剩余的元素,代表左括号比右括号多,数量不匹配。

完整代码如下:

cs 复制代码
typedef char 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);

//初始化和销毁
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 = 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* tmp = realloc(pst->a, NewCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		pst->a = tmp;
		pst->capacity = NewCapacity;
	}
	pst->a[pst->top]=x;
	pst->top++;
}
void STPop(ST* pst)
{
	assert(pst);
	pst->top--;
}
//取栈顶数据
STDataType STTop(ST* pst)
{
	assert(pst);
    assert(pst->top);
	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;
}

bool isValid(char* s) {
    ST st;
    STInit(&st);
    while(*s)
    {
    //左括号入栈
    if(*s=='('||*s=='['||*s=='{')
    {
        STPush(&st,*s);
    }
    //右括号进行匹配
    else
    {
        if(STEmpty(&st))
        {
            return false;
        }
        char top=STTop(&st);
        if(top=='('&&*s!=')'
        ||top=='['&&*s!=']'
        ||top=='{'&&*s!='}')
        {
            return false;
        }
        STPop(&st);
    }
    ++s;
    }
    //栈不为空,说明左括号比右括号多,数量不匹配
    int ret=STEmpty(&st);
    STDestroy(&st);
    return ret;
}

2.用队列实现栈

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

通俗来讲,这道题需要我们把队列先进先出的规则,手动转换出栈后进先出的效果。

思路:

1.维护两个队列q1和q2,始终保持一个队列存全部有效数据,另一个队列为空

2.入栈时:直接把新元素推入不为空的队列

3.出栈时:把非空队列里的前size-1个元素,全部转移到空队列;剩下的最后一个元素就是栈顶元素,直接弹出即可。

关于队列的定义与创建的代码也不再赘述,同在上一篇。

主要代码如下:

易错点:

销毁栈时,不能直接free(obj),obj的底层结构如下图:

obj只是最外层的结构体,真正的动态开辟,链式存储的节点,都保存在内部q1与q2的底层链表中。

直接释放obj只会销毁结构体本身,队列内部的所有内存都没有被回收,会造成严重的内存泄漏。

完整代码如下:

cs 复制代码
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType val;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

//初始化与销毁
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
//队尾插入
void QueuePush(Queue* pq, QDataType x);
//队头删除
void QueuePop(Queue* pq);
//取队头与队尾数据
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);

int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
//队尾插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
		QNode* newnode = (QNode*)malloc(sizeof(QNode));
		if (newnode == NULL)
		{
			perror("malloc");
			return;
		} 

		newnode->next = NULL;
		newnode->val = x;

		if (pq->phead == NULL)
		{
			pq->phead = pq->ptail = newnode;
		}
		else
		{
			pq->ptail->next = newnode;
			pq->ptail = newnode;
		}
		pq->size++;
}
//队头删除
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size!=0);
	//一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead=pq->ptail = NULL;
	}
	//多个节点
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}
//取队头与队尾数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur =pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->ptail = pq->phead = NULL;
	pq->size = 0;
}



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

MyStack* myStackCreate() {
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
    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* Empty=&(obj->q1);
    Queue* NonEmpty=&(obj->q2);
    if(!QueueEmpty(&obj->q1))
    {
        NonEmpty=&(obj->q1);
        Empty=&(obj->q2);
    }
    //将不为空的前size-1个数据导走,再删除最后一个就是栈顶数据
    while(QueueSize(NonEmpty)>1)
    {
       QueuePush(Empty,QueueFront(NonEmpty));
       QueuePop(NonEmpty);
    }
    QDataType top = QueueFront(NonEmpty);
    QueuePop(NonEmpty);

    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);
    obj=NULL;
}

3.用栈实现队列

https://leetcode.cn/problems/implement-queue-using-stacks/

这一道题和上一道是经典互逆题,我们要用两个栈实现队列先进先出的特性。

思路:

1.定义两个栈pushst与popst

2.入队:把新元素压入pushst

3.出队:

· 当popst为空时,把pushst里的所有元素全部一次性转移到popst里

· 经过栈的两次反转,原本后进先出的顺序,就变成了先进先出

· 此时popst的栈顶,正好就是队列最开头,最先进入的元素

只要popst还有剩余元素,后续出队,取队头只需要直接操作popst,不需要来回倒数据。

主要代码如下:

完整代码如下:

cs 复制代码
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);
//初始化和销毁
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 = 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* tmp = realloc(pst->a, NewCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		pst->a = tmp;
		pst->capacity = NewCapacity;
	}
	pst->a[pst->top]=x;
	pst->top++;
}
void STPop(ST* pst)
{
	assert(pst);
	pst->top--;
}
//取栈顶数据
STDataType STTop(ST* pst)
{
	assert(pst);
	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 myQueuePeek(MyQueue* obj) {
    if(STEmpty(&obj->popst))
    {
      //倒数据
     while(!STEmpty(&obj->pushst))
     {
       int top=STTop(&obj->pushst);
       STPush(&obj->popst,top);
       STPop(&obj->pushst);
     }
    }
    return STTop(&obj->popst);
}

int myQueuePop(MyQueue* obj) {
    int front = myQueuePeek(obj);
    STPop(&obj->popst);
    return front;
}

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

void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);

    free(obj);
}

4.设计循环队列

https://leetcode.cn/problems/design-circular-queue/

循环队列主要可通过数组链表 两种数据结构实现,其中基于数组的实现方式更直观,也是最常用的实现思路。

4.1 数组实现与核心缺陷:假溢出

我们先假设数组容量k=4,设置head与tail两个指针表示分别指向头和尾的有效数据。

插入(Push)一个数据,tail就往后移一位;删除(Pop)一个数据,head就往后移一位。

如下图直观展示了数组实现循环队列的完整运行过程:

但是这个过程存在假溢出问题:

当head与tail指针重合时,同时满足了队列为空与队列为满的两种冲突情况,如下图:

4.2 常见解决思路

主要有两种方式来解决这个问题:

(1)增加一个size记录数据个数

(2)额外多开一个空间

本文采用第二种方式来实现:

设定队列容量为k,实际数组容量为k+1,head指向当前队头有效元素,tail指向队尾的下一个可插入空位:

· 队列为空:头尾指针位置相等

· 队列已满:队尾下一位置与头指针重合

上图中(tail+1)%(k+1)== head 该公式兼容以下两种情况:

第一种:如上图第四组,tail未到达数组末尾,下一位置直接追上head

第二种:如上图第二组,tail到达数组末尾,通过取模回到数组头部,追上head

4.3 环形指针的循环实现逻辑

循环队列需要指针在到达数组末尾后自动回到数组起点,形成闭环。

取模运算时循环结构的核心:

head = (head + 1) % (k + 1) ; //出队后队头指针循环偏移
tail = (tail + 1) % (k + 1); //入队后队尾指针循环偏移

4.4 队尾元素的取值逻辑

这是实现循环队列时很容易出错的地方:

很多人习惯性直接用tail-1去获取队尾元素,但当tail=0时,tail-1就会造成数组下标越界。

因此不能简单地做减一处理,必须兼容tail-1时的环形折返情况,这里提供两种取值写法:

写法1:三目运算法

写法2:数学公式法

如下:

完整代码如下:

cs 复制代码
typedef struct {
    int* a;
    int head;//指向头
    int tail;//指向尾的下一个
    int k;
} MyCircularQueue;


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

bool myCircularQueueIsFull(MyCircularQueue* obj) {
   return (obj->tail+1)%(obj->k+1)==obj->head;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->head==obj->tail;
}

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

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    return false;
    obj->head++;
    obj->head%=(obj->k+1);
    return true;

}

int myCircularQueueFront(MyCircularQueue* obj) {
     if(myCircularQueueIsEmpty(obj))
    return -1;

    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    return -1;

    int rear=obj->tail==0?obj->k:obj->tail-1;
    return obj->a[rear];
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}
相关推荐
许长安1 小时前
C++ 原子变量与内存序:从std::atomic到release/acquire
开发语言·数据结构·c++·经验分享·笔记
进击的荆棘1 小时前
递归、搜索与回溯——综合(下)
c++·算法·leetcode·深度优先·dfs
不知名的忻1 小时前
归并排序(Java)
java·算法·排序算法
YUDAMENGNIUBI4 小时前
day20_逻辑回归
算法·机器学习·逻辑回归
澈2078 小时前
C++并查集:高效解决连通性问题
java·c++·算法
旖-旎10 小时前
深搜练习(单词搜索)(12)
c++·算法·深度优先·力扣
企客宝CRM11 小时前
2026年中小企业CRM选型指南:企客宝CRM处于什么位置?
android·算法·企业微信·rxjava·crm
橙淮11 小时前
二叉树核心概念与Java实现详解
数据结构·算法