【发布时间】:2016-02-15 05:34:32
【问题描述】:
MATLAB 中的树类
我正在 MATLAB 中实现一个树形数据结构。向树中添加新的子节点、分配和更新与节点相关的数据值是我期望执行的典型操作。每个节点都有与之关联的相同类型的data。我不需要删除节点。到目前为止,我已经决定从 handle 类继承的类实现能够将节点的引用传递给将修改树的函数。
编辑:12 月 2 日
首先,感谢到目前为止 cmets 和答案中的所有建议。他们已经帮助我提高了我的树类。
有人建议尝试 R2015b 中引入的digraph。我还没有对此进行探索,但是看到它不像从handle 继承的类那样作为参考参数工作,我有点怀疑它在我的应用程序中的工作方式。在这一点上,我还不清楚使用自定义data 来处理节点和边有多容易。
编辑:(12 月 3 日)有关主应用程序的更多信息:MCTS
最初,我认为主应用程序的细节只会引起人们的兴趣,但自从阅读了 @FirefoxMetzger 的 cmets 和 answer 后,我意识到它具有重要意义。
我正在实现一种Monte Carlo tree search 算法。以迭代方式探索和扩展搜索树。维基百科提供了一个很好的过程图形概述:
在我的应用程序中,我执行了大量的搜索迭代。在每次搜索迭代中,我从根开始遍历当前树直到叶节点,然后通过添加新节点来扩展树,然后重复。由于该方法基于随机抽样,因此在每次迭代开始时,我不知道每次迭代将在哪个叶节点结束。相反,这是由树中当前节点的data 和随机样本的结果共同确定的。我在单次迭代期间访问的任何节点都会更新其data。
示例:我在节点n 有几个孩子。我需要访问每个孩子的数据并随机抽取一个样本,以确定我在搜索中移动到下一个孩子。重复此过程,直到到达叶节点。实际上,我通过在根上调用 search 函数来决定接下来要扩展哪个子节点,在该节点上递归调用 search 等等,最后在到达叶节点时返回一个值。从递归函数返回时使用此值来更新搜索迭代期间访问的节点的data。
这棵树可能非常不平衡,以至于一些分支是非常长的节点链,而其他分支在根级别之后很快终止并且不会进一步扩展。
当前实现
以下是我当前的实现示例,其中包含一些用于添加节点、查询树中节点的深度或数量等的成员函数示例。
classdef stree < handle
% A class for a tree object that acts like a reference
% parameter.
% The tree can be traversed in both directions by using the parent
% and children information.
% New nodes can be added to the tree. The object will automatically
% keep track of the number of nodes in the tree and increment the
% storage space as necessary.
properties (SetAccess = private)
% Hold the data at each node
Node = { [] };
% Index of the parent node. The root of the tree as a parent index
% equal to 0.
Parent = 0;
num_nodes = 0;
size_increment = 1;
maxSize = 1;
end
methods
function [obj, root_ID] = stree(data, init_siz)
% New object with only root content, with specified initial
% size
obj.Node = repmat({ data },init_siz,1);
obj.Parent = zeros(init_siz,1);
root_ID = 1;
obj.num_nodes = 1;
obj.size_increment = init_siz;
obj.maxSize = numel(obj.Parent);
end
function ID = addnode(obj, parent, data)
% Add child node to specified parent
if obj.num_nodes < obj.maxSize
% still have room for data
idx = obj.num_nodes + 1;
obj.Node{idx} = data;
obj.Parent(idx) = parent;
obj.num_nodes = idx;
else
% all preallocated elements are in use, reserve more memory
obj.Node = [
obj.Node
repmat({data},obj.size_increment,1)
];
obj.Parent = [
obj.Parent
parent
zeros(obj.size_increment-1,1)];
obj.num_nodes = obj.num_nodes + 1;
obj.maxSize = numel(obj.Parent);
end
ID = obj.num_nodes;
end
function content = get(obj, ID)
%% GET Return the contents of the given node IDs.
content = [obj.Node{ID}];
end
function obj = set(obj, ID, content)
%% SET Set the content of given node ID and return the modifed tree.
obj.Node{ID} = content;
end
function IDs = getchildren(obj, ID)
% GETCHILDREN Return the list of ID of the children of the given node ID.
% The list is returned as a line vector.
IDs = find( obj.Parent(1:obj.num_nodes) == ID );
IDs = IDs';
end
function n = nnodes(obj)
% NNODES Return the number of nodes in the tree.
% Equal to root + those whose parent is not root.
n = 1 + sum(obj.Parent(1:obj.num_nodes) ~= 0);
assert( obj.num_nodes == n);
end
function flag = isleaf(obj, ID)
% ISLEAF Return true if given ID matches a leaf node.
% A leaf node is a node that has no children.
flag = ~any( obj.Parent(1:obj.num_nodes) == ID );
end
function depth = depth(obj,ID)
% DEPTH return depth of tree under ID. If ID is not given, use
% root.
if nargin == 1
ID = 0;
end
if obj.isleaf(ID)
depth = 0;
else
children = obj.getchildren(ID);
NC = numel(children);
d = 0; % Depth from here on out
for k = 1:NC
d = max(d, obj.depth(children(k)));
end
depth = 1 + d;
end
end
end
end
但是,有时性能很慢,树上的操作占用了我大部分的计算时间。有哪些具体的方法可以提高实施效率?如果有性能提升,甚至可以将实现更改为 handle 继承类型以外的其他东西。
使用当前实现分析结果
因为向树中添加新节点是最典型的操作(以及更新节点的data),所以我在上面做了一些profiling。
我使用Nd=6, Ns=10 在以下基准代码上运行分析器。
function T = benchmark(Nd, Ns)
% Tree benchmark. Nd: tree depth, Ns: number of nodes per layer
% Initialize tree
T = stree(rand, 10000);
add_layers(1, Nd);
function add_layers(node_id, num_layers)
if num_layers == 0
return;
end
child_id = zeros(Ns,1);
for s = 1:Ns
% add child to current node
child_id(s) = T.addnode(node_id, rand);
% recursively increase depth under child_id(s)
add_layers(child_id(s), num_layers-1);
end
end
end
R2015b 性能
已发现 R2015b improves the performance of MATLAB's OOP features。我重新进行了上述基准测试,确实观察到了性能的提升:
所以这已经是个好消息了,尽管我们当然可以接受进一步的改进;)
以不同方式保留内存
cmets中也有人建议使用
obj.Node = [obj.Node; data; cell(obj.size_increment - 1,1)];
保留更多内存,而不是使用repmat 的当前方法。这略微提高了性能。我应该注意我的基准代码是针对虚拟数据的,因为实际的data 更复杂,这可能会有所帮助。谢谢!探查器结果如下:
关于进一步提高性能的问题
- 也许有另一种更有效的方式来维护树的内存?遗憾的是,我通常不会提前知道树中有多少个节点。
- 添加新节点和修改现有节点的
data是我在树上做的最典型的操作。截至目前,它们实际上占用了我主要应用程序的大部分处理时间。欢迎对这些功能进行任何改进。
最后一点,理想情况下,我希望将实现保持为纯 MATLAB。但是,可以接受 MEX 等选项或使用一些集成的 Java 功能。
【问题讨论】:
-
运行
profiler可以在性能方面在您的代码中阐明很多。运行一次,看看代码哪里特别慢,它会给你一个从哪里开始改进的指针。 -
Matlab OOP adds a significant overhead unless you use Matlab 2015b or newer 这可能会导致问题。不使用
handle可能无济于事。 -
@Adriaan 感谢您的建议。我添加了一些分析器数据。
-
另外,根据您的数据是什么,使用
repmat分配节点数据可能会增加很多开销。为什么不用obj.Node = [obj.Node; data; cell(obj.size_increment - 1,1)];初始化? -
当您修改数据时,您是如何访问节点的?更具体地说,为什么要保存节点的父节点而不是其子节点?从外观上看,使用单个查找表或结构来存储数据可能会更快。
标签: performance matlab data-structures tree