【数据结构】二叉树链式结构的实现

news2024/11/15 13:23:03

👑作者主页:@进击的安度因
🏠学习社区:进击的安度因(个人社区)
📖专栏链接:数据结构

文章目录

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

今天我们正式开始学习二叉树的链式结构。学习完这块可以大幅度的提高大家的递归水平,同时这块也是一个难点,让我们一起攻克二叉树!

一、二叉树的链式存储

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

结构示意图:image-20221203005324435

逻辑结构:image-20221203005257774

二叉链和三叉链的结构设计:

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 BinTreeNode* left; // 指向当前节点左孩子
    struct BinTreeNode* right; // 指向当前节点右孩子
    BTDataType data; // 当前节点值域
}

手动构建二叉树

对于今天的学习我们直接构建一棵简单的二叉树,如下图所示:

image-20221203121519444

// 创建节点
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 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 Traversal 亦称先序遍历) —— 访问根结点的操作发生在遍历其左右子树之前。

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

前序遍历图解:image-20221203154325260

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

那么当前树的遍历结果为: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);
}

由于是第一个接口,我们在这里详细画一下递归展开图,之后的接口就不详细演示了,需要大家自己下去画啦:

image-20221203162543022

中序遍历

中序遍历(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

平常的层序遍历只要求打印出遍历结果即可,我们这边稍加改良,分别输出每一层的遍历结果。

思路

这里我们需要使用一个队列进行辅助。

一开始,如果二叉树非空,则入一个元素到队列中。并给定一个 levelSize 用来计算二叉树每层的大小。当前大小为1.

然后给定一个循环截止条件为队列为空。当 levelSize 不为0,那么就打印队头元素, 并且出队头。之后如果出队的元素的左右子树非空则入队列;当 levelSize == 0 时,重新计算 当前层数的大小。

循环结束之后,层序遍历完成,这时销毁队列。

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

一句话总结就是:出上一层,带下一层

计算二叉树大小

计算二叉树大小,即计算二叉树中 节点的个数

这里我们可以引申出两种思路:

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

我们先看 思路1 该如何实现:

如果 size 是局部变量,可不可行?当然不可行! 我们遍历的过程还是递归,当每次递归调用时,都会开辟新的函数栈帧,递归中的 size 不是同一个 size ,每次递归时的 size 一开始都是0,无法成功计算出大小。

所以这时我们可以使用全局变量 size,这样 size 在全局数据区,在整个工程内都有效。但是请注意,由于 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返回高度较高的一边。

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

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 层。

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

	if (k == 1)
	{
		return 1;
	}

	return LevalSize(root->left, k - 1) + LevalSize(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;
}

判断是否为完全二叉树

判断是否为完全二叉树需要用到层序遍历的思想。

依然是给一个辅助队列。

如果 root 非空,则将 root 入队。

然后给定循环队列为空则停止,取队头元素,并出队头;这时将取出元素的左右子树都放入,一旦出元素出到 NULL 这时结束循环

完全二叉树是连续的,一旦出现 NULL ,那么后面的元素都应该是空。如果空指针后还有非空元素,那么一定不是完全二叉树。

就像这样:

image-20221203193849766

这时继续出队列,如果出队列过程中遇到非空元素,则销毁队列返回假;否则不断出队列元素。

如果循环结束还没有返回,说明后面都是空指针,这时销毁队列返回真

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

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

三、完整代码

层序遍历和判断是否为完全二叉树所需的队列我就不发出来了,在之前的队列文章中,我们已经实现过。

问题1:队列中存储元素类型为二叉树的节点的指针,Queue.h 引入的位置。

队列的头文件 Queue.h 需要在树的结构后引入,因为队列中存储的元素是 struct BinaryTreeNode*

问题2:如果直接在 Queue.h 中将队列数据类型定义成 BTNode* 会在 Queue.c 中报错,因为在 Queue.c 中找不到 BTNode

这个问题有 两种解决方案

  1. test.c 中关于 BTNode 的定义拷贝到 Queue.h 中。
  2. 用原生的节点指针类型 struct BinaryTreeNode*,然后在 Queue.h 中前置声明。

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

到这里,本篇博客就到此结束了,下一期我会带来二叉树的OJ题,帮助大家巩固二叉树和递归。
如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!
我是anduin,一名C语言初学者,我们下期见!

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

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

相关文章

游戏思考26:游戏服务器压力测试文档(最近在忙这个,这个会更新频繁,12/03未完待续)

文章目录一、压力测试关注点二、计算最耗时的加载操作1&#xff09;从数据库读取数据&#xff0c;对加载的类型进一步划分各种类型&#xff0c;计算最耗时操作2&#xff09;查看CPU随着在线人数的变化所占百分比3&#xff09;查看内存变化4&#xff09;备注一、压力测试关注点 …

【C语言字符串】一道题检验你的字符串学习情况

作者&#xff1a;匿名者Unit 目录 一.字符串引言1.字符串基础二.洛谷P5734详解1.字符串相关库函数&#xff08;1&#xff09; strcpy函数 &#xff08;2&#xff09; strcat函数 &#xff08;3&#xff09;strstr函数 2.题目讲解一.字符串引言 1.字符串基础 字符串通常以\0作为…

008. 子集

1.题目链接&#xff1a; 78. 子集 2.解题思路&#xff1a; 2.1.题目要求&#xff1a; 给一个元素各不相同的数组 nums&#xff0c;返回各种可能的子集&#xff08;子集不能重复&#xff09; 比如&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2],[…

做了8年前端,感谢那些优秀的后端,陪伴我工作,教会我成长

☆ 前段时间由于一时的头脑发热&#xff0c;写了一篇《做了8年前端&#xff0c;细说那些曾经让你浴霸不能的后端》的博客&#xff0c;虽然每个细节也都属实吧&#xff0c;但始终是一些负能量的东西&#xff0c;建议大家不要去看了&#xff0c;今年互联网情况已经这样了&#xf…

安卓APP源码和设计报告——体育馆预约系统

项目名称&#xff1a;体育馆体育场预约系统专业&#xff1a;班级&#xff1a;学号&#xff1a;姓名&#xff1a; 目 录 一、项目功能介绍3 二、项目运行环境3 1、开发环境3 2、运行环境3 3、是否需要联网3 三、项目配置文件及工程结构4 1、工程配置文件4 2、工程结构目…

磨金石教育摄影技能干货分享|上海随手拍——叶落满街,秋意未尽

步入十二月以来&#xff0c;气温也随之骤降&#xff0c;这时候才明显感到初冬已至。冬天的寒风就是最好的脱叶剂&#xff0c;走在街道上&#xff0c;抬眼望去两旁的树木多数已经稀疏。只有残留的绿意还在迎着微微的寒风摇动。 我最喜欢的是秋天&#xff0c;因为秋天的草木最有色…

基于keras与tensorflow手工实现ResNet50网络

前言 在文章 基于tensorflow的ResNet50V2网络识别动物&#xff0c;我们使用了keras已经提供的神经网络&#xff0c;完成了图像分类的。这个时候&#xff0c;小明同学就问了&#xff0c;那么我怎么自己去写一个神经网络来进行训练呢&#xff1f; 本文就基于tensorflow&#xff…

Redis原理篇——五种基本数据类型

一、Redis底层数据结构 1. SDS 获取字符串长度耗时&#xff1a; 由于Redis底层是C语言编写的&#xff0c;C语言中没有字符串这个概念&#xff0c;本质上都是字符数组&#xff0c;获取字符串长度就是遍历数组获取长度&#xff08;遍历到 \0结束标识结束 &#xff09;时间复杂度…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校毕业生信息采集系统05hj2

大四计算机专业的同学们即将面临大学4年的最后一次考验--毕业设计。通过完成毕业设计来对过去4年的大学学习生活做一个总结&#xff0c;也是检验我们学习成果的一种方式&#xff0c;毕业设计作品也是我们将来面试找工作的一个敲门砖。 选题前先看看自己掌握哪些技术点、擅长哪…

数据库常用的数据类型和约束条件

文章目录一. 数据库常用的数据类型1. 数字类型1.1 整数类型:INT(m)和BIGINT(m)1.2 浮点类型:DOUBLE(m,n)2. 字符类型2.1 定长字符:CHAR(n)2.2 变长字符:VARCHAR(n)2.3 变长字符:TEXT(n)3. 日期类型3.1 语法格式:3.2 注意事项二. 约束条件1.主键约束(PRIMARY KEY)1.1 注意事项1.…

[附源码]Python计算机毕业设计Django美发店会员管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Web压测工具http_load原理分析

01、前言 http_load是一款测试web服务器性能的开源工具&#xff0c;从下面的网址可以下载到最新版本的http_load&#xff1a; http://www.acme.com/software/http_load/ 这个软件一直在保持着更新&#xff08;不像webbench&#xff0c;已经是十年的老古董了。 webbench的源…

【Matplotlib绘制图像大全】(二十九):Matplotlib绘制热力图

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

【经验分享】突然我的SM.MS的图床没法访问了(内附解决方法)

【经验分享】突然我的SM.MS的图床没法访问了&#xff08;内附解决方法&#xff09; 一大早写文章&#xff0c;发现Markdown里的图片全部都不能成功加载了&#xff0c;这个的确挺头疼的&#xff01; 文章目录1 说一说现象2 简单排查一下3 查找解决方案4 实施解决方案5 总结6 更多…

高楼扔鸡蛋问题

1.对应letecode链接 高楼扔鸡蛋问题 2.题目描述 解题思路 题目是这样&#xff1a;你面前有一栋从 1 到 N 共 N 层的楼&#xff0c;然后给你 K 个鸡蛋&#xff08;K 至少为 1&#xff09;。现在确定这栋楼存在楼层 0 < F < N&#xff0c;在这层楼将鸡蛋扔下去&#xff…

Windows使用ssh协议远程连接ubuntu linux子系统

Windows使用ssh协议远程连接ubuntu linux子系统一、Windows远程连接ubuntu linux子系统二、开启ubuntu ssh服务三、获取ubuntu子系统的ip地址四、从windows上通过ssh连接到ubuntu子系统五、后记一、Windows远程连接ubuntu linux子系统 当我们在windows上安装好ubuntu子系统后&…

Linux命令总结详细

Linux命令总结详细1.前言2.基础知识2.1.执行命令格式2.2.帮助命令2.2.1.man命令2.3.部分快捷键2.3.1.Tab键2.3.2.Ctrlc组合键2.3.3.Ctrll组合键2.4.服务运行命令2.5.服务开机启动命令3.系统工作命令3.1.date时间命令3.1.1.命令解释3.1.2.命令参数3.1.3.案例3.2.reboot重启命令3…

ESP-01S使用AT指令连接阿里云

这次分享下ESP8266-01S使用AT指令连接阿里云&#xff0c;为了后面stm32--esp-01s-阿里云&#xff08;MQTT&#xff09;做铺垫 目录 步骤&#xff1a; 1.烧录阿里云固件 首先我们打开->安信可官网下载阿里云的固件&#xff0c;如图 1.1串口助手与esp-01s接线说明 注&am…

C语言——VS2019实用调试技巧

前言 要想成为一个合格的程序员&#xff0c;不仅仅要会写代码&#xff0c;更要会调试代码。咔咔一通敲代码&#xff0c;敲出了BUG&#xff0c;这时就分两种程序员&#xff0c;一种是质疑编译器的程序员&#xff0c;“什么&#xff1f;我写出了BUG&#xff0c;是不是机器出了问…

什么是混淆矩阵精度、召回率、准确性、F1 分数、FPR、FNR、TPR、TNR?

在你的数据科学生涯的开始,混淆矩阵会非常混乱,我们会有很多问题,比如什么时候使用精度?什么时候使用召回?在哪些情况下可以使用精度?因此,我将尝试在本博客中回答这些问题。 什么是混淆矩阵? 混淆矩阵是一种将预测结果和实际值以矩阵形式汇总的方法,用来衡量分类问题…