데이터 구조 - 단서 트리

1. 단서 이진 트리를 사용하는 이유는 무엇입니까?

먼저 일반 이진 트리의 단점을 살펴보겠습니다. 다음은 일반적인 이진 트리(체인 저장 방법)입니다.

여기에 이미지 설명을 삽입하세요.
얼핏 보면 일관성이 없어 보이죠? 전체 구조에는 총 7개의 노드와 총 14개의 포인터 필드가 있으며 그 중 8개의 포인터 필드가 비어 있습니다. n개의 노드가 있는 이진 트리의 경우 총 n+1개의 널 포인터 필드가 있습니다. 이 규칙은 모든 이진 트리에 적용됩니다.

너무 많은 널 포인터 필드가 낭비되는 것 같지 않나요? 데이터 구조와 알고리즘 학습의 초점은 시간 효율성과 공간 활용을 향상시키는 방법을 찾는 것입니다. 너무 많은 포인터 필드가 낭비됩니다. 정말 낭비입니다!

따라서 우리는 그것들을 잘 활용하고 이진 트리 데이터 구조를 더 잘 사용하는 데 도움이 되는 방법을 찾아야 합니다.

그렇다면 어떻게 활용해야 할까요?

이진 트리 탐색의 핵심은 이진 트리의 비선형 구조의 노드를 선형 시퀀스로 변환하여 쉽게 탐색할 수 있도록 하는 것입니다.

예를 들어 위 그림의 순회 시퀀스는 DBGEACF입니다.

선형 시퀀스(선형 테이블)의 경우 직접 선행자와 직접 후속자의 개념을 갖습니다. 예를 들어 순회 시퀀스에서 B의 직접 선행자는 D이고 직접 후속자는 G입니다.

B의 직접 선행자와 직접 후임자를 알 수 있는 이유는 순차 순회 알고리즘에 따라 이진 트리의 순차 순회 시퀀스를 작성한 다음 이 시퀀스를 사용하여 전임자와 후임자가 누구인지 알 수 있기 때문입니다. WHO.

직계 선배와 직계 후계자는 이진 트리를 통해 직접 얻을 수 없습니다. 왜냐하면 이진 트리에서는 부모와 자식 노드 사이에 직접적인 관계만 있기 때문입니다. 즉, 이진 트리의 노드 포인터 필드에는 주소만 저장됩니다. 그 자식 노드.

현재 요구 사항은 이진 트리에서 순차 순회 모드로 노드의 직접 전임자와 직접 후임자를 직접 얻을 수 있기를 원한다는 것입니다.

이때 단서 이진 트리를 사용해야 합니다.

2. 단서 이진 트리란 무엇입니까?

물론 직계 전임자와 직계 후임자의 주소를 저장하려면 반드시 노드의 포인터 필드를 사용해야 합니다.

실제로 위 그림의 일반 이진 트리(중간 순서로 순회하여 얻은 시퀀스)에서 일부 노드(포인터 필드가 비어 있지 않은 노드)는 노드의 왼쪽 자식 G와 같은 직접 전임자 또는 후임자를 찾을 수 있습니다. E 노드 E의 직접적인 전임자이고, 노드 A의 오른쪽 자식 C는 노드 A의 직접적인 후임자입니다.

하지만 일부 노드에서는 작동하지 않습니다(포인터 필드가 비어 있음).예를 들어 노드 G의 직계 후계자는 E이고 직계 전임자는 B입니다. 그러나 이진 트리에서는 이러한 결론을 도출할 수 없습니다. 어떻게 하나요? 노드 G의 두 포인터 필드가 모두 NULL이고 사용되지 않은 것을 확인했습니다. 따라서 이 두 포인터를 사용하여 각각 이전 항목과 후속 항목을 가리키는 것이 좋지 않을까요?

여기에 이미지 설명을 삽입하세요.
그것은 진정으로 두 세계의 최고이며 천국의 일치입니다! 하지만 문제는 해결되지 않습니다!

널 포인터 필드를 사용하여 선행자 또는 후임자를 가리키기 때문에 이는 노드 E 및 노드 B와 같이 포인터 필드가 비어 있지 않은 노드의 경우 모순됩니다.

갈등이 있기 때문에 갈등의 근본 원인을 찾아 해결해야 합니다.

모순의 원인은 노드의 포인터 필드가 비어 있고 비어 있지 않은 경우 포인터의 포인팅이 일치하지 않는다는 것입니다. 즉, 비어 있지 않을 때 자식을 가리키는 포인터와 포인터가 비어 있을 때의 선행자 또는 후속자 사이에 모순이 있습니다.

그런 다음 올바른 약을 복용하고 비어 있는 포인터 필드와 비어 있지 않은 포인터 필드를 구별하고 포인터에 명확하게 알려줍니다. 비어 있지 않으면 자식을 가리키고 비어 있으면 전임자 또는 후임자를 가리킵니다. 이를 위해서는 두 포인터 각각에 플래그 비트를 추가해야 합니다.

여기에 이미지 설명을 삽입하세요.

그리고 다음 규칙에 동의합니다:
left_flag == 0일 때 포인터 left_child는 왼쪽 자식을 가리킵니다
. left_flag == 1일 때 포인터 left_child는 바로 전임자를 가리킵니다.
right_flag == 0일 때 포인터 right_child는 오른쪽을 가리킵니다. right_flag
== 1이면 포인터 right_child는 직전 선행자를 가리킵니다.

이진 트리의 노드를 변경해야 합니다.

/*线索二叉树的结点的结构体*/
typedef struct Node {
    
    
    char data; //数据域
    struct Node *left_child; //左指针域
    int left_flag; //左指针标志位
    struct Node *right_child; //右指针域
    int right_flag; //右指针标志位
} TTreeNode;

플래그 비트를 사용하면 모든 것을 정리할 수 있습니다. 우리는 직계 전임자와 후임자 단서에 대한 포인터를 호출합니다. 플래그가 0인 포인터는 자식을 가리키는 포인터이고, 플래그가 1인 포인터는 단서입니다.

이진 연결 리스트 트리는 위와 같은 노드 구조를 가지며 모든 널 포인터를 단서로 변환하는데 이러한 이진 트리가 이진 단서 트리입니다.

3. 단서 이진 트리를 만드는 방법은 무엇입니까?

일반적인 이진 트리에서 특정 순회 순서로 노드의 직접적인 선행자 또는 후임자를 얻으려면 이를 알기 전에 매번 순회 순서를 얻기 위해 순회해야 합니다. 단서 이진 트리에서는 단 한 번만 순회하면 됩니다.(단서 이진 트리를 생성할 때 순회) 그 후 단서 이진 트리는 각 노드의 직접적인 선행자와 후임자를 "기억"할 수 있으므로 얻을 필요가 없습니다. 미래의 순회 순서를 통해 이를 수행합니다.선구자 또는 후속자.

일반적인 이진 트리를 특정 순회 방법에 따라 단서 이진 트리로 변환하는 과정을 이진 트리의 스레딩이라고 합니다.

다음으로 다음 이진 트리 단서를 단서 이진 트리로 변환하기 위해 순차 순회를 사용합니다.
여기에 이미지 설명을 삽입하세요.
플래그 비트 1이 있는 포인터를 사용하여 순서대로 시퀀스를 순회하여 이전 항목 또는 후속 항목을 가리킵니다
여기에 이미지 설명을 삽입하세요.
. , 노드 D에는 직접적인 선행자가 없고, 노드 F에는 직접적인 후속자가 없으므로 포인터는 NULL입니다.

이 시점에서 우리는 n개의 노드가 있는 이진 트리에서 n+1개의 널 포인터 필드로 인해 발생하는 낭비를 해결했습니다. 해결책은 널 포인터 필드를 활용하기 위해 각 노드의 포인터에 플래그 비트를 추가하는 것입니다. 플래그 비트는 0 또는 1의 부울 값을 저장하는데, 이는 낭비되는 널 포인터 필드에 비해 상대적으로 비용 효율적입니다. 또한 이진 트리에는 새로운 기능이 있습니다. 특정 순회 순서에 따라 노드 간의 선행 및 후속 관계를 이진 트리에 저장할 수 있습니다.

4. 단서의 실현

단서 이진 트리는 일반 이진 트리에서 얻어지며, 특정 순회 순서로 얻어집니다. 노드의 선행자와 후행자를 알아야 단서를 설정할 수 있고, 선행자와 후행자의 관계는 이진 트리를 통해 직접적으로 반영될 수 없기 때문에, 관계는 이진 트리를 순회하여 얻은 선형 수열을 통해서만 얻을 수 있습니다. 따라서 일종의 순회 방법을 통해 선행자와 후행자 관계가 있는 시퀀스를 얻은 후 노드의 널 포인터를 수정하고 단서를 설정할 수 있습니다.

즉, 스레딩의 본질은 특정 순회 순서로 이진 트리를 순회하는 프로세스 중에 노드의 널 포인터를 수정하여 해당 순회 순서의 바로 전임자 또는 직접 후임자를 가리키는 프로세스입니다.

따라서 코드의 일반적인 구조는 여전히 동일합니다. 순회 코드의 인쇄 코드를 스레드 코드로 바꾸고 다른 변경 사항을 적용하면 됩니다.

다음 그림은 세 가지 유형의 단서를 소개하는 예입니다.

스레드되지 않은 이진 트리에는 기본값이 0인 모든 플래그가 있습니다.

여기에 이미지 설명을 삽입하세요.
4.1 시퀀스 간 스레딩

순차 순회 순서에 따라 단서를 얻은 후 다음 그림을 얻을 수 있습니다.
여기에 이미지 설명을 삽입하세요.
먼저 다음 내용을 다시 명확히하겠습니다.

  • 이진 트리를 순회하면서 스레딩을 수행합니다.
  • 중위 순회 순서는 왼쪽 하위 트리 >> 루트 >> 오른쪽 하위 트리입니다.
  • 스레딩은 널 포인터 필드와 해당 플래그 비트라는 두 가지를 수정합니다.
  • 수정하는 방법? 널 포인터 필드를 바로 전임자 또는 후임자로 설정합니다.

그래서 우리의 질문은 다음과 같습니다:

  1. 모든 널 포인터 필드를 찾으십시오.
  2. 널 포인터 필드가 속한 노드, 즉 선주문 순서대로 직접 선행 노드와 직접 후행 노드를 찾습니다.
  3. 포인터가 단서라고 부르도록 널 포인터 필드와 해당 플래그의 내용을 수정합니다.

참고: 이진 트리를 순회할 때 재귀를 사용했으므로 스레딩할 때도 이를 사용할 것입니다.

구체적인 코드는 다음과 같습니다.

//全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 中序线索化
 */
void inorder_threading(TTreeNode *root)
{
    
    
    if (root == NULL) {
    
     //若二叉树为空,做空操作
        return;
    }
    inorder_threading(root->left_child);
    if (root->left_child == NULL) {
    
    
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
    
    
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
    inorder_threading(root->right_child);
}

4.2 선주문 스레딩

사전 주문 순서에 따라 힌트를 얻은 후 다음 그림을 얻을 수 있습니다.
여기에 이미지 설명을 삽입하세요.
구체적인 코드는 다음과 같습니다.

// 全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 先序线索化
 */
void preorder_threading(TTreeNode *root)
{
    
    
    if (root == NULL) {
    
    
        return;
    }
    if (root->left_child == NULL) {
    
    
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
    
    
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
    if (root->left_flag == 0) {
    
    
        preorder_threading(root->left_child);
    }
    if (root->right_flag == 0) {
    
    
        preorder_threading(root->right_child);
    }
}

4.3 사후 스레딩

사후 순회 순서에 따라 단서를 얻은 후 다음 그림을 얻을 수 있습니다.

여기에 이미지 설명을 삽입하세요.
구체적인 코드는 다음과 같습니다.

//全局变量 prev 指针,指向刚访问过的结点
TTreeNode *prev = NULL;
 
/**
 * 后序线索化
 */
void postorder_threading(TTreeNode *root)
{
    
    
    if (root == NULL) {
    
    
        return;
    }
    postorder_threading(root->left_child);
    postorder_threading(root->right_child);
    if (root->left_child == NULL) {
    
    
        root->left_flag = 1;
        root->left_child = prev;
    }
    if (prev != NULL && prev->right_child == NULL) {
    
    
        prev->right_flag = 1;
        prev->right_child = root;
    }
    prev = root;
}

5. 요약

스레드 이진 트리는 이진 트리의 널 포인터 필드를 최대한 활용하고 이진 트리에 새로운 기능을 제공합니다. 한 번의 순회를 통한 스레딩 후 해당 노드 간의 선행 및 후속 관계가 이진 트리에 저장될 수 있습니다.

따라서 노드의 직접 선행 노드나 후속 노드를 찾기 위해 이진 트리를 자주 탐색해야 하는 경우 단서 이진 트리를 사용하는 것이 매우 적합합니다.

추천

출처blog.csdn.net/gghhb12/article/details/136082932