返回 登录
0

数据结构与算法(三)两个数组实现MIN栈问题

附上博客链接,欢迎大家来交流和学习。
上一节介绍了合并两个有序链表的操作,带大家简单的熟悉了链表这一数据结构以及通过递归来实现链表的合并,这一节为大家带来一道求栈中最小元素的问题,先看题目要求:
题目:定义栈的数据结构,请在该栈中实现一个能够得到栈中最小元素的MIN函数。在该栈中,调用MIN、PUSH、POP的时间复杂度均是O(1)。
根据题目可以看出,本次我们涉及的数据结构是栈(Stack),先看一下栈的介绍:栈又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除,使其相邻的元素成为新的栈顶元素。
上面的描述很详细,简单点说就是一句话,栈是一种先入后出的数据结构,基于这个特点,每次先压入栈的元素,总是最后才被弹出的,而每次弹出的元素,也肯定是最后被压入的元素,基于这种特点,对栈做排序是比较麻烦的,同时也注意到,题目中要求的时间复杂度是O(1),也就是说不能利用遍历去得到这个最小值,哪有人会说了,我们可以记录一个全局变量,初始值为压入栈中的第一个元素,然后每次压栈都比较该变量中的元素和压栈元素的大小,保证该全局变量中存的就是最小数值,这样的思路倒是满足时间复杂度为O(1)的要求,但如果做了弹栈操作呢?如果把栈中最小的元素弹出怎么办?这个时候你这个全局变量就废了,因此这种办法也是行不通滴。
那该怎么办呢?事实上在压栈操作时就把最小元素存起来的思路是没有问题的,但是却只考虑了 一个最小元素,没有考虑第二小元素,第三小元素等等,而为了保证在最小元素弹出的情况下,可以立马得到第二小元素,我们要借助一个辅助的存储空间来完成这件事。
假如我们存储元素的栈是DATA栈,用来存储最小元素的辅助结构是TEMP栈(选用栈是为了和DATA栈保持一致,也方便理解),这样我们就有两个栈,大家也应该注意到我们的标题就是“两个数组来实现MIN栈”。现在数组就可以排上用场了。
数组应该是大家接触最多,使用最广的数据结构了,其定义简单,理解起来容易,直接通过下标访问数据,可以达到O(1)的时间复杂度,但同时数组也存在诸多缺陷,比如说大小一旦确定,就无法改变,使用过程中如果稍有不慎,就有可能越界带来不可预知的错误。但在我们这道题中,首先就是要做到利用一个数组来实现两个栈,有以下两种思路:
1、定义两个下标index1和index2,初始值index1 = 0, index2 = length - 1 ,其中length为数组长度,每次向栈1压入元素时,index1增1,每次向栈2压入元素时,index2减1,弹栈时相反,栈的最大深度为length的一半,这样相当于索引值都是指向了栈顶,满足要求;
2、同样还是定义两个索引值index1和index2,初始值index1 = 0, index2 = 1, 每次压栈时,相应的索引值都增2,弹栈时减2,相当于把一个数组一分为二,偶数位作为一个栈,奇数位作为一个栈,两个栈互不干扰,独立使用,也可以满足要求。
比较上述两种方法可以看到思路1更为灵活,且几乎不会浪费数组空间,假设两个栈的深度为M和N,若用思路2,则无论M和N的关系如何,都必然会浪费|M-N|个空间,而思路1则不会有这种问题,不过在我们这道题中,DATA栈和TEMP栈大小一样,实际上用那种思路都可以,为了源码更为简洁,我们选用思路2来实现,有兴趣的同学可以尝试用思路1的方法来实现,代码难度都差不多,具体代码如下:

/* 变量初始化 */
#define STACKLENGTH 50   //栈深
#define DATASTACKID 0    //数据栈
#define TEMPSTACKID 1    //临时存储栈

ULONG g_ulArray[STACKLENGTH * 2] = {0}; //长度为栈深两倍的数组
ULONG g_ulDataStackIndex = 0;           //数据栈栈顶索引
ULONG g_ulTempStackIndex = 1;           //临时存储栈栈顶索引

/* 向栈内压入一个元素 */
void PushNodetoArray(int StackId, ULONG ulValue)
{
    if ((DATASTACKID == StackId) && (0 != ulValue))
    {
       g_ulArray[g_ulDataStackIndex] = ulValue;
       g_ulDataStackIndex += 2;
    }
    else if ((TEMPSTACKID == StackId) && (0 != ulValue))
    {
       g_ulArray[g_ulTempStackIndex] = ulValue;
       g_ulTempStackIndex += 2;
    }
    else
    {
        printf("In PushNodetoArray, StackId or ulValue is put error!\r\n");
    }
}

/* 判断该栈是否为空 */
bool JudgeStackisEmpty(int StackId)
{
    if (DATASTACKID == StackId)
    {
        return (g_ulDataStackIndex == 0 ? true : false) ;
    }
    else if (TEMPSTACKID == StackId)
    {
        return (g_ulTempStackIndex == 1 ? true : false) ;
    }
    else
    {
        return false;
    }
}

/*从栈中弹出一个元素*/
void PopNodeinArray(int StackId)
{
    if (DATASTACKID == StackId)
    {
       if (JudgeStackisEmpty(StackId))
       {
           return ;
       }
       else
       {
           g_ulArray[g_ulDataStackIndex] = 0;
           g_ulDataStackIndex -= 2;
       }
    }
    else if (TEMPSTACKID == StackId)
    {
       if (JudgeStackisEmpty(StackId))
       {
           return ;
       }
       else
       {
           g_ulArray[g_ulTempStackIndex] = 0;
           g_ulTempStackIndex -= 2;
       }
    }
    else
    {
       printf("In PushNodetoArray, StackId or ulValue is put error!\r\n");
    }
}

代码分为以上几部分,都还比较容易,无论是POP/PUSH其时间复杂度都是O(1),空间复杂度也是常量,也就是栈的深度,在实现用一个数组当做两个栈以后,我们就根据之前说的,利用两个栈来实现MIN栈,大致思路如下:
对于数据栈来说,压栈和弹栈没有什么特别的,就是普通的压入和弹出,而对于辅助栈来说,当压入第一个元素时,则必然是最小额元素,当有第二个元素压入时,为了保证栈顶元素为最小元素,那么还需要比较压入元素与当前辅助栈中的栈顶uansu大小即可,如果小于栈顶元素,则压入辅助栈中,否则我们不压入该元素,但是要再压入一次栈顶元素,这样做的好出就是弹栈时,可以直接弹出数据栈和辅助栈的元素,而不需要再去判断是否需要弹出该元素,具体代码如下:

void MinPush(ULONG ulValue)
{
    ULONG ulTempMinValue = 0;
    PushNodetoArray(0, ulValue);
    if (g_ulTempStackIndex < 2)
    {
        ulTempMinValue = ulValue;
    }
    else
    {
        ulTempMinValue = g_ulArray[g_ulTempStackIndex - 2];
        if (ulValue > ulTempMinValue)
        {
            //do some thing
        }
        else
        {
            ulTempMinValue = ulValue;
        }
    }
    PushNodetoArray(1, ulTempMinValue);
    return ;
}

void MinPop()
{
    /* 分别弹出数据栈和辅助栈中的元素 */
    printf("弹栈操作!\r\n");
    PopNodeinArray(0);
    PopNodeinArray(1);
}

ULONG GetMinNumber()
{
    ULONG ulResult;
    /* 小于2说明栈中没有元素 */
    if (g_ulTempStackIndex < 2)
    {
        ulResult = 0;
    }
    else
    {
        ulResult = g_ulArray[g_ulTempStackIndex - 2];
    }
    return ulResult;
}

以上三个函数,分别实现了push、pop、getmin的功能,写完代码以后,我们可以简单调试一下,具体结果如下:

图片描述

我们再来分析一下代码的时间复杂度,就是O(1),满足题目要求,同时空间复杂度上,也只是借助了1个与数据栈大小相同的辅助栈而已,因此执行效率尚可,总结这道题实际上也不难,涉及到的数据结构是栈和数组,没用到什么复杂的算法,完全是利用栈的特征来实现的,不过看代码和自己上手写代码是两回事,我希望大家还是能够自己动手去写一下代码,这样可以加深理解,更容易掌握这些基本知识、
这一节就到此为止,下一节开始介绍图论中的最短路径问题,敬请期待!

评论