Làm thế nào để xác định xem cây nhị phân là cân bằng?


113

Nó đã được một thời gian từ những năm học. Có một công việc là chuyên gia CNTT tại một bệnh viện. Đang cố gắng chuyển sang làm một số lập trình thực tế ngay bây giờ. Bây giờ tôi đang làm việc trên cây nhị phân và tôi đã tự hỏi đâu sẽ là cách tốt nhất để xác định xem cây có cân bằng chiều cao hay không.

Tôi đã nghĩ về điều gì đó dọc theo điều này:

public boolean isBalanced(Node root){
    if(root==null){
        return true;  //tree is empty
    }
    else{
        int lh = root.left.height();
        int rh = root.right.height();
        if(lh - rh > 1 || rh - lh > 1){
            return false;
        }
    }
    return true;
}

Đây có phải là một triển khai tốt? hoặc tôi đang thiếu một cái gì đó?


Nếu bạn muốn xem cây nhị phân ascii của Donal Fellows với đồ họa: i.imgur.com/97C27Ek.png
user7643681 Ngày

1
Câu trả lời hay, đã giúp tôi vào được Mỹ. (đùa)
Henry

Câu trả lời:


165

Tình cờ gặp câu hỏi cũ này khi đang tìm kiếm thứ khác. Tôi nhận thấy rằng bạn không bao giờ nhận được một câu trả lời đầy đủ.

Cách giải quyết vấn đề này là bắt đầu bằng cách viết một đặc tả cho hàm bạn đang cố gắng viết.

Đặc điểm kỹ thuật: Một cây nhị phân hình thành tốt được cho là "cân bằng chiều cao" nếu (1) nó trống hoặc (2) con trái và phải của nó cân bằng chiều cao và chiều cao của cây bên trái nằm trong khoảng 1 chiều cao của cây bên phải.

Bây giờ bạn đã có thông số kỹ thuật, viết mã rất đơn giản. Chỉ cần làm theo đặc điểm kỹ thuật:

IsHeightBalanced(tree)
    return (tree is empty) or 
           (IsHeightBalanced(tree.left) and
            IsHeightBalanced(tree.right) and
            abs(Height(tree.left) - Height(tree.right)) <= 1)

Việc dịch ngôn ngữ đó sang ngôn ngữ lập trình mà bạn lựa chọn sẽ rất đơn giản.

Bài tập bổ sung : bản phác thảo mã ngây thơ này đi qua cây quá nhiều lần khi tính toán độ cao. Bạn có thể làm cho nó hiệu quả hơn không?

Bài tập siêu thưởng : giả sử cây mất cân bằng một cách ồ ạt . Giống như, một triệu nút sâu ở một bên và sâu ba nút ở bên kia. Có kịch bản nào trong đó thuật toán này thổi bay ngăn xếp không? Bạn có thể sửa cách triển khai để nó không bao giờ thổi chồng lên nhau, ngay cả khi đưa ra một cây không cân bằng lớn không?

CẬP NHẬT : Nghiên cứu sinh Donal chỉ ra trong câu trả lời của mình rằng có nhiều định nghĩa khác nhau về 'cân bằng' mà người ta có thể chọn. Ví dụ: người ta có thể đưa ra một định nghĩa chặt chẽ hơn về "chiều cao cân bằng" và yêu cầu độ dài đường dẫn tới con trống gần nhất nằm trong một trong những đường dẫn đến con trống xa nhất . Định nghĩa của tôi ít nghiêm ngặt hơn thế, và do đó thừa nhận nhiều cây hơn.

Người ta cũng có thể ít nghiêm ngặt hơn định nghĩa của tôi; người ta có thể nói rằng cây cân bằng là cây trong đó độ dài đường dẫn tối đa đến cây trống trên mỗi nhánh khác nhau không quá hai, hoặc ba, hoặc một số không đổi khác. Hoặc độ dài đường dẫn tối đa là một phần nhỏ của độ dài đường dẫn tối thiểu, chẳng hạn như một nửa hoặc một phần tư.

Nó thực sự không quan trọng thường xuyên. Điểm của bất kỳ thuật toán cân bằng cây nào là đảm bảo rằng bạn không gặp khó khăn trong tình huống bạn có một triệu nút ở một bên và ba nút ở bên kia. Về mặt lý thuyết, định nghĩa của Donal là ổn, nhưng trong thực tế, việc đưa ra thuật toán cân bằng cây đáp ứng mức độ nghiêm ngặt đó là một điều khó khăn. Hiệu suất tiết kiệm thường không phù hợp với chi phí thực hiện. Bạn dành nhiều thời gian để thực hiện việc sắp xếp lại cây không cần thiết để đạt được mức độ cân bằng mà trong thực tế ít tạo ra sự khác biệt. Ai quan tâm nếu đôi khi phải mất bốn mươi cành để đi đến chiếc lá xa nhất trong một cây cân bằng không hoàn hảo một triệu nút trong khi về lý thuyết, nó có thể chỉ mất hai mươi trong một cây cân bằng hoàn hảo? Vấn đề là nó không bao giờ mất một triệu. Chuyển từ trường hợp xấu nhất là một triệu xuống trường hợp xấu nhất là bốn mươi thường là đủ tốt; bạn không cần phải đi đến tất cả các trường hợp tối ưu.


19
1 chỉ câu trả lời đúng, tôi không thể tin rằng không ai có thể trả lời này trong 8 tháng ...
BlueRaja - Danny Pflughoeft

1
Trả lời cho "bài tập" bên dưới…
Potatoswatter

Bài tập Bonus có đáp án bên dưới.
Brian

Câu trả lời của sdk dưới đây có vẻ đúng và chỉ thực hiện 2 lần duyệt cây nên O (n) cũng vậy. Trừ khi tôi thiếu bí ẩn nào đó, nếu không thì điều đó không giải quyết được ít nhất câu hỏi tiền thưởng đầu tiên của bạn. Tất nhiên, bạn cũng có thể sử dụng lập trình động và giải pháp của bạn để lưu vào bộ nhớ cache các độ cao trung gian
Aly

Về mặt lý thuyết, tôi vẫn phải tuân theo định nghĩa của Donal Fellows.
Dhruv Gairola

26

Cân bằng là một tài sản thực sự tinh tế; bạn nghĩ rằng bạn biết nó là gì, nhưng nó rất dễ sai. Đặc biệt, ngay cả câu trả lời (tốt) của Eric Lippert cũng bị tắt. Đó là bởi vì khái niệm về chiều cao là không đủ. Bạn cần phải có khái niệm về chiều cao tối thiểu và tối đa của cây (trong đó chiều cao tối thiểu là số bậc từ gốc đến lá ít nhất và tối đa là ... tốt, bạn sẽ có được hình ảnh). Do đó, chúng ta có thể định nghĩa số dư là:

Một cây nơi chiều cao tối đa của bất kỳ chi nhánh là không nhiều hơn một hơn chiều cao tối thiểu của bất kỳ chi nhánh.

(Điều này thực sự ngụ ý rằng các nhánh tự cân bằng; bạn có thể chọn cùng một nhánh cho cả tối đa và tối thiểu.)

Tất cả những gì bạn cần làm để xác minh thuộc tính này là theo dõi đường ngang cây đơn giản về độ sâu hiện tại. Lần đầu tiên bạn quay lại, điều đó cung cấp cho bạn độ sâu cơ bản. Mỗi lần sau đó khi quay lại, bạn so sánh độ sâu mới với đường cơ sở

  • nếu nó bằng với đường cơ sở, thì bạn chỉ cần tiếp tục
  • nếu nó khác nhiều hơn một, cây không cân đối
  • nếu nó bị tắt một lần, thì bây giờ bạn biết phạm vi cho sự cân bằng và tất cả các độ sâu tiếp theo (khi bạn chuẩn bị lùi lại) phải là giá trị đầu tiên hoặc giá trị thứ hai.

Trong mã:

class Tree {
    Tree left, right;
    static interface Observer {
        public void before();
        public void after();
        public boolean end();
    }
    static boolean traverse(Tree t, Observer o) {
        if (t == null) {
            return o.end();
        } else {
            o.before();
            try {
                if (traverse(left, o))
                    return traverse(right, o);
                return false;
            } finally {
                o.after();
            }
        }
    }
    boolean balanced() {
        final Integer[] heights = new Integer[2];
        return traverse(this, new Observer() {
            int h;
            public void before() { h++; }
            public void after() { h--; }
            public boolean end() {
                if (heights[0] == null) {
                    heights[0] = h;
                } else if (Math.abs(heights[0] - h) > 1) {
                    return false;
                } else if (heights[0] != h) {
                    if (heights[1] == null) {
                        heights[1] = h;
                    } else if (heights[1] != h) {
                        return false;
                    }
                }
                return true;
            }
        });
    }
}

Tôi cho rằng bạn có thể làm điều này mà không cần sử dụng mẫu Observer, nhưng tôi thấy lý do theo cách này dễ dàng hơn.


[EDIT]: Tại sao bạn không thể chỉ lấy chiều cao của mỗi cạnh. Hãy xem xét cây này:

        /\
       /  \
      /    \
     /      \_____
    /\      /     \_
   /  \    /      / \
  /\   C  /\     /   \
 /  \    /  \   /\   /\
A    B  D    E F  G H  J

OK, một chút lộn xộn, nhưng mỗi bên của gốc được cân bằng: Clà độ sâu 2, A, B, D, Elà độ sâu 3, và F, G, H, Jlà chiều sâu 4. Chiều cao của chi nhánh bên trái là 2 (nhớ chiều cao giảm khi bạn đi ngang nhánh), chiều cao của nhánh phải là 3. Tuy nhiên, tổng thể cây không cân đối vì có sự khác biệt về chiều cao 2 giữa CF. Bạn cần một đặc tả minimax (mặc dù thuật toán thực tế có thể ít phức tạp hơn vì chỉ nên có hai độ cao được phép).


Ah, điểm tốt. Bạn có thể có một cây là h (LL) = 4, h (LR) = 3, h (RL) = 3, h (RR) = 2. Do đó, h (L) = 4 và h (R) = 3, do đó, nó có vẻ cân bằng với thuật toán trước đó, nhưng với độ sâu tối đa / phút là 4/2, điều này không cân bằng. Điều này có lẽ sẽ có ý nghĩa hơn với một bức tranh.
Tim

1
Đó là những gì tôi vừa thêm vào (với cây đồ họa ASCII mới nhất thế giới).
Donal Fellows

@DonalFellows: bạn đề cập đến chiều cao của chi nhánh còn lại là 2. nhưng chi nhánh bên trái có 4 nút bao gồm cả gốc và lá A. Chiều cao sẽ là 3 trong trường hợp này đúng
cơn bão não

22

Điều này chỉ xác định xem tầng cao nhất của cây có cân bằng hay không. Đó là, bạn có thể có một cái cây với hai nhánh dài ở ngoài cùng bên trái và xa bên phải, không có gì ở giữa, và điều này sẽ trở lại đúng. Bạn cần phải kiểm tra đệ quy root.leftroot.rightđể xem chúng có cân bằng bên trong hay không trước khi trả về true.


Tuy nhiên, nếu mã có phương pháp chiều cao tối đa và tối thiểu, nếu nó được cân bằng toàn cầu, nó cũng sẽ được cân bằng cục bộ.
Ari

22

Trả lời bài tập có thưởng. Các giải pháp đơn giản. Rõ ràng là trong một triển khai thực tế, người ta có thể bọc cái này hoặc cái gì đó để tránh yêu cầu người dùng bao gồm chiều cao trong phản hồi của họ.

IsHeightBalanced(tree, out height)
    if (tree is empty)
        height = 0
        return true
    balance = IsHeightBalanced(tree.left, heightleft) and IsHeightBalanced(tree.right, heightright)
    height = max(heightleft, heightright)+1
    return balance and abs(heightleft - heightright) <= 1     

Nếu cây cao hơn vài trăm lớp, bạn sẽ có một ngoại lệ stackoverflow. Bạn đã làm điều đó một cách hiệu quả, nhưng nó không xử lý các tập dữ liệu có kích thước trung bình hoặc lớn.
Eric Leschinski

Đây là mã giả mà bạn vừa nghĩ ra hay nó là một ngôn ngữ thực? (Ý tôi là out heightký hiệu biến "")
kap

@kap: Đây là mã giả, nhưng cú pháp out được lấy từ C #. Về cơ bản, nó có nghĩa là tham số đi từ hàm được gọi đến hàm được gọi (trái ngược với các tham số tiêu chuẩn, đi từ người gọi đến hàm được gọi hoặc tham số ref, đi từ người gọi đến hàm được gọi và quay lại). Điều này cho phép các hàm trả về nhiều hơn một giá trị một cách hiệu quả.
Brian

20

Bài giải đặt hàng, đi ngang cây một lần duy nhất. Độ phức tạp thời gian là O (n), không gian là O (1), nó tốt hơn so với giải pháp từ trên xuống. Tôi cung cấp cho bạn một triển khai phiên bản java.

public static <T> boolean isBalanced(TreeNode<T> root){
    return checkBalance(root) != -1;
}

private static <T> int checkBalance(TreeNode<T> node){
    if(node == null) return 0;
    int left = checkBalance(node.getLeft());

    if(left == -1) return -1;

    int right = checkBalance(node.getRight());

    if(right == -1) return -1;

    if(Math.abs(left - right) > 1){
        return -1;
    }else{
        return 1 + Math.max(left, right);
    }
}

4
giải pháp tốt, nhưng độ phức tạp về không gian phải là O (H) trong đó H là chiều cao của cây. Điều này là do cấp phát ngăn xếp cho đệ quy.
legrass

Nghĩa left == -1là gì? Bao giờ sẽ là trường hợp đó? Chúng ta có giả sử rằng lời gọi đệ quy ngụ ý left == -1là đúng nếu tất cả các cây con của con bên trái không cân bằng?
Aspen

left == 1nghĩa là cây con bên trái không cân bằng, sau đó cả cây không cân bằng. Chúng tôi không phải kiểm tra cây con bên phải nữa và có thể quay lại -1.
vào

Độ phức tạp thời gian là O (n) vì bạn phải đi qua tất cả các phần tử. Và, nếu bạn có x nút và sẽ mất y thời gian để kiểm tra số dư; nếu bạn có 2 nút, sẽ mất 2 năm để kiểm tra số dư. Đó là tất cả các âm thanh phải không?
Jack

Vâng lời giải thích với bản vẽ là ở đây: algorithms.tutorialhorizon.com/...
Shir

15

Định nghĩa của cây nhị phân cân bằng chiều cao là:

Cây nhị phân trong đó chiều cao của hai cây con của mọi nút không bao giờ khác nhau quá 1.

Vì vậy, một cây nhị phân rỗng luôn cân bằng chiều cao.
Cây nhị phân không rỗng là cân bằng chiều cao nếu:

  1. Cây con bên trái của nó được cân bằng chiều cao.
  2. Cây con bên phải của nó được cân bằng chiều cao.
  3. Chênh lệch giữa chiều cao của cây con bên trái và bên phải không lớn hơn 1.

Xem xét cây:

    A
     \ 
      B
     / \
    C   D

Như đã thấy, cây con bên trái của Anó được cân bằng chiều cao (vì nó trống) và cây con bên phải của nó cũng vậy. Nhưng cây vẫn không cân đối về chiều cao do điều kiện 3 không được đáp ứng là chiều cao của cây con bên trái 0và chiều cao của cây con bên phải 2.

Cây sau cũng không cân đối về chiều cao mặc dù chiều cao của cây phụ trái và cây phụ phải bằng nhau. Mã hiện tại của bạn sẽ trả về true cho nó.

       A
     /  \ 
    B    C
   /      \
  D        G
 /          \
E            H

Vì vậy từ every trong def rất quan trọng.

Điều này sẽ hoạt động:

int height(treeNodePtr root) {
        return (!root) ? 0: 1 + MAX(height(root->left),height(root->right));
}

bool isHeightBalanced(treeNodePtr root) {
        return (root == NULL) ||
                (isHeightBalanced(root->left) &&
                isHeightBalanced(root->right) &&
                abs(height(root->left) - height(root->right)) <=1);
}

Liên kết Ideone


Vì vậy, câu trả lời này đã giúp tôi rất nhiều. Tuy nhiên, tôi thấy [khóa học giới thiệu thuật toán MIT] miễn phí có vẻ mâu thuẫn với điều kiện 3. Trang 4 cho thấy một cây RB trong đó chiều cao của nhánh trái là 2 và nhánh phải là 4. Bạn có thể giải thích cho tôi một số điều được không? Có lẽ tôi không hiểu được định nghĩa của cây con. [1]: ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
i8abug

Sự khác biệt dường như đến từ định nghĩa này trong các ghi chú của khóa học. Tất cả đường dẫn đơn giản từ bất kỳ nút x để một lá hậu duệ có cùng một số nút đen = màu đen-height (x)
i8abug

Chỉ để theo dõi, tôi đã tìm thấy một định nghĩa thay đổi điểm (3) trong câu trả lời của bạn thành "mỗi lá cách gốc 'không quá một khoảng nhất định' so với bất kỳ lá nào khác". Điều này dường như thỏa mãn cả hai trường hợp. Dưới đây là liên kết từ một số đồ trình ngẫu nhiên
i8abug

8

Nếu cây nhị phân có cân bằng hay không có thể được kiểm tra bằng cách duyệt theo thứ tự Cấp:

private boolean isLeaf(TreeNode root) {
    if (root.left == null && root.right == null)
        return true;
    return false;
}

private boolean isBalanced(TreeNode root) {
    if (root == null)
        return true;
    Vector<TreeNode> queue = new Vector<TreeNode>();
    int level = 1, minLevel = Integer.MAX_VALUE, maxLevel = Integer.MIN_VALUE;
    queue.add(root);
    while (!queue.isEmpty()) {
        int elementCount = queue.size();
        while (elementCount > 0) {
            TreeNode node = queue.remove(0);
            if (isLeaf(node)) {
                if (minLevel > level)
                    minLevel = level;
                if (maxLevel < level)
                    maxLevel = level;
            } else {
                if (node.left != null)
                    queue.add(node.left);
                if (node.right != null)
                    queue.add(node.right);
            }
            elementCount--;
        }
        if (abs(maxLevel - minLevel) > 1) {
            return false;
        }
        level++;
    }

    return true;
}

1
Câu trả lời xuất sắc. Tôi nghĩ rằng nó đáp ứng tất cả các yêu cầu mà Eric đã đăng về Tiền thưởng và Siêu tiền thưởng. Nó lặp đi lặp lại (sử dụng một hàng đợi) và không đệ quy - vì vậy call-stack sẽ không bị tràn và chúng tôi chuyển tất cả các vấn đề về bộ nhớ vào heap. Nó thậm chí không yêu cầu đi ngang qua toàn bộ cây. Nó di chuyển theo từng cấp, vì vậy nếu một cây hoàn toàn không cân bằng về 1 phía, nó sẽ thực sự sớm tìm thấy mất cân bằng sớm hơn nhưng sẽ hoạt động kém hơn ở các cấp độ đầu tiên). Vì vậy, +1 :-)
David Refaeli

7

Điều này đang được thực hiện phức tạp hơn so với thực tế.

Thuật toán như sau:

  1. Cho A = độ sâu của nút cấp cao nhất
  2. Cho B = độ sâu của nút cấp thấp nhất

  3. Nếu abs (AB) <= 1 thì cây cân bằng


Đơn giản và thẳng thắn!
Wasim Thabraze

3
Hai vấn đề, nó không hiệu quả như nó có thể, bạn đang thực hiện hai lần vượt qua toàn bộ cây. Và đối với những cây có một nút ở bên trái và hàng nghìn nút ở bên phải, bạn không cần thiết phải bước qua toàn bộ, khi bạn có thể dừng lại sau 3 lần kiểm tra.
Eric Leschinski

5

Phương tiện cân bằng phụ thuộc một chút vào cấu trúc ở tay. Ví dụ: A B-Tree không thể có các nút nhiều hơn một độ sâu nhất định từ gốc hoặc ít hơn đối với vấn đề đó, tất cả dữ liệu sống ở độ sâu cố định từ gốc, nhưng nó có thể mất cân bằng nếu sự phân bố của các lá cho các lá -nhưng một nút không đồng đều. Danh sách bỏ qua Không có khái niệm nào về sự cân bằng, thay vào đó dựa vào xác suất để đạt được hiệu suất tốt. Các cây Fibonacci mất cân bằng có chủ đích, trì hoãn sự cân bằng lại để đạt được hiệu suất tiệm cận vượt trội để đổi lấy các bản cập nhật đôi khi lâu hơn. Cây AVL và Đỏ-Đen gắn siêu dữ liệu vào mỗi nút để đạt được sự bất biến cân bằng độ sâu.

Tất cả các cấu trúc này và hơn thế nữa đều có trong thư viện tiêu chuẩn của hầu hết các hệ thống lập trình phổ biến (ngoại trừ python, RAGE!). Thực hiện một hoặc hai là phương pháp lập trình tốt, nhưng có lẽ không phải là cách sử dụng thời gian hiệu quả để bạn tự sản xuất, trừ khi vấn đề của bạn có một số hiệu suất đặc biệt không được thỏa mãn bởi bất kỳ bộ sưu tập ngoài giá nào.


4

Lưu ý 1: Chiều cao của bất kỳ cây con nào chỉ được tính một lần.

Lưu ý 2: Nếu cây con bên trái không cân bằng thì việc tính toán cây con bên phải, có khả năng chứa triệu phần tử, sẽ bị bỏ qua.

// return height of tree rooted at "tn" if, and only if, it is a balanced subtree
// else return -1
int maxHeight( TreeNode const * tn ) {
    if( tn ) {
        int const lh = maxHeight( tn->left );
        if( lh == -1 ) return -1;
        int const rh = maxHeight( tn->right );
        if( rh == -1 ) return -1;
        if( abs( lh - rh ) > 1 ) return -1;
        return 1 + max( lh, rh );
    }
    return 0;
}

bool isBalanced( TreeNode const * root ) {
    // Unless the maxHeight is -1, the subtree under "root" is balanced
    return maxHeight( root ) != -1;
}

3

Sự cân bằng thường phụ thuộc vào độ dài của con đường dài nhất trên mỗi hướng. Thuật toán trên sẽ không làm điều đó cho bạn.

Bạn đang cố gắng thực hiện điều gì? Xung quanh có cây tự cân bằng (AVL / Đỏ-đen). Trên thực tế, cây Java là cân bằng.



3
public boolean isBalanced(TreeNode root)
{
    return (maxDepth(root) - minDepth(root) <= 1);
}

public int maxDepth(TreeNode root)
{
    if (root == null) return 0;

    return 1 + max(maxDepth(root.left), maxDepth(root.right));
}

public int minDepth (TreeNode root)
{
    if (root == null) return 0;

    return 1 + min(minDepth(root.left), minDepth(root.right));
}

Tôi nghĩ rằng giải pháp này là không đúng, nếu bạn truyền một cây có một nút duy nhất tức là một gốc, nó sẽ trả về là maxDepth 1(tương tự cho minDepth). Độ sâu mặc dù chính xác nên được 0.A rễ cây của có luôn 0sâu
Cratylus

3

Đây là một giải pháp đã được thử nghiệm hoàn chỉnh trong C # (xin lỗi, tôi không có java dev) (chỉ cần sao chép dán trong ứng dụng console). Tôi biết rằng định nghĩa về cân bằng khác nhau, vì vậy không phải ai cũng có thể thích kết quả thử nghiệm của tôi nhưng vui lòng xem xét cách tiếp cận hơi khác nhau của việc kiểm tra độ sâu / độ cao trong một vòng lặp đệ quy và thoát khỏi sự không khớp đầu tiên mà không lưu độ cao / cấp độ / độ sâu của nút trên mỗi nút (chỉ duy trì nó trong một lệnh gọi hàm).

using System;
using System.Linq;
using System.Text;

namespace BalancedTree
{
    class Program
    {
        public static void Main()
        {
            //Value Gathering
            Console.WriteLine(RunTreeTests(new[] { 0 }));
            Console.WriteLine(RunTreeTests(new int[] { }));

            Console.WriteLine(RunTreeTests(new[] { 0, 1, 2, 3, 4, -1, -4, -3, -2 }));
            Console.WriteLine(RunTreeTests(null));
            Console.WriteLine(RunTreeTests(new[] { 10, 8, 12, 8, 4, 14, 8, 10 }));
            Console.WriteLine(RunTreeTests(new int[] { 20, 10, 30, 5, 15, 25, 35, 3, 8, 12, 17, 22, 27, 32, 37 }));

            Console.ReadKey();
        }

        static string RunTreeTests(int[] scores)
        {
            if (scores == null || scores.Count() == 0)
            {
                return null;
            }

            var tree = new BinarySearchTree();

            foreach (var score in scores)
            {
                tree.InsertScore(score);
            }

            Console.WriteLine(tree.IsBalanced());

            var sb = tree.GetBreadthWardsTraversedNodes();

            return sb.ToString(0, sb.Length - 1);
        }
    }

    public class Node
    {
        public int Value { get; set; }
        public int Count { get; set; }
        public Node RightChild { get; set; }
        public Node LeftChild { get; set; }
        public Node(int value)
        {
            Value = value;
            Count = 1;
        }

        public override string ToString()
        {
            return Value + ":" + Count;
        }

        public bool IsLeafNode()
        {
            return LeftChild == null && RightChild == null;
        }

        public void AddValue(int value)
        {
            if (value == Value)
            {
                Count++;
            }
            else
            {
                if (value > Value)
                {
                    if (RightChild == null)
                    {
                        RightChild = new Node(value);
                    }
                    else
                    {
                        RightChild.AddValue(value);
                    }
                }
                else
                {
                    if (LeftChild == null)
                    {
                        LeftChild = new Node(value);
                    }
                    else
                    {
                        LeftChild.AddValue(value);
                    }
                }
            }
        }
    }

    public class BinarySearchTree
    {
        public Node Root { get; set; }

        public void InsertScore(int score)
        {
            if (Root == null)
            {
                Root = new Node(score);
            }
            else
            {
                Root.AddValue(score);
            }
        }

        private static int _heightCheck;
        public bool IsBalanced()
        {
            _heightCheck = 0;
            var height = 0;
            if (Root == null) return true;
            var result = CheckHeight(Root, ref height);
            height--;
            return (result && height == 0);
        }

        private static bool CheckHeight(Node node, ref int height)
        {
            height++;
            if (node.LeftChild == null)
            {
                if (node.RightChild != null) return false;
                if (_heightCheck != 0) return _heightCheck == height;
                _heightCheck = height;
                return true;
            }
            if (node.RightChild == null)
            {
                return false;
            }

            var leftCheck = CheckHeight(node.LeftChild, ref height);
            if (!leftCheck) return false;
            height--;
            var rightCheck = CheckHeight(node.RightChild, ref height);
            if (!rightCheck) return false;
            height--;
            return true;
        }


        public StringBuilder GetBreadthWardsTraversedNodes()
        {
            if (Root == null) return null;
            var traversQueue = new StringBuilder();
            traversQueue.Append(Root + ",");
            if (Root.IsLeafNode()) return traversQueue;
            TraversBreadthWards(traversQueue, Root);
            return traversQueue;
        }

        private static void TraversBreadthWards(StringBuilder sb, Node node)
        {
            if (node == null) return;
            sb.Append(node.LeftChild + ",");
            sb.Append(node.RightChild + ",");
            if (node.LeftChild != null && !node.LeftChild.IsLeafNode())
            {
                TraversBreadthWards(sb, node.LeftChild);
            }
            if (node.RightChild != null && !node.RightChild.IsLeafNode())
            {
                TraversBreadthWards(sb, node.RightChild);
            }
        }
    }
}

Tôi không hiểu làm thế nào ai đó có thể tiêu cực bỏ phiếu cho câu trả lời này trong vòng 2 phút sau khi đăng câu trả lời ?? Bỏ phiếu tiêu cực cũng được nhưng bạn có thể vui lòng giải thích điều gì sai với giải pháp này không?
sbp

2
#include <iostream>
#include <deque>
#include <queue>

struct node
{
    int data;
    node *left;
    node *right;
};

bool isBalanced(node *root)
{
    if ( !root)
    {
        return true;
    }

    std::queue<node *> q1;
    std::queue<int>  q2;
    int level = 0, last_level = -1, node_count = 0;

    q1.push(root);
    q2.push(level);

    while ( !q1.empty() )
    {
        node *current = q1.front();
        level = q2.front();

        q1.pop();
        q2.pop();

        if ( level )
        {
            ++node_count;
        }

                if ( current->left )
                {
                        q1.push(current->left);
                        q2.push(level + 1);
                }

                if ( current->right )
                {
                        q1.push(current->right);
                        q2.push(level + 1);
                }

        if ( level != last_level )
        {
            std::cout << "Check: " << (node_count ? node_count - 1 : 1) << ", Level: " << level << ", Old level: " << last_level << std::endl;
            if ( level && (node_count - 1) != (1 << (level-1)) )
            {
                return false;
            }

            last_level = q2.front();
            if ( level ) node_count = 1;
        }
    }

    return true;
}

int main()
{
    node tree[15];

    tree[0].left  = &tree[1];
    tree[0].right = &tree[2];
    tree[1].left  = &tree[3];
    tree[1].right = &tree[4];
    tree[2].left  = &tree[5];
    tree[2].right = &tree[6];
    tree[3].left  = &tree[7];
    tree[3].right = &tree[8];
    tree[4].left  = &tree[9];   // NULL;
    tree[4].right = &tree[10];  // NULL;
    tree[5].left  = &tree[11];  // NULL;
    tree[5].right = &tree[12];  // NULL;
    tree[6].left  = &tree[13];
    tree[6].right = &tree[14];
    tree[7].left  = &tree[11];
    tree[7].right = &tree[12];
    tree[8].left  = NULL;
    tree[8].right = &tree[10];
    tree[9].left  = NULL;
    tree[9].right = &tree[10];
    tree[10].left = NULL;
    tree[10].right= NULL;
    tree[11].left = NULL;
    tree[11].right= NULL;
    tree[12].left = NULL;
    tree[12].right= NULL;
    tree[13].left = NULL;
    tree[13].right= NULL;
    tree[14].left = NULL;
    tree[14].right= NULL;

    std::cout << "Result: " << isBalanced(tree) << std::endl;

    return 0;
}

bạn có thể muốn thêm một số ý kiến
jgauffin

2

RE: Giải pháp của @ lucky sử dụng BFS để thực hiện chuyển tải theo thứ tự cấp.

Chúng tôi duyệt qua cây và giữ một tham chiếu đến mức tối thiểu / tối đa của vars mô tả mức tối thiểu mà tại đó một nút là một lá.

Tôi tin rằng giải pháp @lucky yêu cầu sửa đổi. Như được đề xuất bởi @codaddict, thay vì kiểm tra xem một nút có phải là một lá hay không, chúng ta phải kiểm tra xem NÓI các nút con bên trái hay bên phải là null (không phải cả hai). Nếu không, thuật toán sẽ coi đây là một cây cân bằng hợp lệ:

     1
    / \
   2   4
    \   \
     3   1

Trong Python:

def is_bal(root):
    if root is None:
        return True

    import queue

    Q = queue.Queue()
    Q.put(root)

    level = 0
    min_level, max_level = sys.maxsize, sys.minsize

    while not Q.empty():
        level_size = Q.qsize()

        for i in range(level_size):
            node = Q.get()

            if not node.left or node.right:
                min_level, max_level = min(min_level, level), max(max_level, level)

            if node.left:
                Q.put(node.left)
            if node.right:
                Q.put(node.right)

        level += 1

        if abs(max_level - min_level) > 1:
            return False

    return True

Giải pháp này phải đáp ứng tất cả các quy định được cung cấp trong câu hỏi đầu tiên, hoạt động trong O (n) thời gian và O (n) không gian. Tràn bộ nhớ sẽ được chuyển hướng đến heap thay vì thổi một ngăn xếp cuộc gọi đệ quy.

Ngoài ra, ban đầu chúng ta có thể duyệt qua cây để tính toán + chiều cao tối đa trong bộ nhớ cache cho từng cây con gốc một cách lặp đi lặp lại. Sau đó, trong một lần chạy lặp lại khác, hãy kiểm tra xem độ cao được lưu trong bộ nhớ cache của các cây con bên trái và bên phải cho mỗi gốc có bao giờ khác nhau nhiều hơn một hay không. Điều này cũng sẽ chạy trong O (n) thời gian và O (n) không gian nhưng lặp đi lặp lại để không gây ra tràn ngăn xếp.


1

Vâng, bạn cần một cách để xác định chiều cao của trái và phải, và nếu trái và phải là cân bằng.

Và tôi chỉ muốn return height(node->left) == height(node->right);

Để viết một heighthàm, hãy đọc: Hiểu về đệ quy


3
Bạn muốn chiều cao bên trái và bên phải nằm trong khoảng 1, không nhất thiết phải bằng nhau.
Alex B

1

Bạn đang nói về loại cây gì? Có những cây tự cân bằng ngoài kia. Kiểm tra các thuật toán của họ để xác định xem họ có cần sắp xếp lại cây để duy trì sự cân bằng hay không.


1

Đây là phiên bản dựa trên truyền tải đầu tiên theo chiều sâu chung. Nên nhanh hơn câu trả lời đúng khác và xử lý tất cả các "thách thức" đã đề cập. Xin lỗi vì phong cách này, tôi không thực sự biết Java.

Bạn vẫn có thể làm cho nó nhanh hơn nhiều bằng cách quay lại sớm nếu tối đa và tối thiểu đều được đặt và có sự khác biệt> 1.

public boolean isBalanced( Node root ) {
    int curDepth = 0, maxLeaf = 0, minLeaf = INT_MAX;
    if ( root == null ) return true;
    while ( root != null ) {
        if ( root.left == null || root.right == null ) {
            maxLeaf = max( maxLeaf, curDepth );
            minLeaf = min( minLeaf, curDepth );
        }
        if ( root.left != null ) {
            curDepth += 1;
            root = root.left;
        } else {
            Node last = root;
            while ( root != null
             && ( root.right == null || root.right == last ) ) {
                curDepth -= 1;
                last = root;
                root = root.parent;
            }
            if ( root != null ) {
                curDepth += 1;
                root = root.right;
            }
        }
    }
    return ( maxLeaf - minLeaf <= 1 );
}

1
Một nỗ lực tốt nhưng nó rõ ràng không hoạt động. Gọi x là một nút rỗng. Cho một nút cây không rỗng được ký hiệu là (TRÁI GIÁ TRỊ PHẢI). Xét cây (x A (x B x)). "root" trỏ đến các nút A, B, A, B, A, B ... mãi mãi. Bạn muốn thử lại? Một gợi ý: nó thực sự dễ dàng hơn mà không có con trỏ cha.
Eric Lippert

@Eric: Rất tiếc, đã sửa (tôi nghĩ vậy). Chà, tôi đang cố gắng thực hiện việc này mà không có bộ nhớ O (độ sâu) và nếu cấu trúc không có con trỏ mẹ (nó thường có), bạn cần sử dụng ngăn xếp.
Potatoswatter

Vì vậy, những gì bạn đang nói với tôi là bạn muốn sử dụng bộ nhớ vĩnh viễn O (n) trong con trỏ mẹ để tránh cấp phát bộ nhớ tạm thời O (d), trong đó log n <= d <= n? Đây có vẻ như là một nền kinh tế sai lầm.
Eric Lippert

Thật không may, mặc dù bạn đã khắc phục sự cố với đường truyền, nhưng có một vấn đề lớn hơn nhiều ở đây. Điều này không kiểm tra xem một cây có cân bằng hay không, nó kiểm tra xem một cây có tất cả các lá của nó gần bằng nhau hay không. Đó không phải là định nghĩa về "cân bằng" mà tôi đưa ra. Xét cây ((((x D x) C x) B x) A x). Mã của bạn báo cáo rằng điều này là "cân bằng" khi nó rõ ràng là không cân bằng tối đa. Bạn muốn thử lại?
Eric Lippert

@Eric trả lời 1: không phải là một nền kinh tế sai nếu bạn đã sử dụng các con trỏ chính cho việc khác. trả lời 2: chắc chắn, tại sao không. Đây là một cách gỡ lỗi kỳ lạ… Tôi không nên viết một cách mù quáng bất cứ thứ gì vào lúc 4 giờ sáng…
Potatoswatter

1
/* Returns true if Tree is balanced, i.e. if the difference between the longest path and the shortest path from the root to a leaf node is no more than than 1. This difference can be changed to any arbitrary positive number. */
boolean isBalanced(Node root) {
    if (longestPath(root) - shortestPath(root) > 1)
        return false;
    else
        return true;
}


int longestPath(Node root) {
    if (root == null);
        return 0;
    else {
        int leftPathLength = longestPath(root.left);
        int rightPathLength = longestPath(root.right);
        if (leftPathLength >= rightPathLength)
            return leftPathLength + 1;
        else
            return rightPathLength + 1;
    }
}

int shortestPath(Node root) {
    if (root == null);
        return 0;
    else {
        int leftPathLength = shortestPath(root.left);
        int rightPathLength = shortestPath(root.right);
        if (leftPathLength <= rightPathLength)
            return leftPathLength + 1;
        else
            return rightPathLength + 1;
    }
}

1
Bạn nên thêm một số mô tả vào câu trả lời và / hoặc nhận xét vào mẫu mã của bạn.
Brad Campbell

1
class Node {
    int data;
    Node left;
    Node right;

    // assign variable with constructor
    public Node(int data) {
        this.data = data;
    }
}

public class BinaryTree {

    Node root;

    // get max depth
    public static int maxDepth(Node node) {
        if (node == null)
            return 0;

        return 1 + Math.max(maxDepth(node.left), maxDepth(node.right));
    }

    // get min depth
    public static int minDepth(Node node) {
        if (node == null)
            return 0;

        return 1 + Math.min(minDepth(node.left), minDepth(node.right));
    }

    // return max-min<=1 to check if tree balanced
    public boolean isBalanced(Node node) {

        if (Math.abs(maxDepth(node) - minDepth(node)) <= 1)
            return true;

        return false;
    }

    public static void main(String... strings) {
        BinaryTree tree = new BinaryTree();
        tree.root = new Node(1);
        tree.root.left = new Node(2);
        tree.root.right = new Node(3);


        if (tree.isBalanced(tree.root))
            System.out.println("Tree is balanced");
        else
            System.out.println("Tree is not balanced");
    }
}

0

Đây là những gì tôi đã thử cho bài tập tiền thưởng của Eric. Tôi cố gắng giải phóng các vòng lặp đệ quy của mình và quay lại ngay sau khi tôi tìm thấy một cây con không cân bằng.

int heightBalanced(node *root){
    int i = 1;
    heightBalancedRecursive(root, &i);
    return i; 
} 

int heightBalancedRecursive(node *root, int *i){

    int lb = 0, rb = 0;

    if(!root || ! *i)  // if node is null or a subtree is not height balanced
           return 0;  

    lb = heightBalancedRecursive(root -> left,i);

    if (!*i)         // subtree is not balanced. Skip traversing the tree anymore
        return 0;

    rb = heightBalancedRecursive(root -> right,i)

    if (abs(lb - rb) > 1)  // not balanced. Make i zero.
        *i = 0;

    return ( lb > rb ? lb +1 : rb + 1); // return the current height of the subtree
}

0
public int height(Node node){
    if(node==null)return 0;
    else{
        int l=height(node.leftChild);
        int r=height(node.rightChild);
       return(l>r?l+1:r+1);

}}
public boolean balanced(Node n){

    int l= height(n.leftChild);
    int r= height(n.rightChild);

    System.out.println(l + " " +r);
    if(Math.abs(l-r)>1)
        return false;
    else 
        return true;
    }

0

Cây trống có chiều cao cân đối. Một cây nhị phân không rỗng T là cân bằng nếu:

1) Cây con bên trái của T là cân bằng

2) Cây con bên phải của T cân bằng

3) Chênh lệch giữa chiều cao của cây con bên trái và cây con bên phải không quá 1.

/* program to check if a tree is height-balanced or not */
#include<stdio.h>
#include<stdlib.h>
#define bool int

/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct node
{
  int data;
  struct node* left;
  struct node* right;
};

/* The function returns true if root is balanced else false
   The second parameter is to store the height of tree.  
   Initially, we need to pass a pointer to a location with value 
   as 0. We can also write a wrapper over this function */
bool isBalanced(struct node *root, int* height)
{
  /* lh --> Height of left subtree 
     rh --> Height of right subtree */   
  int lh = 0, rh = 0;  

  /* l will be true if left subtree is balanced 
    and r will be true if right subtree is balanced */
  int l = 0, r = 0;

  if(root == NULL)
  {
    *height = 0;
     return 1;
  }

  /* Get the heights of left and right subtrees in lh and rh 
    And store the returned values in l and r */   
  l = isBalanced(root->left, &lh);
  r = isBalanced(root->right,&rh);

  /* Height of current node is max of heights of left and 
     right subtrees plus 1*/   
  *height = (lh > rh? lh: rh) + 1;

  /* If difference between heights of left and right 
     subtrees is more than 2 then this node is not balanced
     so return 0 */
  if((lh - rh >= 2) || (rh - lh >= 2))
    return 0;

  /* If this node is balanced and left and right subtrees 
    are balanced then return true */
  else return l&&r;
}


/* UTILITY FUNCTIONS TO TEST isBalanced() FUNCTION */

/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
struct node* newNode(int data)
{
    struct node* node = (struct node*)
                                malloc(sizeof(struct node));
    node->data = data;
    node->left = NULL;
    node->right = NULL;

    return(node);
}

int main()
{
  int height = 0;

  /* Constructed binary tree is
             1
           /   \
         2      3
       /  \    /
     4     5  6
    /
   7
  */   
  struct node *root = newNode(1);  
  root->left = newNode(2);
  root->right = newNode(3);
  root->left->left = newNode(4);
  root->left->right = newNode(5);
  root->right->left = newNode(6);
  root->left->left->left = newNode(7);

  if(isBalanced(root, &height))
    printf("Tree is balanced");
  else
    printf("Tree is not balanced");    

  getchar();
  return 0;
}

Độ phức tạp về thời gian: O (n)


0

Để có hiệu suất tốt hơn, đặc biệt là trên những cây lớn, bạn có thể lưu chiều cao trong mỗi nút để nó đánh đổi hiệu suất Vs không gian:

class Node {
    Node left;
    Node right;
    int value;
    int height;
}

Ví dụ về việc thực hiện thêm và tương tự để xóa

void addNode(Node root,int v)
{    int height =0;
     while(root != null)
     {
         // Since we are adding new node so the height 
         // will increase by one in each node we will pass by
         root.height += 1;
         height++;
         else if(v > root.value){
            root = root.left();
            }
         else{
         root = root.right();
         }

     }

         height++;
         Node n = new Node(v , height);
         root = n;         
}
int treeMaxHeight(Node root)
{
 return Math.Max(root.left.height,root.right.height);
}

int treeMinHeight(Node root)
{
 return Math.Min(root.left.height,root.right.height);

}

Boolean isNodeBlanced(Node root)
{
   if (treeMaxHeight(root) - treeMinHeight(root) > 2)
       return false;

  return true;
}

Boolean isTreeBlanced (Node root)
{
    if(root == null || isTreeBalanced(root.left) && isTreeBalanced(root.right) && isNodeBlanced(root))
    return true;

  return false;

}

-1
    static boolean isBalanced(Node root) {
    //check in the depth of left and right subtree
    int diff = depth(root.getLeft()) - depth(root.getRight());
    if (diff < 0) {
        diff = diff * -1;
    }
    if (diff > 1) {
        return false;
    }
    //go to child nodes
    else {
        if (root.getLeft() == null && root.getRight() == null) {
            return true;
        } else if (root.getLeft() == null) {
            if (depth(root.getRight()) > 1) {
                return false;
            } else {
                return true;
            }
        } else if (root.getRight() == null) {
            if (depth(root.getLeft()) > 1) {
                return false;
            } else {
                return true;
            }
        } else if (root.getLeft() != null && root.getRight() != null && isBalanced(root.getLeft()) && isBalanced(root.getRight())) {
            return true;
        } else {
            return false;
        }
    }
}

-2

Điều này sẽ không hoạt động?

return ( ( Math.abs( size( root.left ) - size( root.right ) ) < 2 );

Bất kỳ cây nào không cân bằng sẽ luôn luôn thất bại điều này.


4
Nhiều cây cân bằng cũng sẽ thất bại.
Brian
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.