树的相关概念
![](https://i-blog.csdnimg.cn/blog_migrate/52d4d2e1a9324139fe62c89361eec157.png)
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推; 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙 森林:由m(m>0)棵互不相交的树的集合称为森林;
二叉树是度最大为2的树,特殊的二叉树又可以分为满二叉树和完全二叉树。
![](https://i-blog.csdnimg.cn/blog_migrate/ac00b2e347fb615e4d539b10391bba7e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e43e8eaa446a010896c8f03fab442536.png)
上边给出了满二叉树和完全二叉树的样图。如果更通俗的区别满二叉树和完全二叉树,可以按以下方法:
满二叉树:是所有叶子节点都在最后一层,就是所有的分支节点都有两个孩子
完全二叉树:是前n-1层都是满的,最后一层从左到右是连续的
二叉树的性质
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点. 2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 . 3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1 4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2 为底,n+1为对数) 5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有: 1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点 2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子 3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
二叉树的顺序实现
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。
堆的概念及结构
![](https://i-blog.csdnimg.cn/blog_migrate/99e513579647ef819a7c2e89ca77ee38.png)
堆的性质:
1、堆中某个节点的值总是不大于或不小于其父节点的值(也就是大堆或者小堆); 2、堆总是一棵完全二叉树。
![](https://i-blog.csdnimg.cn/blog_migrate/ac4b37073223defeb59b8992228fa3ba.png)
堆的代码实现
//Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Heap {
DataType* arr;
int size;
int capacity;
}Hp;
void AdjustUp(DataType* arr, int child);//向上调整
void AdjustDown(DataType* arr, int size, int parent);//向下调整
void Swap(DataType* p1, DataType* p2);//交换
void HeapInit(Hp* hp);//初始化
void HeapDestroy(Hp* hp);//销毁
void HeapPrint(Hp* hp);//打印
void HeapPush(Hp* hp, DataType x);//入数据
void HeapPop(Hp* hp);//删除堆顶的数据
bool HeapEmpty(Hp* hp);//判空
int HeapSize(Hp* hp);//堆存放数据个数
DataType HeapTop(Hp* hp);//获取堆顶的数据
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//heap.c
void HeapInit(Hp* hp) {
assert(hp);
hp->arr = NULL;
hp->capacity = hp->size = 0;
}
void HeapDestroy(Hp* hp) {
assert(hp);
free(hp->arr);
hp->size = hp->capacity = 0;
}
void HeapPrint(Hp* hp) {
for (int i = 0; i < hp->size; i++) {
printf(" %d ", hp->arr[i]);
}
printf("\n");
}
//向上调整
void AdjustUp(DataType* arr, int child) {
assert(arr);
int parent = (child - 1) / 2;
while (child) {
if (arr[child] < arr[parent]) {
Swap(&arr[child], &arr[parent]);
//DataType tmp = arr[child];
//arr[child] = arr[parent];
//arr[parent] = tmp;
child = parent;
parent = (child - 1) / 2;
}
else {
break;
}
}
}
//向下调整
void AdjustDown(DataType* arr, int size, int parent) {
assert(arr);
int kid = parent * 2 + 1;
while (kid < size) {
//选择出左右孩子较大的那一个
if (kid + 1 < size && arr[kid + 1] < arr[kid]) {
kid++;
}
//如果父节点比子节点小,就交换
if (arr[kid] < arr[parent]) {
Swap(&arr[parent], &arr[kid]);
//迭代
parent = kid;
kid = parent * 2 + 1;
}
else {
break;
}
}
}
//交换
void Swap(DataType* p1, DataType* p2) {
DataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void HeapPush(Hp* hp, DataType x) {
assert(hp);
if (hp->capacity == hp->size) {
//增容
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
DataType* tmp = (DataType*)realloc(hp->arr, sizeof(DataType) * newCapacity);
if (tmp == NULL) {
perror("realloc\n");
exit(-1);
}
hp->arr = tmp;
hp->capacity = newCapacity;
}
//插入数据
hp->arr[hp->size] = x;
hp->size++;
AdjustUp(hp->arr, hp->size - 1);
}
//判空
bool HeapEmpty(Hp* hp) {
assert(hp);
return hp->size == 0;
}
//堆存放数据个数
int HeapSize(Hp* hp) {
assert(hp);
return hp->size;
}
//删除堆顶的数据
void HeapPop(Hp* hp) {
assert(hp && !HeapEmpty(hp));
Swap(&(hp->arr[0]), &(hp->arr[hp->size - 1]));
hp->size--;
AdjustDown(hp->arr, hp->size, 0);
}
//获取堆顶的数据
DataType HeapTop(Hp* hp) {
assert(hp && !HeapEmpty(hp));
return hp->arr[0];
}
堆的应用
堆排序
//升序,建大堆
//降序,建小堆
void HeapSort(int* a,int n) {
//首先找到最后一个分支节点,依次递减循环向下调整
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(a, n, i);
}
//依次选数,调堆
for (int i = n - 1; i > 0; i--) {
//将堆头数据和队尾数据交换,即排好一个数,i--意思是排好的数不看做堆里的数据,继续调未排的数据
Swap(&a[0], &a[i]);
//向下调堆
AdjustDown(a, i, 0);
}
}
topK问题
即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大 。
找最大的k个值,建小堆
找最小的k个值,建大堆
void PrintTopK(int* a, int n, int k)
{
// 1. 建堆--用a中前k个元素建堆
Hp hp;
HeapInit(&hp);
for (int i = 0; i < k; i++) {
HeapPush(&hp, a[i]);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满足则替换
for (int i = k; i < n; i++) {
if (a[i] > HeapTop(&hp)) {
HeapPop(&hp);
HeapPush(&hp, a[i]);
}
}
HeapPrint(&hp);
HeapDestroy(&hp);
}
void TestTopk()
{
int n = 10000;
int* a = (int*)malloc(sizeof(int) * n);
srand(time(0));
for (int i = 0; i < n; ++i)
{
a[i] = rand() % 1000000;
}
//设置10个比100w大的数
a[5] = 1000000 + 1;
a[1231] = 1000000 + 2;
a[531] = 1000000 + 3;
a[5121] = 1000000 + 4;
a[115] = 1000000 + 5;
a[2335] = 1000000 + 6;
a[9999] = 1000000 + 7;
a[76] = 1000000 + 8;
a[423] = 1000000 + 9;
a[3144] = 1000000 + 10;
PrintTopK(a, n, 10);
}
二叉树的链式实现
//BinaryTree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef char BTDataType;
typedef struct BinaryTreeNode {
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
BTNode* CreatBinaryTree();//造树
void PreOrder(BTNode* root);// 二叉树前序遍历
void InOrder(BTNode* root);// 二叉树中序遍历
void PostOrder(BTNode* root);// 二叉树后序遍历
int BinaryTreeSize(BTNode* root);// 二叉树节点个数
int BinaryTreeLeafSize(BTNode* root);// 二叉树叶子节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);// 二叉树第k层节点个数
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);// 二叉树查找值为x的节点
int BinaryTreeDepth(BTNode* root);//二叉树深度/高度
void LevelOrder(BTNode* root);// 层序遍历
int BinaryTreeComplete(BTNode* root);// 判断二叉树是否是完全二叉树
void BinaryTreeDestory(BTNode** root);//二叉树的销毁
//BinaryTree.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "BinaryTree.h"
#include "Queue.h"
//开辟新节点
BTNode* BuyNode(BTDataType x) {
BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
if (newNode == NULL) {
perror("malloc\n");
exit(-1);
}
newNode->data = x;
newNode->right = newNode->left = NULL;
}
//造树
BTNode* CreatBinaryTree()
{
BTNode* nodeA = BuyNode('A');
BTNode* nodeB = BuyNode('B');
BTNode* nodeC = BuyNode('C');
BTNode* nodeD = BuyNode('D');
BTNode* nodeE = BuyNode('E');
BTNode* nodeF = BuyNode('F');
BTNode* nodeG = BuyNode('G');
nodeA->left = nodeB;
nodeA->right = nodeC;
nodeB->left = nodeD;
nodeC->left = nodeE;
nodeC->right = nodeF;
//nodeF->right = nodeG;
nodeB->right = nodeG;
return nodeA;
}
// 二叉树前序遍历
//根 左子树 右子树
void PreOrder(BTNode* root) {
if (root == NULL) {
return;
}
printf("%c ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
// 二叉树中序遍历
//左子树 根 右子树
void InOrder(BTNode* root) {
if (root == NULL) {
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
// 二叉树后序遍历
//左子树 右子树 根
void PostOrder(BTNode* root) {
if (root == NULL) {
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
// 二叉树节点个数
//遍历计数思想:多次调用存在问题
int BinaryTreeSize(BTNode* root) {
return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root) {
if (root == NULL) {
return 0;
}
else if (root->left == NULL && root->right == NULL) {
return 1;
}
else {
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {
assert(k > 0);
if (root == NULL) {
return 0;
}
if (k == 1) {
return 1;
}
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
//二叉树深度/高度
int BinaryTreeDepth(BTNode* root) {
if (root == NULL) {
return 0;
}
//return BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? BinaryTreeDepth(root->left) + 1 : BinaryTreeDepth(root->right) + 1;
int LeftDepth = BinaryTreeDepth(root->left);
int RightDepth = BinaryTreeDepth(root->right);
return LeftDepth > RightDepth ? LeftDepth + 1 : RightDepth + 1;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
if (root == NULL || root->data == x) {
return root;
}
BTNode* left = BinaryTreeFind(root->left, x);
if (left)
return left;
BTNode* right = BinaryTreeFind(root->right, x);
if (right)
return right;
return NULL;
}
// 层序遍历
void LevelOrder(BTNode* root) {
if (root == NULL) {
return;
}
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q)) {
//取出队首的数据域
BTNode* front = QueueFront(&q);
//再将队首pop出队列
QueuePop(&q);
printf("%c ", front->data);
//孩子带进队列
if (front->left) {
QueuePush(&q, front->left);
}
if (front->right) {
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestroy(&q);
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root) {
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q)) {
//取出队首的数据域
BTNode* front = QueueFront(&q);
//再将队首pop出队列
QueuePop(&q);
if (front) {
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
else {
break;
}
}
//遇到空了以后,检查队列中剩下的节点
//1、剩下全是空,则是完全二叉树
//2、剩下存在非空,则不是完全二叉树
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front) {
return false;
}
}
QueueDestroy(&q);
return true;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode* root) {
if (root == NULL) {
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
总结
二叉树的顺序存储一般适用于完全二叉树的存储,因为对于不规则的树会有很多的空间浪费。而在顺序存储中又有堆的分支,堆分为大堆和小堆。可以一次处理topK问题和堆排序的应用。
链式的存储的方式,不存在空间的浪费。但是树是递归定义的,很多的函数接口可以采用递归思想。