二叉树链式结构-c语言实现

news2025/1/15 13:05:46

文章目录

  • 二叉树链式结构实现
    • 1. 链式二叉树结构
    • 2. 二叉树的遍历
      • 2.1 前序遍历
      • 2.2 中序遍历
      • 2.3 后序遍历
      • 2.4 层序遍历
    • 3. 常见功能
      • 3.1 二叉树结点个数
      • 3.2 二叉树叶子结点个数
      • 3.3 第K层结点的个数
      • 3.4 二叉树的深度
      • 3.5 判断是不是树是不是完全二叉树
      • 3.6 在二叉树中查找值为x的结点
      • 3.7 拿到每一层的数据
    • 4. 二叉树的创建和销毁
      • 4.1 二叉树的创建
      • 4.2 二叉树的销毁

二叉树链式结构实现

前面我们已经对堆进行学习,堆就是一个顺序结构的二叉树,把数组看成二叉树,下面一起学习一下链式结构的二叉树,这里是用递归实现功能

1. 链式二叉树结构

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

2. 二叉树的遍历

首先,我要说明一下递归实现;递归实现一般分为三个步骤(递归三要素):初步明确函数功能,限制条件,找到实现此功能的等式

单项递归和二叉树递归(多项递归)的区别?

单项递归并没有分支,多项递归是有分支的,这就意味着二叉树更看中整体,单项递归更看重分治。

单项递归和二叉树递归的共同点?

都是分治思想,子问题再分子问题再分子问题的思想

2.1 前序遍历

思想:把树看成整体:根、左子树、右子树,先遍历根再走左子树再走右子树

void BinaryTreePrevOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

2.2 中序遍历

思想:把树看成整体:根、左子树、右子树,先遍历左子树再走根再走右子树

void BinaryTreeInOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

2.3 后序遍历

void BinaryTreePostOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

2.4 层序遍历

思想:出上一层的同时带着下一层入队列

//链式队列的结构
typedef struct BinaryTreeNode* QueueDataType;
typedef struct QueueNode
{
	QueueDataType data;
	struct QueueNode* next;
}QueueNode;
//因为要直接得到队头的元素和队尾的元素
typedef struct QueueLinkList
{
	QueueNode* head; //队头
	QueueNode* tail; //队尾
	int size; //元素总数
}QLL;
//队列初始化
void QLLInit(QLL* queue)
{
	assert(queue);

	queue->head = NULL;
	queue->tail = NULL;
	queue->size = 0;
}
//进队
void QLLPush(QLL* queue, QueueDataType x)
{
	assert(queue);

	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("QLLPush:malloc is failed!\n");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;

	if (queue->head == NULL)
	{
		queue->head = queue->tail = newnode;
	}
	else
	{
		queue->tail->next = newnode;
		queue->tail = newnode;
	}

	queue->size++;
}
//出队
void QLLPop(QLL* queue)
{
	assert(queue != NULL);
	assert(QLLEmpty(queue) != true);
		
	//只有一个结点时
	if (queue->head->next == NULL)
	{
		free(queue->head); //free的是这个结点的空间,并不是指针
		queue->head = queue->tail = NULL;
	}
	else
	{
		//通常情况
		QueueNode* del = queue->head;
		queue->head = queue->head->next;
		free(del);
		//无需置空
	}

	queue->size--;
}
//拿取队头数据
QueueDataType QLLFront(QLL* queue)
{
	assert(queue != NULL);
	assert(QLLEmpty(queue) != true);

	return queue->head->data;
}
//判空
bool QLLEmpty(QLL* queue)
{
	assert(queue);

	//return queue->size == 0;
	return queue->head == NULL && queue->tail == NULL;
}
//销毁
void QLLDestroy(QLL* queue)
{
	assert(queue);

	QueueNode* cur = queue->head;
	while (cur != NULL)
	{
		QueueNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}

	queue->head = queue->tail = NULL;
	queue->size = 0;
}

//层序遍历实现
void BinaryTreeLevelOrder(BTNode* root)
{
	QLL queue;
	QLLInit(&queue);
    //根先入队列
	if (root != NULL) {
		QLLPush(&queue, root);
	}
	//队列不为NULL的时候进行出队头带下层数据入队操作
	while (QLLEmpty(&queue) != true)
	{
        //出队头操作
		BTNode* front = QLLFront(&queue);
		printf("%c ", front->data);
		QLLPop(&queue);
        //带下一层进队
		if (front->left != NULL)
		{
			QLLPush(&queue, front->left);
		}
		if (front->right != NULL)
		{
			QLLPush(&queue, front->right);
		}
	}
	printf("\n");
	QLLDestroy(&queue);
}

说明:为什么递归不画图来解决呢?

多项递归画图是很难理解的,因为他不是我们逻辑上想的,就拿前序遍历来说,首先是根,再遍历左子树再遍历右子树这样循环来走,但是在实际递归中逻辑是左子树走到底,直到NULL时返回访问右子树,如果说是画图是理解不了二叉树递归的,这里我们就要扣住树的结构:根、左子树、右子树,这样是我们逻辑上的实现,并不是实际中的过程实现,这里我需要说明一下,画图是为了在原有基础上来进行纠错,这里纠正的错也是和根的限制条件有关,这里我还会出几期二叉树的相关练习,到时候希望大佬们看看就能理解了二叉树递归!

3. 常见功能

3.1 二叉树结点个数

递归三要素解决问题

首先二叉树想到整体结构:根、左子树、右子树

函数功能:求二叉树结点的个数

限制条件:根为NULL的时候就不是一个结点(起初的结束条件:针对根来说)

等式:计算左子树中的结点个数和右子树的结点个数,最后加上根这个结点

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL) {
		return 0;
	}

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

代码分析

上述列出来的思路就是实现思路,这里注意的是树的整体结构,我一直扣的就是整体结构,等式中写的也是整体结构的逻辑;这里来看代码很简单就是根和左子树和右子树结构

为什么不写子结构:根、左孩子、右孩子?

原因就是如果写成子结构的话就不是整体,而是把整体分为多个相同的结构来讨论,这里就不是整体观念就很容易陷进去,为什么二叉树递归难,难就难在你没扣住整体,而是扣住的是子结构,如果扣住子结构那就很容易陷进去,只要陷进去了就不是我们自己想的逻辑,而是实际递归的过程逻辑,实际递归的过程逻辑和我们想的逻辑有很大的区别

为什么首先要有个前提:树的结构:根、左子树、右子树?

原因很简单,我们考虑整体就少不了这个结构,这是我们首先要考虑的问题;另外也是因为这里三要素中的实现是离不开这个整体结构的,如果离开了整体结构就又被陷进去了

限制条件是怎么来限制的?

首先我们考虑的结构就是树的整体结构:根、左子树、右子树,我们不可能是来对左右子树来限制吧,因为左右子树中有很多结点,从整体上来说是考虑不到的,另外你只要考虑左右子树中的结点恭喜你,你又被陷进去出不来了哈哈,所以这里的限制条件是针对根来讲的:也就是根的起初的结束条件以及和题意的联系

3.2 二叉树叶子结点个数

递归三要素解决问题

前提:树的结构:根、左子树、右子树

函数功能:求二叉树叶子节点的个数

限制条件:root=NULL的时候就不是叶子结点,根的左右孩子是空的时候但根不是空的时候就是叶子结点

等式:在左右子树中找叶子节点,左右子树中的叶子结点之和也就是树的叶子结点

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

代码分析

3.3 第K层结点的个数

递归三要素解决问题

前提:树的结构:根、左子树、右子树

函数功能:求第K层结点的个数

限制条件:root=NULL(起初的结束条件),根所处的是第一层所以K=1的时候返回1(题意结束条件)

等式:在左右子树的第k-1层中的结点个数(因为第一层是根,所以第K-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);
}

代码分析

3.4 二叉树的深度

递归三要素解决问题

前提:树的结构:根、左子树、右子树

函数功能:求树的深度

限制条件:根为NULL时结束

等式:树的根是第一层,那么我们只用计算出左子树和右子树的哪个深度大就再加上1(根的深度)就是树的深度

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int LeftTreeDepth = BinaryTreeDepth(root->left);
	int RightTreeDepth = BinaryTreeDepth(root->right);

	if (LeftTreeDepth > RightTreeDepth) {
		return LeftTreeDepth + 1;
	}
	else {
		return RightTreeDepth + 1;
	}
}

代码分析

没进行优化的代码:

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right)) {
		return BinaryTreeDepth(root->left) + 1;
	}
	else {
		return BinaryTreeDepth(root->right) + 1;
	}
}

这个代码也是对的,但是时间复杂就要多了1倍,因为判断中用到递归了,找到了并没有记录深度,也就进入判断中的递归,再此递归一次,这样时间复杂度就增了1倍。

3.5 判断是不是树是不是完全二叉树

思路:

完全二叉树的性质:前K-1层是满二叉树,最后一层是从左到右是连续的

思路:用层序遍历来解决,出上一层的同时带下一层的数据,知道遇到NULL的时候就要进行判断队列中是不是还有不为NULL的值,如果有就不是完全二叉树,没有则是

bool BinaryTreeComplete(BTNode* root)
{
	QLL queue;
	QLLInit(&queue);
	if (root != NULL)
	{
		QLLPush(&queue, root);
	}

	//拿到每层的
	while (QLLEmpty(&queue) != true)
	{
		BTNode* front = QLLFront(&queue);
		QLLPop(&queue);

		//当这层遇到NULL的时候进行判断
		if (front == NULL)
		{
			break;
		}
		else
		{
			QLLPush(&queue, front->left);
			QLLPush(&queue, front->right);
		}
	}
    
	//出到NULL进行检查
	//如果后面有非NULL就不是完全二叉树
	while (QLLEmpty(&queue) != true)
	{
		BTNode* front = QLLFront(&queue);
		QLLPop(&queue);
		//不为NULL说明最后一层不是连续的
		if (front != NULL)
		{
			QLLDestroy(&queue);
			return false;
		}
	}
	
	QLLDestroy(&queue);
	return true;
}

3.6 在二叉树中查找值为x的结点

递归三要素解决问题

前提:树的结构:根、左子树、右子树

函数功能: 在二叉树中查找值为x的结点

限制条件:root=NULL时结束,root->val=x时找到了就结束

等式:在根里面找,在左子树和右子树里面找

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}

	if (root->data == x)
	{
		return root;
	}

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

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

	return NULL;
}

代码分析

错误列举


BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}

	if (root->data == x)
	{
		return root;
    }

	return BinaryTreeFind(root->left, x) || BinaryTreeFind(root->right, x);
}

为什么逻辑上是对的,但是是错的?

最后的return的意思翻译过来就是在左子树里面找,找到了就返回,不进右子树,如果左子树中没找到就进右子树,找到了返回,如果都没找到就直接返回NULL;逻辑上是对的,但是呢,这里我们返回的是指针,指针的的关系不能用逻辑关系来表达,所以是错的

3.7 拿到每一层的数据

思路

也是围绕层序遍历来写:记录每一层的结点树来出队列就行了,这里也是层序遍历的知识是主要的,就不再进行讨论了


void EveryLayer(BTNode* root)
{
	QLL queue;
	int levelCount = 0;
	QLLInit(&queue);
	if (root != NULL) {
		QLLPush(&queue, root);
		//第一层就是一个数据
		levelCount = 1;
	}

	while (QLLEmpty(&queue) != true)
	{
		while (levelCount--)
		{
			BTNode* front = QLLFront(&queue);
			printf("%c ", front->data);
			QLLPop(&queue);
			if (front->left != NULL)
			{
				QLLPush(&queue, front->left);
			}
			if (front->right != NULL)
			{
				QLLPush(&queue, front->right);
			}
		}
		//下一层的个数
		levelCount = QLLSize(&queue);
		printf("\n");
	}
	
	QLLDestroy(&queue);
}

结合上述题就很容易看出实际上我们写代码来划分的话也是树的整体结构:根、左子树、右子树,时刻把握着树的整体结构,这个结构充分体现在等式中,同时也影响到了限制条件,限制条件中只用管根的结束条件以及形成条件,其他的不用管,这就是在代码中实现的过程,这里我就不在画图,觉得这个过程不能实现我说的对应的功能的时候你就画图去尝试理解一下,谢谢

4. 二叉树的创建和销毁

4.1 二叉树的创建

思路:

这里用到前序遍历来创建,也就是数组的元素按个放进根的数据域中

限制条件:就是当元素为#,代表的是二叉树中的NULL

BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	//形成条件
	if (a[(*pi)] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("BinaryTreeCreate: malloc is failed!\n");
		exit(-1);
	}
	//根
	root->data = a[(*pi)++];

	//左右子树
	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);

	return root;
}
void Test2()
{
	char str[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };

	int i = 0;
	BTNode* root = BinaryTreeCreate(str, &i);
}

4.2 二叉树的销毁

//二级指针
void BinaryTreeDestory(BTNode** root)
{
	if ((*root) == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free((*root));
	*root = NULL;
}
void FirstPointBinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	FirstPointBinaryTreeDestory(root->left);
	FirstPointBinaryTreeDestory(root->right);
	free(root);
	//root = NULL;(没必要)
}//需要说明的是用这个函数调用后要对root置空

为什么采用后序遍历来销毁二叉树?

因为后序遍历最开始走到的就是左子树的最后一层,然后逐次向上销毁,并不会影响每个结点的指向;如果采用前序遍历呢?采用前序遍历上来就是free掉了根结点,就找到不到这个根结点的左右孩子了

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

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

相关文章

VM系列振弦采集读数模块的测量模式

VM系列振弦采集读数模块的测量模式 模块有连续测量和单次测量两种测量模式, 通过向测量模式寄存器 WKMOD.[0]写入 1 使模块工作于连续测量工作模式, 写入 0 使模块工作于单次测量工作模式。 WKMOD.[15]用来设置是否在模块“ 忙” 时禁用数字接口&#xf…

京东低代码平台:水滴表单联动可视化配置的实现与思考

TL;DR drip-form在0.9.0的alpha版支持了可视化配置联动的功能(仍在测试中)drip-form通过协议到代码的转换,尽可能降低常见联动配置的开发成本探讨:JSON diff动态生成常见联动和校验drip form的后续更新:v0.9.0是v0最后…

计算机组成原理-总线详细讲解(持续更新中)

总线概念与分类 定义 总线是一组能为多个部件分时共享的公共信息传送线路 共享是指总线上可以挂接多个部件,各个部件之间互相交换的信息都可以通过这组线路分时共享。 分时是指同一时刻只允许有一个部件向总线发送信息,如果系统中有多个部件&#xf…

React源码分析1-jsx转换及React.createElement

jsx 的转换 我们从 react 应用的入口开始对源码进行分析&#xff0c;创建一个简单的 hello, world 应用&#xff1a; import React, { Component } from react; import ReactDOM from react-dom; export default class App extends Component {render() {return <div>h…

科普下抖音的规则,为什么别人的内容很容易火,而我的很难?

今天给大家科普下抖音的规则&#xff0c;为什么别人的内容很容易火&#xff0c;而我的很难&#xff1f; 上一篇给大家讲了现在做抖音还来得及么&#xff1f;肯定的回答&#xff0c;一直都来得及。 既然来得及&#xff0c;那么我们怎么才能做好抖音呢&#xff1f; 在我看来&a…

Rust 基础(四)

十、泛型、Traits和生命周期 每种编程语言都有有效处理概念重复的工具。在Rust中&#xff0c;一个这样的工具就是泛型:具体类型或其他属性的抽象替身。我们可以表达泛型的行为&#xff0c;或者它们如何与其他泛型相关联&#xff0c;而不知道在编译和运行代码时它们的位置会是什…

[C++]C++入门--引用

​ &#x1f941;作者&#xff1a; 华丞臧 &#x1f4d5;​​​​专栏&#xff1a;【C】 博主Gitee 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎>在评论区指出。 推荐一款刷题网站 &#x1f449;LeetCode…

IPv6进阶:IPv6 过渡技术之IPv6 over IPv4 手动隧道

实验拓扑 R1-R3-R2之间的网络为IPv4环境&#xff1b;PC1及PC2处于IPv6孤岛。 实验需求 R1及R2为IPv6/IPv4双栈设备&#xff1b;在R1及R2上部署IPv6 over IPv4手工隧道使得PC1及PC2能够互相访问。 配置及实现 R3的配置如下 [R3] interface GigabitEthernet0/0/0 [R3-Gigabi…

【Java实战】工作中如何规范控制语句

目录 一、前言 二、控制语句规范 1.【强制】使用switch注意事项 2.【强制】当 switch 括号内的变量类型为 String 并且此变量为外部参数时&#xff0c;必须先进行 null 判断。 3.【强制】在 if / else / for / while / do 语句中必须使用大括号。 4.【强制】三目运算符高…

[附源码]计算机毕业设计springboot本地助农产品销售系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【C++】string详细介绍及模拟实现string类

【C】string详细介绍及模拟实现string类 文章目录【C】string详细介绍及模拟实现string类1.什么是string2.string常用接口介绍2.1string类对象的常见构造2.2string类对象的容量操作2.3string类对象的访问及遍历操作2.4string类对象的修改操作2.5string类非成员函数3.string类的…

移动跨平台开发跨家选型参考建议

从 iPhone 诞生至今&#xff0c;智能手机风靡全球已将近20年&#xff0c;智能手机操作系统 iOS 和 Android 也成为当仁不让的顶流般的存在&#xff0c;而作为其背后的灵魂&#xff0c;移动应用也随着技术的发展已经越来越丰富。如果从技术层面来讲&#xff0c;移动 App 也从最开…

Cloud-computing 实验镜像 chinaskills_cloud_iaas.iso chinaskills_cloud_paas.iso

Cloud-computing 实验镜像 最近因新项目再次进行云计算环境的搭建&#xff0c; 找这两个镜像&#xff08; 找chinaskills_cloud_paas.iso chinaskills_cloud_iaas.iso&#xff09;颇为费劲&#xff0c;用尽九牛二虎之力总算找到了&#xff0c;该大侠还分享了诸多系统镜像和完…

高衍射效率的偏振无关透射光栅的分析与设计

摘要 光栅&#xff0c;特别是具有与波长相当的特征尺寸的光栅&#xff0c;具有偏振相关的光学特性。 这使得设计的具有高衍射效率的光栅难以用于任意偏振。 根据文献[T. Clausnitzer, et al&#xff0c;Proc. SPIE 5252,174-182&#xff08;2003&#xff09;]中报道的概念&…

VMware-AD域控管理

目录 新建AD用户[ 以张三[zhangsan]、李四[lisi]为例 ] 2.用户信息-属性-管理-编辑&#xff1a; 3.将张三设置为AD域控管理员&#xff0c; 在wqd.com域下新建几个部门&#xff08;IT、HR、PRD&#xff09; 对从主机&#xff08;win7&#xff09;进行AD接管 修改win7计算机名称&…

MATLB|电动车智能充电模式及电力高峰需求预测

目录 0 写在前面 1 电动车 1.1 电动车&#xff08;EV&#xff09; 1.2 电动汽车充电 1.3 智能充电和车联网&#xff08;V2G&#xff09; 1.4 V2G 应用 1.5 可再生能源可用性 1.6 基于价格的收费 2 电动车智能充电 2.1 智能充电 2.2 实时电价 2.3 智能充电模式——算…

国产CPU对比

关于国产CPU&#xff1a;龙芯、飞腾、鲲鹏、海光、申威、兆芯 CPU 是计算机系统的核心和大脑 n CPU&#xff0c;即中央处理器是计算机的运算和控制核心&#xff0c;其功能主要是解释计算机指令以及处理计算机软件中的数据. CISC实际上是以增加处理器本身复杂度作为代价&#xf…

论文翻译:多延迟块频域自适应滤波器

Multidelay Block Frequency Domain Adaptive Filter 作者&#xff1a; JIA-SIEN SOO 和 KHEE K. PANG 文章目录Multidelay Block Frequency Domain Adaptive Filter1.介绍2.MDF自适应滤波器3.仿真结果和性能分析4.计算的复杂性5.结论摘要-本文提出了一种灵活的多延迟块频域自…

农村城镇面板数据集:地级市人均消费与支出2012-2019各省农村数据2013-2019

1、2002-2019年地级市人均消费与支出数据 1、数据来源&#xff1a;wind 2、时间跨度&#xff1a;2012-2019 3、区域范围&#xff1a;287个地级市 4、指标说明&#xff1a; 包含以下四个指标&#xff1a;人均可支配收入&#xff08;农村&#xff09;、人均可支配收入&#…

在el-table表头上引入组件不能实时传参bug

文章目录场景还原解决方法出现原因场景还原 产品要求&#xff1a;点击表格的表头&#xff0c;能触发一个下拉的列表&#xff0c;列表能携带表格的筛选条件&#xff0c;获取相应的数据 写了一个demo&#xff0c;来还原一下bug出现的场景&#xff1a; <div id"demo&qu…