数据结构·二叉树(一)

1. 树概念及结构

1.1 树的概念

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

        有一个特殊的节点,称为根节点,根节点没有前驱节点。

        除了根节点外,其余节点被分成M(M>0)个互不相交的集合,T1、T2、T3······,其中每个集合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

        节点的层次:从根开始定义起,根为第一层,根的子节点为第二层,以此类推

        树的高度或深度:树中节点的最大层次。如上图树的高度是4

        堂兄弟节点:父节点在同一层的节点互为堂兄弟。例如H、I互为堂兄弟节点

        节点的祖先:从根到该节点所经分支上的所有节点。例如A是所有节点的祖先

        子孙:以某节点为根的子树中任意节点都称为该节点的子孙。例如所有节点都是A的子孙

        森林:由m(m>0)棵互不相交的树的集合称为森林

        

1.3 树的表示

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

        这里就简单的了解其中最常用的孩子兄弟表示法(左孩子右兄弟表示法)

        父节点无论有几个孩子,都只指向它的大儿子,然后大儿子再指向它右边的兄弟,兄弟再指向兄弟,以此类推······

   

                

2. 二叉树概念及结构

2.1 概念

        一颗二叉树是节点的一个有限集合,该集合:

                1. 为空        

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

        注意:

                1. 二叉树不存在度大于2的节点

                2. 二叉树的子树有左右之分,次序不能颠倒,因为二叉树是有序树

2.2 特殊的二叉树

        满二叉树:

                一个二叉树,如果每一个层的节点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且总结点数是 2^k-1 则他就是满二叉树

        完全二叉树:

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

        简单点来说,满二叉树就是n-1层都是满的,只有最后一层(第n层)都是叶子节点;完全二叉树就是n-1层都是满的,最后一层不一定满,但是从左到右的节点是连续的

                        


2.3 二叉树的存储结构

        二叉树一般可以使用两种结构存储,顺序结构(数组),链式结构(链表)

2.3.1 顺序存储

        顺序存储只适合用于完全二叉树,因为如果不是的话会造成空间的浪费,而事实上,只有堆才使用顺序存储的方式。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树

        我们分析左图,就可以看出来在存储时,父子节点下标之间是存在数学关系的

        左孩子 = 父节点 * 2 + 1

        右孩子 = 父节点 * 2 + 2

        父节点 = (孩子 - 1)/2

        这是顺序存储的优势,当然命中率高我们就不再提了

        而右图就是它的缺点,当不是完全二叉树时,就要把那些空节点的位置留出来,不然父子节点的数学关系就失效了

2.3.2 链式存储

        链式存储就是用链表直接模拟二叉树,因为孩子最多只有两个,所以我们不用担心空间浪费的问题,因此不必使用左孩子右兄弟法

        链式存储又有两种方式,二叉链表,三叉链表,很好理解,我画图示意一下

3. 堆

3.1 堆的概念及结构

        简单来讲,堆就是把数据按完全二叉树的逻辑存储到一个一维数组中。同时我们将 任意父节点<=孩子 的结构称作小根堆(小堆); 将 任意父节点>=孩子 的结构称作大根堆(大堆)

                        小根堆(小堆)                                                        大根堆(大堆)

        根据小堆、大堆的特点,堆可以用作堆排序和TOP-K问题

3.2 堆的实现

        前面已经讲过很多次实现数据结构的流程了,之后便不再赘述。

        刚刚我们已经确定好了堆的实现方法——数组

                

3.2.1 初始化和销毁

3.2.2 插入数据   O(N*logN)

        这里我们实现一个小根堆,如果要弄大根堆的话道理是一样的,只需要在插入数据时略微改动即可

        插入数据的话肯定是尾插数组,那么思考一下尾插数组相当于在二叉树逻辑的什么位置插入数据,答案显然是下一个位置,哈哈好吧这是句废话

        观察上图,我们要插入一个数据50,那么就是插在数组下标是6的位置,同时二叉树逻辑中也是插在6的位置,它们的对应关系现在显而易见了吧

        现在来到下一步,我们插入了之后发现现在的二叉树不满足小根堆了,因为56大于了它的右儿子50,所以我们要进行调整。

        此时用到向上调整算法:就是说50这个数据的插入只影响它的祖先,并不会影响兄弟或者叔叔什么的,因此我们只对比它的父亲,如果50小于它的父亲,就将它们换位置,如此交换到50的父亲终于小于它为止。

        那么父节点的下标规律我们之前也已经总结过了,现在实现插入数据:

        向上调整算法:O(logN)

        

        

        这里如果我们想要得到大根堆,只需要把交换两个数据的判断条件改成大于号> 就行了,这样就会在孩子大于父亲的时候进行交换,如此形成在同一条线上的上大下小结构

3.2.3 取堆顶

        取堆顶就是取到最小或者最大值

                ​​​​​​​        

        现在问题来了,如果我们想取次小的数据,或者取次次小的数据怎么办,我们可以保证堆顶是最小的,次小的出现在第二层,但是次次小的我们无法保证是出现在第二层还是第三层。

        这样下去次次次小的怎么取,次次次次小的又怎么取,依次类推,那么这个问题的解决办法就是删去堆顶的数据,让次小的数据来到堆顶,并且这个上来的过程不能破坏堆的结构

3.2.4 删除堆顶的数据 O(logN)

        首先错误示范:直接将数组首元素删去,其他元素向前提,这种方法是删掉了堆顶元素,但是仅此而已,这么做破坏了堆的结构,兄弟变父子,叔侄变兄弟,简直倒反天罡!堆的大小结构全都乱套了,那么我们可以将这个堆重新放进堆中再整理一次,就像第一次把数据录进堆中一样。但是这种方式的时间复杂度是 O(N) 不好。

        正确方法:

                1. 收尾数据交换,删除尾部数据

                2. 向下调整算法 O(logN)

        ​​​​​​​        

        这么做不会破坏原堆的结构,同时大大缩减时间复杂度

        那么向下调整算法是怎么弄?

        首先根它的两个儿子比较,与较小的交换位置,如此循环下去。那么怎么判断该停了呢,是比自己的两个儿子都小,或者是换到了叶子位置,那么又如何判断到没到叶子位呢,就是判断自己还有没有左儿子,没有左儿子就说明自己没儿子了

        

        

        这里参数n是用来判断有没有向下调整到了叶子,如果到了就不用再调了,从参数parent开始向下调整

3.2.5 判断是否是空堆

        ​​​​​​​        ​​​​​​​        ​​​​​​​

        到此如何建立一个堆及其各种功能的实现就算是完成了

3.2.6 完整代码

        Heap.h

#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 HPInit(HP* php);
void HPDestroy(HP* php);

void HPPush(HP* php, HPDataType x);

//取堆顶
HPDataType HPTop(HP* php);

//删除堆顶的数据
void HPPop(HP* php);

//判断是否是空堆
bool HPEmpty(HP* php);

        Heap.c

#include"heap.h"

void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}


void HPDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

//交换
void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

//向上调整
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;
		}
	}
}

void HPPush(HP* php, HPDataType x)
{
	assert(php);
	
	if (php->capacity == php->size)
	{
		size_t newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;

		HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size] = x;
	php->size++;

	//向上调整
	AdjustUp(php->a, php->size - 1);
}



//取堆顶
HPDataType HPTop(HP* php)
{
	assert(php);

	return php->a[0];
}



//向下调整
void AdjustDown(HPDataType* a,int n,int parent)
{
	//假设法,选出小的孩子
	int child = parent * 2 + 1;//假设左孩子小
	while (child < n)//当child>=size时出数组了说明是叶子
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			//找到小的那个孩子
			child++;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//删除堆顶的数据
void HPPop(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);
}



//判断是否是空堆
bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

3.2.7 向下调整算法处理数组 O(N)

        这里是说如果给你一整个无序的数组,让你整理成堆备用,我们该怎么处理。

        比较容易想到的方案就是用 HPPop() 将数组中的元素一个一个的插入到堆中去,这个插入的过程中,我们使用了向上调整算法,整个方法的时间复杂度是O(N*logN)

        在此我提供一种新的处理方案,使用向下调整算法:

        我们直接将数组视为一个无序的堆,然后从最后一个非叶子节点开始向下调整,这个节点调整完后再调整前一个节点,直到所有节点全部调整完毕,或者说调到根,根再向下调整,根调整完,整个堆也就调整完了

        那么为什么要这么调整呢,然后一个无序的堆我们是无法进行向上调整的,向上调整的要求是:该节点上面的堆是有序的(不包括该节点)。因此我们只能考虑向下调整,向下调整的要求是:该节点下面的堆是有序的(不包括该节点)。叶子节点们下面已经没有堆了,所以不需要再进行调整,因此我们从最后一个非叶子节点开始调整,而最后一个非叶子节点就是最后一个节点的父亲。

        ​​​​​​​        ​​​​​​​        

        上代码:

        最后我们还可以计算出这种方案的时间复杂度是 O(N)

        我们回头看这两种处理方法,它们的做法是相似的,那为什么时间复杂度有差距,其原因在于满二叉树中一半的节点都会存储在最后一层中,而向下调整算法正好不用处理最下一层,倒数第二层中的每个节点也都最多只用处理一次,这样形成了一个 多数据*少调整次数 的结构。反观向上调整算法,是 多数据*多调整次数 的结构。

3.3 堆的应用

3.3.1 堆排序 O(N*logN)

        堆排序即利用堆的思想来进行排序,要注意:

                        排升序:建大堆

                        排降序:建小堆

        这里我们用排降序举例子

        可能有的同学觉得,排降序不应该排大根堆,然后找到数组中最大的数,放到第一位,之后删除它,找到第二大的数,如此下去,但是我们要注意,当我们将最大的数放到第一位的时候它处在堆根或者说数组的首元素,此时我们将它删除那堆的结构就被破坏了,那处理后面的数据又要重新建堆,每删一个数就要重新建一次堆,时间复杂度极大

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        因此我们要转换思路,排小根堆,将找到的最小值与数组尾部的值交换,再删除,之后向下调整找到第二小的值,再交换,如此反复,处理整个数组。说白了就是排小根堆,每次找到最小值落底。

                                

        代码如下:

        ​​​​​​​        

        这里我要提醒一下,排大根堆还是排小根堆取决于 AdjustDown() 函数中的条件,这里面的条件我们按需调整

        ​​​​​​​        

3.3.2 TOP-K 问题

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

        对于这类问题我们第一思路就是排序,但是由于数据量非常大,排序就不太可能了(可能数据都不能一下子全部加载到内存中)。所以最佳的方式就是用堆来解决,基本思路如下:

        1. 用数据集合前 K 个元素来建堆

                前K个最大元素,则建大堆

                前K个最小元素,则建小堆

        2. 用剩余的N-K个元素依次与堆顶元素来比较,判断是否需要替换堆顶元素

        简单点来讲,这个方案就是先挑出K个元素,之后继续扫描数据,判断是否要替换掉之前那K个元素中最大(或最小)的元素,最后结果就是剩下所有数据中的top-k个数据        

        首先我们先搞一堆数据(10000个)写在文件里

        ​​​​​​​        

        再实现我们上面的思路

                

        最后我们来测试一下能不能跑通

        ​​​​​​​        

        

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis核心数据结构之压缩列表(二)

压缩列表 压缩列表节点的构成 encoding 节点的encoding属性记录了节点的content属性所保存数据的类型及长度: 1.一字节、两字节或者五字节长&#xff0c;值得最高位为00、01或者10的是字节数组编码:这种编码表示节点的content属性保存着字节数组&#xff0c;数组的长度由编…

【Python】random库

专栏文章索引&#xff1a;Python 原文章&#xff1a;Python中random函数用法整理_python random-CSDN博客 目录 1.random.random() 2.random.uniform(a, b) 3.random.randint(a, b) 4.random.randrange([start], stop[, step]) 5. random.choice() 6. random.shuffle(x[,…

【每日八股】Java基础中面试你必须要掌握问题1

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害。## 如何解决浮点数运算的精度丢失问题&#xff1f; BigDecimal 可以解决精度问题的原因在于它是一个精确的十进制数学运算类&…

idea中设置字段唯一,后台做出提示(springboot项目)

在项目中&#xff0c;我们通常需要对某些字段做字段唯一的限制&#xff0c;保证在数据库中该字段对应的值不能出现重复的值&#xff0c;接下来看看怎么做吧~ 数据库中可以同时设置几个字段的unique&#xff0c;就比如用户在进行注册的时候&#xff0c;一个用户只能对应一个电话…

爬虫入门到精通_框架篇14(PySpider架构概述及用法详解)

官方文档 Sample Code&#xff1a; from pyspider.libs.base_handler import *class Handler(BaseHandler):crawl_config {}# minutes24 * 60&#xff1a;每隔一天重新爬取every(minutes24 * 60)def on_start(self):self.crawl(http://scrapy.org/, callbackself.index_page)…

week07day01(powerbi)

一. Power BI简介 1. 构成部分 power query: 进行简单的数据清洗power pivot : 进行指标计算power view &#xff1a; 进行报表视图 二. Power Query (进行数据清洗) 1. 如何获取数据&#xff1a; 点击获取数据 ——> 选择导入数据的类型——> 会出现 "加载&…

【刷题日志3.4--3.10】

绕过flag关键字od读取&#xff08;脚本&#xff09;空格过滤 [广东强网杯 2021 团队组]love_Pokemon <?php error_reporting(0); highlight_file(__FILE__); $dir sandbox/ . md5($_SERVER[REMOTE_ADDR]) . /;if(!file_exists($dir)){mkdir($dir); }function DefenderBon…

Pytorch搭建AlexNet 预测实现

1.导包 import torch import matplotlib.pyplot as plt import json from model import AlexNet from PIL import Image from torchvision import transforms 2.数据预处理 data_transform transforms.Compose([transforms.Resize((224, 224)), # 将图片重新裁剪transform…

【Android】 ClassLoader 知识点提炼

1.Java中的 ClassLoader 1.1 、ClassLoader的类型 Java 中的类加载器主要有两种类型&#xff0c;即系统类加载器和自定义类加载器。其中系统类 加载器包括3种&#xff0c;分别是 Bootstrap ClassLoader、Extensions ClassLoader 和 Application ClassLoader。 1.1.1.Bootstra…

uniapp开发DAPP钱包应用(二) Vue + Java

上一节我们讲了如何通过vue uniapp还有web3以及需要准备的相关组件&#xff0c;来搭建了DAPP开发的环境。 这一节&#xff0c;我们来说说如何用代码来实现DAPP相关接口。 1. ethers实现类 导入组件 import { ethers , providers , utils } from "ethers"; impor…

QML 控件添加键盘事件

在QML中&#xff0c;可以使用Keys类型来处理键盘事件。以下是一个简单的示例&#xff0c;演示如何在QML控件中添加键盘事件&#xff1a; import QtQuick 2.12 import QtQuick.Window 2.12Window {visible: truewidth: 640height: 480title: qsTr("Hello World")Recta…

DFS算法详解及例题

DFS:往深搜索&#xff0c;执着&#xff0c;确认从底下返回后的每个人的节点都已经用完&#xff0c;空间占用少&#xff0c;爆搜。 BFS:每一层搜索&#xff0c;稳重&#xff0c;(当一个图的权重都为1时搜到的一定是最短路) 下面我们以dfs的一道经典例题来讲解 代码: #include…

Ubuntu18.04 安装搜狗输入法

一. 概述 自己的Ubuntu 18.04系统配置中文搜狗输入法&#xff0c;安装步骤&#xff0c;亲测可用 二. 安装步骤 2.1 确认系统版本和CPU架构 查看Ubuntu系统版本号&#xff0c;通过命令 lsb_release -a wuubuntume:~$ lsb_release -a No LSB modules are available. Distr…

基于SpringBoot的“智慧食堂”系统(源码+数据库+文档+PPT)

基于SpringBootVue的智慧食堂系统的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootVUE工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统登录界面图 系统首页界面图 用户注册界面图 菜…

项目总结.

文章目录 1.DDD结构基础2. 抽奖领域3. 发奖领域4.活动领域5. 支撑领域应用层编排 1.DDD结构基础 包含&#xff1a; 接口层、应用层、领域层、基础层&#xff1b;通用包、接口层、接口定义。 接口层&#xff1a;实现RPC接口定义&#xff0c;引入应用层服务&#xff0c;封装具体的…

日期问题 刷题笔记

思路 枚举 19600101 到20591231这个区间的数 获得年月日 判断是否合法 如果合法 关于题目给出的日期 有三种可能 年/月/日 日/月/年 月/日/年 判断 是否和题目给出的日期符合 如果符合 输出 闰年{ 1.被4整除不被100整除 2.被400整除} 补位写法“%02d" 如果不…

【C语言步行梯】自定义函数、函数递归详谈

&#x1f3af;每日努力一点点&#xff0c;技术进步看得见 &#x1f3e0;专栏介绍&#xff1a;【C语言步行梯】专栏用于介绍C语言相关内容&#xff0c;每篇文章将通过图片代码片段网络相关题目的方式编写&#xff0c;欢迎订阅~~ 文章目录 什么是函数库函数自定义函数函数执行示例…

STM32 ADC数模转换器

单片机学习&#xff01; 目录 文章目录 前言 一、ADC简介 1.1 ADC名称 1.2 ADC功能 1.3 分辨率与转换时间 1.4 输入电压与转换范围 1.5 输入通道 1.6 增强功能 1.7 自动监测输入电压范围 1.8 STM32F103C8T6 ADC资源 二、逐次逼近型ADC 2.1 ADC内部结构原理图 2.2 输入通道选择 …

AI 驱动的医疗变革:迈向未来医疗新生态

直面呼啸而来的人工智能&#xff0c;医疗行业将首当其冲&#xff0c;发生翻天覆地的变化。美国心脏病学家兼基因学教授埃里克托普在《未来医疗》中预测&#xff0c;未来人类将拥有“健康小助手”——个人医疗数据和处理能力&#xff0c;还能轻松预防疾病。诸多评论家也持类似观…

Jade 处理XRD并计算半峰宽FWHM、峰面积、峰强度等数据

1.打开软件 2.导入测试的XRD数据 3.平滑数据 4.抠一下基底 5.分析具体数据 6.按住鼠标左键&#xff0c;在峰底部拉一条线&#xff0c;尽量和基底持平 7.结果就出来了&#xff0c;想要的都在里面&#xff0c;直接取值就行