【学习笔记】数据结构(六 ①)

树和二叉树 (一)

文章目录

  • 树和二叉树 (一)
    • 6.1 树(Tree)的定义和基本术语
    • 6.2 二叉树
      • 6.2.1 二叉树的定义
        • 1、斜树
        • 2、满二叉树
        • 3、完全二叉树
        • 4、二叉排序树
        • 5、平衡二叉树(AVL树)
        • 6、红黑树
      • 6.2.2 二叉树的性质
      • 6.2.3 二叉树的存储结构
    • 6.3 遍历二叉树和线索二叉树
      • 6.3.1 遍历二叉树

6.1 树(Tree)的定义和基本术语

树(Tree)是n(n≥0)个结点的有限集。

在任意一棵非空树中:

​ (1) 有且仅有一个特定的称为**根(Root)**的结点;

​ (2) 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每一个集合本身又是一棵树,并且

​ 称为根的子树(SubTree)

在这里插入图片描述

树的特点

  1. 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
  2. 树中所有结点可以有零个或多个后继。
  3. 树中的结点数等于所有结点的度数加1.
    度为m的树中第i层上至多有mi-1个结点(i > = 1)
    高度为h 的m叉树至多有( mh − 1 ) / ( m − 1 )个结点。
    具有n个结点的m叉树的最小高度为[ logm ( n ( m − 1 ) + 1 ) ] 。

树的其他表示形式

​ ( a ) 是以嵌套集合(即是一些集合的集体,对于其中任何两个集合,或者不相交,或者一个包含另一个)的形式表示的;

​ ( b ) 是以广义表的形式表示的,根作为由子树森林组成的表的名字写在表的左边;

​ ( c ) 用的是凹人表示法(类似书的编目)。

在这里插入图片描述

👉 基本术语:

  • 结点的祖先是从根到该结点所经分支上的所有结点。根A到结点K的唯一路径上的任意结点,称为结点K的祖先—如结点B是结点K的祖先

  • 以某结点为根的子树中的任一结点都称为该结点的子孙。结点K是结点B的子孙

  • 结点的子树的根称为该 结点的孩子(Child), 相应地,该结点称为孩子的**双亲(Parent) ** 。最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。

  • 同一个双亲的孩子 之间互称兄弟 (Sibling) 。如结点K和结点L有相同的双亲E,即K和L为兄弟。

  • 其双亲在同一层的结点互为堂兄弟。如结点G与E,F,H,I,J互为堂兄弟。

  • 树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数(树中一个结点的孩子个数)称为结点的度 (Degree) 。如结点B的度为2,结点D的度为3

  • 树的度是树内各结点的度的最大值。树的度为3。

  • 度为0的结点 称为叶子 (Leaf) 或终端结点。

  • 度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点

  • 结点的层次 (Level) 从根开始定义起.根为第一层,根的孩子为第二层。

  • 树中结点的最大层次称为树的深度 (Depth) 或高度。图中树的高度为4。

    结点的深度是从根结点开始自顶向下逐层累加的。
    结点的高度是从叶结点开始自底向上逐层累加的。

  • 如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子。

  • 森林 (Forest) m(m≥0) 棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

    任何一棵树是一个二元组 Tree= (root, F), 其中: root 是数据元素、 称做树的根结点; m(m≥0) 棵树的森林, F=(T1, T2, …, Tm), 其中 Ti = (ri, Fi) 称做 root 的第i棵子树;当m≠0时,在树根和其子树森林之间存在下列关系:RF = { < root , 78ri > I i = 1 , 2, …, m ,m>0 }

6.2 二叉树

6.2.1 二叉树的定义

二叉树 (Binary Tree) 的特点是每个结点至多只有两棵子树 (即二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒。[二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成]

在这里插入图片描述

🌟特殊的二叉树

1、斜树
  • 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
2、满二叉树
  • 一棵高度为h,且含有2h−1个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点。除叶子结点之外的每个结点度数均为2。
  • 可以对满二叉树按层序编号:约定编号从根结点(根结点编号为1)起,自上而下,自左向右。这样,每个结点对应一个编号,对于编号为i的结点,若有双亲,则其双亲为i / 2, 若有左孩子,则左孩子为2i ; 若有右孩子,则右孩子为2i + 1。
3、完全二叉树
  • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边。

在这里插入图片描述

4、二叉排序树
  • 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
    • 若它的左子树不空,则左子树上所有结点的值均小于它根结点的值。
    • 若它的右子树不空,则右子树上所有结点的值均大于它根结点的值。
    • 它的左、右子树都满足为⼆叉排序树

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>#include<stdio.h>
#include<stdlib.h>//二叉排序树节点存储方式
typedef int DataType;
typedef struct BTnode {DataType data;	//数据域struct BTnode* left;	//左指针 struct BTnode* right;	//右指针
}BTnode, * BTree;//插入结点
void Insert_node(BTree* root, DataType data) {if (*root == NULL) {*root = (BTnode*)malloc(sizeof(BTnode));if (!*root) {printf("ERROR\n");exit(-1);}(*root)->data = data;(*root)->left = NULL;(*root)->right = NULL;}else if ((*root)->data <= data)Insert_node(&(*root)->right, data);else if ((*root)->data > data)Insert_node(&(*root)->left, data);
}//创建排序二叉树
void Create_sortBtree(BTree* T, DataType* arr, int size) {if (!arr)return NULL;else {for (int i = 0; i < size; i++) {Insert_node(T, arr[i]);}}
}//中序遍历排序二叉树
void mid_travel(BTree T)
{if (!T)return;mid_travel(T->left);printf("%d ", T->data);mid_travel(T->right);
}//递归查找
BTnode* Btree_search(BTree root, DataType target) {if (!root)return NULL;if (target == root->data) {return root;}return target > root->data ? Btree_search(root->right, target) : Btree_search(root->left, target);
}//非递归查找
BTnode* Btree_search_fa(BTree root, DataType target) {BTnode* p = root;while (p) {if (p->data == target){return p;}p = target > p->data ? p->right : p->left;}return NULL;
}//获取最大值
int Btree_max(BTree T) {BTnode* cur = T;while (cur->right) {cur = cur->right;}return cur->data;
}//获取最小值
int Btree_min(BTree T) {BTnode* cur = T;while (cur->left) {cur = cur->left;}return cur->data;
}//删除结点
void Del_Node(BTree* T, int val)
{if (T == NULL) return ;if ((*T)->data < val){Del_Node(&((*T)->right), val);}else if ((*T)->data > val){Del_Node(&((*T)->left), val);}else {//叶子结点if ((*T)->left == NULL && (*T)->right == NULL){*T = NULL;return;}//没有左孩子,只有右孩子if ((*T)->left == NULL && (*T)->right != NULL){*T = (*T)->right;return ;}//没有右孩子,只有左孩子if ((*T)->left != NULL && (*T)->right == NULL){*T = (*T)->left;return ;}//左右子树都有if ((*T)->left != NULL && (*T)->right != NULL){BTree cur = (*T)->left;while (cur->right != NULL){cur = cur->right;}cur->right = (*T)->right;*T = (*T)->left;return ;}}
}int main()
{BTree* root = NULL;DataType w[9] = { 8, 3, 10, 1, 6, 14, 4, 7, 13 };Create_sortBtree(&root, w, 9);mid_travel(root);printf("\n");printf("递归查找结点:");BTnode* node1 = Btree_search(root, 6);printf("%d, 左子结点 %d, 右子节点 %d\n", node1->data, node1->left->data, node1->right->data);printf("非递归查找结点:");BTnode* node2 = Btree_search_fa(root, 6);printf("%d, 左子结点 %d, 右子节点 %d\n", node2->data, node2->left->data, node2->right->data);printf("该树最大值:%d\n", Btree_max(root));printf("该树最小值:%d\n", Btree_min(root));/*删除叶子结点 4 */Del_Node(&root, 4);mid_travel(root);printf("\n");/*删除只有右子树,没有左子树结点 10 */Del_Node(&root, 10);mid_travel(root);printf("\n");/*删除只有左子树,没有右子树结点 14 */Del_Node(&root, 14);mid_travel(root);printf("\n");/*删除有左子树和右子树结点 8 */Del_Node(&root, 8);mid_travel(root);printf("\n");return 0;
}
5、平衡二叉树(AVL树)

平衡二叉树也叫自平衡二叉搜索树(Self-Balancing Binary Search Tree),所以其本质也是一颗二叉搜索树,树上任一结点的左子树和右子树的深度/高度差不超过1。左右子树的高度差称之为平衡因子。

自平衡 - 旋转操作

触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树

  • 左旋

    在这里插入图片描述

  • 右旋

    在这里插入图片描述

  • 单旋转

    • LL - 左左 一次右旋

      当不平衡节点的左子树的左子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

    • RR - 右右 一次左旋

      当不平衡节点的右子树的右子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

  • 双旋转

    • LR - 左右 先局部左旋,再整体右旋

      当不平衡节点的左子树的右子树有节点插入,导致二叉树不平衡
      在这里插入图片描述

    • RL - 右左 先局部右旋,再整体左旋

      当不平衡节点的右子树的左子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

# include <stdio.h>
# include <stdbool.h>
# include <stdlib.h>
# include <math.h>typedef struct TreeNode { // 直接定义结构体类型int data; // 数据节点struct TreeNode* left; // 指向左子树,现在可以使用 TreeNodestruct TreeNode* right; // 指向右子树,现在可以使用 TreeNode
} TreeNode, * PTreeNode; // 注意这里去掉了*号,因为TreeNode现在已经是结构体类型// 记录平衡二叉树
bool BalanceTrue = false;
// 最小不平衡子树地址
TreeNode* rjt = NULL;// 检查二叉树是否平衡,若不平衡 BalanceTrue 为 true
int checkTreeBalance(TreeNode* root) {if (NULL == root) { return 0; }int x = checkTreeBalance(root->left);int y = checkTreeBalance(root->right);// 若检测到最小二叉树后,不进行后面的检查if (BalanceTrue) return 0;int xx = abs(x - y);if (xx > 1) {// 左子树 和 右子树 相差大于1 , 二叉树不平衡BalanceTrue = true;rjt = root;}return (x > y ? x + 1 : y + 1);
}// 返回二叉树树高
int treeHeight(TreeNode* root) {if (NULL == root) return 0;int ll = treeHeight(root->left);int rr = treeHeight(root->right);return (ll > rr ? ll + 1 : rr + 1);
}// 父节点查询
TreeNode* queryTopData(TreeNode* root, int data) {// 空地址异常抛出if (NULL == root) return NULL;// top: 父节点 ,如果为Null, 该节点为父节点// tmp: 遍历查询节点 TreeNode* top = NULL;TreeNode* tmp = root;while (tmp != NULL) {if (data == tmp->data) {// 节点为 返回Nullif (NULL == top) return NULL;return top;}top = tmp;if (data > tmp->data) {tmp = tmp->right;}else if (data < tmp->data) {tmp = tmp->left;}}return NULL;
}// 左左旋转 - 右旋
bool turnLL(TreeNode** root, TreeNode* notBalanceRoot) {if ((*root) != notBalanceRoot) {printf("左左旋转,非根节点\n");// 非根节点TreeNode* lleft = notBalanceRoot->left;TreeNode* lright = lleft->right;// 查找父节点TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);if (NULL == fdata) return false;lleft->right = notBalanceRoot;notBalanceRoot->left = lright;if (notBalanceRoot == fdata->left) {fdata->left = lleft;}else if (notBalanceRoot == fdata->right) {fdata->right = lleft;}return true;}else {// 根节点printf("左左旋转,是根节点\n");TreeNode* lleft = notBalanceRoot->left;TreeNode* absroot = lleft;TreeNode* lright = lleft->right;lleft->right = notBalanceRoot;notBalanceRoot->left = lright;(*root) = absroot;return true;}}// 左右旋转
bool turnLR(TreeNode** root, TreeNode* notBalanceRoot) {if ((*root) != notBalanceRoot) {printf("左右旋转,非根节点");TreeNode* lleft = notBalanceRoot->left;TreeNode* leftRight = lleft->right;TreeNode* leftRightLeft = leftRight->left;// 第一次调整leftRight->left = lleft;lleft->right = leftRightLeft;notBalanceRoot->left = leftRight;// 查找父节点TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);//if (NULL != fdata) printf("fdata: %d\n",fdata->data);// 第二次调整lleft = notBalanceRoot->left;leftRight = lleft->right;lleft->right = notBalanceRoot;notBalanceRoot->left = leftRight;if (notBalanceRoot == fdata->left) {fdata->left = lleft;}else if (notBalanceRoot == fdata->right) {fdata->right = lleft;}}else {printf("左右旋转,是根节点\n");TreeNode* lleft = notBalanceRoot->left;TreeNode* leftRight = lleft->right;TreeNode* leftRightLeft = leftRight->left;// 第一次调整leftRight->left = lleft;lleft->right = leftRightLeft;notBalanceRoot->left = leftRight;// 第二次调整lleft = notBalanceRoot->left;leftRight = lleft->right;lleft->right = notBalanceRoot;notBalanceRoot->left = leftRight;(*root) = lleft;}
}// 右左旋转
bool turnRL(TreeNode** root, TreeNode* notBalanceRoot) {TreeNode* rright = notBalanceRoot->right;TreeNode* rightLeft = rright->left;TreeNode* rightLeftRight = rightLeft->right;// 第一次调整rightLeft->right = rright;rright->left = rightLeftRight;notBalanceRoot->right = rightLeft;// 查找父节点TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);//if (NULL != fdata) printf("fdata: %d\n",fdata->data);// 第二次调整rright = notBalanceRoot->right;rightLeft = rright->left;rright->left = notBalanceRoot;notBalanceRoot->right = rightLeft;if ((*root) != notBalanceRoot) {printf("右左旋转,非根节点\n");if (notBalanceRoot == fdata->left) {fdata->left = rright;}else if (notBalanceRoot == fdata->right) {fdata->right = rright;}}else {printf("右左旋转,是根节点\n");(*root) = rright;}
}// 右右旋转
bool turnRR(TreeNode** root, TreeNode* notBalanceRoot) {if ((*root) != notBalanceRoot) {printf("右右旋转,非根节点");TreeNode* rright = notBalanceRoot->right;TreeNode* rleft = rright->left;// 查找父节点TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);if (NULL != fdata) printf("fdata: %d\n", fdata->data);rright->left = notBalanceRoot;notBalanceRoot->right = rleft;if (notBalanceRoot == fdata->left) {fdata->left = rright;}else if (notBalanceRoot == fdata->right) {fdata->right = rright;}}else {// 右右旋转,是根节点printf("右右旋转,是根节点\n");TreeNode* rright = notBalanceRoot->right;TreeNode* absroot = rright;TreeNode* rleft = rright->left;rright->left = notBalanceRoot;notBalanceRoot->right = rleft;(*root) = absroot;}
}// 二叉树前序遍历
void Print1(TreeNode* root) {if (NULL == root) return;printf("%d\t", root->data);Print1(root->left);Print1(root->right);
}// 二叉树中序遍历
void Print2(TreeNode* root) {if (NULL == root) return;Print2(root->left);printf("%d\t", root->data);Print2(root->right);
}// 二叉树后续遍历
void Print3(TreeNode* root) {if (NULL == root) return;Print3(root->left);Print3(root->right);printf("%d\t", root->data);
}// 插入二叉树节点
TreeNode* addNode(TreeNode* root, int data) {if (NULL == root) {// 头节点插入TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));if (NULL == Node) {printf("新节点申请内存失败\n");return NULL;}Node->data = data;Node->left = NULL;Node->right = NULL;return Node;}TreeNode* tmp = root;TreeNode* top = NULL;// 找到合适的最尾巴节点while (NULL != tmp) {top = tmp;if (tmp->data == data) {printf("已经存在该节点,节点地址: %p\n", tmp);return root;}if (tmp->data < data) {tmp = tmp->right;}else if (tmp->data > data) {tmp = tmp->left;}}TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));if (NULL == Node) {printf("申请新节点内存失败\n");return root;}Node->data = data;Node->left = NULL;Node->right = NULL;// 链接节点if (data > top->data) {top->right = Node;}else if (data < top->data) {top->left = Node;}return root;
}// 删除二叉排序树节点
bool DeleteTreeNode(TreeNode** TreeRoot, int data) {if (NULL == (*TreeRoot)) return false;printf("删除节点: %d\n", data);TreeNode* tmp = (*TreeRoot);TreeNode* top = NULL;while (tmp != NULL) {if (tmp->data == data) {// 叶子节点if ((NULL == tmp->left) && (NULL == tmp->right)) {// 叶子节点if (NULL == top) {// 仅有根节点的叶子节点free(tmp);return true;}else {// 其他的叶子节点TreeNode* lastNode = top;if (tmp == lastNode->left) {lastNode->left = NULL;}else if (tmp == lastNode->right) {lastNode->right = NULL;}free(tmp);return true;}}else {// 非叶子节点// 算法为: // 默认算法为: 1.  当删除该节点时,获取该树右子树最左子节点//             2.  当右子树为空时,此时应该获取左子树最右端子节点if (NULL != tmp->right) {// 方案 1TreeNode* tmp2 = tmp->right;TreeNode* top2 = NULL;// 找到最后一个节点while (tmp2->left != NULL) {top2 = tmp2;tmp2 = tmp2->left;}// 删除老的节点tmp->data = tmp2->data;// 只有右子树节点 没有左子树节点if (NULL == top2) {tmp->right = NULL;}else {top2->left = NULL;}free(tmp2);}else {// 方案 2TreeNode* tmp2 = tmp->left;TreeNode* top2 = NULL;// 找到最后一个节点while (tmp2->right != NULL) {tmp2 = tmp2->right;}// 删除老的节点tmp->data = tmp2->data;if (NULL == top2) {tmp->left = NULL;}else {top2->right = NULL;}free(tmp2);}}}else {top = tmp;if (data > tmp->data) {tmp = tmp->right;}else {tmp = tmp->left;}}}return false;
}// 二叉树平衡调整
bool treeBalance(TreeNode** root) {checkTreeBalance((*root));while (BalanceTrue) {printf("二叉树不平衡,最小不平衡子树数据结点: %d\n", rjt->data);TreeNode* tmp;if (1 < treeHeight(rjt->left) - treeHeight(rjt->right)) {// 对于不平衡二叉树而言,左子树比右子树高////printf("左\n");if (rjt->left != NULL) {tmp = rjt->left;int ll = treeHeight(tmp->left);int rr = treeHeight(tmp->right);if (ll > rr) {// 对于不平衡子树 左子树 而言, 左子树比右子树高// 左左旋转turnLL(root, rjt);}else {// 对于不平衡子树 左子树 而言, 右子树比左子树高// 左右旋转//turnLR(root, rjt);}}}else if (1 < treeHeight(rjt->right) - treeHeight(rjt->left)) {// 对于不平衡二叉树而言,右子树比左子树高////printf("右\n");if (rjt->right != NULL) {tmp = rjt->right;int ll = treeHeight(tmp->left);int rr = treeHeight(tmp->right);if (ll > rr) {//右左旋转turnRL(root, rjt);}else {//右右旋转turnRR(root, rjt);}}}BalanceTrue = false;checkTreeBalance((*root));printf("二叉树调整平衡后数据结点:\n");printf("前序遍历:");Print1(*root);printf("\n");printf("中序遍历:");Print2(*root);printf("\n");printf("\n");}}int main() {TreeNode* root = NULL;printf("平衡二叉树插入测试\n");int nums[] = { 65,60,70,55,40,63,69,66,68,77 };int i;for (i = 0; i < sizeof(nums) / sizeof(int); i++) {printf("插入数据: %d\n", nums[i]);root = addNode(root, nums[i]);if (NULL == root) {printf("首节点申请失败");return -1;}treeBalance(&root);}printf("\n当前二叉树遍历\n");printf("前序遍历:");Print1(root);printf("\n");printf("中序遍历:");Print2(root);printf("\n");//return 0;printf("\n\n平衡二叉树删除测试\n");for (i = 2; i < 5; i++) {DeleteTreeNode(&root, nums[i]);treeBalance(&root);}printf("\n当前二叉树遍历\n");printf("前序遍历:");Print1(root);printf("\n");printf("中序遍历:");Print2(root);printf("\n");return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

删除节点

  • 如果节点是叶子节点
    • 如果叶子节点是根节点,则直接释放该节点的内存。
    • 如果叶子节点不是根节点,则需要在父节点中删除该节点,并释放该节点的内存。
  • 如果节点不是叶子节点
    • 如果节点有右子树,则找到右子树中最左边的节点(即右子树的最小值节点),用这个节点的值替换当前节点的值,然后删除那个最小值节点。通过替代节点的父节点将替代节点设置为NULL,并释放替代节点的内存。
    • 如果节点没有右子树(即只有左子树),则找到左子树中最右边的节点(即左子树的最大值节点),用这个节点的值替换当前节点的值,然后删除那个最大值节点。通过替代节点的父节点将替代节点设置为NULL,并释放替代节点的内存。
6、红黑树

红黑树也是一种自平衡的二叉查找树,以前也叫做平衡二叉B树(Symmetric Binary B-tree)。

  • 它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色每一个节点可以是红或者黑;

  • 红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的

  • 红黑树增删改查的性能都很好

平衡二叉树红黑树
区别高度平衡
当左右子树高度差超过1时,通过旋转保持平衡
不是高度平衡的
条件: 特有的红黑规则

红黑规则

  • 每一个节点或是红色的,或者是黑色的 【节点是**RED** 或者**BLACK**】
  • 根节点必须是黑色 【根节点必须是**BLACK**】
  • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶子节点,每个叶子节点(Nil)是黑色的【叶子节点(外部节点,空节点)都是**BLACK**】
  • 如果某一个节点是红色,那么它的父节点和子节点必须是黑色(不能出现两个红色节点相连的情况)【RED 节点的父节点和子节点都是**BLACK**】
  • 对每一个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

在这里插入图片描述

红黑树添加节点的规则

  • 默认颜色: 添加节点默认是红色的(效率高)

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>#define RED 0
#define BLACK 1typedef struct RBNode {int color; // 节点颜色int data;  // 数据struct RBNode* left;struct RBNode* right;struct RBNode* parent;
} RBNode, * RBTree;// 函数声明
RBNode* createNode(int data);
void insertFixup(RBTree* tree, RBNode* node);
RBNode* insertNode(RBTree* tree, int data);
void leftRotate(RBNode** root, RBNode* x);
void rightRotate(RBNode** root, RBNode* y);
void Print(RBNode* root);// 创建新节点
RBNode* createNode(int data) {RBNode* node = (RBNode*)malloc(sizeof(RBNode));if (node){node->data = data;node->color = RED;node->left = NULL;node->right = NULL;node->parent = NULL;return node;}return;
}// 插入新节点并修复红黑树
RBNode* insertNode(RBTree* tree, int data) {RBNode* node = createNode(data);if (*tree == NULL) {*tree = node;node->color = BLACK;}else {RBNode* current = *tree;RBNode* parent = NULL;while (current != NULL) {parent = current;if (node->data < current->data) {current = current->left;}else {current = current->right;}}node->parent = parent;if (node->data < parent->data) {parent->left = node;}else {parent->right = node;}insertFixup(tree, node);}return node;
}// 插入修复
void insertFixup(RBTree* tree, RBNode* node) {//父亲节点RBNode* parentOfNode = NULL;//祖父节点RBNode* grandparentOfNode = NULL;//只有父亲是红色的才需要操作,父亲是黑色的无需任何操作while (node != *tree && node->parent->color == RED) {//父亲节点parentOfNode = node->parent;//祖父节点grandparentOfNode = parentOfNode->parent;//父亲节点是祖父的左子节点if (parentOfNode == grandparentOfNode->left) {//叔叔节点RBNode* uncleOfNode = grandparentOfNode->right;//叔叔节点存在并是红色if (uncleOfNode && uncleOfNode->color == RED) {//父亲黑色,叔叔黑色,祖父红色grandparentOfNode->color = RED;parentOfNode->color = BLACK;uncleOfNode->color = BLACK;//祖父作为当前节点node = grandparentOfNode;}else {//叔叔是黑色,并且当前节点是父的右孩子 - 左右LR旋转if (node == parentOfNode->right) {//把父为当前节点node = parentOfNode;//左旋leftRotate(tree, node);}//叔叔是黑色,并且当前节点是父的左孩子 - 左左LL旋转 //即使叔叔是黑色,并且当前节点是父的右孩子,把父为当前节点进行左旋之后,也会变成叔叔是黑色,并且当前节点是父的左孩子的情况//父亲黑色,祖父红色parentOfNode->color = BLACK;grandparentOfNode->color = RED;//以祖父为支点右旋rightRotate(tree, grandparentOfNode);                }}else {//父亲节点是祖父的右子节点RBNode* uncleOfNode = grandparentOfNode->left;if (uncleOfNode && uncleOfNode->color == RED) {grandparentOfNode->color = RED;parentOfNode->color = BLACK;uncleOfNode->color = BLACK;node = grandparentOfNode;}else {//叔叔是黑色,并且当前节点是父的左孩子 - 右左RL旋转if (node == parentOfNode->left) {node = parentOfNode;rightRotate(tree, node);}//叔叔是黑色,并且当前节点是父的右孩子 - 右右LL旋转//即使叔叔是黑色,并且当前节点是父的左孩子,把父为当前节点进行右旋之后,也会变成叔叔是黑色,并且当前节点是父的右孩子的情况parentOfNode->color = BLACK;grandparentOfNode->color = RED;leftRotate(tree, grandparentOfNode);}}}//根只能是黑色的(*tree)->color = BLACK;
}// 左旋
void leftRotate(RBNode** root, RBNode* x) {RBNode* y = x->right;//建立x、y->left的关系x->right = y->left;if (y->left != NULL) {y->left->parent = x;}//建立x->parent和y的关系y->parent = x->parent;if (x->parent == NULL) {*root = y;}else if (x == x->parent->left) {x->parent->left = y;}else {x->parent->right = y;}//建立x、y的关系y->left = x;x->parent = y;
}// 右旋
void rightRotate(RBNode** root, RBNode* y) {RBNode* x = y->left;//建立y、x->right的关系y->left = x->right;if (x->right != NULL) {x->right->parent = y;}//建立y->parent和x的关系x->parent = y->parent;if (y->parent == NULL) {*root = x;}else if (y == y->parent->left) {y->parent->left = x;}else {y->parent->right = x;}//建立y,x关系x->right = y;y->parent = x;
}// 二叉树中序遍历
void Print(RBNode* root) {if (NULL == root) return;Print(root->left);printf("%d - %d\t", root->data, root->color);Print(root->right);
}// 主函数或其他函数可以调用这些函数来操作红黑树
int main() {RBTree tree = NULL;int nums[9] = { 20,18,23,22,17,24,19,15,14 };int i;for (i = 0; i < 9; i++){insertNode(&tree, nums[i]);}Print(tree);return 0;
}

6.2.2 二叉树的性质

  1. 任意一棵树,若结点数量为n, 则边的数量为n − 1。
  2. 非空二叉树上第i层上至多有2i-1个结点
  3. 高度为h的二叉树至多有2h-1个结点
  4. 非空二叉树上的叶子结点数等于度为2的结点数加1,即n0= n2+ 1
  5. 具有 n个结点的完全二叉树的深度为 ⌊ log ⁡ 2 n ⌋ + 1 \left \lfloor \log_2 n \right \rfloor + 1 log2n+1
  6. 对完全二叉树按从上到下、从左到右的顺序依次编号1 , 2… ∗ , n,则有以下关系:
    • 当 i = 1 时, 则结点 i 是二叉树的根-无双亲; 当 i > 1 时, 结点 i 的双亲的编号为 i / 2 ,即当 i 为偶数时,它是双亲的左孩子;当 i 为奇数时,它是双亲的右孩子。
    • 当 2i ≤ n 时,结点 i 的左孩子编号为 2i , 否则无左孩子。
    • 当 2i+1 ≤ n 时,结点 i 的右孩子编号为2i + 1,否则无右孩子。

在这里插入图片描述

6.2.3 二叉树的存储结构

  • 顺序存储结构: 一种极端的情况,一棵深度为k的右斜树,只有k个结点,却需要分配2k-1个存储单元,造成明显的浪费,所以顺序存储结构一般只用于完全二叉树

    #define MAX_TREE_SIZE 100 //二义树的最大结点数
    typedef char TElementType;
    typedef TElementType SqBiTree[MAX_TREE_SIZE];
    SqBiTree bt;
    

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
    #include<math.h>
    #define MAX_TREE_SIZE 100
    #define CHAR 1#define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */#if CHAR
    typedef char TElemType;
    TElemType Nil = ' '; /* 设字符型以空格符为空 */
    #else
    typedef int TElemType;
    TElemType Nil = 0; /* 设整型以0为空 */
    #endif#define MAX_TREE_SIZE 100 /* 二叉树的最大结点数 */
    typedef TElemType SqBiTree[MAX_TREE_SIZE]; /* 0号单元存储根结点 */Status InitBiTree(SqBiTree T); /* 构造空二叉树T */
    Status CreateBiTree(SqBiTree T); /* 按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T */
    Status BiTreeEmpty(SqBiTree T); /* 判空 */
    int BiTreeDepth(SqBiTree T); /*求深度*/
    Status Root(SqBiTree T, TElemType* e); /* 求根节点 */
    void Print_BiTree(SqBiTree T); /* 打印 */int main()
    {Status i;TElemType e;SqBiTree T;InitBiTree(T);CreateBiTree(T);printf("打印二叉树的数据\n");Print_BiTree(T);printf("建立二叉树后,树空否?%d(1:是 0:否) 树的深度=%d\n", BiTreeEmpty(T), BiTreeDepth(T));i = Root(T, &e);if (i)
    #if CHARprintf("二叉树的根为:%c\n", e);
    #elseprintf("二叉树的根为:%d\n", e);
    #endifelseprintf("树空,无根\n");return 0;
    }Status InitBiTree(SqBiTree T)
    { /* 构造空二叉树T。因为T是固定数组,不会改变,故不需要& */int i;for (i = 0; i < MAX_TREE_SIZE; i++)T[i] = Nil; /* 初值为空 */return OK;
    }Status CreateBiTree(SqBiTree T)
    { /* 按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T */int i = 0;
    #if CHARint l;char s[MAX_TREE_SIZE];printf("请按层序输入结点的值(字符),空格表示空结点,结点数≤%d:\n", MAX_TREE_SIZE);gets(s); /* 输入字符串 */l = strlen(s); /* 求字符串的长度 */for (; i < l; i++) /* 将字符串赋值给T */{T[i] = s[i];if (i != 0 && T[(i + 1) / 2 - 1] == Nil && T[i] != Nil) /* 此结点(不空)无双亲且不是根 */{printf("出现无双亲的非根结点%c\n", T[i]);exit(ERROR);}}for (i = l; i < MAX_TREE_SIZE; i++) /* 将空赋值给T的后面的结点 */T[i] = Nil;
    #elseprintf("请按层序输入结点的值(整型),0表示空结点,输999结束。结点数≤%d:\n", MAX_TREE_SIZE);while (1){scanf("%d", &T[i]);if (T[i] == 999)break;if (i != 0 && T[(i + 1) / 2 - 1] == Nil && T[i] != Nil) /* 此结点(不空)无双亲且不是根 */{printf("出现无双亲的非根结点%d\n", T[i]);exit(ERROR);}i++;}while (i < MAX_TREE_SIZE){T[i] = Nil; /* 将空赋值给T的后面的结点 */i++;}
    #endifreturn OK;
    }Status BiTreeEmpty(SqBiTree T)
    { /* 初始条件: 二叉树T存在 *//* 操作结果: 若T为空二叉树,则返回TRUE,否则FALSE */if (T[0] == Nil) /* 根结点为空,则树空 */return TRUE;elsereturn FALSE;
    }int BiTreeDepth(SqBiTree T)
    //高度为h的二叉树至多有2^h-1个结点 - n<=pow(2,h)-1
    { /* 初始条件: 二叉树T存在。操作结果: 返回T的深度 */int i, j = -1;for (i = MAX_TREE_SIZE - 1; i >= 0; i--) /* 找到最后一个结点 */if (T[i] != Nil)break;i++; /* 为了便于计算 */doj++;while (i >= pow(2, j));//计算2的J次幂,返回double值。因为i<=pow(2,j)-1,因此i<pow(2,j).return j;//返回二叉树的深度
    }Status Root(SqBiTree T, TElemType* e)
    { /* 初始条件: 二叉树T存在 *//* 操作结果:  当T不空,用e返回T的根,返回OK;否则返回ERROR,e无定义 */if (BiTreeEmpty(T)) /* T空 */return ERROR;else{*e = T[0];//返回T的根,也就是第一个节点return OK;}
    }void Print_BiTree(SqBiTree T)
    {int i;for (i = 0; i < 100; ++i){if (T[i] != '\0')
    #if CHARprintf("%c", T[i]);
    #elseprintf("%d", T[i]);
    #endif}printf("\n");
    }
    
  • 链式存储结构

    二叉树的结点 由一个数据元素和分别指向其 左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包含3个域:数据域和左、右指针域。有时,为了便于找到结点的双亲,则还可在结点结构中增加一个指向其双亲结点的指针域。利用这两种结点结构所得二叉树的存储结构分别称之为二叉链表三叉链表

    在这里插入图片描述

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS 1
    #include <stdio.h>
    #include <stdlib.h>#define MAX_TREE_SIZE 100typedef char TElementType;
    typedef struct BiTNode {TElementType data;struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
    }BiTNode, * BiTree;//先序
    BiTree CreateBiTree() {char ch;if (scanf(" %c", &ch) != 1) { // 检查输入是否成功// 输入失败,可能是文件结束符或错误输入exit(EXIT_FAILURE);}if (ch != '#') {BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存if (node == NULL) {// 内存分配失败perror("内存分配失败");exit(EXIT_FAILURE);}node->data = ch;node->lchild = CreateBiTree(); // 递归创建左子树node->rchild = CreateBiTree(); // 递归创建右子树return node;}else {return NULL;}
    }//中序
    void Print_Tree(BiTree T)
    {if (T){		Print_Tree(T->lchild);printf("%c  ", T->data);Print_Tree(T->rchild);}
    }int main()
    {BiTree T = NULL;T = CreateBiTree(); // abc##de#g##f###Print_Tree(T); // c  b  e  g  d  f  areturn 0;
    }
    

6.3 遍历二叉树和线索二叉树

6.3.1 遍历二叉树

  • 先(根)序遍历

    (1) 访问根结点; (2) 先序遍历左子树; (3) 先序遍历右子树

  • 中(根)序遍历

    (1) 中序遍历左子树; (2) 访问根结点; (3) 中序遍历右子树

  • 后(根)序遍历

    (1) 后序遍历左子树; (2) 后序遍历右子树; (3) 访问根结点

  • 层序遍历

    层序遍历以树的根节点开始,按照从上到下、从左到右的顺序逐层遍历树中的节点。

    过程:

    1. 创建一个队列,并将根节点入队。
    2. 当队列不为空时,执行以下步骤:
      1. 从队列中取出一个节点,访问该节点。
      2. 将该节点的所有子节点(如果存在)依次入队。
    3. 如果队列为空,则表示遍历结束。
/*  递归  */
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>#define MAX_TREE_SIZE 100
#define MAXQSIZE 10#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define ERROR -2
#define TRUE 1
#define FALSE 0
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */typedef char TElementType;
typedef struct BiTNode {TElementType data;struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
}BiTNode, * BiTree;typedef struct {BiTNode** base; // 队列存储的是TreeNode*的指针int front;int rear;
} SqQueue;Status InitQueue(SqQueue* Q) {Q->base = (BiTNode**)malloc(MAXQSIZE * sizeof(BiTNode*));if (!Q->base) exit(1); // 使用exit(1)表示错误退出Q->front = Q->rear = 0;return OK;
}// 判断队列是否为空
int IsEmpty(SqQueue* q) {return q->rear <= q->front;
}Status EnQueue(SqQueue* Q, BiTNode* node) {if ((Q->rear + 1) % MAXQSIZE == Q->front) {return OVERFLOW; // 队列已满,返回OVERFLOW}Q->base[Q->rear] = node;Q->rear = (Q->rear + 1) % MAXQSIZE;return OK;
}BiTNode* DeQueue(SqQueue* Q) {if (Q->front == Q->rear) {return ERROR; // 队列为空,返回ERROR}BiTNode* dequeuedNode = Q->base[Q->front]; // 使用引用来修改e的值Q->front = (Q->front + 1) % MAXQSIZE;return dequeuedNode;
}void DestroyQueue(SqQueue* Q) {free(Q->base);Q->base = NULL;Q->front = Q->rear = 0;
}//先序
BiTree CreateBiTree() {char ch;if (scanf(" %c", &ch) != 1) { // 检查输入是否成功// 输入失败,可能是文件结束符或错误输入exit(EXIT_FAILURE);}if (ch != '#') {BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存if (node == NULL) {// 内存分配失败perror("内存分配失败");exit(EXIT_FAILURE);}node->data = ch;node->lchild = CreateBiTree(); // 递归创建左子树node->rchild = CreateBiTree(); // 递归创建右子树return node;}else {return NULL;}
}Status PrintElement(TElementType e)
{printf("%c ", e);return OK;
}//先序遍历
Status PreOrderTraverse(BiTree T, Status(* PrintElement)(TElementType e))
{if (T){		if (PrintElement(T->data))if (PreOrderTraverse(T->lchild,PrintElement))if (PreOrderTraverse(T->rchild,PrintElement)) return OK;return OK;}else{return OK;}
}//中序遍历
Status InOrderTraverse(BiTree T, Status(*PrintElement)(TElementType e))
{if (T){if (InOrderTraverse(T->lchild, PrintElement))if (PrintElement(T->data))if (InOrderTraverse(T->rchild, PrintElement)) return OK;return OK;}else{return OK;}
}//后序遍历
Status PostOrderTraverse(BiTree T, Status(*PrintElement)(TElementType e))
{if (T){if (PostOrderTraverse(T->lchild, PrintElement))if (PostOrderTraverse(T->rchild, PrintElement)) if (PrintElement(T->data)) return OK;return OK;}else{return OK;}
}//层序遍历
void LevelOrderTraversal(BiTree T, Status(*PrintElement)(TElementType e)) {if (T == NULL) return;SqQueue Q;InitQueue(&Q);	//初始化辅助队列EnQueue(&Q, T);	//将根节点入队while (!IsEmpty(&Q)) {	//队列不空则循环BiTNode* frontNode = DeQueue(&Q);	//队头结点出队PrintElement(frontNode->data);	//访问出队结点if (frontNode->lchild != NULL) {EnQueue(&Q, frontNode->lchild);	//左子树不空,则左子树根节点入队}if (frontNode->rchild != NULL) {EnQueue(&Q, frontNode->rchild);	//右子树不空,则右子树根节点入队}}
}int main()
{BiTree T = NULL;T = CreateBiTree(); // abc##de#g##f###printf("----------先序---------------\n");PreOrderTraverse(T, PrintElement); // a  b  c  d  e  g  fprintf("\n----------中序---------------\n");InOrderTraverse(T, PrintElement); // c  b  e  g  d  f  aprintf("\n----------后序---------------\n");PostOrderTraverse(T, PrintElement); // c  g  e  f  d  b  aprintf("\n----------层序---------------\n");LevelOrderTraversal(T, PrintElement); // a  b  c  d  e  f  greturn 0;
}

非递归的思路:

先序:

在这里插入图片描述

中序:

在这里插入图片描述

后序:

在这里插入图片描述

/*  非递归  */
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>#define MAX_TREE_SIZE 100
#define MAXQSIZE 10#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define ERROR -2
#define TRUE 1
#define FALSE 0typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */typedef char TElementType;
typedef struct BiTNode {TElementType data;struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
}BiTNode, * BiTree;// 模拟栈结构
typedef struct Stack {BiTNode* nodes[100]; // 假设最多100个节点int top;
} Stack;// 初始化栈
void StackInit(Stack* s) {s->top = -1;
}// 判断栈是否为空
int IsEmpty(Stack* s) {return s->top == -1;
}// 压栈
void Push(Stack* s, BiTNode* node) {if (s->top < 99) { // 防止栈溢出s->nodes[++(s->top)] = node;}
}// 出栈
BiTNode* Pop(Stack* s) {if (!IsEmpty(s)) {return s->nodes[(s->top)--];}return NULL;
}//先序
BiTree CreateBiTree() {char ch;if (scanf(" %c", &ch) != 1) { // 检查输入是否成功// 输入失败,可能是文件结束符或错误输入exit(EXIT_FAILURE);}if (ch != '#') {BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存if (node == NULL) {// 内存分配失败perror("内存分配失败");exit(EXIT_FAILURE);}node->data = ch;node->lchild = CreateBiTree(); // 递归创建左子树node->rchild = CreateBiTree(); // 递归创建右子树return node;}else {return NULL;}
}Status PrintElement(TElementType e)
{printf("%c ", e);return OK;
}// 非递归先序遍历:	
void PreOrderTraverse(BiTNode* root) {if (root == NULL) {return;}Stack stack;StackInit(&stack);Push(&stack, root);while (!IsEmpty(&stack)) {root = Pop(&stack);PrintElement(root->data);if (root->rchild != NULL) {Push(&stack, root->rchild);}if (root->lchild != NULL) {Push(&stack, root->lchild);}}
}//中序遍历
Status InOrderTraverse(BiTNode* head) {if (head == NULL) {return;}Stack stack;StackInit(&stack);BiTNode* current = head;while (current != NULL || !IsEmpty(&stack)) {// 将当前节点压入栈中,并遍历其左子树while (current != NULL) {Push(&stack, current);current = current->lchild;}// 访问栈顶节点,并遍历其右子树current = Pop(&stack);PrintElement(current);current = current->rchild;}
}//后序遍历
Status PostOrderTraverse(BiTNode* root) {if (root == NULL) {return;}Stack s1, s2;StackInit(&s1);StackInit(&s2);Push(&s1, root);while (!IsEmpty(&s1)) {root = Pop(&s1);Push(&s2, root);if (root->lchild != NULL) {Push(&s1, root->lchild);}if (root->rchild != NULL) {Push(&s1, root->rchild);}}while (!IsEmpty(&s2)) {PrintElement(Pop(&s2)->data);}
}int main()
{BiTree T = NULL;T = CreateBiTree(); // abc##de#g##f###printf("----------先序非递归---------------\n");PreOrderTraverse(T, PrintElement); // a  b  c  d  e  g  fprintf("\n----------中序非递归---------------\n");InOrderTraverse(T, PrintElement); // c  b  e  g  d  f  aprintf("\n----------后序非递归---------------\n");PostOrderTraverse(T, PrintElement); // c  g  e  f  d  b  areturn 0;
}

参考:

教材:

严蔚敏《数据结构》(C语言版).pdf

离散数学及其应用(原书第8版) (Kenneth H.Rosen) .pdf

《代码随想录》回溯算法(V3.0).pdf

视频:

https://www.bilibili.com/video/BV1Zf4y1a77g/?spm_id_from=333.788&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV1b7411N798/?p=51&spm_id_from=333.880.my_history.page.click&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV1pu411c7pf/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV17F411T7Ao?p=195&vd_source=a89593e8d33b31a56b894ca9cad33d33

博客:

https://blog.csdn.net/Real_Fool_/article/details/113930623

https://blog.csdn.net/m0_73633088/article/details/133443742

https://blog.csdn.net/wangrunmin/article/details/7831318

https://blog.csdn.net/Colorful___/article/details/133603913

https://blog.51cto.com/u_13360/6732847

https://zhuanlan.zhihu.com/p/600665635

https://blog.csdn.net/crr411422/article/details/129932889

https://blog.51cto.com/u_15773967/6148952

https://blog.csdn.net/2401_82584055/article/details/138349195

https://mp.weixin.qq.com/s?__biz=Mzg5MjkxNTA5MA==&mid=2247484467&idx=2&sn=3ea1749b1c7c301289a40dac4497e87e&chksm=c03798cef74011d8ead16e27ebea8e3a1df2a9a0242385d328f0ec05973e9b12ef1fa7254e14&scene=27

工具:

https://www.cs.usfca.edu/~galles/visualization/DisjointSets.html

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

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

相关文章

Linux启动流程,0,1,2进程,init进程,idle进程,内核态到用户态的kernel_execve(一)

&#xff1f;是&#xff0c;如果定义了&#xff0c;就按Makefile的&#xff0c;如果如下make编译时&#xff0c;就按如下 linux内核入口 进程0在用户空间看不到&#xff0c;因为他是内核进程 进程2就是守护进程&#xff0c;维护内涵运转的 一生二&#xff0c;二生三&#xff…

Redis中Hash(哈希)类型的基本操作

文章目录 一、 哈希简介二、常用命令hsethgethexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyhincrbyfloathstrlen 三、命令小结四、哈希内部编码方式五、典型应用场景六、 字符串&#xff0c;序列化&#xff0c;哈希对比 一、 哈希简介 几乎所有的主流编程语言都提供了哈…

CANopen开源库canfestival的移植

本文记录将CANopen开源库CANfestival移植到GD32F470单片机的过程。CANopen协议理解请参考博客&#xff1a;CANopen协议的理解-CSDN博客 CANfestival开源库下载链接 CSDN链接&#xff1a; https://download.csdn.net/download/heqiunong/89774627 官网链接&#xff1a;https:/…

智能BI项目第五期

本期主要内容 系统问题分析异步化业务流程分析线程池讲解&#xff08;入门 原理 实战&#xff09;系统异步化改造开发 1.系统问题分析 当系统面临大量用户请求时&#xff0c;我们后端的 AI 处理能力有限&#xff0c;例如服务器的内存、CPU、网络带宽等资源有限&#xff0c…

基于微信小程序的游泳馆管理系统--论文源码调试讲解

2 关键技术介绍 2.1 SSM框架 开发信息管理系统的主流框架是SSM&#xff08;Spring Spring MVC MyBatis&#xff09;&#xff0c;SSM框架web层使用Spring MVC框架&#xff0c;使传输前后端数据变得简单&#xff1b;对于业务层使用Spring作为轻量级控制反转和面向切面的容器框…

redis分布式锁(看门枸机制)

分布式锁确保在同一时间只有一个节点能获得对共享资源的独占访问权限&#xff0c;从而解决并发访问问题。 Redisson锁(简称看门狗) 它可以实现锁的延长&#xff0c;确保某个线程执行完才能让其他线程进行抢锁操作 引入看门狗机制后 如何使用&#xff1f; 1、引入依赖包 <…

Java数据结构专栏介绍

专栏导读 在软件工程的世界里&#xff0c;数据结构是构建高效、可靠程序的基石。"Java数据结构"专栏致力于为Java开发者提供一个全面、深入的学习平台&#xff0c;帮助他们掌握各种数据结构的原理、实现及其在Java中的应用。通过这个专栏&#xff0c;读者将能够提升…

【第34章】Spring Cloud之SkyWalking分布式日志

文章目录 前言一、准备1. 引入依赖 二、日志配置1. 打印追踪ID2. gRPC 导出 三、完整日志配置四、日志展示1. 前端2. 后端 总结 前言 前面已经完成了请求的链路追踪&#xff0c;这里我们通过SkyWalking来处理分布式日志&#xff1b; 场景描述&#xff1a;我们有三个服务消费者…

Hive企业级调优[3]—— Explain 查看执行计划

Explain 查看执行计划 Explain 执行计划概述 EXPLAIN 命令呈现的执行计划由一系列 Stage 组成。这些 Stage 之间存在依赖关系&#xff0c;每一个 Stage 可能对应一个 MapReduce Job 或者一个文件系统的操作等。如果某 Stage 对应了一个 MapReduce Job&#xff0c;则该 Job 在 …

OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【内核通信机制】下

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 子系统开发内核 轻量系统内核&#xff08;LiteOS-M&#xff09; 轻量系统内核&#…

微信支付开发-后台统计工厂实现

一、数据库设计图 二、后端统计工厂逻辑 1、统计父抽象类 a、StatisticsHandle.php 2、统计工厂通道类 a、StatisticsFactory.php 3、查询实现类 a、答题统计(Answer.php) 三、后端统计工厂代码实现 1、统计父抽象类(StatisticsHandle.php) <?php /*** 统计父抽象类* Use…

VirtualBox 7.1.0 发布下载 - 开源跨平台虚拟化软件

VirtualBox 7.1.0 (macOS, Linux, Windows) - 开源跨平台虚拟化软件 Oracle VM VirtualBox 7 请访问原文链接&#xff1a;https://sysin.org/blog/virtualbox-7/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 2024 年 9 月 …

Redis面试真题总结(三)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 什么是缓存雪崩&#xff1f;该如何解决&#xff1f; 缓存雪崩是指…

算法课习题汇总(2)

整数划分问题 将正整数n表示成一系列正整数之和&#xff0c;nn1n2…nk(n1>n2>…>nk,k>1)。正整数n的这种表示称为正整数n的划分。 思路&#xff1a; n表示待划分数&#xff0c;m表示最大减数。 #include<iostream> using namespace std;int q(int n, int…

面试题给图例举测试用例或测试点

目录 从功能测试的角度考虑&#xff1a; 从性能角度考虑&#xff1a; 从兼容性的角度考虑&#xff1a; 从自动化角度考虑&#xff1a; 从安全性角度考虑&#xff1a; 用户体验的角度测试&#xff1a; 面试通常会有技术和人事两种&#xff0c;侧重点不一样。 今天聊一下测…

Qt日志输出及QsLog日志库

目录 Qt日志输出及QsLog日志库日志输出格式化日志普通格式化条件格式化环境变量设置格式化日志输出位置日志输出对象信息禁用输出 QsLog日志库使用方法1. 将QsLog目录添加到项目中2. 配置CMakeLists.txt文件3. 配置.pro文件4. 日志记录器的配置5. 运行程序6. 启用行号和文件名C…

有奖直播 | onsemi IPM 助力汽车电气革命及电子化时代冷热管理

在全球汽车行业向电气化和智能化转型的浪潮中&#xff0c;功率管理技术的创新和应用成为了关键驱动力。作为全球领先的半导体解决方案供应商&#xff0c;onsemi&#xff08;安森美&#xff09;致力于通过其先进的智能功率模块&#xff08;IPM&#xff09;技术&#xff0c;推动汽…

[Linux#55][网络协议] 序列化与反序列化 | TcpCalculate为例

目录 1. 理解协议 1.1 结构化数据的传输 序列化与反序列化 代码感知&#xff1a; Request 类 1. 构造函数 2. 序列化函数&#xff1a;Serialize() 3. 反序列化函数&#xff1a;DeSerialize() 补充 4. 成员变量 Response 类 1. 构造函数 2. 序列化函数&#xff1a;…

JavaWeb - 5 - 前端工程化

一.前后端分离开发 前后端混合开发 缺点&#xff1a;沟通成本高&#xff0c;分工不明确&#xff0c;不便管理&#xff0c;不便维护拓展 前后端分离开发 当前最为主流的开发模式&#xff1a;前后端分离 前后端分离开发中很重要的是API接口文档&#xff08;如&#xff1a;YApi&…

胤娲科技:谷歌DeepMind祭出蛋白质设计新AI——癌症治疗迎来曙光

在科技的浩瀚星空中&#xff0c;DeepMind的“阿尔法”家族总是能带来令人瞩目的璀璨光芒。这一次&#xff0c;它们再次以惊人的姿态&#xff0c; 将AI的触角深入到了生命的微观世界——蛋白质设计领域&#xff0c;为我们描绘了一幅未来医疗的宏伟蓝图。 想象一下&#xff0c;一…