DataStructure - 栈 (Stack)

可以从 生活中 来举例, 比如 你家里一共有 10个人在吃饭, 吃完饭后 也就说至少有10个碗清洗和整理到碗橱(假设一个人一个碗,大小都一样.)  现在每清洗好后一个碗, 就依次叠在一起.

当下次只有你一个人在家吃饭时,你现在需要取一个碗来吃饭, 按照正常逻辑 你会从那碟碗上 取第一个.(从下面取或者从中间取都是不明智的) 这就是一个生活中 栈的例子.



 程序设计 角度来谈谈,看下面的一段测试程序.

void testStack(int a,char b,int c) {
           
}          
int main() {
       testStack(2, 'b', 1); //研究下函数参数的入栈和出栈顺序
           
       return 0;
}

我们objdump -d这段程序,由于长度关系 只取出一部分..

<main>:

400489:       ba 01 00 00 00          mov    $0x1,%edx   
 40048e:       be 61 00 00 00          mov    $0x61,%esi
 400493:       bf 02 00 00 00           mov    $0x2,%edi

<testStack>

400478:       89 7d fc                mov    %edi,-0x4(%rbp)
  40047b:       89 f0                     mov    %esi,%eax
  40047d:       89 55 f4                mov    %edx,-0xc(%rbp)
  400480:       88 45 f8                mov    %al,-0x8(%rbp)

从汇编代码可以看出 ,分别把$0x1,$0x61,$0x2 三个立即数存入不同的寄存器中,然后再通过寄存器传参,可以看出 由于栈是由高到低(栈底->栈顶), 所以入栈的顺序为 1, 'b', 2

低          400478       %edi,-0x4(%rbp)       2                 栈顶

            40047b       $0x61,%esi               'b'

高          40047d       %edx,-0xc(%rbp)       1                 栈底

说到这,函数参数从右往左入栈好处是 由于出栈是先pop最左边的 也就是离函数名最近的.所以每次取参数就很方便.


深入认识栈

栈可以说是一个残缺的线性表, 因为元素的添加和删除都是在一端进行的.都遵循着 "后进先出" (LIFO) ,跟上文说的碗一样 ,最后的放的碗总是先被使用.

来看下栈的图示结构.


通过图示可以大概看出两种操作 Push 和 Pop 也就是 入栈 和 出栈.Step 1 开始初始化为一个空栈,Step 2 ,向栈中压入一个值 a, Step 3继续压栈 b ,Step 4中进行 Pop b.只剩下 a. 逻辑比较简单.很容易懂.

程序设计时,可以使用Array 和 List 两种数据结构来实现, Array实现的 为 顺序栈 ,它在内存中为连续存放的,List实现的为链式栈,随机存放.

顺序栈:

实现原理: 使用一个数组 vessel 和 大小 size, 每次push前 先判断 vessel的大小是否小于 size ,如果小于则进行入栈操作, 每次Pop前 先检查 vessel的大小是否为 0 如果不为0 则进行Pop操作.

设计类如下:

template <class T>
class StackArray {
	 int size;
	 int tos;					/**< 索引 */
	 T* vessel;
public:
	 StackArray(int s);
	 void Push(T& elm);
	 T& Pop();
};
/* --- 构造函数 --- */
template <class T>
StackArray<T>::StackArray(int s) {
	 size = s;
	 tos = -1;
	 vessel = new T[s];
}

Push操作实现:

template <class T>
void StackArray<T>::Push(T& elm) {
	 if (tos == size-1) { return; }	/**< 判断栈是否满 */
	 vessel[tos++] = elm;
}

Pop操作实现:

template <class T>
T& StackArray<T>::Pop() {
	 if (tos == -1) { return; }	/**< 判断栈是否为空 */
	 return vessel[tos--];
}

链式栈

实现原理:顺序栈是个效率很高的实现方法,但是对内存利用很不灵活,所以可以进一步使用链式来进行内存管理,利用设计链表的方法,先设计出链式栈节点类 ,然后在设计出 链式栈类. 链式栈节点类 , 需要 保存数据 和 指向下一个节点的指针 两个成员变量.

template <class T>
class StackLinkNode {
	 T data;
	 StackLinkNode<T>* link;
	 StackLinkNode(T& value):data(value),link(NULL){}
};

链式栈设计 和 顺序栈不同,不需要容器来保存 只需要一个 栈顶指针.

template <class T>
class StackLink {
	 StackLinkNode<T>* tos;
public:
	 StackLink():tos(NULL){}
	 void Push(T& value);
	 T& Pop();
};
Push操作实现如下:

template <class T>
void StackLink<T>::Push(T& value) {
	 StackLinkNode<T>* node = new StackLinkNode<T>(value);
	 node->link = tos;			/**< 新创建的结点指向栈顶结点 */
	 tos = node;				/**< tos++ 指向新添加的节点 */
}
看下面的图会比较好理解点:


Pop操作如下:

template <class T>
void StackLink<T>::Pop() {
	 StackLinkNode<T>* old = tos;
	 tos = tos->link;			/**< 向栈底移动 */
	 T data = old->data;		/**< 保存原栈顶元素 */
	 delete old;
	 return data;
}

如图:

实现Pop时需要注意 资源释放问题. 所以需要个临时的来进行保存.


其它知识点:

  由于栈操作是常数时间,所以影响一个栈的执行效率是在冗长的错误检测上,比如链式栈, 但是忽略错误检测是不行的, 我们应该尽量把冗长错误检测进行重新整理. 使其尽可能的高效率.



原文链接: http://blog.csdn.net/crazyjixiang/article/details/6755430

转载于:https://my.oschina.net/chen106106/blog/51116

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值