목차
1: 나무란 무엇인가
[1] 나무의 개념
앞에서 배운 시퀀스 테이블과 연결 목록은 선형 구조에 속하지만 트리는 n(n>=0)개의 유한 노드로 구성된 계층적 관계의 집합인 비선형 데이터 구조입니다 . 뿌리가 위를 향하고 잎이 아래를 향하는 거꾸로 된 나무처럼 보인다고 하여 나무라고 합니다 .
삽화:
(1) 트리에는 그림에서 노드 A인 루트 노드라는 특수 노드가 있습니다 .
(2) 루트 노드를 제외한 다른 노드들은 M(M>0) 개의 상호 분리된 집합 T1 , T2 , ... , Tm 으로 나뉘며 , 각 집합 Ti(1<= i <= m)는 구조가 A 서브트리이다. 나무와 비슷합니다 . 각 하위 트리의 루트 노드에는 하나의 선행 작업만 있고 0개 이상의 후속 작업이 있을 수 있습니다.
(3) 부모-자식 노드: 예를 들어 A는 B, C, D의 부모 노드이고 E와 F는 B 의 자식 노드입니다.
(4) 리프 노드: E, F, C, G, H 와 같이 자식 노드가 없는 노드.
참고: 트리의 하위 트리는 분리되어 있으며 루트 노드를 제외한 다른 노드에는 부모 노드가 하나만 있습니다.
[2] 나무에 대한 몇 가지 다른 중요한 개념
삽화:
(1) 노드 차수: 노드에 포함된 하위 트리의 수를 노드 차수라고 하며 위 그림과 같이 A 는 6 입니다 .(2) 비종단 노드 또는 분기 노드: 차수가 0이 아닌 노드, 위 그림과 같이 D , E , F , G... 와 같은 노드 가 분기 노드입니다.(3) 형제 노드: 부모 노드가 같은 노드를 형제 노드라고 하며 , 위 그림과 같이 B 와 C 는 형제 노드입니다.(4) 트리의 차수: 트리에서 가장 큰 노드의 차수를 트리의 차수라고 하며 , 위와 같이 트리의 차수는 6 입니다.(5) 노드의 계층 구조: 루트의 정의에서 시작하여 루트는 첫 번째 레이어이고 루트의 자식 노드는 두 번째 레이어 등입니다.(6) 트리의 높이 또는 깊이: 트리 노드의 최대 레벨 위의 그림과 같이 트리의 높이는 4 입니다.(7) 노드의 조상: 루트에서 노드의 가지에 있는 모든 노드까지 , 위 그림과 같이 A 는 모든 노드의 조상입니다.(8) 자식 및 손자: 특정 노드를 루트로 하는 하위 트리의 모든 노드를 해당 노드의 자손이라고 합니다 . 위에 표시된 것처럼 모든 노드는 A 의 자손 입니다.(9) 포레스트: m( m>0) 개의 분리된 트리 의 집합을 포레스트라고 합니다 .
[3] 트리의 여러 표현 방법
(1) 시퀀스 테이블을 사용하여 자식 노드의 주소 저장 (복잡한 구조)![]()
(2) 트리의 차수(가장 큰 노드의 차수)를 설명하고 포인터 배열을 설정하여 자식 노드의 주소를 저장
(3) 구조 배열 저장
(4) 왼쪽 자식과 오른쪽 형제의 표기법(일반적으로 사용됨, 구조가 비교적 간단하고 논리가 비교적 명료함)
2: 이진 트리란 무엇인가
【1】개념 및 특징
개념:
이진 트리는 비어 있거나 루트 노드와 왼쪽 및 오른쪽 하위 트리라고 하는 두 개의 이진 트리로 구성된 유한 한 노드 집합입니다 .
특징:
1. 각 노드에는 최대 두 개의 하위 트리가 있습니다. 즉, 이진 트리에는 차수가 2보다 큰 노드가 없습니다.
2. 이진 트리의 하위 트리는 좌우로 나뉘며 하위 트리의 순서는 뒤바뀔 수 없습니다.
[2] 두 개의 특수 이진 트리
1. 전체 이진 트리:
모든 리프 노드는 마지막 레이어에 있습니다.
모든 분기 노드에는 두 개의 자식이 있습니다.
2. 완전한 이진 트리:
이 이진 트리에 N개의 레이어가 있다고 가정하면 첫 번째 N-1 레이어는 가득 차 있어야 합니다.
마지막 층은 채워질 수 있지만 왼쪽에서 오른쪽으로 연속적이어야 합니다.
[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=LogN입니다.
[4] 이진 트리의 두 가지 저장 방법
(1) 순차 구조 저장(어레이)
순차 구조 저장은 저장을 위해 배열을 사용하는 것입니다 . 일반적으로 배열은 완전한 이진 트리를 나타내는 데만 적합합니다 . 완전한 이진 트리가 아닌 경우 공간이 낭비되기 때문입니다. 실제로는 힙만 저장소에 배열을 사용합니다 . 이진 트리 순차 저장소는 물리적으로 배열이고 논리적으로 이진 트리입니다.![]()
(2) 사슬 구조 저장
연결된 목록은 이진 트리를 나타내는 데 사용됩니다 . 즉, 요소의 논리적 관계를 나타내는 데 체인이 사용됩니다.
일반적인 방법은 연결된 목록의 각 노드가 데이터 필드와 왼쪽 및 오른쪽 포인터 필드의 세 필드로 구성되고 왼쪽 및 오른쪽 포인터를 사용하여 왼쪽 자식 및 노드의 오른쪽 자식이 있습니다.
체인 구조는 다시 바이너리 체인과 트리플 체인 으로 나뉘며 현재 바이너리 체인이 일반적으로 사용되며 레드-블랙 트리와 같은 고급 데이터 구조는 트리플 체인을 사용할 것입니다.
3: 힙 구현
힙은 배열로 구현된 이진 트리이며 일반적으로 완전한 이진 트리를 구현하는 데 사용됩니다.
힙은 큰 힙과 작은 힙으로 나뉩니다.
큰 더미: 아버지 >= 자식
작은 더미: 아이 >= 아버지
이 문서는 많은 것을 구현합니다.
힙의 구현은 시퀀스 테이블과 유사하므로 여기에서는 자세히 설명하지 않고 기본 인터페이스 구현만 설명합니다.
시퀀스 테이블 링크 첨부: https://blog.csdn.net/2301_76269963/article/details/129352041?spm=1001.2014.3001.5501
【1】데이터 삽입
(1) 판단 확장은 시퀀스 테이블과 일치합니다.
(2) 시퀀스 테이블과 일치하는 데이터를 저장합니다.
(3) 데이터 삽입 후 삽입 후에도 여전히 큰 더미가 있는지 확인해야 하므로 부모-자식 관계를 조정해야 합니다.
조정을 고려하기 전에 아버지의 첨자와 자녀의 첨자의 관계를 살펴보자.
우리는 그런 규칙을 찾을 수 있습니다
아버지 첨자 = (자녀 첨자 - 1)/2.
이 규칙에 따라 조정 함수를 설계하는데, 삽입할 데이터가 부모보다 크면 부모보다 작아지거나 루트 노드가 될 때까지 둘 을 교체한다 .
후속 삭제에도 스왑 조정이 필요하기 때문에 스왑을 별도로 HeapSwap( ) 함수로 패키징할 수 있습니다.
암호:
[2] 데이터 삭제(힙 정렬 기준)
기본 아이디어:
(1) 힙의 데이터를 삭제하려면 루트 데이터를 삭제 해야 합니다 .
(2) 원래 힙의 구조를 파괴하는 시퀀스 테이블처럼 직접 덮어쓸 수 없다는 점에 유의 해야 합니다 .
삽화:
(3) 원본 루트 데이터를 삭제하고 조정해야 합니다 .
삭제: 루트를 마지막 리프와 교환한 다음 크기(유효 데이터 수)를 1로 직접 줄일 수 있습니다.
삽화:
조정: 첨자 0 에서 하향 조정 . 자식이 아버지보다 클 경우 두 자식보다 클 때까지 교체하거나 잎이 조정됨 각 판단 전에 더 큰 두 자식을 비교하여 구조가 파괴되는 것을 방지 큰 힙 , 더 오래된 자식을 부모와 바꾸고 반복합니다.
삽화:
암호:
전체 코드:
Heap.h (필요한 헤더 파일 포함, 함수 및 구조 선언)
#pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> typedef int HPDataType; typedef struct Heap { //存储数据 HPDataType* a; //有效数据个数 int size; //容量 int capacity; }HP; //初始化 void HeapInit(HP* hp); //销毁 void HeapDestory(HP* hp); //交换函数 void HeapSwap(int* p1, int* p2); //判空函数 bool HeapEmpty(HP* hp); //调整函数 void AdjustUp(HPDataType* a,int child); //向下调整,n是有效个数 void AdjustDown(HPDataType* a, int n, int parent); //插入数据 void HeapPush(HP* hp, HPDataType x); //打印数据 void HeapPrint(HP* hp); //删除数据 void HeapPop(HP* hp);
Heap.c(인터페이스 구현)
#define _CRT_SECURE_NO_WARNINGS 1 #include "Heap.h" //初始化 void HeapInit(HP* hp) { //断言,不能传空的结构体指针 assert(hp); hp->a = NULL; //初始化size和容量都为0 hp->size = hp->capacity = 0; } //销毁 void HeapDestory(HP* hp) { free(hp->a); hp->size = hp->capacity = 0; } //交换函数 void HeapSwap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } //判空函数 bool HeapEmpty(HP* hp) { return hp->size == 0; } //调整函数 //向上调整 void AdjustUp(HPDataType* a, int child) { //断言,不能传空指针 assert(a); //找到父结点的下标 int parent = (child - 1) / 2; //循环,以child到树根为结束条件 while (child > 0) { //如果父结点比child下,交换并更新 if (a[child] > a[parent]) { HeapSwap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } //如果父结点比child大,跳出循环 else { break; } } } //向下调整 void AdjustDown(HPDataType* a, int n, int parent) { //默认左孩子最大 int child = parent * 2 + 1; //当已经调整到超出数组时结束 while (child<n) { //找出两个孩子中大的一方 //考虑右孩子不存在的情况 if (child+1<n&&a[child + 1] > a[child]) { //如果右孩子大,child加1变成右孩子 child++; } //如果父亲比大孩子小,进行调整,否则跳出 if (a[child] > a[parent]) { HeapSwap(&a[child], &a[parent]); //迭代 parent = child; child = parent * 2 + 1; } else { break; } } } //插入数据 void HeapPush(HP* hp, HPDataType x) { if (hp->size == hp->capacity) { //判断扩容多少 int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2; //扩容 HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity); //更新 hp->capacity = newcapacity; hp->a = tmp; } //存储数据 hp->a[hp->size] = x; hp->size++; //进行调整 AdjustUp(hp->a, hp->size-1); } //打印数据 void HeapPrint(HP* hp) { //断言,不能传空的结构体指针 assert(hp); int i = 0; for (i = 0; i < hp->size; i++) { printf("%d ", hp->a[i]); } printf("\n"); } //删除数据 void HeapPop(HP* hp) { //断言,不能传空的结构体指针 assert(hp); //如果为空,不能删除,避免数组越界 assert(!HeapEmpty(hp)); //不为空,先交换根和最后一片叶子,然后size减1 HeapSwap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; AdjustDown(hp->a, hp->size, 0); }
text.c (테스트)
#define _CRT_SECURE_NO_WARNINGS 1 #include "Heap.h" void text1() { HP hp; HeapInit(&hp); HPDataType a[] = { 70,30,56,25,15,10.85,79}; int i = 0; for (i = 0; i < sizeof(a) / sizeof(a[0]); i++) { HeapPush(&hp, a[i]); } HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapDestory(&hp); } int main() { text1(); }