C语言【数据结构】栈和队列【OJ题(C++)、选择题】

目录

一.OJ题

1.225. 用队列实现栈

2.232. 用栈实现队列

3.622. 设计循环队列

4.20. 有效的括号

二.选择题

1.下列关于栈的叙述正确的是(B)

2.一个栈的入栈序列为ABCDE,则不可能的出栈序列为(D)

3.链栈与顺序栈相比,比较明显的优点是(C)

4.下列关于用栈实现队列的说法中错误的是(B)

5.用无头单链表存储队列,其头指针指向队头结点,尾指针指向队尾结点,则在进行出队操作时(C)

6.以下不是队列的基本运算的是(B)

7.下面关于栈和队列的说法中错误的是(AB)

8.下列关于顺序结构实现循环队列的说法,正确的是(C)

9.现有一循环队列,其队头指针为front,队尾指针为rear,循环队列长度为N,最多存储N-1个数据。其队内有效长度为(B)


前言:

栈和队列->​​​​​​C语言【数据结构】栈与队列实现_糖果雨滴a的博客-CSDN博客_c语言实现栈和队列

一.OJ题

1.225. 用队列实现栈

(1)描述

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

实现 MyStack 类:

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

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

(2)思路

        用队列去实现栈,可以通过两个队列互相存放元素进行实现。当两个队列都为空时,要插入元素时,随便插入在任何一个队列即可,之后插入元素时,要插入在有元素的那个队列。

        要删除元素时,因为要模拟实现栈,栈是后进先出,因此要删除的元素是刚刚插入的元素。因此要把不为空的那个队列中的元素放入空队列中,只留下非空的队列中的最后一个元素(队列的最后一个元素,就是刚刚插入的元素),然后把这个元素删掉即可。

        要想返回栈顶元素,就直接返回非空队列的最后一个元素即可,判断栈是否为空就判断两个队列是否都为空。

代码:

class MyStack {
public:
    queue<int> _q1;
    queue<int> _q2;

    MyStack() {
        
    }
    
    void push(int x) {
        // q1和q2一定有一个不为空
        // q1不为空
        if(!_q1.empty())
        {
            _q1.push(x);
        }
        // q2不为空
        else
        {
            _q2.push(x);
        }
    }
    
    int pop() {
        queue<int>* emptyQ = &_q1;
        queue<int>* nonemptyQ = &_q2;
        // 与假设不符,交换
        if(!_q1.empty())
        {
            swap(emptyQ, nonemptyQ);
        }

        // 把非空队列的元素转移到空队列中,只留下队尾的元素
        while(nonemptyQ->size() > 1)
        {
            emptyQ->push(nonemptyQ->front());
            nonemptyQ->pop();
        }

        // 非空队列最后的队尾就相对于栈顶元素
        int top = nonemptyQ->front();
        nonemptyQ->pop();
        return top;
    }
    
    int top() {
        if(!_q1.empty())
        {
            return _q1.back();
        }
        else
        {
            return _q2.back();
        }
    }
    
    bool empty() {
        return _q1.empty() && _q2.empty();
    }
};

2.232. 用栈实现队列

(1)描述

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

实现 MyQueue 类:

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

说明:

你只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

(2)思路

        用栈去实现队列,可以通过一个栈专门去用来插入元素,另外一个栈在删除和返回队头时使用。

        在s1中插入数据,当要删除数据或者返回队头数据时,如果s2为空,就先把s1中的数据放入s2中,这时候因为是栈,所以当s1的元素放入s2时,顺序会正好与之前相反,这时候就正好相当于队列,因此top就直接相当于队头,删除即可。取队头元素也是如此。

class MyQueue {
public:
    stack<int> _s1;
    stack<int> _s2;

    MyQueue() {

    }
    
    void push(int x) {
        _s1.push(x);
    }
    
    int pop() {
        if(_s2.empty())
        {
            while(!_s1.empty())
            {
                _s2.push(_s1.top());
                _s1.pop();
            }
        }

        int top = _s2.top();
        _s2.pop();
        return top;
    }
    
    int peek() {
        if(_s2.empty())
        {
            while(!_s1.empty())
            {
                _s2.push(_s1.top());
                _s1.pop();
            }
        }

        return _s2.top();
    }
    
    bool empty() {
        return _s1.empty() && _s2.empty();
    }
};

3.622. 设计循环队列

(1)描述

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

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

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

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

 

示例:

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1);  // 返回 true
circularQueue.enQueue(2);  // 返回 true
circularQueue.enQueue(3);  // 返回 true
circularQueue.enQueue(4);  // 返回 false,队列已满
circularQueue.Rear();  // 返回 3
circularQueue.isFull();  // 返回 true
circularQueue.deQueue();  // 返回 true
circularQueue.enQueue(4);  // 返回 true
circularQueue.Rear();  // 返回 4

(2)思路

        这里我们用数组来实现这个循环队列,这里也可以用单链表,但是数组要更好一些。

        如果我们需要一个k个大小的数组,我们再设两个变量,一个指向第一个元素,一个指向最后一个元素,即头和尾,这时当头和尾相等时,我们不知道是数组满了还是数组为空,因此我们可以再额外开一个空间,即开k+1个空间。

        这时当头和尾相等时,说明数组为空,当尾的下一个为头时,说明数组满了。

        在写的过程中,要注意一些特殊情况,比如尾在最大空间处,这时尾的下一个就是0了,要控制好这些情况。

class MyCircularQueue {
public:
    MyCircularQueue(int k) {
        // 多开一个
        _v.resize(k + 1);
        _head = _tail = 0;
        _k = k;
    }
    
    bool enQueue(int value) {
        // 如果满了就插入失败
        if(isFull())
        {
            return false;
        }
        
        // 没满就插入
        _v[_tail] = value;
        // 尾到最大空间时,要重新回到0的位置
        if(_tail == _k)
        {
            _tail = 0;
        }
        // 尾正常往后走
        else
        {
            ++_tail;
        }

        return true;
    }
    
    bool deQueue() {
        // 如果为空,就删除失败
        if(isEmpty())
        {
            return false;
        }

        // 要删除的头到最大空间时,重新回到0的位置
        if(_head == _k)
        {
            _head = 0;
        }
        else
        {
            ++_head;
        }
        
        return true;
    }
    
    int Front() {
        // 如果为空,就返回-1
        if(isEmpty())
        {
            return -1;
        }

        return _v[_head];
    }
    
    int Rear() {
        // 如果为空,就返回-1
        if(isEmpty())
        {
            return -1;
        }

        // 尾在0的位置时,要返回前一个,就是k位置的值
        if(_tail == 0)
        {
            return _v[_k];
        }
        // 正常情况,返回尾的前一个
        else
        {
            return _v[_tail - 1];
        }
    }
    
    bool isEmpty() {
        // 尾和头相同时为空
        return _tail == _head;
    }
    
    bool isFull() {
        // 要注意尾在最后,头在最开始
        if(_tail == _k && _head == 0)
        {
            return true;
        }
        // 正常情况,尾+1等于头就是满了
        else
        {
            return _tail + 1 == _head;
        }
    }
private:
    vector<int> _v;
    int _head;
    int _tail;
    int _k;
};

4.20. 有效的括号

(1)描述

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

有效字符串需满足:

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

示例1:

输入:s = "()"
输出:true

示例2:

输入:s = "()[]{}"
输出:true

示例3:

输入:s = "(]"
输出:false

示例4:

输入:s = "([)]"
输出:false

示例5:

输入:s = "{[]}"
输出:true

(2)思路

        利用栈后进先出的特性,如果是左括号就入栈,如果是右括号就与当前栈顶的括号(与右括号最近的括号)进行比较,如果相同,就删掉该栈顶括号,继续往后走,如果不相同,那么就不闭合。同时也要注意,如果在过程中,栈空了,却还有右括号,那么也说明不闭合。

        最后,如果字符串全部遍历过了之后,栈内还有元素,说明左括号比右括号多了,这依旧说明不闭合。

        如果字符串个数是奇数,那么肯定是不闭合的,只有偶数才可能对应闭合。

不用unordered_map:

class Solution {
public:
    bool isValid(string s) {
        // 字符串为奇数一定不闭合
        if(s.size() % 2 == 1)
        {
            return false;
        }

        stack<char> st;
        for (auto& e : s)
        {
            // 左括号,入栈
            if (e == '(' || e == '[' || e == '{')
            {
                st.push(e);
            }
            // 右括号
            else
            {
                // 栈空了,还有右括号,没闭合
                if(st.empty())
                {
                    return false;
                }

                // 栈顶元素与当前右括号比较,不对应,则不闭合
                int top = st.top();
                if (e == ')' && top != '('
                    || e == ']' && top != '['
                    || e == '}' && top != '{')
                {
                    return false;
                }
                // 对应,就去掉该栈顶元素继续
                else
                {
                    st.pop();
                }
            }
        }

        // 如果字符串结束,栈还有括号,说明没闭合
        if (st.empty())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

用unordered_map:

class Solution {
public:
    bool isValid(string s) {
        // 字符串为奇数一定不闭合
        if(s.size() % 2 == 1)
        {
            return false;
        }

        // 存入哈希表map中
         unordered_map<char, char> um = { { ')' , '('}, { ']', '['}, { '}', '{'} };

        stack<char> st;
        for (auto e : s)
        {
            // 如果是右括号
            if (um.count(e))
            {
                // 栈为空或者栈顶元素不是um中key对应的value(左括号),就不闭合
                if (st.empty() || st.top() != um[e])
                {
                    return false;
                }
                // 对应,就去掉该左括号
                st.pop();
            }
            // 左括号,压栈
            else
            {
                st.push(e);
            }
        }

        // 栈还有括号就没闭合
        return st.empty();
    }
};

二.选择题

1.下列关于栈的叙述正确的是(B)

A.栈是一种“先进先出”的数据结构

B.栈可以使用链表或顺序表来实现

C.栈只能在栈底插入数据

D.栈不能删除数据

 解释:

        栈是一种线性结构,任何线性表都可以用来实现栈,只是实现方式有所差异。栈后进先出,只能在栈顶进行插入和删除操作。

2.一个栈的入栈序列为ABCDE,则不可能的出栈序列为(D)

 

A.ABCDE

B.EDCBA

C.DCEBA

D.ECDBA

 

解释:

如果是E先出,说明所有的元素都已经全部入栈,此时C不可能先于D出栈

 

3.链栈与顺序栈相比,比较明显的优点是(C)

A.插入操作更加方便

B.删除操作更加方便

C.无需扩展空间大小

D.无需缩小空间大小

 

解释:

A:如果是链栈,一般需要进行头插操作,而顺序栈一般进行尾插操作,此时顺序栈的操作更方便

B:类似于A,链栈进行头删,顺序栈进行尾删,仍然是顺序栈更方便

C:如果是顺序栈,插入元素要考虑增容问题,但是链栈不需要考虑增容,链栈每插入一个元素就对应开辟一个元素的空间。

D:删除操作,链栈需要释放空间,会有空间的缩小

 

4.下列关于用栈实现队列的说法中错误的是(B)

A.用栈模拟实现队列可以使用两个栈,一个栈模拟入队列,一个栈模拟出队列

B.每次出队列时,都需要将一个栈中的全部元素导入到另一个栈中,然后出栈即可

C.入队列时,将元素直接往模拟入队列的栈中存放即可

D.入队列操作时间复杂度为O(1)

解释:

        一个栈模拟入队列,一个栈模拟出队列,出队列时直接弹出模拟出队列栈的栈顶元素,是当该栈为空时,将模拟入队列栈中所有元素导入即可,而不是每次都需要导入元素,故错误。

 

5.用无头单链表存储队列,其头指针指向队头结点,尾指针指向队尾结点,则在进行出队操作时(C)

A.仅修改队头指针

B.队头、队尾指针都要修改

C.队头、队尾指针都可能要修改

D.仅修改队尾指针

 

解释:

与5一样,出队操作,就是头删,一定会修改头指针,如果出队之后,队列为空,需要修改尾指针。

6.以下不是队列的基本运算的是(B)

A.从队尾插入一个新元素

B.从队列中删除队尾元素

C.判断一个队列是否为空

D.读取队头元素的值

 

解释:

队列只能从队头删除元素,只能头删和尾插

7.下面关于栈和队列的说法中错误的是(AB)

A.队列和栈通常都使用链表实现

B.队列和栈都只能从两端插入、删除数据

C.队列和栈都不支持随机访问和随机插入

D.队列是“先入先出”,栈是“先入后出”

 

解释:

        顺序表的结构相比链表简单,不影响效率的情况下会优先使用顺序表。所以栈一般用顺序表实现队列一般用链表实现栈只能在一端插入和删除数据

8.下列关于顺序结构实现循环队列的说法,正确的是(C)

 

A.循环队列的长度通常都不固定

B.直接用队头和队尾在同一个位置可以判断循环队列是否为满

C.直接用队头和队尾在同一个位置可以判断循环队列是否为空

D.循环队列是一种非线性数据结构

解释:

A:循环队列的长度是固定的,到最大空间处就会到起始点

B:队头的下一个是队尾时,说明循环队列满了

C:队头和队尾相同时,说明循环队列为空

D:循环队列是线性数据结构,可以用数组或链表实现

9.现有一循环队列,其队头指针为front,队尾指针为rear,循环队列长度为N,最多存储N-1个数据。其队内有效长度为(B)

A.(rear - front + N) % N + 1

B.(rear - front + N) % N

C.(rear - front) % (N + 1)

D.(rear - front + N) % (N - 1)

解释:

        有效长度一般是rear-front,但是循环队列中rear有可能小于front,所以需要+N,最大长度为N,所以有效长度不可能超过N,故需要%N

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰果滴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值