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

树和二叉树 (一)

文章目录

  • 树和二叉树 (一)
    • 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.mzph.cn/bicheng/54710.shtml

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

相关文章

通用大模型 vs 垂直大模型:谁将赢得AI战场?

引言 在人工智能领域&#xff0c;大模型的快速发展引发了广泛的关注和讨论。大模型&#xff0c;尤其是基于深度学习和海量数据训练的模型&#xff0c;已经在多个领域展现出强大的能力。从自然语言处理、图像识别到自动驾驶和医疗诊断&#xff0c;AI大模型正深刻改变着我们的生…

基于二自由度汽车模型的汽车质心侧偏角估计

一、质心侧偏角介绍 在车辆坐标系中&#xff0c;质心侧偏角通常定义为质心速度方向与车辆前进方向的夹角。如下图所示&#xff0c;u为车辆前进方向&#xff0c;v为质心速度方向&#xff0c;u和v之间的夹角便是质心侧偏角。 质心侧偏角的作用有如下三点&#xff1a; 1、稳定性…

SVN笔记-SVN安装

SVN笔记-SVN安装 1、在windows下安装 SVN 1、准备svn的安装文件 下载地址&#xff1a;https://sourceforge.net/projects/win32svn/ 2、下载完成后&#xff0c;在相应的盘符中会有一个Setup-Subversion-1.8.17.msi的文件&#xff0c;目前最新的版本是1.8.17&#xff0c; 这里…

opencv4.5.5 GPU版本编译

一、安装环境 1、opencv4.5.5 下载地址&#xff1a;https://github.com/opencv/opencv/archive/refs/tags/4.5.5.ziphttps://gitee.com/mirrors/opencv/tree/4.5.0 2、opencv-contrib4.5.5 下载地址&#xff1a;https://github.com/opencv/opencv_contrib/archive/refs/tags/4…

Python中的数据可视化:从基础图表到高级可视化

数据可视化是数据分析和科学计算中不可或缺的一部分。它通过图形化的方式呈现数据&#xff0c;使复杂的统计信息变得直观易懂。Python提供了多种强大的库来支持数据可视化&#xff0c;如Matplotlib、Seaborn、Plotly等。本文将从基础图表入手&#xff0c;逐步介绍如何使用这些库…

【第十一章:Sentosa_DSML社区版-机器学习之分类】

目录 11.1 逻辑回归分类 11.2 决策树分类 11.3 梯度提升决策树分类 11.4 XGBoost分类 11.5 随机森林分类 11.6 朴素贝叶斯分类 11.7 支持向量机分类 11.8 多层感知机分类 11.9 LightGBM分类 11.10 因子分解机分类 11.11 AdaBoost分类 11.12 KNN分类 【第十一章&…

Java语言程序设计基础篇_编程练习题***18.33 (游戏:骑士旅途的动画)

目录 ***18.33 (游戏:骑士旅途的动画) 习题思路 代码示例 动画演示 ***18.33 (游戏:骑士旅途的动画) 为骑士旅途的问题编写一个程序&#xff0c;该程序应该允许用户将骑士放到任何一个起始正方形&#xff0c;并单击Solve按钮&#xff0c;用动画展示骑士沿着路径的移动&…

深度学习之表示学习 - 贪心逐层无监督预训练篇

引言 在人工智能的浩瀚星空中&#xff0c;深度学习以其强大的数据处理与模式识别能力&#xff0c;成为了一颗璀璨的明星。而表示学习&#xff0c;作为深度学习的核心基石之一&#xff0c;正引领着这一领域不断突破边界。表示学习旨在将原始数据转换为更加抽象、更有意义的特征…

leetcode第二十六题:删去有序数组的重复项

给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &#xff0c;你…

Rasa对话模型——做一个语言助手

1、Rasa模型 1.1 模型介绍 Rasa是一个用于构建对话 AI 的开源框架&#xff0c;主要用于开发聊天机器人和语音助手。Rasa 提供了自然语言理解&#xff08;NLU&#xff09;和对话管理&#xff08;DM&#xff09;功能&#xff0c;使开发者能够创建智能、交互式的对话系统。 1.2…

Apache Iceberg 数据类型参考表

Apache Iceberg 概述-链接 Apache Iceberg 数据类型参考表 数据类型描述实例方法注意事项BOOLEAN布尔类型&#xff0c;表示真或假true, false用于条件判断&#xff0c;例如 WHERE is_active true。确保逻辑条件的正确性。INTEGER32位有符号整数42, -7可用于计算、聚合&#xf…

【系统架构设计师】专题:中间件技术

更多内容请见: 备考系统架构设计师-核心总结目录 文章目录 一、中间件概述二、中间件特点三、中间件的分类四、中间件产品介绍一、中间件概述 中间件(middleware) 是基础软件的一大类,属于可复用软件的范畴。顾名思义,中间件处在操作系统、网络和数据库之上,应用软件的下层…

着色器ShaderMask

说明 实现一个渐变进度条&#xff0c;要求&#xff1a; 颜色渐变的过程是循序渐进的&#xff0c;而不是看起来像是将渐变条逐渐拉长了。 效果 源码 // 渐变进度条Stack(children: [// 背景色板Container(width: 300,height: 8,decoration: BoxDecoration(borderRadius: Bord…

ollama 部署教程(window、linux)

目录 一、官网 二、安装方式一&#xff1a;window10版本下载 三、安装方式二&#xff1a;linux版本docker 四、 模型库 五、运行模型 六、API服务 七、python调用 ollama库调用 langchain调用 requests调用 aiohttp调用 八、模型添加方式 1.线上pull 2.导入 GGU…

Parallels Desktop 20 for Mac 推出:完美兼容 macOS Sequoia 与 Win11 24H2

Parallels Desktop 20 for Mac 近日正式发布&#xff0c;这一新版本不仅全面支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;还在企业版中引入了一个全新的管理门户。新版本针对 Windows、macOS 和 Linux 虚拟机进行了多项改进&#xff0c;其中最引人注目的当属 Parallels …

C++编程语言:基础设施:源文件和程序(Bjarne Stroustrup)

第15章 源文件和程序 (Source Files and Programs) 目录 15.1 单独编译(Separate Compilation) 15.2 链接(Linkage) 15.2.1 文件局部名(File-Local Names) 15.2.2 头文件(Header Files) 15.2.3 一次定义原则(The One-Definition Rule) 15.2.4 标准库头文件 1…

基于YOLOv8+LSTM的商超扶梯场景下行人安全行为姿态检测识别

基于YOLOv8LSTM的商超扶梯场景下行人安全行为姿态检测识别 手扶电梯 行为识别 可检测有人正常行走&#xff0c;有人 跌倒&#xff0c;有人逆行三种行为 跌倒检测 电梯跌倒 扶梯跌倒 人体行为检测 YOLOv8LSTM。 基于YOLOv8LSTM的商超扶梯场景下行人安全行为姿态检测识别&#xf…

STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)

在研究声音、电力或任何形式的波形时&#xff0c;我们常常需要穿过表面看本质。FFT&#xff08;快速傅里叶变换&#xff09;就是这样一种强大的工具&#xff0c;它能够揭示隐藏在复杂信号背后的频率成分。本文将带你走进FFT的世界&#xff0c;了解它是如何将时域信号转化为频域…

如何将Excel表格嵌入Web网页在线预览、编辑并保存到自己服务器上?

猿大师办公助手作为一款专业级的网页编辑Office方案&#xff0c;不仅可以把微软Office、金山WPS和永中Office的Word文档内嵌到浏览器网页中实现在线预览、编辑保存等操作&#xff0c;还可以把微软Office、金山WPS和永中Office的Excel表格实现网页中在线预览、编辑并保存到服务器…

python中ocr图片文字识别样例(二)

一、说明 本次解决图片相关出现中文乱码问题&#xff0c;属于上篇文章的优化&#xff0c;前提条件依赖上篇文章的包&#xff0c;当然ocr的具体应用场景很多&#xff0c;根据自身需求进行调整 二、具体实现 2.1 代码实现&#xff1a; # -*- coding: utf-8 -*- import easyoc…