기술나눔

[데이터 구조] 09. 트리와 이진트리

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. 트리의 개념과 구조

1.1 트리의 개념

나무는비선형 n(n>=0)개의 제한된 노드로 구성된 계층적 관계 집합인 데이터 구조입니다.묶음뿌리가 위를 향하고 잎이 아래를 향하고 있는 거꾸로 된 나무처럼 보이기 때문에 나무라고 불립니다.

  • 루트 노드: 루트 노드에는 선행 노드가 없습니다.
  • 루트 노드를 제외한 나머지 노드는 트리와 유사한 구조를 갖는 하위 트리로 나누어집니다. 각 하위 트리의 루트 노드에는 단 하나의 선행 노드가 있고 0개 이상의 후속 노드가 있을 수 있습니다.
  • 그러므로 나무는재귀 정의의.

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

1.2 트리 관련 개념

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

  • 노드의 정도: 노드에 포함된 하위 트리의 수를 위 그림과 같이 노드의 차수라고 합니다. A는 4입니다.
  • 리프 노드 또는 터미널 노드: 차수가 0인 노드를 리프 노드라고 합니다. 위 그림에 표시된 대로 노드 B, F, G, D 및 H는 리프 노드입니다.
  • 비단말 노드 또는 분기 노드: 차수가 0이 아닌 노드. 위 그림에 표시된 대로: 노드 A, C, E는 분기 노드입니다.
  • 상위 노드 또는 상위 노드: 노드에 하위 노드가 포함된 경우 이 노드를 하위 노드의 상위 노드라고 합니다. 위 그림에 표시된 대로 A는 B의 상위 노드입니다.
  • 하위 노드 또는 하위 노드: 노드에 포함된 하위 트리의 루트 노드를 노드의 하위 노드라고 합니다. 위 그림에 표시된 대로 B는 A의 하위 노드입니다.
  • 형제 노드: 위와 같이 동일한 부모 노드를 가진 노드를 형제 노드라고 합니다. B와 C는 형제 노드입니다.
  • 나무의 정도: 트리에서 가장 큰 노드의 차수는 위에 표시된 대로 트리의 차수라고 합니다. 트리의 차수는 4입니다.
  • 노드 수준: 루트 정의부터 시작하여 루트는 첫 번째 수준이고 루트의 하위 노드는 두 번째 수준입니다.
  • 나무의 높이 또는 깊이: 위에 표시된 대로 트리의 최대 노드 수준: 트리의 높이는 3입니다.
  • 사촌 노드: 부모가 같은 레벨에 있는 노드는 위 그림과 같이 서로 사촌 관계입니다. F와 G는 서로 형제 노드입니다.
  • 노드의 조상: 위 그림과 같이 루트에서 노드까지의 분기에 있는 모든 노드: A는 모든 노드의 조상입니다.
  • 자손 : 특정 노드를 루트로 하는 하위 트리의 모든 노드를 해당 노드의 자손이라고 합니다.위에 표시된 대로: 모든 노드는 A의 자손입니다.
  • : m개의 분리된 나무의 집합을 숲이라고 합니다.

1.3 트리 표현

트리 구조는 선형 테이블보다 더 복잡하고, 이를 저장하고 표현하는 것이 더 번거롭습니다.값 범위와 노드와 노드 사이의 관계를 모두 저장해야 합니다.실제로 다음과 같이 트리를 표현하는 방법은 다양합니다.부모 표기, 자식 표기, 자식 부모 표기, 자식 형제 표기기다리다.

다음 저장소 구조를 도입하는 과정에서 다음 트리를 예로 들어 보겠습니다.여기에 이미지 설명을 삽입하세요.

1.3.1 모회사 대표

우리는 트리의 노드가 일련의 연속적인 공간에 저장되어 있으며 동시에각 노드에는 연결된 목록에서 상위 노드의 위치를 ​​나타내는 표시기가 첨부됩니다. . 즉, 각 노드는 자신이 누구인지 아는 것 외에도 부모가 어디에 있는지도 알고 있습니다.
여기에 이미지 설명을 삽입하세요.
그 중 데이터는 노드의 데이터 정보를 저장하는 데이터 필드이다. 그리고 parent는 배열에 있는 노드 부모의 첨자를 저장하는 포인터 필드입니다.
다음은 상위 표현에 대한 노드 구조 정의 코드입니다.

/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;	//树结点的数据类型,目前暂定为整型
/*结点结构*/
typedef struct TreeNode
{
	TElemType data;	//结点数据
	int parent;	//双亲位置
}TreeNode;
/*树结构*/
typedef struct
{
	TreeNode nodes[MAX_TREE_SIZE];	//结点数组
	int r, n;	//根的位置和结点数
}PTree;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

이러한 저장 구조를 사용하면 사용된 노드의 부모 포인터에 따라 부모 노드를 쉽게 찾을 수 있습니다.시간 복잡도는 0(1)입니다. , 부모가 -1이 될 때까지 트리 노드의 루트가 발견되었음을 나타냅니다. 하지만 노드의 자식 노드가 무엇인지 알고 싶다면 죄송합니다. 전체 구조를 탐색하세요.

1.3.2 남동생 대표

방금 우리는 부모의 관점과 자식의 관점에서 나무의 저장 구조를 연구했는데, 나무 노드의 형제를 살펴보면 어떨까요? 관찰 후에는 어떤 트리에서든 그 노드의 첫 번째 자식이 존재하면 고유하고 오른쪽 형제도 고유하다는 것을 발견했습니다. 따라서 노드의 첫 번째 자식과 노드의 오른쪽 형제를 가리키는 두 개의 포인터를 설정합니다.

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

/*树的孩子兄弟表示法结构定义*/
typedef struct TreeNode
{
	TElemtype data;
	struct TreeNode *firstchild, *rightsib;
} TreeNode,* pTreeNode;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.4 나무의 실제 응용

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

2. 이진트리의 개념과 구조

2.1 개념

이진 트리는 다음과 같은 유한한 노드 집합입니다.

  • 또는 비어 있음
  • 이는 루트 노드와 왼쪽 하위 트리 및 오른쪽 하위 트리라고도 알려진 두 개의 이진 트리로 구성됩니다.

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

위 그림에서 볼 수 있듯이:

  1. 이진 트리에는 차수가 2보다 큰 노드가 없습니다.
  2. 이진 트리의 하위 트리는 왼쪽과 오른쪽 하위 트리로 나눌 수 있고 순서는 바뀔 수 없으므로 이진 트리는 다음과 같습니다.정렬된 나무

참고: 모든 이진 트리는 다음 상황으로 구성됩니다.
여기에 이미지 설명을 삽입하세요.

2.2 특수 이진 트리

  1. 완전 이진 트리: 이진 트리,각 계층의 노드 수가 최대에 도달하면 이진 트리는 완전 이진 트리입니다. . 즉, 이진 트리의 수준 수가 K이고 총 노드 수가 2^k-1이면 완전 이진 트리입니다.
  2. 완전 이진 트리: 완전 이진 트리는 매우 효율적인 데이터 구조입니다.완전 이진 트리는 완전 이진 트리에서 파생됩니다. . 깊이 K의 n개 노드가 있는 이진 트리의 경우 각 노드가 깊이 K의 전체 이진 트리에서 1부터 n까지 번호가 매겨진 노드와 일대일로 대응하는 경우에만 완전 이진 트리라고 합니다. 주의할 점완전 이진 트리는 특별한 종류의 완전 이진 트리입니다.
    여기에 이미지 설명을 삽입하세요.

2.3 이진 트리의 속성

  1. 루트 노드의 레벨 수를 1로 지정하면i번째 레이어에는 최대 2^(i-1)개가 있습니다. 노드
  2. 루트 노드의 레벨 수를 1로 지정하면깊이가 h인 이진 트리의 최대 노드 수는 2^h-1입니다.
  3. 임의의 이진 트리에 대해 다음과 같은 경우차수가 0인 리프 노드의 수는 n0이고, 차수가 2인 가지 노드의 수는 n2이므로 n0=n2 +1입니다.
  4. 루트 노드의 레벨 수를 1로 지정하면,n 노드가 있는 완전 이진 트리의 깊이, h=log (n+1) (ps: log는 밑이 2이고, n+1은 로그입니다.)
  5. n개의 노드가 있는 완전한 이진 트리의 경우, 모든 노드가 위에서 아래로, 왼쪽에서 오른쪽으로 배열 순서에 따라 0부터 시작하여 번호가 매겨지면 일련 번호가 i인 노드의 경우:
    • i>0이면 위치 i에 있는 노드의 상위 번호: (i-1)/2;, i는 루트 노드 번호이고, 부모 노드는 없습니다.
    • 2i+1인 경우, 2i+1>=n 그렇지 않으면 왼쪽 자식이 없습니다.
    • 2i+2인 경우, 2i+2>=n 그렇지 않으면 오른쪽 자식이 없습니다.

2.4 이진 트리의 저장 구조

이진 트리는 일반적으로 두 가지 구조를 사용하여 저장할 수 있습니다.순차적 구조, 사슬 구조.

  1. 순차 저장
    순차 구조 저장을 사용하는 것입니다정렬저장하려면 일반적으로 배열을 사용합니다.완전한 이진 트리를 나타내는 데에만 적합합니다. , 완전한 이진 트리가 아니고 공간 낭비가 있기 때문입니다. 실제로는 힙만이 저장을 위해 배열을 사용합니다.이진 트리의 순차 저장은 물리적으로는 배열이고 논리적으로는 이진 트리입니다.
    여기에 이미지 설명을 삽입하세요.2. 체인 보관
    이진 트리의 연결된 저장 구조는 다음을 의미합니다.연결리스트를 사용하여 이진 트리 표현 즉, 체인을 사용하여 요소의 논리적 관계를 나타냅니다. **일반적인 방법은 연결된 목록의 각 노드가 데이터 필드와 왼쪽 및 오른쪽 포인터 필드의 세 가지 필드로 구성된다는 것입니다. 왼쪽 및 오른쪽 포인터는 왼쪽 자식과 오른쪽 포인터가 있는 링크 지점의 저장 주소를 제공하는 데 사용됩니다. 노드의 오른쪽 자식이 각각 위치합니다. **사슬구조는 이원사슬(binary chain)과 삼각사슬(trifurcated chain)로 구분됩니다. 현재 우리는 이원사슬을 주로 연구하고 있습니다.
    여기에 이미지 설명을 삽입하세요.

3. 이진 트리의 순차 구조 및 구현

일반적인 이진 트리는 낭비되는 공간이 많을 수 있으므로 배열에 저장하는 데 적합하지 않습니다.그리고완전한 이진 트리는 순차 구조 저장에 더 적합합니다. .실제로 우리는 보통힙(이진 트리)은 순차 구조의 배열을 사용하여 저장합니다., 여기의 힙과 운영 체제의 가상 프로세스 주소 공간에 있는 힙은 서로 다른 두 가지라는 점에 유의해야 합니다. 하나는 데이터 구조이고, 다른 하나는 운영 체제에서 메모리를 관리하는 영역 분할입니다.
구체적인 구현 및 적용에 대해서는 다음을 참조하세요.[데이터 구조] 08. 힙과 힙 응용

4. 이진 트리의 체인 구조 및 구현

4.1 이진 트리 순회

이진 트리 구조를 학습하는 가장 간단한 방법은 이를 순회하는 것입니다.소위이진 트리 순회(Traversal)는 특정 규칙에 따라 이진 트리의 노드에 대해 순차적으로 해당 작업을 수행하는 것이며 각 노드는 한 번만 작동합니다. . 노드에 액세스하여 수행되는 작업은 특정 애플리케이션 문제에 따라 다릅니다. 순회는 이진 트리에서 가장 중요한 작업 중 하나이며 이진 트리에서 다른 작업의 기초이기도 합니다.

4.1.1 선주문 순회

//根 左子树 右子树
void PrevOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

아래 그림은 재귀 프로세스를 보여줍니다.
여기에 이미지 설명을 삽입하세요.

4.1.2 순차 순회

//左子树 根 右子树
void InOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

재귀 프로세스는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.

4.1.3 후순위 순회

//左子树 右子树 根
void PostOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

재귀 프로세스는 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.

4.2.4 레벨 순서 탐색

수준 순회: 선순 순회, 순순 순회, 후순 순회 외에도 이진 트리에서 수준 순회를 수행할 수도 있습니다. 이진 트리의 루트 노드의 레벨 번호가 1이라고 가정합니다. 레벨 순서 탐색은 이진 트리의 루트 노드에서 시작하여 먼저 첫 번째 레벨의 루트 노드를 방문한 다음 두 번째 레벨의 노드를 방문합니다. 왼쪽에서 오른쪽으로 레벨을 지정한 다음 레이어의 노드 등을 지정합니다.트리의 노드를 위에서 아래로, 왼쪽에서 오른쪽으로 레이어별로 방문하는 과정이 레이어순회(layer-order traversal)이다.
삽화:
여기에 이미지 설명을 삽입하세요.

여기서 우리는 이진 트리의 선주문 순회를 수행하기 위해 대기열에 개입합니다.

// 层序遍历
void LevelOrder(pTreeNode root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			printf("NULL ");
		}
		else
		{
			printf("%d ", front->val);
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	QueueDestory(&q);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

4.1 이진 트리의 생성과 소멸

이진 트리를 생성하고 파괴하려면 다음을 사용합니다.이진 트리 순회예를 들어.

//二叉树的创建
struct TreeNode* Creat(char* arr,int n,int* i)
{ 
    if(*i<n&&arr[*i]=='#')
    {
        (*i)++;
        return NULL;
    }
    
    TreeNode* newnode=(TreeNode*)malloc(sizeof(TreeNode));
    newnode->left=NULL;
    newnode->right=NULL;

    newnode->val=arr[(*i)++];
    newnode->left=Creat(arr,n,i);
    newnode->right=Creat(arr,n,i);
    
    return newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
//二叉树的销毁
void TreeDestroy(struct TreeNode* root)
{
    if(root==NULL)
    {
        return;
    }
    TreeDestroy(root->left);
    TreeDestroy(root->right);
    free(root);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.3 이진 트리에 대한 기타 연산

다음 작업은 모두 순회라는 개념에 따라 수행됩니다.

// 二叉树节点个数
int TreeSize(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 二叉树叶子节点个数
int TreeLeafSize(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
// 二叉树第k层节点个数
int TreeLevelKSize(pTreeNode root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
// 二叉树查找值为x的节点
pTreeNode TreeFind(pTreeNode root, TreeDataType x)
{

	if (root == NULL)
	{
		return NULL;
	}
	//相等就返回
	if (root->val == x)
		return root;
	//找左子树
	pTreeNode left=TreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	//找右子树
	pTreeNode right = TreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	//都没找到
	return NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
//二叉树的高度
int TreeHeight(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	int max_left = TreeHeight(root->left) ;
	int max_right = TreeHeight(root->right);
	return max_left > max_right ? max_left + 1 : max_right + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//判断是否是完全二叉树
bool TreeComplete(pTreeNode root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		else
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestory(&q);
			return false;
		}
	}
	QueueDestory(&q);
	return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36