分享

数据结构-四叉树

 TT_TYG 2015-03-03

这是一个四叉树的数据结构,在UI虚拟化中有优化性能的作用。
public class QuadTree<T> where T : class
    {
        Rect _bounds; // overall bounds we are indexing.
        Quadrant _root;
        IDictionary<T, Quadrant> _table;

        /// <summary>
        /// Each node stored in the tree has a position, width & height.
        /// </summary>
        internal class QuadNode
        {
            Rect _bounds;
            QuadNode _next; // linked in a circular list.
            T _node; // the actual visual object being stored here.

            /// <summary>
            /// Construct new QuadNode to wrap the given node with given bounds
            /// </summary>
            /// <param name="node">The node</param>
            /// <param name="bounds">The bounds of that node</param>
            public QuadNode(T node, Rect bounds)
            {
                _node = node;
                _bounds = bounds;
            }

            /// <summary>
            /// The node
            /// </summary>
            public T Node
            {
                get { return _node; }
                set { _node = value; }
            }

            /// <summary>
            /// The Rect bounds of the node
            /// </summary>
            public Rect Bounds
            {
                get { return _bounds; }
            }

            /// <summary>
            /// QuadNodes form a linked list in the Quadrant.
            /// </summary>
            public QuadNode Next
            {
                get { return _next; }
                set { _next = value; }
            }
        }


        /// <summary>
        /// The canvas is split up into four Quadrants and objects are stored in the quadrant that contains them
        /// and each quadrant is split up into four child Quadrants recurrsively.  Objects that overlap more than
        /// one quadrant are stored in the _nodes list for this Quadrant.
        /// </summary>
        internal class Quadrant
        {
            Quadrant _parent;
            Rect _bounds; // quadrant bounds.

            QuadNode _nodes; // nodes that overlap the sub quadrant boundaries.

            // The quadrant is subdivided when nodes are inserted that are 
            // completely contained within those subdivisions.
            Quadrant _topLeft;
            Quadrant _topRight;
            Quadrant _bottomLeft;
            Quadrant _bottomRight;

#if DEBUG_DUMP
            public void ShowQuadTree(Canvas c)
            {
                Rectangle r = new Rectangle();
                r.Width = _bounds.Width;
                r.Height = _bounds.Height;
                Canvas.SetLeft(r, _bounds.Left);
                Canvas.SetTop(r, _bounds.Top);
                r.Stroke = Brushes.DarkRed;
                r.StrokeThickness = 1;
                r.StrokeDashArray = new DoubleCollection(new double[] { 2.0, 3.0 });
                c.Children.Add(r);

                if (_topLeft != null) _topLeft.ShowQuadTree(c);
                if (_topRight != null) _topRight.ShowQuadTree(c);
                if (_bottomLeft != null) _bottomLeft.ShowQuadTree(c);
                if (_bottomRight != null) _bottomRight.ShowQuadTree(c);
            }

            public void Dump(LogWriter w)
            {
                w.WriteAttribute("Bounds", _bounds.ToString());
                if (_nodes != null)
                {
                    QuadNode n = _nodes;
                    do
                    {
                        n = n.Next; // first node.
                        w.Open("node");
                        w.WriteAttribute("Bounds", n.Bounds.ToString());
                        w.Close();
                    } while (n != _nodes);
                }
                DumpQuadrant("TopLeft", _topLeft, w);
                DumpQuadrant("TopRight", _topRight, w);
                DumpQuadrant("BottomLeft", _bottomLeft, w);
                DumpQuadrant("BottomRight", _bottomRight, w);
            }

            public void DumpQuadrant(string label, Quadrant q, LogWriter w)
            {
                if (q != null)
                {
                    w.Open("Quadrant");
                    w.WriteAttribute("Name", label);
                    q.Dump(w);
                    w.Close();
                }
            }
#endif

            /// <summary>
            /// Construct new Quadrant with a given bounds all nodes stored inside this quadrant
            /// will fit inside this bounds.  
            /// </summary>
            /// <param name="parent">The parent quadrant (if any)</param>
            /// <param name="bounds">The bounds of this quadrant</param>
            public Quadrant(Quadrant parent, Rect bounds)
            {
                _parent = parent;
                Debug.Assert(bounds.Width != 0 && bounds.Height != 0);
                if (bounds.Width == 0 || bounds.Height == 0)                
                {
                    // todo: localize
                    throw new ArgumentException("Bounds of quadrant cannot be zero width or height");
                }
                _bounds = bounds;
            }

            /// <summary>
            /// The parent Quadrant or null if this is the root
            /// </summary>
            internal Quadrant Parent
            {
                get { return _parent; }
            }

            /// <summary>
            /// The bounds of this quadrant
            /// </summary>
            internal Rect Bounds 
            { 
                get { return _bounds; } 
            }

            /// <summary>
            /// Insert the given node
            /// </summary>
            /// <param name="node">The node </param>
            /// <param name="bounds">The bounds of that node</param>
            /// <returns></returns>
            internal Quadrant Insert(T node, Rect bounds)
            {
                Debug.Assert(bounds.Width != 0 && bounds.Height != 0);
                if (bounds.Width == 0 || bounds.Height == 0)
                {
                    // todo: localize
                    throw new ArgumentException("Bounds of quadrant cannot be zero width or height");
                }

                double w = _bounds.Width / 2;
                if (w == 0)
                {
                    w = 1;
                }
                double h = _bounds.Height / 2;
                if (h == 0)
                {
                    h = 1;
                }

                // assumption that the Rect struct is almost as fast as doing the operations
                // manually since Rect is a value type.

                Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h);
                Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h);
                Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h);
                Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h);

                Quadrant child = null;

                // See if any child quadrants completely contain this node.
                if (topLeft.Contains(bounds))
                {
                    if ( _topLeft == null)
                    {
                        _topLeft = new Quadrant(this, topLeft);
                    }
                    child = _topLeft;
                }
                else if (topRight.Contains(bounds))
                {
                    if ( _topRight == null)
                    {
                        _topRight = new Quadrant(this, topRight);
                    }
                    child = _topRight;
                }
                else if (bottomLeft.Contains(bounds))
                {
                    if ( _bottomLeft == null)
                    {
                        _bottomLeft = new Quadrant(this, bottomLeft);
                    }
                    child = _bottomLeft;
                }
                else if (bottomRight.Contains(bounds))
                {
                    if ( _bottomRight == null)
                    {
                        _bottomRight = new Quadrant(this, bottomRight);
                    }
                    child = _bottomRight;
                }

                if (child != null)
                {
                    return child.Insert(node, bounds);
                }
                else
                {
                    QuadNode n = new QuadNode(node, bounds);
                    if (_nodes == null)
                    {
                        n.Next = n;
                    }
                    else
                    {
                        // link up in circular link list.
                        QuadNode x = _nodes;
                        n.Next = x.Next;
                        x.Next = n;
                    }
                    _nodes = n;
                    return this;
                }
            }
            
            /// <summary>
            /// Returns all nodes in this quadrant that intersect the given bounds.
            /// The nodes are returned in pretty much random order as far as the caller is concerned.
            /// </summary>
            /// <param name="nodes">List of nodes found in the given bounds</param>
            /// <param name="bounds">The bounds that contains the nodes you want returned</param>
            internal void GetIntersectingNodes(List<QuadNode> nodes, Rect bounds)
            {
                if (bounds.IsEmpty) return;
                double w = _bounds.Width / 2;
                double h = _bounds.Height / 2;

                // assumption that the Rect struct is almost as fast as doing the operations
                // manually since Rect is a value type.

                Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h);
                Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h);
                Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h);
                Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h);

                // See if any child quadrants completely contain this node.
                if (topLeft.IntersectsWith(bounds) && _topLeft != null)
                {
                    _topLeft.GetIntersectingNodes(nodes, bounds);
                }

                if (topRight.IntersectsWith(bounds) && _topRight != null)
                {
                    _topRight.GetIntersectingNodes(nodes, bounds);
                }

                if (bottomLeft.IntersectsWith(bounds) && _bottomLeft != null)
                {
                    _bottomLeft.GetIntersectingNodes(nodes, bounds);
                }

                if (bottomRight.IntersectsWith(bounds) && _bottomRight != null)
                {
                    _bottomRight.GetIntersectingNodes(nodes, bounds);
                }

                GetIntersectingNodes(_nodes, nodes, bounds);
            }

            /// <summary>
            /// Walk the given linked list of QuadNodes and check them against the given bounds.
            /// Add all nodes that intersect the bounds in to the list.
            /// </summary>
            /// <param name="last">The last QuadNode in a circularly linked list</param>
            /// <param name="nodes">The resulting nodes are added to this list</param>
            /// <param name="bounds">The bounds to test against each node</param>
            static void GetIntersectingNodes(QuadNode last, List<QuadNode> nodes, Rect bounds)
            {                
                if (last != null)
                {                    
                    QuadNode n = last;
                    do
                    {
                        n = n.Next; // first node.
                        if (n.Bounds.IntersectsWith(bounds))
                        {
                            nodes.Add(n);
                        }
                    } while (n != last);                    
                }
            }

            /// <summary>
            /// Return true if there are any nodes in this Quadrant that intersect the given bounds.
            /// </summary>
            /// <param name="bounds">The bounds to test</param>
            /// <returns>boolean</returns>
            internal bool HasIntersectingNodes(Rect bounds)
            {
                if (bounds.IsEmpty) return false;
                double w = _bounds.Width / 2;
                double h = _bounds.Height / 2;

                // assumption that the Rect struct is almost as fast as doing the operations
                // manually since Rect is a value type.

                Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h);
                Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h);
                Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h);
                Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h);

                bool found = false;

                // See if any child quadrants completely contain this node.
                if (topLeft.IntersectsWith(bounds) && _topLeft != null)
                {
                    found = _topLeft.HasIntersectingNodes(bounds);
                }

                if (!found && topRight.IntersectsWith(bounds) && _topRight != null)
                {
                    found = _topRight.HasIntersectingNodes(bounds);
                }

                if (!found && bottomLeft.IntersectsWith(bounds) && _bottomLeft != null)
                {
                    found = _bottomLeft.HasIntersectingNodes(bounds);
                }

                if (!found && bottomRight.IntersectsWith(bounds) && _bottomRight != null)
                {
                    found = _bottomRight.HasIntersectingNodes(bounds);
                }
                if (!found)
                {
                    found = HasIntersectingNodes(_nodes, bounds);
                }
                return found;
            }

            /// <summary>
            /// Walk the given linked list and test each node against the given bounds/
            /// </summary>
            /// <param name="last">The last node in the circularly linked list.</param>
            /// <param name="bounds">Bounds to test</param>
            /// <returns>Return true if a node in the list intersects the bounds</returns>
            static bool HasIntersectingNodes(QuadNode last, Rect bounds)
            {
                if (last != null)
                {
                    QuadNode n = last;
                    do
                    {
                        n = n.Next; // first node.
                        if (n.Bounds.IntersectsWith(bounds))
                        {
                            return true;
                        }
                    } while (n != last);
                }
                return false;
            }

            /// <summary>
            /// Remove the given node from this Quadrant.
            /// </summary>
            /// <param name="node">The node to remove</param>
            /// <returns>Returns true if the node was found and removed.</returns>
            internal bool RemoveNode(T node)
            {
                bool rc = false;
                if (_nodes != null)
                {
                    QuadNode p = _nodes;
                    while (p.Next.Node != node && p.Next != _nodes)
                    {
                        p = p.Next;
                    }
                    if (p.Next.Node == node)
                    {
                        rc = true;
                        QuadNode n = p.Next;
                        if (p == n)
                        {
                            // list goes to empty
                            _nodes = null;
                        }
                        else
                        {
                            if (_nodes == n) _nodes = p;
                            p.Next = n.Next;
                        }
                    }
                }
                return rc;
            }

        }

        /// <summary>
        /// This determines the overall quad-tree indexing strategy, changing this bounds
        /// is expensive since it has to re-divide the entire thing - like a re-hash operation.
        /// </summary>
        public Rect Bounds
        {
            get { return _bounds; }
            set { _bounds = value; ReIndex();  }
        }

        /// <summary>
        /// Insert a node with given bounds into this QuadTree.
        /// </summary>
        /// <param name="node">The node to insert</param>
        /// <param name="bounds">The bounds of this node</param>
        public void Insert(T node, Rect bounds)
        {
            if (_bounds.Width == 0 || _bounds.Height == 0)
            {
                // todo: localize.
                throw new InvalidOperationException("You must set a non-zero bounds on the QuadTree first");
            }
            if (bounds.Width == 0 || bounds.Height == 0)
            {
                // todo: localize.
                throw new InvalidOperationException("Inserted node must have a non-zero width and height");
            } 
            if (_root == null)
            {
                _root = new Quadrant(null, _bounds);
            }

            Quadrant parent = _root.Insert(node, bounds);

            if (_table == null)
            {
                _table = new Dictionary<T, Quadrant>();
            }
            _table[node] = parent;


        }

        /// <summary>
        /// Get a list of the nodes that intersect the given bounds.
        /// </summary>
        /// <param name="bounds">The bounds to test</param>
        /// <returns>List of zero or mode nodes found inside the given bounds</returns>
        public IEnumerable<T> GetNodesInside(Rect bounds)
        {
            foreach (QuadNode n in GetNodes(bounds))
            {
                yield return n.Node;
            }
        }

        /// <summary>
        /// Get a list of the nodes that intersect the given bounds.
        /// </summary>
        /// <param name="bounds">The bounds to test</param>
        /// <returns>List of zero or mode nodes found inside the given bounds</returns>
        public bool HasNodesInside(Rect bounds)
        {
            if (_root != null)
            {
                _root.HasIntersectingNodes(bounds);
            }
            return false;
        }

        /// <summary>
        /// Get list of nodes that intersect the given bounds.
        /// </summary>
        /// <param name="bounds">The bounds to test</param>
        /// <returns>The list of nodes intersecting the given bounds</returns>
        IEnumerable<QuadNode> GetNodes(Rect bounds)
        {
            List<QuadNode> result = new List<QuadNode>();
            if (_root != null)
            {
                _root.GetIntersectingNodes(result, bounds);
            }
            return result;
        }

        /// <summary>
        /// Remove the given node from this QuadTree.
        /// </summary>
        /// <param name="node">The node to remove</param>
        /// <returns>True if the node was found and removed.</returns>
        public bool Remove(T node)
        {
            if (_table != null)
            {
                Quadrant parent = null;
                if (_table.TryGetValue(node, out parent))
                {
                    parent.RemoveNode(node);
                    _table.Remove(node);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Rebuild all the Quadrants according to the current QuadTree Bounds.
        /// </summary>
       public void ReIndex()
        {
            _root = null;
            foreach (QuadNode n in GetNodes(_bounds))
            {
                // todo: it would be more efficient if we added a code path that allowed
                // reuse of the QuadNode wrappers.
                Insert(n.Node, n.Bounds);
            }
        }
#if DEBUG_DUMP
        public void ShowQuadTree(Canvas container)
        {
            if (_root != null)
            {
                _root.ShowQuadTree(container);
            }
        }

        public void Dump(LogWriter w)
        {
            if (_root != null)
            {
                _root.Dump(w);
            }
        }
#endif
    }

#if DEBUG_DUMP
    public class LogWriter : IDisposable
    {
        XmlWriter _xw;
        int _indent;
        int _maxdepth;

        public LogWriter(TextWriter w)
        {
            XmlWriterSettings s = new XmlWriterSettings();
            s.Indent = true;            
            _xw = XmlWriter.Create(w, s);
        }

        public int MaxDepth
        {
            get
            {
                return _maxdepth;
            }
        }

        public void Open(string label)
        {
            _xw.WriteStartElement(label);
            _indent++;
            if (_indent > _maxdepth) _maxdepth = _indent;

        }
        public void Close()
        {
            _indent--;
            _xw.WriteEndElement();
        }
        public void WriteAttribute(string name, string value)
        {
            _xw.WriteAttributeString(name, value);
        }

    #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && _xw != null)
            {
                using (_xw)
                {
                    _xw.Flush();
                }
                _xw = null;
            }
        }

        #endregion
    }
#endif

}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约