启迪思维:链式栈
分享一个技术常识:我又一次去移动营业厅打印一年的话单,发现营业员MM按照月份打印,看MM长相不错,顺势搭讪为啥不能一次性打印出来呢?MM很友好处说我们公司规定一次只打印一个月,后面做这方面的应用才明白,这哪是规定,是做系统的厂商技术不到位,在电信的boss系统话单表数据量非常大,设计系统都人很自然想分表(每一个月一张表),这个设计直接导致一次只能查询一月(查询一个月都很慢,更别说联合查询12个月),貌似现在已经支持查询3个月的话单。淘宝的目前在这一块已经做非常棒,可以随便选择时间段查询自己的订单(KV查询系统),下面进入正题。
上一篇博客分析栈的相关知识和分析用数组的方式实现栈(顺序栈),顺序栈在内存中的存储位置是连续的,且编译器要求我们在编译期就要确定栈的大小,这样对于多数应用都不能很好的确定初始值(设置过大导致浪费内存,设置过小导致频繁分配造成大量内存碎片),使用了链表来实现栈,链表中的元素存储在不连续的地址,由于是动态申请内存(也会造成内存碎片),由于每个节点占用的内存空间一样并且很小,可以用内存池(boost的pool)避免频繁向系统申请和释放内存,基于上面的原因很多开源项目对栈的实现都是链式栈。
一:示例图
二:代码分析
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_ */
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_ */
三:环境
1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、开发工具:Eclipse+make
四:题记
1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;
2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;
3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";
4、运行上面的代码还需要在包含一个公共的头文件(Common.h)
欢迎继续阅读“启迪思维:数据结构和算法”系列