启迪思维:链式栈

  分享一个技术常识:我又一次去移动营业厅打印一年的话单,发现营业员MM按照月份打印,看MM长相不错,顺势搭讪为啥不能一次性打印出来呢?MM很友好处说我们公司规定一次只打印一个月,后面做这方面的应用才明白,这哪是规定,是做系统的厂商技术不到位,在电信的boss系统话单表数据量非常大,设计系统都人很自然想分表(每一个月一张表),这个设计直接导致一次只能查询一月(查询一个月都很慢,更别说联合查询12个月),貌似现在已经支持查询3个月的话单。淘宝的目前在这一块已经做非常棒,可以随便选择时间段查询自己的订单(KV查询系统),下面进入正题

 

  上一篇博客分析栈的相关知识和分析用数组的方式实现栈(顺序栈),顺序栈在内存中的存储位置是连续的,且编译器要求我们在编译期就要确定栈的大小,这样对于多数应用都不能很好的确定初始值(设置过大导致浪费内存,设置过小导致频繁分配造成大量内存碎片),使用了链表来实现栈,链表中的元素存储在不连续的地址,由于是动态申请内存(也会造成内存碎片),由于每个节点占用的内存空间一样并且很小,可以用内存池(boostpool)避免频繁向系统申请和释放内存,基于上面的原因很多开源项目对栈的实现都是链式栈。

示例图

 

二:代码分析

1、元素入栈

入栈效果图如下:

代码分析如下:

 1 /**
 2  * 元素入栈
 3  */
 4 void Push(const T &e){
 5     //新创建一个节点,当频繁插入和删除数据,
 6     //下面这样内存操作方式是非常低效的(频繁分配内存和释放内存已经非常低效,
 7     //而且会造成内存碎片),大多算情况list里边存储都小对象,这样应用场景适合
 8     //在系统加载时候分配很多小内存装入内存池,需要直接从内存池拿,释放时放回内存池
 9     //更多关于stl内存分配问题请参考侯捷<<STL源码剖析>>
10     LNode<T> *p = new LNode<T>(e);
11     //新的节点指针域指向原栈顶
12     p->next = tos;
13     //修改栈顶指针指向新的节点
14     tos = p;
15 }

2、元素出栈

出栈效果图如下:

代码分析如下:

 1 /**
 2  * 元素出栈
 3  */
 4 void Pop(T &e){
 5     //判断栈是否为空
 6     if(tos != 0){
 7         //释放删除节点占用内存,更多关于auto_ptr知识,请阅读memory里auto_ptr源码
 8         std::auto_ptr<LNode<T> > new_ptr(tos);
 9         //获取删除节点的值
10         e = new_ptr->data;
11         //修改栈顶指向下一个节点
12         tos = tos->next;
13     }
14 }

3、清空链表

 1 /**
 2  * 清楚栈
 3  */
 4 void Clear(){
 5     T e;
 6     //如果栈不为空,弹出栈顶元素
 7     while(!IsEmpty()){
 8         Pop(e);
 9     }
10 }

4、判断是否为空

1 /**
2  * 判断栈是否为空
3  */
4 bool IsEmpty(){
5     return tos == 0;
6 }

5、计算大小

 1 /**
 2  * 计算栈的大小
 3  */
 4 size_t GetSize(){
 5 
 6     //获取栈顶指针
 7     LNode<T> *p = tos;
 8     int len = 0;
 9 
10     //循环整这个栈大小,当栈==0,表示栈已经遍历完成
11     while(p != 0){
12         ++len;
13         //指向下一个栈节点
14         p = p->next;
15     }
16 
17     return len;
18 }

6、运行结果

测试代码如下:

 1 /**
 2  * 测试所有链式栈的方法
 3  */
 4 void test(){
 5     std::cout<<"-----------push stack begin------------"<<std::endl;
 6     for(size_t i = 0; i < 5; ++i){
 7         Push(i);
 8     }
 9     std::cout<<"-----------push stack end------------"<<std::endl;
10 
11     std::cout<<"the frist "<<GetSize()<<"\n";
12 
13     std::cout<<"-----------pop stack begin------------"<<std::endl;
14     T e;
15     for(size_t i = 0; i < 4; ++i){
16         Pop(e);
17         std::cout<<"e is value = "<<e<<std::endl;
18     }
19     std::cout<<"-----------pop stack end------------"<<std::endl;
20 
21     std::cout<<std::endl;
22     std::cout<<"the secend "<<GetSize()<<"\n";
23     Clear();
24     std::cout<<"the thrid "<<GetSize()<<"\n";
25 
26 }

结果图如下:

7、完整代码

LinkStack.h代码如下:

  1 /*
  2  * LinkList.h
  3  *    链式栈的实现代码
  4  *  Created on: May 6, 2013
  5  *      Author: sunysen
  6  */
  7 
  8 //条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译
  9 #ifndef LinkStack_H_
 10 #define LinkStack_H_
 11 
 12 template <class T>
 13 class LinkStack{
 14 private:
 15 LNode<T> *tos;//栈顶
 16 public:
 17 LinkStack():tos(0){
 18 }
 19 
 20 /**
 21  * 判断栈是否为空
 22  */
 23 bool IsEmpty(){
 24     return tos == 0;
 25 }
 26 
 27 /**
 28  * 计算栈的大小
 29  */
 30 size_t GetSize(){
 31 
 32     //获取栈顶指针
 33     LNode<T> *p = tos;
 34     int len = 0;
 35 
 36     //循环整这个栈大小,当栈==0,表示栈已经遍历完成
 37     while(p != 0){
 38         ++len;
 39         //指向下一个栈节点
 40         p = p->next;
 41     }
 42 
 43     return len;
 44 }
 45 
 46 /**
 47  * 清楚栈
 48  */
 49 void Clear(){
 50     T e;
 51     //如果栈不为空,弹出栈顶元素
 52     while(!IsEmpty()){
 53         Pop(e);
 54     }
 55 }
 56 
 57 /**
 58  * 元素出栈
 59  */
 60 void Pop(T &e){
 61     //判断栈是否为空
 62     if(tos != 0){
 63         //释放删除节点占用内存,更多关于auto_ptr知识,请阅读memory里auto_ptr源码
 64         std::auto_ptr<LNode<T> > new_ptr(tos);
 65         //获取删除节点的值
 66         e = new_ptr->data;
 67         //修改栈顶指向下一个节点
 68         tos = tos->next;
 69     }
 70 }
 71 
 72 /**
 73  * 元素入栈
 74  */
 75 void Push(const T &e){
 76     //新创建一个节点,当频繁插入和删除数据,
 77     //下面这样内存操作方式是非常低效的(频繁分配内存和释放内存已经非常低效,
 78     //而且会造成内存碎片),大多算情况list里边存储都小对象,这样应用场景适合
 79     //在系统加载时候分配很多小内存装入内存池,需要直接从内存池拿,释放时放回内存池
 80     //更多关于stl内存分配问题请参考侯捷<<STL源码剖析>>
 81     LNode<T> *p = new LNode<T>(e);
 82     //新的节点指针域指向原栈顶
 83     p->next = tos;
 84     //修改栈顶指针指向新的节点
 85     tos = p;
 86 }
 87 
 88 
 89 /**
 90  * 测试所有链式栈的方法
 91  */
 92 void test(){
 93     std::cout<<"-----------push stack begin------------"<<std::endl;
 94     for(size_t i = 0; i < 5; ++i){
 95         Push(i);
 96     }
 97     std::cout<<"-----------push stack end------------"<<std::endl;
 98 
 99     std::cout<<"the frist "<<GetSize()<<"\n";
100 
101     std::cout<<"-----------pop stack begin------------"<<std::endl;
102     T e;
103     for(size_t i = 0; i < 4; ++i){
104         Pop(e);
105         std::cout<<"e is value = "<<e<<std::endl;
106     }
107     std::cout<<"-----------pop stack end------------"<<std::endl;
108 
109     std::cout<<std::endl;
110     std::cout<<"the secend "<<GetSize()<<"\n";
111     Clear();
112     std::cout<<"the thrid "<<GetSize()<<"\n";
113 
114 }
115 };
116 
117 #endif /* LinkStack_H_ */
View Code

Common.h代码如下: 

 1 /*
 2  * Common.h
 3  *
 4  *  Created on: May 17, 2012
 5  *      Author: sunysen
 6  */
 7 
 8 #ifndef COMMON_H_
 9 #define COMMON_H_
10 
11 #include <iostream>
12 #include "memory"
13 #include "string"
14 #include "core/node/LNode.h"
15 
16 using namespace std;
17 #endif /* COMMON_H_ */
View Code

 :环境

1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3

2、开发工具:Eclipse+make

:题记

1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;

2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;

3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛"

4、运行上面的代码还需要在包含一个公共的头文件(Common.h)

 欢迎继续阅读“启迪思维:数据结构和算法”系列

posted @ 2013-05-18 22:44  sunysen  阅读(1036)  评论(2编辑  收藏  举报