四叉树是用于二维空间对象查找的一个数据结构,本实现包括了三个类:QuadTree<T>,QuadTreeNode, QuadNodeItem。见名思意用于对外提供构建和查找功能的接口都定义在泛型的QuadTree中,QuadTreeNode类和QuadNodeItem类都做为QuadTree类的内部类型实现,对外不可见。QuadTree类应该对外提供的接口如下:
public class QuadTree<T> where T : class
{
public void Insert(T item, Rect bounds)
public void Remove(T item)
public IEnumerable<T> GetInsideItems(Rect bounds)
public IEnumerable<T> GetIntersectedItems(Rect bounds)
public void Restructure(Rect bounds)
public bool PredicateItemsCount(Rect bounds, int thresholdCount)
}
Insert方法用于向四叉树中插入节点。
Remove方法用于从四叉树中移除节点。
GetInsideItems方法用于从四叉树中检索指定矩形内的元素(元素的边界被指定矩形包含)。
GetIntersectedItems方法用于从四叉树中检索指定矩形内的元素(元素的边界跟指定矩形有交集)
Restructure方法用指定的包含所有元素的边界重新构建四叉树
PredicateItemsCount方法用于判断指定矩形的的元素个数是否是小于thresholdCount参数指定的值(与指定矩形相交即可算作矩形内的元素)。
四叉树包含一个根节点,每个节点又包含最多四个字节点(为了节约存储空间,该四叉树中不会存在既没有子节点也没有QuadNodeItem的QuadTreeNode)。每个节点都包含一个QuadNodeItem列表,该列表存储了所有被该节点表示的边界内的元素。QuadNodeItem对象存储了插入到四叉树中的对象和其边界,定义如下:
internal class QuadNodeItem
{
public QuadNodeItem(T item, Rect bounds)
{
Datum = item;
Bounds = bounds;
}
public T Datum { get; private set; }
public Rect Bounds { get; private set; }
}
四叉树的所有功能基本上都是在QuadTreeNode中实现的,QuadTree中的方法在QuadTreeNode中都有同名方法用于实现功能,不过需要有一点说明的是,在QuadTreeNode中的很多方法的参数都是用ref修饰,这是为了提高性能,因为作为实参传递结构对象,需要拷贝该结构,所以在很多次的方法调用中重用结构对象会对性能有很多的提升。
一般来说,四叉树都会禁止插入边界为0的元素,因为这样会造成四叉树无限膨胀,最后导致内存溢出。但在本四叉树实现中是允许插入边界为0的元素的,因为我在四叉树实现加入了深度限制,也就是说四叉树节点的深度不能超过指定的深度,否则将不再创建子节点。四叉树深度的默认值为255,这对于一般的应用来说应该足够了。
为了测试四叉树,我开发了一些本应该为private的变量。正式使用的时候应该将这些变量修改为private。测试同样通道了以前讲过的RectangleSelectionGesture 类,用来从UI中获取的一个选择区域。QuadTree的所有代码如下:
public class QuadTree<T> where T : class
{
private readonly int _MaxDepth;
internal readonly Dictionary<T, QuadTreeNode> _Table;
private QuadTreeNode _RootNode;
public Rect Bounds { get; private set; }
public QuadTree(Rect bounds, byte maxDepth)
{
if (maxDepth <= 0)
{
throw new ArgumentException("maxDepth", "maxDepth cannot be less than 1.");
}
Bounds = bounds;
_MaxDepth = maxDepth;
_Table = new Dictionary<T, QuadTreeNode>();
_RootNode = new QuadTreeNode(bounds);
}
public QuadTree(Rect bounds)
: this(bounds, 255)
{ }
public void Insert(T item, Rect bounds)
{
Insert(new QuadNodeItem(item, bounds));
}
public void Remove(T item)
{
QuadTreeNode node;
if (_Table.TryGetValue(item, out node))
{
node.RemoveItem(item);
_Table.Remove(item);
}
}
public IEnumerable<T> GetInsideItems(Rect bounds)
{
return _RootNode.GetInsideItems(ref bounds);
}
public IEnumerable<T> GetIntersectedItems(Rect bounds)
{
return _RootNode.GetIntersectedItems(ref bounds);
}
public void Restructure(Rect bounds)
{
Bounds = bounds;
_RootNode = new QuadTreeNode(bounds);
QuadTreeNode[] nodes = _Table.Values.ToArray();
foreach (QuadTreeNode node in nodes)
{
foreach (var nodeItem in node.Items)
{
Insert(nodeItem);
}
}
}
public bool PredicateItemsCount(Rect bounds, int thresholdCount)
{
if (thresholdCount <= 0)
{
throw new ArgumentException("thresholdCount", "thresholdCount cannot be less or equal than 0.");
}
int count = 0;
bool ret = _RootNode.PredicateItemsCount(ref bounds, thresholdCount, ref count);
return ret;
}
private void Insert(QuadNodeItem item)
{
if (!IsvalidBounds(item.Bounds))
{
throw new ArgumentException("bounds");
}
Rect bounds = item.Bounds;
QuadTreeNode node = _RootNode.Insert(item, ref bounds, 1, _MaxDepth);
_Table[item.Datum] = node;
}
private bool IsvalidBounds(Rect bounds)
{
return IsValidBound(bounds.X) && IsValidBound(bounds.Y)
&& IsValidBound(bounds.Width) && IsValidBound(bounds.Height);
}
private bool IsValidBound(double boundValue)
{
return !double.IsNaN(boundValue) && !double.IsInfinity(boundValue);
}
internal class QuadTreeNode
{
private List<QuadNodeItem> _Items = new List<QuadNodeItem>();
private Rect _Bounds;
private QuadTreeNode _TopLeftNode;
private QuadTreeNode _TopRightNode;
private QuadTreeNode _BottomLeftNode;
private QuadTreeNode _BottomRightNode;
public List<QuadNodeItem> Items
{
get { return _Items; }
}
public Rect Bounds
{
get { return _Bounds; }
}
public QuadTreeNode(Rect bounds)
{
_Bounds = bounds;
}
#region Insert
public QuadTreeNode Insert(QuadNodeItem item, ref Rect bounds, int depth, int maxDepth)
{
if (depth < maxDepth)
{
QuadTreeNode child = GetItemContainerNode(ref bounds);
if (child != null)
{
return child.Insert(item, ref bounds, depth + 1, maxDepth);
}
}
_Items.Add(item);
return this;
}
private QuadTreeNode GetItemContainerNode(ref Rect bounds)
{
double halfWidth = _Bounds.Width / 2;
double halfHeight = _Bounds.Height / 2;
QuadTreeNode child = null;
if (_TopLeftNode == null)
{
Rect topLeftRect = new Rect(_Bounds.X, _Bounds.Y, halfWidth, halfHeight);
if (topLeftRect.Contains(bounds))
{
_TopLeftNode = new QuadTreeNode(topLeftRect);
child = _TopLeftNode;
}
}
else if (_TopLeftNode._Bounds.Contains(bounds))
{
child = _TopLeftNode;
}
if (child == null)
{
if (_TopRightNode == null)
{
Rect topRightRect = new Rect(_Bounds.X + halfWidth, _Bounds.Y, halfWidth, halfHeight);
if (topRightRect.Contains(bounds))
{
_TopRightNode = new QuadTreeNode(topRightRect);
child = _TopRightNode;
}
}
else if (_TopRightNode._Bounds.Contains(bounds))
{
child = _TopRightNode;
}
}
if (child == null)
{
if (_BottomRightNode == null)
{
Rect bottomRightRect = new Rect(_Bounds.X + halfWidth, _Bounds.Y + halfHeight, halfWidth, halfHeight);
if (bottomRightRect.Contains(bounds))
{
_BottomRightNode = new QuadTreeNode(bottomRightRect);
child = _BottomRightNode;
}
}
else if (_BottomRightNode._Bounds.Contains(bounds))
{
child = _BottomRightNode;
}
}
if (child == null)
{
if (_BottomLeftNode == null)
{
Rect bottomLeftRect = new Rect(_Bounds.X, _Bounds.Y + halfHeight, halfWidth, halfHeight);
if (bottomLeftRect.Contains(bounds))
{
_BottomLeftNode = new QuadTreeNode(bottomLeftRect);
child = _BottomLeftNode;
}
}
else if (_BottomLeftNode._Bounds.Contains(bounds))
{
child = _BottomLeftNode;
}
}
return child;
}
#endregion
#region Get Inside Items
public IEnumerable<T> GetInsideItems(ref Rect bounds)
{
if (!bounds.IntersectsWith(_Bounds))
{
return Enumerable.Empty<T>();
}
List<T> containedNodes = new List<T>();
if (bounds.Contains(_Bounds))
{
GetItemWithoutCheck(containedNodes);
return containedNodes;
}
if (_TopLeftNode != null && _TopLeftNode._Bounds.IntersectsWith(bounds))
{
var items = _TopLeftNode.GetInsideItems(ref bounds);
containedNodes.AddRange(items);
}
if (_TopRightNode != null && _TopRightNode._Bounds.IntersectsWith(bounds))
{
var items = _TopRightNode.GetInsideItems(ref bounds);
containedNodes.AddRange(items);
}
if (_BottomRightNode != null && _BottomRightNode._Bounds.IntersectsWith(bounds))
{
var items = _BottomRightNode.GetInsideItems(ref bounds);
containedNodes.AddRange(items);
}
if (_BottomLeftNode != null && _BottomLeftNode._Bounds.IntersectsWith(bounds))
{
var items = _BottomLeftNode.GetInsideItems(ref bounds);
containedNodes.AddRange(items);
}
GetContainedItems(ref bounds, containedNodes);
return containedNodes;
}
private void GetContainedItems(ref Rect bounds, List<T> nodes)
{
foreach (QuadNodeItem item in _Items)
{
if (bounds.Contains(item.Bounds))
{
nodes.Add(item.Datum);
}
}
}
#endregion
#region Get Intersected Items
public IEnumerable<T> GetIntersectedItems(ref Rect bounds)
{
if (!bounds.IntersectsWith(_Bounds))
{
return Enumerable.Empty<T>();
}
List<T> intersectedNodes = new List<T>();
if (bounds.Contains(_Bounds))
{
GetItemWithoutCheck(intersectedNodes);
return intersectedNodes;
}
if (_TopLeftNode != null && _TopLeftNode._Bounds.IntersectsWith(bounds))
{
var items = _TopLeftNode.GetIntersectedItems(ref bounds);
intersectedNodes.AddRange(items);
}
if (_TopRightNode != null && _TopRightNode._Bounds.IntersectsWith(bounds))
{
var items = _TopRightNode.GetIntersectedItems(ref bounds);
intersectedNodes.AddRange(items);
}
if (_BottomRightNode != null && _BottomRightNode._Bounds.IntersectsWith(bounds))
{
var items = _BottomRightNode.GetIntersectedItems(ref bounds);
intersectedNodes.AddRange(items);
}
if (_BottomLeftNode != null && _BottomLeftNode._Bounds.IntersectsWith(bounds))
{
var items = _BottomLeftNode.GetIntersectedItems(ref bounds);
intersectedNodes.AddRange(items);
}
GetIntersectedItems(ref bounds, intersectedNodes);
return intersectedNodes;
}
private void GetIntersectedItems(ref Rect bounds, List<T> nodes)
{
foreach (QuadNodeItem item in _Items)
{
if (bounds.IntersectsWith(item.Bounds))
{
nodes.Add(item.Datum);
}
}
}
#endregion
public void RemoveItem(T item)
{
int itemIndex = _Items.FindIndex(nodeItem => nodeItem.Datum == item);
_Items.RemoveAt(itemIndex);
}
#region Predicate Item Count
public bool PredicateItemsCount(ref Rect bounds, int thresholdCount, ref int count)
{
if (!bounds.IntersectsWith(_Bounds))
{
return true;
}
if (bounds.Contains(_Bounds))
{
return PredicateItemsCountWithoutCheck(thresholdCount, ref count);
}
if (!PredicateIntersectedItemsCount(ref bounds, thresholdCount, ref count))
{
return false;
}
if (_TopLeftNode != null && _TopLeftNode._Bounds.IntersectsWith(bounds))
{
if (!_TopLeftNode.PredicateItemsCount(ref bounds, thresholdCount, ref count))
{
return false;
}
}
if (_TopRightNode != null && _TopRightNode._Bounds.IntersectsWith(bounds))
{
if (!_TopRightNode.PredicateItemsCount(ref bounds, thresholdCount, ref count))
{
return false;
}
}
if (_BottomRightNode != null && _BottomRightNode._Bounds.IntersectsWith(bounds))
{
if (!_BottomRightNode.PredicateItemsCount(ref bounds, thresholdCount, ref count))
{
return false;
}
}
if (_BottomLeftNode != null && _BottomLeftNode._Bounds.IntersectsWith(bounds))
{
if (!_BottomLeftNode.PredicateItemsCount(ref bounds, thresholdCount, ref count))
{
return false;
}
}
return true;
}
private bool PredicateItemsCountWithoutCheck(int thresholdCount, ref int count)
{
count += _Items.Count;
if (count > thresholdCount)
{
return false;
}
if (_TopLeftNode != null)
{
if (!_TopLeftNode.PredicateItemsCountWithoutCheck(thresholdCount, ref count))
{
return false;
}
}
if (_TopRightNode != null)
{
if (!_TopRightNode.PredicateItemsCountWithoutCheck(thresholdCount, ref count))
{
return false;
}
}
if (_BottomLeftNode != null)
{
if (!_BottomLeftNode.PredicateItemsCountWithoutCheck(thresholdCount, ref count))
{
return false;
}
}
if (_BottomRightNode != null)
{
if (!_BottomRightNode.PredicateItemsCountWithoutCheck(thresholdCount, ref count))
{
return false;
}
}
return true;
}
private bool PredicateIntersectedItemsCount(ref Rect bounds, int thresholdCount, ref int count)
{
foreach (QuadNodeItem item in _Items)
{
if (bounds.IntersectsWith(item.Bounds))
{
if (++count > thresholdCount)
{
return false;
}
}
}
return true;
}
#endregion
private void GetItemWithoutCheck(List<T> nodes)
{
nodes.AddRange(_Items.Select(item => item.Datum));
if (_TopLeftNode != null)
{
_TopLeftNode.GetItemWithoutCheck(nodes);
}
if (_TopRightNode != null)
{
_TopRightNode.GetItemWithoutCheck(nodes);
}
if (_BottomLeftNode != null)
{
_BottomLeftNode.GetItemWithoutCheck(nodes);
}
if (_BottomRightNode != null)
{
_BottomRightNode.GetItemWithoutCheck(nodes);
}
}
}
internal class QuadNodeItem
{
public QuadNodeItem(T item, Rect bounds)
{
Datum = item;
Bounds = bounds;
}
public T Datum { get; private set; }
public Rect Bounds { get; private set; }
}
}