【海贼王的数据航海:利用数据结构成为数据海洋的霸主】探究二叉树的奥秘

目录

1 -> 树的概念及结构

1.1 -> 树的概念

1.2 -> 树的相关概念

1.3 -> 树的表示

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

2 -> 二叉树概念及结构

2.1 -> 二叉树的概念

2.2 -> 现实中的二叉树

2.3 -> 特殊的二叉树

2.4 -> 二叉树的性质

2.5 -> 二叉树的存储结构

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

3.1 -> 二叉树的顺序结构

3.2 -> 堆的概念及结构

3.3 -> 堆的实现

3.3.1 -> 堆向下调整算法

3.3.2 -> 堆的创建

3.3.3 -> 建堆的时间复杂度

3.3.4 -> 堆的插入

3.3.5 -> 堆的删除

3.3.6 -> 堆的代码实现

Heap.h

Heap.c

3.4 -> 堆的应用

3.4.1 -> 堆排序

4 -> 二叉树链式结构的实现

4.1 -> 前置说明

4.2 -> 二叉树的遍历

4.2.1 -> 前序、中序和后序遍历

 4.3 -> 节点个数以及高度


1 -> 树的概念及结构

1.1 -> 树的概念

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

  • 有一个特殊的结点,称为根结点,根结点没有前驱结点。
  • 除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、……、Tn,其中每一个集合Ti(1 <= i <= m)又是一棵结构与树类似的子树。每棵树的根结点有且只有一个前驱,可以有0个或多个后继。
  • 树是递归定义的。

注:

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

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

1.3 -> 树的表示

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

#include <iostream>
using namespace std;typedef int DataType;typedef struct Node
{struct Node* _firstChild1; // 第一个孩子结点struct Node* _pNextBrother; // 指向其下一个兄弟结点DataType _data; // 结点中的数据域
}Node;int main()
{return 0;
}

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

2 -> 二叉树概念及结构

2.1 -> 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或为空
  2. 由一个根结点加上两颗分别称为左子树和右子树的二叉树组成

从上图可以看出:

  1. 二叉树不存在度大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注:

对于任意二叉树都是由以下几种情况复合而成的:

2.2 -> 现实中的二叉树

2.3 -> 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^{k} - 1,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出的。对于深度为K的,有n个结点的二叉树,当且仅当每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。

2.4 -> 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^{(i- 1)}个结点。
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^{h} - 1
  3. 对任何一棵二叉树,如果度为0其叶结点个数为n_{_{0}},度为2的分支结点个数为n{_{2}},则有n{_{0}} = n_{_{2}} + 1
  4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h = log_{2}(n+1),(ps:log_{2}(n+1)是log以2为底,n+1为对数)。
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
  • 若i > 0,i位置结点的双亲序号:(i - 1) / 2;i = 0,i为根结点编号,无双亲结点
  • 若2i + 1 < n,左孩子序号:2i + 1,2i + 1 >= n否则无左孩子
  • 若2i + 2 < n,右孩子序号:2i + 2,2i + 2 >= n否则无右孩子

2.5 -> 二叉树的存储结构

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

1. 顺序存储:

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2. 链式存储:

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

#include <iostream>
using namespace std;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,……,则称为小堆(或大堆)。将根结点的最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。

堆的性质:

  • 堆中某个结点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

3.3 -> 堆的实现

3.3.1 -> 堆向下调整算法

现在给出一个数组,逻辑上看做一棵完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int arr[] = {27,15,19,18,28,34,65,49,25,37};

3.3.2 -> 堆的创建

int arr[] = {1,5,3,8,7,6};

3.3.3 -> 建堆的时间复杂度

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

因此:建堆的时间复杂度为O(N).

3.3.4 -> 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

3.3.5 -> 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

3.3.6 -> 堆的代码实现

Heap.h
#pragma once#define  _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;// 堆的初始化
void HeapInit(HP* php);// 堆的销毁
void HeapDestory(HP* php);// 堆的向上调整
void AdjustUp(HPDataType* a, int child);// 堆的向下调整
void AdjustDown(HPDataType* a, int child);// 堆的插入
void HeapPush(HP* php, HPDataType x);// 堆的删除
void HeapPop(HP* php);// 取堆顶的数据
HPDataType HeapTop(HP* php);// 堆的数据个数
int HeapSize(HP* php);// 堆的判空
bool HeapEmpty(HP* php);
Heap.c
#include "Heap.h"// 堆的初始化
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}// 堆的销毁
void HeapDestory(HP* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->capacity = 0;
}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;}else{break;}}
}// 堆的向下调整
void AdjustDown(HPDataType* a, int child)
{int parent = (child - 1) / 2;//while (parent >= 0)while (child > 0){if (a[child] < a[parent]){HPDataType tmp = a[child];a[child] = a[parent];a[parent] = tmp;child = parent;parent = (child - 1) / 2;}else{break;}}
}// 堆的插入
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");return;}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustDwon(php->a, php->size - 1);
}// 堆的删除
void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}// 取堆顶的数据
HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];
}// 堆的数据个数
int HeapSize(HP* php)
{assert(php);return php->size;
}// 堆的判空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

3.4 -> 堆的应用

3.4.1 -> 堆排序

堆排序即利用堆的思想来进行排序,总共分两个步骤:

1. 建堆

  • 升序:建大堆
  • 降序:建小堆

2. 利用堆删除思想来进行排序

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

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;
}

注:

上述代码并不是创建二叉树的方式。

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 PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

中序遍历:

void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}

后序遍历:

void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}

4.2.2 -> 层序遍历

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

层序遍历:

void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->data);if(front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}printf("\n");QueueDestroy(&q);
}

 4.3 -> 节点个数以及高度

// 求节点的个数
int BTreeSize(BTNode* root)
{/*if (root == NULL)return 0;return BTreeSize(root->left)+ BTreeSize(root->right)+ 1;*/return root == NULL ? 0 : BTreeSize(root->left)+ BTreeSize(root->right) + 1;
}// 求叶子节点的个数
int BTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL&& root->right == NULL){return 1;}return BTreeLeafSize(root->left)+ BTreeLeafSize(root->right);
}// 求二叉树的高度
int BTreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = BTreeHeight(root->left);int rightHeight = BTreeHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}// 二叉树第k层结点个数
int BTreeLevelKSize(BTNode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;return BTreeLevelKSize(root->left, k - 1)+ BTreeLevelKSize(root->right, k - 1);
}// 二叉树查找值为x的结点
BTNode* BTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret1 = BTreeFind(root->left, x);if (ret1)return ret1;BTNode* ret2 = BTreeFind(root->right, x);if (ret2)return ret2;return NULL;
}

感谢各位大佬支持!!!

大佬们互三啦!!!

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

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

相关文章

储能系统--户用储能美洲市场(三)

2、美洲市场 2.1、美国户储发展驱动力 &#xff08;1&#xff09;电网老化带来配储需求&#xff0c;户用光储成家庭第二用电保障 美国大部分电网建于20世纪60和70年代&#xff0c;超70%以上的输电系统已经超过了25年&#xff0c;在高负荷运转或者外部环境承压时&#xff0c;…

波动数列 刷题笔记

思路分析 dp 找出状态转移方程 设d为a或者-b 代码 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N1010,MOD100000007; int get_mod(int a,int b){ return (a%bb)%b; …

每日五道java面试题之springMVC篇(二)

目录&#xff1a; 第一题. 请描述Spring MVC的工作流程&#xff1f;描述一下 DispatcherServlet 的工作流程&#xff1f;第二题. MVC是什么&#xff1f;MVC设计模式的好处有哪些?第三题. 注解原理是什么?第四题. Spring MVC常用的注解有哪些&#xff1f;第五题. SpingMvc中的…

vscode插件-TONGYILingma

通义灵码&#xff0c;是一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研发智能问答、异常报错排查等能力&#xff0c;并针对阿里云 SDK/API 的使用场景调优&#xff0c;为开发者带来高…

04-ESP32S3-GPIO

ESP32S3-IDF GPIO GPIO简介 ESP32S3提供了多达45个物理GPIO管脚&#xff0c;这些管脚不仅可以作为通用的输入输出接口&#xff0c;还可以连接到内部外设信号。通过GPIO交换矩阵、IO MUX和RTC IO MUX&#xff0c;可以灵活地配置外设模块的输入信号来源于任何GPIO管脚&#xff0…

访问一次网站的全过程

目录 流程图&#xff1a; 一、应用层开始 1. 在浏览器输入https://www.baidu.com 2. DNS获取IP地址 3. 根据HTTP协议生成HTTP请求报文 应用层结束 二、传输层开始 4. TCP三次握手 传输层结束 三、网络层开始 5. IP寻址 6. ARP协议获取MAC地址 网络层结束 四、数据…

C++面试宝典一部分

今天整理书籍资料时&#xff0c;发现多年前打印的面试资料&#xff0c;拍照分享给大家。

2013-2021年全国31省电子商务销售额数据(无缺失)

2013-2021年全国31省电子商务销售额数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;2013-2021年 2、指标&#xff1a;电子商务销售额数据 3、来源&#xff1a;国家TJ局、各省NJ 4、范围&#xff1a;31省 5、缺失情况&#xff1a;无缺失 6、指标解释&#xff1a;…

阿里云实现两个VPC网络资源互通

背景 由于实际项目预算有限&#xff0c;两套环境虽然分别属于不同的专有网络即不同的VPC&#xff0c;但是希望借助一台运维机器实现对两个环境的监控和日常的运维操作 网络架构 如下是需要实现的外网架构图&#xff0c;其中希望实现UAT环境的一台windows的堡垒机可以访问生产…

js 添加、删除DOM元素

1. js添加、删除DOM元素 1.1. 添加DOM元素 1.1.1. appendChild()方法 该方法添加的元素位于父元素的末尾&#xff0c;使用方法&#xff1a; parentNode.appenChild(NewNode) // parentNode是需要添加元素的容器&#xff0c;NewNode是新添加的元素   创建一个li元素并添加到…

Hack The Box-Crafty

目录 信息收集 rustscan whatweb WEB 漏洞利用 漏洞说明 漏洞验证 提权 get user.txt get Administrator 总结 信息收集 rustscan ┌──(root㉿ru)-[~/kali/hackthebox] └─# rustscan -a 10.10.11.249 --range0-65535 --ulimit5000 -- -A -sC [~] Automatically…

php调用guzzlehttp库时出现Segmentation fault的解决方案

先说结论&#xff0c;这个问题的原因是因为php7.4与openssl3不兼容产生的&#xff0c;解决方案如下&#xff1a; 输入openssl version -a查看openssl版本&#xff0c;如果是3以上的版本与php7.4不兼容&#xff0c;7.4以下的没测试过&#xff0c;估计也有问题。我最终是安装上了…