1. 树概念及结构2. 二叉树概念及结构3. 二叉树顺序结构及实现4. 二叉树链式结构及实现
一、树概念及结构
1 .树的概念
1.有一个 特殊的结点,称为根结点 ,根节点没有前驱结点2.除根节点外, 其余结点被分成 M(M>0) 个互不相交的集合 T1 、 T2 、 …… 、 Tm ,其中每一个集合 Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或多个后继3.因此, 树是递归定义 的。
![](https://img-blog.csdnimg.cn/direct/e43cf943b47e4698ba2fb7231f41a299.png)
2.树的相关概念![](https://img-blog.csdnimg.cn/direct/c9192b0333044af28d83049ef7bd7417.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 )棵互不相交的树的集合称为森林。(如C盘D盘)
3.树的表示
typedef int DataType ;struct Node{struct Node * _firstChild1 ; // 第一个孩子结点struct Node * _pNextBrother ; // 指向其下一个兄弟结点DataType _data ; // 结点中的数据域};
这种表示法:就是父亲的左边指向自己的其中一个孩子,右边指向自己的兄弟。
4.树在实际中的运用(表示文件系统的目录树结构)
二、叉树概念及结构
1 .概念
1. 或者为空2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
1. 二叉树不存在度大于 2 的结点2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
2.现实中的二叉树:
3 特殊的二叉树:
1. 满二叉树 :一个二叉树,如果 每一个层的结点数都达到最大值 ,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K ,且结点总数是 2^k-1 ,则它就是满二叉树。
每层都是满的2. 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树 。前h-1层是满的,最后一层不满,但是从左到右必须连续 [ 2^(h-1),2^h-1 ],(最后一层最少有一个,最多是2^h-1 个
4. 二叉树的性质
1. 若规定根节点的层数为 1 ,则一棵非空二叉树的 第 i 层上最多有 2^(i-1) 个结点.2. 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是 2^h-13. 对任何一棵二叉树 , 如果度为 0 其叶结点个数为n0 , 度为 2 的分支结点个数为n2 , 则有 n0=n2+14. 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 , h=log2(n+1). (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 , 否则无左孩子3. 若 2i+2<n ,右孩子序号: 2i+2 , 否则无右孩子
除了根结点外,每一个结点都是由其双亲结点的某一条边连接而来。因此,总的边数(即非根结点的数量)为 n−1,其中 n 是二叉树的总结点数。这些边要么连接到叶子结点(度为0),要么连接到度为1的结点的子结点,要么连接到度为2的结点的两个子结点。因此,我们有:n−1=n1+2n2
5.二叉树的存储结构
顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中 只有堆 才会使用数组来存储。二叉树顺 序存储在 物理上 是一个 数组 ,在 逻辑上 是一颗 二叉树 。
二叉树的链式存储结构是指,用 链表 来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成, 数据域和左右指针域 ,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的 存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pParent; // 指向当前节点的双亲
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
};
三、二叉树的顺序结构及实现
1 .二叉树的顺序结构
2. 堆的概念及结构
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;2.堆总是一棵完全二叉树。
3.堆的实现
1. 堆向下调整算法
这里补充一个交换函数,后面涉及交换会用到
//堆的向下调整算法
void AdjustDown(HPDataType* a, size_t size,int parent)
{
assert(a);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child] > a[child + 1])
child++;
if (a[parent] > a[child])
{
swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
//交换函数
void swap(HPDataType* x, HPDataType* y)
{
HPDataType tmp = *x;
*x = *y;
*y = tmp;
}
2.堆向上调整算法
如图:先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
//堆的向上调整算法
void AdjustUp(HPDataType* a, int child)//向上调整不需要得到大小,因为下标不会超过最大下标
{
assert(a);
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
3.堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的 第一个非叶子节点的子树 开始调整,一直调整到根节点的树,就可以调整成堆。
int a [] = { 1 , 5 , 3 , 8 , 7 , 6 };
4.建堆时间复杂度
5.堆的插入
![](https://img-blog.csdnimg.cn/direct/eaa6cc49237a40d6828be616400b8752.png)
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->_size == hp->_capacity)//判断是否需要扩容
{
int newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
hp->_a = tmp;
hp->_capacity = newcapacity;
}
hp->_a[hp->_size++] = x;
AdjustUp(hp->_a, hp->_size - 1);
}
6.堆的删除
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
swap(&hp->_a[--hp->_size], &hp->_a[0]);
AdjustDown(hp->_a, hp->_size, 0);
}
7.堆的实现总体代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
void swap(HPDataType* x, HPDataType* y)
{
HPDataType tmp = *x;
*x = *y;
*y = tmp;
}
void AdjustUp(HPDataType* a, int child)//向上调整不需要得到大小,因为下标不会超过最大下标
{
assert(a);
int parent = (child - 1) / 2;
while (child>0)
{
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void AdjustDown(HPDataType* a, size_t size,int parent)
{
assert(a);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child] > a[child + 1])
child++;
if (a[parent] > a[child])
{
swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapInit(Heap* php)
{
assert(php);
php->_a = NULL;
php->_capacity = 0;
php->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_capacity = 0;
hp->_size = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->_size == hp->_capacity)//判断是否需要扩容
{
int newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
hp->_a = tmp;
hp->_capacity = newcapacity;
}
hp->_a[hp->_size++] = x;
AdjustUp(hp->_a, hp->_size - 1);
}
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
swap(&hp->_a[--hp->_size], &hp->_a[0]);
AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size==0;
}
4.堆的应用
1.堆排序
升序:建大堆降序:建小堆
1:向下调整建堆
2:向上调整建堆
时间复杂度:Nlog(N)
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
//堆排序
void HeapSort(HPDataType* a, size_t size)
{
//向上调整法建堆 时间复杂度 nlog(n)
//for (int i = 1; i < size; i++)
// AdjustUp(a, i);
//向下调整法建堆 时间复杂度 n
for (int i = (size - 1 - 1) / 2; i >= 0; i--)
AdjustDown(a, size, i);
//向下调整+删除思想进行排序
for (int i = size - 1; i > 0; i--)
{
swap(&a[0], &a[i]);
AdjustDown(a, --size, 0);
}
}
2.TOP-K问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
1. 用数据集合中前K个元素来建堆
前 k 个最大的元素,则建小堆前 k 个最小的元素,则建大堆2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。
void PrintTopK(int* a, int n, int k)
{
// 1. 建堆--用a中前k个元素建堆
FILE* fout = fopen("data.txt", "r");
int x = 0;
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &a[i]);
AdjustUp(a, i);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > a[0])
{
a[0] = x;
AdjustDown(a, k, 0);
}
}
}
void TestTopk()
{
int n = 1000000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = (rand() + i) % 10000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
int* a = (int*)malloc(sizeof(int) * 10);
PrintTopK(a, n, 10);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
}
四、二叉树链式结构的实现
1 .二叉树的创建和销毁
二叉树的构建及遍历。二叉树遍历_牛客题霸_牛客网 (nowcoder.com)
1. 空树2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
{
if (a[*pi] == '#')
{
*pi += 1;
return NULL;
}
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->_data = a[(*pi)++];
node->_left = BinaryTreeCreate(a,pi );
node->_right = BinaryTreeCreate(a, pi);
return node;
}
void BinaryTreeDestory(BTNode** root)
{
if (!*root)
return;
BinaryTreeDestory((*root)->_left);
BinaryTreeDestory((*root)->_right);
free(*root);
*root = NULL;
}
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
if (cur == NULL)
break;
QueuePush(&q, cur->_left);
QueuePush(&q, cur->_right);
}
while (!QueueEmpty(&q))
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
if (cur)
return 0;
}
return 1;
}
2 .二叉树的遍历
![](https://img-blog.csdnimg.cn/direct/70b519c004394e13931ce82a1608e81e.png)
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
void BinaryTreeInOrder(BTNode* root)
{
if (!root)
return;
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
void BinaryTreePostOrder(BTNode* root)
{
if (!root)
return;
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
![](https://img-blog.csdnimg.cn/direct/9e0f6b3c457a4129950b70e702970cbc.png)
层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
![](https://img-blog.csdnimg.cn/direct/ec99e65922de4cdeb232d932643d1174.png)
void BinaryTreeLevelOrder(BTNode* root)
{//不换行遍历
//Queue q;
//QueueInit(&q);
//QueuePush(&q, root);
//while (!QueueEmpty(&q))
//{
// BTNode* cur = QueueFront(&q);
// QueuePop(&q);
// printf("%c ", cur->_data);
// if (cur->_left)
// QueuePush(&q, cur->_left);
// if (cur->_right)
// QueuePush(&q,cur->_right);
//}
//QueueDestroy(&q);//分层遍历
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while(!QueueEmpty(&q))
{
int size = QueueSize(&q);
while (size--)
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
printf("%c ", cur->_data);
if (cur->_left)
QueuePush(&q, cur->_left);
if (cur->_right)
QueuePush(&q, cur->_right);
}
printf("\n");
}
}
3 .节点个数以及高度等
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
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);
}
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
return BinaryTreeFind(root->_left, x) || BinaryTreeFind(root->_right, x);
}
4 .二叉树基础oj练习
5 .总体代码:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct BinaryTreeNode* QDataType;//必须用原类型,不能用typedef的类型
typedef struct QueueNode
{
struct Queue* next;
QDataType val;
}Node;
typedef struct Queue
{
Node* _rear;
Node* _front;
int size;
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
{
if (a[*pi] == '#')
{
*pi += 1;
return NULL;
}
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->_data = a[(*pi)++];
node->_left = BinaryTreeCreate(a,pi );
node->_right = BinaryTreeCreate(a, pi);
return node;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
if (!*root)
return;
BinaryTreeDestory((*root)->_left);
BinaryTreeDestory((*root)->_right);
free(*root);
*root = NULL;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
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);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
return BinaryTreeFind(root->_left, x) || BinaryTreeFind(root->_right, x);
}
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (!root)
return;
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (!root)
return;
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
//层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
//不换行遍历
//Queue q;
//QueueInit(&q);
//QueuePush(&q, root);
//while (!QueueEmpty(&q))
//{
// BTNode* cur = QueueFront(&q);
// QueuePop(&q);
// printf("%c ", cur->_data);
// if (cur->_left)
// QueuePush(&q, cur->_left);
// if (cur->_right)
// QueuePush(&q,cur->_right);
//}
//QueueDestroy(&q);
//分层遍历
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while(!QueueEmpty(&q))
{
int size = QueueSize(&q);
while (size--)
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
printf("%c ", cur->_data);
if (cur->_left)
QueuePush(&q, cur->_left);
if (cur->_right)
QueuePush(&q, cur->_right);
}
printf("\n");
}
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
if (cur == NULL)
break;
QueuePush(&q, cur->_left);
QueuePush(&q, cur->_right);
}
while (!QueueEmpty(&q))
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);
if (cur)
return 0;
}
return 1;
}