数据结构c版(2)——二叉树

本章我们来了解一下二叉树这一概念。

目录

1.树概念及结构

1.1树的概念​​​​​​​

1.2 树的特点:

1.3 树的相关概念

1.4 树的表示​​​​​​​

1.5 树在实际中的运用(表示文件系统的目录树结构)

2.二叉树概念及结构

2.1概念

2.3 特殊的二叉树:

        1. 满二叉树:

        2. 完全二叉树:

2.4 二叉树的性质

2.5 二叉树的存储结构

1. 顺序存储

2. 链式存储

3.二叉树的顺序结构及实现

3.1 二叉树的顺序结构

3.2 堆的概念及结构

3.3 堆的实现

3.2.1 堆向下调整算法

3.2.2堆的创建

3.2.3 建堆时间复杂度​​​​​​​

3.2.4 堆中元素调整

3.2.5 堆的插入

3.2.6 堆中元素的判断

3.2.7 堆中元素的删除

3.2.8 堆的摧毁

3.2.9 堆的头文件

3.3 堆的应用

3.3.1 堆排序

3.3.2 TOP-K问题

4.二叉树链式结构的实现

4.1 前置说明

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历

4.2.2 层序遍历

4.3 节点个数以及高度等

4.4 二叉树的创建和销毁

4.5 二叉树头文件


1.树概念及结构

1.1树的概念

树是一种非线性的数据结构,它是由n( n>=0)个有限结点组成一个具有层次关系的集合。  把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。         

1.2 树的特点:

          (1) 所有树都有一个特殊的结点,称为根结点,它是整个树的起点,其他节点都是从根节点开始向下延伸的。

          (2) 每个节点在树中都是唯一的,每个节点都可以通过其在树中的位置被唯一地标识。

          (3) 树形结构的节点之间呈现出层级关系,每个节点可以有多个子节点,但只能有一个父节点,除了根节点没有父节点。

          (4)在数据结构树中,每个节点都不能构成环,即不存在一个节点的子孙节点中存在其祖先节点的情况。

          (5) 树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

1.3 树的相关概念

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)棵互不相交的树的集合称为森林;

1.4 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了, 既然保存值域,也要保存结点和结点之间 的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法 。
typedef int DataType;
struct Node
{struct Node* _firstChild1; // 第一个孩子结点struct Node* _pNextBrother; // 指向其下一个兄弟结点DataType _data; // 结点中的数据域
};

1.5 树在实际中的运用(表示文件系统的目录树结构)

2.二叉树概念及结构

2.1概念

一棵二叉树是结点的一个有限集合,该集合 :
        1. 或者为空
        2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:
        1. 二叉树不存在度大于2的结点。
        2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
注意:对于任意的二叉树都是由以下几种情况复合而成的 :

2.3 特殊的二叉树:

        1. 满二叉树:

        一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为h,且结点总数是,则它就是满二叉树。

        2. 完全二叉树:

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

2.4 二叉树的性质

1. 若规定根节点的层数为 1 ,则一棵非空二叉树的 第 i 层上最多有2^{(i-1)}个结点。
2. 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是2^{h}-1。
3. 对任何一棵二叉树 , 如果度为 0 其叶结点个数为 n_{0} , 度为 2 的分支结点个数为n_{1} ,则有
n_{0}= n_{1}+1
4. 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 , h=log_{2}(n+1)。(ps:log_{2}(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 否则无右孩子。

2.5 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

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; // 当前节点值域
};

3.二叉树的顺序结构及实现

3.1 二叉树的顺序结构

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆( 一种二叉树 ) 使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

3.2 堆的概念及结构

        如果有一个关键码的集合 K=\left \{ k_{0},k_{1},k_{2},..., k_{n-1} \right \} ,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K_{i}<=K_{2*i+1} 且 K_{i}<=K_{2*i+2} ( K_{i}>=K_{2*i+1} 且 K_{i}>=K_{2*i+2} ) i = 0,1 ,2…,则称为小堆 ( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
        1.堆中某个节点的值总是不大于或不小于其父节点的值;
        2.堆总是一棵完全二叉树。

3.3 堆的实现

3.2.1 堆向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};

3.2.2堆的创建

        下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
int a[] = {1,5,3,8,7,6};

// 小堆
//堆初始化函数
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}

3.2.3 建堆时间复杂度

        因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明( 时间复杂度本来看的就是近似值,多几个节点不影响最终结果) :
因此: 建堆的时间复杂度为 O(N) 。

3.2.4 堆中元素调整

为了便于插入元素的同时满足堆的特性,我们需要写一个调整堆中元素的函数:

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整法 
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//while (parent >= 0)while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;//child = (child - 1) / 2;//parent = (parent - 1) / 2;}else{break;}}
}//向下调整法
void AdjustDown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 假设左孩子小,如果解设错了,更新一下if (child+1 < size && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

​​​​​​​

3.2.5 堆的插入

先插入一个 10 到数组的尾上,再进行向上调整算法,直到满足堆。
// O(logN)
//堆的插入函数
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}

3.2.6 堆中元素的判断

为了方便对堆中元素的状况的判断,我们还需要写一下这些函数:

//获取堆顶元素
HPDataType HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//获取堆中元素的个数
size_t HeapSize(HP* php)
{assert(php);return php->size;
}//判断堆是否为空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

3.2.7 堆中元素的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
//堆中元素的删除
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}

3.2.8 堆的摧毁

在堆使用完毕后,我们需要摧毁已经建立的队,释放系统资源,释放之前向系统申请的堆区域的空间。

//摧毁堆
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

3.2.9 堆的头文件

#define _CRT_SECURE_NO_WARNINGS 
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);void HeapDestroy(HP* php);void HeapPush(HP* php, HPDataType x);
// 规定删除堆顶(根节点)
void HeapPop(HP* php);HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int size, int parent);

3.3 堆的应用

3.3.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
        升序:建大堆
        降序:建小堆
2. 利用堆删除思想来进行排序
        建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序
例:
以下是一个堆排代码:
// 升序堆排
void HeapSort(int* a, int n)
{// 建大堆// O(N*logN)/*for (int i = 1; i < n; i++){AdjustUp(a, i);}*/// O(N)for (int i = (n-1-1)/2; i >= 0; --i){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

3.3.2 TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大 。
比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。
        对于Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能数据都不能一下子全部加载到内存中) 。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
        前k 个最大的元素,则建小堆;
        前k 个最小的元素,则建大堆。
2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素。
        将剩余N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。
以下为参考解决代码:
//创建文件存入10000000个随机数
void CreateNDate()
{// 造数据int n = 10000000;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);
}//堆排获取文件中最大的k个数
void PrintTopK(const char* file, int k)
{FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}// 建一个k个数小堆int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc error");return;}// 读取前k个,建小堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);AdjustUp(minheap, i);}int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > minheap[0]){minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}printf("\n");free(minheap);fclose(fout);
}

4.二叉树链式结构的实现

4.1 前置说明

        在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;BTNode* CreatBinaryTree()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
再看二叉树基本操作前,再回顾下二叉树的概念, 二叉树是:
        1. 空树
        2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历

        学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历 :
        1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
        2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
        3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
        由于被访问的结点必是某子树的根,所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树 。 NLR 、 LNR 和 LRN 分别又称为先根遍历、中根遍历和后根遍历。
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){return;}printf("%c ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){return;}BinaryTreeInOrder(root->left);printf("%c ", root->data);BinaryTreeInOrder(root->right);
}// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%c ", root->data);
}
下面主要分析前序递归遍历,中序与后序图解类似。
前序遍历递归图解 :
前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1

4.2.2 层序遍历

        层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;   //这里创建了一个队列,借助队列先进先出的特性实现了层序QueueInit(&q);   //队列初始化if (root){QueuePush(&q, root);  //元素入队列}int levelsize = 1;while (!QueueEmpty(&q))      //队列为空停止训话{while (levelsize--)       {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");levelsize = QueueSize(&q);    //获取队列元素个数}printf("\n");QueueDestroy(&q);                 //摧毁队列}

4.3 节点个数以及高度等

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}// 二叉树叶子节点个数
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 TreeLevelK(root->left, k - 1)+ TreeLevelK(root->right, k - 1);
}// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (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;
}// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;           //同样借助队列进行QueueInit(&q);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){QueueDestroy(&q);return 0;}}QueueDestroy(&q);return 1;}

4.4 二叉树的创建和销毁

//创建二叉树
BTNode *BinaryTreeCreate(BTDataType * src, int n, int* pi)
{if (*pi >= n || src[*pi] == '#'){(*pi)++;return NULL;}BTNode * cur = (BTNode *)malloc(sizeof(BTNode));cur->_data = src[*pi];(*pi)++;cur->left = BinaryTreeCreate(src, n, pi);cur->right = BinaryTreeCreate(src, n, pi);return cur;
}//摧毁二叉树void BinaryTreeDestory(BTNode** root)
{if (*root){BinaryTreeDestory(&(*root)->left);BinaryTreeDestory(&(*root)->right);free(*root);*root = NULL;}
}

4.5 二叉树头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

本章结束! 

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

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

相关文章

Qt 简约美观的动画 摆钟风格 第十季

&#x1f60a; 今天给大家分享一个摆钟风格的加载动画 &#x1f60a; 效果如下: 最近工作忙起来了 , 后续再分享其他有趣的加载动画吧. 一共三个文件 , 可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <Q…

【C++】用文件流的put和get成员函数读写文件

题目 编写一个mycopy程序&#xff0c;实现文件复制的功能。用法是在控制台输入&#xff1a; mycooy 源文件名 目标文件名 参数介绍 m a i n main main 函数的参数有两个&#xff0c;一个int类型参数和一个指针数组。 a r g c argc argc 表示参数的个数。参数为void时 a r g …

机器人 标准DH与改进DH

文章目录 1 建立机器人坐标系1.1 连杆编号1.2 关节编号1.3 坐标系方向2 标准DH(STD)2.1 确定X轴方向2.2 建模步骤2.3 变换顺序2.4 变换矩阵3 改进DH(MDH)3.1 确定X轴方向3.2 建模步骤3.3 变换顺序3.4 变换矩阵4 标准DH与改进DH区别5 Matlab示例参考链接1 建立机器人坐标系 1.1…

Elasticsearch:如何创建搜索引擎

作者&#xff1a;Jessica Taylor 搜索引擎是生活中我们认为理所当然的事情之一。 每当我们寻找某些东西时&#xff0c;我们都会将一个单词或短语放入搜索引擎&#xff0c;就像魔术一样&#xff0c;它会为我们提供一个匹配结果列表。 现在可能感觉不那么神奇了&#xff0c;因为这…

Go-知识struct

Go-知识struct 1. struct 的定义1.1 定义字段1.2 定义方法 2. struct的复用3. 方法受体4. 字段标签4.1 Tag是Struct的一部分4.2 Tag 的约定4.3 Tag 的获取 githupio地址&#xff1a;https://a18792721831.github.io/ 1. struct 的定义 Go 语言的struct与Java中的class类似&am…

第二十三章 :Docker 部署 Redis

第二十三章 :Docker Redis 部署 Docker version 25.0.3, build 4debf41 ,Docker Compose version v2.24.2Redis-6.0.6 镜像 redis:6.0.6-alpineRedis-6.0.6版本 部署规划 服务器IP192.168.92.105端口6379安装目录/home/work/docker-redis-6.0.6数据映射目录/home/work/do…

最简单的基于 FFmpeg 的收流器(以接收 RTMP 为例)

最简单的基于 FFmpeg 的收流器&#xff08;以接收 RTMP 为例&#xff09; 最简单的基于 FFmpeg 的收流器&#xff08;以接收 RTMP 为例&#xff09;正文结果工程文件下载参考链接 最简单的基于 FFmpeg 的收流器&#xff08;以接收 RTMP 为例&#xff09; 参考雷霄骅博士的文章…

蓝凌OA frpt_listreport_definefield.aspx接口存在SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

DevStack 部署 OpenStack

Devstack 简介 DevStack 是一系列可扩展的脚本&#xff0c;用于基于 git master 的最新版本快速调出完整的 OpenStack 环境。devstack 以交互方式用作开发环境和 OpenStack 项目大部分功能测试的基础。 devstack 透过执行 stack.sh 脚本&#xff0c;搭建 openstack 环境&…

lv20 QT主窗口4

熟悉创建主窗口项目 1 QAction 2 主窗口 菜单栏&#xff1a;fileMenu menuBar()->addMenu(tr("&File")); 工具栏&#xff1a;fileToolBar addToolBar(tr("File")); 浮动窗&#xff1a;QDockWidget *dockWidget new QDockWidget(tr("Dock W…

Threejs之精灵模型Sprite

参考资料 精灵模型Sprite…Sprite模拟下雨、下雪 知识点 注&#xff1a;基于Three.jsv0.155.0 精灵模型Sprite精灵模型标注场景(贴图)Sprite模拟下雨、下雪 精灵模型Sprite Three.js的精灵模型Sprite和Threejs的网格模型Mesh一样都是模型对象&#xff0c;父类都是Object3…

【设计者模式】单例模式

文章目录 1、模式定义2、代码实现&#xff08;1&#xff09;双重判空加锁方式两次判空的作用&#xff1f;volatile 关键字的作用&#xff1f;构造函数私有&#xff1f; &#xff08;2&#xff09;静态内部类【推荐】&#xff08;3&#xff09;Kotlin中的单例模式lateinit 和 by…

Matlab 最小二乘插值(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在多项式插值时,当数据点个数较多时,插值会导致多项式曲线阶数过高,带来不稳定因素。因此我们可以通过固定幂基函数的最高次数 m(m < n),来对我们要拟合的曲线进行降阶。之前的函数形式就可以变为: 二、实现…

spring Boot 报错RedisConnectionFailureException

错误描述&#xff1a; 错误重点&#xff1a;&#xff08;图片中蓝色区域&#xff09; Unable to connect to Redis; 无法连接到Redis Unable to connect to 127.0.0.1 无法连接到本地服务器 所以&#xff0c;错误是本地服务器没有连接上Redis所引起的 错误解析…

vue3中的父传子,子传父

在Vue 3中&#xff0c;父组件向子组件传递数据和子组件向父组件通信的方式与Vue 2非常相似&#xff0c;但Vue 3提供了Composition API和更多的响应式API&#xff0c;为组件间的通信提供了更多的可能性。下面是父传子和子传父的基本方法&#xff1a; ### 父组件传递数据给子组件…

【InternLM 实战营笔记】使用SDK接口上传模型到OpenXLab

概述 浦源内容平台-模型中心的Python SDK旨在为开发人员提供编程方式来管理和操作模型中心平台的功能&#xff0c;以便他们可以轻松地与模型中心进行交互和模型管理。通过Python SDK提供的推理接口&#xff0c;开发人员能够高效地调用不同的模型&#xff0c;实现模型应用的开发…

递归实现排列型枚举(c++题解)

题目描述 把 1~n 这 n(n<10) 个整数排成一行后随机打乱顺序&#xff0c;输出所有可能的次序。 输入格式 一个整数n。 输出格式 按照从小到大的顺序输出所有方案&#xff0c;每行1个。 首先&#xff0c;同一行相邻两个数用一个空格隔开。其次&#xff0c;对于两个不同的…

Linux——进程控制(二)进程等待

目录 前言 一、进程等待 二、如何进行进程等待 1.wait 2.waitpid 2.1第二个参数 2.2第三个参数 3. 等待多个进程 三、为什么不用全局变量获取子进程的退出信息 前言 前面我们花了大量的时间去学习进程的退出&#xff0c;退出并不难&#xff0c;但更深入的学习能为本…

048 异常

什么是异常 异常体系结构 异常的继承关系 Error Exception 异常处理机制 try&#xff1a;用{}将可能产生异常的代码包裹catch&#xff1a;与try搭配使用&#xff0c;捕获try包裹代码中抛出的异常并进行后续动作finally&#xff1a;跟在try后&#xff0c;在try和catch之后执行…

web3时事粥报

比特币正成为更具有吸引力的通胀对冲工具 在通胀的宏观经济浪潮中&#xff0c;比特币正逐渐崭露头角&#xff0c;成为那些渴望多元化投资组合的投资者眼中的璀璨明星。Kooner 预测&#xff0c;2024年&#xff0c;各种宏观经济挑战可能进一步提升比特币、黄金和白银等资产的避险…