Cách hiệu quả nhất để tạo ra tất cả các hậu duệ của tất cả các nút trong cây


9

Tôi đang tìm kiếm thuật toán hiệu quả nhất để lấy một cây (được lưu dưới dạng danh sách các cạnh; HOẶC làm danh sách ánh xạ từ nút cha sang danh sách các nút con); và tạo ra, cho MỌI nút, một danh sách tất cả các nút xuất phát từ nó (cấp độ lá và cấp độ không lá).

Việc thực hiện phải thông qua các vòng lặp thay vì thu hồi, do quy mô; và lý tưởng nhất là O (N).

Câu hỏi SO này bao gồm một giải pháp hợp lý rõ ràng tiêu chuẩn để tìm câu trả lời cho MỘT nút trong cây. Nhưng rõ ràng, việc lặp lại thuật toán đó trên mỗi nút cây rất kém hiệu quả (từ đỉnh đầu của tôi, O (NlogN) đến O (N ^ 2)).

Rễ cây được biết đến. Cây có hình dạng hoàn toàn tùy ý (ví dụ không phải N-nary, không cân đối về bất kỳ cách nào, hình dạng hoặc hình thức, không có độ sâu đồng đều) - một số nút có 1-2 con, một số có 30K con.

Ở mức độ thực tế (mặc dù nó không ảnh hưởng đến thuật toán), cây có các nút ~ 100K-200K.


Bạn có thể mô phỏng đệ quy bằng cách sử dụng vòng lặp và ngăn xếp, điều này có được phép cho giải pháp của bạn không?
Giorgio

@Giorgio - tất nhiên rồi. Đó là những gì tôi đã cố gắng ám chỉ bằng cách "thông qua các vòng lặp thay vì hồi tưởng".
DVK

Câu trả lời:


5

Nếu bạn thực sự muốn SẢN XUẤT mọi danh sách dưới dạng các bản sao khác nhau, bạn không thể hy vọng đạt được khoảng trống hơn 2 ^ trong trường hợp xấu nhất. Nếu bạn chỉ cần TIẾP CẬN vào từng danh sách:

Tôi sẽ thực hiện một giao dịch theo thứ tự của cây bắt đầu từ gốc:

http://en.wikipedia.org/wiki/Tree_traversal

Sau đó, đối với mỗi nút trong cây lưu trữ số thứ tự tối thiểu và số thứ tự tối đa trong cây con của nó (điều này dễ dàng được duy trì thông qua đệ quy - và bạn có thể mô phỏng số đó với một ngăn xếp nếu bạn muốn).

Bây giờ bạn đặt tất cả các nút trong một mảng A có độ dài n trong đó nút có số thứ tự i ở vị trí i. Sau đó, khi bạn cần tìm danh sách cho nút X, bạn tìm trong A [X.min, X.max] - lưu ý rằng khoảng này sẽ bao gồm nút X, cũng có thể dễ dàng sửa.

Tất cả điều này được thực hiện trong thời gian O (n) và chiếm không gian O (n).

Tôi hi vọng cái này giúp được.


2

Phần không hiệu quả không phải là đi ngang qua cây, mà là xây dựng danh sách các nút. Có vẻ hợp lý để tạo danh sách như thế này:

descendants[node] = []
for child in node.childs:
    descendants[node].push(child)
    for d in descendants[child]:
        descendants[node].push(d)

Vì mỗi nút con cháu được sao chép vào danh sách của mỗi cha mẹ, chúng tôi kết thúc với độ phức tạp O (n log n) trung bình cho các cây cân bằng và trường hợp xấu nhất đối với các cây thoái hóa là danh sách thực sự được liên kết.

Chúng tôi có thể giảm xuống O (n) hoặc O (1) tùy thuộc vào việc bạn có cần thực hiện bất kỳ thiết lập nào hay không nếu chúng tôi sử dụng thủ thuật tính toán danh sách một cách lười biếng. Giả sử chúng ta có một child_iterator(node)cái cho chúng ta con của nút đó. Sau đó, chúng ta có thể định nghĩa một cách tầm thường descendant_iterator(node)như thế này:

def descendant_iterator(node):
  for child in child_iterator(node):
    yield from descendant_iterator(child)
  yield node

Một giải pháp không đệ quy có liên quan nhiều hơn, vì luồng điều khiển lặp là khó khăn (coroutines!). Tôi sẽ cập nhật câu trả lời này sau hôm nay.

Vì truyền tải của cây là O (n) và lặp đi lặp lại trong danh sách là tuyến tính, nên thủ thuật này hoàn toàn trì hoãn chi phí cho đến khi nó được trả bằng mọi giá. Ví dụ, in ra danh sách con cháu cho mỗi nút có độ phức tạp trường hợp xấu nhất O (n²): Lặp lại trên tất cả các nút là O (n) và lặp đi lặp lại trên mỗi con cháu của nút, cho dù chúng được lưu trữ trong danh sách hay tính ad hoc .

Tất nhiên, điều này sẽ không hoạt động nếu bạn cần một bộ sưu tập thực tế để làm việc.


Xin lỗi, -1. Toàn bộ mục đích của aglorithm là tính toán trước dữ liệu. Lười tính toán là hoàn toàn đánh bại lý do thậm chí chạy algo.
DVK

2
@DVK Ok, tôi có thể đã hiểu nhầm yêu cầu của bạn. Bạn đang làm gì với danh sách kết quả? Nếu tính toán trước danh sách là một nút cổ chai (nhưng không sử dụng danh sách), điều này cho thấy bạn không sử dụng tất cả dữ liệu bạn tổng hợp và tính toán lười biếng sẽ là một chiến thắng. Nhưng nếu bạn sử dụng tất cả dữ liệu, thuật toán để tính toán trước phần lớn không liên quan - độ phức tạp thuật toán của việc sử dụng dữ liệu ít nhất sẽ bằng độ phức tạp của việc xây dựng danh sách.
amon

0

Thuật toán ngắn này nên làm điều đó, Hãy xem mã public void TestTreeNodeChildrenListing()

Thuật toán thực sự đi qua các nút của cây theo trình tự và giữ danh sách cha mẹ của nút hiện tại. Theo nút yêu cầu hiện tại của bạn là một con của mỗi nút cha, nó được thêm vào mỗi nút trong số chúng khi còn nhỏ.

Kết quả cuối cùng được lưu trữ trong từ điển.

    [TestFixture]
    public class TreeNodeChildrenListing
    {
        private TreeNode _root;

        [SetUp]
        public void SetUp()
        {
            _root = new TreeNode("root");
            int rootCount = 0;
            for (int i = 0; i < 2; i++)
            {
                int iCount = 0;
                var iNode = new TreeNode("i:" + i);
                _root.Children.Add(iNode);
                rootCount++;
                for (int j = 0; j < 2; j++)
                {
                    int jCount = 0;
                    var jNode = new TreeNode(iNode.Value + "_j:" + j);
                    iCount++;
                    rootCount++;
                    iNode.Children.Add(jNode);
                    for (int k = 0; k < 2; k++)
                    {
                        var kNode = new TreeNode(jNode.Value + "_k:" + k);
                        jNode.Children.Add(kNode);
                        iCount++;
                        rootCount++;
                        jCount++;

                    }
                    jNode.Value += " ChildCount:" + jCount;
                }
                iNode.Value += " ChildCount:" + iCount;
            }
            _root.Value += " ChildCount:" + rootCount;
        }

        [Test]
        public void TestTreeNodeChildrenListing()
        {
            var iteration = new Stack<TreeNode>();
            var parents = new List<TreeNode>();
            var dic = new Dictionary<TreeNode, IList<TreeNode>>();

            TreeNode node = _root;
            while (node != null)
            {
                if (node.Children.Count > 0)
                {
                    if (!dic.ContainsKey(node))
                        dic.Add(node,new List<TreeNode>());

                    parents.Add(node);
                    foreach (var child in node.Children)
                    {
                        foreach (var parent in parents)
                        {
                            dic[parent].Add(child);
                        }
                        iteration.Push(child);
                    }
                }

                if (iteration.Count > 0)
                    node = iteration.Pop();
                else
                    node = null;

                bool removeParents = true;
                while (removeParents)
                {
                    var lastParent = parents[parents.Count - 1];
                    if (!lastParent.Children.Contains(node)
                        && node != _root && lastParent != _root)
                    {
                        parents.Remove(lastParent);
                    }
                    else
                    {
                        removeParents = false;
                    }
                }
            }
        }
    }

    internal class TreeNode
    {
        private IList<TreeNode> _children;
        public string Value { get; set; }

        public TreeNode(string value)
        {
            _children = new List<TreeNode>();
            Value = value;
        }

        public IList<TreeNode> Children
        {
            get { return _children; }
        }
    }
}

Đối với tôi, điều này trông rất giống độ phức tạp của O (n log n) đến O (n²) và nó chỉ cải thiện một chút so với câu trả lời mà DVK liên quan đến trong câu hỏi của họ. Vì vậy, nếu điều này là không cải thiện, làm thế nào điều này trả lời câu hỏi? Giá trị duy nhất mà câu trả lời này thêm vào là hiển thị một biểu thức lặp của thuật toán ngây thơ.
amon

Đó là O (n), nếu bạn nhìn kỹ thuật toán, nó lặp lại một lần qua các nút. Đồng thời nó tạo ra tập hợp các nút con cho mỗi nút cha cùng một lúc.
Chim bồ câu bay thấp

1
Bạn lặp qua tất cả các nút, đó là O (n). Sau đó, bạn lặp qua tất cả các trẻ em, mà bây giờ chúng ta sẽ bỏ qua (hãy tưởng tượng đó là một số yếu tố không đổi). Sau đó, bạn lặp qua tất cả các cha mẹ của nút hiện tại. Trong cây cân bằng, đây là O (log n), nhưng trong trường hợp suy biến trong đó cây của chúng ta là một danh sách liên kết, nó có thể là O (n). Vì vậy, nếu chúng ta nhân chi phí của vòng lặp qua tất cả các nút với chi phí lặp qua cha mẹ của chúng, chúng ta sẽ có độ phức tạp thời gian O (n log n) đến O (n²). Nếu không có đa luồng, thì không có bản nào cùng lúc.
amon

"cùng một lúc" có nghĩa là nó tạo ra bộ sưu tập trong cùng một vòng lặp và không có vòng lặp nào khác liên quan.
Chim bồ câu bay thấp

0

Thông thường, bạn sẽ chỉ sử dụng một cách tiếp cận đệ quy, bởi vì nó cho phép bạn chuyển đổi thứ tự thực hiện xung quanh để bạn có thể tính toán số lượng lá bắt đầu từ các lá trở lên. Vì, bạn sẽ phải sử dụng kết quả của cuộc gọi đệ quy để cập nhật nút hiện tại, nên sẽ cần một nỗ lực đặc biệt để có được phiên bản đệ quy đuôi. Nếu bạn không nỗ lực, thì dĩ nhiên, cách tiếp cận này chỉ đơn giản là làm nổ tung ngăn xếp của bạn cho một cái cây lớn.

Cho rằng chúng tôi nhận ra ý tưởng chính là để có được một thứ tự vòng lặp bắt đầu từ những chiếc lá và quay trở lại gốc, ý tưởng tự nhiên xuất hiện trong đầu là thực hiện một loại cấu trúc liên kết trên cây. Chuỗi kết quả của các nút có thể được duyệt theo tuyến tính để tính tổng số lượng lá (giả sử bạn có thể xác minh một nút là một lá trong O(1)). Độ phức tạp thời gian tổng thể của loại cấu trúc liên kết là O(|V|+|E|).

Tôi giả sử rằng Nsố lượng nút của bạn là |V|thông thường (từ danh pháp DAG). Mặt khác, kích thước của nó Ephụ thuộc rất nhiều vào độ thơm của cây. Ví dụ, một cây nhị phân có nhiều nhất 2 cạnh trên mỗi nút, do đó, O(|E|) = O(2*|V|) = O(|V|)trong trường hợp đó, điều đó sẽ dẫn đến một O(|V|)thuật toán tổng thể . Lưu ý rằng do cấu trúc tổng thể của cây, bạn không thể có một cái gì đó như thế O(|E|) = O(|V|^2). Trong thực tế, vì mỗi nút có một cha mẹ duy nhất, bạn có thể có tối đa một cạnh để đếm trên mỗi nút khi bạn chỉ xem xét quan hệ cha mẹ, vì vậy đối với cây, chúng tôi đảm bảo rằng O(|E|) = O(|V|). Do đó, thuật toán trên luôn luôn tuyến tính trong kích thước của cây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.