ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 114598|回复: 200

[原创] VBA编程技巧 之 浅谈数据结构 (不定期持续更新,最后更新130411)

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2012-12-18 15:08 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:
本帖最后由 lee1892 于 2013-4-11 13:05 编辑

引言

    还是一个心血来潮的东西,先是看到不少关于Bill Of Material(BOM)材料清单的帖子,于是写了个用字典链接构造链表的代码。而后又试着用数组、自定义数据类型改写。突然意识到论坛里有不少高深算法的帖子(代表性的法师的一系列算法帖,一直怀疑这家伙是学应用数学之类的)、有大量介绍各种对象的、有列出或利用诸多系统API函数而随心所欲的做出各种功能的,貌似没人介绍最基本的数据结构。所以就想试着写些,套个俗话:权当抛砖引玉了。

    VBA作为Visual Basic的一个子集,可以非常便捷的帮助我们实现一些原有软件不能做到的功能。虽说它是一个简化了的工具,但究其实质还是一个完整的计算机程序语言。那么要用好VBA,势必需要我们了解计算机程序语言一些最基本的概念。而无论你学习任何一种程序语言,数据结构必然是最基础的。

    那么什么是数据结构呢,可以从两方面来理解。当我们使用数据来描述现实世界中一样或一类事物时,这些数据本身之间的相互内在关系就是数据结构需要表达的。所以我们可以理解为数据结构是描述数据之间的逻辑关系得,即逻辑结构,比如门牌号码系统就是天然的树形数据结构。而另一方面,在计算机世界中,数据是要进行存储和取用的即所谓的存取,如何组织存储数据就决定了取用时实现的方法,比如是链表顺序存储还是使用索引随机存储,所以我们可以理解为数据结构是指数据的存储结构,即物理结构。作为软件的使用者,我们显然不需要对后者有过多的了解,所以这里只聊聊前者。

    至于为什么要了解数据结构,这是因为数据结构在某种程度上决定了算法的难易程度,也决定了程序的执行效率。

    Pascal 之父 Niklaus Wirth 的一本著名著作就叫《算法 + 数据结构 = 程序》

(会是一个长贴吗......? By Lee1892, 2012.12.21)

目录
楼层
标题
附件
02楼VBA提供的数据类型
05楼迭代(Iteration)与递归(Recursion)
07楼迭代与递归的使用迭代循环查询 | 递归循环查询
08楼队列(Queue)和堆栈(Stack)
10楼队列与堆栈的使用
13楼使用队列和堆栈获得正确的顺序
18楼链表(Linked List)
19楼使用链表构建树形结构单向链表构建树形结构
20楼树(Tree)和二叉树(Binary Tree)
22楼二叉查找树(Binary Searching Tree)的 向左走、向右走
25楼也谈排序
27楼字典(Dictionary)和散列表(Hash Table)
29楼n叉树转为二叉树的示例n叉树转二叉树示例
32楼使用案例-两棵树的比较两棵树的比较
37楼堆(Heap)、二叉堆(Binary Heap)、优先队列(Priority Queue)二叉堆的类代码演示
42楼使用正确的数据结构的例子(一)彭希仁的汇总表
43楼使用正确的数据结构的例子(二)地理信息的网格查找示例 | 及其改进
87楼使用正确的数据结构的例子(三)发货明细统计
94楼使用正确的数据结构的例子(四)由28秒到2秒!水样监测_使用数据结构加速

====================================================================================
Original Post:

下午没事,逛论坛。。。
貌似不少BOM的东西,估计都是从ERP之类的软件导出来的数据,数据完全没有组织,每个条目仅有所属部件/产品的编号和自身的编号。
大致翻了一下相关的帖子,没看到什么好的方案,所以动手写了一段字典套字典的。

供参考~~

补充内容 (2013-4-15 11:08):
本来还存了些有趣的案例,傻了吧叽的论坛不让编辑了,这个贴子也就到此为止不再更新了。只能说有病~

BOM查询-By Lee1892.rar

158.22 KB, 下载次数: 1538

使用字典嵌套构造双向链表

BOM查询-数组构造双向链表-By Lee1892.rar

124.38 KB, 下载次数: 961

使用数组构造双向链表

BOM查询-数组结合自定义数据类型构造双向链表-By Lee1892.rar

119.87 KB, 下载次数: 965

使用自定义数据类型构造双向链表

BOM查询-不构造任何数据结构,递归循环查询-By Lee1892.rar

117.7 KB, 下载次数: 1344

不构造任何数据结构,递归循环查询

BOM查询-不构造任何数据结构,迭代循环查询-By Lee1892.rar

118.34 KB, 下载次数: 1206

不构造任何数据结构,迭代循环查询

BOM查询-单向链表构建树形结构-By Lee1892.rar

118.98 KB, 下载次数: 1151

单向链表构建树形结构

BOM查询-单向链表构建树形结构继而转为二叉树-By Lee1892.rar

121.39 KB, 下载次数: 1027

n叉树转为二叉树的示例及二叉树的遍历

带层次结构的BOM比较-使用单向链表的树形结构-By Lee1892.rar

26.22 KB, 下载次数: 1029

两棵结构内容相近的树的比较

二叉堆 的 类 代码演示 by Lee1892.rar

19.86 KB, 下载次数: 1062

二叉堆 的 类 代码实现演示

评分

26

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2012-12-18 15:09 | 显示全部楼层
本帖最后由 lee1892 于 2012-12-22 11:14 编辑

VBA提供的数据类型

VBA中的基本数据类型有:字节(Byte)、整型(Integer)、长整型(Long)、单精度(Single)、双精度(Double)、货币型(Currency)、字符串(String)、布尔型(Boolean)、日期(Date)、Object(对象)、变体(Variant)、用户自定义类型

其中的用户自定义类型是用 Type 在申明部分显式申明的,可以将一系列相关数据组织到一起,从而仅使用一个变量就可以获得这些数据。使用方式如:
Private Type 美女
    胸围 As Single
    腰围 As Single
    臀围 As Single
End Type
Public Sub 查找美女()
    Dim Girl as 美女
    Girl.胸围 = ...
    ...
End Sub

再有就是数组了,就物理结构而言,数组是顺序存储的由多个元素组成的表状数据结构,其中的元素则可以是任何数据类型。由于VBA不能像正经的程序语言如C之类的直接读写内存,所以我们会经常使用数组来模拟实现其它数据结构。

Collection 集合对象,VBA中原生的可以用关键字(字符串)索引的数据类型,由多个元素构成的顺序存储的表状数据结构,元素可以是任何数据类型。Excel内的大量对象都是通过它来封装的,如 Ranges、Cells、Rows、Columns 等都是Range对象的集合封装。值得注意的是,VBA中的集合对象是支持插入操作的,其Add方法允许定义Before或After参数。

Dictionary 字典对象,VBS中的可以用关键字(任何数据类型)索引的数据类型,猜测存储方式也是顺序的,但内键了某种机制(据猜测是散列表,一称哈希表,Hashing),使得能够快速的将关键字转化为存储地址而获得其索引的数据,元素可以是任何数据类型。

,一种特殊的自定义数据类型,其不仅包含了静态的数据,同时也包含了其内部的过程,并可捕获事件以激发某个过程,从而使得其脱离了前述各种静态数据类型的范畴,而将 数据结构、算法 融于一体,实现所谓的面向对象。

以上,是我们使用的工具。

=====================================================
Original Post (字典构造双向链表的ParseData代码,删,详见1楼附件)

TA的精华主题

TA的得分主题

发表于 2012-12-18 15:15 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
{:soso_e179:}学习了

TA的精华主题

TA的得分主题

发表于 2012-12-18 15:23 | 显示全部楼层
做的挺好,就是不具备通用性。估计这段代码给其他人改起来也是超费劲的

TA的精华主题

TA的得分主题

 楼主| 发表于 2012-12-18 15:26 | 显示全部楼层
本帖最后由 lee1892 于 2012-12-22 12:59 编辑

迭代(Iteration)与递归(Recursion)

在我们讲数据结构之前,先作一下思维训练。经常看到有人说什么递归算法,严格来说递归不能称为算法,只能说是一种思维方式。

从数学角度而言,所谓迭代可以指函数迭代的过程,即反复的运用同一函数计算,前一次迭代得到的结果被用于作为下一次迭代的输入。而在计算机世界中,迭代是程序中对一组指令(或一步骤)的重复。

而递归是指在函数或过程的定义中使用函数自身的方法。很显然,作为前述第一种意义下的迭代,递归实质上是迭代的一种特例。我一直认为,递归的思维逻辑是和数学中的归纳法一样的,比如证明由1至n的连续自然数的和的计算公式。

以计算等差数列的和为例,使用迭代的函数可以是这样的:
[code=vb]
Function Sum_Iteration&(n&)
    Dim i&
    Sum_Iteration= 0
    For i = 1 to n
        Sum_Iteration = Sum_Iteration + i
    Next
End Function
[/code]
而使用递归的函数:
[code=vb]
Function Sum_Recursion&(n&)
    If n = 1 Then
        Sum_Recursion = 1
    Else
        Sum_Recursion = n + Sum_Recursion(n - 1)
    End If
End Function
[/code]
非常有趣的是,上述递归代码在计算 n > 5835 时会报“溢出堆栈空间”的错误。
========================================================
Original Post:

guojianlin1985 发表于 2012-12-18 15:23
做的挺好,就是不具备通用性。估计这段代码给其他人改起来也是超费劲的

嗯嗯,我个人意见以为恰恰相反。
只要是符合我对原始数据描述的,基本只需要改改几个常量就可以了。。。

哼哼~~~

点评

似乎是n>6115时会出错……或是不同计算机有所不同?  发表于 2015-1-1 22:13

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2012-12-18 15:30 | 显示全部楼层
呵呵,各人的想法不同,思路不同,但是只要结果是正确的就好!

TA的精华主题

TA的得分主题

发表于 2012-12-28 22:04 | 显示全部楼层
本帖最后由 hehex 于 2012-12-28 22:17 编辑

好帖啊,好好学习。佩服楼主扎实的数据结构基本功和算法知识。厉害{:soso_e179:}{:soso_e179:}

TA的精华主题

TA的得分主题

 楼主| 发表于 2012-12-19 15:14 | 显示全部楼层
本帖最后由 lee1892 于 2012-12-22 13:38 编辑

迭代与递归的使用

1、本贴使用案例的原始数据分析:

此贴中所使用的案例是一个BOM清单,每一条记录反映了一个 产品/部件 所需要的一种 材料 的信息,包括如名称、规格、数量等。

该案例的最初的功能述求是取得某个 产品 所需的全部 部件材料 信息,并罗列出来。显然这些数据的内在关联是一种树形结构,每个产品是一棵树,其根是产品,部件则是分支节点,材料则是叶子。

注:本贴所有代码,均假设原始数据中没有产生循环。
       本贴中使用aData数组来缓存原始数据,作为数据存储的模拟,行号则模拟了储存地址,列号对应的是储存的不同信息。

让我们分别使用迭代和递归的思维逻辑来作这个查询。

2、迭代:
具体代码见一楼附件:BOM查询-不构造任何数据结构,迭代循环查询
其中获得材料信息函数GetBOMList不作详细介绍,着重讲迭代过程,代码如下:
[code=vb]
Private Sub GetChildren(sNo$, aRows, nCount&, iLayer&)
    ' sNo: 产品/部件 的编号,对应于 GC 列,过程内改变值,应定义为 ByRef
    ' aRows: 二维数组,用于保存查找的结果,第二维对应的是查找到的项目数量,第一维有2个元素分别对应该项的层数和地址,过程内改变,应定义为 ByRef
    ' nCount: 查找得到结果的数量,过程内改变,应定义为 ByRef
    ' iLayer: 查找得到结果的层数,由于是迭代过程,这个参数实际为内部变量,为不改变其它代码,故作保留
    ' 以上参数中,aRows实际上是返回值。
    Dim i&, nCurrent&
    iLayer = 1 ' 初始化层数为 1
    Do
    ' 循环查找,退出机制使用Exit Do方式设于循环内部
        For i = 2 To UBound(aData)
        ' 遍历数据,进行查找。递归的结束是查找完全部数据。
            If sNo = aData(i, nParentCol) Then
            ' 于GC列,找到 产品/部件 的编号
                nCount = nCount + 1 ' 找到的项目数量 +1
                aRows(1, nCount) = iLayer ' 在返回数组中记录该项目的层数
                aRows(2, nCount) = i ' 在返回数组中记录该项目的地址
            End If
        Next
        nCurrent = nCurrent + 1 ' 在查找到的结果中,下次循环查找项目指针 +1
        If nCurrent > nCount Then Exit Do ' 如果下次查找项目指针大于已经找到的项目数量,则完成全部查询,退出
        sNo = aData(aRows(2, nCurrent), nItemCol) ' 获得下次查询的 部件/材料 编号
        iLayer = aRows(1, nCurrent) + 1 ' 下次查询的层数 是 本次查询的层数 +1
    Loop
End Sub
[/code]
由于上述迭代查询代码的限制,显示的查询结果是按层数排列的,即总是低层的在前面。

3、递归:
具体代码见一楼附件:BOM查询-不构造任何数据结构,递归循环查询
[code=vb]
Private Sub GetChildren(sNo$, aRows, nCount&, iLayer&)
    ' sNo: 产品/部件 的编号,对应于 GC 列,过程内不改变值,可定义为 ByVal
    ' aRows: 二维数组,用于保存查找的结果,第二维对应的是查找到的项目数量,第一维有2个元素分别对应该项的层数和地址,过程内改变,应定义为 ByRef
    ' nCount: 查找得到结果的数量,过程内改变,应定义为 ByRef
    ' iLayer: 查找得到结果的层数,过程内改变,应定义为 ByRef
    ' 以上参数中,aRows实际上是返回值。
    Dim i&, sItemNo$ ' , nRow&, nChildNodeNo& 这俩变量忘删了...
    For i = 2 To UBound(aData)
    ' 遍历数据,进行查找。递归的结束是查找完全部数据。
        If sNo = aData(i, nParentCol) Then
        ' 于GC列,找到 产品/部件 的编号
            iLayer = iLayer + 1 ' 层数 +1
            nCount = nCount + 1 ' 找到的项目数量 +1
            aRows(1, nCount) = iLayer ' 在返回数组中记录该项目的层数
            aRows(2, nCount) = i ' 在返回数组中记录该项目的地址
            sItemNo = aData(i, nItemCol) ' 获得该项目的 部件/材料 编号
            Call GetChildren(sItemNo, aRows, nCount, iLayer) ' 对该项目的 部件/材料 编号进行查找
        End If
    Next
    iLayer = iLayer - 1 ' 本次数据遍历查找结束,层数 -1
End Sub
[/code]
由于采用递归的方法,每次查询树的分支情况时,总是在到达叶子后才返回,所以查询得到的结果是也是同样的顺序,即按 部件/材料 的从属关系排列的。
=======================================================
Original Post (原有用数组构造双向链表的ParseData代码,删,详见1楼附件)

改写成用数组构造双向链表,貌似初始化的速度快了很多啊,而且内存占用也很小
看来字典多重嵌套不是什么好办法,应该尽量少用。。。
附件在1楼



TA的精华主题

TA的得分主题

 楼主| 发表于 2012-12-20 13:28 | 显示全部楼层
本帖最后由 lee1892 于 2012-12-22 14:02 编辑

队列(Queue)和堆栈(Stack)

队列是指遵循先进先出原则的线性表。队列只允许在末端进行插入操作、在前端进行删除操作。
[code=vb]'│队列末端│
'├────┤
'│       │
'├────┤
'│       │
'├────┤
'│队列前端│
[/code]

打印机的打印工作流就是一个队列,当一个打印工作被添加时,位于打印工作记录表的末端,而当一个打印工作结束后,该打印工作从打印工作记录表的前端删除,并进行下一项打印工作。现实世界中有大量的例子,比如排队、零件加工工序、仓库财务报表的先进先出原则等等。

堆栈是指遵循后进先出原则的线性表。堆栈只允许在末端进行插入槽作、在末端进行删除操作。[code=vb]
'│堆栈末端│
'├────┤
'│       │
'├────┤
'│       │
'├────┤
'│堆栈前端│
'└────┘
[/code]

集装箱的装箱卸载过程就是一个很好的例子,最先被装入的货物位于箱底(即前端),最后被装入的货物位于靠门处(即末端),当取出货物时,只能从其末端开始取出。

=================================================
Original Post (使用自定义数据类型构造双向列表的ParseData代码,删,详见1楼附件)
继续改,用自定义数据类型,同前贴上ParseData代码

TA的精华主题

TA的得分主题

发表于 2012-12-20 13:43 | 显示全部楼层
好好学习一下,,,还看的我稀里糊涂的。。。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

最新热点上一条 /1 下一条

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-4-20 12:12 , Processed in 0.048555 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表