二叉树顺序结构及链式结构

news2025/1/24 4:51:43

一.二叉树的顺序结构

1.定义:使用数组存储数据,一般使用数组只适合表示完全二叉树,此时不会有空间的浪费

注:二叉树的顺序存储在逻辑上是一颗二叉树,但是在物理上是一个数组,此时需要程序员自己想清楚调整数据时应该怎样调整

2.堆

(1)定义:集合中所有元素按照完全二叉树的顺序存储在一个一维数组中,并满足Ki<=K2*i+1且K2*i+2(或Ki<=K2*i+1且K2*i+2)的规律,则称之为堆

(2)性质:

1>堆中某个节点的值总是不大于(或不小于)其父节点的值

2>堆是一颗完全二叉树

3>大堆:任何一个父节点的值>=孩子的值

    小堆:任何一个父节点的值<=孩子的值

(3)堆的实现(以小堆为例)

1>堆的结构体定义:数组指针(用于存储数据),有效数据个数,空间容量大小

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* a;//数组指针
	int size;//有效数据个数
	int capacity;//有效空间大小
}HP;

2>堆的初始化:

//堆的初始化
void HPInit(HP* ph)
{
	assert(ph);
	ph->a = NULL;
	ph->size = ph->capacity = 0;
}

3>堆的销毁:

//堆的销毁
void HPDestory(HP* ph)
{
	assert(ph);
	free(ph->a);
	ph->a = NULL;
	ph->size = ph->capacity = 0;
}

4>向堆中插入数据:

**思路:将数据插在原数据的最后面,在不断向上调整以保证插入数据后仍为小堆 

**画图解释

**代码实现

//向堆内插入数据
void HPPush(HP* ph, HeapDataType x)
{
	assert(ph);
	//判断是否需要增容
	if (ph->size == ph->capacity)
	{
		int newcapacity = ph->capacity * 2 == 0 ? 4 : ph->capacity * 2;
		HeapDataType* tmp = (HeapDataType*)realloc(ph->a, sizeof(HeapDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ph->a = tmp;
		ph->capacity = newcapacity;
	}
	ph->a[ph->size] = x;
	ph->size++;
	AdjustUp(ph->a, ph->size-1);

}

5>向上调整数据:

思路:找孩子的父亲,判断父亲是否大于孩子,若大于则交换父子地位,继续向上调整

 注:由于堆是完全二叉树,一个父亲最多有两个孩子,所以父亲的下标应该是孩子的下标减一再除以2,即parent=(child-1)/2;

//向上调整
void AdjustUp(HeapDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent= (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

6>删除根部数据:

思路:先交换根部数据和最后一个数据,再删除根部数据,同时将最后一个数据向下调整以保证删除后的堆仍为小堆

//删除堆顶数据(根位置)
void HPPop(HP* ph)
{
	assert(ph);
	Swap(&ph->a[0], &ph->a[ph->size - 1]);
	ph->size--;
	AdjustDown(ph->a, ph->size, 0);
}

7>向下调整数据:

**思路:先找左右孩子中较小的那个孩子,与父亲相比,若父亲大于孩子,则交换父子地位,继续向下调整

注:由于堆是完全二叉树,一个父亲最多有两个孩子,所以左孩子的下标应该是父亲的下标乘以2再加1,即child=parent*2+1;

**画图解释

**代码实现

//向下调整
void AdjustDown(HeapDataType* 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[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}

	}
}

(4)堆的应用

1>堆排序

1.1)思路:注:降序:建小堆,升序建大堆

             将数组中的元素建堆,交换最后的数据与首位数据,并进行向下调整,让size--,循环操                 作,直至完成排序

1.2)时间复杂度:O(N*log N)

1.3)画图解释:

1.4)代码实现

void HeapSort(int* a, int size)
{
	//将数组建堆
	for (int i = 1; i < size; i++)
	{
		AdjustUp(a, i);
	}
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

2>TOP_K(以求前K个最大的数据为例)

2.1)定义:求出数据中前K个最大的数据(或前K个最小的数据)

2.2)思路:

思路一:创建一个含N个节点的大堆,PopK次,即可获取前K个最大的数据

              弊端:当N非常大时,在创建节点时需要占用大量的内存

思路二:建一个含K个节点的小堆,将剩余的N-K个数据与堆顶数据相比,若大于堆顶数据则入                    堆进行向下调整,否则进行下一个比较

注:思路二更优,效率高

2.3)代码实现

void TOP_K(int* a,int n,int k) 
{
    //在文件中读取数据
	const char* fp = "data.txt";
	FILE* fout = fopen(fp, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	//读取前K个数据
	for (int i = 0; i < k; i++)
	{
		fscanf(fout,"%d", &a[i]);
	}

	//建小堆
	for (int i = (k-1-1)/2;i>=0;i--)
	{
		AdjustDown(a, k, i);
	}

	//将剩余的n-k个数据于堆顶数据相比
	int x = 0;
	while (fscanf(fout,"%d",&x)!=EOF)
	{
		if (a[0]<x)
		{
			a[0] = x;
			AdjustDown(a, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

2.4)数据验证

思路:将N的节点的数据模上N,是他们处于小于N的状态,在随机挑K个数据将他们调大于N,若在选出来的K个数据为大于N的数据,则说明该程序执行的是选取前K个最大的数据的指令

二.二叉树的链式结构(以二叉链表为例)

注:在二叉树的实现中多用递归来达到一层一层向下查找的目的(所以读者需要熟练掌握递归的相关知识)

1.定义:用链表表示一颗二叉树,即用链表表示元素之间的逻辑关系

2.二叉树节点的定义:该节点内存储的数据,指向左孩子的指针,指向右孩子的指针

typedef int BTNodeData;
typedef struct BinaryTreeNode
{
	BTNodeData val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

3.二叉树的创建(根据前序遍历来创建二叉树):

以数组abd##e#h##cf##g##构建二叉树

1>思路:1)若当前数据为#,则返回NULL;

               2)否则开辟一个二叉树节点root,将该数据赋给val,给左,右孩子赋值,通过调用函数递                 归实现

2>代码实现:

//创建二叉树
BTNode* BTCreate(BTNodeData* a, int* pi)
{
	if (a[*pi] == ‘#’)
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->val = a[*pi];
	(*pi)++;
	root->left = BTCreate(a,pi);
	root->right = BTCreate(a, pi);

	return root;
}

4.二叉树的前序遍历:

1>访问顺序:根,左子树,右子树

2>思路:如果根为空,打印N,什么都不返回;否则先打印根的值,再调用该函数,将左子树的地址作为参数,然后调用该函数,将右子树的地址作为参数,利用递归实现前序遍历

3>代码实现

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

5.二叉树的中序遍历

1>访问顺序:左子树,根,右子树

2>思路:与前序遍历类似

3>代码实现

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}

6.二叉树的后序遍历

1>访问顺序:左子树,右子树,根

2>思路:与前序遍历类似

3>代码实现

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

7.二叉树的层序遍历

1>思路:利用队列,让上一层的节点先入队列,在删除他们时,带进去下一层,若节点为空不进队列

2>代码实现:

//层序遍历
void BTLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->val);

		//当节点不为空时入队列
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if(front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	printf("\n");
	QueueDestory(&q);
}

8.二叉树的节点个数

1>思路:1)如果根节点为空,返回0;

               2)否则返回左子树的节点数+右子树的节点数+1(利用递归法实现左右子树节点数的计算)

2>代码实现:

//二叉树节点个数
int BTSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	return  BTSize(root->left) + BTSize(root->right) + 1;
}

9.二叉树的叶子节点个数

1>思路:1)如果根为空,返回0;

               2)如果左右孩子均为空,返回1;

               3)否则返回左子树叶子数+右子树叶子数(利用递归实现左右子树叶子数的计算)

2>代码实现:

//二叉树叶子节点个数
int BTLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BTLeafSize(root->left) + BTLeafSize(root->right);
}

10.二叉树第K层节点个数

1>思路:1)如果根为空返回0;

               2)如果k==1,返回1;

               3)否则返回左子树的k-1层+右子树的k-1层

注:将树一层一层向下压,直至出现两种特殊情况

2>代码实现:

//二叉树第K层节点个数
int BTLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
}

11.二叉树查找值为X的节点

1>思路:1)若根为空,返回空;

               2)若根的值等于待查找数据,返回根的地址;

              3)否则查找左子树是否有值为X的节点若有返回该节点的地址,否则查找右子树是否有值                  为X的节点若有返回该节点的地址,若左右子树均没有是否有值为X的节点,则返回空

2>代码实现:

//二叉树查找值为X的节点
BTNode* BTFind(BTNode* root, BTNodeData x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->val == x)
	{
		return root;
	}

	//比较左子树是否有节点值为x
	BTNode* ret1 = BTFind(root->left, x);
	if (ret1!=NULL)
	{
		return ret1;
	}

	//比较右子树是否有节点值为x
	BTNode* ret2 = BTFind(root->right, x);
	if (ret2 != NULL)
	{
		return ret2;
	}

	//左右子树中均未找到值为x的节点
	return NULL;
}

12.树的高度

1>思路:1)若根为空,返回0;

               2)否则记录并分别求左右子树的高度,哪个值更大哪个即为树的高度

注:每次都需记录所求的树的高度,否则会出现走到上一层忘了下一层的情况,导致反复求某棵树高度的情况

2>代码实现:

//二叉树的高度
int BTHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int HeightLeft = BTHeight(root->left)+1;
	int HeightRight = BTHeight(root->right)+1;

	return HeightLeft > HeightRight ? HeightLeft  : HeightRight ;
}

13.二叉树的销毁

1>思路:用后序遍历的思想先销毁左右子树,再销毁根(若先销毁根,销毁根后,无法找到左右子树)

2>代码实现

//二叉树的销毁
void BTDestory(BTNode* root)
{
	if (root == NULL)
		return;

	BTDestory(root->left);
	BTDestory(root->right);
	free(root);

}

14.判断二叉树是否为完全二叉树

1>思路:无论节点是否为空都入队列,当检查到第一个空节点时,开始遍历后面的节点看是否有非空节点,若有,则不是完全二叉树,否则是完全二叉树

2>代码实现

//二叉树是否为完全二叉树
bool BTComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		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)
		{
			QueueDestory(&q);
			return false;
		}
	}

	QueueDestory(&q);
	return true;
}

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

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

相关文章

GPT-4o: 未来的智能助手

GPT-4o: 未来的智能助手 在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为我们生活中不可或缺的一部分。作为OpenAI最新推出的语言模型&#xff0c;GPT-4o不仅继承了前几代模型的优点&#xff0c;还在多个方面进行了显著的提升。本文将带你深入了解…

C++:vector基础讲解

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《C&#xff1a;vector基础讲解》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞&#xff01;&#…

网络编程day7

思维导图 数据库编程实现学生管理系统 #include <header.h> #define ID 1 #define NAME 2 #define AGE 3 #define SCORE 4 int do_add(sqlite3 *ppdb) {int add_numb;char add_name[20];int add_age;double add_score;printf("enter student id:");scanf(&quo…

1076: 判断给定有向图是否存在回路

解法&#xff1a; 直观的方法用邻接矩阵dfs,这是错误的代码 #include<iostream> #include<vector> using namespace std; int arr[100][100]; int f 0; void dfs(vector<int>& a, int u) {a[u] 1;for (int i 0; i < a.size(); i) {if (arr[u][i]…

绝缘监测系统在1kV 及以下低压配电系统的应用

安科瑞电气股份有限公司 祁洁 acrelqj 一、系统概述 Acrel-2000L/A 绝缘监测系统设备适用于 1kV 及以下低压配电系统。该设备可以集中采集监测显示绝缘监测仪的数据&#xff0c;实现最多 8 个绝缘监测仪的数据&#xff0c;并且实时记录告警信息和曲线查询。匹配的绝缘监测仪…

bootstrap入门

官方网站&#xff1a;全局 CSS 样式 Bootstrap v3 中文文档 | Bootstrap 中文网 里面各种可以直接用的组件 不全的话可以网上搜索Boostrap常用的按钮样式_btn-large-dim-CSDN博客 怎么在vue项目中使用呢 npm install bootstrap 下载下来然后在main.js加上红框三句后&#…

SpringCloudAlibaba:6.2RocketMQ的普通消息的使用

简介 普通消息也叫并发消息&#xff0c;是发送效率最高&#xff0c;使用最多的一种 依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSch…

1701java药品进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web药品进销存管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境 为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为s…

解决vue3 vite打包报Root file specified for compilation问题

解决方法&#xff1a; 修改package.json打包命令 把 "build": "vue-tsc --noEmit && vite build" 修改为 "build": "vite build" 就可以了 另外关于allowJs这个问题&#xff0c;在tsconfig.json文件中配置"allowJs&qu…

重学java 39.多线程 — 线程安全

逐渐成为一个情绪稳定且安静成长的人 ——24.5.24 线程安全 什么时候发生&#xff1f; 当多个线程访问同一个资源时&#xff0c;导致了数据有问题&#xff0c;出现并发问题&#xff0c;数据不能及时更新&#xff0c;导致数据发生错误&#xff0c;出现线程安全问题 多线程安全问…

【不太正常的题】LeetCode.232:用栈的函数接口实现队列

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;初阶数据结构刷题 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f697; 1.问题描述&#xff1a; 题目中说了只能使用两个栈实现队列&#xff0c;并且只能使用…

【Crypto】password

文章目录 password解题感悟 password 试试flag{zs19900315} 提交成功 解题感悟 这题有点大病

软考--软件设计师--试题六--工厂方法模式(Factory Method)

工厂方法模式(Factory Method) 1、意图 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪儿一个类&#xff0c;factory method使一个类的实例化延迟到其子类。 2、结构 3、适用性 a、当一个类不知道它所必须创建的对象的类的时候。 b、当一个类希望由它的子类来指定…

UPPAAL使用方法

UPPAAL使用方法 由于刚开始学习时间自动机及其使用方法&#xff0c;对UPPAAL使用不太熟悉&#xff0c;网上能找到的教程很少&#xff0c;摸索了很久终于成功实现一个小例子&#xff0c;所以记录一下详细教程。 这里用到的例子参考【UPPAAL学习笔记】1&#xff1a;基本使用示例…

CyberScheduler调度引擎

CyberScheduler 架构设计 1. 多租户架构&#xff0c;支持 SaaS 化部署和私有化部署 2. 多源异构数据&#xff08;多种集群、数据库&#xff09;、多计算引擎、多类型任务的统一编排调度 3. 灵活资源管理能力&#xff0c;支持不同类型任务的资源管理和资源隔离&#xff0c;优…

docker 挂载运行镜像

文章目录 前言docker 挂载运行镜像1. 作用2. 命令3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#xff0c;那欢…

1077: 平衡二叉树的判定

解法&#xff1a; 平衡二叉树是一种特殊的二叉树&#xff0c;它满足以下两个条件&#xff1a; 左子树和右子树的高度差不超过1&#xff08;即&#xff0c;左右子树高度差的绝对值不超过1&#xff09;。左子树和右子树都是平衡二叉树。 后序遍历过程中每次判断左右子树高度差…

收入极高的副业兼职,单价179元的养生爆款卖出3000份

做养生赛道切忌不要只靠接广告变现&#xff0c;换个思路以产品为核心&#xff0c;走低粉高变现才是变现效率最高的。 周周近财&#xff1a;让网络小白少花冤枉钱&#xff0c;赚取第一桶金 今天&#xff0c;我们要分析的这位养生博主&#xff0c;仅凭一款售价为179元的温通膏&a…

STM32 MAP文件结合固件文件分析

文章目录 加载域的结束地址并不是固件的结束地址&#xff1f;ROM中执行域的描述RAM中执行域的描述问题分析 中断向量表在固件中的存储位置代码段在固件中的位置只读数据Regin$$Table RW Data段其中的内部机理 总结 MAP 文件分析可以参考之前的文章 程序代码在未运行时在存储器…

漫谈企业信息化安全 - 勒索软件攻击

一、引言 首先&#xff0c;网络攻击是一个非常广泛的话题&#xff0c;网络攻击从一般分类上包含了恶意软件攻击、钓鱼攻击、拒绝服务攻击&#xff08;DoS/DDoS&#xff09;、中间人攻击、SQL注入、跨站脚本、0-Day攻击、供应链攻击、密码攻击等等&#xff0c;勒索软件攻击只是…