【数据结构】难度上一个台阶的二叉树实现

news2024/11/26 16:54:06

【数据结构】难度上一个台阶的二叉树实现

  • 一、什么是树和二叉树?
  • 二、目标
  • 三、实现
    • 3.1、初始化工作
    • 3.2、二叉树的前序遍历
      • 3.2.1、原理图解
      • 3.2.2、代码实现
    • 3.3、二叉树的创建
      • 3.3.1、原理解析
      • 3.3.2、代码实现
    • 3.4、二叉树的中序遍历
    • 3.5、二叉树的后序遍历
    • 3.6、二叉树的层序遍历
      • 3.6.1、原理图解
      • 3.6.2、代码实现
    • 3.7、二叉树的节点个数
      • 3.7.1、原理图解
      • 3.7.2、代码实现
    • 3.8、二叉树的叶子结点个数
      • 3.8.1、原理解析
      • 3.8.2、代码实现
    • 3.9、二叉树第k层节点个数
      • 3.9.1、原理图解
      • 3.9.2、代码实现
    • 3.10、二叉树的高度
      • 3.10.1、原理图解
      • 3.10.2、代码实现
    • 3.11、二叉树的查找值为X的节点
      • 3.11.1、原理图解
      • 3.11.2、代码实现
    • 3.12、判断二叉树是否为完全二叉树
      • 3.12.1、原理图解
      • 3.12.2、代码实现
    • 3.13、二叉树的销毁
      • 3.13.1、原理解析
      • 3.13.2、代码实现

一、什么是树和二叉树?

“树”在现实生活中大家都知道是什么,但在计算机界中却另有所指,指的是一种数据结构。
理论部分我还是引用百度百科上对于数据结构树的简介:

是一种数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。
————————————————引自百度百科

此外,树还有一些概念:

在这里插入图片描述
节点的度: 一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点: 度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
非终端节点或分支节点: 度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
双亲节点或父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点: 一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点: 具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度: 一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次: 从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度: 树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点: 双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先: 从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙: 以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林: 由m(m>0)棵互不相交的树的集合称为森林;

其实对于每一棵树,都可以分为根节点和若干棵子树,每一棵子树也可以被分为根节点和若干棵子树,所以树是一种递归定义的数据结构。
而恰好也是因为树是一种递归定义的数据结构,在我们在我们后面的实现当中,二叉树的各种接口几乎都是使用递归来实现的。

而在树中,有一种特殊的数被应用的非常广泛,那就是二叉树。
所谓的二叉树指的是度最大为2的树,也就是对于一个根节点来说最多有两个子节点,可以只有一个也可以一个没有。
在这里插入图片描述
在二叉树中也有两个比较特殊的二叉树:

  1. 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
    说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  2. 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
    的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
    应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
    在这里插入图片描述

其实对于树和二叉树还有很多的性质和公式,但这些在我们的实现中都用不到,想要实现一颗二叉树,我们只需要死死地抓住“递归”这个性质即可。
好,接下来就让我们进入二叉树的实现吧。

二、目标

今天要实现的二叉树主要有以下这些接口:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* tree, int* pi);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树的高度
int BinaryTreeHeight(BTNode* root);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);

三、实现

3.1、初始化工作

开始之前我们得先定义好二叉树的节点类型,很简单,只需要定义两个左右子树的指针和一个值域即可:

// 重定义数据类型
typedef char BTDataType;

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

3.2、二叉树的前序遍历

之所以要先讲前序遍历而不是先讲二叉树的创建,是因为后面的创建也要用到前序遍历的思路,所以先讲前序遍历的思路会比较好一点儿。

3.2.1、原理图解

前序遍历即先访问根节点,然后依次是左子树和右子树,那我们应该怎么用代码实现呢?
记得我说过实现二叉树只需要死死地抓住“递归”这个性质,所以前序遍历当然是用递归实现的啦。

其实对于每一颗二叉树,我们都可以抽象的把它看成是三个点,即根节点、左子树、右子树:
在这里插入图片描述
而至于,左子树和右子树到底包含着什么内容(到底是空还是也包含了左子树和右子树),我们可以暂时不管,想象在访问到它们的时候就会自动展开,这也正是递归的精髓所在。

所以对于前序遍历,我们就是先访问根节点(打印),然后再递归地访问左子树和右子树(左在上右在下)。
对于左子树和右子树的递归结果我们可以暂时不管,它底层会自动处理。
而遇到空节点,我们可以直接返回,或者也可以打印出一个空(NULL)。

3.2.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

void BinaryTreePrevOrder(BTNode* root) {
    if (NULL == root) {
        return;
    }
    printf("%c", root->val);
    BinaryTreePrevOrder(root->left);
    BinaryTreePrevOrder(root->right);
}

3.3、二叉树的创建

其实通过前序遍历序列来创建二叉树在牛客上就有一道题:KY11 二叉树遍历和这里的是一模一样的。
感兴趣的朋友可以去做做。

3.3.1、原理解析

对于二叉树的遍历,其实前中后序的实现都很容易实现,但是对于二叉树的创建,就只有前序创建是最方便的。
因为这就像是单链表一样,如果你先创建了后面的节点,那你就找不到前面的节点了。二叉树也是如此,如果你先创建了左子树或者是右子树,那你怎么找得到根节点呢?
而如果非要用中序或者是后序实现,那也只能单链表那样保存前一个节点的位置,这里用递归实现的话就是在递归的时候再传入一个根节点的指针。但这样做的话就太麻烦了。

所以对于二叉树的创建,我们最好的就是用前序的方法,先创建根节点创建左子树和右子树,最后在返回根节点的指针即可。
因为我们这里是通过前序遍历的字符串来创建二叉树的,所以我们要传入一个序列的字符串,并且传入一个下标。
但因为局部变量的作用域只是在函数内,所以我们的这个下标要以指针的形式传入,这是因为指针的指向是唯一的,所以我们在任何一次递归中对于指针指向的整数的操作都是累加在一个变量上的。这也就避免了下标出错的情况。

3.3.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* tree, int* pi) {
    if (NULL == tree) {
        return NULL;
    }
    if ('#' == tree[*pi]) {
        (*pi)++;
        return NULL;
    }
    BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    if (NULL == root) {
        perror("malloc fail!\n");
        exit(-1);
    }
    root->val = tree[*pi];
    (*pi)++;
    root->left = BinaryTreeCreate(tree, pi);
    root->right = BinaryTreeCreate(tree, pi);
    return root;
}

序列中的‘#’表示空。

3.4、二叉树的中序遍历

相信有了前面前序遍历的实现,再来理解中序和后序也就没什么问题了,不同之处也就是访问的先后不一样,即打印的位置不一样。

中序遍历的访问就是放在,左子树和右子树的递归结果中间,而后序就是放在两者之后:

void BinaryTreeInOrder(BTNode* root) {
    if (NULL == root) {
        return;
    }
    BinaryTreeInOrder(root->left);
    printf("%c", root->val);
    BinaryTreeInOrder(root->right);
}

3.5、二叉树的后序遍历

void BinaryTreePostOrder(BTNode* root) {
    if (NULL == root) {
        return;
    }
    BinaryTreePostOrder(root->left);
    BinaryTreePostOrder(root->right);
    printf("%c", root->val);
}

3.6、二叉树的层序遍历

与前中后序遍历不同,前面三个遍历都是用递归来完成了,唯独这个层序遍历不是用递归完成的,层序遍历的完成需要借助队列,然后通过循环的方式来完成。

3.6.1、原理图解

层序遍历就是从上至下,逐层访问,并且方向是从左往右,例如下面的这棵二叉树:
在这里插入图片描述
对它进行层序遍历的结果就是:
在这里插入图片描述
想要实现层序遍历,我们得要借助队列的先进先出规则,将各个节点的指针压入队列,只有这样我们才能实现“不依赖左子树和右子树的走向”来遍历二叉树,具体看下面的分析:
当只有三个节时:
在这里插入图片描述
首先我们现将根节点入队:
在这里插入图片描述
然后再将队头元素出队,并判断该节点的左右子树是否有空,将不为空的节点入队,这里的明显不为空,所以我们将左右子树的根节点都入队:
在这里插入图片描述
最后因为左右子树之后就没有节点了,所以我们依次将左右节点出队,当队列为空时,遍历就结束了:
在这里插入图片描述
所以,这可只有三个节点的二叉树的层序遍历序列就为:“ABC”。
有的朋友可能就认为,这不是和前序遍历一样吗?

其实这就理解错了,我们决不能用个例来分析全局,加入我再增加一个节点:
在这里插入图片描述
那结果就和前序遍历不一样了,因为在将B节点入队后不是先将D节点入队,而是先将C节点入队,在弹出B节点时才将D节点入队:
在这里插入图片描述
所以最后的遍历序列是:“ABCD”这就和前序不一样了。

重复执行上面的操作,我们可以报次的一个规律是:对于任何一颗二叉树及其子树,我们都是先将它的左子树的左右子树入队,再将它的右子树的左右子树入队,当然空节点就不需要入队了。
这样就完成了层序遍历。

3.6.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

void BinaryTreeLevelOrder(BTNode* root) {
    if (NULL == root) {
        printf("NULL\n");
        return;
    }
    Queue queue;
    QueueInit(&queue);
    QueuePush(&queue, root);
    BTNode* curRoot = NULL;
    while (!QueueEmpty(&queue)) {
        curRoot = QueueFront(&queue);
        QueuePop(&queue);
        printf("%c", curRoot->val);
        if (curRoot->left) {
            QueuePush(&queue, curRoot->left);
        }
        if (curRoot->right) {
            QueuePush(&queue, curRoot->right);
        }
    }
    QueueDestroy(&queue);
    printf("\n");
}

3.7、二叉树的节点个数

3.7.1、原理图解

其实统计节点个数也是有点像前序遍历,当然也是用递归来实现。
同样的我们还是可以把所有的二叉树都抽象成只有三个节点的形式:
在这里插入图片描述
所以在每次调用中,我们就只需要计算当前节点(根节点),的节点数,然后左右子树的节点数就交给递归来完成啦,想象递归就是一个干苦力的,他会老老实实的帮我们完成其他任务,我们只需要关心自己的那一点任务即可。
对于根节点,如果根节点为空,那我们就返回0,否则我们就返回左右子树节点数的递归结果再加上根节点的节点数1即可。

3.7.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

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

3.8、二叉树的叶子结点个数

3.8.1、原理解析

其实统计叶子结点的个数和上面的统计节点个数思路类似,也是一个前序的思路,其实只是统计节点个数变一下型就好了。
叶子结点,即左右子树都为空的节点,那我们的返回条件就要相应的更改了,只有在左右子树都为空的情况下我们才返回1,当然空节的话也是返回0。而最后的递归返回就不用再加1了,因为很明显,如果一个根节点的左右子树有一个不为空,那这个根节点就一定不为叶子结点了。

3.8.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

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

3.9、二叉树第k层节点个数

3.9.1、原理图解

解决这个接口其实我们得用到一个等价思维,因为第k层的位置是不变的,而我们没往下走一层,都会离第k层越来越近。
假设根节点为第1层,所以我们要统计第k层的节点个数,就可以转化成统计第2层的第k - 1层的节点个数、统计第3层的第k - 2层的节点个数……
一次类推,当k递减到1时候,说明我们已经到达了第k层,也就不用再往下走了。
在这里插入图片描述
返回条件是:
如果根节点为空,无论是第几层都是返回0。
如果k为1并且根不为空,返回1.
如果跟不为空且k还不为1,递归返回左子树和右子树的第k - 1层统计结果。

3.9.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

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

3.10、二叉树的高度

二叉树的高度即二叉树的最大深度、二叉树的层数。

3.10.1、原理图解

这里的计算高度其实也使用了一个前序的思路,极限计算根节点的高度,如果根节点为空就直接返回0,再计算左右子树的高度。
只不过这计算的左右子树的高度要进行二选一,选出其中较大的高度再加上根的高度1,因为二叉树的高度即为二叉树的最大深度,我们很有可能遇到的是两颗子树的高度不相同的情况:
在这里插入图片描述
所以我们每次的递归中到要选择的是左右子树中高度最大的那个。

3.10.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

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

3.11、二叉树的查找值为X的节点

3.11.1、原理图解

节点的查找虽然说使用前中后的思路都行,还是优先使用前序的思路会比较好,因为如果查找同一个节点,使用前序思路的递归层数要比使用中序或后续的层数要少一点,而若是使用的是中序或后序,可能就要递归到最底层然后回归了才能找到。
例如下面这棵树,我们想要找到值为B的节点:
在这里插入图片描述
如果我们使用的是前序的思路,因为前序一进函数就进行比较,所以我们这里最多只用递归两层就能找到了:
在这里插入图片描述
而使用中序的话,那我们就要先递归到最底层,然后返回才能找到:
在这里插入图片描述
很明显就多了很多递归,中序尚且如此,后续就更不用说了。
所以查找我们也是优先采用先序。

3.11.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
    if (NULL == root) {
        return NULL;
    }
    if (root->val = x) {
        return root;
    }
    BTNode* leftNode = BinaryTreeFind(root->left, x);
    if (leftNode) { // 为空就表示找不到,就不返回
        return leftNode;
    }

    BTNode* rightNode = BinaryTreeFind(root->right, x);
    if (rightNode) {
        return rightNode;
    }
    return NULL; // 左右子树都找不到,就返回空
}

3.12、判断二叉树是否为完全二叉树

3.12.1、原理图解

其实判断完全二叉树的思路和层序遍历的思路如出一辙,只不过结束的条件不用罢了。
我们先来回顾一下完全二叉树的概念:

完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

抛这些冗长的概念不谈,我们就把目光放在“当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应”这个条件上。
那么什么才是一一对应呢?
我们可以先拿一个满二叉树和完全二叉树编上号对比一下:
在这里插入图片描述
如上图,可以理解为完全二叉树的各个节点的编号的位置和满二叉树的位置是可以重合的,即某一个在在满二叉树的第几层第几子树的左子树还是右子树,在完全二叉树中也是一样的。
所以在完全二叉树中决不能出现这种情况:
在这里插入图片描述
就是最底层的叶子结点不是并排在一起,中间空了一位,这就满二叉树对不上了。
其实也可以总结为,若完全二叉树的最底层的叶子结点未满,那么最后一个叶子结点一定是左孩子。

所以这也正是完全二叉树能用层序遍历的思路实现的原因,因为最后一层的叶子结点是连续的,所以当我们层序遍历在遇到空的时候,再往后走一定也都是空了:
在这里插入图片描述
而后面只要在遇到一个不为空的,就可以判定出不是完全二叉树。

所以判断完全二叉树的代码也就以下出来了:

3.12.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

bool BinaryTreeComplete(BTNode* root) {
    Queue queue;
    QueueInit(&queue);
    QueuePush(&queue, root);
    BTNode* curRoot = NULL;
    while (!QueueEmpty(&queue)) {
        curRoot = QueueFront(&queue);
        if (NULL == curRoot) {
            break;
        }
        QueuePop(&queue);
        QueuePush(&queue, curRoot->left);
        QueuePush(&queue, curRoot->right);
    }

    while (!QueueEmpty(&queue)) {
        curRoot = QueueFront(&queue);
        if (curRoot != NULL) {
            QueueDestroy(&queue);
            return false;
        }
        QueuePop(&queue);
    }
    QueueDestroy(&queue);
    return true;
}

3.13、二叉树的销毁

3.13.1、原理解析

二叉树的销毁最适合用的是后序的思路,因为先将根节点销毁了,那你就找不到左右子树了,这样还要额外的再保存左右子树的指针,太麻烦了。
而使用后序,因为递归的回归性,在销毁了后面的节点之后就会返回到上一个节点。所以这也就很方便,不用我们再额外的保存节点了。

3.13.2、代码实现

有了以上的思路,那我们写起代码来也就水到渠成了:

void BinaryTreeDestory(BTNode* root) {
    if (NULL == root) {
        return;
    }
    BinaryTreeDestory(root->left);
    BinaryTreeDestory(root->right);
    free(root);
}

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

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

相关文章

K8S系列文章之快速入门K8S

搭建K8S环境 前置 目前生产部署Kubernetes 集群主要有两种方式: kubeadm Kubeadm 是一个K8s 部署工具,提供kubeadm init 和kubeadm join,用于快速部署Kubernetes 集群。 官方地址:https://kubernetes.io/docs/reference/setu…

ChatGPT出来后,为什么老有人想让我们程序员失业?

🐱 个人主页:不叫猫先生,公众号:前端舵手 🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2,前端领域优质作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步…

【LeetCode每日一题】——1248.统计「优美子数组」

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 滑动窗口 二【题目难度】 中等 三【题目编号】 1248.统计「优美子数组」 四【题目描述】 给…

NVIDIA NCCL 源码学习(十)- 多机间ncclSend和ncclRecv的过程

先回忆一下单机的执行流程,用户执行ncclSend之后通过ncclEnqueueCheck将sendbuff,sendbytes,peer等信息保存到了comm->p2plist中;然后执行ncclGroupEnd,如果发现channel没有建立到peer的链接则先建链,然…

深入理解设计原则之接口隔离原则(ISP)

系列文章目录 C高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C并发线程编程 LSP:接口隔离原则 系列文章目录1、接口隔离原则的定义和解读2、案例解读3、如何判断一个接口是否符合接口隔离原则?小结 1、接口隔离原则的定义和解读…

网络工程师一定要会关键技能:如何进行IP子网划分?

对于所有从事IP网络方面工作的工程师来说,进行IP子网划分操作属于一个必备的关键技能,也属于必须掌握的专业内容;但对于初学者来说,真正理解IP子网划分的概念也是一件相当困难的事情。 怎样才能正确地进行子网划分操作 IP子网划…

影响现代办公室隔断设计的因素有哪些,办公室隔断设计方案要求

影响现代办公室隔断设计的因素有哪些 1. 办公空间的用途和功能要求 2. 员工数量及工作场所的布局 3. 设计风格和企业文化要求 4. 预算和材料选择 5. 环保节能和安全性要求 办公室隔断设计方案要求: 1. 合理利用空间,满足办公室的功能需求 2. 设计…

Creepypastsa VoxEdit 竞赛

召唤所有恐怖都市传闻爱好者。 通过 Creepypasta VoxEdit 竞赛,潜入黑暗领域,并释放你们的创造力!踏入阴森恐怖的神秘世界,把你最可怕的噩梦变成现实! 设计终极的 Creepypasta 体素资产 你是恐怖大师吗?是一…

RHCE 作业三

1.基于域名访问网站 [rootserver ~]# setenforce 0 [rootserver ~]# systemctl stop firewalld [rootserver ~]# systemctl disable firewalld [rootserver ~]# yum install httpd -y [rootserver ~]# systemctl start httpd [rootserver ~]# syst…

npm发包/发布群组过程记录

目录 创建群组 本地发包 1、初始化本地的包到群组 2、 登录npm账号 3、设置公开库 4、发布到npm上 5、升级更新发布 创建群组 打开npm的网站,登录个人账户密码--->点击右侧上方个人的头像--->在下拉框点击【add Organization】 接下来会出现一个添加群…

C语言 打印杨辉三角

杨辉三角的前五行如下&#xff1a; 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1、首先我们可以定义定义一个5行5列的二维数组 代码&#xff1a; int main() {int arr[5][5] { 0 };初始化int i 0;int j 0;//循环输出for (i 0; i < 5; i) {for (j 0; j <5; j) {printf("…

华为OD机试之寻找相同子串(Java源码)

寻找相同子串 题目描述 给定一个字符串s&#xff0c;最多只能进行一次变换&#xff0c;返回变换后能得到的最小字符串&#xff08;按照字典序进行比较&#xff09;。 变换规则&#xff1a;交换字符串中任意两个不同位置的字符。 输入描述 一串小写字母组成的字符串s 输出描述 …

华硕天选FA506IU(R7 4600h)电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板华硕天选FA506IU 处理器AMD Ryzen 7 4600h已驱动 内存8GB DDR4 3200MHz已驱动 硬盘东芝 KXG5AZNV256G Windows 10已驱动 显卡集成显卡…

基于MapReduce的京东20年口红数据的分析与实现

基于MapReduce的京东20年口红数据的分析与实现 文章目录 基于MapReduce的京东20年口红数据的分析与实现一、前言二、数据可视化1、安装Python和Flask框架2、创建Flask应用程序3、创建基于Echarts的网页展示代码4、最终结果 三、最后我想说 一、前言 这是一个利用HadoopMapRedu…

Java设计模式(二)

系列文章目录 里氏替换原则 开闭原则 文章目录 系列文章目录前言一、里氏替换原则1.OO中的继承性的思考和说明2.里氏替换原则基本介绍 二、开闭原则1.开闭原则基本介绍 总结 前言 大家好呀&#xff0c;欢迎来到柚子的博客~让我们一起成长吧o(&#xffe3;▽&#xffe3;)ブ 提…

二、浏览器广告屏蔽插件_Adblock Plus

1、浏览器广告 在浏览器浏览内容时&#xff0c;有广告无可厚非&#xff0c;但有些网页做的太过分了。如广告影响了正常网页的浏览&#xff1b;广告内容低俗恶心。特推荐以下浏览器广告屏蔽插件。 2、浏览器广告屏蔽插件 2.1、Adblock Plus 简介 (1)这里推荐Adblock Plus插件…

有哪些代码调试工具推荐? - 易智编译EaseEditing

下面是一些常用的代码调试工具&#xff0c;它们可以帮助开发人员识别和解决代码中的问题&#xff1a; 调试器(Debugger)&#xff1a; 调试器是一种强大的工具&#xff0c;用于在代码执行过程中逐行执行和检查代码。它允许您设置断点、观察变量的值、跟踪函数调用和返回等。 …

通义千悟-阿里通义千问系列

【通义听悟】我发现了一个宝藏产品&#xff0c;推荐给你&#xff5e; https://tingwu.aliyun.com/u/T1YJJcJcc0030c30 工作学习AI助手&#xff0c;依托大模型&#xff0c;为每一个人提供全新的音视频体验。点击链接立即注册&#xff0c;公测期免费体验。 阿里的通义千问系列 …

Spring Boot如何实现分布式任务调度?

Spring Boot如何实现分布式任务调度&#xff1f; 随着互联网的快速发展&#xff0c;越来越多的企业开始将自己的业务迁移到分布式系统中。在这种情况下&#xff0c;分布式任务调度变得尤为重要。对于分布式系统中的每个任务来说&#xff0c;它需要在多个节点上定时执行&#x…

VMware虚拟机最新详细安装保姆级教程(2023年新版教程)

VMware最新详细安装保姆级教程&#xff08;2023年新版教程&#xff09; 大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大…