How to randomly generate a binary tree given the node number?

maplemaple :
//Definition for a binary tree node.

public class TreeNode {
    int key;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { key = x; }
} 

Given the total number of TreeNode int n, how to generate a randomly distributed binary tree(I mean the random shape of binary tree, not the random key value. You can set all key values of TreeNodes equal to 1) and return the TreeNode root.

That is how to implement the following API:

public class RandomBinaryTree{
    public TreeNode binaryTreeGenerator(int n){

    }
}

PS: For example, n = 3, I want the algorithm can randomly generate one of the following 5 binary trees each time:

     1      1        1           1          1
    /      /        / \           \          \
   1      1        1   1           1          1
  /        \                      /            \
 1          1                    1              1

Is there any algorithm to generate binary trees equiprobably with fixed number of nodes n?

Arminius :

Biased distribution, simple algorithm

Starting from the root, randomly choose the number of nodes in each subtree, then recurse :

public class RandomBinaryTree {
    private Random random = new Random();

    public TreeNode binaryTreeGenerator(int n, int key){
        if (n == 0)
            return null;

        TreeNode root = new TreeNode(key);

        // Number of nodes in the left subtree (in [0, n-1])
        int leftN = random.nextInt(n);

        // Recursively build each subtree
        root.setLeft(binaryTreeGenerator(leftN, key));
        root.setRight(binaryTreeGenerator(n - leftN - 1, key));

        return root;
    }
}

This algorithm does not enforce uniform distribution of results and will heavily favor balanced trees. For a simple proof, consider the case n = 3 and calculate probability of occurence for each of the 5 possible binary trees (see Catalan numbers for the related combinatorics).

Uniform distribution, more challenging

There has been some research on this subject and this is probably one of the simplest and fastest methods (in O(n)). The idea is to generate a random word containing left and right brackets in equal numbers, then map that to a binary tree using a transformation that preserves uniform distribution.

Step 1 : Generate a random balanced word :

private static Random random = new Random();

// true means '(', false means ')'
private static boolean[] buildRandomBalancedWord(int n) {
    boolean[] word = new boolean[n * 2];
    List<Integer> positions = IntStream.range(0, 2 * n).boxed()
        .collect(Collectors.toList());
    for (int i = n; i > 0; i--) {
        int index = random.nextInt(n + i);
        word[positions.remove(index)] = true;
    }
    return word;
}

Step 2 : The generated word may have k "defects", which are basically unmatched closing brackets. The paper shows that there is a way to rearrange the generated word such that the resulting mapping is a bijection from the set of words with k defects to the set of words with 0 defects (well-formed words). Here is the procedure :

private static void rearrange(boolean[] word, int start, int end) {
    int sum = 0;
    int defectIndex = -1;
    for (int i = start; i < end; i++) {
        sum = sum + (word[i] ? 1 : -1);
        if (defectIndex < 0 && sum < 0) {
            defectIndex = i;
        } else if (defectIndex >= 0 && sum == 0) {
            // We now have irreducible u = rtl spanning [defectIndex, i]
            int uLength = i - defectIndex + 1;
            boolean[] flipped = new boolean[uLength - 2];
            for (int j = 0; j < flipped.length; j++)
                flipped[j] = !word[defectIndex + j + 1];

            // Shift the remaining word
            if (i + 1 < end)
                System.arraycopy(word, i + 1, word, defectIndex + 1, end - (i + 1));

            // Rewrite uw as lwrt*, t* being the flipped array
            word[defectIndex] = true;
            System.arraycopy(flipped, 0, word, end - flipped.length, flipped.length);
            word[end - uLength + 1] = false;

            // Now recurse on w, worst case we go (word.length/2)-deep
            rearrange(word, defectIndex + 1, end - uLength + 1);
            break;
        }
    }
}

Step 3 : There is a one-to-one mapping from well-formed bracket words to binary trees : every pair of matching brackets is a node, everything inside is the left subtree, and everything after is the right subtree :

// There is probably a smarter way to do this
public static TreeNode buildTree(boolean[] word, int key) {
    Deque<TreeNode> stack = new ArrayDeque<>();
    boolean insertRight = false;
    TreeNode root = null;
    TreeNode currentNode = null;
    for (int i = 0; i < word.length; i++) {
        if (word[i]) {
            TreeNode previousNode = currentNode;
            currentNode = new TreeNode(key);

            if (root == null) {
                root = currentNode;
            } else if (insertRight) {
                previousNode.setRight(currentNode);
                insertRight = false;
            } else {
                previousNode.setLeft(currentNode);
            }

            stack.push(currentNode);
        } else {
            currentNode = stack.pop();
            insertRight = true;
        }
    }
    return root;
}

Some utility functions :

public static boolean[] buildRandomWellFormedWord(int n) {
    boolean[] word = buildRandomBalancedWord(n);
    rearrange(word, 0, word.length);
    return word;
}

public static String toString(boolean[] word) {
    StringBuilder str = new StringBuilder();
    for (boolean b : word)
        str.append(b ? "(" : ")");
    return str.toString();
}

Test : Let's print the actual distribution over 10 millions runs of size 3 :

public static void main(String[] args) throws Exception {
    Map<String, Integer> counts = new HashMap<String, Integer>();
    int N = 10000000, n = 3;
    for (int i = 0; i < N; i++) {
        boolean[] word = buildRandomWellFormedWord(n);
        String str = toString(word);
        Integer count = counts.get(str);
        if (count == null)
            count = 0;
        counts.put(str, count + 1);
    }

    counts.entrySet().stream().forEach(e -> 
        System.out.println("P[" + e.getKey() + "] = " + e.getValue().doubleValue() / N));
}

The output should look something like :

P[()()()] = 0.200166
P[(()())] = 0.200451
P[()(())] = 0.199894
P[((()))] = 0.199006
P[(())()] = 0.200483

So buildTree(buildRandomWellFormedWord(n), key) will yield a binary tree of size n following a uniform distribution over all possible trees.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324668&siteId=1