def trial_multiple_rotations(output=True, num_attempts=10000): """Some trial and error went into these ranges.""" from ch05.challenge import fib tbl = DataTable([6, 6, 6, 6], ['NumRot', 'Height', 'N', 'Random Tree'], output=output) tbl.format('Random Tree', 's') tbl.format('NumRot', 'd') tbl.format('Height', 'd') tbl.format('N', 'd') for extra in range(3): (structure, _) = find_multiple_rotations(extra, lo=4, hi=40, num_attempts=num_attempts, output=False) n = recreate_tree(structure) def count_nodes(n): if n is None: return 0 return 1 + count_nodes(n.left) + count_nodes(n.right) tbl.row([extra + 1, n.height, count_nodes(n), structure]) # Now use Fibonacci Trees to accomplish the same result. if output: print() tbl = DataTable([6, 6, 6, 13], ['NumRot', 'Height', 'N', 'Fib AVL Trees'], output=output) tbl.format('Fib AVL Trees', 's') tbl.format('NumRot', 'd') tbl.format('Height', 'd') tbl.format('N', 'd') for n in range(6, 14, 2): root = fibonacci_avl(n) root.compute_height() check_avl_property(root) # double-check structure = tree_structure(root) bt = ObservableBinaryTree() height = root.height bt.root = root count = count_nodes(root) num_rotations = rotations[0] to_delete = fib(n + 1) - 1 bt.remove(to_delete) check_avl_property(bt.root) num_rotations = rotations[0] - num_rotations tbl.row([num_rotations, height, count, structure]) return tbl
def find_multiple_rotations(extra, lo=4, hi=15, num_attempts=10000, output=True): """Find the smallest binary-tree that requires extra rotations upon insert.""" # Single rotation on remove with four nodes # for n=4, found (3,(1,,(3,,)),(3,,)) when removing 3 # # Double rotations on remove? # for n=12, found (4,(3,(1,(0,,),(1,,(3,,))),(3,,(4,,))),(9,(6,,(7,,)),(11,,))) when removing 9 # But I never got it to find a double-rotation on 11 # # Triple rotations on remove? # for n=33, found (11,(5,(1,(0,(0,(0,,),),(1,,)),(4,,(5,,))),(7,(6,,),(9,,(10,,)))),(19,(15,(12,(12,,),(13,(13,,),)),(17,,(19,,))),(24,(22,(22,(21,,),),(24,,)),(29,(28,(28,,),),(32,(31,,),(33,(33,,),)))))) when removing 5 # # Four rotations on remove? too computationally expensive, but these # numbers (4, 12, 33) suggest a relationship with Fibonacci numbers... # Might be related to https://oeis.org/A027941 random.seed(11) for n in range(lo, hi): if output: print('trying {}'.format(n)) for _ in range(num_attempts): bt1 = ObservableBinaryTree() keys = [] for _ in range(n): k = random.randint(0, n) keys.append(k) bt1.insert(k) check_avl_property(bt1.root) s = tree_structure(bt1.root) # Try to force max # of rotations on deletion... num_rotations = rotations[0] to_delete = random.choice(keys) bt1.remove(to_delete) check_avl_property(bt1.root) if rotations[0] > num_rotations + extra: if output: print( 'for extra={} and after n={} tries, found {} when removing {}' .format(extra, n, s, to_delete)) return (s, to_delete) if output: print('{} total rotations encountered: none in [{}, {}] with extra={}'. format(rotations[0], lo, hi, extra)) return (None, None)
def test_fill_fibonacci_avl_trees(self): from ch06.challenge import fibonacci_avl_tree, rotations from ch05.challenge import fib tree = fibonacci_avl_tree(6) check_avl_property(tree.root) orig = tree.root.height for i in range(fib(7), 2**(tree.root.height + 1)): # up to a complete tree... tree.insert(i) check_avl_property(tree.root) self.assertTrue(abs(tree.root.height - orig) <= 1) # Number of rotations continue to increase. Hope to find some formula # to account for these all! # [0, 0, 1, 5, 16, 39, 90, 196, 418, 874, 1809, 3712, 7575, 15389 for n in range(2, 12): rotations[0] = 0 tree = fibonacci_avl_tree(n) check_avl_property(tree.root) orig = tree.root.height for i in range(fib(n + 1), 2**(tree.root.height + 1)): # up to a complete tree... tree.insert(i) check_avl_property(tree.root) self.assertTrue(abs(tree.root.height - orig) <= 1)
def test_max_rotations(self): from ch06.challenge import find_multiple_rotations, recreate_tree, rotations, ObservableBinaryTree extra = 1 (tree_rep, to_delete) = find_multiple_rotations(extra=extra, lo=9, hi=30, num_attempts=10000, output=False) bt3 = recreate_tree(tree_rep, convert=int) tree = ObservableBinaryTree() tree.root = bt3 num_rotations = rotations[0] tree.remove(to_delete) check_avl_property(tree.root) self.assertEqual(num_rotations + extra + 1, rotations[0]) # This exceeds #rotations
def fibonacci_avl_tree_up_to_2k(N): """ Return AVL tree for Fibonacci AVL Binary Tree that was extended to add nodes up to 2*height-1, which simulates, in a way, an attempt to structurally recreate a complete tree. Resulting heights are just one greater than what you would have in a completed tree, with |Left| + |Right-Grandchild| = |left-child-of-Right| """ from ch05.challenge import fib tree = ObservableBinaryTree() tree.root = fibonacci_avl(N) for i in range(fib(N + 1), 2**(tree.root.height + 1)): # up to a complete tree... tree.insert(i) check_avl_property(tree.root) return tree
def test_val_height_valid_on_remove(self): from ch06.balanced import BinaryTree bt1 = BinaryTree() bt1.insert(7) self.assertEqual(7, bt1.min()) bt1.insert(4) bt1.insert(10) bt1.insert(8) self.assertEqual(2, bt1.root.height) self.assertEqual(4, bt1.root.size()) check_avl_property(bt1.root) bt1.remove(7) self.assertEqual(3, bt1.root.size()) self.assertEqual(1, bt1.root.height) check_avl_property(bt1.root) self.assertEqual(4, bt1.min()) self.assertTrue(4 in bt1) self.assertTrue(10 in bt1) self.assertTrue(8 in bt1) self.assertFalse(7 in bt1)
def test_all_rotations_challenge(self): from ch06.challenge import ObservableBinaryTree bt1 = ObservableBinaryTree() self.assertTrue(bt1.max_value() is None) self.assertTrue(bt1.min_value() is None) self.assertTrue(bt1.remove(99) is None) vals = list(range(201)) random.shuffle(vals) for v in vals: bt1.insert(v) check_avl_property(bt1.root) for _ in range(10): for _ in range(5): vmin = bt1.min_value() bt1.remove(vmin) check_avl_property(bt1.root) self.assertFalse(vmin in bt1) for _ in range(10): bt1.remove(bt1.root.value) check_avl_property(bt1.root) for _ in range(5): bt1.remove(bt1.max_value()) check_avl_property(bt1.root)
def test_pq_stress(self): from ch06.pq import PQ pq1 = PQ() self.assertTrue(pq1.is_empty()) self.assertFalse(pq1.is_full()) with self.assertRaises(ValueError): pq1.enqueue(999, None) with self.assertRaises(RuntimeError): pq1.peek() self.assertFalse(9 in pq1) N = 31 keys = list(range(N)) n = 0 for k in keys: pq1.enqueue(k, k) n += 1 self.assertEqual(list(range(k + 1)), [key for key, _ in list(pq1)]) self.assertEqual(n, len(pq1)) check_avl_property(pq1.tree.root) self.assertEqual(list(range(N)), [key for key, _ in list(pq1)]) # remove keys for k in keys: val1 = pq1.peek() val2 = pq1.dequeue() self.assertEqual(val1, val2) n -= 1 self.assertEqual(list(range(0, N - k - 1)), [key for key, _ in list(pq1)]) self.assertEqual(n, len(pq1)) if pq1.tree: check_avl_property(pq1.tree.root) for k in keys: pq1.enqueue(k, k) n += 1 self.assertEqual(list(range(k + 1)), [key for key, _ in list(pq1)]) self.assertEqual(n, len(pq1)) check_avl_property(pq1.tree.root)
def test_symbol_stress(self): from ch06.symbol import BinaryTree sy1 = BinaryTree() N = 127 keys = list(range(N)) for k in keys: sy1.put(k, k + 1) self.assertEqual(k + 1, sy1.root.size()) self.assertEqual(list(range(k + 1)), [key for key, _ in list(sy1)]) check_avl_property(sy1.root) sy1.put(k, k + 2) self.assertEqual(list(range(N)), [key for key, _ in list(sy1)]) # remove keys count = sy1.root.size() for k in keys: sy1.remove(k) count -= 1 if sy1.root: check_avl_property(sy1.root) self.assertEqual(count, sy1.root.size()) self.assertEqual(list(range(k + 1, N)), [key for key, _ in list(sy1)]) for k in keys: sy1.put(k, k + 3) self.assertEqual(list(range(k + 1)), [key for key, _ in list(sy1)]) self.assertEqual(list(range(N)), [key for key, _ in list(sy1)]) # remove in random order random.shuffle(keys) count = sy1.root.size() for k in keys: sy1.remove(k) count -= 1 if sy1.root: check_avl_property(sy1.root) self.assertEqual(count, sy1.root.size())
def test_fibonacci_avl_trees(self): from ch06.challenge import fibonacci_avl_tree, rotations from ch05.challenge import fib tree = fibonacci_avl_tree(8) check_avl_property(tree.root) self.assertEqual(fib(8), tree.root.value) # multiple rotations detect when remove lARGEST VALUE last_rotations = rotations[0] self.assertTrue(fib(9) - 1 in tree) tree.remove(fib(9) - 1) self.assertEqual(3, rotations[0] - last_rotations) # three rotations # Number of rotations continue to increase, every other one for n in range(5, 20): rotations[0] = 0 tree = fibonacci_avl_tree(n) check_avl_property(tree.root) self.assertEqual(fib(n), tree.root.value) tree.remove(fib(n) - 1) check_avl_property(tree.root) self.assertEqual((n - 1) // 2 - 1, rotations[0])
def test_avl_stress(self): from ch06.balanced import BinaryTree bt1 = BinaryTree() N = 63 keys = list(range(N)) for k in keys: bt1.insert(k) self.assertEqual(list(range(k + 1)), list(bt1)) check_avl_property(bt1.root) self.assertEqual(list(range(N)), list(bt1)) # remove in order for k in keys: bt1.remove(k) self.assertEqual(list(range(k + 1, N)), list(bt1)) check_avl_property(bt1.root) for k in keys: bt1.insert(k) check_avl_property(bt1.root) self.assertEqual(list(range(k + 1)), list(bt1)) self.assertEqual(list(range(N)), list(bt1)) # remove in reverse order for k in reversed(keys): bt1.remove(k) check_avl_property(bt1.root) self.assertEqual(list(range(k)), list(bt1)) for k in keys: bt1.insert(k) check_avl_property(bt1.root) self.assertEqual(list(range(k + 1)), list(bt1)) self.assertEqual(list(range(N)), list(bt1)) # remove in random order. This revealed subtle defect in _remove_min() shuffled = list(keys) random.shuffle(shuffled) for k in shuffled: bt1.remove(k) check_avl_property(bt1.root) self.assertTrue(bt1.is_empty())