Обмен технологиями

[Структура данных] 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;
  • Уровень узла: Начиная с определения корня, корень — это 1-й уровень, дочерние узлы корня — 2-й уровень и так далее;
  • высота или глубина дерева: Максимальный уровень узлов в дереве, как показано выше: высота дерева равна 3;
  • двоюродный узел: Узлы, родители которых находятся на одном уровне, являются двоюродными братьями друг друга, как показано на рисунке выше: F и G — узлы-братья друг друга;
  • Предок узла: Все узлы на ветвях от корня до узла, как показано на рисунке выше: A — предок всех узлов;
  • потомки : Любой узел в поддереве, корнем которого является определенный узел, называется потомком этого узла.Как показано выше: все узлы являются потомками A.
  • лес: Совокупность m непересекающихся деревьев называется лесом;

1.3 Древовидное представление

Древовидная структура сложнее линейной таблицы, ее сложнее хранить и представлять.Необходимо сохранить как диапазон значений, так и связь между узлами и узлами.На самом деле существует множество способов представления деревьев, например:Обозначение родителя, обозначение дочернего элемента, обозначение дочернего родителя и обозначение дочернего братаждать.

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

1.3.1 Представительство родителей

Мы предполагаем, что узлы дерева хранятся в множестве непрерывных пространств, и в то же времяК каждому узлу прикреплен индикатор, указывающий положение его родительского узла в связанном списке. . Другими словами, каждый узел не только знает, кто он, но и знает, где находятся его родители.
Вставьте сюда описание изображения
Среди них данные — это поле данных, в котором хранится информация данных узла. Родитель — это поле указателя, в котором хранятся индексы родителей узла в массиве.
Ниже приведен код определения структуры узла для нашего родительского представления.

/*树的双亲表示法结点结构定义*/
#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. полное двоичное дерево: Полное двоичное дерево — очень эффективная структура данных.Полные бинарные деревья получаются из полных бинарных деревьев. . Двоичное дерево с n узлами глубины K называется полным двоичным деревом тогда и только тогда, когда каждый узел взаимно однозначно соответствует узлам с номерами от 1 до n в полном двоичном дереве глубины K. Чтобы быть осторожнымПолное двоичное дерево — это особый вид полного двоичного дерева.
    Вставьте сюда описание изображения

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 иначе левого дочернего элемента нет
    • Если 2и+2, 2i+2>=n иначе нет подходящего дочернего элемента

2.4 Структура хранения двоичного дерева

Двоичные деревья обычно можно хранить с использованием двух структур:Последовательная структура, цепная структура.

  1. последовательное хранение
    Для хранения последовательной структуры следует использоватьмножествоДля хранения обычно используйте массивПодходит только для представления полных двоичных деревьев. , потому что это не полное двоичное дерево и будет пустая трата места. На самом деле только куча использует массивы для хранения.Последовательное хранилище двоичных деревьев физически представляет собой массив, а логически — двоичное дерево.
    Вставьте сюда описание изображения2. Хранение цепи
    Связанная структура хранения двоичного дерева относится к:Используйте связанный список для представления двоичного дерева , то есть использование цепочек для обозначения логической связи элементов. **Обычный метод заключается в том, что каждый узел в связанном списке состоит из трех полей: поля данных и полей левого и правого указателей. Левый и правый указатели используются для указания адресов хранения точек связи, где находятся левый дочерний узел и узел. правый дочерний элемент узла расположены соответственно. **Цепные структуры делятся на бинарные цепи и тройчатые цепи. В настоящее время мы в основном изучаем бинарные цепи.
    Вставьте сюда описание изображения

3. Последовательная структура и реализация двоичного дерева.

Обычные бинарные деревья не подходят для хранения в массивах, так как может оказаться много ненужного пространства.иПолные двоичные деревья больше подходят для хранения последовательной структуры. .На самом деле мы обычноКуча (двоичное дерево) использует для хранения массив последовательных структур., следует отметить, что куча здесь и куча в адресном пространстве виртуального процесса операционной системы — это две разные вещи: одна — это структура данных, а другая — сегментация области, которая управляет памятью в операционной системе.
Подробнее о конкретной реализации и применении см.:[Структура данных] 08. Куча и приложения кучи

4. Цепная структура бинарного дерева и ее реализация.

4.1 Обход двоичного дерева

Самый простой способ изучить бинарную древовидную структуру — пройти по ней.так называемыеОбход бинарного дерева (Trversal) заключается в последовательном выполнении соответствующих операций над узлами бинарного дерева по определенным правилам, причем каждый узел обрабатывается только один раз. . Операции, выполняемые при доступе к узлам, зависят от конкретной проблемы приложения. Обход — одна из наиболее важных операций над двоичным деревом, а также основа для других операций над двоичным деревом.

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. Обход уровня начинается с корневого узла двоичного дерева. Сначала он посещает корневой узел первого уровня, затем посещает узлы второго уровня. уровень слева направо, а затем третий уровень. Узлы слоя и так далее.Процесс посещения узлов дерева послойно сверху вниз и слева направо представляет собой обход послойного порядка.
Иллюстрация:
Вставьте сюда описание изображения

Здесь мы вмешиваемся в очередь, чтобы выполнить предварительный обход двоичного дерева.

// 层序遍历
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