当前位置: 首页 > news >正文

数据结构和算法——用C语言实现所有树形结构及相关算法

文章目录

  • 前言
  • 树和森林基础概念
  • 二叉树
    • 二叉树的遍历
    • 二叉树的构造
    • 树和森林与二叉树之间的转化
    • 树和森林的遍历
  • 满二叉树
  • 完全二叉树
  • 线索二叉树
    • 线索二叉树的构造
    • 寻找前驱和后继
    • 线索二叉树的遍历
  • 最优二叉树(哈夫曼树)
    • 哈夫曼树的构造
    • 哈夫曼编码
  • 二叉排序树(BST)
    • 二叉排序树的插入
    • 二叉排序树的构造
    • 二叉排序树的查找
    • 二叉排序树的删除
  • 平衡二叉树(AVL)
    • 平衡二叉树失衡调整
    • 平衡二叉树的插入
    • 平衡二叉树的删除
  • 红黑树
    • 红黑树的插入
    • 红黑树的删除
  • B树
    • B树的插入
    • B树的删除
    • B树的查找
  • B+树

前言

本文所有代码均在仓库中,这是一个完整的由纯C语言实现的可以存储任意类型元素的数据结构的工程项目。

在这里插入图片描述

  • 首先是极好的工程意识,该项目是一个中大型的CMake项目,结构目录清晰,通过这个项目可以遇见许多工程问题并且可以培养自己的工程意识。
  • 其次是优秀的封装性(每个数据结构的头文件中只暴漏少量的信息),以及优秀的代码风格和全面的注释,通过这个项目可以提升自己的封装技巧:

在这里插入图片描述

  • 异常处理功能:在使用C语言编写代码的时候不能使用类似Java的异常处理机制是非常难受的,所以我也简单实现了一下。详情可看在C语言中实现类似面向对象语言的异常处理机制

在这里插入图片描述

最后也是最重要的一点,数据结构的通用性和舒适的体验感,下面以平衡二叉树为例:

  • 第一步:要想使用平衡二叉树,只需要引入其的头文件:
#include "tree-structure/balanced-binary-tree/BalancedBinaryTree.h"
  • 第二步:定义自己任意类型的数据,并构造插入数据(以一个自定义的结构体为例):
#include "tree-structure/balanced-binary-tree/BalancedBinaryTree.h"int dataCompare(void *, void *);typedef struct People {char *name;int age;
} *People;int main(int argc, char **argv) {struct People dataList[] = {{"张三", 15},{"李四", 3},{"王五", 7},{"赵六", 10},{"田七", 9},{"周八", 8},};BalancedBinaryTree tree = balancedBinaryTreeConstructor(NULL, 0, dataCompare);for (int i = 0; i < 6; ++i) {balancedBinaryTreeInsert(&tree, dataList + i, dataCompare);}return 0;
}/*** 根据人的年龄比较*/
int dataCompare(void *data1, void *data2) {int sub = ((People) data1)->age - ((People) data2)->age;if (sub > 0) {return 1;} else if (sub < 0) {return -1;} else {return 0;}
}
  • 第三步:打印一下平衡二叉树:
#include "tree-structure/balanced-binary-tree/BalancedBinaryTree.h"int dataCompare(void *, void *);void dataPrint(void *);typedef struct People {char *name;int age;
} *People;int main(int argc, char **argv) {struct People dataList[] = {{"张三", 15},{"李四", 3},{"王五", 7},{"赵六", 10},{"田七", 9},{"周八", 8},};BalancedBinaryTree tree = balancedBinaryTreeConstructor(NULL, 0, dataCompare);for (int i = 0; i < 6; ++i) {balancedBinaryTreeInsert(&tree, dataList + i, dataCompare);balancedBinaryTreePrint(tree, dataPrint);printf("-------------\n");}return 0;
}/*** 根据人的年龄比较*/
int dataCompare(void *data1, void *data2) {int sub = ((People) data1)->age - ((People) data2)->age;if (sub > 0) {return 1;} else if (sub < 0) {return -1;} else {return 0;}
}/*** 打印人的年龄* @param data*/
void dataPrint(void *data) {People people = (People) data;printf("%d", people->age);
}

打印的结果如下:

在这里插入图片描述
最后期待大佬们的点赞。

树和森林基础概念

树是由 n n n个结点组成的有限集,其中每个结点有零个或多个子结点以及零个或一个父结点,没有父结点的结点称为根结点,每一个非根结点有且只有一个父结点。除根结点外的其余结点可以分为 m m m个互不相交的有限集,其中每个有限集本身就是一棵树,这些树又称为根的子树森林就是 m m m棵不相交的树的集合,树一定是森林,但森林不一定是树。

  • 结点间的关系:A是E的祖先,E是A的子孙,A是B的双亲,B是A的孩子,有相同双亲的结点称为兄弟
  • 度:一个结点的孩子数称为该结点的度,树中结点最大的度称为树的度。度数为零的结点称为叶子结点,度不为零的结点称为分支结点
    • 树中的结点数就等于所有结点的度数之和加一。
    • 度为 m m m的树第 i i i层最多有 m i − 1 m^{i-1} mi1个结点。
  • m m m叉树:任意结点的度小于 m m m的树
  • 层次:结点的层次从根结点开始定义,在同一层的非兄弟结点互为堂兄弟。
  • 深度:结点的深度从根结点开始向下逐层累加,树的深度就是结点的最大层数。
  • 高度:结点的高度从叶子节点开始向上逐层累加,树的高度就是结点的最大层数。
    • 高度为 h h h m m m叉树至多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m1mh1个结点,最少有 h h h个结点。
    • 高度为 h h h、度为 m m m的树至少有 h + m − 1 h+m-1 h+m1个结点。
    • 具有 n n n个结点的 m m m叉树的最小高度为 ⌈ l o g m ( n ( m − 1 ) + 1 ) ⌉ \lceil log_m(n(m-1)+1)\rceil logm(n(m1)+1)⌉
  • 结点的间的路径和路径长度:结点间的路径由这两个结点之间所经过的结点序列构成;路径长度是路径上所经过的边的条数。树的路径长度为根到每个结点的路径长度之和。
  • 有序树和无序树:结点的每个子树是有序的不能互换的树称为有序树,否则就是无序树。

在这里插入图片描述

树一般有以下三种存储方式:

  • 顺序存储:双亲表示法
  • 顺序+链式存储:孩子表示法
  • 链式存储:孩子兄弟表示法

双亲表示法采用一组连续的空间存储每一个结点,同时在每个结点中增设一个伪指针,这个伪指针指向其双亲结点在连续空间中的位置:

在这里插入图片描述

struct ParentTreeNode {void *data;int parent;
};struct ParentTree {struct ParentTreeNode *nodeList;int size;int nodeCount;
};

在孩子表示法中,每个结点不再增设一个伪指针,而是增设一个存储当前结点孩子下标的链表:

在这里插入图片描述

struct ChildTreeNode {void *data;int index; //表示当前接结点在数组中的位置,仅在链表中使用struct ChildTreeNode *child;
};struct ChildTree {struct ChildTreeNode *nodeList;int size;int nodeCount;
};

孩子兄弟表示法又称为二叉树表示法,它采用二叉链表作为树的存储结构,其中每个结点包含三部分内容:结点值、指向第一个孩子结点的指针以及指向下一个兄弟结点的指针:

在这里插入图片描述

struct ChildSiblingTreeNode {void *data;struct ChildSiblingTreeNode *firstChild;struct ChildSiblingTreeNode *nextSibling;
};

二叉树

二叉树是指每个结点最多只有两棵子树且子树之间顺序不能颠倒的树,这两棵子树分别称为左子树和右子树。二叉树与度为 2 2 2的有序树的区别如下:

  • 度为 2 2 2的有序树至少有三个结点,而二叉树可以为空树。
  • 度为 2 2 2的有序树的孩子的左右次序是相对于另一个孩子而言的,如果只有一个孩子那么无需区分左右,而二叉树无论几个孩子必须区分左右。

二叉树的性质如下:

  • 二叉树在第 i i i层至多有 2 i − 1 2^{i-1} 2i1个结点,最少有 1 1 1个结点。

  • 高度为 h h h的二叉树最多有 2 h − 1 2^h-1 2h1个结点。

  • 设非空二叉树中度为 0 、 1 、 2 0、1、2 012的结点的个数分别为 n 1 、 n 2 、 n 3 n_1、n_2、n_3 n1n2n3,设树中结点的总个数为 n n n,则

    • n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n=n0+n1+n2
    • n = n 1 + 2 n 2 + 1 n=n_1+2n_2+1 n=n1+2n2+1
    • n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  • 对于任意一颗二叉树,如果对其进行编号,那么结点编号所对应的二进制可以反应从根节点到该结点的路径,其中最高位是 1 1 1,其余各位从高到低表示从根节点到第 k k k个节点的移动方向, 0 0 0表示移动到左子节点, 1 1 1表示移动到右子节点。

在这里插入图片描述

二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左向右存储完全二叉树上的结点元素,这样树中结点的序号就可以唯一反应结点之间的关系。对于不是完全二叉树的二叉树,需要填充空结点让其每个结点与完全二叉树上的结点对应。因此非完全二叉树的二叉树通常采用链式存储,链表结点至少包含三个域:数据域、左孩子指针域和右孩子指针域。还可以增加一个指针域指向其双亲。

struct BinaryTreeNode {void *data;struct BinaryTreeNode *lNode;struct BinaryTreeNode *rNode;
};

二叉树的遍历

遍历一棵二叉树要决定对根节点N、、左子树L和右子树R的访问顺序,常见的访问顺序如下:

  • 先序遍历(NLR):首先访问根结点,然后遍历左子树,最后遍历右子树。
void preOrder(BinaryTree tree, LinkedQueue preOrderList) {if (tree != NULL) {linkedQueueEnQueue(preOrderList, tree->data);preOrder(tree->lNode, preOrderList);preOrder(tree->rNode, preOrderList);}
}
  • 中序遍历(LNR):先遍历左子树,然后访问根结点,然后遍历右子树。
void inOrder(BinaryTree tree, LinkedQueue inOrderList) {if (tree != NULL) {inOrder(tree->lNode, inOrderList);linkedQueueEnQueue(inOrderList, tree->data);inOrder(tree->rNode, inOrderList);}
}
  • 后序遍历(LRN):是先遍历左子树,然后遍历右子树,最后访问根结点。
void postOrder(BinaryTree tree, LinkedQueue postOrderList) {if (tree != NULL) {postOrder(tree->lNode, postOrderList);postOrder(tree->rNode, postOrderList);linkedQueueEnQueue(postOrderList, tree->data);}
}
  • 层序遍历:根据二叉树的层次层次结构,从第一层开始自上而下、自左向右访问每一个结点。
void levelOrder(BinaryTree tree, LinkedQueue levelOrderList) {LinkedQueue queue = linkedQueueConstructor();linkedQueueEnQueue(queue, tree);while (!linkedQueueIsEmpty(queue)) {BinaryTreeNode *node = linkedQueueDeQueue(queue);linkedQueueEnQueue(levelOrderList, node->data);if (node->lNode != NULL) {linkedQueueEnQueue(queue, node->lNode);}if (node->rNode != NULL) {linkedQueueEnQueue(queue, node->rNode);}}
}

二叉树的构造

由二叉树的以下遍历序列组合可以唯一确认一棵二叉树:

  • 先序+中序
  • 后序+中序
  • 层序+中序
BinaryTree binaryTreePreInOrderConstructor(void **preOrderList, void **inOrderList, int (*compare)(void *, void *), int ps, int pe, int is, int ie) {BinaryTreeNode *root = malloc(sizeof(BinaryTreeNode));root->data = *(preOrderList + ps);int lLen, rLen;for (int i = is; i <= ie; ++i) {if (compare(*(inOrderList + i), *(preOrderList + ps))) {lLen = i - is;rLen = ie - i;break;}}if (lLen) {root->lNode = binaryTreePreInOrderConstructor(preOrderList, inOrderList, compare, ps + 1, ps + lLen, is, is + lLen - 1);} else {root->lNode = NULL;}if (rLen) {root->rNode = binaryTreePreInOrderConstructor(preOrderList, inOrderList, compare, pe - rLen + 1, pe, ie - rLen + 1, ie);} else {root->rNode = NULL;}return root;
}

如果将二叉树的某一遍历序列使用填充元素填充为满二叉树的相同遍历序列,那么也可以唯一确定一棵二叉树:

BiTree  completeLevelOrderConstruct(BiTreeElemType completeLevelOrderList[],int len,BiTreeElemType filler){LinkQueue queue= linkQueueConstruct();BiTNode * root= malloc(sizeof (BiTNode));root->data=completeLevelOrderList[0];enQueue(queue,root);for (int i = 1; !isEmpty(queue); ++i) {BiTNode * node=deQueue(queue);if(node!=NULL){if((2*1-1)>=len||(2*i+1-1)>=len){node->lChild=NULL;node->rChild=NULL;continue;}if(completeLevelOrderList[2*i-1]!=filler){node->lChild= malloc(sizeof (BiTNode));node->lChild->data=completeLevelOrderList[2*i-1];enQueue(queue,node->lChild);} else{node->lChild=NULL;enQueue(queue,NULL);}if(completeLevelOrderList[2*i+1-1]!=filler){node->rChild= malloc(sizeof (BiTNode));node->rChild->data=completeLevelOrderList[2*i+1-1];enQueue(queue,node->rChild);} else{node->rChild=NULL;enQueue(queue,NULL);}} else{continue;}}linkQueueFinalize(queue);return root;
}

树和森林与二叉树之间的转化

由于二叉树的结构简单、规律性最强,因此在处理树和森林时一般将它们转换成一个唯一的二叉树进行。由于树和二叉树都可以使用二叉链表表示,那么就可以以二叉链表做媒介来进行转化。

  • 树转换为二叉树:每个结点左指针指向它的第一个孩子,右指针指向它在树中的相邻右兄弟,由于根节点没有兄弟,所以对应的二叉树没有右子树。

  • 树转换为二叉树的画法:在兄弟结点之间加一条线;对每一个结点,只保留它与第一个孩子的连线,与其它孩子的连线全部抹掉;以树根为中心顺时针旋转四十五度。

  • 森林转换为二叉树:先将森林中的每一棵树转换为二叉树,然后将第二棵二叉树作为第一棵二叉树的右子树,再将第三棵二叉树作为第二棵二叉树的右子树···依次类推。

  • 森林转换为二叉树的画法:将森林中的每一棵树转换为二叉树;每棵树的根视为兄弟关系,在每棵树的根之间加一条连线;以第一棵树的根为中心顺时针旋转四十五度。

  • 二叉树转换为森林:将二叉树的根及左子树视为第一棵树的二叉树形式,并将右链断开;将右子树视为一个由除第一棵树外的森林转换后的二叉树,应用相同的方法,直到最后只剩一棵没有右子树的二叉树为止;最后将每棵二叉树依次转换为树就得到了森林。

树和森林的遍历

树的遍历方式如下:

  • 先根遍历:先访问根结点,再依次访问根结点的每棵子树,依次类推。其遍历序列对应二叉树的先序序列。
  • 后根遍历:先访问根结点的每棵子树,再访问根结点,依次类推。其遍历序列对应二叉树的中序序列。
  • 层序遍历:按层次访问。

森林的遍历方式如下:

  • 先根遍历:访问森林中第一棵树的根节点;先根遍历第一棵树中根结点的子树森林;先根遍历除去第一棵树之后剩余的树构成的森林;其遍历序列对应二叉树的先序序列。
  • 后根遍历:后根遍历森林中第一棵树的根结点的子树森林;访问第一棵树根结点;后根遍历除第一棵树之后剩余的树构成的森林;其遍历序列对应二叉树的中序序列。
  • 层序遍历:按层次遍历。

满二叉树

每一层的结点数都达到最大值的二叉树称为满二叉树,满二叉树的性质如下:

  • 所有叶子节点都在最后一层。
  • 一棵高为 h h h的满二叉树的结点数为 2 h − 1 2^h-1 2h1
  • 不存在度为 1 1 1的结点。
  • 若对满二叉树自上而下,自左到右从 1 1 1开始编号,那么对于编号为 i i i的结点,若有双亲则其双亲编号为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2,若有左孩子则左孩子编号为 2 i 2i 2i,若有右孩子则其编号为 2 i + 1 2i+1 2i+1

在这里插入图片描述

完全二叉树

一棵深度为 k k k的有 n n n个结点的二叉树,对树中的结点自上而下,自左到右从 1 1 1开始编号,如果编号为 i i i的结点与满二叉树中编号为 i i i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。完全二叉树的性质如下:

  • i < = ⌊ n / 2 ⌋ i<=⌊n/2⌋ i<=n/2,那么结点 i i i为分支结点,否则为叶子结点。
  • 叶子节点只可能在层次最大的两层上出现,对于最大层次中的叶子结点都依次排列在该层的最左边的位置。
  • 若有度为 1 1 1的结点,则只可能有一个,且该结点只有左孩子而没有右孩子。
  • 一但某结点 i i i为叶子结点或只有左孩子,则编号大于 i i i的结点均为叶子结点。
  • n n n为奇数,则每个分支结点都有左孩子和右孩子;若 n n n为偶数,则编号最大的分支结点只有左孩子没有右孩子,其余分支结点左孩子右孩子都有。
  • i i i为偶数时,其双亲的编号为 i / 2 i/2 i/2,它是双亲的左孩子;当 i i i为奇数时,其双亲的编号为 ( i − 1 ) / 2 (i-1)/2 (i1)/2,它是双亲的右孩子。
  • 2 i < = n 2i<=n 2i<=n时,结点i的左孩子编号为 2 i 2i 2i,否则无左孩子。
  • 2 i + 1 < = n 2i+1<=n 2i+1<=n时,结点i的右孩子编号为 2 i + 1 2i+1 2i+1,否则无右孩子。
  • 结点 i i i所在的层次为 ⌊ l o g 2 i ⌋ + 1 ⌊log₂i⌋+1 log2i+1
  • 具有 n n n个结点的完全二叉树的高度为 ⌈ l o g 2 ( n + 1 ) ⌉ ⌈log₂(n+1)⌉ log2(n+1)⌉ ⌊ l o g 2 n ⌋ + 1 ⌊log₂n⌋+1 log2n+1

在这里插入图片描述

线索二叉树

在含有 n n n个结点的二叉树中,含有 n + 1 n+1 n+1个空指针域。线索化是指利用这些空指针域存放指向结点的前驱和后继的指针,它的规则如下:

  • 如果某个结点的左孩子为空,则将空的左孩子引用指向其前驱;
  • 如果某结点的右孩子为空,则将空的右孩子引用指向其后驱。

此外还需要增加两个标志位标识指针域是指向左右孩子还是指向前驱后继,最好再添加一个指针域指向其父结点。

struct ThreadedBinaryTreeNode {void *data;ThreadedBinaryTreeNode *lNode;ThreadedBinaryTreeNode *rNode;ThreadedBinaryTreeNode *pNode;bool lIsPredecessor;bool rIsSuccessor;
};

在这里插入图片描述

线索二叉树的构造

构造一棵线索二叉树分为两步:

  • 首先使用普通二叉树的构造方法构造一棵ThreadTree
  • 然后使用线索化方法将ThreadTree转换成一个真正的线索二叉树

以前序线索化为例,它的算法思想为:

  • 本质是一次前序遍历
  • tree指针指向当前访问的结点,predecessor指针指向tree的前驱
  • 在遍历时
    • 检查tree的左指针是否为空,若为空则将其指向predecessor
    • 检查predecessor 的右指针是否为空,若为空则将其指向tree
/*** 先序线索化* @param tree* @param predecessor* @return*/
ThreadedBinaryTree preOrderThreaded(ThreadedBinaryTree tree, ThreadedBinaryTreeNode *predecessor) {if (tree != NULL) {if (tree->lNode == NULL) {tree->lNode = predecessor;tree->lIsPredecessor = true;}if (predecessor != NULL && predecessor->rNode == NULL) {predecessor->rNode = tree;predecessor->rIsSuccessor = true;}predecessor = tree;if (tree->lIsPredecessor == false) {preOrderThreaded(tree->lNode, predecessor);}if (tree->rIsSuccessor == false) {preOrderThreaded(tree->rNode, predecessor);}}
}

寻找前驱和后继

以前序线序哦二叉树为例,寻找tree结点前驱的算法思想如下:

  • 如果tree->lIsPredecessor == true,则直接返回其左孩子
  • tree的左孩子不指向其前驱:
    • tree是其父结点的左孩子,则直接返回其父结点
    • tree是其父结点的右孩子
      • 若父结点没有左孩子,则直接返回父结点
      • 若父结点有左孩子,则返回父结点左子树中按照前序序列访问到的最后一个结点
/*** 寻找前序序列的前驱* @param tree* @return*/
ThreadedBinaryTreeNode *preOrderFirstNode(ThreadedBinaryTree tree) {if (tree->lIsPredecessor == true) {return tree->lNode;} else {ThreadedBinaryTreeNode *parent = tree->pNode;if (parent->lNode == tree) {return parent;} else {if (parent->lIsPredecessor == true || parent->lNode == NULL) {return parent;} else {ThreadedBinaryTreeNode *node = parent->lNode;while (node != NULL) {if (node->rNode == tree) {break;} else if (node->rNode != NULL) {node = node->rNode;} else if (node->lIsPredecessor == false && node->lNode != NULL) {node = node->lNode;}}return node;}}}
}

寻找后继的算法思想如下:

  • 如果tree->rIsSuccessor == true,则直接返回其右孩子
  • tree的右孩子不指向其后继:
    • 如果 tree->lIsPredecessor == false && tree->lNode != NULL,返回其左孩子
    • 否则返回其右孩子
/*** 寻找前序序列的后继* @param tree* @return*/
ThreadedBinaryTreeNode *preOrderNextNode(ThreadedBinaryTree tree) {if (tree->rIsSuccessor == true) {return tree->rNode;} else {if (tree->lIsPredecessor == false && tree->lNode != NULL) {return tree->lNode;} else {return tree->rNode;}}

线索二叉树的遍历

以前序线序哦二叉树为例,前序线索二叉树中包含了二叉树的前驱和后继的信息,在对其进行遍历时,只需要找到前序序列中的第一个结点,然后依次找结点的后继,直至其后继为空。

/*** 线索二叉树前序遍历* @param tree* @param queue*/
void threadedBinaryTreePreOrder(ThreadedBinaryTree tree, LinkedQueue queue) {for (ThreadedBinaryTreeNode *node = tree; node != NULL; node = preOrderNextNode(node)) {linkedQueueEnQueue(queue, node->data);}
}

最优二叉树(哈夫曼树)

如果给树中结点赋一个有某种含义的数值,则这个数值称为该结点的。结点的权和根结点到该结点的路径长度的乘积称为结点的带权路径长度,树的带权路径长度是树中所有叶子节点带权路径长度之和。带权路径长度最小的二叉树称为最优二叉树。最优二叉树的特点如下:

  • 包含 n n n个叶子节点的最优二叉树共有 2 n − 1 个 2n-1个 2n1结点。
  • 最优二叉树结点的度要么是 0 0 0要么是 2 2 2
struct HuffmanTreeNode {void *data;void *weight;struct HuffmanTreeNode *lNode;struct HuffmanTreeNode *rNode;
};

在这里插入图片描述

哈夫曼树的构造

给定 n n n个结点以及它们的权值,那么构造过程如下:

  • 构造森林全是根:将这 n n n个结点分别作为 n n n棵仅包含一个结点的二叉树,构成森林 F F F
  • 选用两小造新树:构造一个新结点,在 F F F中选择两棵根结点权值最小的树作为新结点的左右子树新结点权值即为两个孩子节点的权值之和。
  • 删除两小添新人:从 F F F中删除刚才选出的两棵树,并将新构造的二叉树加入 F F F
  • 重复2、3剩单根:重复2和3步直至F中只剩一棵树。
/*** 构造哈夫曼树* @param dataList 数据列表* @param weightList 权值列表* @param size 大小* @param reCompare 权值逆序比较* @param weightSum 权值相加* @return*/
HuffmanTree huffmanTreeNodeConstructor(void *dataList[], void *weightList[], int size, int (*reCompare)(void *, void *), void *(*weightSum)(void *, void *)) {HuffmanTreeNode *forest[size];int treeCount = 0;for (int i = 0; i < size; ++i) {HuffmanTreeNode *node = malloc(sizeof(HuffmanTreeNode));node->data = dataList[i];node->weight = weightList[i];node->lNode = NULL;node->rNode = NULL;forest[i] = node;treeCount++;}sorted(forest, size, reCompare);for (; treeCount != 1;) {HuffmanTreeNode *node = malloc(sizeof(HuffmanTreeNode));node->lNode = forest[0];forest[0] = NULL;treeCount--;node->rNode = forest[1];forest[1] = NULL;treeCount--;node->data = NULL;node->weight = weightSum(node->lNode->weight, node->rNode->weight);forest[0] = node;sorted(forest, size, reCompare);}return forest[0];
}

哈夫曼编码

由哈夫曼树可以得到哈夫曼编码:

  • 首先将一段文本中每个出现的字符当作一个独立的结点构造成哈夫曼树,每个字符都会出现在叶子结点,其权值为它出现的频度;
  • 然后在每条路径上标记 0 0 0 1 1 1,标记为 0 0 0表示转向左孩子,标记为 1 1 1表示转向右孩子。
  • 那么字符的哈夫曼编码就为从根结点到该结点的路径上边标记的 01 01 01序列。
  • 哈夫曼树的带权路径长度就是该文本最终编码得到的二进制编码长度。
/*** 是否是叶子结点* @param node* @return*/
static bool isLeafNode(HuffmanTreeNode *node) {return node->lNode == NULL && node->rNode == NULL;
}/*** 哈夫曼编码* @param tree* @param target* @param compare* @return*/
char *huffmanCoding(HuffmanTree tree, void *target, int (*compare)(void *, void *)) {if (tree != NULL) {char *lr = huffmanCoding(tree->lNode, target, compare);char *rr = huffmanCoding(tree->rNode, target, compare);if (lr != NULL) {char *code = calloc(strlen(lr) + 2, sizeof(char));strcat(code, "0");strcat(code, lr);return code;} else if (rr != NULL) {char *code = calloc(strlen(rr) + 2, sizeof(char));strcat(code, "1");strcat(code, rr);return code;} else if (tree->lNode != NULL && isLeafNode(tree->lNode) && compare(tree->lNode->data, target) == 0) {return "0";} else if (tree->rNode != NULL && isLeafNode(tree->rNode) && compare(tree->rNode->data, target) == 0) {return "1";} else {return NULL;}} else {return NULL;}
}

二叉排序树(BST)

二叉排序树是满足以下性质的二叉树:

  • 若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意结点的左、右子树也分别为二叉排序树;

对二叉排序树进行中序遍历可以得到一个递增有序的有序序列。

struct SortedBinaryTreeNode {void *data;SortedBinaryTreeNode *lNode;SortedBinaryTreeNode *rNode;
};

二叉排序树的插入

算法思想:

  • 若树为空,则新建根结点
  • 若树非空,则插入值与根结点值比较
    • 若相等则插入失败
    • 若小于根结点值,则插入到左子树
    • 若大于根结点值,则插入到右子树
/*** 二叉排序树的插入* @param tree* @param element* @param compare* @return*/
bool sortedBinaryTreeInsert(SortedBinaryTree *tree, void *element, int (*compare)(void *, void *)) {if ((*tree) == NULL) {*tree = malloc(sizeof(SortedBinaryTreeNode));(*tree)->data = element;(*tree)->lNode = NULL;(*tree)->rNode = NULL;return true;} else {int cmpResult = compare(element, (*tree)->data);if (cmpResult == 0) {return false;} else if (cmpResult > 0) {if ((*tree)->rNode == NULL) {SortedBinaryTreeNode *node = malloc(sizeof(SortedBinaryTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;(*tree)->rNode = node;return true;} else {return sortedBinaryTreeInsert(&(*tree)->rNode, element, compare);}} else {if ((*tree)->lNode == NULL) {SortedBinaryTreeNode *node = malloc(sizeof(SortedBinaryTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;(*tree)->lNode = node;return true;} else {return sortedBinaryTreeInsert(&(*tree)->lNode, element, compare);}}}
}

二叉排序树的构造

/*** 二叉排序树构造函数* @param elementList* @param size* @param compare* @return*/
SortedBinaryTree sortedBinaryTreeConstructor(void **elementList, int size, int (*compare)(void *, void *)) {static SortedBinaryTree tree = NULL;for (int i = 0; i < size; ++i) {sortedBinaryTreeInsert(&tree, *(elementList + i), compare);}return tree;
}

二叉排序树的查找

算法思想:

  • 若树非空,则目标值与根结点值比较
  • 若相等则查找成功
  • 若小于根结点值,则在左子树上查找
  • 若大于根结点值,则在右子树上查找
/*** 二叉排序树的查找* @param tree* @param element* @param compare* @return*/
SortedBinaryTreeNode *sortedBinaryTreeSearch(SortedBinaryTree tree, void *element, int (*compare)(void *, void *)) {if (tree == NULL) {return NULL;} else {int cmpResult = compare(element, tree->data);if (cmpResult == 0) {return tree;} else if (cmpResult > 0) {return sortedBinaryTreeSearch(tree->rNode, element, compare);} else {return sortedBinaryTreeSearch(tree->lNode, element, compare);}}
}

二叉排序树的删除

算法思想:

  • 首先找到要删除的结点。
  • 若删除结点是叶子结点,则直接删除。
  • 若删除结点只有左子树或者右子树,则让左子树或右子树替代删除结点。
  • 若若删除结点左右子树都存在:
    • 方法一:找到右子树中最小的值替代删除结点的值,然后再删除这个最小值所在的结点。
    • 方法二:找到左子树中最大的值替代删除结点的值,然后再删除这个最大值所在的结点。
/*** 删除元素* @param tree* @param element* @param compare* @return*/
bool sortedBinaryTreeDelete(SortedBinaryTree *tree, void *element, int (*compare)(void *, void *)) {if (*tree == NULL) {return false;} else {int cmpResult = compare(element, (*tree)->data);if (cmpResult == 0) {if ((*tree)->lNode == NULL && (*tree)->rNode == NULL) {free(*tree);*tree = NULL;return true;} else if ((*tree)->lNode == NULL) {SortedBinaryTreeNode *node = (*tree);*tree = (*tree)->rNode;free(node);return true;} else if ((*tree)->rNode == NULL) {SortedBinaryTreeNode *node = (*tree);*tree = (*tree)->lNode;free(node);return true;} else {SortedBinaryTreeNode *node = (*tree)->rNode;while (node->lNode != NULL) {node = node->lNode;}(*tree)->data = node->data;return sortedBinaryTreeDelete(&(*tree)->rNode, node->data, compare);}} else if (cmpResult > 0) {return sortedBinaryTreeDelete(&(*tree)->rNode, element, compare);} else {return sortedBinaryTreeDelete(&(*tree)->lNode, element, compare);}}
}

平衡二叉树(AVL)

结点的平衡因子定义如下:
平衡因子 = 左子树高 − 右子树高 平衡因子=左子树高-右子树高 平衡因子=左子树高右子树高
树中任一结点的平衡因子的绝对值不超过 1 1 1的二叉树称为平衡二叉树,平衡二叉树避免了树高的过度增长,如果可以保证一棵二叉排序树为平衡二叉树,那么就可以减少搜索元素时比较的次数,从而提高二叉排序树的性能。

struct BalancedBinaryTreeNode {void *data;BalancedBinaryTreeNode *lNode;BalancedBinaryTreeNode *rNode;
};

平衡二叉树失衡调整

当对一棵平衡二叉树进行插入或删除操作时,有可能会导致平衡二叉树失衡,此时应该从插入或删除结点往回找到第一个不平衡结点,调整以该结点为根的子树,这棵被调整的树称为最小不平衡子树。平衡二叉树的失衡类型有以下四种,针对不同的类型有不同的调整策略。

  • LL平衡旋转:B结点带左子树代替A成为根结点;A结点带右子树成为B的右子树;B结点原来的右子树作为A的左子树。
void ll(BalancedBinaryTree *tree) {BalancedBinaryTreeNode *rootNode = *tree;BalancedBinaryTreeNode *lNode = rootNode->lNode;BalancedBinaryTreeNode *lrNode = lNode->rNode;*tree = lNode;lNode->rNode = rootNode;rootNode->lNode = lrNode;
}

在这里插入图片描述

  • RR平衡旋转:B结点带右子树代替A成为根结点;A结点带左子树成为B的左子树;B结点原来的左子树作为A的右子树。
void rr(BalancedBinaryTree *tree) {BalancedBinaryTreeNode *rootNode = *tree;BalancedBinaryTreeNode *rNode = rootNode->rNode;BalancedBinaryTreeNode *rlNode = rNode->lNode;*tree = rNode;rNode->lNode = rootNode;rootNode->rNode = rlNode;
}

在这里插入图片描述

  • LR先左后右双旋转:C结点代替A结点成为根结点;B结点带左子树成为C的左孩子,A结点带右子树成为C的右孩子;原来C结点的左子树作为B的右子树,原来C的右子树作为A的左子树。
void lr(BalancedBinaryTree *tree) {BalancedBinaryTreeNode *rootNode = *tree;BalancedBinaryTreeNode *lNode = rootNode->lNode;BalancedBinaryTreeNode *lrNode = lNode->rNode;BalancedBinaryTreeNode *lrlNode = lrNode->lNode;BalancedBinaryTreeNode *lrrNode = lrNode->rNode;*tree = lrNode;lrNode->lNode = lNode;lrNode->rNode = rootNode;lNode->rNode = lrlNode;rootNode->lNode = lrrNode;
}

在这里插入图片描述

  • RL先右后左双旋转:C结点代替A作为根结点;A结点带左子树成为C的左孩子,B结点带右子树成为C的右孩子;原来C结点的左子树作为A的右子树,原来C的右子树作为B的左子树。
void rl(BalancedBinaryTree *tree) {BalancedBinaryTreeNode *rootNode = *tree;BalancedBinaryTreeNode *rNode = rootNode->rNode;BalancedBinaryTreeNode *rlNode = rNode->lNode;BalancedBinaryTreeNode *rllNode = rlNode->lNode;BalancedBinaryTreeNode *rlrNode = rlNode->rNode;*tree = rlNode;rlNode->lNode = rootNode;rlNode->rNode = rNode;rNode->lNode = rlrNode;rootNode->rNode = rllNode;
}

在这里插入图片描述

最后是失衡调整算法,在这个算法中只需要判断失衡类型是哪种并调用相应的算法即可:

void imbalanceAdjust(BalancedBinaryTree *tree) {int lDeep = balancedBinaryTreeDeep((*tree)->lNode);int rDeep = balancedBinaryTreeDeep((*tree)->rNode);int balance = lDeep - rDeep;if (balance > 1) {lDeep = balancedBinaryTreeDeep((*tree)->lNode->lNode);rDeep = balancedBinaryTreeDeep((*tree)->lNode->rNode);//左左和左右if (lDeep > rDeep) {ll(tree);} else {lr(tree);}} else if (balance < -1) {lDeep = balancedBinaryTreeDeep((*tree)->rNode->lNode);rDeep = balancedBinaryTreeDeep((*tree)->rNode->rNode);//右右和右左if (rDeep > lDeep) {rr(tree);} else {rl(tree);}} else {return;}
}

平衡二叉树的插入

对于平衡二叉树的插入而言,只要调整第一棵最小不平衡子树,只要第一颗最小不平衡子树恢复了平衡,那么整棵树就恢复了平衡。

/*** 二叉排序树的插入* @param tree* @param element* @param compare* @return*/
bool balancedBinaryTreeInsert(BalancedBinaryTree *tree, void *element, int (*compare)(void *, void *)) {if ((*tree) == NULL) {*tree = malloc(sizeof(BalancedBinaryTreeNode));(*tree)->data = element;(*tree)->lNode = NULL;(*tree)->rNode = NULL;return true;} else {int cmpResult = compare(element, (*tree)->data);if (cmpResult == 0) {return false;} else if (cmpResult > 0) {if ((*tree)->rNode == NULL) {BalancedBinaryTreeNode *node = malloc(sizeof(BalancedBinaryTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;(*tree)->rNode = node;return true;} else {bool result = balancedBinaryTreeInsert(&(*tree)->rNode, element, compare);imbalanceAdjust(tree);return result;}} else {if ((*tree)->lNode == NULL) {BalancedBinaryTreeNode *node = malloc(sizeof(BalancedBinaryTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;(*tree)->lNode = node;return true;} else {bool result = balancedBinaryTreeInsert(&(*tree)->lNode, element, compare);imbalanceAdjust(tree);return result;}}}
}

平衡二叉树的删除

对于平衡二叉树的删除而言,需要在被删除的结点处一直向上回溯,找到所有最小不平衡子树并进行失衡调整。

/*** 删除元素* @param tree* @param element* @param compare* @return*/
bool balancedBinaryTreeDelete(BalancedBinaryTree *tree, void *element, int (*compare)(void *, void *)) {if (*tree == NULL) {return false;} else {int cmpResult = compare(element, (*tree)->data);if (cmpResult == 0) {if ((*tree)->lNode == NULL && (*tree)->rNode == NULL) {free(*tree);*tree = NULL;return true;} else if ((*tree)->lNode == NULL) {BalancedBinaryTreeNode *node = (*tree);*tree = (*tree)->rNode;free(node);return true;} else if ((*tree)->rNode == NULL) {BalancedBinaryTreeNode *node = (*tree);*tree = (*tree)->lNode;free(node);return true;} else {BalancedBinaryTreeNode *node = (*tree)->rNode;while (node->lNode != NULL) {node = node->lNode;}(*tree)->data = node->data;bool result = balancedBinaryTreeDelete(&(*tree)->rNode, node->data, compare);imbalanceAdjust(tree);return result;}} else if (cmpResult > 0) {bool result = balancedBinaryTreeDelete(&(*tree)->rNode, element, compare);imbalanceAdjust(tree);return result;} else {bool result = balancedBinaryTreeDelete(&(*tree)->lNode, element, compare);imbalanceAdjust(tree);return result;}}
}

红黑树

虽然平衡二叉树可以保证二叉排序树的性能,但是频繁的平衡调整代价较大,为此在平衡二叉树上放宽了条件,引入了红黑树,红黑树是具有以下性质的二叉排序树:

  • 每个结点或是红色或是黑色的。
  • 根结点是黑色的。
  • 所有叶子结点(虚构的外部结点,即NULL结点)都是黑色的。
  • 不存在两个相邻的红结点,即红结点的父结点和孩子结点都是黑色的。
  • 对于每个结点而言,其到任意叶子结点的路径都包含相同数量的黑结点(不包含自己),这些黑结点的数量称为该结点的黑高,根结点的黑高称为树的黑高。
struct RedBlackTreeNode {void *data;RedBlackTreeNode *lNode;RedBlackTreeNode *rNode;RedBlackTreeNode *pNode;int color;
};

红黑树的性质如下:

  • 从根结点到叶子结点的最长路径不大于最短路径的两倍。
  • n n n个内部结点的红黑树的高度 h ≤ 2 l o g 2 ( n + 1 ) h\leq 2log₂(n+1) h2log2(n+1)

红黑树的插入

红黑树插入的算法思想如下:①先查找,确定插入的位置(和二叉搜索树相同)。②插入结点是根结点则染成黑色,否则就染成红色。③如果插入新结点后满足红黑树的定义,则插入结束,否则就进行调整(此时只可能违反红黑树的第四条性质)。针对不同的类型有不同的调整策略:

  • 黑叔+LL型:和平衡二叉树一样的LL平衡旋转+父和爷染色
static void ll(RedBlackTree *tree) {RedBlackTreeNode *rootNode = *tree;RedBlackTreeNode *rootPNode = (*tree)->pNode;RedBlackTreeNode *lNode = rootNode->lNode;RedBlackTreeNode *lrNode = lNode->rNode;*tree = lNode;lNode->pNode = rootPNode;lNode->rNode = rootNode;rootNode->pNode = lNode;rootNode->lNode = lrNode;if (lrNode != NULL) {lrNode->pNode = rootNode;}reverseColor(rootNode);reverseColor(lNode);
}
  • 黑叔+RR型:和平衡二叉树一样的RR平衡旋转+父和爷染色
static void rr(RedBlackTree *tree) {RedBlackTreeNode *rootNode = *tree;RedBlackTreeNode *rootPNode = (*tree)->pNode;RedBlackTreeNode *rNode = rootNode->rNode;RedBlackTreeNode *rlNode = rNode->lNode;*tree = rNode;rNode->pNode = rootPNode;rNode->lNode = rootNode;rootNode->pNode = rNode;rootNode->rNode = rlNode;if (rlNode != NULL) {rlNode->pNode = rootNode;}reverseColor(rootNode);reverseColor(rNode);
}
  • 黑叔+LR型:和平衡二叉树一样的LR平衡旋转+儿和爷染色
static void lr(RedBlackTree *tree) {RedBlackTreeNode *rootNode = *tree;RedBlackTreeNode *rootPNode = (*tree)->pNode;RedBlackTreeNode *lNode = rootNode->lNode;RedBlackTreeNode *lrNode = lNode->rNode;RedBlackTreeNode *lrlNode = lrNode->lNode;RedBlackTreeNode *lrrNode = lrNode->rNode;*tree = lrNode;lrNode->pNode = rootPNode;lrNode->lNode = lNode;lNode->pNode = lrNode;lrNode->rNode = rootNode;rootNode->pNode = lrNode;lNode->rNode = lrlNode;if (lrlNode != NULL) {lrlNode->pNode = lNode;}rootNode->lNode = lrrNode;if (lrrNode != NULL) {lrrNode->pNode = rootNode;}reverseColor(rootNode);reverseColor(lrNode);
}
  • 黑叔+RL型:和平衡二叉树一样的RL平衡旋转+儿和爷染色
static void rl(RedBlackTree *tree) {RedBlackTreeNode *rootNode = *tree;RedBlackTreeNode *rootPNode = (*tree)->pNode;RedBlackTreeNode *rNode = rootNode->rNode;RedBlackTreeNode *rlNode = rNode->lNode;RedBlackTreeNode *rllNode = rlNode->lNode;RedBlackTreeNode *rlrNode = rlNode->rNode;*tree = rlNode;rlNode->pNode = rootPNode;rlNode->lNode = rootNode;rootNode->pNode = rlNode;rlNode->rNode = rNode;rNode->pNode = rlNode;rNode->lNode = rlrNode;if (rlrNode != NULL) {rlrNode->pNode = rNode;}rootNode->rNode = rllNode;if (rllNode != NULL) {rllNode->pNode = rootNode;}reverseColor(rootNode);reverseColor(rlNode);
}
  • 红叔:叔父爷染色,爷变为新结点(代码见失衡调整)

完整的失衡调整代码如下:

static void imbalanceAdjust(RedBlackTree tree, RedBlackTree *root) {RedBlackTreeNode *fatherNode = tree->pNode;if (fatherNode == NULL) {return;}RedBlackTreeNode *grandFatherNode = fatherNode->pNode;if (grandFatherNode == NULL) {return;}RedBlackTreeNode *uncleNode = grandFatherNode->lNode == fatherNode ? grandFatherNode->rNode : grandFatherNode->lNode;if (!(tree->color == RED && fatherNode->color == RED)) {return;}if (uncleNode == NULL || uncleNode->color == BLACK) {if (grandFatherNode->lNode == fatherNode) {if (fatherNode->lNode == tree) {if (grandFatherNode->pNode == NULL) {ll(root);} else {ll(grandFatherNode->pNode->lNode == grandFatherNode ? &(grandFatherNode->pNode->lNode) : &(grandFatherNode->pNode->rNode));}} else {if (grandFatherNode->pNode == NULL) {lr(root);} else {lr(grandFatherNode->pNode->lNode == grandFatherNode ? &(grandFatherNode->pNode->lNode) : &(grandFatherNode->pNode->rNode));}}} else {if (fatherNode->rNode == tree) {if (grandFatherNode->pNode == NULL) {rr(root);} else {rr(grandFatherNode->pNode->lNode == grandFatherNode ? &(grandFatherNode->pNode->lNode) : &(grandFatherNode->pNode->rNode));}} else {if (grandFatherNode->pNode == NULL) {rl(root);} else {rl(grandFatherNode->pNode->lNode == grandFatherNode ? &(grandFatherNode->pNode->lNode) : &(grandFatherNode->pNode->rNode));}}}} else {reverseColor(grandFatherNode);reverseColor(fatherNode);reverseColor(uncleNode);if (grandFatherNode->pNode == NULL) {grandFatherNode->color = BLACK;} else {imbalanceAdjust(grandFatherNode, root);}}
}

完整的插入代码如下:

static bool insert(RedBlackTree *tree, RedBlackTree *root, void *element, int (*compare)(void *, void *)) {if ((*tree) == NULL) {*tree = malloc(sizeof(RedBlackTreeNode));(*tree)->data = element;(*tree)->lNode = NULL;(*tree)->rNode = NULL;(*tree)->pNode = NULL;(*tree)->color = BLACK;return true;} else {int cmpResult = compare(element, (*tree)->data);if (cmpResult == 0) {return false;} else if (cmpResult > 0) {if ((*tree)->rNode == NULL) {RedBlackTreeNode *node = malloc(sizeof(RedBlackTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;node->pNode = *tree;node->color = RED;(*tree)->rNode = node;imbalanceAdjust(node, root);return true;} else {return insert(&(*tree)->rNode, root, element, compare);}} else {if ((*tree)->lNode == NULL) {RedBlackTreeNode *node = malloc(sizeof(RedBlackTreeNode));node->data = element;node->rNode = NULL;node->rNode = NULL;node->pNode = *tree;node->color = RED;(*tree)->lNode = node;imbalanceAdjust(node, root);return true;} else {return insert(&(*tree)->lNode, root, element, compare);}}}
}/*** 二叉排序树的插入* @param tree* @param element* @param compare* @return*/
bool redBlackTreeInsert(RedBlackTree *tree, void *element, int (*compare)(void *, void *)) {return insert(tree, tree, element, compare);
}

红黑树的删除

B树

B树又称为多路平衡查找树,B数中所有结点的孩子个数的最大值称为B树的阶,通常用 m m m表示,B树要么是空树,要么是满足以下条件的 m m m叉树:

  • 树中每个结点至多有 m m m棵子树,最多含有 m − 1 m-1 m1个数据
  • 若根结点不是终端结点,则至少有两棵子树。
  • 除根结点之外的非叶子结点至少有 ⌈ m / 2 ⌉ ⌈m/2⌉ m/2棵子树
  • 所有叶子节点都出现在同一层次上,且不带任何信息。
  • 每个结点数据和子树的关系如下:
    子树 0 < 数据 1 < 子树 1 < 数据 2 < 子树 2 < … 子树0<数据1<子树1<数据2<子树2<\dots 子树0<数据1<子树1<数据2<子树2<
struct BTreeNode {void **dataList;BTreeNode **childList;int dataCount;
};

B树的插入

B树的删除

B树的查找

B+树

相关文章:

数据结构和算法——用C语言实现所有树形结构及相关算法

文章目录 前言树和森林基础概念二叉树二叉树的遍历二叉树的构造树和森林与二叉树之间的转化树和森林的遍历 满二叉树完全二叉树线索二叉树线索二叉树的构造寻找前驱和后继线索二叉树的遍历 最优二叉树&#xff08;哈夫曼树&#xff09;哈夫曼树的构造哈夫曼编码 二叉排序树&…...

OTA: Optimal Transport Assignment for Object Detection 论文和代码学习

OTA 原因步骤什么是最优传输策略标签分配的OT正标签分配负标签分配损失计算中心点距离保持稳定动态k的选取 整体流程代码使用 论文连接&#xff1a; 原因 1、全部按照一个策略如IOU来分配GT和Anchors不能得到全局最优&#xff0c;可能只能得到局部最优。 2、目前提出的ATSS和P…...

前后端交互—跨域与HTTP

跨域 代码下载 同源策略 同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。 MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这 是一个用于隔离潜在恶意文件的重要安全机制。 通俗的理解:浏览器规定&a…...

Error和Exception的关系以及区别

在Java中&#xff0c;Error 和 Exception 是两种不同类型的异常类&#xff0c;它们都继承自 java.lang.Throwable&#xff0c;但在用途和处理方式上有重要区别。 Error: Error 表示在程序运行过程中&#xff0c;通常由于系统或环境的严重问题而引起的异常情况。这些问题通常是无…...

Hive SQL 函数高阶应用场景

HIVE作为数据仓库处理常用工具&#xff0c;如同RDBMS关系型数据库中标准SQL语法一样&#xff0c;Hive SQL也内置了不少系统函数&#xff0c;满足于用户在不同场景下的数据分析需求&#xff0c;以提高开发SQL数据分析的效率。 我们可以使用show functions查看当下版本支持的函数…...

linux下C++开发环境搭建

一.安装GCC,GDB 1.1 先更新软件包安装源 sudo apt update1.2 安装编译器和调试器 sudo apt install build-essential gdb"build-essential" 是编译代码所需要的工具。 "gdb" 是调试器。1. build-essential:- "build-essential" 是一个用于Ubu…...

报错问题解决办法:Decryption error sun.security.rsa.RSAPadding.unpadV15

报错问题解决办法&#xff1a;Decryption error sun.security.rsa.RSAPadding.unpadV15 出现的问题 javax.crypto.BadPaddingException: Decryption errorat sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380) ~[na:1.8.0_131]at sun.security.rsa.RSAPadding.unpa…...

LVS+DR部署

LVS-DR的工作原理&#xff1a; 1.客户端会发送请求到vip 2.LVS的调度器接受请求之后&#xff0c;根据算法选择一台真实服务器&#xff0c;请求转发到后端RS&#xff0c;请求的报文的目的MAC地址&#xff0c;修改成后端真实服务器的MAC地址&#xff0c;转发。 3.后端真实服务器…...

C++项目——云备份-②-第三方库认识

文章目录 专栏导读1. json 认识1.1 JSON 数据结构的特点 2. jsoncpp库认识3. json实现序列化案例4. json实现反序列化案例5. bundle文件压缩库认识6. bundle库实现文件压缩案例7.bundle库实现文件解压缩案例8.httplib库认识9. httplib库搭建简单服务器案例10. httplib库搭建简单…...

Linux入门攻坚——4、shell编程初步、grep及正则表达式

bash的基础特性&#xff08;续&#xff09;&#xff1a; 1、提供了编程环境&#xff1a; 编程风格&#xff1a;过程式&#xff1a;以指令为中心&#xff0c;数据服务于执行&#xff1b;对象式&#xff1a;以数据为中心&#xff0c;指令服务于数据 shell编程&#xff0c;编译执…...

TCP/IP(二十二)TCP 实战抓包分析(六)TCP 快速建立连接

一 TCP Fast Open 快速建立连接 说明&#xff1a; 之前讲解TCP 相关知识点遗漏了这个知识点,补充上 ① TFO简介 ② 请求 Fast Open Cookie过程 "原理图" ③ 真正开始 TCP Fast Open 重点&#xff1a; TFO 使 SYN包 可以包含payload 数据 ④ 抓包分析 1、…...

IDEA如何拉取gitee项目?

1.登录gitee 说明&#xff1a;打开idea&#xff0c;在设置上面搜索框输入gitee&#xff0c;然后登录gitee注册的账号。 2. 创建gitee仓库 说明&#xff1a;创建idea中的gitee仓库。 3.寻找项目文件 说明&#xff1a;为需要添加gitee仓库的项目进行添加。 4.项目右键 说明&a…...

视频编辑不求人,教你一招制胜批量添加封面

视频添加封面是一个相当简单的任务&#xff0c;您只需要一款专门的软件&#xff0c;就能轻松搞定&#xff01;下面就是详细教程啦&#xff01; 首先&#xff0c;您需要在浏览器中搜索“固乔智剪软件”&#xff0c;进入官网并下载这款软件。固乔智剪软件是一款非常专业的视频剪辑…...

产品的竞争力是什么

产品的竞争力归根到底是3点&#xff1a;功能&#xff0c;性能&#xff0c;容量。 功能 我这个产品完成了别人没有实现的功能&#xff0c;而且是用户需要的。解决了客户的痛点 性能 我这个产品的功能虽然别人有&#xff0c;但是我性能好&#xff0c;性能好意味着干同样的活给…...

vue3 拖拽插件 Vue3DraggableResizable

Vue3DraggableResizable 拖拽插件的官方文档 一、Vue3DraggableResizable 的属性和事件 1、Vue3DraggableResizable 的属性配置 属性类型默认值功能描述示例initWNumbernull设置初始宽度&#xff08;px&#xff09;<Vue3DraggableResizable :initW“100” />initHNumb…...

VUE父组件向子组件传递数据和方法

文章目录 1 父组件写法2 子组件写法 1 父组件写法 父组件参数和方法 data() {return {// 遮罩层loading: true,// 表格数据yfeList: []}}导入组件 import yfTable from "/views/yf/yfTable.vue";组件 components: {yfTabTable},传值使用 <yfTabTable :loadin…...

NPI加速器在烽火科技SMT车间的应用:贴片机程序制作效率的革新

烽火科技&#xff0c;一个在国内颇具知名度的高科技企业&#xff0c;坐落于武汉光谷的SMT车间中&#xff0c;机器嗡嗡作响&#xff0c;作业员们忙碌地进行着生产。工厂使用的是ASM的贴片机&#xff0c;使用Sipalce Pro作为其编程软件。然而&#xff0c;在高效的生产线背后&…...

如何给照片添加水印?请看下面3个简单教程

如何给照片添加水印&#xff1f;随着智能手机的普及和不断提升的拍摄技术&#xff0c;如今人们可以轻松使用手机进行高质量的照片拍摄。从老人到小孩&#xff0c;每个人都可以在日常生活中捕捉到美好瞬间&#xff0c;并将其记录下来。作为一种表达自己的方式&#xff0c;现在手…...

仿写知乎日报第一周

效果图 主要的逻辑 Manager封装网络请求 首先&#xff0c;对于获取网络请求&#xff0c;我是将这些方法封装成了一个类Manager&#xff0c;后续在获取以往的内容时又封装了一个beforeManager类用于网络请求。这里不多赘述&#xff0c;Manager封装网络请求的知识参考我的以往博…...

32二叉树——DFS深度优先遍历

目录 深度优先算法&#xff08;Depth-First Search&#xff0c;DFS&#xff09; LeetCode之路——102. 二叉树的层序遍历 分析 深度优先算法&#xff08;Depth-First Search&#xff0c;DFS&#xff09; DFS是一种用于遍历或搜索树状数据结构的算法&#xff0c;其中它首先探…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...