二叉树详解(2)

news2024/9/21 12:25:10

文章目录

    • 4. 二叉树链式结构的实现
    • 5. 二叉树基础oj练习

4. 二叉树链式结构的实现

首先,我们先要了解一下二叉树的遍历顺序有哪些:
二叉树的遍历顺序

通过了解二叉树的遍历顺序,我们不难看出要实现二叉树的遍历需要用到递归,而使用递归我们就要思考以下两点:

  1. 子问题
  2. 结束条件(最小子问题)

在这里,空树就是不可再分割的子问题。


我们先手搓一棵树,方便我们一会进行验证:

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

typedef struct BinTreeNode
{
	struct BinTreeNode* left;
	struct BinTreeNode* right;
	int val;
}BTNode;

BTNode* BuyBTNode(int val)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));

	if (NULL == newnode)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->val = val;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}

//手搓一棵树
BTNode* CreateTree()
{
	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;

	return n1;
}

int main()
{
	BTNode* root = CreateTree();
	
	return 0;
}
  1. 前序
void PreOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("N ");
		return;
	}

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

前序的递归展开图

  1. 中序
void InOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("N ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}
  1. 后序
void PostOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("N ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}
  1. 节点个数
    最容易想到的就是用计数的方法来实现:
int TreeSize(BTNode* root)
{
	static int size = 0;

	if (NULL == root)
	{
		return 0;
	}
	else
	{
		++size;
	}

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}

但是这样写会出现问题:

int main()
{
	BTNode* root = CreateTree();

	printf("%d\n", TreeSize(root));
	printf("%d\n", TreeSize(root));
	printf("%d\n", TreeSize(root));

	return 0;
}

多次调用时它的个数就会出错,这是因为第一次调用完之后第二次再调用时size的值并不为0,但是size又是在函数里面的,在函数外又不能把它变为0,所以我们可以这样修改:

//static int size = 0;
int size = 0;

int TreeSize(BTNode* root)
{
	if (NULL == root)
	{
		return 0;
	}
	else
	{
		++size;
	}

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}

int main()
{
	BTNode* root = CreateTree();

	printf("%d\n", TreeSize(root));
	size = 0;
	printf("%d\n", TreeSize(root));
	size = 0;
	printf("%d\n", TreeSize(root));

	return 0;
}

但是因为创建的是全局变量,这样写会有线程安全的风险,比如说这个函数给多个线程用,它们之间互相会影响(这里先了解一下,之后学了多线程就可以理解了)

如果一定要用计数的方式来实现,可以这样写:

void TreeSize(BTNode* root, int* psize)
{
	if (NULL == root)
	{
		return;
	}
	else
	{
		++(*psize);
	}

	TreeSize(root->left, psize);
	TreeSize(root->right, psize);
}

那么还有没有其他更好的方法呢?

还是递归,递归本质上就是分而治之。

我们举一个形象的例子:
二叉树节点个数的举例

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

int main()
{
	BTNode* root = CreateTree();
	
	printf("%d\n", TreeSize(root));
	printf("%d\n", TreeSize(root));
	printf("%d\n", TreeSize(root));

	return 0;
}

这种写法可以看作是后序,因为先算出左子树的节点个数,再算出右子树的节点个数,最后加上就可以算出根所在的这棵树的节点个数。(代码中的 + 1 不管放在最前面,还是中间还是最后面都不会影响它是后序,因为我们必须要先算出左右子树的个数才能算出总的节点个数;如果把 + 1 放在中间就认为是中序,那么就变成了只要算出左子树的节点个数,再加上根就可以算出总的节点个数了,这显然是不对的;所以判断前中后序不能简单的看代码,而是要了解它的核心逻辑,它是如何达到这个结果的)

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

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

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

如果我们不记录左右子树的高度,而是直接把递归写到return里,也是对的,但是它的时间复杂度会变得很大:
求树的高度的时间复杂度

  1. 第k层的节点个数
    第k层的节点个数
int TreeKLevel(BTNode* root, int k)
{
	assert(k > 0);

	if (NULL == root)
	{
		return 0;
	}

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

	//不等于空,且k > 1说明第k层的节点在子树里面,转换成子问题求解
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
  1. 查找x所在的节点
    查找x所在的节点

用递归写很容易会写出这样错误的代码:

BTNode* TreeFind(BTNode* root, int x)
{
	if (NULL == root)
	{
		return NULL;
	}

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

	TreeFind(root->left, x);
	TreeFind(root->right, x);
}

通过画递归展开图我们就可以很容易发现问题:有一些递归函数是没有返回值的。

那我们这样修改:

BTNode* TreeFind(BTNode* root, int x)
{
	if (NULL == root)
	{
		return NULL;
	}

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

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

这样写也是不对的,如果是实现查找x在不在二叉树中,那么可以用这种写法:

bool TreeFind(BTNode* root, int x)
{
	if (NULL == root)
	{
		return false;
	}

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

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

正确写法:

BTNode* TreeFind(BTNode* root, int x)
{
	if (NULL == root)
	{
		return NULL;
	}

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

	BTNode* ret1 = TreeFind(root->left, x);

	if (ret1)
	{
		return ret1;
	}
	
	BTNode* ret2 = TreeFind(root->right, x);

	if (ret2)
	{
		return ret2;
	}

	return NULL;
}

当然,也可以简化一下:

BTNode* TreeFind(BTNode* root, int x)
{
	if (NULL == root)
	{
		return NULL;
	}

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

	BTNode* ret1 = TreeFind(root->left, x);

	if (ret1)
	{
		return ret1;
	}

	return TreeFind(root->right, x);
}

第一种写法:为空树就返回空;找到了就返回当前节点;找不到就先找左子树,找到了返回节点;找不到就再找右子树,找到了返回节点;左右子树都找不到就返回空

第二种写法:为空树就返回空;找到了就返回当前节点;找不到就先找左子树,找到了返回节点;找不到就直接返回右子树(因为右子树中如果找到了就返回节点,找不到就返回空,而左右子树都没找到的时候确实应该返回空,所以这样写没有问题 )

5. 二叉树基础oj练习

  1. 检查两颗树是否相同
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
	//根
	//左子树
	//右子树

	//都为空
	if (p == NULL && q == NULL)
	{
		return true;
	}

	//其中一个为空
	if (p == NULL || q == NULL)
	{
		return false;
	}

	if (p->val != q->val)
	{
		return false;
	}

	return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
  1. 单值二叉树

子问题:根(根的值和它的孩子的值进行比较)、左子树、右子树

结束条件(最小子问题):空树

bool isUnivalTree(struct TreeNode* root)
{
	if (NULL == root)
	{
		return true;
	}

	if (root->left && root->left->val != root->val)
	{
		return false;
	}

	if (root->right && root->right->val != root->val)
	{
		return false;
	}

	return isUnivalTree(root->left) && isUnivalTree(root->right);
}
  1. 对称二叉树

这道题其实就是检查两棵树是否相同变形:将左子树和左子树比,右子树和右子树比改成了左子树和右子树比,右子树和左子树比

bool _isSymmetric(struct TreeNode* root1, struct TreeNode* root2)
{
	//根比较
	//左子树和右子树比较
	//右子树和左子树比较

	//都为空
	if (root1 == NULL && root2 == NULL)
	{
		return true;
	}

	//一个为空 另一个不为空
	if (root1 == NULL || root2 == NULL)
	{
		return false;
	}

	if (root1->val != root2->val)
	{
		return false;
	}

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

bool isSymmetric(struct TreeNode* root)
{
	return _isSymmetric(root->left, root->right);
}
  1. 二叉树的前序遍历

这道题的第二个参数有些小伙伴可能会觉得有些奇怪,其实这个参数传的是数组大小的一个指针;因为我们最后要返回一个数组,后台为了方便测试,就统一规定了返回数组的时候就要返回数组的大小

法一:像顺序表一样,空间不够了就扩容

法二:先把树的节点个数算出来,再malloc数组

第一次写的时候可能会出现这样的问题:

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

void preorder(struct TreeNode* root, int* a, int i)
{
    if (NULL == root)
    {
        return;
    }

    a[i++] = root->val;
    preorder(root->left, a, i);
    preorder(root->right, a, i);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
    *returnSize = TreeSize(root);
    int* a = (int*)malloc(sizeof(int) * (*returnSize));
    preorder(root, a, 0);

    return a;
}

这样写只能过一部分测试用例,原因在于:函数在递归的时候开辟了很多块栈帧,一个栈帧中的i++是不会影响另一个栈帧中的i的,所以当递归返回的时候上一个递归函数中的i并没有被++,就会导致数组中存的数据被覆盖掉(画递归展开图可以很直观的看出来)

正确代码:

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

void preorder(struct TreeNode* root, int* a, int* pi)
{
	if (NULL == root)
	{
		return;
	}

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

int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
	*returnSize = TreeSize(root);
	int* a = (int*)malloc(sizeof(int) * (*returnSize));
	int i = 0;
	preorder(root, a, &i);

	return a;
}
  1. 另一颗树的子树

思路:跟原树的每一棵子树进行比较,看是不是相同(比较两棵树是不是相同前面的题已经写过,可以直接复用前面的代码)

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
	//根
	//左子树
	//右子树

	//都为空
	if (p == NULL && q == NULL)
	{
		return true;
	}

	//其中一个为空
	if (p == NULL || q == NULL)
	{
		return false;
	}

	if (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 (NULL == root)
	{
		return false;
	}

	if (root->val == subRoot->val && isSameTree(root, subRoot))
	{
		return true;
	}

	return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
  1. 二叉树的构建及遍历

二叉树的构建

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

typedef struct BinTreeNode
{
	struct BinTreeNode* left;
	struct BinTreeNode* right;
	char val;
}BTNode;

BTNode* CreateTree(char* a, int* pi)
{
	if ('#' == a[(*pi)])
	{
		(*pi)++;
		return NULL;
	}

	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->val = a[(*pi)++];
	root->left = CreateTree(a, pi);
	root->right = CreateTree(a, pi);

	return root;
}

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

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

int main()
{
	char a[100];
	scanf("%s", a);
	int i = 0;
	BTNode* root = CreateTree(a, &i);
	InOrder(root);

	return 0;
}

补充:

  1. 二叉树的销毁:

后序比较好(前序也可以,但是比较麻烦,要在销毁根之前把它保存起来)

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

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}
  1. 层序遍历

层序遍历

//Queue.h

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

typedef struct BinTreeNode* QDataType;

typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);

//入队列
void QueuePush(Queue* pq, QDataType x);
//出队列
void QueuePop(Queue* pq);

QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
//Test.c

typedef struct BinTreeNode
{
	struct BinTreeNode* left;
	struct BinTreeNode* right;
	int val;
}BTNode;

#include "Queue.h"

void TreeLevelOrder(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");
	QueueDestroy(&q);
}

注: 队列具体的实现由于之前讲过,所以这里就不展示出来了

如果想要把空也打印出来,可以这样写:

void TreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
	{
		QueuePush(&q, root);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			printf("%d ", front->val);

			//带入下一层
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			printf("N ");
		}
	}

	printf("\n");
	QueueDestroy(&q);
}

  1. 判断二叉树是否是完全二叉树
    判断二叉树是否是完全二叉树
bool TreeIsComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
	{
		QueuePush(&q, root);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		//遇到空以后就跳出后续判断
		if (NULL == front)
		{
			break;
		}

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	//如果都是空,就是完全二叉树,存在非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

补充:

二叉树的性质:

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点

  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h - 1

  3. 对任何一棵二叉树, 如果度为0的叶结点个数为n0,度为2的分支结点个数为n2,则有 n0=n2 + 1

  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度 h= log2(n + 1) (ps:log2(n + 1) 是log以2为底,n+1为对数)

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:

  • 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

  • 若2i+1<n,左孩子序号:2i+1;2i+1>=n,无左孩子

  • 若2i+2<n,右孩子序号:2i+2;2i+2>=n,无右孩子

二叉树的性质
选择题:

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为(B)
    A 不存在这样的二叉树
    B 200
    C 198
    D 199

  2. 下列数据结构中,不适合采用顺序存储结构的是(A)
    A 非完全二叉树
    B 堆
    C 队列
    D 栈

  3. 在具有 2n 个结点的完全二叉树中,叶子结点个数为(A)
    A n
    B n+1
    C n-1
    D n/2

  4. 一个具有767个节点的完全二叉树,其叶子节点个数为(B)
    A 383
    B 384
    C 385
    D 386

选择题分析

  1. .一棵完全二叉树的节点数位为531个,那么这棵树的高度为(B)
    A 11
    B 10
    C 8
    D 12

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

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

相关文章

基于STM32开发的智能电力监控与管理系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化电流电压数据采集与处理能耗计算与负载管理OLED显示与状态提示Wi-Fi通信与远程监控应用场景 工业设施的电力监控与优化智能家居中的电力管理与节能常见问题及解决方案 常见问题解决…

【C++ 面试 - STL】每日 3 题(二)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

Code Practice Journal | Day58_Graph08 Topological Sorting

1. 概念 在一个有向无环图(DAG)中&#xff0c;根据节点的依赖关系&#xff0c;对所有的节点进行线性排序的算法 拓扑排序的结果不一定是唯一的 2. 实现 2.1 BFS&#xff08;卡恩算法&#xff09; 1、步骤 2、代码实现 以KamaCoder 117.软体构建 题目&#xff1a;117. 软件…

Stable Diffusion绘画 | 插件-宽高比调整助手:让计算器毕业

在调整图片宽高时&#xff0c;如果每次都需要用计算器根据比例算好&#xff0c;再手工输入&#xff0c;非常影响效率。 推荐使用以下的插件&#xff0c;来实现高效准确地调整图片宽高比例。 Aspect Ratio Helper 安装地址&#xff1a;https://github.com/thomasasfk/sd-webui…

80、k8s概念及组件介绍

一、k8s kubernetes:k8s----希腊语&#xff0c;舵手&#xff0c;飞行员 1.1、k8s作用&#xff1a; ​ 用于自动部署&#xff0c;扩展&#xff0c;管理容器化部署的应用程序。开源&#xff08;半开源。&#xff09; ​ k8s的底层语言是由go语言。 ​ k8s理解成负责自动化运…

Jetson Orin Nano GPIO 舵机

jetson orin nano 40针引脚扩展接头&#xff1a; 图源 Jetson Orin Nano Developer Kit User Guide - | NVIDIA Developer 引脚配置 使用jetson-io tool配置引脚&#xff1a; sudo /opt/nvidia/jetson-io/jetson-io.py 选择“Configure Jetson 40pin Header”: "Confi…

啥是纳米微纤维?咋制作?有啥用?

大家好&#xff0c;今天我们来聊聊纳米/微纤维——《Tailoring micro/nano-fibers for biomedical applications》发表于《Bioactive Materials》。这些纤维近年来备受关注&#xff0c;因为它们具有独特的功能和性质&#xff0c;在生物医学等领域有广泛应用。它们可以通过多种技…

滴滴出行:分布式数据库的架构演进之路|OceanBase案例

本文作者&#xff1a;吴其朋&#xff0c;滴滴分布式存储运维负责人 滴滴出行&#xff0c;作为一个集网约车、出租车、顺风车、代驾等多种出行方式于一体的综合性出行服务平台&#xff0c;其用户遍布全球&#xff0c;总数已突破6.5亿。面对如此多样化的出行需求及庞大的用户群体…

Python优化算法18——教与学优化算法(TLBO)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

800G和1.6T以太网:创新与挑战

随着大数据、5G网络、云计算和物联网&#xff08;IoT&#xff09;技术的广泛应用&#xff0c;市场对带宽和数据传输速率的更高需求日益增长&#xff0c;800G和1.6T网络速率的升级备受期待。本文将从800G以太网和1.6T网络在升级方面的所做出的创新举措及其所面临的主要挑战两个方…

小柴冲刺软考中级嵌入式系统设计师系列一、计算机系统基础知识(4)计算机硬件组成及主要部件

目录 计算机系统的基本硬件组成包括 一、中央处理单元 1、CPU的功能 2、CPU的组成 运算器&#xff1a;是数据加工和处理的部件&#xff0c;是执行部件 控制器&#xff1a;运算器只能完成运算&#xff0c;而控制器用于控制整个CPU的工作&#xff0c;它决定了计算机的运行过…

ChatGPT、Claude 和 Gemini 在数据分析方面的合作(第 3 部分):机器学习的最佳 AI 助手

人工智能如何加速你的机器学习项目从特征工程到模型训练 人工智能如何加速你的机器学习项目从特征工程到模型训练 欢迎来到雲闪世界。 在本文中&#xff0c;我们将重点介绍这些 AI 工具如何协助机器学习项目。机器学习是数据科学的基石。虽然使用 LLM 模型完全自动化建模过程具…

代码随想录算法训练营_day32

题目信息 509. 斐波那契数 题目链接: https://leetcode.cn/problems/fibonacci-number/description/题目描述: 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也…

【0-1背包hard】力扣3181. 执行操作可获得的最大总奖励 II

给你一个整数数组 rewardValues&#xff0c;长度为 n&#xff0c;代表奖励的值。 最初&#xff0c;你的总奖励 x 为 0&#xff0c;所有下标都是 未标记 的。你可以执行以下操作 任意次 &#xff1a; 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。 如果 rewardValues[i] 大…

【C++11及其特性】左值和右值

左值和右值目录 一.左值和右值的报错1.简单定义2.函数返回值作左值3.表达式作左值 二.存储的层次结构1.CPU2.内存3.磁盘4.三者联系5.寄存器 三.左值和右值的概念1.左值2.右值3.转换 一.左值和右值的报错 1.简单定义 赋值号’左边的为左值,右边的为右值. 2.函数返回值作左值 …

html2Canvas和jspdf导出长pdf

续使用html2canvas和jspdf导出pdf包含跨页以及页脚_jspdf.umd.min.js-CSDN博客我的这篇文章再写一种情况因为最近我也使用到了 具体的html2Canvas和jspdf的我就不说了&#xff0c;直接开始了&#xff0c; 在公共方法的文件夹中建立一个新的文件htmlToPdf.js用来写咱们得方法然…

亦菲喊你来学机器学习(17) --DBSCAN聚类算法

文章目录 DBSCAN聚类算法基本概念算法步骤特点构建模型模型参数训练模型完整代码展示 总结 DBSCAN聚类算法 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的空间聚类算法&#xff0c;它能够将具有足够高密度的区…

宿舍|基于SprinBoot+vue的宿舍管理系统(源码+数据库+文档)

宿舍管理系统 基于SprinBootvue的私人诊所管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员功能实现 学生功能实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&…

10 个最佳网络爬虫工具和软件,零基础入门到精通,收藏这一篇就够了

据 Strait Research 称&#xff0c;数据提取的需求正在不断增加&#xff0c;预计到 2031 年将达到 18 亿美元。 使用最好的网络爬行工具启动您的数据提取项目&#xff0c;并告别烦人的爬行头痛。我们研究和测试了数百种免费和付费软件&#xff0c;然后为您提出了十种最佳网络爬…

重大内幕!揭秘数据“零丢失”,全靠它

2017年&#xff0c;某运营商设备扩容&#xff0c;误删80万用户数据… 2020年初疫情期间&#xff0c;某电商公司恶意删库事件&#xff0c;导致业务停机3天&#xff0c;公司赔付1.5亿元人民币 “链家程序员删库”事件&#xff0c;恶意删除公司 9TB 数据&#xff0c;造成公司财务…