天天看点

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

作者:IT民工冯老师

大家在学习编程时或程序开发中,经常涉及数据结构中的“队列”和“栈”。那么他们分别具有什么结构特点?底层实现原理是什么?他们在STL中是如何实现的呢?都有哪些应用场景呢?

备注:因为篇幅限制,本文先讲解数据结构中的栈(stack)。

1.引言

栈和队列是两种特殊的线性表,是操作受限的线性表,称限定性DS。

通常称,栈和队列是限定插入和删除只能在表的“端点”进行的线性表。

举例:

线性表Insert(L, i, x)1≤i≤n+1Delete(L, i) 1≤i≤n

栈Insert(S, n+1, x) Delete(S, n)

队列Insert(Q, n+1, x)Delete(Q, 1)

2.栈的定义和特点

栈(stack)是一种先进后出的数据结构。它只有一个出口。stack允许新增元素、删除元素、取得最顶端元素。但是除了最顶端外,没有任何其它方法可以存取stack的其他元素。换言之,stack不允许有遍历行为。

定义:限定仅在表尾进行插入或删除操作的线性表。表尾是栈顶,表头是栈底,不含元素的空表称空栈。

特点:先进后出(FILO)或后进先出(LIFO)

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

3.栈的实现原理

类似于线性表的顺序映象实现,利用一组存储单元依次存放自栈底到栈顶的数据元素,同时附设指向表尾的指针top,指示栈顶元素在顺序栈中的位置,称为栈顶指针,连续存储单元的基址用指针base指示,称为栈底指针。

//----- 栈的顺序存储表示 -----

#define STACK_INIT_SIZE 100;

typedef struct {

SElemType *base;

SElemType *top;

int stacksize;

} SqStack;

top的初值指向栈底:top=base,此条件也可作为判栈空的条件;

在实际使用中,top 始终指向栈顶元素的下一个位置上;

入栈(插入新的栈顶元素):top增1;

出栈(删除栈顶元素): top减1。

3.1.顺序栈

利用一组"地址连续"的存储单元依次存放自栈底到栈顶的数据元素

数据结构和入栈出栈示意图:

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

3.2.链栈

利用一组链表依次存放自栈底到栈顶的数据元素。

链表节点定义:

typedef struct node

{

int data;

struct node *link;

} node;

数据结构和入栈出栈示意图:

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景
数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

4.STL中的栈实现

在c++开发中,我们常常直接使用STL中封装好的容器。那么,对于栈在STL中是如何实现的呢?又有哪些特点呢?

①SGI STL的stack默认是以deque为底层数据容器的。(对应前面提到的“顺序栈”,也可以使用vector为底层数据容器)

以某种既有容器作为底部结构,将其接口改变,使之符合“先进后出”的特性,形成一个stack,是很容易做到的。deque是双向开口的数据结构,若以deque为底部结构并封闭其头端开口,便轻而易举地形成了一个stack。因此,SGI STL便以deque作为缺省情况下的stack底部结构,stack的实现因而非常简单,源代码十分简短。

template <class T, class Sequence = deque<T> >

class stack {

friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);

friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);

public:

typedef typename Sequence::value_type value_type;

typedef typename Sequence::size_type size_type;

typedef typename Sequence::reference reference;

typedef typename Sequence::const_reference const_reference;

protected:

Sequence c;// 底层容器

public:

// 以下完全利用 Sequence c 的操作,完成 stack 的操作。

bool empty() const { return c.empty(); }

size_type size() const { return c.size(); }

reference top() { return c.back(); }

const_reference top() const { return c.back(); }

// deque 是两头可进出,stack 是末端进,末端出(所以后进者先出)。

void push(const value_type& x) { c.push_back(x); }

void pop() { c.pop_back(); }

};

template <class T, class Sequence>

bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {

return x.c == y.c;

}

template <class T, class Sequence>

bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {

return x.c < y.c;

}

②stack没有提供迭代器

stack所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stck不提供走访功能,也不提供迭代器。

③可以使用list作为stack的底层容器。(对应前面提到的“链栈”)

除了deque之外,Iist也是双向开口的数据结构。上述stack源代码中使用的底层容器的函数list都具备。因此,若以list为底部结构并封闭其头端开口,一样能够轻易形成一个stack。

测试代码:

参考文章最后的附录。

测试程序运行效果:

分别使用STL的deque、list、vector作为stack的底层容器,进行同样数据的入栈和出栈。

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

5.栈的应用举例

①数制转换

例如:(1348)10 = (2504)8 ,其运算过程如下:

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

②括号匹配的检验

例如:假设在表达式中,只有[]()两种括号,如:([]())、[([ ][ ])]等为正确的格式,[( ])或([( ))或 (() ] )均为不正确的格式。检验括号是否匹配。

算法的设计思想:

1)凡出现左括号,则进栈;

2)凡出现右括号,首先检查栈是否空:

若栈空,则表明该“右括号”多余不匹配,

否则和栈顶元素比较,

若相匹配,则“左括号出栈”,否则表明不匹配

3)表达式检验结束时,

若栈空,则表明表达式中匹配正确

否则表明“左括号”有余,不匹配

③回文游戏

例如:顺读与逆读字符串一样(不含空格)

数据结构的栈(stack)是如何实现的?一文了解原理和应用场景

比如,字符串:“madam im adam”

算法设计思想:

1.读入字符串

2.去掉空格(原串)

3.压入栈

4.原串字符与出栈字符依次比较

若不等,非回文

直到栈空都相等,回文

④行编辑程序问题、迷宫求解、表达式求值、实现递归等

这些场景就不给大家一一举例了,感兴趣的同学可以再评论区留言,我再留言区回复大家。

6.附录

测试代码源码。

#include<iostream>

#include <stack>

#include <list>

#include <vector>

#include <algorithm>

using namespace std;

template <class T, class Sequence = deque<T> >

void stack_fun_test(stack<T, Sequence> & list_stack)

{

//入栈

list_stack.push(1);

list_stack.push(3);

list_stack.push(5);

list_stack.push(7);

if (true != list_stack.empty())

{

}

// 元素依次出栈

while (true != list_stack.empty())

{

// 打印栈顶元素

// 出栈

list_stack.pop();

}

}

//以deque作为缺省情况下的stack底部结构

void stack_fun_deque_test()

{

stack<int> list_stack; //创建stack

stack_fun_test(list_stack);

}

//以list作为stack底部结构

void stack_fun_list_test()

{

stack<int, list<int>> list_stack; //创建stack

stack_fun_test(list_stack);

}

//以vector作为stack底部结构

void stack_fun_vector_test()

{

stack<int, vector<int>> list_stack; //创建stack

stack_fun_test(list_stack);

}

int main(int argc, char* argv[])

{

stack_fun_deque_test();

stack_fun_list_test();

stack_fun_vector_test();

return 0;

}

原创不易,欢迎关注、收藏、转发!

继续阅读