用C语言把一棵普通二叉树安排得明明白白

news2025/1/18 14:49:50

1. 树的相关术语

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图: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)棵互不相交的树的集合称为森林;

满二叉树:深度为k且有2^{k}-1个结点的二叉树。在满二叉树中,每层结点都是满的,即每层结点都具有最大结点数。

满二叉树的顺序表示:从二叉树的根开始,按层间从上到下,层内从左到右的顺序进行编号(0,1,2,……,n-1)。

完全二叉树:深度为k,结点数为n的二叉树,如果其结点0~n-1的位置序号分别与等高的满二叉树的结点1~n-1的位置序号一一对应,则为完全二叉树。完全二叉树的特点为:(1)叶子结点只可能出现在最后两层;(2)度数为1的结点个数为0或1。

 2. 二叉树的性质

性质1:在二叉树的第i层上至多有2^{i-1}个结点(i>=1)

性质2:深度为k的二叉树至多有2^{k}-1个结点(k>=1)

性质3:对任意一棵二叉树T,若终端结点数为n_{1},而其度数为2的结点数为n_{2},则                             n_{1} = n_{2}+1

性质4:具有n个结点的完全二叉树的深度为(log_{2}n)+1

性质5:对于具有n个结点的完全二叉树,如果按照从上到下,从左到右的顺序对完全二叉树               中的所有结点从0开始编号,则对于任意序号为i的结点,其双亲结点的序号为                         (i-1)/2,左孩子的序号为2*i+1,右孩子的序号为2*i+2。

证明略,但是上面的性质5在堆中有较为重要价值,在上一篇文章中有证明:全面详解堆-CSDN博客 

1. 二叉树的声明以及要实现的接口

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);
//树的高度
int TreeHeight(BTNode* root);

2. 通过前序遍历数组构建二叉树

在对二叉树进行操作之前,我们需要有一棵二叉树。

根据二叉树的用途不同,在二叉树中插入元素的方式繁多且复杂,在初学阶段不需要太多的了解。

于是,我们可以采用暴力构建二叉树的方式,也就是手动实现结点之间的相互链接关系:

BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}

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

但是,按照这种方式来构建树,每个不同的树都需要我们编写逻辑来实现,十分的麻烦。

由于包含空指针表示的前序遍历序列与二叉树之间存在着一一对应的关系,所以,我们可以采用将前序遍历数组还原为树的方式来构建树(前序最好理解,中序后序调整子树创建顺序即可):

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (*pi >= n || a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	else
	{
		BTNode* root = BuyNode(a[*pi]);
		(*pi)++;
		root->left = BinaryTreeCreate(a, n, pi);
		root->right = BinaryTreeCreate(a, n, pi);
		return root;
	}
}

a为存放前序遍历序列的数组,'#'表示当前结点为空指针;

n表示数组a的元素个数;

pi为表示当前数组下标的变量的指针,初始值为0,每有一个元素加入到树中,(*pi)++。

3. 二叉树销毁

 这里采用的是后序遍历来释放结点,否则需要定义变量来记录左右子树的根结点。

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
		return;
	else
	{
		BinaryTreeDestory(&((*root)->left));
		BinaryTreeDestory(&((*root)->right));
		free(*root);
		*root = NULL;
	}
}

4. 二叉树的结点个数

利用递归即可解决,整棵树的结点数 = 左子树的结点数 + 根结点(1个)+ 右子树的结点数

同理,左右子树也是相同的逻辑。

某棵子树(某结点的左子树或右子树)为空时,返回0。

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

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

5. 二叉树的叶子结点数

与上面的算法相似,但是此处我们只统计叶子结点数。

叶子结点区别于其他结点的特点是,其左右孩子都为空,如果当前结点符合条件我们就返回1,表示当前结点为叶子结点。

假如当前结点不是叶子结点,那么:

以当前结点为根的子树的叶子结点数 = 左子树叶子结点数 + 右子树叶子结点数。

等式中不再包含根结点。

// 二叉树叶子节点个数
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);
}

6. 二叉树第k层的结点数

同样的递归思路,第k层结点数 = 左子树第k-1层结点数 + 右子树第k-1层结点数

但是我们需要一个参数k来判断当前结点位于第几层,以及是否需要返回。

每深入一层,k就减1,当k等于1时返回1,表示已找到一个所求结点。

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

7. 查找值为x的结点

如果当前结点的值为x,则直接返回当前结点。

否则,先到左子树进行寻找,若左子树没有(返回值为NULL)就找右子树。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	BTNode* Find = BinaryTreeFind(root->left, x);
	if (Find == NULL)
	{
		Find = BinaryTreeFind(root->right, x);
	}
	return Find;
}

8.二叉树的递归与非递归遍历

二叉树的递归遍历较为简单,但是非递归算法却十分复杂,需要用到栈来模拟递归进行实现。

在该文章中有详细介绍:栈与递归的实现-CSDN博客

9. 树的高度

树的高度指树中结点的最大层次。

所以树的高度 = 左子树与右子树中较高者的高度 + 1(根结点)。

//树的高度
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

 10. 层序遍历

层序遍历也就是广度优先遍历,实现这个算法需要用到队列的帮助。

由于C语言不支持队列,所以在这里写了一个简易的队列。

其中,Get函数会在获取队头结点的同时将其出队,这样实现对于我们当前算法的实现来说,使用起来更加方便。

当然,下面这段代码只是帮助理解,直接忽略也可,只要你知道我对队列进行了什么操作。

typedef BTNode* QDataType;

// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;
	QNode* _rear;
	int size;
}Queue;

//初始化
void Inite(Queue* q)
{
	q->size = 0;
	q->_front = NULL;
	q->_rear = NULL;
}

//入队
void Push(Queue* q, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->_data = x;
	newnode->_next = NULL;

	if (q->_rear == NULL)
	{
		q->_front = q->_rear = newnode;
	}
	else
	{
		q->_rear->_next = newnode;
		q->_rear = newnode;
	}
	q->size++;
}

//出队
QDataType Get(Queue* q)
{
	if (q->size == 0)
		return NULL;
	QDataType temp = q->_front->_data;
	QNode* del = q->_front;
	if (q->size == 1)
	{
		q->_rear = NULL;
	}
	q->_front = q->_front->_next;
	free(del);
	q->size--;

	return temp;
}

//销毁队列
void DestroyQueue(Queue* q)
{
	QDataType temp;
	while (q->_front)
	{
		temp = Get(q);
	}
}

基本思路就是:

1. 出队得到一个结点并访问。

2. 如果当前结点有孩子,则将其孩子全部入队。

3. 不断重复以上两步,知道队中无结点。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	Inite(&q);
	Push(&q, root);
	BTNode* cur = NULL;
	while (cur = Get(&q))
	{
		printf("%c ", cur->data);
		if(cur->left != NULL)
		Push(&q, cur->left);
		if(cur->right != NULL)
		Push(&q, cur->right);
	}
	DestroyQueue(&q);
}

 11. 判断二叉树是否是完全二叉树

根据完全二叉树的特点,我们总结出以下判断标准:

1. 如果某个结点只有右孩子而没有左孩子,则该树一定不是完全二叉树。

2. 如果某个结点不是满的(缺少左孩子或右孩子),则位置序号在其之后的结点一定既没有      左孩子,又没有右孩子,否则就是非完全二叉树。

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	Inite(&q);
	Push(&q, root);
	BTNode* cur = NULL;
	do
	{
		cur = Get(&q);
		if (cur->left == NULL && cur->right != NULL)
			return 0;
		else if(cur->left != NULL && cur->right != NULL)
		{
			Push(&q, cur->left);
			Push(&q, cur->right);
		}
	} while (cur->right);

	while (q.size != 0)
	{
		cur = Get(&q);
		if (cur->left != NULL || cur->right != NULL)
			return 0;
	}
	return 1;

	DestroyQueue(&q);
}

12. oj练习

12.1 单值二叉树. - 力扣(LeetCode)

第一种思路:保存根结点的值,然后用其与所有结点进行比较。

bool judge(struct TreeNode* root, int com)
{
    if(root == NULL)
    return true;
    if(root->val != com)
    return false;

    return judge(root->left, com) && judge(root->right, com);
}

bool isUnivalTree(struct TreeNode* root) {
    int com = root->val;
    return judge(root, com);
}

为了提高代算法的效率,也可以将com变量改为全局变量。

第二种思路:我们找到题目要求的一个等价条件——除了叶子结点,所有结点的值都与自己孩子的值相等。

bool isUnivalTree(struct TreeNode* root) {
    if(root == NULL)
    return true;
    
    if(root->left && root->left->val != root->val || root->right && root->right->val != root->val)
    return false;

    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

12.2 相同的树. - 力扣(LeetCode)

采用递归的思路,两棵树相同意味着他们:根结点相同,左子树相同,右子树相同。

左右子树相同,包括左右子树都为空的情况。

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p == NULL && q == NULL)
    return true;
    if(p == NULL || q == NULL || p->val != q->val)
    return false;

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

12.3 对称的树. - 力扣(LeetCode)

 

观察示例可以发现,这道题可以采用与上道题如出一辙的思路,只不过我们要比较的两棵树是根结点的左右子树。

同时,比较的逻辑也需要更改,左子树的右孩子应该与右子树的左孩子比较,左子树的左孩子应该与右孩子的右子树比较。

bool judge(struct TreeNode* root1, struct TreeNode* root2)
{
    if(root1 == NULL && root2 == NULL)
    return true;
    if(root1 == NULL || root2 == NULL || root1->val != root2->val)
    return false;

    return judge(root1->left, root2->right) && judge(root1->right, root2->left);
}

bool isSymmetric(struct TreeNode* root) {
    return judge(root->left, root->right);
}

12.4 另一棵树的子树. - 力扣(LeetCode)

这道题可以利用到“相同的树”那道题的函数,我们只需要遍历root,然后将其的子树依次与subRoot进行比较即可。

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p == NULL && q == NULL)
    return true;
    if(p == NULL || q == NULL || p->val != q->val)
    return false;

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL)
    return false;
    if(isSameTree(root, subRoot))
    return true;

    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

12.5 二叉树的前序遍历. - 力扣(LeetCode)

 这道题并不是简单地对二叉树进行前序遍历,而是要求我们返回存储前序遍历序列的数组。

首先需要动态开辟一个存储结果的数组,但问题是开辟多少合适呢?

当然,在这道题中我们可以直接开辟100个空间的数组。

假如我们不知道这么一个条件呢?

可以用动态的顺序表来管理,但鉴于需要我们自己实现,似乎就有点麻烦。

这时,我们就可以用到上面的BinaryTreeSize函数了,先求出树中的结点个数,再进行我们接下来的操作。

与利用前序遍历数组构建二叉树相同,我们这里也需要一个pi来指示数组当前的下标。

下面的这段代码中,直接用returnSize来充当了这个pi。

int TreeSize(struct TreeNode* root)
{
    return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

void pretraversal(struct TreeNode* root, int* arr, int* pi)
{
    if(root == NULL)
    return;

    arr[*pi] = root->val;
    (*pi)++;
    pretraversal(root->left, arr, pi);
    pretraversal(root->right, arr, pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int* arr = (int*)malloc(sizeof(arr) * TreeSize(root));
    *returnSize = 0;
    pretraversal(root, arr, returnSize);
    return arr;
}

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

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

相关文章

linux与windows脚本格式必须转换,linux只有LF

如果windows下的脚本在linux下直接执行,则会造成无穷的错误。 在文本处理中, CR, LF, CR/LF是不同操作系统上使用的换行符. Dos和windows: 采用回车换行CR/LF表示下一行. UNIX/Linux : 采用换行符LF表示下一行. MAC OS : 采用回车…

Git提交和配置命令

一、提交代码到仓库 在软件开发中,版本控制是一个至关重要的环节。而Git作为目前最流行的版本控制系统之一,为我们提供了便捷高效的代码管理和协作工具。在日常开发中,我们经常需要将本地代码提交到远程仓库,以便于团队协作和版本…

c++笔记3

优先队列 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。优先队列是一种按照优先级决定出队顺序的数据结构,优先队列中的每个元素被赋予级别,队首元素的优先级最高。 例如:4入队&#xff0c…

142.栈和队列:用栈实现队列(力扣)

题目描述 代码解决 class MyQueue { public:stack<int> stIn; // 输入栈&#xff0c;用于push操作stack<int> stOut; // 输出栈&#xff0c;用于pop和peek操作MyQueue() {}void push(int x) {stIn.push(x); // 将元素压入输入栈}int pop() {// 如果输出栈为空&…

如何*永久*禁用edge打开PDF文件?

要永久禁用Microsoft Edge打开PDF文件&#xff0c;您可以按照以下步骤进行操作&#xff1a; 打开文件资源管理器并找到任意一个PDF文件。 右键单击该文件并选择“属性”。 在“属性”对话框中&#xff0c;单击“更改”按钮旁边的“打开方式”。 在“打开方式”对话框中&…

键盘盲打是练出来的

键盘盲打是练出来的&#xff0c;那该如何练习呢&#xff1f;很简单&#xff0c;看着屏幕提示跟着练。屏幕上哪里有提示呢&#xff1f;请看我的截屏&#xff1a; 截屏下方有8个带字母的方块按钮&#xff0c;这个就是提示&#xff0c;也就是我们常说的8个基准键位&#xff0c;我…

vscode打造舒适的python开发环境

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 背景 突然想写一些python小脚本&#xff0c;提升我的mac使用体验和日常效率提升…

新媒体运营十大能力,让品牌闻达天下!

" 现在新媒体蓬勃发展&#xff0c;很多品牌都有新媒体运营这个岗位。新媒体运营好的话&#xff0c;可以提高公司品牌曝光、影响力。那新媒体运营具备什么能力&#xff0c;才能让品牌知名度如虎添翼呢&#xff1f;" 信息收集能力 在移动互联网时代&#xff0c;信息的…

vscode插件-03 PHP

PHP Intelephense 如果php在远程计算机上&#xff0c;要把插件安装在远程&#xff0c;而不是本地。 这个插件&#xff0c;要求php版本大于7&#xff0c;且设置环境变量&#xff08;好像不一定要设置&#xff09;。 设置里面搜索php.executablePath&#xff0c;打开setting.js…

音视频安卓主板记录仪手持终端定制开发_基于MT6762平台解决方案

音视频安卓主板采用了基于MT6762高性能处理器芯片的设计&#xff0c;其中包括4个主频高达2.0GHz的Cortex-A53核心和4个主频1.5GHz的Cortex-A53高效聚焦核心&#xff0c;可提供无比流畅的体验。搭载Android 12操作系统&#xff0c;系统版本进行了全新的优化&#xff0c;进一步确…

TCP Spurious Retransmission

TCP虚假重传是网络中可能存在丢包问题的一个指标&#xff0c;但并不意味着一定存在丢包。虚假重传指的是当TCP发送方错误地认为原始数据包已丢失时&#xff0c;会不必要地进行重传&#xff0c;而实际上原始数据包已成功到达目的地。 虚假重传可能由多种因素引起&#xff0c;例…

编译报错:undefined reference to `std::__cxx11::basic_string 或者@GLIBCXX_3.4.21

出现错误起因&#xff1a; 编译环境&#xff0c;gcc从4.8.5版本升级到9.3.0版本。项目引用客户的SDK库&#xff08;库是gcc7.2.0版本编译的&#xff09;。用gcc7.2.0版本编译正常。 错误信息&#xff1a; 排查错误&#xff1a; 1.网上搜索了undefined reference to std::__cx…

网络架构与IP技术:4K/IP演播室制作的关键支撑

随着科技的不断发展&#xff0c;广播电视行业也在不断迭代更新&#xff0c;其中4K/IP演播室技术的应用成了一个引人注目的焦点。4K超高清技术和IP网络技术的结合&#xff0c;不仅提升了节目制作的画质和效果&#xff0c;还为节目制作带来了更高的效率和灵活性。那么4K超高清技术…

基于windows通过kind部署轻量级便携式k8s集群

感谢老师的视频教程&#xff1a; 基于windows通过kind部署轻量级便携式k8s集群 wsl windows下的linux wsl --set-default-version 2 wsl --help wsl --list --online wsl --install -d Ubuntu wsl -l -v &#xff08;看看版本是不是2&#xff0c;否则docker那边识别不到&…

【时间复杂度和空间复杂度之间的故事】

【时间复杂度和空间复杂度之间的故事】 一.前言 二.时间复杂度定义时间复杂度的计算规则习题 三.空间复杂度定义计算方法习题空间复杂度 O(1)空间复杂度 O(n) 本文主要讲解关于时间复杂度与空间复杂度 &#x1f600;&#x1f603;&#x1f601;&#x1f601;&#x1f607;&…

鸿蒙ArkUI-X跨语言调用说明:平台差异化【Android、ios动态化】

动态化介绍 简介 ArkUI-X支持动态化&#xff0c;使用者可以根据自己需要动态发布跨平台内容&#xff0c;从而使跨平台部分和宿主应用进行解耦。动态化能力支持如下两个典型场景&#xff1a; 框架动态化&#xff1a;指ArkUI跨平台开发框架本身可以做到动态下发&#xff0c;即…

webpack优化构建体积示例-压缩图片:

不同的图片格式有不同的特点和用途&#xff0c;它们也需要不同的压缩算法和技术&#xff0c;也为了保证能在各个浏览器环境下能正常加载显示&#xff0c;所以需要用到多个插件 在使用imagemin-webpack-plugin来配置图片压缩时&#xff0c;你需要确保已经安装了该插件以及它可能…

GBDT调参--贝叶斯调参

随机抽特征和随机抽样本 n_estimators 是控制森林中树木的数量&#xff0c;即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的&#xff0c;n_estimators越 大&#xff0c;模型的效果往往越好。但是相应的&#xff0c;任何模型都有决策边 n_estimators达到一定的程…

WPS二次开发系列:第三方应用快速接入WPS打印功能

作者持续关注 WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 目录 应用场景 Demo效果演示 SDK申请及初始化 SD…

Java面试八股之HashSet和TreeSet有什么区别

Java中HashSet和TreeSet有什么区别 1. 底层数据结构 HashSet: 基于哈希表&#xff08;实际上是 HashMap 的内部实现&#xff09;实现。每个元素通过其 hashCode() 方法计算出哈希码&#xff0c;并通过哈希码确定其在哈希表中的位置。这种结构使得 HashSet 在插入、删除和查找…