栈与队列常见习题分析

1.括号匹配问题

在这里插入图片描述
接下来我们分析一下这道题存在的几种情况:

1.字符串中只存在左括号或者是右括号:
在这里插入图片描述
此时,肯定是不匹配的。
2.字符串中既有左括号,也有右括号,但是不一定全部的左括号都有对应的右括号:
在这里插入图片描述
此时左括号与右括号也是不匹配的。
3.字符串中左括号右括号的数量相对应,但是不是以正确的顺序闭合:
在这里插入图片描述
此时也是不匹配的。

那么这道题思路应该是怎样的呢?其实这儿我们就可以用到栈先入后出的思想了,如果此时是左括号,我们就入栈,字符串就右移一位,如果遇见了右括号,就出栈,然后比较此时左右括号是否匹配就可以了:
在这里插入图片描述
代码实现:

bool isValid(char * s){
    Stack st;
    StackInit(&st);
    while(*s)
    {
        //如果是左括号,就入栈
        if(*s == '(' || *s == '[' || *s == '{')
        {
            StackPush(&st, *s);
            s++;
        }
        //如果不是左括号,就开始判断
        else
        {
            //如果此时栈里面为空,说明没有左括号存在,就返回false;
            if(StackEmpty(&st))
            {
                return false;
            }
            else
            {
                //栈顶元素出栈,判断是否匹配
                char top = StackTop(&st);
                if(top == '(' && *s != ')'
                || top == '{' && *s != '}'
                || top == '[' && *s !=']')
                {
                    StackPop(&st);
                    return false;
                }
                else
                {
                    StackPop(&st);
                    s++;
                }
            }
        }
    }
    //如果此时栈里面不为空,就不匹配
    if(!StackEmpty(&st))
    {
        return false;
    }
    //为空就匹配
    else{
        return true;
    }
    //最后销毁
    StackDestory(&st);
}

2.用队列实现栈

在这里插入图片描述
我们通过学习可以知道栈是一种先入后出的结构,队列是队头出,队尾入的结构,那么如何用队列来实现栈呢?

首先我们创建两个队列,然后用这两个队列来模拟栈的实现:

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;
//栈的创造
MyStack* myStackCreate() {
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q1);
    QueueInit(&obj->q2);
    return obj;
}

2.1 元素的入栈

此时我们模拟实现的是栈,插入就需要从栈顶入,而队列入元素是从队尾入,他们性质都是一样的,但是此时有两个队列,我们该入那个呢?在这儿就必须保证有一个队列是空的,如果两个队列里都有元素,我们无法进行下一步了。
如果q1为空,就从q2入,如果q2为空,就从q1入,如果俩都为空,从谁入都无所谓:
在这里插入图片描述
代码实现:

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

2.2 元素的移除并返回栈顶元素

因为栈每次出栈都是栈顶元素先出,我们此时就可以将q2的前n-1个元素全部插入q1中,此时q2就只剩队尾的元素了,再将队尾的元素拿出来,就相当于是栈顶出,此时q2就为空了,如果还需要继续出,依然重复刚才操作,只不过是q1,q2的位置换了而已:
在这里插入图片描述
代码实现:

int myStackPop(MyStack* obj) {
    Queue* empty = &obj->q1;
    Queue* noempty = &obj->q2;
    //判断是空队列
    if(!QueueEmpty(&obj->q1))
    {
        empty = &obj->q2;
        noempty = &obj->q1;
    }
    while(QueueSize(noempty) > 1)
    {
        QueuePush(empty,QueueFront(noempty));
        QueuePop(noempty);
    }
    int top = QueueFront(noempty);
    QueuePop(noempty);
    return top;
}

2.3 返回栈顶元素

其实就是将队尾的元素返回,q1为空时返回q2的队尾元素,q2为空时返回q1的队尾元素:
在这里插入图片描述
代码实现:

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

2.4 判断栈是否为空

也就是判断两个队列是否为空,两个队列都为空了,就为空:

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

2.5 销毁栈

也就是将两个队列销毁,在将我们的栈销毁

void myStackFree(MyStack* obj) {
    QueueDestory(&obj->q1);
    QueueDestory(&obj->q2);

    free(obj);
}

3.用栈实现队列

在这里插入图片描述
栈是先入后出,队列是队头出,队尾入,又怎么样用栈来实现队列呢?

3.1 元素的插入

其实入很简单,我们只要保证从非空的那个栈里面入就可以了,这样就能保证入的元素肯定是处于栈顶,所以我们两个栈一个可以设计为专门入栈,另一个专门出栈:
在这里插入图片描述

typedef struct {
    Stack pushst;//专门入栈
    Stack popst;//专门出栈
} MyQueue;

MyQueue* myQueueCreate() {
   MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)) ;
   StackInit(&obj->pushst);
   StackInit(&obj->popst);

   return obj;
}

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

3.2 从开头移除队列元素并返回

因为栈是先入后出,如果要模拟出队列性质,就需要最先将栈底的元素拿出来,我们就可以考虑将pushst里的元素挨个入进popst里面,这样就在pushst里面最底的元素在popst里面就成为了最顶元素,如果最开始popst不为空,我们就直接移除就可以了,就可以模拟出先入先出的性质:
在这里插入图片描述
代码实现:

int myQueuePop(MyQueue* obj) {
	//判断popst是否为空
    if(StackEmpty(&obj->popst))
    {
	    while(!StackEmpty(&obj->pushst))
	    {
	    	//将pushst的元素挨个放入popst里面
	        StackPush(&obj->popst,StackTop(&obj->pushst));
	        //将pushst里的元素移除
	        StackPop(&obj->pushst);
	    }
    }
    //获取popst里栈顶元素
    int top = StackTop(&obj->popst);
    StackPop(&obj->popst);
    
    return top;
}

3.3 返回队列开头元素

其实就是返回popst里面最顶的元素:

代码实现:

int myQueuePeek(MyQueue* obj) {
	//判断popst是否为空
    if(StackEmpty(&obj->popst))
    {
	    while(!StackEmpty(&obj->pushst))
	    {
	    	//将pushst的元素挨个放入popst里面
	        StackPush(&obj->popst,StackTop(&obj->pushst));
	        //将pushst里的元素移除
	        StackPop(&obj->pushst);
	    }
    }
    return StackTop(&obj->popst);
}

3.4 判断队列是否为空

就是判断popst 与 pushst是否都为空:

代码实现:

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

3.5 销毁队列

也就是销毁掉popst与pushst,在销毁掉队列:

代码实现:

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

    free(obj);
}

4.循环队列实现

首先我们得理解循环队列是什么?

循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

1.当head == tail 时,这就是一个空的循环队列。
在这里插入图片描述
2.当head == tail + 1时,这就是一个满的循环队列;
在这里插入图片描述
我们在开辟循环队列时,总会多开辟一个空间,为什么呢?

因为如果不多开辟一个空间,判空和判满容易混淆,比如说下面这种情况,在tail位置插入一个元素,tail就要向后移动,此时head == tail,但他却是满的:
在这里插入图片描述
在这里插入图片描述

4.1 初始化队列

循环队列既可以用数组实现,也可以用链表实现,但是链表实现的话不容易找到尾,所以在这儿我们使用数组来实现:
在这里插入图片描述
在这里插入图片描述

typedef int CTDataType;
typedef struct CQueue
{
    CTDataType* data;//存储元素队列
    int head;//循环队列头
    int tail;//循环队列尾
    int size;//存储数据个数
} CQueue;
//假设队列长度为k
void CQueueInit(CQueue* ps, int k)
{
    assert(ps);
    ps->data = (CTDataType*)malloc(sizeof(CTDataType) * (k + 1));
    ps->head = ps->tail = 0;
    ps->size = k;
}

4.2 向循环队列中插入一个元素

插入过程中如果没满,就很容易,插入一个,tail向后挪动就可以,但是会出现两种情况:

1.此时tail并不是在下标为k的位置,继续插入的话tail向后挪动就可以:
在这里插入图片描述

2.当tail == k时,在插入时tail就会向后挪动,此时tail == k+1就会越界:
在这里插入图片描述

此时我们修正一下tail的位置,让tail == 0,就又可以继续插入了:
在这里插入图片描述
代码实现:

//队列插入x,如果插入成功,就返回真
bool CQueuePush(CQueue* ps, int x)
{
    assert(ps);
    //判断队列是否满了,没满,就插入
    if (!CQueueFull(ps))
    {
        //值赋给tail位置
        ps->data[ps->tail] = x;
        //tail指向下一个位置
        ps->tail++;
        //如果此时的tail == k+1
        if (ps->tail == ps->size + 1)
        {
            //修正一下tail的位置
            ps->tail = 0;
        }
        //插入成功
        return true;
    }
    //如果队列满了,就插入失败
    else
    {
        return false;
    }
}

当然,当tail == k时我们修正也是可以的:

    //判断队列是否满了,没满,就插入
    if (!CQueueFull(ps))
    {
        //值赋给tail位置
        ps->data[ps->tail] = x;
        //如果此时的tail == k
        if (ps->tail == ps->size)
        {
            //修正一下tail的位置
            ps->tail = 0;
        }
        else//如果tail != k,tail就直接++
        {
            ps->tail++;
        }
        //插入成功
        return true;
    }
    //若果队列满了,就插入失败
    else
    {
        return false;
    }

4.3 从循环队列中删除一个元素

删除元素也会出现两种情况:

1.head并没有在下标为k的位置,head只需要向后挪动就可以了:
在这里插入图片描述
2.head此时位置在下标为k的位置,head往后挪动一位,就越界了:
在这里插入图片描述
我们修正一下head的位置,令head == 0,就可以了:
在这里插入图片描述
代码实现:

//队列删除一个元素,如果删除成功,就返回真
bool CQueuePush(CQueue* ps, int x)
{
    assert(ps);
    //判断是否为空,不为空进行删除
    if (!CQueueEmpty(ps))
    {
        //head向后挪动位置
        ps->head++;
        //如果此时head == k+1
        if (ps->head == ps->size + 1)
        {
            //修正一下head的值
            ps->head = 0;
        }
        return true;
    }
    //为空,就返回假
    else
    {
        return false;
    }
}

同样也可以写成:

    //判断是否为空,不为空进行删除
    if (!CQueueEmpty(ps))
    {
        //如果此时head == k
        if (ps->head == ps->size + 1)
        {
            //修正一下head的值
            ps->head = 0;
        }
        //此时head != k,就直接++
        else
        {
            ps->head++;
        }
        return true;
    }
    //为空,就返回假
    else
    {
        return false;
    }

4.4 获取队顶元素

获取队顶元素就简单了,还接返回head位置的值就可以了:

代码实现:

//获取队顶元素
bool CQueueFront(CQueue* ps)
{
    assert(ps);
    //如果不为空,就返回head位置的值
    if (!CQueueEmpty(ps))
    {
        return ps->data[ps->head];
    }
    //为空,直接返回-1;
    else
    {
        return -1;
    }
}

4.5 获取队尾元素

获取队尾元素就直接返回tail - 1位置的值,此时就会出现两种情况了:

1.tail并不处于下标为0的位置,此时直接返回tail - 1位置的值:
在这里插入图片描述
2.tail处于下标为0的位置,此时tail - 1位置是下标为k的位置,返回的是下标为k的位置的值:
在这里插入图片描述

//获取队尾元素
bool CQueueRear(CQueue* ps)
{
    assert(ps);
    //如果不为空,就返回tail-1位置的值
    if (!CQueueEmpty(ps))
    {
        //tail == 0,就返回k位置的值;
        if (ps->tail == 0)
        {
            return ps->data[ps->size];
        }
        //tail != 0,返回tail-1位置的值
        else
        {
            return ps->data[ps->tail - 1];
        }
    }
    //为空,直接返回-1;
    else
    {
        return -1;
    }
}

还可以写成:

    //如果不为空,就返回tail-1位置的值
    if (!CQueueEmpty(ps))
    {
        int tailprev = ps->tail - 1;
        if (tailprev == -1)
        {
            tailprev = ps->size;
        }
        return ps->data[tailprev];
    }
    //为空,直接返回-1;
    else
    {
        return -1;
    }

4.6 检查循环队列是否已满

循环队列满的条件是tail + 1 == head ,我们只需要将tail+1 == k+1这个条件考虑进去,然后直接返回就可以了:

//检查循环队列是否已满
bool CQueueFull(CQueue* ps)
{
    assert(ps);
    int tailnext = ps->tail + 1;
    if (tailnext == ps->size + 1)
    {
        tailnext = 0;
    }
    return tailnext == ps->head;
}

4.7 检查循环队列是否为空

队列为空的条件就是head == tail,直接返回就行了:

//检查循环队列是否已满
bool CQueueEmpty(CQueue* ps)
{
    assert(ps);
    return ps->head == ps->tail;
}

4.8 销毁队列

void CQueueDestory(CQueue* ps)
{
    assert(ps);
    free(ps->data);
    ps->head = ps->tail = 0;
    ps->size = 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值