【二叉树---堆的C语言实现】

news2024/9/20 5:29:53

1.树的概念与结构

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

  • 有一个特殊的节点,称为根节点,根节点没有前驱节点。
  • 除根节点外,其余节点被分成M(M>0)个互不交的集合,其中每一个集合又是一棵结构与树类似的子树。每棵子树的根节点有且仅有一个前驱,可以有0个或多个后驱。因此树是递归定义的

2.树的相关术语

父亲节点/双亲节点:若有一个节点含有子节点,则这个节点称为其子节点的父节点。

子节点/孩子节点:一个节点含有的子树的根节点称为该节点的子节点。

节点的度:一个节点有几个孩子,它的度就是多少。

树的度:一棵树中,最大的节点的度称为该树的度。

叶子结点/终端节点:度为0的节点称为叶子结点。

分支节点/非终端节点:度不为0的节点。

兄弟节点:具有相同的父节点互称为兄弟节点(亲兄弟)。

节点的层次:从根开始定义起,根为第一层,根的子节点为第二层。

树的高度或深度:树中节点的最大层次。

节点的祖先:从根到该节点所经分支上的所有的节点

路径:一条从树中任意节点出发,沿父子节点-子节点连接,达到任意节点的序列。

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。

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

3.二叉树的概念与结构

在树形结构中,我们最常用的就是二叉树,一棵二叉树就是节点的一个有限集合,该集合是由一个根节点加上两棵分别为左子树和右子树的二叉树组成或者为空。

二叉树具有的特点:

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

注意:对于任意的二叉树都是由以下几种情况复合而成的,空树(度为0),只有根节点(的二叉树),只有左子树(度为1),只有右节点(度为1),左右子树均存在(度为2)。

4.特殊的二叉树

4.1.满二叉树 :

一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树。

也就是说,如果一个二叉树的层数为k ,且节点的总数是2^k-1,则它就是满二叉树。 (会涉及到等比数列求和)

4.2.完全二叉树:

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。

对于深度为k的,有n个节点的二叉树,当且仅当其每一个节点都与深度为k 的满二叉树的满二叉树中编号从1至n的节点----对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。(假设二叉树层次为k,除了第k 层外,每层节点的个数的达到最大节点数,但第k层节点个数不一定达到最大节点数。

注:完全二叉树的节点的顺序是从左到右的。

满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

5.二叉树的存储

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

5.1顺序存储使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树就会有空间的浪费,完全二叉树更适合使用顺序结构存储。

5.2 链式存储就是使用链表俩表示一棵二叉树,即用链表来指示元素的逻辑关系。通常的方法是链表中每个节点由三个域组成,数据域和左右指针域,左右指针域分别用来给出该节点做孩子和右孩子所在的链表节点的存储地址。链式结构又分为二叉连和三叉链,当前我们学习中一般使用的都是二叉连。后面学习到高阶数据结构如红黑树会用到三叉链。

6.实现顺序结构二叉树(小堆)

一般使用顺序结构来存储数据,堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他特性。

6.1  堆的概念与结构

如果有一个关键码的集合k,把它所有的元素按完全二叉树的顺序存储方式,在一个一维数组中,并满足K(i)<=K(2*i+1)或者K(i)>K(2*i+1)且K(i)<=K(2*i+2),则称为小堆(大堆)将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

注意: 

        现实中我们通常把堆(一种二叉树)使用顺序结构来存储,需要注意的是这里的堆和操作系统的虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统管理内存的一块区域分段。

6.2 堆具有以下性质:

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

补充

  • 小堆的堆顶是堆的最小值;大堆的堆顶是堆的最大值。

6.3 二叉树的性质:

对于具有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否则无右孩子。

6.4 堆的实现

和前面的数据结构实现相类似,都需要三个文件Heap.h(堆结构的定义以及函数的声明),Heap.c(函数功能的实现),test.c(函数功能的测试)。

6.4.1  Heap.h(堆结构的定义以及函数的声明)

堆底层结构为数组,因此定义堆的结构和顺序表的结构类似。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//二叉树--堆的结构的定义和函数的声明
//堆是完全二叉树--基于数组进行实现

//堆的结构的定义
typedef int HPDatatype;
typedef struct Heap
{
	HPDatatype * arr;
	int size;
	int capacity;
}HP;

//初始化
void HPInit(HP* php);
//销毁
void HPDestroy(HP* php);

//插入数据
void HPPush(HP* php, HPDatatype x);

//判空
bool HPEmpty(HP* php);

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


//获取堆顶数据
HPDatatype HPTop(HP* php);

//获取堆底的最后一个数据
HPDatatype HPBottom(HP* php);

//获取堆中有效的数据个数
int HPSize(HP* php);

6.4.2 Heap.c(函数功能的实现)

#include "Heap.h"
6.4.2.1 堆的初始化和销毁
//初始化
void HPInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->size = php->capacity = 0;
}
//销毁
void HPDestroy(HP* php)
{
	assert(php);
	if (php->arr)
		free(php->arr);

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

【实现思路】 

        因为堆底层结构为数组,因此定义堆的结构和顺序表的结构类似。所以堆的初始化和销毁的实现和顺序表的销毁和初始化基本相同。

6.4.2.2 堆的插入数据(基于小堆)

//进行数据交换
void Swap(HPDatatype* x, HPDatatype* y)
{
	HPDatatype temp = *x;
	*x = *y;
	*y = temp;
}
//进行向上调整
void AdjustUp(HPDatatype* arr, int child)
{
	assert(arr);
	//由孩子节点求出双亲节点 进行大小比较
	int parent = (child - 1) / 2;
	while (child > 0)   //不要走到根节点即可,因为根节点没有双亲节点
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent= (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}
//插入数据   --建立小堆
void HPPush(HP* php, HPDatatype x)
{
	assert(php);
	//先判断空间够不够
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDatatype* temp = (HPDatatype*)realloc(php->arr,newcapacity*sizeof(HPDatatype));
		if (temp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		php->capacity = newcapacity;
		php->arr = temp;
	}
	php->arr[php->size]=x;
	//入堆是入堆底,需要进行 先  向上调整  ,再进项数据个数的更新
	AdjustUp(php->arr, php->size);
	php->size++;
}

【实现思路】 

        先进行所传地址的有效性的判断,再进行判断空间够不够,(判断空间够不够和顺序表的基本相同 ),其次再进行数据的插入。不过只将数据插入后,整体的结构未必满足堆的概念,所以在这里我们需要对堆中的数据进行调整(向上调整法)

        在实现向上调整法的时候,我们要一直保持父节点的值始终小于或等于孩子节点,所以我们需要通过孩子节找父亲节点,找到父亲节点之后需要和当前的孩子节点进行比较,孩子小就进行向上调整(如果孩子大就不需要进行调整,就直接跳出循环),即就是就进行数据的交换。

        每次调整完之后需要将指向当前孩子节点的变量向上移,当孩子节点指向根节点的时候终止循环

        调整完之后需要进行数据的++。

注意:向上调整方法的传参,需要传底层数组的地址和当前堆中有效的数据个数。

6.4.2.3 堆的判空
//判空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

【实现思路】

        我们需要先对所传地址的有效性进行判断,然后直接将堆中的有效的数据个数是否为0返回。(函数的返回值类型为bool类型)

6.4.2.4 堆的数据删除(基于小堆)

//向下调整方法  依照小根堆
void AdjustDown(HPDatatype* arr, int parent, int size)
{
	//计算出的是左孩子
	int child = 2 * parent + 1;
	while (child < size)
	{
		//找左,右孩子中最小值
		if (child+1<size && arr[child] > arr[child + 1])
			child++;
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child= 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

//删除数据  删除堆顶的数据
void HPPop(HP* php)
{
	assert(php);
	assert(php->size);
	//先进行数据交换,再进行数据的调整
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	//数据个数减1
	php->size--;
	//进行数据的调整
	//向下调整法
	AdjustDown(php->arr,0, php->size);
}

【实现思路】

        先判断所传地址的有效性,再进行堆的判空,如果堆为空则不能删除数据,堆不为空就可以进行删除数据。

        堆中要删除数据是删除的堆堆顶的元素。所以要先将堆顶的数据和堆底的最后一个元素先进行交换,再进行堆的size--,最后进行堆中数据的调整。(因为交换之后堆不一定能满足堆的结构,所以需要进行堆的调整)。

        在这里我们删除要实现的堆的调整是向下调整。在实现堆的向下调整的方法的时候,要保持父节点的值小于或等于孩子节点的值。我们在实现该方法的时候我们需要通过父亲节点找孩子节点。     通过当前的父亲节点找到其孩子节点,然后比较大小,如果父亲节点的值比其孩子节点的值大就交换两者的所对应的值。(反之就不交换,直接跳出循环)每次这样调整完之后,需要将指向当前父亲节点的变量指向其孩子节点的位置,然后将孩子节点移动到下一层(即孩子的孩子节点)。

        终止循环的条件为,当孩子节点刚走出最后一个位置时。

注意:向下调整方法的传参,堆的底层数组的地址,首元素的下标,堆中有效的元素的个数。

6.4.2.5 获取堆顶数据
//获取堆顶数据
HPDatatype HPTop(HP* php)
{
	assert(php&&php->size);
	return php->arr[0];
}

【实现思路】

        先判断所传地址的有效性,然后将堆的底层数组的第一个数据的值返回即可。

6.4.2.6 获取堆底的最后一个数据
//获取堆底的最后一个数据
HPDatatype HPBottom(HP* php)
{
	assert(php&&php->size);
	return php->arr[php->size - 1];
}

【实现思路】

        先判断所传地址的有效性,然后将堆的底层数组的最后一个数据的值返回即可。

6.4.2.7 获取当前堆的有效的数据个数
//获取堆中有效的数据个数
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

【实现思路】

        先判断所传地址的有效性,然后将堆中的size值返回即可。

6.4.3 test.c(函数功能的测试)

#include "Heap.h"
//ܵIJ
void test()
{
	HP hp;
	HPInit(&hp);
	int arr[]= { 17,20,10,13,19,15 };
	for (int i = 0; i < 6; i++)
	{
		HPPush(&hp, arr[i]);
	}
	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}
	HPDestroy(&hp);
    //剩余函数的功能测试可以自己进行测试
}
int main()
{
	test();
	return 0;
}

今日博客就先分享到这里了,记得数据结构这块需要多加练习,多思考,多敲代码,熟能生巧!!!

如有错误还望大家指出!

关注博主,优质内容不断更新!!!

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

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

相关文章

【基础算法】位运算

位运算 概念位运算模板模板题 概念 异或&#xff08;x⊕y或x ^ y&#xff09; 高低位交换:https://www.luogu.com.cn/problem/P1100 题意&#xff1a;给定一个32 3232位整数x xx&#xff0c;在二进制下交换其前16 1616位与后16 1616位&#xff0c;输出最终的数。 答案为ans (…

JVM系列--垃圾回收

在C/C这类没有自动垃圾回收机制的语言中&#xff0c;一个对象如果不再使用&#xff0c;需要手动释放&#xff0c;否则就会出现内存泄漏。内存泄漏指的是不再使用的对象在系统中未被回收&#xff0c;内存泄漏的积累可能会导致内存溢出。 在这段代码中&#xff0c;通过死循环不停…

besier打断和升阶,高阶性质

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 问题描述 对besier曲线在u处打断&#xff0c;生成两条besier曲线对besier曲线升阶处理 bezier高阶性质 求导推导 P ( t ) ∑ i 0 n B i n ( t ) b i \boldsymbol …

uniapp中H5网页怎么实现自动点击事件

<template><view><button ref"myButton" click"handleClick">点击我</button></view> </template><script> export default {mounted() {this.$nextTick(() > {const button this.$refs.myButton;console.l…

【系统分析师】-综合知识-计算机系统基础

1、流水线的吞吐率是指流水线在单位时间里所完成的任务数或输出的结果数。设某流水线有 5 段&#xff0c;有 1 段的时间为 2ns &#xff0c;另外 4 段的每段时间为 1ns&#xff0c;利用此流水线完成 100 个任务的吞吐率约为&#xff08;16&#xff09;个/s 。 2、矢量图像通过使…

Python+PyCharm安装和配置(详细步骤)

Python的安装步骤可以根据用户选择的安装方式&#xff08;如使用安装包安装或源码安装&#xff09;而有所不同。以下将详细讲解两种安装方式的步骤&#xff0c;并附上源码安装的相关说明。 一、使用安装包安装Python 1. 访问Python官网 打开浏览器&#xff0c;输入Python官…

Resilience4J服务熔断隔离与限流

为了保障文章的流畅性&#xff08;文章穿插大量的环境搭建没意思&#xff0c;会干扰文章的主题&#xff0c;无聊的很&#xff09;&#xff0c;将环境的搭建与测试&#xff0c;工具的版本说明放了文末&#xff1a; 六、环境搭建。 一、Circuit Breaker是什么 1.1、官网 https…

C++笔记---内存管理

1. 内存分布 在对操作系统有更加深入的了解之前&#xff0c;在写代码的层面我们需要对下面的几个内存区域有所了解&#xff1a; 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 2. 堆--用于程序运行时动态内存分配&#xff0c;堆是可以上增长…

【数据结构3】哈希表、哈希表的应用(集合与字典、md5算法和文件的哈希值)

1 哈希表 哈希表一个通过哈希函数来计算数据存 储位置的数据结构&#xff0c;通常支持如下操作: 插入(键&#xff0c;值):插入键值对(键&#xff0c;值) Get(key):如果存在键为键的键值对则返回其值&#xff0c;否则返回空值 删除(键):删除键为键的键值对哈希表(Hash Table&am…

数据仓库系列 2:数据仓库的核心特点是什么?

想象一下,你正站在一座巨大的数据金矿前。这座金矿蕴含着海量的商业洞察,可以帮助你的公司做出精准决策,提升效率,远超竞争对手。但是,如何高效地开采、提炼和利用这些数据黄金呢?答案就是:数据仓库。 目录 什么是数据仓库?数据仓库的核心特点面向主题的组织集成性非易失性…

RTL-SDR SpectrumPy频谱显示

GITHUB大佬开源的基于RTL-SDR的python频谱显示程序链接&#xff0c;下载下来后&#xff0c;安装必要的库&#xff0c;编译运行&#xff0c;运行报错。 修改了以下两个地方&#xff1a; 修改点1&#xff1a; 修改前&#xff1a; self.spinBoxFrequency.setValue(self.center_fr…

【Python从入门到进阶】63.Pandas如何实现数据的Merge

接上篇《62、Pandas中DataFrame对象案例实践》 上一篇我们延续之前学习的DataFrame对象的知识&#xff0c;结合一个数据案例进行了实践操作。本篇我们来学习Pandas如何实现数据的Merge。 一、引言 在当今数据驱动的时代&#xff0c;数据分析已成为各行各业不可或缺的一部分。…

【JAVA基础】四则运算符

文章目录 四则运算结合运算符自增运算符关系和boolean运算符 四则运算 在java当中&#xff0c;使用运算符、-、*、/ 表示加减乘除&#xff0c;当参与 / 运算的两个操作数都是整数的时候&#xff0c;表示整数除法&#xff1b;否则表示浮点数。整数的求余操作用 % 表示。 Syste…

【Java】/* 与树有关的一些概念 */

一、关于树的一些概念 1. 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看 起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a;…

记录一次经历:使用flask_sqlalchemy集成flask造成循环导入问题

前言&#xff1a; 工作需求&#xff0c;写一个接口&#xff0c;用Python来编写&#xff0c;我首先想到用flask小型框架来支撑&#xff0c;配置sqlalchemy来实现&#xff0c;但是在实现的过程中&#xff0c;发生循环导入问题 我想到用蓝图来解决此问题&#xff0c;但是仍然会出死…

UI测试使用webdriver-manager免安装浏览器驱动

引言&#xff1a; selenium传统的方式是下载浏览器对应的driver&#xff08;驱动&#xff09;&#xff0c;放到本地的指定位置&#xff0c;然后写代码加载这个driver&#xff08;驱动&#xff09;再执行相应的操作。 弊端&#xff1a; 传统方法存在两个麻烦的地方: 1.需要下…

安全面试常见问题任意文件下载

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 1.1 任意文件下…

Git的使用教程及常用语法03

七.如何从版本库中删除文件 第一种方式&#xff1a;直接在工作区删除文件&#xff0c;然后提交 rm ffile1.txt (注意&#xff1a;这个不是git命令&#xff0c;而是linux命令) 看到状态发现&#xff0c;文件file1.txt已经被删除&#xff0c;提示需要提交到暂存区。 因为我们只…

蓝牙对象交换协议(OBEX) - 概念介绍

零.声明 本专栏文章我们会以连载的方式持续更新&#xff0c;本专栏计划更新内容如下&#xff1a; 第一篇:蓝牙综合介绍 &#xff0c;主要介绍蓝牙的一些概念&#xff0c;产生背景&#xff0c;发展轨迹&#xff0c;市面蓝牙介绍&#xff0c;以及蓝牙开发板介绍。 第二篇:Trans…

SpringBoot集成kafka-监听器注解

SpringBoot集成kafka-监听器注解 1、application.yml2、生产者3、消费者4、测试类5、测试 1、application.yml #自定义配置 kafka:topic:name: helloTopicconsumer:group: helloGroup2、生产者 package com.power.producer;import com.power.model.User; import com.power.uti…