棧(二十二)

        今天我們來學習下棧。那麼什麼是棧呢?棧是一種特殊的線性表,它僅能在線性表的一段進行操作。棧頂(Top)是允許操作的一端,棧底(Bottom)是不允許操作的一端。棧的特性是後進先出(last in first out),另外一種說法就是先進後出。如下圖所示

圖片.png

        下來我們來看看棧的操作,有如下操作:

        1、創建棧(Stack())

        2、銷燬棧(~Stack())

        3、清空棧(clear())

        4、進棧(push())

        5、出棧(pop())

        6、獲取棧頂元素(top())

        7、獲取棧的大小(size())


        下來我們來看看棧的實現,處於如下位置。因爲它是一個抽象父類,因此其成員函數都作爲純虛函數。

圖片.png

        棧的順序實現如下圖所示

圖片.png

        下來來看看 StaticStack 的設計要點:首先必須是一個類模板,使用原生數組作爲棧的存儲空間,使用模板參數決定棧的最大容量。下來看看 StaticStack 是怎樣實現的,源碼如下


Stack.h 源碼

#ifndef STACK_H
#define STACK_H

#include "Object.h"

namespace DTLib
{

template < typename T >
class Stack : public Object
{
public:
    virtual void push(const T& e) = 0;
    virtual void pop() = 0;
    virtual T top() const = 0;
    virtual void clear() = 0;
    virtual int size() const = 0;
};

}

#endif // STACK_H


StaticStack.h 源碼

#ifndef STATICSTACK_H
#define STATICSTACK_H

#include "Stack.h"
#include "Exception.h"

namespace DTLib
{

template < typename T, int N >
class StaticStack : public Stack<T>
{
protected:
    T m_space[N];
    int m_top;
    int m_size;
public:
    StaticStack()
    {
        m_top = -1;
        m_size = 0;
    }

    int capacity() const
    {
        return N;
    }

    void push(const T& e)
    {
        if( m_size < N )
        {
            m_space[m_top + 1] = e;
            m_top++;
            m_size++;
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No space in current stack ...");
        }
    }

    void pop()
    {
        if( m_size > 0 )
        {
            m_top--;
            m_size--;
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current stack ...");
        }
    }

    T top() const
    {
        if( m_size > 0)
        {
            return m_space[m_top];
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current stack ...");
        }
    }

    void clear()
    {
        m_top = -1;
        m_size = 0;
    }

    int size() const
    {
        return m_size;
    }
};

}

#endif // STATICSTACK_H

        我們來測試下這個代碼,看看那有沒有什麼問題

#include <iostream>
#include "StaticStack.h"

using namespace std;
using namespace DTLib;

int main()
{
    StaticStack<int, 5> stack;

    try
    {
        stack.pop();
    }
    catch(const Exception& e)
    {
        cout << e.message() << endl;
        cout << e.location() << endl;
    }

    for(int i=0; i<5; i++)
    {
        stack.push(i);
    }

    while( stack.size() > 0 )
    {
        cout << stack.top() << endl;

        stack.pop();
    }

    return 0;
}

        編譯結果如下

圖片.png

        我們看到在剛開始棧中沒有數據元素的情況下,就直接 pop 肯定會出錯,所以 try...catch 捕獲到了異常信息,下面的輸出跟我們想的是一致的。所以這個代碼是沒錯的。那麼此時我們已經完成了 StaticStack 類的代碼,但是我們有沒有考慮下這種情況:那便是當存儲的元素爲類類型時,StaticStack 的對象在創建時,會多次調用元素類型的構造函數,便會極大的影響效率。下來我們來試試,測試代碼如下

#include <iostream>
#include "StaticStack.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    StaticStack<Test, 5> stack;
    
    cout << stack.size() << endl;

    return 0;
}

        我們創建了 5 個 StaticStack 元素的對象,它是原生數組泛指類型的。但是我們並沒有進行 push 操作,因此它的大小還是爲 0。我們來看看打印信息

圖片.png

        我們看到確實是觸發了構造和析構函數,這便極大的影響了效率。那麼我們此時是不是需要一種新的棧的存儲方式呢?我們可以開發出一種新的鏈式棧,它的實現方式如下圖所示

圖片.png

        那麼鏈式棧的設計要點如下:

        1、類模板,抽象父類 Stack 的直接子類;

        2、在內部組合使用 LinkList 類,實現棧的鏈式存儲;

        3、只在單鏈表成員對象的頭部進行操作。

        我們來看看鏈式棧的結構,如下

圖片.png

        鏈式棧的源碼如下


LinkStack.h 源碼

#ifndef LINKSTACK_H
#define LINKSTACK_H

#include "Stack.h"
#include "LinkList.h"
#include "Exception.h"

namespace DTLib
{

template < typename T >
class LinkStack : Stack<T>
{
protected:
    LinkList<T> m_list;
public:
    void push(const T& e)
    {
        m_list.insert(0, e);
    }

    void pop()
    {
        if( m_list.length() > 0 )
        {
            m_list.remove(0);
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current stack ...");
        }
    }

    T top() const
    {
        if( m_list.length() > 0 )
        {
            return m_list.get(0);
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current stack ...");
        }
    }

    void clear()
    {
        m_list.clear();
    }

    int size() const
    {
        return m_list.length();
    }
};

}

#endif // LINKSTACK_H

        我們再用 LinkStack 類重試剛纔的實驗,看看還沒有效率的問題,編譯運行如下

圖片.png

        我們看到此時顯然已經不再調用 Test 類的構造函數了。下來我們來看看一個棧的應用:符號匹配問題。在 C 語言中有一些成對出現的符號,如括號:(), [], {}, <>,再如引號:' ', " "。該如何實現編譯器中的符號成對檢測呢?

        算法思路:

        1、從第一個字符開始掃描:當遇見普通字符時忽略;當遇見左符號時壓入棧中;當遇見右符號時彈出棧頂符號,並進行匹配。

        2、結束:成功時,所有字符掃描完畢,且棧爲空;失敗時,匹配失敗或所有字符掃描完畢但棧非空。

        編寫代碼如下

#include <iostream>
#include "LinkStack.h"

using namespace std;
using namespace DTLib;

bool is_left(char c)
{
    return (c == '(') || (c == '[') || (c == '{');
}

bool is_right(char c)
{
    return (c == ')') || (c == ']') || (c == '}');
}

bool is_quot(char c)
{
    return (c == '\'') || (c == '\"');
}

bool is_match(char l, char r)
{
    return ( (l == '(') && (r == ')') ) ||
           ( (l == '{') && (r == '}') ) ||
           ( (l == '[') && (r == ']') ) ||
           ( (l == '<') && (r == '>') ) ||
           ( (l == '\'') && (r == '\'') ) ||
           ( (l == '\"') && (r == '\"') );
}

bool scan(const char* code)
{
    LinkStack<char> stack;
    int i=0;
    bool ret = true;

    code = (code == NULL) ? "" : code;

    while( ret && (code[i] != '\0') )
    {
        if( is_left(code[i]) )
        {
            stack.push(code[i]);
        }
        else if( is_right(code[i]) )
        {
            if( (stack.size() > 0) && is_match(stack.top(), code[i]) )
            {
                stack.pop();
            }
            else
            {
                ret = false;
            }
        }
        else if( is_quot(code[i]) )
        {
            if( (stack.size() == 0) || !is_match(stack.top(), code[i]) )
            {
                stack.push(code[i]);
            }
            else if( is_match(stack.top(), code[i]) )
            {
                stack.pop();
            }
        }

        i++;
    }

    return ret && (stack.size() == 0);
}

int main()
{
    cout << scan("abcd") << endl;

    return 0;
}

        我們看看編譯運行的結果是否爲 1。

圖片.png

        我們再將上面的 scan 中的內容改爲 \"<a{b(\'e\')c}d>\" 看看結果是否還是匹配完成

圖片.png

        我們將前面的雙引號去掉看看結果

圖片.png

        我們看到已經實現了符號的成對檢測功能。通過今天對棧的學習,總結如下:1、棧是一種特殊的線性表,棧值允許在線性表的一端進行操作;2、StaticStack 使用原生數組作爲內部存儲空間,它的最大容量由模板參數決定;3、鏈式棧的實現組合使用了單鏈表對象,在單鏈表的頭部進行操作能夠實現高效的入棧和出棧操作;4、棧“後進先出”的特性適用於檢測成對出現的符號;5、棧非常適合於需要“就近匹配”的場合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章