questions about binary search tree - binary-search-tree

Show that every n-node binary search tree is not equally likely (assuming items are inserted in random order), and that balanced trees are more probable than straight-line trees.
How is it prove mathematical?

Number of possible tree configurations: see With ' N ' no of nodes, how many different Binary and Binary Search Trees possible?
Number of ways to get a single line, most imbalanced, deepest tree with n nodes: 2^(n-1)
Explanation:
2 ways to pick up first node (greatest or smallest)
X 2 ways to pick up second node (greatest or smallest among the n-1 remaining nodes
...
X 2 ways to pick up the (n-1)th node
X 1 way to pick up the last node
Number of ways to add n items to a binary tree in such a way that it is balanced:
Let g(n,m) denote the number of ways to merge two ordered lists by picking elements from one list or the other one at a time until both lists are empty. g(n,m) = g(n-1,m) + g(n,m-1) which happen to correspond to the numbers defined in the Pascal Triangle. That would give n+m chose n or n+m chose m = (n+m)! / n! (n+m-n)! = (n+m)!/(n! m!)
Example:
Number of ways to merge two ordered lists containing 2 elements each = 4!/(2! 2!) = 24 / 4 = 6
Assuming the two lists are as follows:
1 A
2 B
The six merging combinations are:
1 1 1 A A A
2 A A B 1 1
A 2 B 1 B 2
B B 2 2 2 B
Now, let f(n) denote the number of combinations in which n sorted elements can be added to a empty binary tree such that the binary tree is balanced. The only way to achieve that is to first add
the middle element if n is odd. That would be element ceiling(n/2). Each side would have n/2-1 elements.
either element n/2 or element n/2+1 if n is even. One side would have n/2-1 element, the other side n/2 elements and vice versa.
Once the middle element is selected, all elements to the left will always fall on the left subtree and all elements on the right will always fall on the right subtree. Assuming the elements on the right are ordered in such a way that the left subtree is balanced and the elements on the right are also ordered in such a way that the right subtree is balanced, merging the two lists in any way will always result in both subtree being balanced. That means that for each list of n and m elements that respectively fall on the left and right subtree such that both subtrees are balanced, there are (n+m)!/(n!m!) to merge them so as to achieve the same result.
That would give us (n+m)!/(n!m!) x f(n) x f(m)
We can now formulate this as follows:
Base cases:
f(1) = 1
f(2) = 2
General case:
f(n) = (n-1)!/[((n-1)/2)!]^2 x [f((n-1)/2)]^2 | n odd
f(n) = (n-1)!/((n/2-1)! x (n/2)!) x 2 x f(n/2-1) x f(n/2) | n even
Rest to transform this into a non recursive formula in terms of n. Maybe it would be easier to start with the easiest case where n = 2^m - 1 (i.e. removing a node and dividing by two will always give a whole number and will result in a completely balanced tree).
Note: I posted a related math question here: https://math.stackexchange.com/questions/460767/recurrent-relation-for-number-of-ways-to-get-a-balanced-n-binary-tree
Following is a C# console application that lists all the sequences in which nodes can be added to a binary tree so as to have it balanced (Run it here ):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AllBalancedTrees
{
class Program
{
static void Main(string[] args)
{
char[] nodes = { 'A', 'B', 'C', 'D', 'E' };
AllBalancedTrees<char> AllBalancedTrees = new AllBalancedTrees<char>();
foreach (char[] a in AllBalancedTrees.allBalancedTreeCombinations(nodes))
{
foreach (char c in a)
{
Console.Write(c + " ");
}
Console.WriteLine();
}
Console.ReadKey();
}
}
class AllBalancedTrees<T>
{
public IEnumerable<T[]> allBalancedTreeCombinations(T[] nodes)
{
T[] result;
if (nodes.Length == 1)
{
yield return nodes;
}
else if (nodes.Length == 2)
{
yield return nodes;
T[] nodes2 = new T[2];
nodes2[0] = nodes[1];
nodes2[1] = nodes[0];
yield return nodes2;
}
else if (nodes.Length % 2 == 1)
{
int mid = (nodes.Length - 1) / 2;
T[] left = new T[mid];
T[] right = new T[mid];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
else
{
int mid = (nodes.Length) / 2;
T[] left = new T[mid];
T[] right = new T[mid - 1];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid - 1);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
mid = nodes.Length / 2 - 1;
left = new T[mid];
right = new T[mid + 1];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid+1);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
}
public IEnumerable<T[]> allMergeCombinations(T[] firstArray, T[] secondArray)
{
if (firstArray.Length == 0)
{
yield return secondArray;
}
else if (secondArray.Length == 0)
{
yield return firstArray;
}
else
{
T[] result;
T[] firstMinusOne;
firstMinusOne = new T[firstArray.Length - 1];
Array.Copy(firstArray, 1, firstMinusOne, 0, firstMinusOne.Length);
foreach (T[] a in allMergeCombinations(firstMinusOne, secondArray))
{
result = new T[firstArray.Length + secondArray.Length];
result[0] = firstArray[0];
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
T[] secondMinusOne;
secondMinusOne = new T[secondArray.Length - 1];
Array.Copy(secondArray, 1, secondMinusOne, 0, secondMinusOne.Length);
foreach (T[] a in allMergeCombinations(firstArray, secondMinusOne))
{
result = new T[firstArray.Length + secondArray.Length];
result[0] = secondArray[0];
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
}

Every n-node binary search tree is not equally likely because, if items are inserted in random order, there is a much greater probability that the input will not be in strictly increasing or decreasing order. As long as the items are not in ascending or descending order, a straight-line tree is an impossibility.
For example, in a tree of four records with keys 1, 2, 3, and 4, there are 4! or 24 ways for these items to be ordered as input. Only two of these ways can result in a straight-line tree (4, 3, 2, 1 and 1, 2, 3, 4). In this case the probability of a straight-line tree is approximately 8.3%, whereas the probability of a (somewhat) balanced tree is 91.6%. Clearly, the odds are against the chances of getting a straight-line tree for a result.

Related

Create a euphonious word

All the letters of the English alphabet are divided into vowels and consonants.
A word is considered euphonious if it doesn't have three or more vowels or consonants in a row.
My goal is to create euphonious words from the discordant ones and output the minimum number of characters needed to create a euphonious word from a given word.
Examples:
Input:
schedule
Output:
1
Input:
biiiiig
Output:
2
Code
fun main() {
val word = readLine()!!.toMutableList()
checkWord(word)
}
fun isVowel(c: Char): Boolean {
val vowels = listOf('a', 'e', 'i', 'o', 'u', 'y')
return c in vowels
}
fun checkWord(word: MutableList<Char>){
var counter = 0
for (number in 0 .. word.size - 2) {
if (isVowel(word[number]) && isVowel(word[number + 1]) && isVowel(word[number + 2])) {
counter++
word.add(number + 2, 'b')
// println(word)
}
if (!isVowel(word[number]) && !isVowel(word[number + 1]) && !isVowel(word[number + 2])) {
counter++
word.add(number + 2, 'a')
// println(word)
}
}
println(counter)
}
My code is working for those examples but not for a case like eeeeeeeeeeeeeeeee where the output is supposed to be 8 but my counter is 6.
Since the list is growing as you iterate, your for loop never reaches the end of the list. Your code can be fixed by replacing
for (number in 0 .. word.size - 2) {
with
var number = -1
while (++number < word.size - 1) {
so it checks the current list size on each iteration.
I want to point out however that it is unnecessary to use a MutableList and keep enlarging it since you don't use the "fixed" euphonious list afterwards. It is also unnecessary to repeatedly search neighbors on each iteration. You can just count as you go.
fun checkWord (word: String) {
var count = 0
var currentTypeCount = 0
var lastTypeVowel = true
for (c in word) {
if (isVowel(c) == lastTypeVowel) {
if (++currentTypeCount == 3) {
count++
currentTypeCount = 1
}
} else {
lastTypeVowel = !lastTypeVowel
currentTypeCount = 1
}
}
println(count)
}
Let's analyze the modifications of your word:
eebeeeeeeeeeeeeeee
eebeebeeeeeeeeeeeee
eebeebeebeeeeeeeeeee
eebeebeebeebeeeeeeeee
eebeebeebeebeebeeeeeee
eebeebeebeebeebeebeeeee
eebeebeebeebeebeebeebeee
eebeebeebeebeebeebeebeebe
Your last 2 modification take place on the letters with index, which is bigger than your word's original length. That happens because for loop iterations number is dependent on your word's original length.
I recommend you to use while loop, as its condition is always recalculated and word.size will be updated there
var i = 0
while (i + 2 < word.size) {
// the same logic
i++
}

Deleting the maximum element in BST

Some of the code below seems too obvious, traversing the tree using its right most branch since that is where all the max values are.However, I don't understand a few things about this code I saw in Robert Sedgewick's Algorithms book.
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("");
root = deleteMax(root);
assert check();
}
private Node deleteMax(Node x) {
if (x.right == null) return x.left;
x.right = deleteMax(x.right);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
In the private method why do we return the left element if the right child of x is null ?From my understanding x would be the maximum if x has no right children and is the right most node we could go to.Also I don't understand when do we return x in the last line of the 2nd method.
If x doesn't have a right child then x is the maximum node. We "delete" it by returning x.left (the new max node). We return x after we've modified its right subtree.

remove invalid parentheses time complexity

Try to understand running time of below algorithm for problem; Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results
This is a simple BFS solution that generates all possible strings by removing "(" or ")".
public List<String> removeInvalidParentheses(String s) {
List<String> ret = new LinkedList<>();
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
queue.add(s);
while (!queue.isEmpty()) {
String current = queue.poll();
if (isValidParentheses(current)) {
ret.add(current);
}
if (ret.size() > 0) continue;
for (int i = 0; i < current.length(); i++) {
if (current.charAt(i) == '(' || current.charAt(i) == ')') {
String next = current.substring(0, i) + current.substring(i + 1);
if (!visited.contains(next)) {
visited.add(next);
queue.offer(next);
}
}
}
}
return ret;
}
public boolean isValidParentheses(String current) {
int open = 0;
int close = 0;
for (char c : current.toCharArray()) {
if (c == '(') open++;
else if (c == ')') close++;
if (close > open) return false;
}
return open == close;
}
It starts with generate n possible strings and next level it generate all strings with size n-1 length, and n-2 length, etc .. for )()( example
)()( len n
()( ))( ()( )() n-1
() (( () n-2
each level it checks all possible strings with n-level length.
given this - I was having hard to time figure out how to finalize the running time of this algorithm. How do I generalize this algorithm and analyze the complexity?
For the worst case, lets try with input as ((((. As per the logic above, it will push (((( in the queue, checking that this is invalid. So it would generate 4 more possible substrings of length 3 pushing them inside the queue. Again, on processing that queue elements, it would again generate more strings of length 2 for each substring of length 3, then for two and then end. We are assuming that T(1) = 1.
If you try to make a recurrence relation for the above logic, it would be
T(n) = nT(n-1) + 1, which can be written as
= `n((n-1)T(n-2) + 1) + 1` and so on.
On solving it completely, we would get T(n) = n! + n(n-1)/2 + 1 which would be O(n!).
Hence, I think the time complexity would be of order O(n!)
For more details on how to solve the recurrence relation T(n) = nT(n-1) + 1, please refer:
this post on its solution

Cyclomatic Complexity edges

So I'm trying to figure out if that blue line is in the right place, I know that I should have 9 edges but not sure if it's correct.
The code
public int getResult(int p1, int p2) {
int result = 0; // 1
if (p1 == 0) { // 2
result += 1; //3
} else {
result += 2; //4
}
if (p2 == 0) { //5
result += 3; //6
} else {
result += 4; //7
}
return result; //8 exit node
}
so 8 nodes and it should have 9 edges, right? Did I do the right thing?
Yes, the blue line is placed correctly because after the 3rd line, your program is going to jump to the 5th line.
The easiest way to compute cyclomatic complexity without drawing any flow diagram is as follows:
Count all the loops in the program for, while, do-while, if. Assign a value of 1 to each loop. Else should not be counted here.
Assign a value of 1 to each switch case. Default case should not be counted here.
Cyclomatic complexity = Total number of loops + 1
In your program, there are 2 if loops, so the cyclomatic complexity would be 3(2+1)
You can cross-check it with the standard formulae available as well which are as below:
C = E-N+2 (9-8+2=3)
OR
C = Number of closed regions + 1 (2+1=3)
According to wikipedia:
M = E − N + 2P,
where
E = the number of edges of the graph.
N = the number of nodes of the graph.
P = the number of connected components.
so:
9 - 8 + 2*1 = 3

Merge two Binary trees

public int merge(BNode node, int array[], int i) {
if (node == null)
return i;
//Flatten left subtree
i = merge(node.left, array, i);
//Get data from the current node
array[i] = node.value;
//Flatten right subtree
i = merge(node.right, array, i + 1);
return i;
}
I'm trying to merge two binary trees and retain the BST property.
The approach im using is to flatten the trees and store them in arrays.
The function above flattens my first tree and stores it in the array[] .
I want a function which would take the rootnode and blank array[] as input and RETURNS me a flattened tree with all the nodes into an array.
As you are doing, if you want to merge 2 binary search tree, the best way is:
1)Flatten trees into sorted lists.
2)Merge lists.
3)Transform the merged list to a BST.
You can implement the function you are looking for easily this way:
BinarySearchTree* arrayToTree(int arr[], int start, int end) {
if (start > end) return NULL;
int mid = start + (end - start) / 2;
BinarySearchTree *node = new BinarySearchTree(arr[mid]);
node->left = arrayToTree(arr, start, mid-1);
node->right = arrayToTree(arr, mid+1, end);
return node;
}
BinarySearchTree* arrayToTree(int arr[], int n) {
return arrayToTree(arr, 0, n-1);
}