数据结构与算法:链式二叉树

上一篇文章我们结束了二叉树的顺序存储,本届内容我们来到二叉树的链式存储!

链式二叉树

  • 1.链式二叉树的遍历
    • 1.1二叉树的前序,中序,后序遍历
    • 1.2 三种遍历方法代码实现
  • 2. 获取相关个数
    • 2.1获取节点个数
    • 2.2获取叶节点个数
    • 2.3 获取树的高度
    • 2.4 计算第K层节点个数
  • 3.寻找某个节点
  • 4.用前序遍历建造一个二叉树
  • 5.二叉树的销毁
  • 6.链式二叉树的层序遍历
  • 7.判断是否为完全二叉树

1.链式二叉树的遍历

首先我们定义二叉树节点内容:

typedef int BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinartTreeNode* left;struct BinartTreeNode* right;
}TreeNode;

每个节点包含两个指针,指向左子树和右子树
在这里插入图片描述
左右子树的节点又可以细分为:根,左子树,右子树

1.1二叉树的前序,中序,后序遍历

前序遍历、中序遍历和后序遍历。这些遍历方式指的是节点访问的顺序

  • 前序遍历:在前序遍历中,我们首先访问根节点,然后递归地进行左子树的前序遍历,接着递归地进行右子树的前序遍历
  • 中序遍历:中序遍历中,我们首先递归地进行左子树的中序遍历,然后访问根节点,最后递归地进行右子树的中序遍历
  • 后序遍历:后序遍历中,我们首先递归地进行左子树的后序遍历,然后递归地进行右子树的后序遍历,最后访问根节点。

我们以一颗树为例来展开讨论

在这里插入图片描述
首先讨论前序遍历

在前序遍历中,我们首先访问根节点,然后是左子树,最后是右子树
对于上述树的前序遍历,遍历顺序将是:

  • 访问根节点 1
  • 访问左子节点 2
  • 访问左子节点 2 的左子节点 3
  • 左子节点3的左子树为空,返回到3
  • 访问完3的左子树访问3的右子树,为空,返回到3
  • 2的左子树访问完,访问2的右子树,为空,返回到2
  • 1的左子树访问完成,回到1访问右子树4
  • 访问4的左子树5,再访问5的左,5的左为空,则返回,访问5的右,再返回到5
  • 4的左子树遍历完成,访问右子树6,6的左右都为空,访问结束

如果用N来代表空,我们可以表示出访问顺序:

1 2 3 N N N 4 5 N N 6 N N

接下来讨论中序遍历

在中序遍历中,我们首先遍历左子树,然后访问根节点,最后遍历右子树

遍历顺序

  • 首先访问左子树,1的左子树2,再到2的左子树3,3的左子树为NULL,访问空,返回到三,再访问节点3,根访问后到右子树NULL,所以开始访问顺序为:
N 3 N
  • 访问完2的左子树,再按照顺序访问根节点2,再访问2的右子树N
N 3 N 2 N
  • 访问完1的左子树,访问节点1,再访问1的右子树,而右子树4部分优先访问左子树,所以先访问到5的左子树NULL,再访问5节点和5的右子树NULL
N 3 N 2 N 1 N 5 N
  • 访问完4的左子树,访问根节点4,再访问4的右子树,右子树优先访问左子树部分,即6的左子树NULL,再到6节点,最后访问6的右子树NULL
N 3 N 2 N 1 N 5 N 4 N 6 N

最后我们来看后序遍历

  • 首先访问1的左子树,在子树中,首先访问 2 的左子树,3的左子树为空NULL,访问后访问3的右子树NULL,再访问根节点3
N N 3
  • 访问完2的左子树后,访问2的右子树NULL,再访问节点2
N N 3 N 2
  • 访问完1的左子树,再访问1的右子树,子树中,先访问4的左子树,5组成的子树部分,先访问5的左子树NULL,再访问5的右子树NULL,最后访问根节点5
N N 3 N 2 N N 5
  • 4的左子树访问完成后,访问4的右子树,子树部分6,先访问6的左右NULL,再访问根节点6
N N 3 N 2 N N 5 N N 6
  • 4的左右子树访问完成,访问根节点4
  • 1的左右子树访问完成,访问根节点1
N N 3 N 2 N N 5 N N 6 4 1

1.2 三种遍历方法代码实现

接下来我们硬代码构造一个上图的树,来完成我们三种遍历方式的代码

TreeNode* CreatTreeNode(int x)
{TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));node->data = x;node->left = NULL;node->right = NULL;return node;
}
TreeNode* FCreatTree()
{TreeNode* node1 = CreatTreeNode(1);TreeNode* node2 = CreatTreeNode(2);TreeNode* node3 = CreatTreeNode(3);TreeNode* node4 = CreatTreeNode(4);TreeNode* node5 = CreatTreeNode(5);TreeNode* node6 = CreatTreeNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

在上述三种方法的讨论中我们可以知道,三种遍历是用递归方法来完成的

前序遍历,首先访问当前节点(根节点),然后递归地进行左子树的前序遍历,最后递归地进行右子树的前序遍历

代码如下:

void PrevOrder(TreeNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
  • 如果遇到节点为空,则打印N 并返回
  • 注意:这里是递归进行的,返回是指返回到上一层函数
  • printf("%d ", root->data);首先访问根节点
  • PrevOrder(root->left);访问左子树,进入左子树里面又分为先访问根节点,再访问左子树
  • PrevOrder(root->right);访问完左子树后,访问右子树

我们可以画出递归展开图来加深理解

在这里插入图片描述
由1先访问根节点1,再到左子树2,2访问完根节点,到左子树3

在这里插入图片描述
3访问左子树为空,返回到上一层函数,接着访问3的右子树
在这里插入图片描述
3函数结束,返回到上一层函数,来对2的右子树进行访问,以此类推
在这里插入图片描述
递归展开图可以更好的帮我们理解递归的过程

有了上述的讲解,中序遍历和后续遍历则变得很简单了

中序遍历代码:

void InOrder(TreeNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}
  • InOrder(root->left);先访问左子树
  • printf("%d ", root->data);左子树访问完后访问根节点
  • InOrder(root->right);最后访问右子树

后序遍历代码:

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

代码测试如下:
在这里插入图片描述
与刚刚讨论结果一致!

2. 获取相关个数

2.1获取节点个数

获取二叉树节点个数的核心思想是基于递归遍历整个树并计数。基于二叉树的性质,我们可以采用分治法二叉树的节点总数是其左子树的节点数加上右子树的节点数,再加上根节点本身

基本步骤:

  1. 递归的基准情况:如果当前节点为NULL,意味着到达了叶子节点的外部(空树),返回0。
  2. 递归分解问题:递归地计算左子树的节点个数和右子树的节点个数。
  3. 合并结果:将左子树的节点数和右子树的节点数相加,然后加1(代表当前节点),得到的总和就是整个二叉树的节点个数。
int TreeSize(TreeNode* root)
{if (root == NULL){return 0;}return 1 + TreeSize(root->left) + TreeSize(root->right);
}

在上面的代码中,TreeSize函数会递归地遍历整棵树每遇到一个非空节点,就通过递归计算它的左右子树的节点数,然后将这两个数加起来,并加上1(当前节点)。通过这种方式,当递归遍历完整棵树后,我们就可以得到二叉树的节点总数。

我们还可以用三目运算符简化上述代码:

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

测试代码,以上树为例:

    1/ \2   4/   / \
3   5   6

在这里插入图片描述

2.2获取叶节点个数

获取二叉树中叶节点(叶子节点)个数的思路与获取节点总数相似,但关注的是那些没有子节点的节点。叶子节点定义为没有左右子树的节点。基于递归的方法,我们可以遍历整棵树,并在遇到叶子节点时对计数器进行增加。

  1. 递归的基准情况:如果当前节点为NULL,意味着到达叶子节点的外部(空树),直接返回0。
  2. 识别叶子节点:如果当前节点既没有左子树也没有右子树,意味着它是一个叶子节点,因此返回1
  3. 递归分解问题:如果当前节点不是叶子节点,递归地计算左子树的叶子节点个数和右子树的叶子节点个数。
  4. 合并结果:将左子树的叶子节点数和右子树的叶子节点数相加,得到的和就是整个二叉树的叶子节点个数
int TreeleafSize(TreeNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}else{return TreeleafSize(root->left) + TreeleafSize(root->right);}
}

对于每一个节点,算法检查是否它是一个叶子节点:如果是,返回1;否则,继续在它的左右子树中寻找叶子节点并计数。最终,通过对所有找到的叶子节点进行计数,我们可以得到整个二叉树的叶子节点数。

测试:
在这里插入图片描述

2.3 获取树的高度

获取二叉树的高度(或深度)的基本思路是遵循分治法原则,即递归地计算二叉树每个节点的左右子树的高度,并从中选择最大的一个,然后加1(当前节点在路径上增加的高度)来得到以该节点为根的子树的总高度。树的总高度即是从根节点到最远叶子节点的最长路径上的节点数。

  1. 递归基准:如果当前的节点为NULL,意味着到达了叶子节点的外部(空树),其高度为0。
  2. 递归地检查:递归地计算左子树的高度和右子树的高度。
  3. 选择最大的子树高度并加上当前节点:从左子树和右子树的高度中选择更大的一个,然后加1(当前节点本身)来得到整个子树的高度。

首先我们来看这串代码

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

这串代码有很大的缺陷

三目运算符对 TreeHeight(root->left)TreeHeight(root->right) 各调用了两次。对于每一个非叶子节点,它的左右子树高度都被计算了两遍。这种重复计算对于简单的树结构可能不明显,但对于较大的二叉树来说,就会导致大量不必要的计算,进而降低程序的运行效率。

改进:

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

2.4 计算第K层节点个数

计算二叉树第 (k) 层的节点个数同样可以采用递归的方法来解决。这个问题可以拆分为:计算某节点左子树的第 (k-1) 层节点个数和右子树的第 (k-1) 层节点个数的和,因为从当前节点的视角看,它的子树的层数都少了一层。

int TreeKcount(TreeNode* root, int k)
{if (root == NULL||k<1){return 0;}if (k == 1){return 1;}return TreeKcount(root->left, k - 1) + TreeKcount(root->right, k - 1);
}

当达到第 1 层时,若二叉树非空,则第1层的节点数肯定是1(即当前节点),因为每次递归减少1层,直到递归到目标层级

  • 当我们要求的是第1层的节点数量时(即树的根节点),且树非空,那么节点数量为1。
  • 当 k 值大于1时,我们通过递归调用来求解左右子树在第 (k-1) 层的节点数量,然后将它们相加得到答案。
  • 通过递归减少 k 的值,我们逐渐深入到树中更深的层次,直到 k 减至1,这时我们就到达了目标层,并可以直接计算节点数量

测试代码:

    1/ \2   4/   / \
3   5   6

在这里插入图片描述

3.寻找某个节点

在二叉树中寻找值为 x 的节点可以通过递归方法来实现。本质上,这是一种深度优先搜索(DFS)策略。思路是从根节点开始,先检查根节点的值是否等于 x,如果是,就返回当前节点。如果不是,就先在左子树中递归查找,如果左子树中找到了,就直接返回结果;如果左子树中没有找到,再在右子树中查找。如果整棵树都没找到,最终返回 NULL 表示不存在值为 x 的节点。

TreeNode* TreeFind(TreeNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;TreeNode* tmp1 = TreeFind(root->left, x);TreeNode* tmp2 = TreeFind(root->right, x);if (tmp1){return tmp1;}if (tmp2){return tmp2;}return	NULL;
}

假设我们寻找3

        1/ \2   4/   / \3   5   6
  1. 开始于根节点(值为1):首先检查根节点的值,它是1,不等于我们要找的值3。

  2. 递归搜索左子树(值为2的节点):进入下一层,移至节点值2,检查这个节点,它也不是我们要找的值。

  3. 递归搜索左子树的左子树(值为3的节点):继续递归进入下一层,移至节点值3,这个节点是我们要找的,因为它的值等于3。函数返回这个节点的地址

这里简单解释一下这段代码的含义:

if (tmp1) {return tmp1;
}
if (tmp2) {return tmp2;
}
return NULL;
  • tmp1tmp2分别是对当前节点的左子树和右子树递归执行TreeFind函数后得到的结果。tmp1是左子树中查找的结果,而tmp2是右子树中查找的结果。

  • if (tmp1)检查左子树递归查找的结果。如果tmp1不为NULL,这意味着在左子树中找到了值为x的节点,因此直接返回该节点。这里的if (tmp1)、

  • 如果tmp1为NULL,表明左子树中没有找到目标节点,程序将继续检查tmp2,即右子树递归查找的结果。同理,如果tmp2不为NULL(if (tmp2)),意味着在右子树中找到了值为x的节点,函数则返回该节点。

  • 如果两个条件判断if (tmp1)和if (tmp2)都不成立,即tmp1和tmp2都为NULL,表示在当前节点的左右子树中都没有找到值为x的节点,那么函数最终返回NULL,表示查找失败。

4.用前序遍历建造一个二叉树

在上述铺垫后,我们来做一个题:

通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

这里的#可以当做前面讲的NULL

首先我们定义函数;

TreeNode* CreatTree(BTDataType* a, int *pi);
  • 字符串"ABD##E#H##CF##G##",按照前序遍历构建二叉树,其中’A’是根节点,继续按照根-左-右的顺序解析字符串。
  • 'A'的左子节点是'B''B'的左子节点是'D''D'的左右子节点都是空("##"), 这表示’D’是叶子节点
  • 回到'B’,'B'的右子节点是'E''E'左子节点为空(由"#"表示),右子节点是’H’'H’的左右子节点也都是空
  • 回到根节点'A',它的右子节点是'C''C'的左子节点为空,右子节点是'F'。'F’的左右子节点都是空。
  • 最后,'C'的右子节点是'G',同样,'G’的左右子节点都是空

得到的树结构如下:

    A/ \B   C/ \   \
D   E   F\   \H   G

代码如下:

TreeNode* CreatTree(char* a, int* pi)
{if (a[*pi] == '#'){*pi++;return NULL;}TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));if (root == NULL){perror("malloc fail");exit(-1);}root->data = a[(*pi)++];root->left = CreatTree(a, pi);root->right = CreatTree(a, pi);return root;
}

这里TreeNode的数据类型需要从int 改为char,我们将typedef后面的int修改即可

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

5.二叉树的销毁

void DestroyTree(TreeNode* root) {if (root == NULL) {return;}DestroyTree(root->left);  // 销毁左子树DestroyTree(root->right); // 销毁右子树free(root);               // 释放当前节点
}

当你调用DestroyTree并传入树的根节点时,函数首先检查是否给定的节点(在最初的调用中,这是根节点)为NULL。如果不是,它会递归地调用自身来销毁左子树和右子树。完成这些递归调用之后,返回到最初的调用层次时,它会释放根节点本身占用的内存。

  1. 检查root是否为NULL。如果是,说明已经处理到了空子树,函数返回。
  2. 递归调用DestroyTree(root->left);销毁左子树。这将遍历到最左侧的叶子节点,沿途经过的每个节点的左子树都将被先递归调用DestroyTree处理。
  3. 递归调用DestroyTree(root->right);销毁右子树。每个节点的右子树同样会经历相同的递归处理。
  4. 最后,经过左子树和右子树的递归处理后,回到当前的节点,并使用free(root);释放掉该节点占用的内存。

尽管根节点被销毁,但原始root变量本身在函数返回后仍然存在,只是它现在成为了一个悬空指针。为了安全起见,调用完DestroyTree之后,应该手动将root设置为NULL

例如:

int main() {TreeNode* root = FCreatTree()DestroyTree(root);root = NULL; return 0;
}

如果不想手动设置为NULL,只用一个DestroyTree函数来解决,这里意味着我们需要在函数里面实现置空过程,意味着我们要修改指针,则需要传参二级指针

void DestroyTree(TreeNode** root) {if (*root == NULL) {return;}DestroyTree(&((*root)->left));DestroyTree(&((*root)->right));free(*root);*root = NULL; // 将调用方的根节点指针置为NULL
}

6.链式二叉树的层序遍历

层序遍历(Level Order Traversal)是指按照从上到下、从左到右的顺序访问二叉树的每个节点。这种遍历方式可以保证二叉树中每个节点的访问顺序正好对应其在树中的层次和位置。层序遍历通常借助于队列这一数据结构来实现。

所以要想实现层序遍历,首先得有队列的函数,在前几节内容中我们已经对其讲解,这里直接引用其代码:

void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size=0;
}
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->val = x;newnode->next = NULL;if (pq->ptail == NULL){pq->ptail = pq->phead = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}
void QueuePop(Queue* pq) {assert(pq);                     // 确保 pq 不是 NULLif (pq->phead == NULL) return;  // 如果队列为空,则没有元素可以弹出QNode* temp = pq->phead;        // 临时保存队列头部节点的地址,以便后续释放内存pq->phead = pq->phead->next;    // 更新头指针指向下一个节点free(temp);                     // 释放原头节点占据的内存temp = NULL;if (pq->phead == NULL) {        // 如果弹出之后队列变为空,则尾指针也要更新为 NULLpq->ptail = NULL;}pq->size--;  // 更新队列大小
}
QDataType QueueFront(Queue* pq)
{assert(pq);                     // 确保 pq 不是 NULLassert(pq->phead != NULL);return pq->phead->val; }
QDataType QueueBack(Queue* pq) {assert(pq != NULL); // 确保队列指针不为NULLassert(pq->ptail != NULL); // 确保队列不为空,即队尾指针不为NULL// 返回队列尾部元素的值return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}

队列在层序遍历中的使用是由于其先进先出(First In First Out, FIFO)的特性,这保证了树的节点能够按照从上到下、从左到右的顺序进行访问,这里解释为什么要用队列进行层序遍历:

  • 在层序遍历过程中,我们首先访问根节点然后是根节点的子节点(从左到右),接着是子节点的子节点,以此类推。队列能够保证节点的访问顺序正好符合这一层级结构,因为我们总是先将左子节点加入队列,然后是右子节点。当从队列中取出一个节点时,它的子节点已经按照正确的顺序排在后面

实现步骤

  • 初始化:创建一个队列,将根节点入队
  • 循环执行以下操作,直到队列为空:
  • 将队头的节点出队,并访问该节点
    1. 如果该节点有左子节点,则将左子节点入队
    2. 如果该节点有右子节点,则将右子节点入队

我们思考一下,这里队列里面放的是什么,节点还是节点的值?

这里放入的是节点,如果只放入节点的值,则找不到下一个节点,所以这里我们需要修改队列的typedef类型!

typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{QDataType val;struct QueueNode* next;
}QNode;
typedef struct Queue 
{QNode* phead;QNode* ptail;int size;
}Queue;

接下来实现代码:

void LevelOrder(TreeNode* root)
{Queue q;QueueInit(&q);if (root != NULL){QueuePush(&q, root);}while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->data);if (front->left != NULL)QueuePush(&q, front->left);if (front->right != NULL)QueuePush(&q, front->right);}QueueDestroy(&q);
}

我们来进行分析:
以最开始的那棵树为例子:

       1/ \2   4/   / \3   5   6

让我们展开遍历的过程:

  1. 开始时,队列状态:1
  2. 取出1,打印1,加入1的左子节点2和右子节点4,队列状态:2, 4
  3. 取出2,打印2,加入2的左子节点3,队列状态:4, 3
  4. 取出4,打印4,加入4的左子节点5和右子节点6,队列状态:3, 5, 6
  5. 取出3,打印3,队列状态:5, 6
  6. 取出5,打印5,队列状态:6
  7. 取出6,打印6,队列空

打印顺序:1, 2, 4, 3, 5, 6。

7.判断是否为完全二叉树

判断一棵树是否为完全二叉树,可以利用层序遍历的思想。完全二叉树的特点是,除了最后一层外,每一层都是完全填满的,且最后一层的所有节点都集中在左侧。在层序遍历过程中,如果遇到了一个节点,其有右子节点而无左子节点,那么这棵树肯定不是完全二叉树;另外,如果遇到了一个节点并非左右子节点都有,那么所有接下来遍历到的节点都必须是叶子节点。

我们再原有层序遍历代码上修改即可:

bool BinaryTreeComplete(TreeNode* root)
{Queue q;QueueInit(&q);if (root != NULL){QueuePush(&q, root);}while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);}while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front){QueuePop(&q);return false;}}QueueDestroy(&q);return true;
}

第一部分:将所有节点(包括空节点)按层序添加到队列中

  1. 首先,初始化一个队列 q。
  2. 如果根节点不为空,则将根节点添加到队列中。
  3. 接下来进入一个循环,直到队列为空。在这个循环中:
    • 从队列中弹出一个节点(记为 front)。
    • 如果 front 是 NULL,即遇到了第一个空节点,则退出循环。这是因为在完全二叉树中,一旦按层序遍历遇到空节点,其后的所有节点也应该是空的
    • 如果 front 非空,将其左右子节点(即使是空的)添加到队列中。这样做是为了保证即使是空节点也按照层序顺序排列。

第二部分:检查队列中剩余的所有节点是否都是空的

退出第一部分的循环后,理论上队列中剩下的所有节点应该都是空节点(如果是完全二叉树的话)。因此,接下来的循环会遍历队列中剩余的所有节点:

从队列中弹出一个节点(记为 front)。
如果 front 是非空的,说明在遇到第一个空节点之后还有非空节点,这违反了完全二叉树的性质。因此,函数返回 false。
如果循环顺利结束,没有遇到任何非空节点,说明这棵树是一棵完全二叉树,函数返回 true。

本节内容到此结束!!感谢大家阅读!!!
在这里插入图片描述

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

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

相关文章

《软件安装与使用教程》— Git 在Windows的安装教程

目录 Git在Windows安装教程1 获得安装包2 解压文件3 注意事项4 安装步骤4.1 运行安装程序4.2 声明许可4.3 选择安装路径4.4 选择需要安装的组件4.5 选择开始菜单4.6 选择默认编辑器4.7 选择PATH环境4.8 选择HTTPS后端传输4.9 配置行尾巴符转换4.10 配置Git Bash终端仿真器4.11 …

NLP 算法实战项目:使用 BERT 进行模型微调,进行文本情感分析

本篇我们使用公开的微博数据集(weibo_senti_100k)进行训练&#xff0c;此数据集已经进行标注&#xff0c;0: 负面情绪&#xff0c;1:正面情绪。数据集共计82718条(包含标题)。如下图&#xff1a; 下面我们使用bert-base-chinese预训练模型进行微调并进行测试。 技术交流&#x…

STC89C52串口通信详解

目录 前言 一.通信基本原理 1.1串行通信与并行通信 1.2同步通信和异步通信 1.2.1异步通信 1.2.2同步通信 1.3单工、半双工与全双工通信 1.4通信速率 二.串口通信简介 2.1接口标准 2.2串口内部结构 2.3串口相关寄存器 三.串口工作方式 四.波特率计算 五.串口初始化步骤 六.实验…

万界星空科技MES系统中的车间管理的作用

在了解mes生产管理系统的作用包括哪些方面之前&#xff0c;我们先来了解一下作为生产管理信息化的关键部分&#xff0c;车间管理系统包含哪几个部分&#xff1a;一、mes系统中的车间管理通常包含以下部分&#xff1a; 1、设备管理&#xff1a;用于监控车间内的设备状态&#xf…

新规正式发布 | 百度深度参编《生成式人工智能服务安全基本要求》

2024年2月29日&#xff0c;全国网络安全标准化技术委员会&#xff08; TC260 &#xff09;正式发布《生成式人工智能服务安全基本要求》&#xff08;以下简称《基本要求》&#xff09;。《基本要求》规定了生成式人工智能服务在安全方面的基本要求&#xff0c;包括语料安全、模…

springboot整合shiro的实战教程(二)

文章目录 整合思路1.创建springboot项目2.引入依赖3.创建Shiro Filter0.创建配置类1.配置shiroFilterFactoryBean2.配置WebSecurityManager3.创建自定义Relm4.配置自定义realm5.编写控制器跳转至index.html6.加入资源的权限控制7. 常见过滤器 登录认证实现登录界面开发controll…

目标网站屏蔽右键检查(使用开发者工具)

问题&#xff1a; 通过网络触手中想要获取某网站的数据出现&#xff1a;鼠标右击&#xff0c;或按ctrl F10 键 无反应&#xff08;也就是打不开类似谷歌的开发工具&#xff09; 问题同等与&#xff1a; 解决网页屏蔽F12或右键打开审查元素 引用&#xff1a; 作者&#xff…

C/C++ BM19 寻找峰值

文章目录 前言题目解决方案一1.1 思路阐述1.2 源码 解决方案二2.1 思路阐述2.2 源码 总结 前言 这道题第一遍做的时候题目条件没有好好的审阅&#xff0c;导致在判断边界问题的时候出了不少岔子。 我的方法是时间复杂度为O(N)的&#xff0c;官方的logN可能更好一些。我的就是简…

启发式算法:遗传算法

文章目录 遗传算法-引例交叉变异遗传算法遗传算法流程遗传算法应用遗传算法-引例 在一代代演化过程中,父母扇贝的基因组合产生新扇贝,所以遗传算法会选择两个原有的扇贝,然后对这两个扇贝的染色体进行随机交叉形成新的扇贝。迭代演化也会造成基因突变,遗传算法让新产生扇贝…

Mysql索引底层数据结构

Mysql索引底层数据结构 一、数据结构1.1.索引的本质1.2.MySQl特点1.3.索引数据结构1.4.B-Tree结构1.5.BTree结构1.6.查看mysql文件页大小&#xff08;16K&#xff09;1.7.为什么mysql页文件默认16K&#xff1f;1.8.Hash结构 二、存储引擎2.1.InnoDB2.1.1.聚集索引2.1.2.为什么建…

力扣:数组篇

1、数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合。 需要两点注意的是 数组下标都是从0开始的。数组内存空间的地址是连续的 因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址。 …

【你也能从零基础学会网站开发】Web建站之javascript入门篇 JavaScript中的表达式、运算符、位运算、递增递减

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 JavaScript…

kali当中不同的python版本切换(超简单)

kali当中本身就是自带两个python版本的 配置 update-alternatives --install /usr/bin/python python /usr/bin/python2 100 update-alternatives --install /usr/bin/python python /usr/bin/python3 150 切换版本 update-alternatives --config python 0 1 2编号选择一个即可…

【MySQL篇】 MySQL基础学习

文章目录 前言基础数据类型DDL数据库操作查询数据库创建数据库删除数据库使用数据库 DDL表操作创建表查询表修改表删除 DML-增删改添加数据更改数据删除数据 DQL-查询基础查询条件查询聚合函数分组查询排序查询分页查询编写顺序 DML-用户及权限用户管理权限控制 函数字符串函数…

挑战杯 基于设深度学习的人脸性别年龄识别系统

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习机器视觉的…

浅谈2024 年 AI 辅助研发趋势!

目录 ​编辑 引言 一、AI辅助研发现状 1. 技术发展 2. 工具集成 3. 应用场景 二、AI辅助研发趋势 1. 更高的自动化程度 2. 更高的智能化程度 3. 更多的领域应用 4. 更高的重视度 三、结论 四. 完结散花 悟已往之不谏&#xff0c;知来者犹可追 创作不易&#xff…

(南京观海微电子)——I3C协议介绍

特点 两线制总线&#xff1a;I2C仅使用两条线——串行数据线&#xff08;SDA&#xff09;和串行时钟线&#xff08;SCL&#xff09;进行通信&#xff0c;有效降低了连接复杂性。多主多从设备支持&#xff1a;I2C支持多个主设备和多个从设备连接到同一总线上。每个设备都有唯一…

017-$route、$router

$route、$router 1、$route2、$router 1、$route $route 对象表示当前的路由信息&#xff0c;包含了当前 URL 解析得到的信息。包含当前的路径&#xff0c;参数&#xff0c;query对象等。 使用场景&#xff1a; 获取路由传参&#xff1a;this.$route.query、this.$route.par…

【布局:1688,阿里海外的新筹码?】1688重新布局跨境海外市场:第一步开放1688API数据采集接口

2023年底&#xff0c;阿里巴巴“古早”业务1688突然成为“重头戏”&#xff0c;尤其宣布正式布局跨境业务的消息&#xff0c;一度引发电商圈讨论。1688重新布局跨境海外市场&#xff1a;第一步开放1688API数据采集接口 2023年11月中旬&#xff0c;阿里财报分析师电话会上&…

VUE——v-cloak指令

VUE——v-cloak指令 属性选择器&#xff0c;可以控制vue实例化完成前的dom样式 功能&#xff1a;利用vue实例化后v-cloak属性会消失&#xff0c;设置其样式 官网介绍 没用前效果&#xff1a;当vue没渲染完前&#xff0c;界面效果会看到{{aboutCloak}}字符&#xff0c;影响用户…