How can I eliminate gaps in the breadth-first ordering of a binary search tree? - algorithm

A gap-less binary search tree is a self-balancing binary search tree with the gap-less property. The gap-less property states that there are no gaps in the breadth-first ordering of the tree. A gap in the breadth-first ordering is best defined through a diagram. In the image below, the areas highlighted by red dashed circles are considered gaps in the breadth-first ordering:
If this tree were restructured to eliminate the gaps, it would look like this:
If the number 7 were added to this restructured tree without re-balancing, it would look like this:
Again, after removing the gaps:
Is there a log(n) algorithm to ensure the gap-less property after insertions and deletions to trees of arbitrary sizes?

Is there a log(n) algorithm to ensure the gap-less property after insertions and deletions to trees of arbitrary sizes?
No.
To see why, consider this tree (which has the gap-less property):
4
/ \
2 6
/| |\
1 3 5 7
To insert 8, you'd need to end up with this:
5
/ \
3 7
/| |\
2 4 6 8
/
1
which clearly requires visiting every node at least once, because every single node has a different parent afterward than it had before. Therefore, you cannot possibly guarantee better than O(n) time.
Likewise, to remove 1, you'd need to end up with this:
5
/ \
3 7
/| |
2 4 6
which, same problem.

Related

If we have some Binary Search Tree and perform the operations add(x) followed by remove(x) do we necessarily return to the original tree?

The value of x should be the same. Do we get the same tree back again with one add and one remove operation?
No, not necessarily. The set of elements does not uniquely determine the structure of the tree, and there are many ways to implement a BST, with different mechanisms for adding and removing elements. Some types of BST, such as self-balancing BSTs, can adjust themselves in ways that destroy information about their previous structures, and therefore these operations are not generally reversible.
For instance, suppose we have a self-balancing BST:
8
/ \
3 9
\
4
We add 5:
8
/ \
4 9
/ \
3 5
and remove 5:
8
/ \
4 9
/
3

Can we construct BST from inorder sequence?

We can construct a Binary Search Tree (BSTĂ  from a preOrder or postOrder sequence. But can we construct a BST from an inorder sequence alone? I think it is not possible to construct a BST using an inOrder sequence, as we cannot find root element.
You are correct. Two different Binary Search Trees can have the same inorder sequence, so you wouldn't know which one to pick. If however, it would be OK to pick any BST that has that inorder sequence, then it is possible.
But I guess your question is about reconstructing the same BST as the tree from which the inorder sequence was derived. That is not possible: by converting a BST to an inorder sequence, you lose information. Even if you would be given the root as additional information, you would not generally be able to do it. In fact, the shape of the BST can be anything possible with the given number of nodes.
The smallest example is inorder [1, 2]. It can represent both of these trees:
2 1
/ \
1 2
If in this case you were given the root as extra information, then it would of course be possible to derive the BST from it.
The next smallest example: [1, 2, 3]. These trees all have that as inorder sequence:
3 2 1
/ / \ \
2 1 3 2
/ \
1 3
Also here it is true that if you were given the root as extra information, you would be able to know which of the three BSTs was the right one.
But once we get to larger trees, we would not always be able to know the right BST, even if we were given the root.
Note also that a BST does not have to be optimally balanced. But even if we would only consider optimally balanced BSTs, an inorder sequence does not uniquely define the tree. The example with 2 nodes above is already proof of that. But let's look at four nodes for which the inorder sequence would be [1, 2, 3, 4]. The most-balanced trees with that inorder sequence are:
3 3 2 2
/ \ / \ / \ / \
2 4 1 4 1 4 1 3
/ \ / \
1 2 3 4
And here we also see that if we were given the root of the optimally balanced BST, there still would be two possibilities left.

Number of levels in Binary Tree given the list of datas

Lets consider that, the following is the resultant of the Level Order Traversal of the Binary Tree.
Ex: 1,2,3,4,5,6,7,8
But, I got a question like, with the given list of data, how to compute the total number of levels in the binary tree.
I thought some thing like, Sqrt(8) and doing the Math.Round to it, will yield the result.
But I doubt that, I am wrong.
May I know, what is the perfect to do that.
Thanks in advance...
In the general case, a binary tree with n nodes will have at least 1 + floor(log_2(n)) levels. For example, you can fit 7 nodes on 3 levels, but 8 nodes will take at least 4 levels no matter what.
Also in the general case, the upper limit is n levels in the case of a degenerate binary tree (which looks like a linked list hanging down from the root). Consider your example, where the level-order traversal (also known as breadth-first traversal) is 1 2 3 4 5 6 7 8. The following cases are possible, along with everything in between:
1 1
/ \ \
/ \ 2
2 3 \
/ \ / \ 3
4 5 6 7 \
/ 4
8 \
5
(4 levels) \
6
\
7
\
8
(8 levels)
There are particular types of binary trees for which you can put stronger constraints on the upper limit. For complete or full binary trees, the number of levels is always 1 + floor(log_2(n)), because the shape of the tree depends only on n.
If you label the nodes with an index in breadth-first order, you can compute the level without any traversal in O(1) time. So if you are doing multiple queries, you can do an O(N) BFT and have each query answered in O(1) time.
The formula for the level is:
level = floor(log(index + 1))
Where the log is to the base 2
This link help you How can I calculate the level of a node in a perfect binary tree from its depth-first order index?
The height of a complete binary tree is up to O(logN).
Where N is the number of nodes you fill in the tree.
Note that Big O notation is required due to the fact that the actual height could vary by some addition or scaling factor.
https://www.cs.cmu.edu/~adamchik/15-121/lectures/Trees/trees.html
Level index value either can starts from 0 or 1.
If you are counting the level index starting from 0 (i.e., Root at Level 0) then
#no.of levels = floor(log_2(n))
If you are counting the level index starting from 1 (i.e., Root at Level 1) then
#no.of levels = 1 + floor(log_2(n))

How do you know where to perform rotations in an AVL tree?

So I'm self teaching AVL trees and I understand the basic idea behind it, but I just want to make sure my intuition of actually implementing it is valid:
I'll examine it with the left rotation-
So, the following situation is simple:
8
/ \
7 10
/
6
/
3
When we add the 3, the tree rebalances itself to:
8
/ \
6 10
/ \
3 7
But is the rotation based on the addition of the 3 or the imbalance of the subtree rooted at 7? Is it even based on the imbalance of the tree rooted at 8?
The following example is where things get a bit hairy, in my opinion:
9
/ \
7 10
/ \
6 8
/
3
So, in this case, the subtree at 7 is fine when the 3 is added, so that subtree doesn't need to rotate. However, the tree at 9 is imbalanced with the addition of 3, so we base the rotation at 9. We get:
7
/ \
6 9
/ / \
3 8 10
So in writing my code, which I will quite soon, would the following code, starting from small subtrees working up to bigger subtrees do the trick?
pseudocode:
function balanceTree(Node n){
if (n is not null){
balanceTree(n.rightchild);
balanceTree(n.leftchild);
}
if (abs(balanceFactor(n))>1){
rotateAsNeeded(n);// rotate based on balance factor
}
}
Thanks in advance!
The pseudocode that you've posted will correctly balance a tree. That said, it is too inefficient to be practical - notice that you're recursively exploring the entire tree trying to do rebalancing operations, which will make all insertions and deletions take O(n) time, eating away all the efficiency gains of having a balanced tree.
The idea behind AVL trees is that globally rebalancing the tree can be done by iteratively applying local rotations. In other words, when you do an insertion or deletion and need to do tree rotations, those rotations won't appear in random spots in the tree. They'll always appear along the access path you took when inserting or deleting the node.
For example, you were curious about inserting the value 3 into this tree:
9
/ \
7 10
/ \
6 8
Let's start off by writing out the difference in balance factors associated with each node (it's critical that AVL tree nodes store this information, since it's what makes it possible to do insertions and deletions efficiently):
9(+1)
/ \
7 (0) 10 (0)
/ \
6(0) 8(0)
So now let's see what happens when we insert 3. This places the 3 here:
9(+1?)
/ \
7 (0?) 10 (0)
/ \
6(0?) 8(0)
/
3(0)
Notice that I've marked all nodes on the access path with a ?, since we're no longer sure what their balance factors are. Since we inserted a new child for 6, this changes the balance factor for the 6 node to +1:
9(+1?)
/ \
7 (0?) 10 (0)
/ \
6(+1) 8(0)
/
3(0)
Similarly, the left subtree of 7 grew in height, so its balance factor should be incremented:
9(+1?)
/ \
7 (+1) 10 (0)
/ \
6(+1) 8(0)
/
3(0)
Finally, 9's left subtree grew by one, which gives this:
9(+2!)
/ \
7 (+1) 10 (0)
/ \
6(+1) 8(0)
/
3(0)
And here we find that 9 has a balance factor of +2, which means that we need to do a rotation. Consulting Wikipedia's great table of all AVL tree rotations, we can see that we're in the case where we have a balance factor of +2 where the left child has a balance factor of +1. This means that we do a right rotation and pull the 7 above the 9, as shown here:
7(0)
/ \
6(+1) 9(0)
/ / \
3(0) 8(0) 10 (0)
Et voilĂ ! The tree is now balanced.
Notice that when we did this fixup procedure, we didn't have to look over the entire tree. Instead, all we needed to do was look along the access path and check each node there. Typically, when implementing an AVL tree, your insertion procedure will do the following:
If the tree is null:
Insert the node with balance factor 0.
Return that the tree height has increased by 1.
Otherwise:
If the value to insert matches the current node, do nothing.
Otherwise, recursively insert the node into the proper subtree and get the amount that the tree height has changed by.
Update the balance factor of this node based on the amount that the subtree height changed.
If this mandates a series of rotations, perform them.
Return the resulting change in the height of this tree.
Since all these operations are local, the total work done is based purely on the length of the access path, which in this case is O(log n) because AVL trees are always balanced.
Hope this helps!
PS: Your initial example was this tree:
8
/ \
7 10
/
6
/
3
Note that this tree isn't actually a legal AVL tree, since the balance factor of the root node is +2. If you consistently maintain tree balance using the AVL algorithm, you will never encounter this case.

When two trees are equal?

If in-order traversal of two binrary trees (not binary search trees) are the same, does it guarantee that the two trees are the same?
if answer is no, then what about both in-order and pre-order traversal are the same?
Definitely not. The two trees
b
/ \
a d
/ \
c e
and
d
/ \
b e
/ \
a c
both have an inorder traversal of a b c d e. They are, in fact, rotations, an operation which preserves inorder traversal.
NO, and its seen with this simple example
3 2
2 1 3
1 0
0
Both have same inorder traversals [0,1,2,3]
But if inorder and preorder traversals are same then the trees are equal.
I'm thinking "no."
It's possible for two trees to be balanced differently, but have the same "order" of node values. For instance, if, of the set
1,2,3,4,5,6,7
You build a tree:
4
2 6
1 3 5 7
in-order traversal will yield 1,2,3,4,5,6,7.
however, if you choose a different root node (if the list is not sorted correctly beforehand)
5
4 6
2 7
1 3
These two trees will give the same in-order traversal result, but are (clearly) not identical.
or even
7
6
5
4
3
2
1
et cetera.
This is also related to a problem with BSP (binary space partition) trees, typically used in game development collision detection and visibility determination.
The BSP stores triangles in a tree. Each node contains a triangle or facet. The left node contains all children that are "behind" the facet, while the right child contains everything that is in "front." Recurse as expected.
If you pick the left-most facet in the scene as your root, the right child will then own every other facet. If you make a bad decision for the right child, the same thing will happen. It's perfectly possible for one to build a BSP compiler that, through idiotic analysis, builds a "tree" that is actually a list (as in my last example above). The problem is to partition the data set so that each node divides the remaining list as equally as possible. This is one of the reasons that BSPs are typically generated at compile time, as building one for a very complex geometry can take hours to find the/an optimal solution.
Inorder and any one of pre-order or post-order, uniquely define a tree structure. Nothing less.
One thing you can do is use level order
5
4 6
2 7
1 3
lvel order- 5 4 6 2 N N 7 1 3 N N N N N N

Resources