Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

파이썬(Python)으로 잘못된 링크가 있는 이진 트리 수정하는 방법

문제 개요

이진 트리에 오류가 하나 있는 상황을 가정해 보겠습니다. 어떤 노드의 오른쪽 자식 포인터가 같은 레벨에 있는 다른 노드를 잘못 가리키고 있는 것입니다. 이 문제를 해결하려면 오류가 발생한 노드를 찾아낸 뒤, 그 노드가 잘못 가리키는 대상 노드를 제외한 나머지 자손 노드들과 함께 해당 노드를 삭제해야 합니다. 최종적으로 수정된 이진 트리의 루트 노드를 반환하면 됩니다.

예를 들어 입력이 다음과 같다고 해봅시다.

파이썬(Python)으로 잘못된 링크가 있는 이진 트리 수정하는 방법

위 트리에서는 노드 4와 노드 6 사이에 잘못된 연결이 존재합니다. 즉, 노드 4의 오른쪽 자식 포인터가 같은 레벨의 노드 6을 가리키고 있습니다.

이 경우 출력, 즉 수정된 트리의 중위 순회(inorder) 결과는 다음과 같습니다.

2, 3, 5, 6, 7, 8,

노드 4는 노드 6을 잘못 가리키는 링크를 가지고 있으므로 트리에서 삭제됩니다.

해결 접근 방법

이 문제는 BFS(너비 우선 탐색)를 활용해 해결할 수 있습니다. 핵심 아이디어는 각 노드를 한 번씩만 방문해야 하는데, 큐에서 어떤 노드가 두 번째로 등장한다면 그것은 곧 잘못된 링크가 존재한다는 신호라는 점입니다. 구체적인 단계는 다음과 같습니다.

  • q := 루트 노드를 담고 있는 새로운 데크(deque)를 생성합니다.
  • p := 각 노드의 부모 정보를 저장할 새로운 딕셔너리(맵)를 생성합니다.
  • visited := 방문 여부를 추적할 새로운 집합(set)을 생성합니다.
  • q가 비어 있지 않은 동안 다음을 반복합니다.
    • cur := q의 가장 앞쪽 요소를 꺼냅니다.
    • 만약 cur가 visited에 이미 존재한다면(중복 방문 감지):
      • grand_p, is_left := p[p[cur][0]] — cur의 부모를 조회하고, 그 부모의 부모(조부모) 정보와 부모가 왼쪽 자식인지 여부를 얻습니다.
      • is_left가 참이면 조부모의 왼쪽 자식을 None으로 설정합니다.
      • 그렇지 않으면 조부모의 오른쪽 자식을 None으로 설정합니다.
      • 루트를 반환하고 종료합니다.
    • visited에 cur를 추가합니다.
    • cur의 왼쪽 자식이 존재하면:
      • p[왼쪽 자식] := (cur, 1)로 기록합니다.
      • q의 끝에 왼쪽 자식을 삽입합니다.
    • cur의 오른쪽 자식이 존재하면:
      • p[오른쪽 자식] := (cur, 0)으로 기록합니다.
      • q의 끝에 오른쪽 자식을 삽입합니다.
  • 모든 순회가 정상적으로 끝나면 루트를 반환합니다.

중복 방문이 감지되면, 해당 노드의 올바른 부모를 통해 조부모까지 거슬러 올라가 잘못된 링크를 가진 노드와 그 전체 서브트리를 연결 지점에서 제거하는 방식입니다.

구현 예제

아래 파이썬 코드를 통해 더 잘 이해해 보겠습니다.

import collections
class TreeNode:
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right

def insert(temp, data):
    que = []
    que.append(temp)
    while len(que):
        temp = que[0]
        que.pop(0)
        if not temp.left:
            if data is not None:
                temp.left = TreeNode(data)
            else:
                temp.left = TreeNode(0)
            break
        else:
            que.append(temp.left)
        if not temp.right:
            if data is not None:
                temp.right = TreeNode(data)
            else:
                temp.right = TreeNode(0)
            break
        else:
            que.append(temp.right)

def make_tree(elements):
    Tree = TreeNode(elements[0])
    for element in elements[1:]:
        insert(Tree, element)
    return Tree

def search_node(root, element):
    if root == None:
        return None
    if root.data == element:
        return root
    res1 = search_node(root.left, element)
    if res1:
        return res1
    res2 = search_node(root.right, element)
    return res2

def print_tree(root):
    if root is not None:
        print_tree(root.left)
        print(root.data, end=', ')
        print_tree(root.right)

def solve(root):
    q = collections.deque([root])
    p, visited = dict(), set()
    while q:
        cur = q.popleft()
        if cur in visited:
            grand_p, is_left = p[p[cur][0]]
            if is_left:
                grand_p.left = None
            else:
                grand_p.right = None
            return root
        visited.add(cur)
        if cur.left:
            p[cur.left] = (cur, 1)
            q.append(cur.left)
        if cur.right:
            p[cur.right] = (cur, 0)
            q.append(cur.right)
    return root

root = make_tree([5, 3, 7, 2, 4, 6, 8])
link_from = search_node(root, 4)
link_to = search_node(root, 6)
link_from.right = link_to
print_tree(solve(root))

입력

root = make_tree([5, 3, 7, 2, 4, 6, 8])
link_from = search_node(root, 4)
link_to = search_node(root, 6)
link_from.right = link_to

출력

2, 3, 5, 6, 7, 8,

동작 원리 정리

이 알고리즘은 시간 복잡도 O(n), 공간 복잡도 O(n)으로 트리의 모든 노드를 한 번씩 처리합니다. BFS 순회 중 동일한 노드가 두 번 큐에 들어오면, 부모 정보 딕셔너리 p를 이용해 해당 노드의 실제 부모와 조부모를 추적하고, 조부모의 자식 포인터를 None으로 설정하여 오류 노드와 그 서브트리 전체를 안전하게 제거합니다. 이렇게 하면 잘못 가리키던 대상 노드(예제에서는 노드 6)와 그 자손들은 그대로 유지되면서 트리가 정상적인 이진 트리 구조로 복원됩니다.