예제 #1
0
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
예제 #2
0
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)
예제 #3
0
    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)
예제 #4
0
 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
예제 #5
0
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
예제 #6
0
 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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
    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())
예제 #10
0
    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])
예제 #11
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())