[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列

人见人爱目录君

  • 有效的括号
    • 分析时间
    • 代码
  • 用栈实现队列
  • 用队列实现栈
  • 设计循环队列
    • 喜闻乐见的分析环节
    • 代码实现
      • 数组版本
      • 链表版本
  • 一些栈和队列的选择题
  • 最后

[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第1张图片

菜鸡大学生的数据结构——刷题篇5

磕磕绊绊终于到了栈和队列,菜鸡大学生在水完一篇文章之后心情极佳鸡血上头,决定对栈和队列的题目进行一波制裁。

结果痛心地发现栈和队列这些数据结构c语言是没有现成的库的,它们的代码还得自己手敲一遍,遂,cv。

有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第2张图片

分析时间

经常看我博客的好兄弟们肯定知道,菜鸡大学生的做题习惯是先给出大概思路之后再写题。

这题该这么盘呢?

我们康康示例,发现右括号一定要与最近的左括号匹配起来。
那么我们可以把左括号存到栈里面,遇到右括号就进行匹配,成功就出栈,失败就返回false。

好——

特殊情况时间:

  1. 一开始就是右括号栈为空导致出错。
  2. 左右括号数量不对等。

解决方案:

  1. 遇到右括号先判断栈是不是空。
  2. 最后判断栈是不是空。

代码

栈的代码可以去之前博客自取——

bool isValid(char * s){
    ST st;
    StackInit(&st);
    char* cur=s;
    while(*cur)
    {
        if((*cur=='(')||(*cur=='[')||(*cur=='{'))
        {
            StackPush(&st,*cur);
            ++cur;
        }
        else
        {
            if(StackEmpty(&st))
                return false;
            char top=StackTop(&st);
            if((*cur==')'&&top=='(')||(*cur==']'&&top=='[')||(*cur=='}'&&top=='{'))
            {
                StackPop(&st);
                ++cur;
            }
            else
            {
                StackDestory(&st);
                return false;
            }
        }
    }
    if(StackEmpty(&st))
    {
        StackDestory(&st);
        return true;
    }
    else
    {
        StackDestory(&st);
        return false;
    }
}

用栈实现队列

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

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

在写这道题的时候我们先把这个队列准备好:

typedef struct {
   ST pushST;
   ST popST;
} MyQueue;

然后是初始化,只要把这俩栈分别初始化就行。

MyQueue* myQueueCreate() {
    MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));

    assert(obj);
	
    StackInit(&obj->pushST);
    StackInit(&obj->popST);

    return obj;
}

接着是push和pop。
这俩玩意怎么写呢?

push的话我们只要把数据放到pushST里面,pop的时候如果popST为空,就把pushST里面的数据全部放到popST里面。
此时popST里面的数据正好是颠倒的,只要向后依次出数据就好了。

不为空就直接出数据。
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第3张图片

void myQueuePush(MyQueue* obj, int x) {
    assert(obj);

    StackPush(&obj->pushST,x);
}

int myQueuePop(MyQueue* obj) {
    assert(obj);

    if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST,StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    int front=StackTop(&obj->popST);
    StackPop(&obj->popST);

    return front;
}

peek就更简单了。
就是pop的简化版本
复制粘贴大法好。

int myQueuePeek(MyQueue* obj) {
    assert(obj);

    if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST,StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    int front=StackTop(&obj->popST);

    return front;
}

empty呢?
两个栈都是空就表示队列是空。

bool myQueueEmpty(MyQueue* obj) {
    assert(obj);

    return StackEmpty(&obj->popST)&&StackEmpty(&obj->pushST);
}

最后的free函数。
先free两个栈,最后free掉队列。

void myQueueFree(MyQueue* obj) {
    assert(obj);

    StackDestory(&obj->popST);
    StackDestory(&obj->pushST);

    free(obj);
}

解决!


用队列实现栈

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

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

是差不多的题目呢。
那么,先建栈吧。

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

MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));

    assert(obj);
    QueueInit(&obj->q1);
    QueueInit(&obj->q2);

    return obj;
}

push到空队列里面去,原因在pop里面讲。

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

开始pop:
我们要先把非空的队列里面的内容放到空队列里面去,直到剩下最后一个。
然后直接把最后一个push出去就好。
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第4张图片
看图更好理解一些:>

int myStackPop(MyStack* obj) {
    assert(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 ret=QueueFront(nonemptyq);
    QueuePop(nonemptyq);
    return ret;
}

栈顶直接返回非空队列的队尾就好。

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

剩下的操作和上一题是一样的。

bool myStackEmpty(MyStack* obj) {
    assert(obj);

    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    assert(obj);

    QueueDestory(&obj->q1);
    QueueDestory(&obj->q2);

    free(obj);
}

好耶!


设计循环队列

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

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

你的实现应该支持如下操作:

  • MyCircularQueue(k): 构造器,设置队列长度为 k 。
  • Front: 从队首获取元素。如果队列为空,返回 -1 。
  • Rear: 获取队尾元素。如果队列为空,返回 -1 。
  • enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  • deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  • isEmpty(): 检查循环队列是否为空。
  • isFull(): 检查循环队列是否已满。

喜闻乐见的分析环节

为了能使tail==front能够区分是空队列还是满队列,我们一般会开辟k+1个空间。

此时

  • 空队列就是tail==front,满队列就是tail+1=front。
  • 添加tail就往前走一步,删除front就往前走一步。

[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第5张图片
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第6张图片
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第7张图片
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第8张图片

画图真的能看的很清楚,我感觉都不需要讲什么了hh。

代码实现

数组版本

有了之前的思路我们就可以写代码了。
我们一个函数一个函数写。

循环队列本体以及初始化,
很多都是之前写过的~

typedef struct {
    int* arr;
    int front;
    int tail;
    int k;
} MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k) {
   MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));

   obj->arr=(int*)malloc(sizeof(int)*(k+1));
   obj->k=k;
   obj->front=obj->tail=0;

   return obj;
}

插入和修改:
和图片描述的差不多,但是要注意数组这玩意不是循环的,
当tail为k时,增加数据要手动把tail改成0,front同理。
也不要忘了判断一下队列是不是空/满。

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;

    obj->arr[obj->tail]=value;
    if(obj->tail==obj->k)
    {
        obj->tail=0;
    }
    else
    {
        obj->tail++;
    }
    return true;
}

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

    if(obj->front==obj->k)
    {
        obj->front=0;
    }
    else
    {
        obj->front++;
    }
    return true;
}

队头队尾元素,插入删除的青春版。
对头元素直接返回
队尾元素返回tail的前一个
如果tail在第一个位置说明队尾元素在最后一个。

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

    return obj->arr[obj->front];
}

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

    if(obj->tail==0)
    {
        return obj->arr[obj->k];
    } 
    else
    {
        return obj->arr[obj->tail-1];
    }
}

空和满分析有讲。
不谈。

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

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if(obj->tail==obj->k&&obj->front==0)
    {
        return true;
    }
    else
    {
        return obj->front==obj->tail+1;
    }
}

销毁。

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

链表版本

大体思路和数组差不多,但是比数组复杂一些。

本体以及初始化:
复杂了许多。
由于链表之间是不连续的,所以我们要存下链表的头和尾位置。

typedef struct SListNode{
    int data;
	struct SListNode* next;
	struct SListNode* prev;
}SListNode;

typedef struct {
    SListNode * SList;
    SListNode * front;
    SListNode * tail;
    SListNode * SLfront;
    SListNode * SLtail;
} MyCircularQueue;

初始化我们要建好一整个完整的双链表,该连的都要连上。
一开始SLfront,front和tail都在第一个节点。
SLtail在最后一个节点。
为了方便里面的值随便赋个0吧。

//建立新节点
SListNode* BuySListNode(int x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* newnode=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    SListNode* head=BuySListNode(0);
    
    newnode->SList=head;
    newnode->front=head;
    newnode->tail=head;
    newnode->SLfront=head;

    SListNode* cur=head;

    for(int i=0;i<k;i++)
    {
        SListNode* node=BuySListNode(0);
        cur->next=node;
        node->prev=cur;
        cur=node;
    }

    cur->next=head;
    head->prev=cur;
    
    newnode->SLtail=cur;

    return newnode;
}

插入删除队头队尾空满函数和数组版本是一个道理,不细讲。

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
 if(myCircularQueueIsFull(obj))
        return false;

    obj->tail->data=value;
    if(obj->tail->next==obj->SLfront)
    {
        obj->tail=obj->SLfront;
    }
    else
    {
        obj->tail=obj->tail->next;
    }
    return true;
}

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

    if(obj->front->next==obj->SLfront)
    {
        obj->front=obj->SLfront;;
    }
    else
    {
        obj->front=obj->front->next;
    }
    return true;
}

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

    return obj->front->data;
}

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

    if(obj->tail==obj->SLfront)
    {
        return obj->SLtail->data;
    } 
    else
    {
        return obj->tail->prev->data;
    }
}

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

bool myCircularQueueIsFull(MyCircularQueue* obj) {
  if(obj->tail==obj->SLtail&&obj->front==obj->SLfront)
    {
        return true;
    }
    else
    {
        return obj->front==obj->tail->next;
    }
}

销毁函数,由于是链表还得专门写个链表销毁函数…
啊——为什么不用数组呢,好麻烦。

void ListDestory(SListNode* phead)
{
	assert(phead);

	SListNode* cur = phead->next;
	SListNode* next = cur->next;
	while (next!=phead)
	{

		free(cur);
		cur = next;
		next = next->next;
	}
	free(cur);
	free(phead);
    phead=NULL;
}

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

一些栈和队列的选择题

  1. 一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
    A. 12345ABCDE
    B. EDCBA54321
    C. ABCDE12345
    D. 54321EDCBA

栈的特点是后进先出,怎么入的就倒着出,选B。

  1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
    A. 1,4,3,2
    B. 2,3,4,1
    C. 3,1,4,2
    D. 3,4,2,1

A选项,1入栈,1出栈,2,3,4入栈,4,3,2出栈,可以。
B选项,1,2入栈,2出栈,3入栈,3出栈,4入栈,4出栈,1出栈,可以。
C选项,1,2,3入栈,3出栈,由于2还没有出栈,1不可能出栈,错误。
D选项,1,2,3入栈,3出栈,4入栈,4出栈,2,1出栈,可以。

  1. 循环队列的存储空间为 Q(1:100) ,初始状态为 front=rear=100 。经过一系列正常的入队与退队操作
    后, front=rear=99 ,则循环队列中的元素个数为( )
    A. 1
    B. 2
    C. 99
    D. 0或者100

front=rear,有可能是满队列也可能是空队列,选D。

  1. 以下( )不是队列的基本运算?
    A. 从队尾插入一个新元素
    B. 从队列中删除第i个元素
    C. 判断一个队列是否为空
    D. 读取队头元素的值

这道题不会的建议看一下队列的概念,选B。

  1. 现有一循环队列,其队头指针为front,队尾指针为rear;循环队列长度为N。其队内有效长度为?(假设队头不存放数据)
    A. (rear - front + N) % N + 1
    B. (rear - front + N) % N
    C. (rear - front) % (N + 1)
    D. (rear - front + N) % (N - 1)

遇到这种题目怎么办?举个栗子吧!
[数据结构]有些事不见不知道——栈和队列刷题 :有效的括号、用栈实现队列、用队列实现栈、循环队列_第9张图片
我们可能遇见这两种情况: rear在前或者front在前。
先带几个数字试试看,再来推一下公式:(rear - front + N) % N
选B。

最后


这周的画。
画图工作量明显少了,挺高兴的。

队列终于结束辣,下面就是二叉树惹!
其实不是,先出十大排序。

你可能感兴趣的:(每天刷题,快乐到家。,C语言入土之路,c语言,数据结构,栈和队列)