【第四节】C/C++数据结构之树与二叉树

目录

一、基本概念与术语

二、树的ADT

三、二叉树的定义和术语

四、平衡二叉树

4.1 解释

4.2 相关经典操作

4.3 代码展示


一、基本概念与术语

树(Tree)是由一个或多个结点组成的有限集合T。其中:
1 有一个特定的结点,称为该树的根(root)结点;
2 每个树都有且仅有一个特定的,称为根(Root)的节点。

树的常用术语:
1 当n>1时,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree);
2 节点的子树称为该节点的子节点(Child),相应的,该节点称为子节点的父节点(Parent );
3 同一个父节点的子节点之间称为兄弟节点(Sibling);
4 节点的层次(Level)从根节点开始定义,根为第一层,根的子节点为第二层,双亲在同一层的节点互为堂兄弟,树中节点最大的层次称为树的深度或高度;
5 如果将树中节点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序R树,否则称为无序树;
6 如果有n颗互不相交的树组成一个集合,则这个集合被称之为森林。

二、树的ADT

数据及关系:
    具有相同数据类型的数据元素或结点的有限集合。树T的二元组形式为:                           
            T=(D,R)
    其中D为树T中结点的集合,R为树中结点之间关系的集合。
                           D={Root}∪DF
    其中,Root为树T的根结点,DF为树T的根Root的子树集合。
                      R={<Root,ri>,i=1,2,…,m}
    其中,ri是树T的根结点Root的子树Ti的根结点。
操作:
    Constructor:
        前提:已知根结点的数据元素之值。
        结果:创建一棵树。
    Getroot:
        前提:已知一棵树。.
        结果:得到树的根结点。
    FirstChild:
        前提:已知树中的某一指定结点 p。
        结果:得到结点 p 的第一个儿子结点。
    NextChild:
        前提:已知树中的某一指定结点 p 和它的一个儿子结点 u。
        结果:得到结点 p 的儿子结点 u 的下一个兄弟结点 v。

基本操作:

        初始化一棵空树;
        创建一棵树;
        判断空树,为空返回True,否则返回False;
        按照某特定顺序遍历一棵树;
        求树的深度;
        在树中某特定位置插入结点;
        在树中某特定位置删除结点;
        求某结点的双亲结点;
        销毁树;
        等等;

三、二叉树的定义和术语

        二叉树(Tree)是n个节点的有限集合,该集合为空集(或称为空二叉树),或者由一个根节点和两颗互不相交的、分别称为根节点的左子树与右子树的二叉树组成。
A. 所有节点都只有左子树的二叉树叫左斜树,所有节点都只有右子树的二叉树叫右斜树,这两者统称为斜树;
B. 在一棵二叉树中,如果所有分支节点都存在左子树与右子树,并且所有叶子都在同一层次上,这样的二叉树称为满二叉树;
C. 对一颗具有n个结点的二叉树按层序编号,如果编号i(1<i<n)的节点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这颗二叉树称为完全叉树。

四、平衡二叉树

4.1 解释

        平衡二叉树(Balanced Binary Tree),又称为AVL树(有别于AVL算法),是一种特殊的二叉搜索树(Binary Search Tree)结构12。它具有以下性质:

  1. 它可以是空树2。
  2. 它的左子树和右子树的高度差的绝对值不超过12。
  3. 它的左子树和右子树都是平衡二叉树12。

        平衡二叉树的设计目的是为了解决普通二叉搜索树在插入、删除等操作时可能产生的不平衡问题,从而避免树的高度过高,确保搜索效率始终保持在相对优化的水平。在平衡二叉树中,查找、插入和删除操作的时间复杂度都可以维持在O(logN)2。

平衡二叉树在计算机科学中有广泛的应用,例如:

  • 数据库索引:用于加速数据库的查询操作3。
  • 查找和排序:可以快速查找和排序数据3。
  • 模拟实际问题:如航班预定系统中的座位分配3。
  • 实现字典或符号表:键是树中的节点,值是与该键相关联的数据,支持高效的查找、插入和删除操作3。
  • 实现线性数据结构:如栈、队列和优先队列3。
  • 网络路由:用于实现网络路由表,进行快速的路由查找3。
  • 文件系统:用于实现文件系统的索引结构,支持快速的文件查找和访问3。

        总之,平衡二叉树是一种高效的数据结构,它通过保持树的平衡来优化搜索性能,并在各种应用中发挥着重要作用。

4.2 相关经典操作

        平衡二叉树(AVL树)在插入或删除节点后,可能会破坏其平衡性(即左右子树的高度差超过1)。为了重新恢复平衡,需要进行旋转操作。旋转操作包括四种:左单旋、右单旋、左右旋和右左旋。下面我会逐一解释这四种操作:

1. 左单旋
        当某个节点的左子树的左子树插入了一个新节点,导致该节点失去平衡时,需要进行左单旋。

步骤:

  • 以失去平衡的节点为根的子树中,找到该节点左子树的根节点(记作A)。
  • 将A节点提升为新的根节点。
  • 将原根节点变为A的右子树。
  • A的左子树保持不变。

左单旋的结果是将不平衡向右侧转移。

左单旋举例:

        针对节点8,它的左子树的高度是1,右子树的高度是3,高度差超过1.并且出错的节点13和15均位于节点8的右子节点12的右边,则通过左旋便可修复。

其一左单旋的结果:

动图展示:

2. 右单旋
        与左单旋对称,当某个节点的右子树的右子树插入了一个新节点,导致该节点失去平衡时,需要进行右单旋。

步骤:

  • 以失去平衡的节点为根的子树中,找到该节点右子树的根节点(记作A)。
  • 将A节点提升为新的根节点。
  • 将原根节点变为A的左子树。
  • A的右子树保持不变。

右单旋的结果是将不平衡向左侧转移。

右单旋举例:

        针对节点8,它的左子树的高度为3,右子树高度为1,高度差超过1。并且出错的节点1和3位于8节点的左子节点4的左边。针对这种类型的非平衡树,通过右旋便可以使其重新平衡。
具体做法: 将节点8作为节点4的右子节点,节点6作为节点8的左子节点

其一右单旋的结果:

用一个动图来表示 右旋

3. 左右旋
        当某个节点的左子树的右子树插入了一个新节点,导致该节点失去平衡时,需要进行左右旋。

步骤:

  • 先对失去平衡的节点的左子树进行右单旋。
  • 再对整棵树进行左单旋。

        左右旋实际上是右单旋和左单旋的组合,它首先尝试将不平衡向右侧转移,然后再将不平衡向左侧转移。

左右旋举例:

        针对节点8,左子树的高度是3,右子树高度是1,高度差超过1。并且出错的节点5和7均位于节点8的左节点4的右边。这种情况需要先左旋再右旋便可恢复。

        针对节点4进行左旋,左旋后变成了需要右旋的情况,可参考上面的右旋进行旋转即可。

4. 右左旋
        与左右旋对称,当某个节点的右子树的左子树插入了一个新节点,导致该节点失去平衡时,需要进行右左旋。

步骤:

  • 先对失去平衡的节点的右子树进行左单旋。
  • 再对整棵树进行右单旋。

        右左旋实际上是左单旋和右单旋的组合,它首先尝试将不平衡向左侧转移,然后再将不平衡向右侧转移。

        这些旋转操作确保了AVL树在插入或删除节点后仍然保持平衡,从而保证了树的搜索效率。在进行旋转操作时,还需要更新相关节点的高度信息,以便在后续操作中继续检查平衡性。

右左旋举例:

类似的针对12节点先进行右旋,再整体左旋,原理类似 不再赘述

4.3 代码展示

        平衡二叉树的左单旋,右单旋,左右旋,右左旋操作代码演示

#include "Tree.h"


CTree::CTree() :m_pRoot(0), m_nCount(0)
{
}


CTree::~CTree()
{
}

//************************************
// Method:    AddData 添加数据
// FullName:  CTree::AddData
// Access:    private 
// Returns:   bool
// Parameter: int nData
//************************************
bool CTree::AddData(int nData)
{
	return AddData(m_pRoot, nData);
}

//************************************
// Method:    AddData
// FullName:  CTree::AddData
// Access:    private 
// Returns:   bool
// Parameter: PTREE_NODE & pTree 根节点
// Parameter: int nData
//************************************
bool CTree::AddData(TREE_NODE*& pTree, int nData)
{
	//pTree是否为空,如果为空说明有空位可以添加
	if (!pTree)
	{
		pTree = new TREE_NODE{};
		pTree->nElement = nData;
		m_nCount++;
		return true;
	}
	//与根做对比,小的放在其左子树,否则放在右子树
	if (nData > pTree->nElement)
	{
		AddData(pTree->pRChild, nData);
		//判断是否平衡
		if (GetDeep(pTree->pRChild) - 
			GetDeep(pTree->pLChild) == 2)
		{
			//判断如何旋转
			if (pTree->pRChild->pRChild)
			{
				//左旋
				LeftWhirl(pTree);
			}
			else if (pTree->pRChild->pLChild)
			{
				//右左旋
				RightLeftWhirl(pTree);
			}
		}
	}
	if (nData < pTree->nElement)
	{
		AddData(pTree->pLChild, nData);
		//判断是否平衡
		if (GetDeep(pTree->pLChild) -
			GetDeep(pTree->pRChild) == 2)
		{
			//判断如何旋转
			if (pTree->pLChild->pLChild)
			{
				//右旋
				RightWhirl(pTree);
			}
			else if (pTree->pLChild->pLChild)
			{
				//左右旋
				LeftRightWhirl(pTree);
			}
		}
	}
}

//************************************
// Method:    DelData   删除元素
// FullName:  CTree::DelData
// Access:    private 
// Returns:   bool
// Parameter: int nData
//************************************
bool CTree::DelData(int nData)
{
	return DelData(m_pRoot, nData);
}

//************************************
// Method:    DelData
// FullName:  CTree::DelData
// Access:    private 
// Returns:   bool
// Parameter: PTREE_NODE & pTree 根节点
// Parameter: int nData
//************************************
bool CTree::DelData(PTREE_NODE& pTree, int nData)
{
	bool bRet = false;
	//判断是否为空树
	if (empty())
	{
		return false;
	}
	//开始遍历要删除的数据
	if (pTree->nElement == nData)
	{
		//判断是否为叶子节点,是就可以直接删除,
		//不是则需要找代替
		if (!pTree->pLChild && !pTree->pRChild)
		{
			delete pTree;
			pTree = nullptr;
			m_nCount--;
			return true;
		}
		//根据左右子树的深度查找要替换的节点
		if (GetDeep(pTree->pLChild) >= 
			GetDeep(pTree->pRChild))
		{
			PTREE_NODE pMax = GetMaxOfLeft(pTree->pLChild);
			pTree->nElement = pMax->nElement;
			DelData(pTree->pLChild, pMax->nElement);
		}
		else
		{
			PTREE_NODE pMin = GetMinOfRight(pTree->pRChild);
			pTree->nElement = pMin->nElement;
			DelData(pTree->pRChild, pMin->nElement);
		}
	}
	else if (nData > pTree->nElement)
	{
		bRet = DelData(pTree->pRChild, nData);
		//判断是否平衡
		if (GetDeep(pTree->pLChild) -
			GetDeep(pTree->pRChild) == 2)
		{
			//判断如何旋转
			if (pTree->pLChild->pLChild)
			{
				//右旋
				RightWhirl(pTree);
			}
			else if (pTree->pLChild->pLChild)
			{
				//左右旋
				LeftRightWhirl(pTree);
			}
		}
	}
	else /*if (nData < pTree->nElement)*/
	{
		bRet = DelData(pTree->pLChild, nData);
		//判断是否平衡
		if (GetDeep(pTree->pRChild) -
			GetDeep(pTree->pLChild) == 2)
		{
			//判断如何旋转
			if (pTree->pRChild->pRChild)
			{
				//左旋
				LeftWhirl(pTree);
			}
			else if (pTree->pRChild->pLChild)
			{
				//右左旋
				RightLeftWhirl(pTree);
			}
		}
	}
	return bRet;
}

//************************************
// Method:    ClearTree 清空元素
// FullName:  CTree::ClearTree
// Access:    private 
// Returns:   void
//************************************
void CTree::ClearTree()
{
	ClearTree(m_pRoot);
	m_nCount = 0;
}

//************************************
// Method:    ClearTree
// FullName:  CTree::ClearTree
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE & pTree 根节点
//************************************
void CTree::ClearTree(PTREE_NODE& pTree)
{
	//从叶子节点开始删除
	//删除其左右子树后再删除根节点本身

	//判断是否为空树
	if (empty())
	{
		return;
	}
	//判断是否为叶子节点
	if (!pTree->pLChild && !pTree->pRChild)
	{
		delete pTree;
		pTree = nullptr;
		return;
	}
	ClearTree(pTree->pLChild);
	ClearTree(pTree->pRChild);
	ClearTree(pTree);
}

//************************************
// Method:    TravsoualPre 前序遍历
// FullName:  CTree::TravsoualPre
// Access:    private 
// Returns:   void
//************************************
void CTree::TravsoualPre()
{
	TravsoualPre(m_pRoot);
}

//************************************
// Method:    TravsoualPre
// FullName:  CTree::TravsoualPre
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE pTree 根节点
//************************************
void CTree::TravsoualPre(PTREE_NODE pTree)
{
	//递归的返回条件
	if (!pTree)
	{
		return;
	}
	//根左右
	printf("%d ", pTree->nElement);
	TravsoualPre(pTree->pLChild);
	TravsoualPre(pTree->pRChild);
}

//************************************
// Method:    TravsoualMid  中序遍历
// FullName:  CTree::TravsoualMid
// Access:    private 
// Returns:   void
//************************************
void CTree::TravsoualMid()
{
	TravsoualMid(m_pRoot);
}

//************************************
// Method:    TravsoualMid
// FullName:  CTree::TravsoualMid
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE pTree 根节点
//************************************
void CTree::TravsoualMid(PTREE_NODE pTree)
{
	//递归的返回条件
	if (!pTree)
	{
		return;
	}
	//左根右
	TravsoualMid(pTree->pLChild);
	printf("%d ", pTree->nElement);
	TravsoualMid(pTree->pRChild);
}

//************************************
// Method:    TravsoualBack  后序遍历
// FullName:  CTree::TravsoualBack
// Access:    private 
// Returns:   void
//************************************
void CTree::TravsoualBack()
{
	TravsoualBack(m_pRoot);
}

//************************************
// Method:    TravsoualBack
// FullName:  CTree::TravsoualBack
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE pTree 根节点
//************************************
void CTree::TravsoualBack(PTREE_NODE pTree)
{
	//递归的返回条件
	if (!pTree)
	{
		return;
	}
	//左右根
	TravsoualBack(pTree->pLChild);
	TravsoualBack(pTree->pRChild);
	printf("%d ", pTree->nElement);
}

//************************************
// Method:    层序遍历
// FullName:  CTree::LevelTravsoual
// Access:    public 
// Returns:   void
//************************************
void CTree::LevelTravsoual()
{
	vector<PTREE_NODE> vecRoot;  //保存根节点
	vector<PTREE_NODE> vecChild; //保存根节点的子节点

	vecRoot.push_back(m_pRoot);

	while (vecRoot.size())
	{
		for (int i = 0; i < vecRoot.size();i++)
		{
			printf("%d ", vecRoot[i]->nElement);
			//判断其是否左右子节点
			if (vecRoot[i]->pLChild)
			{
				vecChild.push_back(vecRoot[i]->pLChild);
			}
			if (vecRoot[i]->pRChild)
			{
				vecChild.push_back(vecRoot[i]->pRChild);
			}
		}
		vecRoot.clear();
		vecRoot = vecChild;
		vecChild.clear();
		printf("\n");
	}
}

//************************************
// Method:    GetCount  获取元素个数
// FullName:  CTree::GetCount
// Access:    public 
// Returns:   int
//************************************
int CTree::GetCount()
{
	return m_nCount;
}

//************************************
// Method:    GetDeep 获取节点深度
// FullName:  CTree::GetDeep
// Access:    private 
// Returns:   int
// Parameter: PTREE_NODE & pTree 
//************************************
int CTree::GetDeep(PTREE_NODE pTree)
{
	//判断pTree是否为空
	if (!pTree)
	{
		return 0;
	}
	int nL = GetDeep(pTree->pLChild);
	int nR = GetDeep(pTree->pRChild);
	//比较左右子树的深度,取最大值加 1 返回
	return (nL >= nR ? nL : nR) + 1;
}

//************************************
// Method:    GetMaxOfLeft 获取左子树中的最大值
// FullName:  CTree::GetMaxOfLeft
// Access:    private 
// Returns:   PTREE_NODE
// Parameter: PTREE_NODE pTree
//************************************
PTREE_NODE CTree::GetMaxOfLeft(PTREE_NODE pTree)
{
	//只要存在右子树就有更大的值
	//是否空
	if (!pTree)
	{
		return 0;
	}
	//判断是否有右子树
	while (pTree->pRChild)
	{
		pTree = pTree->pRChild;
	}
	//返回最大值节点
	return pTree;
}

//************************************
// Method:    GetMinOfRight 获取右子树中的最小值
// FullName:  CTree::GetMinOfRight
// Access:    private 
// Returns:   PTREE_NODE
// Parameter: PTREE_NODE pTree
//************************************
PTREE_NODE CTree::GetMinOfRight(PTREE_NODE pTree)
{
	//只要存在左子树就有更小的值
	//是否空
	if (!pTree)
	{
		return 0;
	}
	//判断是否有右子树
	while (pTree->pLChild)
	{
		pTree = pTree->pLChild;
	}
	return pTree;
}

//************************************
// Method:    LeftWhirl 左旋
// FullName:  CTree::LeftWhirl
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE & pTree
//************************************
void CTree::LeftWhirl(PTREE_NODE& pK2)
{
/*
   k2                  k1
     k1   ==>       k2    N
	X  N              X
*/
	//保存k1
	PTREE_NODE pK1 = pK2->pRChild;
	//保存X
	pK2->pRChild = pK1->pLChild;
	//k2变成k1的左子树
	pK1->pLChild = pK2;
	//k1变成k2
	pK2 = pK1;
}

//************************************
// Method:    RightWhirl  右旋
// FullName:  CTree::RightWhirl
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE & pTree
//************************************
void CTree::RightWhirl(PTREE_NODE& pK2)
{
/*
	 k2            k1
   k1     ==>    N    k2
  N  X               X
*/
	//保存k1
	PTREE_NODE pK1 = pK2->pLChild;
	//保存X
	pK2->pLChild = pK1->pRChild;
	//k1的右子树为k2
	pK1->pRChild = pK2;
	//k2为k1
	pK2 = pK1;
}

//************************************
// Method:    LeftRightWhirl 左右旋
// FullName:  CTree::LeftRightWhirl
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE & pTree
//************************************
void CTree::LeftRightWhirl(PTREE_NODE& pK2)
{
/*
      k2               k2              N
	k1     左旋       N       右旋  K1   K2
	  N             k1 [x]             [x]
	   [x]     
*/
	LeftWhirl(pK2->pLChild);
	RightWhirl(pK2);
}

//************************************
// Method:    RightLeftWhirl 右左旋
// FullName:  CTree::RightLeftWhirl
// Access:    private 
// Returns:   void
// Parameter: PTREE_NODE & pTree
//************************************
void CTree::RightLeftWhirl(PTREE_NODE& pK2)
{
/*
	k2               k2                   N
	   k1    右旋       N     左旋    k2     K1
	 N               [x]  k1           [x]
  [x]
*/
	RightWhirl(pK2->pRChild);
	LeftWhirl(pK2);
}

bool CTree::empty()
{
	return m_pRoot == 0;
}

调用代码:

#include "Tree.h"

int main()
{
	CTree tree;

	tree.AddData(1);
	tree.AddData(2);
	tree.AddData(3);
	tree.AddData(4);
	tree.AddData(5);
	tree.AddData(6);
	tree.AddData(7);
	tree.AddData(8);
	tree.AddData(9);
	tree.AddData(10);
	tree.LevelTravsoual();
	tree.DelData(4);
	tree.LevelTravsoual();

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPT-4o:突出优势 和 应用场景

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

centos官方yum源不可用 解决方案(随手记)

昨天用yum安装软件的时候&#xff0c;就报错了 [rootop01 ~]# yum install -y net-tools CentOS Stream 8 - AppStream 73 B/s | 38 B 00:00 Error: Failed to download metadata for repo appstream: Cannot prepare internal mirrorlis…

从零开始:疾控中心实验室装修攻略,让你的实验室一步到位!

在当今充满挑战和变化的世界中&#xff0c;疾病的控制和预防成为了人类生存与发展的重要课题。而疾控中心作为防控疾病的核心机构&#xff0c;其疾控中心实验室设计建设显得尤为重要。下面广州实验室装修公司小编将分享疾控中心实验室设计建设方案&#xff0c;为疾病防控工作提…

“冻干”凭什么好吃不肥喵?既能当零食又可做主食的冻干分享

近年来&#xff0c;冻干猫粮因其高品质而备受喜爱&#xff0c;吸引了无数猫主人的目光&#xff0c;像我这样的资深养猫人早已开始选择冻干喂养。但新手养猫的人&#xff0c;可能会感到迷茫&#xff1a;冻干猫粮到底是什么&#xff1f;冻干可以一直当主食喂吗&#xff1f; 一、…

TqdmWarning: IProgress not found. Please update jupyter and ipywidgets.

jupyter notebook报错 在pycharm的terminal中 安装完成后就不会再报错了

缓存方法返回值

1. 业务需求 前端用户查询数据时,数据查询缓慢耗费时间; 基于缓存中间件实现缓存方法返回值:实现流程用户第一次查询时在数据库查询,并将查询的返回值存储在缓存中间件中,在缓存有效期内前端用户再次查询时,从缓存中间件缓存获取 2. 基于Redis实现 参考1 2.1 简单实现 引入…

电源小白入门学习10——浪涌、防浪涌器件、浪涌保护芯片

浪涌、防浪涌器件、浪涌保护芯片 浪涌浪涌保护器件的分类与原理保险丝TVS二极管新防护电路 浪涌 浪涌&#xff0c;相信不少学习过电子的同学或多或少都通过这个词&#xff0c;但是到底什么是浪涌呢&#xff0c;GPT给我的答案是这样的&#xff1a; 浪涌&#xff0c;也称为瞬态…

MS21112S单通道 LVDS 差分线路接收器

MS21112S 是一款单通道低压差分信号 (LVDS) 线 路接收器。在输入共模电压范围内&#xff0c;差分接收器可以 将 100mV 的差分输入电压转换成有效的逻辑输出。 该芯片可应用于 100Ω 的受控阻抗介质上&#xff0c;进行点对 点基带数据传输。传输介质可以是印刷电路板、…

分水岭算法分割和霍夫变换识别图像中的硬币

首先解释一下第一种分水岭算法&#xff1a; 一、分水岭算法 分水岭算法是一种基于拓扑学的图像分割技术&#xff0c;广泛应用于图像处理和计算机视觉领域。它将图像视为一个拓扑表面&#xff0c;其中亮度值代表高度。算法的目标是通过模拟雨水从山顶流到山谷的过程&#xff0…

数据库存储过程和锁机制

存储过程 存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的&#xff0c;存储过程思想上很简单,就是数据库SQL语言层面的代码封装与有重用 …

【动态规划-BM69 把数字翻译成字符串】

题目 BM69 把数字翻译成字符串 描述 有一种将字母编码成数字的方式&#xff1a;‘a’->1, ‘b->2’, … , ‘z->26’。 现在给一串数字&#xff0c;返回有多少种可能的译码结果 分析 特判一个‘0’的情况 后面可以用动态规划&#xff1a; dp[n]为考虑前n个字符时&…

VMware虚拟机与MobaXterm建立远程连接失败

VMware虚拟机与MobaXterm建立远程连接失败 首先可以检查一下是不是虚拟机的ssh服务并不存在 解决方法&#xff1a; 1.更新镜像源 yum -y update 这个过程会有点久&#xff0c;请耐心等待 2.安装ssh yum install openssh-server 3.启动ssh systemctl restart sshd 4.查…

计算机毕业设计基于YOLOv8的头盔检测系统

1、安装Anaconda 官网下载或者哔哩哔哩有的up分享 https://www.anaconda.com/download 版本无所谓&#xff0c;安装位置不要有中文就行 2、创建环境yolov8 winR打开命令行 conda create -n yolov8 python3.9 3、打开源码 下载下来放到你想放的目录&#xff0c;直接用pyCharm或者…

NSSCTF CRYPTO MISC题解(一)

陇剑杯 2021刷题记录_[陇剑杯 2021]签到-CSDN博客 [陇剑杯 2021]签到 下载附件压缩包&#xff0c;解压后得到 后缀为.pcpang&#xff0c;为流量包&#xff0c;流量分析&#xff0c;使用wireshark打开 {NSSCTF} [陇剑杯 2021]签到 详解-CSDN博客 选择统计里面的协议分级 发现流…

go语言接口之接口值

概念上讲一个接口的值&#xff0c;接口值&#xff0c;由两个部分组成&#xff0c;一个具体的类型和那个类型的值。它们 被称为接口的动态类型和动态值。对于像Go语言这种静态类型的语言&#xff0c;类型是编译期的概 念&#xff1b;因此一个类型不是一个值。在我们的概念模型中…

DexCap——斯坦福李飞飞团队泡茶机器人:更好数据收集系统的原理解析、源码剖析

前言 2023年7月&#xff0c;我司组建大模型项目开发团队&#xff0c;从最开始的论文审稿&#xff0c;演变成目前的两大赋能方向 大模型应用方面&#xff0c;以微调和RAG为代表 除了论文审稿微调之外&#xff0c;目前我司内部正在逐一开发论文翻译、论文对话、论文idea提炼、论…

男士内裤怎么选?五款不能错过的超舒适男士内裤

在快节奏的现代都市生活中&#xff0c;男士们同样需要关注内在穿搭的品质与舒适度。一条优质贴身的男士内裤&#xff0c;不仅是日常穿着的舒适保障&#xff0c;更是展现男性精致品味的秘密武器。今天&#xff0c;就让我们一同探讨如何挑选出最适合自己的男士内裤&#xff0c;并…

劝大家:打个工而已,千万不要太老实,上周,我们单位一位兢兢业业,工作了20年的老员工,被公司辞退了...

学习资源已打包&#xff0c;需要的小伙伴可以戳这里 学习资料 在当今社会&#xff0c;职场竞争激烈&#xff0c;每个人都在努力工作&#xff0c;追求自己的目标。然而&#xff0c;随着工作经验的积累和观察的深入&#xff0c;我发现了一些工作中的现象&#xff0c;希望通过本文…

Asp .Net Core 系列:详解鉴权(身份验证)以及实现 Cookie、JWT、自定义三种鉴权 (含源码解析)

什么是鉴权&#xff08;身份验证&#xff09;&#xff1f; https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/?viewaspnetcore-8.0 定义 鉴权&#xff0c;又称身份验证&#xff0c;是确定用户身份的过程。它验证用户提供的凭据&#xff08;如用户名和…

链表的回文结构OJ

链表的回文结构_牛客题霸_牛客网对于一个链表&#xff0c;请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法&#xff0c;判断其是否为。题目来自【牛客题霸】https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId49&&tqId29370&rp1&a…