【每日算法】栈&队列

栈和队列

栈:后进先出(last-in, first-out, LIFO)

队列:先进先出(first-in, first-out, FIFO)

栈的实现

栈的实现有多种方法,比如静态数组、动态数组以及链表。

下面我们先简单介绍一下静态数组的实现方法:

我们可以用一个数组s[0…n-1]来实现一个至多有n个元素的栈,使用top来表示栈顶元素的下标,当top = -1时栈为空,当top = n-1时栈满。

int s[n];
int top = -1;

bool empty()
{
    if (-1 == top)
        return true;
    return false;
}

void push(int x)
{
    if (n-1 == top)
    {
        cout << "Error : Stack is full" <<endl;
        return;
    }
    s[++top] = x;
}

void pop()
{
    if (empty())
    {
        cout << "Error : Stack is empty" <<endl;
        return;
    }
    --top;
}

int top()
{
    if (empty())
    {
        cout << "Error : Stack is empty" <<endl;
        return -1;
    }
    return s[top];
}

静态数组的特点是实现简单,而且占用内存小,不足之处是大小固定,不能动态扩充。

由于栈只有顶部元素才可以被访问,因此使用单链表来实现栈也是一个不错的选择,其优点是没有长度限制,缺点是实现稍微麻烦,易出错,而且存放指针需要额外的空间。

使用动态数组实现的stack可同时具备以上两者的优点,其大体实现与静态数组相同,不过数组的空间是动态分配的,因此,如果空间不够用,我们可以通过重新分配更大的内存空间来解决,为提高效率,我们并不是每次push一个就分配一个,而是设置一个capacity,扩充的时候可变为capacity*2的大小。

另外,为支持多种数据类型,可使用C++的模板机制来实现。

队列的实现

队列实现起来相对比栈复杂一点,因为栈的一端是固定的,元素的出和入都在另一端,而队列一边入一边出,导致首尾两端都是移动的。

鉴于这种移动性,我们可以将数组定义为环形数组,将首和尾连接起来。

我们使用一个数组q[0…n-1]来实现一个至多含n-1个元素的队列。队列具有属性head和tail,head指向队列的头,tail指向新元素将会被插入的位置。

于是队列的元素为q[head], q[head+1],…,q[tail-1],注意,在n-1和0的地方要进行卷绕。当head=tail时,队列为空,当head=tail+1时,队列是满的。

注意到,我们有一个位置是留空的,因为如果不留空,那么在队列满的时候,会有head=tail,这个条件跟队列是空的一样,使得我们无法分辨队列是空还是满,所以需要牺牲一点空间来换取编程的便利性。

下面只提供入队和出队的代码:

void push(int x)
{
    if (head == tail+1)
    {
        cout << "Error : Stack is full" <<endl;
        return;
    }
    q[tail++] = x;
    if (tail == n)
        tail = 0;
}

void pop()
{
    if (head == tail)
    {
        cout << "Error : Stack is empty" <<endl;
        return;
    }
    ++head;
    if (head == n)
        head = 0;
}

int front()
{
    if (head == tail)
    {
        cout << "Error : Stack is empty" <<endl;
        return;
    }
    return q[head];
}

栈和队列的相互转换

参见:

使用两个栈实现一个队列

使用两个队列实现一个栈

这里附上两个栈实现一个队列的代码:

template<typename T>
void CQueue<T>::appendTail(const T & element)
{
    stack1.push(element);
}

template<typename T>
T CQueue<T>::deleteHead()
{
    if (stack2.size() <= 0)
    {
        while (stack1.size() > 0)
        {
            T & data = stack1.top();
            stack1.pop();
            stack2.push(data);
        }
    }
    if (stack2.size() == 0)
        throw new exception("queue is empty");
    T head = stack2.top();
    stack2.pop();
    return head;
}

相关问题

栈的压入、弹出序列

输入两个整数序列,第一个表示栈的压入顺序,请判断第二个序列是否为该栈的弹出序列,假设压入栈的所以数字均不相等。

例如:1,2,3,4,5为压栈序列,则4,5,3,2,1为一个可能的弹出序列,而4,3,5,1,2则不是。

思路如下:

  1. 用cur1来表示第一个序列的当前元素,用cur2来表示第二个序列的当前元素,初始cur1=cur2=0;
  2. 当第一个序列都入栈,进入步骤4,否则,将cur1入栈,cur1++;
  3. 如果cur2等于栈顶元素,栈顶元素出栈,cur2++,如果栈非空,继续步骤3;否则,回到步骤2。
  4. 如果栈为空,返回true,否则返回false;

也就是说,每次第二个序列的当前元素跟栈顶元素比较,相等,则将栈顶弹出,第二个序列当前元素右移,表示匹配成功,否则继续将第一个序列入栈,直到找到匹配的。

下面实现:

bool isPopOrder(const int *pPush, const int *pPop, int length)
{
    if (NULL != pPush && NULL != pPop && length > 0)
    {
        stack<int> s;
        const int *pPushNext = pPush;
        const int *pPopNext = pPop;
        while (pPushNext - pPush < length)
        {
            s.push(*pPushNext);
            ++pPushNext;
            while (!s.empty() && pPopNext - pPop < length && (*pPopNext) == s.top())
            {
                s.pop();
                ++pPopNext;
            }
        }
        if (s.empty() && pPopNext - pPop == length)
            return true;
    }
    return false;
}

包含min函数的栈

定义栈的数据结构,在该类型中实现一个能够得到栈最小元素的min函数,且min、push、pop时间复杂度都是O(1)。

一开始的思路是:多一个成员变量来存放最小值。但是问题是,如果该最小值被弹出了,如何找到次小值?

所以一个成员变量是不够的,考虑栈特殊的数据结构,假设栈里有5个元素,那么最小值是5个元素中的最小值,当弹出栈顶元素之后,那么新的最小值应该是4个元素中的最小值……依此类推,于是我们可以每压进一个元素,就多记录一个值,该值表示从栈底到当前元素的最小值。当弹出一个元素时,也同时将该位的最小值元素去掉。

由于最小值的行为跟栈元素的行为类似,所以我们可以使用两个栈来实现这个数据结构:一个存放元素,一个存放从栈底到该元素的最小值。

比如对于序列3,4,2,1,5

辅助栈为:3,3,2,1,1

弹出5之后:

3,4,2,1
3,3,2,1

弹出1之后:
3,4,2
3,3,2

滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里的最大值。

首先容易察觉到,其实滑动窗口类似一个队列,当窗口右移时,相当于队头元素出队,队尾添加一个新的元素。

在前面我讲了可以找到min的栈,又讲了两个栈实现队列,两者综合起来可以找到队列的min,同理也可以找max。

不过上面的实现会复杂一点,因为要综合两个题目的做法了。

另一种思路是,在队列(deque)中仅存放有可能成为最大值的数。比如滑动窗口为3,此时已经有3,当4进来后,3不可能为最大了,删掉,之后加入有2进来,由于4滑走之后,2可能成为最大,所以保留,再进来6,则4和2不可能是最大,删去……这是大体的思路,具体细节以及实现留待大家自行思考~

以上题目参考自:《剑指offer》


每天进步一点点,Come on!

(●’◡’●)

本人水平有限,如文章内容有错漏之处,敬请各位读者指出,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值