目录
一、树概念及结构
1. 树的概念
- 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
- 有一个特殊的结点,称为根结点,根节点没有前驱结点,除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继 ,因此,树是递归定义的。
注意:树形结构中,子树之间不能有交集,否则就不是树形结构
2. 树的相关概念
- 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图: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)棵互不相交的树的集合称为森林;
3. 树的表示
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
4. 树在实际中的运用(表示文件系统的目录树结构)
二、二叉树概念及结构
1.二叉树概念
一棵二叉树是结点的一个有限集合,该集合 :1. 或者为空2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:
1. 二叉树不存在度大于 2 的结点2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
2. 特殊的二叉树
1. 满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K ,且结点总数是 (2^k)-1,则它就是满二叉树。2. 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
3. 二叉树的性质
1. 若规定根节点的层数为 1 ,则一棵非空二叉树的 第 i 层上最多有 2^(i-1) 个结点.2. 若规定根节点的层数为 1 ,则 深度为 h的二叉树的最大结点数是 (2^h)-1.3. 对任何一棵二叉树 , 如果度为 0其叶结点个数为n₀, 度为2的分支结点个数为n₂ ,则有 n₀=n₂ +14. 若规定根节点的层数为 1 ,具有 n个结点的满二叉树的深度,h=㏒₂(n+1) . (ps:㏒₂(n+1) 是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 否则无右孩子
4. 二叉树的存储结构
1. 顺序存储
2. 链式存储
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. 堆的概念及结构
如果有一个关键码的集合K={K₀,K₁,K₂,…,K(n-1)},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki<=K(2*i+1)且Ki<=K(2*i+2)(Ki>=K(2*i+1)且Ki>=K(2*i+2))i=0,1,2...,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
3. 堆的实现
3.1 堆的结构
typedef int HpDataType;
typedef struct Heap
{
HpDataType* _a;
int _size;
int _capacity;
}Heap;
3.2 堆的创建
堆的构建这里我直接复用一下堆的初始化和插入,插入的里面我用的是向上调整建堆,这里不管是建大堆还是小堆,自己进行控制即可。
//堆的初始化
void HeapInit(Heap* hp)
{
assert(hp);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
// 堆的构建
void HeapCreate(Heap* hp, HpDataType* a, int n)
{
assert(hp);
HeapInit(hp);
for(int i = 0;i < n;++i)
{
HeapPush(hp, a[i]);
}
}
//插入
void HeapPush(Heap* hp, HpDataType x)
{
assert(hp);
if (hp->_capacity == hp->_size)
{
HpDataType newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
HpDataType* tmp = (HpDataType*)realloc(hp->_a, sizeof(HpDataType)* newcapacity);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
hp->_a = tmp;
hp->_capacity = newcapacity;
}
hp->_a[hp->_size] = x;
hp->_size++;
Adjustup(hp->_a,hp->_size - 1);//向上调整建堆
}
3.3 向上调整建堆
因为从末尾插入一个数据以后,可能会让这个堆的节后出问题,所以每插入一个数据要进行一次调整。
假如是建小堆,从最后的位置开始走,最后的下标一定是孩子,那么如果孩子位置的值比父亲位置的值小,就应该交换他们两的值,然后继续往上走。
//向上调整
void Adjustup(HpDataType* a,int child)
{
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.4 删除堆顶数据
删除堆顶的数据不能直接把第一个数据删除掉,如果直接删除堆的节后就会出现问题,后面还需要重新建堆,费时费力。所以首先将末尾的数据和堆顶的数据进行交换,然后size--,数据就删除掉了,此时堆顶的数据是交换后的,不符合堆的结构,再把堆顶的数据向下调整,调整然后删除数据才算完成。
//删除堆顶数据
void HeapPop(Heap* hp)
{
assert(hp);
if (!HeapEmpty(hp))
{
swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
hp->_size--;
Adjustdown(hp->_a,hp->_size,0);
}
}
3.5 向下调整
还是以建小堆为例,先看左右孩子谁小,谁小就交换谁。孩子比父亲小,交换而后继续往下走,直到满足堆的结构,调整结束。
//向下调整
void Adjustdown(HpDataType* a,int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if ( child + 1< n && a[child] > a[child + 1])
{
++child;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
3.6 堆的其他函数
//显示堆的数据
void HeapPrint(Heap* hp)
{
assert(hp);
for(int i = 0;i < hp->_size;++i)
{
printf("%d ", hp->_a[i]);
}
printf("\n");
}
//交换
void swap(HpDataType* q1, HpDataType* q2)
{
HpDataType tmp = *q1;
*q1 = *q2;
*q2 = tmp;
}
//判断是否为空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
//获取堆顶的数据
HpDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->_a[0];
}
//堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
//堆的销毁
void HeapDestry(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_capacity = hp->_size = 0;
}
4.建堆时间复杂度
5. 堆的应用
5.1 堆排序
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
//升序建大堆,降序建小堆
for (int i = (n-2)/2; i >= 0; --i)
{
Adjustdown(a, n, i);
}
//先选出最大的或最小的,交换到数组尾部,向下调整后再选出次大或次小的再次交换到尾部位置的前一个位置
for (int i = 1; i < n; ++i)
{
swap(&a[0], &a[n - i]);
Adjustdown(a, n - i, 0);
}
}
5.2 TOP-K问题
//找n个数里最大或最小的前K个
void PrintTopK(int* a, int n, int k)
{
const char* filename = "test1.txt";
FILE* fout = fopen(filename, "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
for(int i = 0;i<k;++i)
{
fscanf(fout, "%d", &a[i]);
}
for (int i = (k - 2) / 2; i >= 0; --i)
{
Adjustdown(a, k, i);
}
int ret = 0;
while (fscanf(fout, "%d", &ret) != EOF)
{
if (ret > a[0])
{
a[0] = ret;
Adjustdown(a, k, 0);
}
}
//free(a);
fclose(fout);
}
//往文件里写数据
void WriteData(const char* filename, int n)
{
FILE* fin = fopen(filename, "w");
if (fin == NULL)
{
perror("fopen fail");
return;
}
srand(time(NULL));
while (n--)
{
fprintf(fin, "%d ", rand());
}
fclose(fin);
}
6. 二叉树链式结构的实现
6.1 二叉树的结构
这里再回顾一下二叉树的概念:
二叉树是: 1. 空树 ,2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
//符号和结构的定义
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
6.2 二叉树的遍历
二叉树的遍历分为四种:前序,中序,后序以及层序。
二叉树每种遍历的顺序:前序:根,左子树,右子树;中序:左子树,根,右子树;后序:左子树,右子树,根;层序:第一层,第二层,第N层,(从左往右)
![](https://img-blog.csdnimg.cn/d3fcdfab6bda445b9c16e406295ca140.png)
// 二叉树前序遍历
void BTPrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->_Data);
BTPrevOrder(root->_Left);
BTPrevOrder(root->_Right);
}
// 二叉树中序遍历
void BTInOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BTPrevOrder(root->_Left);
printf("%d ", root->_Data);
BTPrevOrder(root->_Right);
}
// 二叉树后序遍历
void BTPostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BTPrevOrder(root->_Left);
BTPrevOrder(root->_Right);
printf("%d ", root->_Data);
}
6.2 构建二叉树
这里节点的数据用的是字符,数字也一样,只需更改自定义的类型即可。用前序遍历去构建二叉树,用#代表NULL,将数组a里的内容依次赋值到每个节点上。
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
return NULL;
}
root->_Data = a[(*pi)++];
root->_Left = BinaryTreeCreate(a, pi);
root->_Right = BinaryTreeCreate(a, pi);
return root;
}
6.3 二叉树的节点个数
// 二叉树节点个数
int BTSize(BTNode* root)
{
if (root == NULL)
return 0;
return BTSize(root->_Left) + BTSize(root->_Right) + 1;
}
// 二叉树叶子节点个数
int BTLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
int leSize = BTLeafSize(root->_Left);
int riSize = BTLeafSize(root->_Right);
return leSize > riSize ? leSize + 1 : riSize + 1;
}
// 二叉树第k层节点个数
int BTLevelKSize(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BTLevelKSize(root->_Left, k - 1) + BTLevelKSize(root->_Right, k - 1);
}
6.4 查找节点
这里如果找到了,直接一层一层返回当前节点即可,如果左子树找不到,右子树也找不到需要返回空。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_Data == x)
{
return root;
}
BTNode* lret = BinaryTreeFind(root->_Left, x);
if (lret != NULL)
return lret;
BTNode* rret = BinaryTreeFind(root->_Right, x);
if (rret != NULL)
return rret;
return NULL;
}
6.5 销毁二叉树
// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeDestory(root->_Left);
BinaryTreeDestory(root->_Right);
free(root);
}
6.6 层序遍历
这里需要用到队列,那么就需要参考之前的代码咯。
// 层序遍历
void BTLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);//队列初始化
if (root)
QueuePush(&q, root);//入队列
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//获取队头
QueuePop(&q);//出队列
printf("%c ", front->_Data);
if(front->_Left)
QueuePush(&q, front->_Left);
if (front->_Right)
QueuePush(&q, front->_Right);
}
printf("\n");
QueueDestory(&q);//销毁队列
}
这里用队列的代码需要注意将二叉树的结构放到队列的头文件中,不然编译报错过不了。
6.7 判断二叉树是否是完全二叉树
就是以层序遍历的方法遍历二叉树,先入根,根不为空就出队列,同时让它的左右子树也入队列,一直遍历直到遇到第一个空,那么它的后面也都必须是空,否则就不是完全二叉树。
// 判断二叉树是否是完全二叉树
int BTComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->_Left);
QueuePush(&q, front->_Right);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestory(&q);
return false;
}
}
QueueDestory(&q);
return true;
}