Подтвердить что ты не робот

Алгоритм для обмена ролями двух случайно выбранных узлов из движущихся указателей дерева

Я создал алгоритм, цель которого должна быть, учитывая два узла A и B в BST, он переключает роли (или позиции в дереве) этих двух, просто перемещая указатели. В моем представлении BST я использую двойное связанное соединение (т.е. A.parent == B и (B.left == A) или (B.right == A)). Я не уверен, полностью ли он исправлен или нет. Я разделил алгоритм в двух ситуациях.

  • A и B напрямую связаны (либо A является родителем B, либо B родителем A)

  • Все остальные случаи

Для каждого из предыдущих случаев я создал вложенную функцию. Я хотел бы получить ваше мнение о первом правильности алгоритмов, и если можно каким-то образом улучшить его. Здесь код:

def switch(self, x: BSTNode, y: BSTNode, search_first=False):
    if not x:
        raise ValueError("x cannot be None.")
    if not y:
        raise ValueError("y cannot be None.")
    if x == y:
        raise ValueError("x cannot be equal to y")

    if search_first:
        if not self.search(x.key) or not self.search(y.key):
            raise LookupError("x or y not found.")

    def switch_1(p, s):
        """Switches the roles of p and s,
        where p (parent) is the direct parent of s (son)."""
        assert s.parent == p

        if s.is_left_child():
            p.left = s.left
            if s.left:
                s.left.parent = p

            s.left = p

            s.right, p.right = p.right, s.right
            if s.right:
                s.right.parent = s
            if p.right:
                p.right.parent = p
        else:
            p.right = s.right
            if s.right:
                s.right.parent = p

            s.right = p

            s.left, p.left = p.left, s.left
            if s.left:
                s.left.parent = s
            if p.left:
                p.left.parent = p

        if p.parent:
            if p.is_left_child():
                p.parent.left = s 
            else:
                p.parent.right = s
        else:  # p is the root
            self.root = s

        s.parent = p.parent
        p.parent = s

    def switch_2(u, v):
        """u and v are nodes in the tree
        that are not related by a parent-son
        or a grandparent-son relantionships."""
        if not u.parent:
            self.root = v
            if v.is_left_child():
                v.parent.left = u
            else:
                v.parent.right = u
        elif not v.parent:
            self.root = u
            if u.is_left_child():
                u.parent.left = v
            else:
                u.parent.right = v
        else:  # neither u nor v is the root                
            if u.is_left_child():
                if v.is_left_child():                   
                    v.parent.left, u.parent.left = u, v
                else:
                    v.parent.right, u.parent.left = u, v
            else:
                if v.is_left_child():                   
                    v.parent.left, u.parent.right = u, v
                else:
                    v.parent.right, u.parent.right = u, v                    

        v.parent, u.parent = u.parent, v.parent
        u.left, v.left = v.left, u.left
        u.right, v.right = v.right, u.right

        if u.left:
            u.left.parent = u
        if u.right:
            u.right.parent = u
        if v.left:
            v.left.parent = v
        if v.right:
            v.right.parent = v

    if x.parent == y:
        switch_1(y, x)            
    elif y.parent == x:
        switch_1(x, y)
    else:
        switch_2(x, y)

Мне действительно нужно, чтобы switch работал во всех случаях независимо от того, какие узлы x или y мы выберем. Я уже провел некоторые тесты, и, похоже, это работает, но я все еще не уверен.

ИЗМЕНИТЬ

В конце концов, если это будет полезно как-то, здесь у вас есть полная реализация моего BST (с тестами, которые я делаю):

https://github.com/dossan/ands/blob/master/ands/ds/BST.py

РЕДАКТИРОВАТЬ 2 (просто любопытство)

@Ришав прокомментировал:

Я не понимаю намерения этой функции. Если нужно обменять два узла в BST, недостаточно ли их обмена данными вместо того, чтобы манипулировать указателями?

Я ответил:

Хорошо, возможно, я должен был добавить немного больше о причине всей этой "монстра". Я могу вставлять объекты BSTNode или любые сопоставимые объекты в свой BST. Когда пользователь решает вставить какой-либо сопоставимый объект, ответственность за создание BSTNode принадлежит мне, поэтому пользователь не имеет доступа к исходной ссылке BSTNode, если только они не ищут ключ. Но BSTNode будет возвращен только после вставки ключа, или в нем уже есть другой объект BSTNode с тем же ключом (или значением), но этот последний случай не имеет значения.

Пользователь также может вставить объект BSTNode в дерево, у которого есть начальный (и должен оставаться постоянным) ключ (или значение). Тем не менее, если бы я просто обменял значения или ключи узлов, у пользователя была бы ссылка на node с другим ключом, а затем на ключ node, который он вставил. Конечно, я хочу этого избежать.

4b9b3361

Ответ 1

вам нужно надлежащее модульное тестирование. Я рекомендую python-нос - очень прост в использовании.

Что касается тестовых векторов, я бы рекомендовал использовать каждую потенциальную комбинацию из двух узлов a и b:

В случае деревьев BSP у вас есть 3 типа узлов:

  • лист node,
  • 1-child node,
  • 2-children node.

в сочетании со следующими дополнительными случаями:

  • a - корень, или
  • a является родительским элементом b,
  • a не является родительским элементом b.

и их комбинации (также в симметричной ситуации).

то после замены вам нужно будет проверить все задействованные узлы, а именно: a, b, дети a и b, родители a и b, если все пойдет по плану.

Я бы сделал это, используя небольшое дерево, содержащее все типы узлов. Затем пройдите все возможные комбинации узлов и замените узлы и проверьте их на ожидаемый результат, а затем снова поменяйте, чтобы вернуть дерево в исходное состояние.

[EDIT]

Если бы ваш вопрос заключался в том, как избежать всей утомительной работы. Вы можете рассмотреть возможность поиска хорошо зарекомендовавшей себя реализации BSP и сравнить результаты с вашей функцией. Векторы могут создаваться автоматически, используя подготовленное дерево и генерируя все возможные пары узлов этого дерева.

[/EDIT]

Что касается нежелательного ввода функции. Вам нужно будет использовать свое воображение, хотя, на мой взгляд, у вас больше всего случаев. Кроме того, что Остин Гастингс упоминает, где по крайней мере на входных узлах не принадлежит древо.

Я нашел старую версию той же функции, написанной для одного из моих частных проектов, возможно, вы можете найти ее полезной:

def swap( a, b ):
    if a == b: return
    if a is None or b is None: return
    #if a not in self or b not in self: return

    if b.parent == a:
        a, b = b, a

    #swap connections naively
    a.parent, b.parent = b.parent, a.parent
    a.left, b.left = b.left, a.left
    a.right, b.right = b.right, a.right

    if b.parent == b: #b was the p of a
        b.parent = a

    if a.parent is not None:
        if a.parent.left == b: a.parent.left = a
        else: a.parent.right = a
    else:
        self.root = a

    if b.parent is not None:
        if b.parent.left == a: b.parent.left = b
        else: b.parent.right = b
    else:
        self.root = b

    if a.right is not None: a.right.parent = a
    if a.left is not None: a.left.parent = a
    if b.right is not None: b.right.parent = b
    if b.left is not None: b.left.parent = b

и оптимизированная по производительности версия:

def swap_opt( a, b ):
    if a == b: return
    if a is None or b is None: return
    #if a not in self or b not in self: return

    if b.p == a:
        a, b = b, a

    #swap connections naively
    a.p, b.p = b.p, a.p
    a.l, b.l = b.l, a.l
    a.r, b.r = b.r, a.r

    if b.p == b: #b was the p of a
        b.p = a
        if a.l == a:
            a.l = b
            if a.r is not None: a.r.p = a
        else:
            a.r = b
            if a.l is not None: a.l.p = a

        if b.r is not None: b.r.p = b
        if b.l is not None: b.l.p = b
        if a.p is not None:
            if a.p.l == b: a.p.l = a
            else: a.p.r = a
        else:
            #set new root to a
            pass

    else:
        if a.r is not None: a.r.p = a
        if a.l is not None: a.l.p = a
        if b.r is not None: b.r.p = b
        if b.l is not None: b.l.p = b

        if a.p is not None:
            if a.p.l == b: a.p.l = a
            else: a.p.r = a
        else:
            #set new root to a
            pass
        if b.p is not None:
            if b.p.l == a: b.p.l = b
            else: b.p.r = b
        else:
            #set new root to b
            pass

Я не делал правильных модульных тестов для этого кода - он работал так, как я ожидал. Меня больше интересовали различия в производительности между реализациями. swap_opt обрабатывает соседние узлы немного быстрее, давая ему около 5% увеличения скорости по сравнению с компактной реализацией swap. [EDIT2] Но это зависит от дерева, используемого для тестирования и аппаратного обеспечения [/EDIT2]

Ответ 2

Ваш BST.py определяет class BST. Члены этого класса имеют элемент self.root, который может указывать на node. Ваш код, как показано, не учитывает это.

Я считаю, что вам нужно обрабатывать эти случаи:

  • Смените корень node на один из его дочерних элементов.
  • Смените корень node на не-дочерний.
  • Смените без root node с одним из своих дочерних элементов.
  • Подкачка без root node с не-дочерним без root node.

Изменить: После повторного изучения switch_1, я думаю, что вы справляетесь со всеми случаями.

Кроме того, существует вероятность, что вызывающий может запросить обмен файлом node, который не является членом дерева для node, который является членом. Или замените два узла, которые не являются членами текущего дерева. Для обнаружения этих случаев стоило бы некоторого кода, но, возможно, вы могли бы пройти с помощью dict или set для отслеживания членства в деревьях. Я не знаю, хотите ли вы считать "swap-ins" действительной или нет.

В нескольких местах вы сравниваете узлы с помощью ==. Это операция, которая может быть переопределена. Вы должны использовать is и is not для сравнения и сравнения идентичности с None.

Наконец, рассмотрим Pythonifying вашего класса BST. Это изменяемый итерируемый контейнер, поэтому он должен максимально поддерживать стандартные операции.