• 启迪思维:链式栈


      分享一个技术常识:我又一次去移动营业厅打印一年的话单,发现营业员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)

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

    一根球杆,几行代码,品世间酸甜苦辣

    如果你喜欢这篇文章,欢迎推荐

    年轻人有理想、有野心,更需要脚踏实地做事情

  • 相关阅读:
    【字符串】
    SQL Server 一些使用小技巧
    SQL语句将一个表的数据写入到另一个表中
    SQL语句用一个表的数据更新另一个表
    Sqlserver数据库中无自增Id的情况下使用ROW_NUMBER()函数进行数据分页
    Sqlserver存储过程中使用Select和Set给变量赋值
    NPOI导Excel样式设置
    NPOI根据模板生成chart图表导出Excel
    Sqlserver中通过Select Into语句快速单表备份
    NPOI插入图片到excel指定单元格
  • 原文地址:https://www.cnblogs.com/sunysen/p/3084353.html
Copyright © 2020-2023  润新知