这个数据机构是二叉树

news2024/11/23 6:31:13

文章目录

  • 前言
  • 一、二叉树的链式存储
  • 二、二叉树链式结构的实现
    • 二叉树的结构设计
    • 手动构建二叉树
    • 二叉树的前序遍历
    • 二叉树的中序遍历
    • 二叉树的后序遍历
    • 二叉树的层序遍历
    • 计算二叉树大小
    • 计算叶子节点个数
    • 计算二叉树高度
    • 计算第K层的节点个数
    • 查找某个值对应的节点
    • 二叉树的销毁
  • 三、完整代码
    • test.c


前言

今天五一假期,学习了数据结构的二叉树相关内容,所以写这一篇博客来记录自己的学习内容,主要就是重点知识的梳理。


一、二叉树的链式存储

概念:二叉树的链式存储结构是指用 链表 来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域左右指针域,左右指针分别用来给出该结点左孩子右孩子所在的链结点的存储地址 。

结构示意图:
在这里插入图片描述
逻辑结构:
在这里插入图片描述
二叉链和三叉链的结构设计:

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
    struct BinTreeNode* left; // 指向当前节点左孩子
    struct BinTreeNode* right; // 指向当前节点右孩子
    BTDataType data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
    struct BinTreeNode* parent; // 指向当前节点的双亲
    struct BinTreeNode* left; // 指向当前节点左孩子
    struct BinTreeNode* right; // 指向当前节点右孩子
    BTDataType data; // 当前节点值域
}

二、二叉树链式结构的实现

在学习二叉树的基本操作前,需要创建一棵二叉树,然后才能学习其相关的基本操作。为了降低学习成本,我们直接手动创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

而对于增删查改等功能,对于我们当前学习的二叉树并没有价值,所以我们的学习主要围绕遍历二叉树,或者计算节点高度来进行。

二叉树的结构设计

此处我们使用的结构为 二叉链 的结构

typedef int BTDataType;
//二叉链
struct BinaryTreeNode
{
	struct BinaryTreeNode* left; //指向当前节点的左孩子
	struct BinaryTreeNode* right; //指向当前节点的右孩子
	BTDataType data; //当前节点的值
}

手动构建二叉树

本篇博客 我构建了一颗简单的二叉树,如下图所示:

在这里插入图片描述

//创建节点
BTNode* BuyBTNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	
	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;
	
	return newnode;
}

void Test1()
{
	// 构建二叉树
	BTNode* n1 = BuyBTNode(1);
	BTNode* n2 = BuyBTNode(2);
	BTNode* n3 = BuyBTNode(3);
	BTNode* n4 = BuyBTNode(4);
	BTNode* n5 = BuyBTNode(5);
	BTNode* n6 = BuyBTNode(6);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
}

二叉树的前序遍历

前序遍历(Preorder Traversal 亦称先序遍历) —— 访问根结点的操作发生在遍历其左右子树之前。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根的左子树根的右子树。所以 前序遍历 又可以被称为 NLR

前序遍历图解:

在这里插入图片描述
前序遍历的访问次序是 先访问根节点,再访问左子树,再访问右子树。

那么当前树的遍历结果为1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL

前序遍历的代码实现:

void PreOrder(BTNode* root)
{
	if(root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ",root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

在这里插入图片描述

二叉树的中序遍历

中序遍历(Inorder Traversal) —— 访问根结点的操作发生在遍历其左右子树之中(间)。

中序遍历 又可以被称为 LNR

中序遍历的访问次序是 先访问左子树,再访问根节点,再访问右子树。

那么当前树的遍历结果为:NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL

中序遍历的代码实现:

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ",root->data);
	InOrder(root->right);
}

二叉树的后序遍历

后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

后序遍历 又可以被称为 LRN

后序遍历的访问次序是 先访问左子树,再访问右子树,再访问根节点。

那么当前树的遍历结果为:NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL 6 4 1

后序遍历的代码实现:

void PostOrder(BTNode* root)
{
	if(root==NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ",root->data);
}

二叉树的层序遍历

序遍历就是从第一层开始从左向右逐个遍历。

那么当前树的遍历结果为:1 2 4 3 5 6

在这里插入图片描述

计算二叉树大小

计算二叉树的大小,其实就是计算二叉树中 节点的个数

思路:
1.给一个size,遍历二叉树,分别遍历左右子树,遍历过程中遍历到非空节点size++,遍历到空,则返回0。
2.二叉树的大小 = 根节点 + 左子树节点+右子树节点,将其分成多个子问题,递归求解。

思路1的实现:
size是局部变量的时候,显然是不可以的!我们遍历的过程还是递归,当每次递归的时候,都会建立新的函数栈帧,递归当中的size不是同一个size,每次递归时的size一开始都是0,无法成功计算出大小。

所以这时候,我们需要设置一个全局变量的size,但是size不会被销毁,所以每一次计算二叉树大小的时候,就要重新初始化size的大小为0,避免计算错误。

思路一的代码实现:

int size=0;

int TreeSize1(BTNode* root)
{
	if (root==NULL)
	{
		return 0;
	}
	size++;
	TreeSize1(root->left);
	TreeSize1(root->right);
	return size;
}

思路1每次调用之前置0都很麻烦,代码也不是那么简洁,那么 思路2 能否改良这些?

显然思路2正能解决这两个缺陷

如果访问节点为空,则返回0;如果访问节点不为空,则 +1返回,就这样分别递归左子树和右子树,最后的返回结果就是节点个数。

思路二的代码实现:

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

计算叶子节点个数

叶子节点,就是没有左右孩子的节点。

如果节点为空,那么不是叶子结点,返回0;如果左右孩子都为空,那么就是叶子结点,返回1。

最后分别递归计算左右子树的叶子结点。

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

	if(root->left==NULL && root->right == NULL)
		return 1;

	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

计算二叉树高度

二叉树的高度就是二叉树左右子树中最高的一边加上当前 1(根节点)。

我们可以分别递归到左右子树的最后一层,如果当前节点为空,返回0;不为空则比较当前左右子树的高度,+1返回高度较高的一边。

在递归过程中使用 leftHeight 和 rightHeight 分别记录当前高度,防止重复递归调用。

计算二叉树高度代码的实现:

int TreeHeight(BTNode* root)
{
	if(root==NULL)
	{
		return 0;
	}
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

计算第K层的节点个数

假设 k 足够大:

对于第一层,需要计算的是 第 k 层的节点个数;
对于第二层,需要计算的是 第 k - 1 层的节点个数;
… … … … … …
对于第 k 层,计算的就是 第 1 层( 当前层数 ) 的节点个数。

那么有了这个思路,我们就可以解决当前这个接口:

如果 当前节点为空,则返回0;

如果 节点不为空,且 k == 1,那么说明已经到达第 k 层,返回1;

如果 节点不为空,且 k > 1,那么说明需要继续递归,分别递归左右子树的 第 k - 1 层。

计算第K层的节点个数代码实现:

int TreeLevalSize(BTNode* root, int k)
{
	if (root == NULL) return 0;

	if (k == 1)	return 1;

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

查找某个值对应的节点

如果查找节点为空,则返回空;如果找到了则返回当前节点。

否则就分别递归查找左右子树,在查找过程中可以用变量来保存查找的值,如果查找返回的结果非空,那么说明找到了,就返回结果;如果左右子树都没有找到,则返回空。

BTNode* TreeFind(BTNode* root, int x)
{
	// 如果 root 为空,则返回空
	if (root == NULL)
	{
		return NULL;
	}

	// 找到了
	if (root->data == x)
	{
		return root;
	}

	// 递归左右子树
	BTNode* ret1 = TreeFind(root->left, x);
	// 如果左子树非空,则找到了,返回
	if (ret1 != NULL)
		return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2 != NULL)
		return ret2;

	// 到这里没找到,就得返回NULL
	return NULL;
}

二叉树的销毁

销毁二叉树,我们使用 后序遍历 的思想销毁。

遍历到最后一层,先销毁左子树,再销毁右子树,如果遇到空指针,那么直接返回。

void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

这里我们需要注意一下,由于传的是一级指针,所以改变形参并不影响实参。在调用处销毁后需要 置空,防止误用。

三、完整代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1 

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


typedef int BTDataType;

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

#include "Queue.h"

BTNode* BuyBTNode(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;
}

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

// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

// 计算二叉树大小
int size = 0;

int TreeSize1(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	size++;
	TreeSize1(root->left);
	TreeSize1(root->right);

	return size;
}

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

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

int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

// 层序遍历
//void LevelOrder(BTNode* root)
//{
//	Queue q;
//	QueueInit(&q);
//
//	// 如果根非空,则入队列
//	if (root != NULL)
//	{
//		QueuePush(&q, root);
//	}
//
//	// 队列非空则进行遍历
//	while (!QueueEmpty(&q))
//	{
//		BTNode* front = QueueFront(&q);
//		printf("%d ", front->data);
//		QueuePop(&q);
//
//		// 非空入队列
//		if (front->left)
//		{
//			QueuePush(&q, front->left);
//		}
//		
//		if (front->right)
//		{
//			QueuePush(&q, front->right);
//		}
//	}
//
//	printf("\n");
//	QueueDestroy(&q);
//}

// 控制一下层序遍历一层一层出
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	// 如果根非空,则入队列
	if (root != NULL)
	{
		QueuePush(&q, root);
	}
	// 算出每层的大小
	int levelSize = QueueSize(&q);
	// 不为空,则继续
	while (!QueueEmpty(&q))
	{
		// 如果一层不为空,则继续出
		while (levelSize--)
		{
			BTNode* front = QueueFront(&q);
			printf("%d ", front->data);
     			QueuePop(&q);

			// 非空入队列
			if (front->left)
			{
				QueuePush(&q, front->left);
			}

			if (front->right)
			{
				QueuePush(&q, front->right);
			}
		}
		printf("\n");
		levelSize = QueueSize(&q);
	}
	printf("\n");
	QueueDestroy(&q);
}

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

BTNode* TreeFind(BTNode* root, int x)
{
	// 如果 root 为空,则返回空
	if (root == NULL)
	{
		return NULL;
	}

	// 找到了
	if (root->data == x)
	{
		return root;
	}

	// 否则递归左右子树
	BTNode* ret1 = TreeFind(root->left, x);
	// 如果左子树非空,则找到了,返回
	if (ret1 != NULL)
		return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2 != NULL)
		return ret2;

	// 到这里没找到,就得返回NULL
	return NULL;
}

// 判断二叉树是否是完全二叉树
bool TreeComplete(BTNode* root)
{
	// 使用层序遍历思想
	Queue q;
	QueueInit(&q);

	// 如果非空,则入队列
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		
		// 一旦出队列到空,就出去判断
		if (front == NULL)
		{
			break;
		}
		else
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
		else
		{
			QueuePop(&q);
		}
	}
	QueueDestroy(&q);
	return true;
}

// 销毁
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

void TestBTree1()
{
	// 构建二叉树
	BTNode* n1 = BuyBTNode(1);
	BTNode* n2 = BuyBTNode(2);
	BTNode* n3 = BuyBTNode(3);
	BTNode* n4 = BuyBTNode(4);
	BTNode* n5 = BuyBTNode(5);
	BTNode* n6 = BuyBTNode(6);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;

	PreOrder(n1);
	printf("\n");

	InOrder(n1);
	printf("\n");

	PostOrder(n1);
	printf("\n");
}


void TestBTree2()
{
	// 构建二叉树
	BTNode* n1 = BuyBTNode(1);
	BTNode* n2 = BuyBTNode(2);
	BTNode* n3 = BuyBTNode(3);
	BTNode* n4 = BuyBTNode(4);
	BTNode* n5 = BuyBTNode(5);
	BTNode* n6 = BuyBTNode(6);
	BTNode* n7 = BuyBTNode(7);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	n2->right = n7;

	/*size = 0;
	printf("%d\n", TreeSize1(n1));
	size = 0;
	printf("%d\n", TreeSize1(n1));
	size = 0;
	printf("%d\n", TreeSize1(n1));

	printf("%d\n", TreeSize2(n1));

	printf("%d\n", TreeLeafSize(n1));

	printf("%d\n", TreeHeight(n1));

	printf("%d\n", TreeKLevelSize(n1, 3));*/

	printf("%d\n", TreeComplete(n1));

	LevelOrder(n1);

	TreeDestroy(n1);
	n1 = NULL;
}

int main()
{
	//TestBTree1();
	TestBTree2();

	return 0;
}


在这里插入图片描述

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

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

相关文章

2.7 协程设计原理

目录 一、为什么要有协程&#xff1f;二、协程的原语操作1、基本操作2、让出&#xff08;yield&#xff09;和恢复&#xff08;resume&#xff09; 三、协程的切换&#xff08;switch&#xff09;1、汇编2、ucontext3、longjmp / setjmp 四、协程结构的定义五、协程调度器结构的…

【C++】string类的深入介绍

【C】string类的深入介绍&#xff08;1&#xff09; 目录 【C】string类的深入介绍&#xff08;1&#xff09;标准库中的string类string类&#xff08;了解即可&#xff09;string类的常用接口说明&#xff08;最常用的&#xff09;详细介绍string::operator[] 迭代器string中的…

AI时代来临!使用ChatGPT和Kapa.ai协助学习成长!

在加密领域畅游时&#xff0c;常常会遇到不懂的技术名词或是其背后代表的含义&#xff0c;此时通常都需要花费大量的时间进行研究和学习方能掌握。但是自从ChatGPT人工智能的出现&#xff0c;通过简单有效地运用其特性&#xff0c;不仅可以大大提高研究的效率&#xff0c;还可以…

统信UOS V20 安装mysql5.7.42详细教程

1 安装包准备 到mysql官网可以看到最新的是8.0.33&#xff0c;想下载其他版本的点击 Looking for previous GA versions?Select Operating System: 选择如下版本的mysql 安装包 2 安装 2.1 上传文件至服务器 下载后通过远程将安装包上传至服务器&#xff0c;我这里将安装…

软件测试在不同应用场景中,我们该如何进行测试呢?

在我们的日常工作中&#xff0c;我们通常接触到的都是比较复杂的系统。而复杂的系统就意味着比较复杂的测试程序。首先&#xff0c;对于复杂的系统来说&#xff0c;如果想要做功能测试&#xff0c;一般需要考虑到测试数据的问题&#xff0c;还要考虑如何从全局出发&#xff0c;…

canal 环境搭建和配置

canal 环境搭建和配置 安装依赖环境 安装canal服务端 canal客户端配置 安装依赖环境 下载Linux版jdk 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;5r2e --来自百度网盘超级会员V5的分享上传到 /soft/java目录下&#xff0c;并解压-执行如下命令 tar -zxvf jdk…

基于Java在线医疗服务系统设计与实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

Media3:Android下一代媒体框架

无论您是在构建音乐播放器、视频流应用程序还是其他需要播放媒体内容的 Android 应用程序&#xff0c;拥有可靠的媒体播放库都是必不可少的。 这就是 Media3 发挥作用的地方。 什么是 Media3&#xff1f; Media3 是由 Google 作为 AndroidX 的一部分推出的强大媒体播放库。它…

从零开始 Spring Boot 38:Lombok 与依赖注入

从零开始 Spring Boot 38&#xff1a;Lombok 与依赖注入 图源&#xff1a;简书 (jianshu.com) 在之前的文章中&#xff0c;我详细介绍了 Lombok 的用法&#xff0c;考虑到在 Spring 中使用依赖注入&#xff08;DI&#xff09;是如此的频繁&#xff0c;因此有必要讨论使用 Lomb…

精通postman教程(五)postman请求参数化

作为一名测试工程师&#xff0c;那么Postman绝对是大伙必备的工具之一。 在这个系列教程中&#xff0c;我将为大伙详细讲解如何使用Postman进行API测试。 今天我带大伙实战一番postman如何请求参数化 &#xff0c;让你们快速上手这款工具。 请求参数化 数据参数化是Postman…

Kivy系列(一)—— Kivy buildozer的Docker镜像制作

接触Kivy是奔着使用python便捷又是跨平台工具去的&#xff0c;如此一套代码可以发布为各类平台的成果。但是由于网络环境限制&#xff0c;以及kivy工具链上各类工具的频繁迭代&#xff0c;即使按照github上的kivy buildozer官方文档&#xff0c;也很难打包成功&#xff0c;kivy…

调试笔记-stm32的OTA/IAP 通过485升级固件

背景&#xff1a;最近需要在stm32上实现通过rs485升级固件功能。经过几天搜索和调试&#xff0c;实现了功能。 目标&#xff1a;使用cubeIDE实现stm32F407VGT6&#xff0c;通过RS485升级固件 调试记录&#xff1a; 步骤1. 在keil环境下的rs485升级固件(含源码)&#xff1a;S…

react 18.2 官网学习笔记(1)

useMemo const cachedValue useMemo(calculateValue, dependencies)&#xff1b;参数一&#xff1a;计算要缓存的值的函数。它应该是纯的&#xff0c;不应该接受任何参数&#xff0c;并且应该返回任何类型的值。React会在初始渲染时调用你的函数。在下一次渲染时&#xff0c;…

从搭建hadoop开始学习大数据中分而治之的MapReduce(伪集群模式)

环境准备 首先需要将如下四个必要的文件下载到计算机&#xff08;已经附上了下载地址&#xff0c;点击即可下载&#xff09;。 Vmware Workstation 17.x 【官方的下载地址】 CentOS-7-x86_64-Minimal-2009【阿里云镜像站下载地址】 openjdk-8u41-b04-linux-x64-14_jan_2020【开…

入栏需看——全国硕士研究生入学统一考试管理类专业学位联考

本栏意在收集关于全国硕士研究生入学统一考试管理类专业学位联考&#xff0c;简称管理类联考的知识点&#xff0c;考点&#xff0c;希望大家一起沟通&#xff0c;一起进步&#xff0c;管它贵不贵&#xff0c;考过了再说咯 英语 知识篇 阅读 完型填空 作文 技巧篇 第二章…

rolling的用法实例

在数据分析的过程中&#xff0c;经常用到对计算移动均值&#xff0c;使用rolling可以轻松实现这个功能~ rolling函数是一个用于时间序列分析的函数&#xff1b; 一、参数解析 首先&#xff0c;让我们来了解一下rolling的各个参数吧 DataFrame.rolling(window, min_periodsN…

Echarts—X轴鼠标滑动或者缩放/多列柱状图中某一列数据为0时不占位

这里写目录标题 需求背景图表展示X轴鼠标滑动或者缩放设置多列柱状图中某一列数据为0时不占位图表代码展示 需求背景 用柱状图展示12个月的项目对应的供应商数据&#xff1b;每个月有多个项目不确定&#xff0c;1-50之间&#xff0c;也就是说&#xff0c;12个月&#xff0c;每…

1.数据库的基本操作

SQL句子中语法格式提示&#xff1a; 1.中括号&#xff08;[]&#xff09;中的内容为可选项&#xff1b; 2.[&#xff0c;...]表示&#xff0c;前面的内容可重复&#xff1b; 3.大括号&#xff08;{}&#xff09;和竖线&#xff08;|&#xff09;表示选择项&#xff0c;在选择…

魏可伟受邀参加 2023 开放原子全球开源峰会

6月11日-13日&#xff0c;2023 开放原子全球开源峰会在京举行。作为开源行业年度盛事&#xff0c;本次峰会以“开源赋能&#xff0c;普惠未来”为主题&#xff0c;聚集政、产、学、研等各领域优势&#xff0c;汇聚顶尖大咖&#xff0c;共话开源未来。 KaiwuDB CTO 魏可伟受邀出…

Rancher的安装(k8s)

1、 Rancher概述 rancher官方文档 Rancher 是一个 Kubernetes 管理工具&#xff0c;让你能在任何地方和任何提供商上部署和运行集群。 Rancher 可以创建来自 Kubernetes 托管服务提供商的集群&#xff0c;创建节点并安装 Kubernetes&#xff0c;或者导入在任何地方运行的现有…