树、二叉树(C语言版)详解

🍕博客主页:️自信不孤单

🍬文章专栏:数据结构与算法

🍚代码仓库:破浪晓梦

🍭欢迎关注:欢迎大家点赞收藏+关注

文章目录

  • 🍊树的概念及结构
    • 1. 树的概念
    • 2. 树的相关概念
    • 3.树的性质
    • 4. 树的存储结构
      • 4.1 双亲表示法
      • 4.2 孩子表示法
      • 4.3 孩子兄弟表示法
    • 5. 树在实际中的应用
  • 🍓二叉树概念及结构
    • 1. 二叉树的概念
    • 2. 特殊的二叉树
      • 2.1 斜树
      • 2.2 满二叉树
      • 2.3 完全二叉树
    • 3. 二叉树的性质
    • 4. 二叉树的存储结构
      • 1. 二叉树的顺序结构
      • 2. 二叉树的链式结构
  • 🍒二叉树的顺序结构实现
    • 1. 堆的概念及结构
    • 2. 堆的性质
    • 3. 堆的实现
      • 3.1 初始化堆
      • 3.2 🍚堆的向上调整算法
      • 3.3 🍚堆的向下调整算法
      • 3.4 堆的插入
      • 3.5 堆的删除
      • 3.6 获取堆顶数据
      • 3.7 获取堆中数据个数
      • 3.8 堆的判空
      • 3.9 堆的销毁
    • 4. 堆的应用
      • 4.1 堆排序
      • 4.2 Top-K问题
  • 🍥二叉树的链式结构实现
    • 1. 遍历二叉树
      • 1.1 前序遍历二叉树
      • 1.2 中序遍历二叉树
      • 1.3 后序遍历二叉树
      • 1.4 层序遍历二叉树
    • 2. 通过前序遍历的数组构建二叉树
    • 3. 二叉树结点个数
    • 4. 二叉树叶子结点个数
    • 5. 二叉树第k层结点个数
    • 6. 二叉树查找值为x的结点
    • 7. 二叉树的深度
    • 8. 判断二叉树是否是完全二叉树
    • 9. 二叉树的销毁


🍊树的概念及结构

1. 树的概念

树是n(n>=0)个结点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:

  1. 有且仅有一个特定的称为根的结点。
  2. 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,并且称为根的子树。

因此,树是递归定义的。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:

  1. 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
  2. 树中所有结点可以有零个或多个后继。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构了,因此n个结点的树中有n-1条边。

在这里插入图片描述

2. 树的相关概念

在这里插入图片描述

  1. 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6。
  2. 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点。
  3. 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点。
  4. 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
  5. 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。
  6. 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。
  7. 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6。
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  9. 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
  10. 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点。
  11. 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先。
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
  13. 森林:由m(m>0)棵互不相交的树的集合称为森林;

3.树的性质

  1. 树中的结点数等于所有结点的度数加 1 1 1

  2. 度为 m m m的树中第 i i i层上至多有 m ( n − 1 ) m^{(n-1)} m(n1)个结点( i ⩾ 1 i \geqslant 1 i1 )。

  3. 高度为 h h h m m m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h - 1)/(m - 1) (mh1)/(m1)个结点。

  4. 具有 n n n个结点的 m m m叉树的最小高度为 [ log ⁡ 2 ( n ( m − 1 ) + 1 ) ] [\log_2 (n(m - 1) + 1)] [log2(n(m1)+1)]

4. 树的存储结构

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。这里我们来介绍几种表示方法:

4.1 双亲表示法

这里以数组的方式为例,在每个结点中,存储该结点的数据信息 data 以及双亲的位置 parent。
以下是双亲表示法的结点结构定义代码:

/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 10
typedef int TElemType; // 树结点的数据类型
/*结点结构*/
typedef struct PTNode
{TElemType data; // 结点数据int parent; // 双亲位置
}PTNode;
/*树结构*/
typedef struct
{PTNode nodes[MAX_TREE_SIZE]; // 结点数组int r, n; // 根的位置和结点数
}PTree;

这样的存储结构可以根据结点的 parent 的值很容易找到它的双亲结点,直到 parent 为 -1 时,表示找到了树结点的根。可如果我们要想知道,某结点的孩子是什么,需要遍历整个结构。

4.2 孩子表示法

把每个结点的孩子结点排列起来,以单链表作存储结构,则 n 个结点有 n 个孩子链表,如果是叶子结点则此单链表为空。然后 n 个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。如下图:

在这里插入图片描述

设计两个结点,一个孩子结点,一个表头结点。孩子结点中 child 是数据域,用来存储某个结点在表头数组中的下标。next 是指针域,用来存储指向某结点的下一个孩子结点的指针。表头结点中 data 是数据域,存储某结点的数据信息。firstchild 是头指针域,存储该结点的孩子链表的头指针。
以下是孩子表示法的结点结构定义代码:

/*树的孩子表示法结构定义*/
#define MAX_TREE_SIZE 10
typedef int TElemType; // 树结点的数据类型
/*孩子结点*/
typedef struct CTNode
{int child;struct CTNode* next;
}*ChildPtr;
/*表头结点*/
typedef struct
{TElemType data;ChildPtr firstchild;
}CTBox;
/*树结构*/
typedef struct
{CTBox nodes[MAX_TREE_SIZE]; // 结点数组int r, n; // 根的位置和结点数
}

这样的存储结构对于我们要查找某个结点的某个孩子,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,对头结点的数组循环即可。但是我们要想找到某个结点的双亲,还需要遍历整个结构。

4.3 孩子兄弟表示法

任意一棵树, 它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。 因此,我们设置两个指针,分别指向该结点的第一个孩子和该结点的右兄弟。如下图:

在这里插入图片描述

以下是孩子表示法的结点结构定义代码:

/*树的孩子兄弟表示法结构定义*/
typedef int TElemType; //树结点的数据类型
typedef struct CSNode
{TElemtype data;struct CSNode* firstchild, * rightsib;
} CSNode, * CSTree;

5. 树在实际中的应用

在这里插入图片描述

🍓二叉树概念及结构

1. 二叉树的概念

二叉树是一种树形结构,其特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒。

二叉树是 n (n≥0) 个结点的有限集合:

  1. 或者为空二叉树,即n=0。
  2. 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。

二叉树的5种基本形态如图所示:

在这里插入图片描述

2. 特殊的二叉树

2.1 斜树

所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。

2.2 满二叉树

在这里插入图片描述

一棵高度为 h h h,且含有个 2 h − 1 2^h-1 2h1结点的二叉树称为满二叉树,即树中的每层都含有最多的结点。满二叉树的叶子结点都集中在二叉树的最下一层,并且除叶子结点之外的每个结点度数均为 2 2 2。可以对满二叉树按层序编号:约定编号从根结点(根结点编号为 1 1 1)起,自上而下,自左向右。这样,每个结点对应一个编号,对于编号为 i 的结点,若有双亲,则其双亲为 i / 2 i/2 i/2,若有左孩子,则左孩子为 2 i 2i 2i;若有右孩子,则右孩子为 2 i + 1 2i+1 2i+1

2.3 完全二叉树

在这里插入图片描述

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。对于高度为 h h h的,有 n n n个结点的二叉树,当且仅当其每一个结点都与高度为 h h h的满二叉树中编号从 1 1 1 n n n的结点一一对应时称之为完全二叉树。满二叉树就是一种特殊的完全二叉树。

  1. i ⩽ n / 2 i \leqslant n/2 in/2,则结点 i i i为分支结点,否则为叶子结点。

  2. 叶子结点只可能在层次最大的两层上出现。对于最大层次中的叶子结点,都依次排列在该层最左边的位置上。

  3. 若有度为 1 1 1的结点,则只可能有一个,且该结点只有左孩子而无右孩子(重要特征)。

  4. 按层序编号后,一旦出现某结点(编号为 i i i)为叶子结点或只有左孩子,则编号大于 i i i的结点均为叶子结点。

  5. n n n为奇数,则每个分支结点都有左孩子和右孩子;若为偶数,则编号最大的分支结点(编号为 n / 2 n/2 n/2)只有左孩子,没有右孩子,其余分支结点左、右孩子都有。

3. 二叉树的性质

  1. 任意一棵树,若结点数量为 n n n,则边的数量为 n − 1 n-1 n1
  2. 非空二叉树上的叶子结点数等于度为 2 2 2的结点数加 1 1 1,即 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  3. 非空二叉树上第 k k k层上至多有 2 k − 1 2^{k-1} 2k1个结点( k ⩾ 1 k \geqslant 1 k1)。
  4. 高度为 h h h的二叉树至多有 2 h − 1 2^h-1 2h1个结点( h ⩾ 1 h \geqslant 1 h1)。
  5. 对完全二叉树按从上到下、从左到右的顺序依次编号 0 , 1 , … , n 0,1,…,n 0,1,,n,则有以下关系:
  • i > 0 i>0 i>0时,结点 i i i的双亲的编号为 ( i − 1 ) / 2 (i-1)/2 (i1)/2,即当 i i i为奇数时, 它是双亲的左孩子;当 i i i为偶数时,它是双亲的右孩子。
  • 2 i + 1 < n 2i+1<n 2i+1<n时,结点的左孩子编号为 2 i + 1 2i+1 2i+1,否则无左孩子。
  • 2 i + 2 < n 2i+2<n 2i+2<n时,结点 i i i的右孩子编号为 2 i + 2 2i+2 2i+2,否则无右孩子。
  • 结点 i i i所在层次(深度)为 [ log ⁡ 2 ( i + 1 ) ] + 1 [\log_2 (i+1)]+1 [log2(i+1)]+1。(方括号表示向下取整)
  1. 具有 n n n n > 0 n>0 n>0)个结点的完全二叉树的高度为 [ log ⁡ 2 n ] + 1 [\log_2 n]+1 [log2n]+1。(方括号表示向下取整)
  2. 具有 n n n n > 0 n>0 n>0)个结点的满二叉树的高度为 log ⁡ 2 ( n + 1 ) \log_2 (n+1) log2(n+1)

4. 二叉树的存储结构

1. 二叉树的顺序结构

二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素,即将完全二叉树上编号为 i i i的结点元素存储在一维数组下标为 i − 1 i-1 i1的分量中。

依据二叉树的性质,使用顺序结构数组只适合表示完全二叉树和满二叉树,树中结点的序号可以唯一地反映结点之间的逻辑关系,这样既能最大可能地节省存储空间,又能利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。在实际应用中,堆的实现就很好的体现了这一结构。二叉树的顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

在这里插入图片描述

2. 二叉树的链式结构

二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。我们称这样的链表叫做二叉链表。

在含有 n n n个结点的二叉链表中,含有 n + 1 n+1 n+1个空链域。

在这里插入图片描述

/*二叉树的二叉链表结点构造定义*/
/*结点结构*/
typedef int TElemType;
typedef struct BTNode {TElemType data;	// 结点数据struct BTNode* lchild, * rchild;	// 左右孩子指针
} BTNode, * BTree;

🍒二叉树的顺序结构实现

1. 堆的概念及结构

如果有一个关键码的集合 K = { k 0 , k 1 , k 2 , … , k n − 1 } K=\{k_0,k_1, k_2,…,k_{n-1}\} K={k0k1k2kn1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K i ⩽ K 2 ∗ i + 1 Ki \leqslant K_{2*i+1} KiK2i+1 K i ⩽ K 2 ∗ i + 2 Ki \leqslant K_{2*i+2} KiK2i+2 K i ⩾ K 2 ∗ i + 1 Ki \geqslant K_{2*i+1} KiK2i+1 K i ⩾ K 2 ∗ i + 2 Ki \geqslant K_{2*i+2} KiK2i+2)(其中 i = 0 , 1 , 2 , … i = 0,1,2,… i=0,1,2,),则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

在这里插入图片描述

2. 堆的性质

  1. 堆中某个节点的值总是不大于或不小于其父节点的值;

  2. 堆总是一棵完全二叉树。

3. 堆的实现

首先创建两个文件来实现堆:

  1. Heap.h(节点的声明、接口函数声明、头文件的包含)
  2. Heap.c(堆接口函数的实现)

如图:

在这里插入图片描述

Heap.h 文件内容如下:

#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 AdjustUp(HPDataType* a, int child);
// 堆的向下调整
void AdjustDown(HPDataType* a, int n, int parent);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 获取堆顶数据
HPDataType HeapTop(Heap* php);
// 获取堆中数据个数
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);

接下来我们在 Heap.c 文件中实现各个接口函数。

3.1 初始化堆

对堆进行置空。

void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->capacity = php->size = 0;
}

3.2 🍚堆的向上调整算法

当我们在堆尾插入数据时,需要进行调整使其仍然是堆,就要用到堆的向上调整算法。

以向上调整算法的基本思想(以建小堆为例):

  1. 将目标结点与其父结点比较。

  2. 若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小堆了。

图解:

在这里插入图片描述

代码实现:

// 交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{assert(p1);assert(p2);HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataType*  a, int child)
{while (child > 0){int parent = (child - 1) / 2;if (a[parent] > a[child]){Swap(&a[parent], &a[child]);child = parent;}else{break;}}
}

拓展:

向上调整一次的时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN)

向上调整建堆的思路:

  • 从第一个结点开始,依次向上调整即可。

向上调整建堆的时间复杂度为 O ( N ∗ log ⁡ N ) O(N*\log N) O(NlogN)

向上调整建堆的代码实现:

for (int i = 0; i < n; i++)
{AdjustUp(a, i);
}

3.3 🍚堆的向下调整算法

现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

使用向下调整算法需要满足一个前提:

  1. 若想将其调整为小堆,那么根结点的左右子树必须都为小堆。

  2. 若想将其调整为大堆,那么根结点的左右子树必须都为大堆。

向下调整算法的基本思想(以建小堆为例):

  1. 从根结点处开始,选出左右孩子中值较小的孩子。
  2. 让小的孩子与其父亲进行比较。若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

图解:

在这里插入图片描述

代码实现:

// 交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{assert(p1);assert(p2);HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}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[parent] > a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}

拓展:

向下调整一次的时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN)

使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意完全二叉树调整为堆呢?

答案很简单,我们只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可。

向下调整建堆的代码实现:

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{AdjustDown(a, n, i);
}

那么向下调整建堆的时间复杂度为多少呢?

当结点数无穷大时,完全二叉树与其层数相同的满二叉树相比较来说,它们相差的结点数可以忽略不计,所以计算时间复杂度的时候我们可以将完全二叉树看作与其层数相同的满二叉树来进行计算。

在这里插入图片描述

建堆过程中最坏情况交换的次数:

T ( n ) = 1 × ( h − 1 ) + 2 × ( h − 2 ) + 2 h − 2 × 1 T(n) = 1 \times (h-1)+2 \times (h-2)+2^{h-2} \times 1 T(n)=1×(h1)+2×(h2)+2h2×1

通过求和我们得出

T ( n ) = 2 h − h − 1 T(n)=2^h-h-1 T(n)=2hh1

由二叉树的性质得 N = 2 h − 1 N=2^h-1 N=2h1

h = log ⁡ 2 ( N + 1 ) h=\log_2(N+1) h=log2(N+1)

所以 T ( n ) = N − log ⁡ 2 ( N + 1 ) T(n)=N-\log_2(N+1) T(n)=Nlog2(N+1)

用大O的渐近表示法:

T ( N ) = O ( N ) T(N)=O(N) T(N)=O(N)

最终推出向下调整建堆的时间复杂度为 O ( N ) O(N) O(N)

3.4 堆的插入

先判断扩容, 然后将结点插到堆数组的最后,再进行向上调整操作。

void HeapPush(Heap* php, HPDataType x)
{assert(php);if (php->capacity == php->size){int NewCapacity = php->capacity == 0 ? 5 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, NewCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = NewCapacity;}php->size++;php->a[php->size - 1] = x;AdjustUp(php->a, php->size - 1);
}

3.5 堆的删除

堆的删除是指删除堆顶元素。

实现思路:

如果非空就将堆顶结点与最后一个结点交换,然后去掉最后一个结点,将堆顶结点进行向下调整操作。

void HeapPop(Heap* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}

3.6 获取堆顶数据

断言判空,返回堆顶元素的值。

HPDataType HeapTop(Heap* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];
}

3.7 获取堆中数据个数

返回堆中数据个数。

int HeapSize(Heap* php)
{assert(php);return php->size;
}

3.8 堆的判空

堆非空返回ture,否则返回false。

int HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

3.9 堆的销毁

释放动态开辟好的内存,并对数据进行置空。

void HeapDestory(Heap* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}

4. 堆的应用

堆的应用相对广泛,我们在此仅介绍堆排序Top-K问题

4.1 堆排序

堆排序是利用堆的思想进行的排序,是一种选择排序。时间复杂度为 O ( N ∗ log ⁡ N ) O(N*\log N) O(NlogN),它也是一种不稳定排序。

实现分为两步:

  1. 建堆。
  • 升序:建大堆
  • 降序:建小堆
  1. 利用堆删除思想进行排序。

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

实现具体思路(以升序为例):

  1. 将待排序的 n n n个元素构建成一个大堆。此时,整个序列的最大值就是堆顶的根节点。
  2. 将其与末尾元素进行交换,此时末尾元素为最大值。
  3. 然后将剩余 n − 1 n-1 n1个元素通过向下调整的重新建成大堆,这样会得到 n n n个元素的次大值。如此反复执行,便能得到一个有序序列了。

堆排序动图演示网站

代码实现:

void HeapSort(HPDataType* 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--){Swap(&a[0], &a[i]);AdjustDown(a, i, 0);}
}

4.2 Top-K问题

求数据集合中前K个最大元素或最小元素,一般情况下数据量都比较大。

基本思路:

  1. 用数据集合中前 K K K个元素来建堆。
    求前 k k k个最大的元素,则建小堆。
    求前 k k k个最小的元素,则建大堆。

  2. 用剩余的 N − K N-K NK个元素依次与堆顶元素来比较,不满足则替换堆顶元素。

剩余 N − K N-K NK个元素依次与堆顶元素比完之后,堆中剩余的 K K K个元素就是所求的前 K K K个最小或者最大的元素。

接下来,我们通过文件操作的方式来实现。首先在文件中造10000个随机数据,然后再执行以上思路。

代码实现:

// 造数据
void CreateNDate()
{int n = 10000;srand((unsigned int)time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (size_t i = 0; i < n; ++i){int x = rand() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}//打印最大的前K个数据
void PrintTopK(int k)
{const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc error");return;}// 存入前k个数据for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}// 建小堆for (int i = (k-1-1)/2; i >= 0; i--){AdjustDown(kminheap, k, i);}// 比较int val = 0;while (!feof(fout)){fscanf(fout, "%d", &val);if (val > kminheap[0]){kminheap[0] = val;AdjustDown(kminheap, k, 0);}}// 打印for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}

🍥二叉树的链式结构实现

根据二叉树的概念,我们发现链式结构能更清晰地表示二叉树。二叉树每个结点最多有两个孩子,所以我们设计一个数据域和两个指针域,数据域存放该节点的数据,指针域存放左右孩子指针。

二叉树链式结构的结点定义代码:

/*二叉树的二叉链表结点构造定义*/
/*结点结构*/
typedef char TElemType;
typedef struct BTNode {TElemType data;	// 结点数据struct BTNode* lchild, * rchild;	// 左右孩子指针
} BTNode;

1. 遍历二叉树

动图演示点这里!!!

1.1 前序遍历二叉树

前序遍历:先根 再左 再右

void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%c ", root->data);BinaryTreePrevOrder(root->lchild);BinaryTreePrevOrder(root->rchild);
}

1.2 中序遍历二叉树

中序遍历:先左 再根 再右

void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BinaryTreeInOrder(root->lchild);printf("%c ", root->data);BinaryTreeInOrder(root->rchild);
}

1.3 后序遍历二叉树

后序遍历:先左 再右 再根

void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BinaryTreePostOrder(root->lchild);BinaryTreePostOrder(root->rchild);printf("%c ", root->data);
}

1.4 层序遍历二叉树

从上到下从左往右依次遍历

我们发现层序遍历的特点是根据根、左、右的顺序遍历每个结点。于是我们想到让根结点入队列,出队列时再将该节点的左右孩子依次入队列,直到队列为空则层序遍历结束。

队列结点及各接口函数定义

void BinaryTreeLevelOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}Queue* qu = (Queue*)malloc(sizeof(Queue));QueueInit(qu);QueuePush(qu, root);while (!QueueEmpty(qu)){BTNode* ret = QueueFront(qu);if (ret){printf("%c ", ret->data);QueuePush(qu, ret->lchild);QueuePush(qu, ret->rchild);}elseprintf("N ");QueuePop(qu);}QueueDestroy(qu);free(qu);printf("\n");
}

2. 通过前序遍历的数组构建二叉树

给定前序数组(其中空节点用#表示),通过前序递归开辟并连接结点。

BTNode* BTBuyNode(TElemType x)
{BTNode* ret = (BTNode*)malloc(sizeof(BTNode));if (!ret){perror("malloc fail");return NULL;}ret->lchild = NULL;ret->rchild = NULL;ret->data = x;return ret;
}BTNode* BinaryTreeCreate(TElemType* a, int n, int* pi)
{if (*pi == n){return NULL;}if (a[*pi] == '#'){(*pi)++;return NULL;}BTNode* root = BTBuyNode(a[*pi]);(*pi)++;root->lchild = BinaryTreeCreate(a, n, pi);root->rchild = BinaryTreeCreate(a, n, pi);return root;
}

3. 二叉树结点个数

int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->lchild) +BinaryTreeSize(root->rchild) + 1;
}

4. 二叉树叶子结点个数

int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->lchild == NULL && root->rchild == NULL){return 1;}return BinaryTreeLeafSize(root->lchild) +BinaryTreeLeafSize(root->rchild);
}

5. 二叉树第k层结点个数

int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelKSize(root->lchild, k - 1) +BinaryTreeLevelKSize(root->rchild, k - 1);
}

6. 二叉树查找值为x的结点

BTNode* BinaryTreeFind(BTNode* root, TElemType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode* l = BinaryTreeFind(root->lchild, x);if (l){return l;}BTNode* r = BinaryTreeFind(root->rchild, x);if (r){return r;}return NULL;
}

7. 二叉树的深度

int BinaryTreeMaxDepth(BTNode* root) {if (root == NULL)return 0;int l = BinaryTreeMaxDepth(root->lchild);int r = BinaryTreeMaxDepth(root->rchild);return l > r ? l + 1 : r + 1;
}

8. 判断二叉树是否是完全二叉树

通过层序遍历的方式找到第一个空节点,如果后面还有非空结点就不是完全二叉树,否则为完全二叉树。

bool BinaryTreeComplete(BTNode* root)
{if (root == NULL)return true;Queue* qu = (Queue*)malloc(sizeof(Queue));QueueInit(qu);QueuePush(qu, root);while (!QueueEmpty(qu)){BTNode* ret = QueueFront(qu);QueuePop(qu);if (ret){QueuePush(qu, ret->lchild);QueuePush(qu, ret->rchild);}elsebreak;}int result = true;while (!QueueEmpty(qu)){BTNode* ret = QueueFront(qu);if (ret){result = false;break;}QueuePop(qu);}QueueDestroy(qu);free(qu);return result;
}

9. 二叉树的销毁

void BinaryTreeDestory(BTNode* root)
{if (root == NULL){return;}BinaryTreeDestory(root->lchild);BinaryTreeDestory(root->rchild);free(root);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/17983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决sonar的单元测试的覆盖率会为0问题

今天做项目遇到一个问题&#xff0c;明明做单元测试时覆盖率已经百分百了&#xff0c;然后传到Jenkin上&#xff0c;构建也成功了&#xff0c;但偏偏覆盖率就是为零&#xff0c;非常确定代码没有问题&#xff0c;所以唯一的问题就是出现在配置上了。 一开始的结果如下&#xf…

css 利用模糊属性 制作水滴

<style>.box {background-color: #111;height: 100vh;display: flex;justify-content: center;align-items: center;/* 对比度*/filter: contrast(20);}.drop {width: 150px;height: 159px;border-radius: 50%;background-color: #fff;position: absolute;/* 模糊 */filt…

Flowable-服务-微服务任务

目录 定义图形标记XML内容界面操作 定义 Sc 任务不是 BPMN 2.0 规范定义的官方任务&#xff0c;在 Flowable 中&#xff0c;Sc 任务是作为一种特殊的服务 任务来实现的&#xff0c;主要调用springcloud的微服务使用。 图形标记 由于 Sc 任务不是 BPMN 2.0 规范的“官方”任务…

CASAIM自动化平面度检测设备3D扫描零部件形位公差尺寸测量

平面度是表面形状的度量&#xff0c;指示沿该表面的所有点是否在同一平面中&#xff0c;当两个表面需要连接在一起形成紧密连接时&#xff0c;平面度检测至关重要。 CASAIM自动化平面度检测设备通过搭载领先的激光三维测头和智能检测软件自动获取零部件高质量测量数据&#xf…

剑指offer刷题笔记--Num61-68

1--扑克牌中的顺子&#xff08;61&#xff09; 主要思路&#xff1a; 五个数是顺子的充要条件&#xff1a;① 最大值 - 最小值 < 5&#xff08;大小王除外&#xff09;&#xff1b;② 没有出现重复的值&#xff08;大小王除外&#xff09;&#xff1b; 判断是否出现重复的值…

IDEA如何快捷创建serialVersionUID【详细图解】

在Java Bean&#xff0c;快速创建serialVersionUID&#xff0c;详细操作流程如下&#xff1a; 1.File->Settings->Editor->Inspections 在搜索框里搜索 Uid,选择下图中勾选的选择 2.如何使用 双击选中需要序列化Uid的类名&#xff0c;使用Alt enter&#xff0c;快捷…

Java里的static import使用小结

Java里的static import使用小结 换了工作要把Java重新捡起来了&#xff0c;这个在大学里用过的语言&#xff0c;虽然不复杂&#xff0c;还是有一些奇怪的地方的。比如static Slgluimport。 Static import是JDK 1.5中引进的特性&#xff0c;不过读大学那会还真没注意到。它的作…

机器学习 | Python实现NARX模型预测控制

机器学习 | Python实现NARX模型预测控制 目录 机器学习 | Python实现NARX模型预测控制效果一览基本介绍研究内容程序设计参考资料效果一览 基本介绍 机器学习 | Python实现NARX模型预测控制 研究内容 贝叶斯黑盒模型预测控制,基于具有外源输入的非线性自回归模型的预期自由能最…

微信小程序防盗链referer问题处理

公司使用百度云存储一些资源&#xff0c;然后现在要做防盗链&#xff0c;在CDN加入Referer白名单后发现PC是正常的&#xff0c;微信小程序无法正常访问资源了。然后是各种查啊&#xff0c;然后发现是微信小程序不支持Referer的修改&#xff0c;且在小程序开发工具是Referer是固…

将程序打包成单一一个可执行文件

最近做了一个界面交互渲染的小项目&#xff0c;项目主要的功能是通过TCP接收数据然后在界面中渲染出对应的状态。由于用户的最大需求是炫酷&#xff0c;于是为了方便实现特殊的交互逻辑&#xff0c;我选择用freeglut自行实现了界面的交互和渲染&#xff0c;又用OpenCV做了部分图…

图数据库Neo4j学习四——Spring Data NEO

1配置 1.1Maven依赖 <!--neo4j --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency>1.2yml配置 spring:data:neo4j:uri: bolt://localhost:76…

QT--day4(定时器事件、鼠标事件、键盘事件、绘制事件、实现画板、QT实现TCP服务器)

QT实现tcpf服务器代码&#xff1a;&#xff08;源文件&#xff09; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化空间server new QTc…

excel中使地址按十六进制进行数值递增的函数

这里是尼德兰的喵工具相关文章&#xff0c;欢迎您的访问&#xff01; 如果文章对您有所帮助&#xff0c;期待您的点赞收藏&#xff01; 让我们一起为成为芯片前端全栈工程师而努力&#xff01; 在进行寄存器编写时很多时候会涉及到算地址的问题&#xff0c;通常32bit位宽的寄存…

棕榈酰三肽-38——对额间纹,鱼尾纹,抬头纹和颈纹非常有效

简介 棕榈酰三肽-38由三个氨基酸组成&#xff0c;是一种双氧化的脂肽。这种肽的灵感源自于天然存在于胶原蛋白VI和层粘连蛋白中的一种三肽。 它可以在需要的地方&#xff0c;从内部重建肌肤&#xff0c;使皱纹平滑皮肤得到舒缓&#xff0c;尤其对额间纹&#xff0c;鱼尾纹&…

Spring整合Mybatis、Spring整合JUnit

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Spring整合 一、Spring整合Mybatis1.1 整合Mybatis&#x…

记录一次通过iostat命令定位系统数据库CPU飙升的案例

一、背景 我们有个移动考勤的系统&#xff0c;运维监控系统显示&#xff0c;每到上下班时间&#xff0c;考勤数据库的CPU就飙升到100%&#xff0c;磁盘读写请求等待时间变长&#xff0c;最初无法确定是磁盘性能下降导致的CPU飙升&#xff0c;还是CPU飙升导致的磁盘性能下降&…

C# 外观模式

概述 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个统一的接口&#xff0c;用于访问子系统中的一组接口。外观模式隐藏了子系统的复杂性&#xff0c;使得客户端可以通过简单的接口与子系统进行交互。 外观模式定义了一个高层…

设计模式之观察者模式

定义&#xff1a;对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发货所能改变时&#xff0c;所有依赖它的对象都得到通知并被自动更新。 例子&#xff1a;报纸-邮局-用户 不用设计模式实现 被观察者 package com.tao.YanMoDesignPattern.observer.case3_Origin;…

Docker快速入门笔记

Docker快速入门 前言 当今软件开发领域的一股热潮正在迅速兴起&#xff0c;它融合了便捷性、灵活性和可移植性&#xff0c;让开发者们欣喜若狂。它就是 Docker&#xff01;无论你是一个初学者&#xff0c;还是一位经验丰富的开发者&#xff0c;都不能错过这个引领技术浪潮的工…

windwos server 2008 更新环境,且vs_redis 安装失败

KB2919442 下载地址:https://www.microsoft.com/zh-cn/download/confirmation.aspx?id42153 KB2919355 下载地址:https://www.microsoft.com/zh-cn/download/confirmation.aspx?id42153 安装步骤:先安装442,后安装355