4.数据结构-树和二叉树
树和二叉树
- 4.1树和二叉树的定义
- 4.1.1树的定义
- 4.1.2树的基本术语
- 4.1.3二叉树的定义
- 4.2二叉树的性质和存储结构
- 4.2.1二叉树的性质
- 4.2.1二叉树的存储结构
- 顺序存储
- 链式存储
- 4.3遍历二叉树和线索二叉树
- 4.3.1遍历二叉树
- 根据遍历序确定二叉树
- 先序序列创建二叉链表
- 复制二叉树
- 计算二叉树的深度
- 4.3.2线索二叉树
- *线索化
- 遍历线索二叉树
- 遍历中序线索二叉树
- 4.4树和森林
- 4.4.1树的存储结构
- 4.4.2森林与二叉树的转换
- 将森林转换成二叉树c++代码
- 4.5哈夫曼树及其应用
- 4.5.1哈夫曼树的基本概念
- 4.5.2哈夫曼树的构造算法
- 哈夫曼树的构造过程
- 哈夫曼算法的实现
- 4.5.3哈夫曼编码
- 哈夫曼编码的算法实现
4.1树和二叉树的定义
4.1.1树的定义
树是 n ( n ≥ 0 ) n \ (n\ge0) n (n≥0)个结点的有限集,它或为空树 ( n = 0 ) (n=0) (n=0);或为非空树。对于非空树 T T T:
1.有且仅有一个称为根的结点
2.除根节点以外的其余节点可分为 m ( m > 0 ) m \ (m>0) m (m>0)个互不相交的有限集 T 1 , T 2 . . . , T m T_ 1,T_2...,T_m T1,T2...,Tm,其中每一个集合本身又是一棵树,并且称为根的子树。
树的结构定义是一个递归的定义,即在树的定义中又用到树的定义,它道出了树的固有特性。
4.1.2树的基本术语
结点:树中独立的一个单元。
结点的度:结点拥有的子树数称为结点的度。
树的度:树的度是树内各节点度的最大值。
叶子:度为0的结点称为叶子或终端结点。
非终端结点:度不为0的结点称为非终端结点或分支结点。除根节点以外,非终端结点也成为内部节点。
双亲和孩子:结点的子树的根称为该结点的孩子,相应地,该节点称为孩子的双亲。
兄弟:同一个双亲的孩子之间互称为兄弟
祖先:从根节点到该节点所经历分支上所有的结点。
子孙:以某节点为根的子树中的任一结点都称为该节点的子孙。
层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。
堂兄弟:双亲在同一层次的结点互为堂兄弟。
树的深度:树中结点的最大层次称为树的深度或告诉。
有序树和无序树:如果将树中的结点各子树看成从左至右是有次序的,则称该树为有序树,否则称为无序树。
森林:是 m ( m ≥ 0 ) m \ (m\ge0) m (m≥0)棵互补相交的树的集合。对树中的每个结点而言,其子树的集合即为森林。
4.1.3二叉树的定义
二叉树是 n ( n ≥ 0 ) n \ (n\ge0) n (n≥0)个结点所构成的集合,它或为空树 ( n = 0 ) (n=0) (n=0);或为非空树。对于非空树 T T T:
1.有且仅有一个称为根的结点
2.除根节点以外的其余结点分为两个互不相交的子集 T 1 , T 2 T_ 1,T_2 T1,T2,分别称为 T T T的左子树和右子树,且 T 1 , T 2 T_ 1,T_2 T1,T2本身又都是二叉树。
4.2二叉树的性质和存储结构
4.2.1二叉树的性质
性质1:在二叉树的第i层上至多又 2 i − 1 2^{i-1} 2i−1个结点 ( i ≥ 1 ) (i\ge1) (i≥1)。
性质2:深度为 k k k的二叉树至多有 2 k − 1 2^{k}-1 2k−1个结点 ( k ≥ 1 ) (k\ge1) (k≥1)。
性质3:对任何一颗二叉树 T T T,如果其终端结点数为 n 0 n_0 n0,度为2的结点数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1。
满二叉树:深度为 k k k且含有 2 k − 1 2^{k}-1 2k−1个结点的二叉树。
满二叉树的特点是:
每一层上的结点数都是最大结点数,即每一层 i i i的结点数都具有最大值 2 i − 1 2^{i-1} 2i−1。
完全二叉树:深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至 n n n一一对应时,称之为完全二叉树。
完全二叉树的特点:
1.叶子结点只可能在层次最大的两层上出现;
2.对任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为 l 或 l + 1 l或l+1 l或l+1。(因为计数是从左至右的顺序)
性质4:具有n个结点的完全二叉树的深度为 k = ⌊ l o g 2 n ⌋ + 1 k=\left \lfloor log_2n \right \rfloor +1 k=⌊log2n⌋+1
这一条性质记住:对于一个深度为 k k k的完全二叉树,它的前 k − 1 k-1 k−1层一定是一个满二叉树。
所以:
2 k − 1 − 1 < n ≤ 2 k − 1 2^{k-1}-1 < n \le2^k-1 2k−1−1<n≤2k−1
因为k为整数,由此得出 k = ⌊ l o g 2 n ⌋ + 1 k=\left \lfloor log_2n \right \rfloor +1 k=⌊log2n⌋+1。
性质5:如果对一颗有 n n n个结点的完全二叉树(其深度为 ⌊ l o g 2 n ⌋ + 1 \left \lfloor log_2n \right \rfloor +1 ⌊log2n⌋+1)的结点按层序编号(从第1层到第 ⌊ l o g 2 n ⌋ + 1 \left \lfloor log_2n \right \rfloor +1 ⌊log2n⌋+1层,每层从左到右),则对任一结点 i ( 1 ≤ i ≤ n ) i \ (1\le i \le n) i (1≤i≤n),有:
1.如果 i = 1 i=1 i=1, 则结点 i i i是二叉树的根,无双亲;如果 i > 1 i>1 i>1,则其双亲 P A R E N T ( i ) PARENT(i) PARENT(i)是结点 ⌊ i 2 ⌋ \left \lfloor \frac{i}{2} \right \rfloor ⌊2i⌋。
2.如果 2 i > n 2i>n 2i>n,则结点 i i i无左孩子(结点 i i i为叶子结点);否则其左孩子 L C H I L D ( i ) LCHILD(i) LCHILD(i)是结点 2 i 2i 2i。
3.如果 2 i + 1 > n 2i+1>n 2i+1>n,则结点 i i i无右孩子;否则其右孩子 R C H I L D ( i ) RCHILD(i) RCHILD(i)是结点 2 i + 1 2i+1 2i+1。
(对于一个结点i来说若他有左孩子,那么其编号为 2 i 2i 2i,若有右孩子,则其编号为 2 i + 1 2i+1 2i+1)
4.2.1二叉树的存储结构
顺序存储
类似线性表,二叉树的存储结构也可采用顺序存储和链式存储两种方式。
顺序存储结构使用一组地址连续的存储单元来存储数据元素,为了能够在存储结构中反应出结点之间的逻辑关系,必须将二叉树中的结点依照一定的规律安排在这组单元中。
#define MAXSIZE 100
#define TElemType int
#define Status intusing namespace std; typedef TElemType SqBiTree[MAXSIZE];
SqBiTree bt;
对于完全二叉树,只要从根起按层序存储即可,依次自上而下,自左至右存储结点元素,即:
对于一般二叉树,则将其每一个结点与完全二叉树的结点相对照,存储在一维数组的相应分量中,以’0‘表示不存在此点。
由此可见,这种顺序存储结构仅适用于完全二叉树。因为,在最坏的情况下:
一个深度为 k k k且只有k个结点的单支树就需要长度为 2 k − 1 2^k-1 2k−1的一维数组。造成了极大的浪费。
链式存储
二叉树的链表中的结点至少包含3个域:数据域和左、右指针域。
typedef struct BiTNode{TElemType data;struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
4.3遍历二叉树和线索二叉树
4.3.1遍历二叉树
遍历二叉树:是指按某条搜索路径巡防树种每个结点,使得每个结点均被访问一次,而且仅被访问一次。
先序遍历:
1.访问根结点
2.先序遍历左子树
3.先序遍历右子树
中序遍历
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
后序遍历
1.后序遍历左子树
2.后续遍历右子树
3.访问根节点
先序遍历结果:
− + a ∗ b − c d / e f -+a*b-cd/ef −+a∗b−cd/ef
中序遍历结果:
a + b ∗ c − d − e / f a+b*c-d-e/f a+b∗c−d−e/f
后序遍历结果:
a b c d − ∗ + e f / − abcd-*+ef/- abcd−∗+ef/−
#include <iostream>
#include <fstream>
#include <cstring>#define MAXSIZE 100
#define TElemType int
#define Status intusing namespace std; typedef struct BiTNode {TElemType data;struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;// 递归创建二叉树
void CreateBiTree(BiTree &T) {int value;cout << "请输入节点值(输入 -1 表示空节点):";cin >> value;if (value == -1) { // -1 代表空节点T = NULL;} else {T = new BiTNode; // 创建新节点T->data = value;cout << "输入 " << value << " 的左子节点:" << endl;CreateBiTree(T->lchild); // 递归创建左子树cout << "输入 " << value << " 的右子节点:" << endl;CreateBiTree(T->rchild); // 递归创建右子树}
}// 先序遍历(根 → 左 → 右)
void PreOrderTraversal(BiTree T) {if (T) {cout << T->data << " ";PreOrderTraversal(T->lchild);PreOrderTraversal(T->rchild);}
}// 中序遍历(左 → 根 → 右)
void InOrderTraversal(BiTree T) {if (T) {InOrderTraversal(T->lchild);cout << T->data << " ";InOrderTraversal(T->rchild);}
}// 后序遍历(左 → 右 → 根)
void PostOrderTraversal(BiTree T) {if (T) {PostOrderTraversal(T->lchild);PostOrderTraversal(T->rchild);cout << T->data << " ";}
}int main() {BiTree T;cout << "开始创建二叉树:" << endl;CreateBiTree(T);cout << "\n二叉树的先序遍历:" << endl;PreOrderTraversal(T);cout << "\n二叉树的中序遍历:" << endl;InOrderTraversal(T);cout << "\n二叉树的后序遍历:" << endl;PostOrderTraversal(T);return 0;
}
根据遍历序确定二叉树
由二叉树的先序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一颗二叉树。
由定义,二叉树的先序遍历是由先访问根结点,其次再按先序遍历方式遍历根结点的左子树,最后按先序遍历根结点的右子树。也就是说,先续遍历的第一个结点一定是二叉树的根节点。另一方面,中序遍历是先遍历左子树,然后访问根结点,最后再遍历右子树。这样,根节点在中序序列中必然分割成两个子序列,前一个子序列是根结点的左子树的中序序列,而后一个子序列是根结点的右子树的中序序列。根据这两个子序列,在先序序列中找到对应的左子序列和右子序列。在先序序列中,左子序列的第一个结点是左子树的根结点,右子序列的第一个结点是右子树的根结点。这样,就确定了二叉树的三个结点。同时,左子树和右子树的根节点又可以分别把左子序列和右子序列的划分成两个子序列,如此递归下去,当取尽先序序列中的结点时,便可以得到一颗二叉树。
同理,由二叉树的后序序列和中序序列也可以唯一确定一棵二叉树。因为,依据后序遍历和中序便利的定义,后续遍历的最后一个结点,就如同先序序列的第一个结点一样,可将中序序列分成两个子序列,分别未这个结点左子树的中序序列和右子树的中序序列,再拿出后序序列的倒数第二个结点,并继续分割中序序列,如此递归下去,当倒着取尽后序序列中的结点时,便可以得到一棵二叉树。
但是由一颗二叉树的先序序列和后序序列不能唯一确定一颗二叉树
先序序列创建二叉链表
// 递归创建二叉树
void CreateBiTree(BiTree &T) {int value;cout << "请输入节点值(输入 -1 表示空节点):";cin >> value;if (value == -1) { // -1 代表空节点T = NULL;} else {T = new BiTNode; // 创建新节点T->data = value;cout << "输入 " << value << " 的左子节点:" << endl;CreateBiTree(T->lchild); // 递归创建左子树cout << "输入 " << value << " 的右子节点:" << endl;CreateBiTree(T->rchild); // 递归创建右子树}
}
复制二叉树
与先序序列创建二叉树类似的想法。
//复制二叉树
void Copy(BiTree &T, BiTree &NewT){if(T == NULL){NewT == NULL;return;}else{NewT = new BiTNode;NewT->data = T->data;Copy(T->lchild, NewT->lchild);Copy(T->rchild, NewT->rchild);}
}
计算二叉树的深度
//统计二叉树中结点个数
int NodeCount(BiTree T){if(T == NULL) return 0;else return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}
4.3.2线索二叉树
遍历二叉树的实质是对一个非线性结构进行线性化操作,使每个结点在这些线性序列中有且仅有一个直接前驱和直接后驱。
但是,当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在任一序列中的前去和后继信息,这种信息这有遍历的动态过程中才能得到,为此引入线索二叉树来保存这些在动态过程中得到的有关前驱后继的信息。
试作如下规定:若结点有左子树,则其lchild域指示其左孩子,否则令lchilid域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild域指示其后继。为了避免混淆,尚需改变结点结构,增加两个标志域,其结点形式如下:
L T a g = { 0 l c h i l d 域指示结点的左孩子 1 l c h i l d 域指示结点的前驱 LTag= \left\{\begin{matrix} 0 \ lchild域指示结点的左孩子\\ 1 \ \ \ lchild域指示结点的 前驱 \end{matrix}\right. LTag={0 lchild域指示结点的左孩子1 lchild域指示结点的前驱
R T a g = { 0 l c h i l d 域指示结点的左孩子 1 l c h i l d 域指示结点的后继 RTag= \left\{\begin{matrix} 0 \ lchild域指示结点的左孩子\\ 1 \ \ \ lchild域指示结点的 后继 \end{matrix}\right. RTag={0 lchild域指示结点的左孩子1 lchild域指示结点的后继
线索链表的定义:
typedef struct BiThrNode {TElemType data;struct BiThrNode *lchild, *rchild;int LTag, RTag;
} BiThrNode, *BiThrTree;
其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
因为中序遍历的结果为:
a + b ∗ c − d − e / f a+b*c-d-e/f a+b∗c−d−e/f
按照中序遍历的结果在独照上图就一目了然了。
*线索化
#include <iostream>
using namespace std;// 线索二叉树结点结构
typedef struct BiThrNode {char data; // 结点数据struct BiThrNode *lchild, *rchild; // 左右孩子指针int LTag, RTag; // 线索标记:0=孩子,1=线索
} BiThrNode, *BiThrTree;// 全局变量:用于记录遍历过程中的前驱结点
BiThrTree pre = NULL;// 创建普通二叉树(先序输入,'#' 表示空结点)
void CreateBiTree(BiThrTree &T) {char ch;cin >> ch;if (ch == '#') {T = NULL;} else {T = new BiThrNode;T->data = ch;T->LTag = T->RTag = 0; // 初始化标记,表示普通孩子指针CreateBiTree(T->lchild);CreateBiTree(T->rchild);}
}// 中序线索化以结点p为根
void InThreading(BiThrTree p) {if (p) {InThreading(p->lchild); // 递归左子树// 处理左线索if (!p->lchild) {p->LTag = 1;p->lchild = pre;} else {p->LTag = 0;}// 处理右线索if (pre && !pre->rchild) {pre->RTag = 1;pre->rchild = p;} else if (pre) {pre->RTag = 0;}pre = p; // 更新前驱指针,pre最后会变成中序遍历的最后一个值InThreading(p->rchild); // 递归右子树}
}// 带头结点线索化二叉树(包装函数)
void InOrderThreading(BiThrTree &Thrt, BiThrTree T) {Thrt = new BiThrNode; // 生成头结点Thrt->LTag = 0; //树没空左孩子为树根Thrt->RTag = 1; //右孩子指针为右线索Thrt->rchild = Thrt; // 头结点右指针回指if (!T) {Thrt->lchild = Thrt; //树为空做指针也指向自己} else {Thrt->lchild = T; //头结点左孩子指向根pre = Thrt; // 头结点作为前驱InThreading(T);//在InThreading中pre最后会变成中序遍历的最后一个值pre->rchild = Thrt; pre->RTag = 1;Thrt->rchild = pre; // 头结点的右孩子指向最后一个结点,// 最后结点的后继指向头结点,所以形成了循环链表}
}// 中序遍历(非递归,利用线索)
void InOrderTraverse_Thr(BiThrTree T) {BiThrTree p = T->lchild; // 从根节点开始while (p != T) {while (p->LTag == 0) p = p->lchild; // 找到最左结点cout << p->data << " ";while (p->RTag == 1 && p->rchild != T) {p = p->rchild;cout << p->data << " ";}p = p->rchild;}
}// 主函数
int main() {BiThrTree T, Thrt;cout << "请输入二叉树的先序序列(用 # 代表空):";CreateBiTree(T);// 线索化InOrderThreading(Thrt, T);cout << "中序遍历(线索二叉树)结果:";InOrderTraverse_Thr(Thrt);cout << endl;return 0;
}
头结点在线索化二叉树中的主要作用是:
1.作为树的边界节点,指示树的开始和结束。
2.简化树的遍历过程,特别是中序遍历。
3.提高遍历效率,避免使用栈或递归。
4.形成循环链表,使得树的遍历变得更加灵活。
5.统一树的操作,简化了插入、删除和查找等操作。
因此,虽然头结点本身不存储数据,它在组织和优化树的结构方面起到了至关重要的作用。好好看代码注释!
遍历线索二叉树
*在中序线索二叉树中查找结点的前驱和后继
1.查找p指针所指结点的前驱:
若p->LTag=1,则p的左链指示前驱;
若p->LTag=0,则说明p有左子树,结点的前驱是遍历左子树时最后访问的一个结点。
中序遍历的结果是42513,2和1都有左子树,1的前驱是他遍历左子树最后一个结点也就是5,2的前驱就是遍历其左子树最后一个结点也就是4。2.查找p指针所指结点的后驱:
若p->RTag=1,则p的右链指示前驱
若p->RTag=0,则说明p有右子树,根据中序遍历的规律可知,结点的后继应是遍历其右子树时访问的第一个结点。
在先序线索二叉树中查找结点的前驱和后继
1.查找p指针所指结点的前驱:
若p->LTag=1,则p的左链指示前驱;
若p->LTag=0,则说明p有左子树,结点的前驱有两种情况:若*p是其双亲的左孩子,则其双亲为他的前驱;否则前驱是其双亲的左子树先序遍历最后访问的一个结点。2.查找p指针所指结点的后驱:
若p->RTag=1,则p的左右指示前驱
若p->RTag=0,则说明p有右子树,根据先序遍历的规律可知,结点的后继应是遍历其左子树(存在)或右子树根
在后序线索二叉树中查找结点的前驱和后继
1.查找p指针所指结点的前驱:
若p->LTag=1,则p的左链指示前驱;
若p->LTag=0,当p->RTag=0,则p的右链指示其前驱;若p->LTag=0,当p->RTag=1,则p的左链指示其前驱2.查找p指针所指结点的后驱:
若* p是二叉树的根,则其后继为空;
若 * p是其双亲的右孩子,则其后继为双亲结点;
若p是其双亲的左孩子,且 * p没有右兄弟,则其后继为双亲结点;
若p是其双亲的左孩子,且 * p有右兄弟,则其后继为双亲的右子树上按后续遍历出的第一个结点。
遍历中序线索二叉树
void InOrderTraverse_Thr(BiThrTree T) {BiThrTree p = T->lchild; // 从根节点开始while (p != T) {while (p->LTag == 0) p = p->lchild; // 找到最左结点cout << p->data << " ";while (p->RTag == 1 && p->rchild != T) {p = p->rchild;cout << p->data << " ";}p = p->rchild;}
}
前提是构建好了中序线索树
4.4树和森林
4.4.1树的存储结构
双亲表示法
这种表示法中,以一组连续的存储单元存储树的结点,每个结点除了数据与data外,还附设一个parent域用以指示其双亲结点的位置。
这种存储结构利用了每个结点(除根结点以外)只有唯一双亲的性质。这种存储结构,求结点的双亲十分方便。
孩子表示法
由于树种每个结点可能有多个子树,则可用多重链表,即每个结点有多个指针域,其中每个指针指向一颗子树的根结点,此时链表中的结点可以有如下两种结点格式:
若采用第一种结点格式,则多重链表中的结点是同构的,其中d为树的度。由于树中很多结点的度小于d,所以链表中有很多空链域,空间较浪费,不难推出,在一棵有n个结点度为k的树中必有n(k-1)+1个空链域。
若采用第一种结点格式,则多重链表中的结点是同构的,其中d为树的度。由于树中很多结点的度小于d,所以链表中有很多空链域,空间较浪费,不难推出,在一棵有n个结点度为k的树中必有n(k-1)+1个空链域。
*孩子兄弟法
又称二叉树表示法,或二叉链表表示法,即以二叉链表做树的存储结构。链表中结点的两个链域分别指向该结点的第一个孩子节点和下一个兄弟结点。
typedef struct CSNode {ElemType data;struct CSNode *firstchild, *nextsibling
} CSNode, *CSTree;
这种存储结构的优点是它和二叉树的二叉链表表示完全一样,便于将一般的树结构转换为二叉树进行处理,利用二叉树的算法实现对树的操作。
#include <iostream>
#define ElemType intusing namespace std;typedef struct CSNode {ElemType data;struct CSNode *firstchild, *nextsibling;
} CSNode, *CSTree;CSNode* CreateNode(ElemType value) {CSNode* newNode = new CSNode;newNode->data = value;newNode->firstchild = NULL;newNode->nextsibling = NULL;return newNode;
}void CreateTree(CSTree &T) {ElemType value;cout << "请输入节点值(输入 -1 表示空节点):";cin >> value;if (value == -1) { // -1 代表空节点T = NULL;} else {T = CreateNode(value); // 创建新节点cout << "输入 " << value << " 的第一个子节点:" << endl;CreateTree(T->firstchild); // 递归创建第一个子节点if (T->firstchild != NULL) {cout << "输入 " << value << " 的下一个兄弟节点:" << endl;CreateTree(T->nextsibling); // 递归创建下一个兄弟节点}}
}int main() {return 0;
}
4.4.2森林与二叉树的转换
从树的二叉链表表示的定义可知,任何一颗和树对应的二叉树,其根结点的右子树必空。
主要是要理解孩子兄弟法。将树转成二叉树的结构存储,第一个兄弟结点,会成为该结点的右儿子。
森林(Forest) 是一种由 若干棵不相交的树 组成的集合。具体来说,森林是由多个树构成的图,每棵树的节点和边都不与其他树的节点和边相连。
若把森林中的第二棵树的根结点,看成是第一颗树的根结点的兄弟,则同样可导出森林和二叉树的对应关系。
森林转换成二叉树,就是把E看成A的兄弟,所以转换成了A的右儿子了。
二叉树转换成森林,主要看右子树。
将森林转换成二叉树c++代码
#include <iostream>
#include <vector>
#define ElemType intusing namespace std;// 树节点结构
typedef struct CSNode {ElemType data;struct CSNode *firstchild, *nextsibling;
} CSNode, *CSTree;// 创建树节点
CSNode* CreateNode(ElemType value) {CSNode* newNode = new CSNode;newNode->data = value;newNode->firstchild = NULL;newNode->nextsibling = NULL;return newNode;
}void CreateTree(CSTree &T) {ElemType value;cout << "请输入节点值(输入 -1 表示空节点):";cin >> value;if (value == -1) { // -1 代表空节点T = NULL;} else {T = CreateNode(value); // 创建新节点cout << "输入 " << value << " 的第一个子节点:" << endl;CreateTree(T->firstchild); // 递归创建第一个子节点cout << "输入 " << value << " 的下一个兄弟节点:" << endl;CreateTree(T->nextsibling); // 递归创建下一个兄弟节点}
}// 用户输入创建森林(多棵树)
void CreateForest(vector<CSTree> &forest) {int treeCount;cout << "请输入森林中树的数量:";cin >> treeCount;for (int i = 0; i < treeCount; i++) {cout << "创建第 " << i + 1 << " 棵树:" << endl;CSTree tree = NULL;CreateTree(tree); // 创建一棵树forest.push_back(tree); // 将树的根节点加入森林}
}// 将森林转换为二叉树
CSTree ForestToBinaryTree(vector<CSTree> &forest) {if (forest.empty()) {return NULL;}// 将森林中的每棵树的根节点通过兄弟指针连接起来for (size_t i = 0; i < forest.size() - 1; i++) {forest[i]->nextsibling = forest[i + 1];}// 返回第一棵树的根节点,作为二叉树的根节点return forest[0];
}// 打印二叉树(前序遍历)
void PrintBinaryTree(CSTree T) {if (T == NULL) {return;}cout << T->data << " "; // 访问根节点PrintBinaryTree(T->firstchild); // 递归访问左子树PrintBinaryTree(T->nextsibling); // 递归访问右子树
}int main() {vector<CSTree> forest;CreateForest(forest); // 创建森林cout << "将森林转换为二叉树:" << endl;CSTree binaryTree = ForestToBinaryTree(forest);cout << "二叉树的前序遍历结果:" << endl;PrintBinaryTree(binaryTree);return 0;
}
4.5哈夫曼树及其应用
4.5.1哈夫曼树的基本概念
哈夫曼树 又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途。
路径 :从树中一个结点到另一个结点之间的分支构成两个结点之间的路径。
路径长度:路径上的分支数目称作路径长度。
树的路径长度:从树根到每一个结点的路径长度之和。
权:赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所对应有结点和权边。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和,通常记作 W P L = ∑ k = 1 n w k l k WPL=\sum_{k=1}^{n}w_kl_k WPL=∑k=1nwklk。
哈夫曼树:假设有m个权值 w 1 , w 2 , . . . , w m {w_1,w_2,...,w_m } w1,w2,...,wm,可构造一颗含n个叶子结点的二叉树,每个叶子结点的权为 w i w_i wi,则其中带权路径长度 W P L WPL WPL最小的二叉树称作最优二叉树或哈夫曼树。
可以看出,在哈夫曼树中,权值越大的结点离根结点越近。
4.5.2哈夫曼树的构造算法
哈夫曼树的构造过程
1.根据给定的n个权值 w 1 , w 2 , . . . , w n {w_1,w_2,...,w_n} w1,w2,...,wn,构造n棵只有根结点的二叉树,这n棵二叉树构成一个森林F。
2.在森林F中选取两棵根结点的权值最小的数作为左右子树构造出一颗新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
3.在森林F中删除这两棵树,同时新得到的二叉树加入F中。
4.重复2、3,直到F只含一棵树为止。这棵树便是哈夫曼树。
哈夫曼算法的实现
哈夫曼树是一种二叉树,当然可以采用前面介绍过的通用存储方法,而由于哈夫曼树中没有度为1 的结点,则一棵有n个叶子结点的哈夫曼树共有 2 n − 1 2n-1 2n−1个结点,可以存储在一个大小为 2 n − 1 2n-1 2n−1的一维数组中。树中每个结点还要包括其双亲信息和结点信息。
typedef struct{int weight;int parent, lchild, rchild;
}HTNode, *HuffmanTree;
哈夫曼树的各结点存储在由HuffmanTree定义的动态分配的数组中,为了实现方便,0号单元不使用,从1号开始,所以数组大小为2n。将叶子结点集中存储在前面部分1~n个位置,而后面的n-1个位置存储其余非叶子结点。
步骤:
1. 首先动态申请2n个单元;然后循环2n-1次,从一号单元开始,依次将1~2n-1所有单元中的双亲、左孩子、右孩子的下标初始化为0;再循环n次,输入前n个单元中叶子结点的权值。
2. 循环n-1次,通过n-1次选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根结点 s 1 、 s 2 s_1、s_2 s1、s2;删除是指将结点 s 1 、 s 2 s_1、s_2 s1、s2的双亲改为非0;合并就是将 s 1 、 s 2 s_1、s_2 s1、s2的权值和作为一个新结点的权值依次存入到数组n+1之后的单元中,同时记录这个新结点的左孩子下标为 s 1 s_1 s1,右孩子的下标为 s 1 s_1 s1。
完整代码
#include <iostream>
#include <vector>
#define ElemType intusing namespace std;typedef struct{int weight;int parent, lchild, rchild;
}HTNode, *HuffmanTree; void Select(HuffmanTree HT, int end, int &s1, int &s2) {s1 = s2 = 0;int min1 = 100000, min2 = 100000;for (int i = 1; i <= end; i++) {if (HT[i].parent == 0) { // 只考虑尚未选中的结点if (HT[i].weight < min1) {min2 = min1;s2 = s1;min1 = HT[i].weight;s1 = i;} else if (HT[i].weight < min2) {min2 = HT[i].weight;s2 = i;}}}
}int CalculateWPL(HuffmanTree HT, int n) {int WPL = 0;for (int i = 1; i <= n; i++) { // 只计算叶子节点int depth = 0, current = i;while (HT[current].parent != 0) { // 追溯到根节点current = HT[current].parent;depth++;}WPL += HT[i].weight * depth; // 计算 WPL}return WPL;
}void CreatHuffmanTree(HuffmanTree &HT, int n){if(n <= 1) return;int m = 2*n-1;HT = new HTNode[m+1]; //HT[m]表示根结点for(int i = 1; i <= m; i ++){HT[i].parent = 0;HT[i].lchild = 0;HT[i].rchild = 0;} for(int i = 1; i <= n; i ++){cin >> HT[i].weight;}for(int i = n+1; i <= m; i ++){int s1 = 0, s2 = 0;Select(HT, i-1, s1, s2);//HT[k] 中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2HT[s1].parent = i, HT[s2].parent = i;HT[i].lchild = s1;HT[i].rchild = s2;HT[i].weight = HT[s1].weight+HT[s2].weight;}
}int main() {HuffmanTree HT;int n = 8;CreatHuffmanTree(HT, n);cout << HT[2*n-1].weight << endl;cout << CalculateWPL(HT, n) << endl;return 0;
}
4.5.3哈夫曼编码
如图所示的哈夫曼树中,约定左分支标记为0,右分支标记为1,则根结点到每个叶子结点路径上的0、1序列即为相应字符编码。
前缀编码:如果在一个编码方案中,任一编码都不是其他任何编码的前缀(最左子串),则称编码是前缀编码。
哈夫曼编码:对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的路径上,各分支的赋值分别构成一个二进制串,该二进制串就成为哈夫曼编码。
哈夫曼编码满足以下性质:
性质1 :哈夫曼编码是前缀编码。
性质2 :哈夫曼编码是最优前缀编码。
哈夫曼编码的算法实现
主要思想是:依次以叶子为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支则生成代码1;
#include <iostream>
#include <vector>
#include <cstring>
#define ElemType intusing namespace std;typedef struct{int weight;int parent, lchild, rchild;
}HTNode, *HuffmanTree; typedef char **HUffmanCode;void Select(HuffmanTree HT, int end, int &s1, int &s2) {s1 = s2 = 0;int min1 = 100000, min2 = 100000;for (int i = 1; i <= end; i++) {if (HT[i].parent == 0) { // 只考虑尚未选中的结点if (HT[i].weight < min1) {min2 = min1;s2 = s1;min1 = HT[i].weight;s1 = i;} else if (HT[i].weight < min2) {min2 = HT[i].weight;s2 = i;}}}
}int CalculateWPL(HuffmanTree HT, int n) {int WPL = 0;for (int i = 1; i <= n; i++) { // 只计算叶子节点int depth = 0, current = i;while (HT[current].parent != 0) { // 追溯到根节点current = HT[current].parent;depth++;}WPL += HT[i].weight * depth; // 计算 WPL}return WPL;
}void CreatHuffmanTree(HuffmanTree &HT, int n){if(n <= 1) return;int m = 2*n-1;HT = new HTNode[m+1]; //HT[m]表示根结点for(int i = 1; i <= m; i ++){HT[i].parent = 0;HT[i].lchild = 0;HT[i].rchild = 0;} for(int i = 1; i <= n; i ++){cin >> HT[i].weight;}for(int i = n+1; i <= m; i ++){int s1 = 0, s2 = 0;Select(HT, i-1, s1, s2);//HT[k] 中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2HT[s1].parent = i, HT[s2].parent = i;HT[i].lchild = s1;HT[i].rchild = s2;HT[i].weight = HT[s1].weight+HT[s2].weight;}
}void CreatHuffmanCode(HuffmanTree HT, HUffmanCode &HC, int n){HC = new char*[n+1];char *cd = new char[n];cd[n-1] = '\0';for(int i = 1; i <= n; i ++){int start = n-1;int c = i;int f = HT[i].parent;while(f != 0){--start;if(HT[f].lchild == c) cd[start] = '0';else cd[start] = '1';c = f; f = HT[f].parent;} HC[i] = new char[n-start];strcpy(HC[i], &cd[start]);}delete cd;
}int main() {HuffmanTree HT;HUffmanCode HC;int n = 8;CreatHuffmanTree(HT, n);CreatHuffmanCode(HT, HC, n);cout << HT[2*n-1].weight << endl;cout << CalculateWPL(HT, n) << endl;for(int i = 1; i <= n; i ++)cout << HC[i] << endl;return 0;
}
相关文章:
4.数据结构-树和二叉树
树和二叉树 4.1树和二叉树的定义4.1.1树的定义4.1.2树的基本术语4.1.3二叉树的定义 4.2二叉树的性质和存储结构4.2.1二叉树的性质4.2.1二叉树的存储结构顺序存储链式存储 4.3遍历二叉树和线索二叉树4.3.1遍历二叉树根据遍历序确定二叉树先序序列创建二叉链表复制二叉树计算二叉…...
【工作记录】F12查看接口信息及postman中使用
可参考 详细教程:如何从前端查看调用接口、传参及返回结果(附带图片案例)_f12查看接口及参数-CSDN博客 1、接口信息 接口基础知识2:http通信的组成_接口请求信息包括-CSDN博客 HTTP类型接口之请求&响应详解 - 三叔测试笔记…...
k8s搭建kube-prometheus
后续再补一个k8s集群搭建的博客,从0开始搭建k8s集群。使用kube-prometheus非常方便,主要问题只在于拉取镜像。除了拉取镜像外其他时间5分钟即可。耐心等待拉取镜像。 一.kube-prometheus简介 kube-prometheus 是一个专为 Kubernetes 设计的开源监控解决…...
Linux应用:Linux的信号
什么是信号 信号是一种软件中断,用于通知进程系统中发生了某种特定事件。它是操作系统与进程之间,以及进程与进程之间进行异步通信的一种方式。在 Linux 系统中,信号是一种比较简单的进程间通信机制。当一个信号产生时,内核会通过…...
C++特性——RAII、智能指针
RAII 就像new一个需要delete,fopen之后需要fclose,但这样会有隐形问题(忘记释放)。RAII即用对象把这个过程给包起来,对象构造的时候,new或者fopen,析构的时候delete. 为什么需要智能指针 对于…...
springboot项目,指定用alibaba连接池所需要的配置
1、依赖:引入相关的两个依赖 2、application.yml...
在本地跑通spark环境
官网下载spark 下载spark 解压就好 本地配置环境变量 配置环境变量(系统环境变量) 新增 SPARK_HOME 变量名:SPARK_HOME 变量值:F:\class\spark\Spark_env\spark-3.4.4-bin-hadoop3 配置 PATH,新增如下:…...
python-56-基于Vue和Flask进行前后端分离的项目开发示例实战
文章目录 1 创建Vue前端项目1.1 运行demo1.2 实现需求2 flask部署上述dist(前后端未分离)2.1 代码app.py2.2 运行访问3 nginx部署(前后端分离)3.1 nginx前端服务3.3.1 windows安装nginx3.3.2 修改nginx.conf配置文件3.3.3 启动nginx3.3.3 停止nginx3.2 启动后端服务3.2.1 app.p…...
云盘搭建笔记
报错问题: No input file specified. 伪静态 location / {if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last;break;} } location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last; break; } } 设…...
学习C2CRS Ⅳ (Conversational Recommender Model)
C2CRS_Model C2CRS_Model 是一个用于对话推荐系统(Conversational Recommender System, C2CRS)的端到端模型。该模型结合了知识图谱(KG)、上下文信息、用户表示和对话生成等多个模块,以实现高效的推荐和对话功能。它通过以下模块实现: 用户表示模块(CoarseFineDRUserMo…...
【工具】huggingface 模型下载过程
前述 记录下自己下载模型的几种方式 方式 1、网页直接浏览器下载: 简单,但是随时可能断 2、git lfs # 拉代码 GIT_LFS_SKIP_SMUDGE1 git clone https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0 # 进入目录 cd stable-diffusion-…...
空调遥控器低功耗单片机方案
RAMSUN空调遥控器采用先进的32位低功耗单片机作为核心控制器,通过优化软件算法和硬件设计,实现了空调遥控器的低功耗运行。单片机集成了多种功能模块,包括红外发射、按键扫描、电源管理等,有效降低了整体功耗。同时,该…...
K8S学习之基础三十五:k8s之Prometheus部署模式
Prometheus 有多种部署模式,适用于不同的场景和需求。以下是几种常见的部署模式: 1. 单节点部署 这是最简单的部署模式,适用于小型环境或测试环境。 特点: 单个 Prometheus 实例负责所有的数据采集、存储和查询。配置简单&…...
Agent toolkits集成指南
文章目录 CSV Agent的集成Pandas Dataframe Agent的集成PowerBI Dataset Agent的集成Agent toolkits的集成旨在简化并增强LLM应用中的数据处理和分析功能。CSVAgent提供了一个专门的工具,允许开发者处理CSV数据。Pandas Agent则集成了Pandas框架,赋予了开发者在应用中进行高效…...
蓝桥杯关于字符串的算法题目(leetcode回文串的判断问题)
文章目录 1.题目概述2.思路分析3.代码解析 1.题目概述 这个题目主要是需要我们找到回文串,这个回文实际上就是文学里面的这个修辞手法,在这个编程的时候:大概说的就是这个字符串从左向右个从右向左都是一样的这个效果,我们把这样…...
数据结构-----队列
顺序队列(Queue) 一、队列核心概念 1. 基本特性 先进先出(FIFO):最早入队的元素最先出队操作限制: 队尾(Rear):唯一允许插入的位置队头(Front)&…...
GitHub Copilot 在 VS Code 上的终极中文指南:从安装到高阶玩法
GitHub Copilot 在 VS Code 上的终极中文指南:从安装到高阶玩法 前言 GitHub Copilot 作为 AI 编程助手,正在彻底改变开发者的编码体验。本文将针对中文开发者,深度解析如何在 VS Code 中高效使用 Copilot,涵盖基础设置、中文优化…...
深入理解 RLP 编码与 JSON:原理、应用与比较
在区块链和数据存储领域,RLP(Recursive Length Prefix)编码和**JSON(JavaScript Object Notation)**是两种重要的数据编码方式。它们分别适用于不同的应用场景,并具有不同的优缺点。本文将系统性地分析 RLP…...
AI大白话(三):深度学习——AI的‘大脑‘是如何构建的?
🌟引言: 专栏:《AI大白话》 AI大白话(一):5分钟了解AI到底是什么? AI大白话(二):机器学习——AI是怎么“学习“的? 大家好!继前两篇介绍AI基础和机器学习的文章后,今天我们来聊聊深度学习——这个让AI技术近年来突飞猛进的"神奇引擎"。别担心,我会用…...
初识R语言饼状图
目录 基础饼图 标签个性化 边界修改 密度条纹 边框颜色 基础饼图 rm(list ls())# Create Data Prop <- c(3,7,9,1,2) # Make the default Pie Plot P1 <- pie(Prop) dev.off() 标签个性化 P2 <-pie(Prop , labels c("Gr-A","Gr-B","…...
[DeepRetrieval] 用DeepSeek-R1-Zero的思路教会模型怎么用搜索引擎找文本
前段时间很火的 DeepSeek-R1-Zero,通过这种方式既然能增强模型的推理能力,那是否可以在RAG的方面上增强文本的召回呢? 今天带来一篇关于这个方面工作的技术报告来分享一下。 技术报告: https://arxiv.org/pdf/2503.00223 原文链接…...
⭐算法OJ⭐二叉树的后序遍历【树的遍历】(C++实现)Binary Tree Postorder Traversal
⭐算法OJ⭐二叉树的中序遍历【树的遍历】(C实现)Binary Tree Inorder Traversal ⭐算法OJ⭐二叉树的前序遍历【树的遍历】(C实现)Binary Tree Preorder Traversal Given the root of a binary tree, return the postorder traver…...
【LeetCode 热题100】 234. 回文链表的算法思路及python代码
234. 回文链表 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false 。 示例 1: 输入:head [1,2,2,1] 输出:true示例 2: 输入&…...
Grid布局示例代码
示例一 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Grid Layout Example</title><styl…...
【K8S】ImagePullBackOff状态问题排查。
ImagePullBackOff 是在使用 Kubernetes(K8s)时经常遇到的一种错误状态,下面为你详细介绍其含义、可能的原因及解决办法。 含义 当你在 K8s 集群中创建一个 Pod 时,Kubelet 会尝试从指定的镜像仓库拉取所需的容器镜像。如果拉取镜…...
在 Kubernetes(k8s)部署过程中常见的问题
在 Kubernetes(k8s)部署过程中,常见的问题主要包括以下几类,以下是具体示例及简要说明: 1. 资源配额不足(Resource Quota) 现象:Pod 处于 Pending 状态,事件日志显示 Insufficient CPU/Memory。 原因: 节点(Node)资源不足,无法满足 Pod 的 requests 或 limits。 命…...
微信小程序状态管理与计算属性同时使用:miniprogram-computed 和 mobx-miniprogram
两个框架扩展提供的 ComponentWithStore 与 ComponentWithComputed 方法无法结合使用。如果需要在一个组件中既想使用 mobx-miniprogram-bindings 又想使用 miniprogram-computed解决方案是: 使用旧版 API 自定义组件仍然使用 Component 方法构建组件,将…...
Redis设置开机自启报错start-limit-hit
Redis设置开机自启报错start-limit-hit 问题:在银河麒麟服务器上编译安装了redis后设置systemctl开机自启报错start-limit-hit 如何解决? 因为开机自启的需求是后面新增的,所以一开始使用的是命令启动,使用命令启动就会直接在前台…...
[数据结构]排序之 归并排序(有详细的递归图解)
一、非递归 基本思想: 归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列&#x…...
pdf文件分页按需查看
pdf预览本来打算粗暴点,一次性查看全部,但是一个pdf四五百页导致手机端查看超出内存直接崩掉,崩掉会导致页面疯狂刷新,所以不得不进行优化 解决思路大致如下: canvas转为blob格式以图片的形式加载在页面(B…...
栈/堆/static/虚表
在 C 里,栈空间主要用来存放局部变量、函数调用信息等。下面为你介绍栈空间在 C 里的运用方式。 1. 局部变量的使用 在函数内部定义的变量会被存于栈空间,当函数执行结束,这些变量会自动被销毁。 #include <iostream>void exampleFu…...
计算机网络技术服务管理基于Spring Boot-SSM
目录 一、引言 二、用户需求分析 三、功能介绍 3.1.资源管理: 3.2.故障管理: 3.3.性能管理: 3.4.安全管理: 3.5.配置管理: 3.6.日志管理: 3.7.用户管理࿱…...
Redisson 分布式锁原理
加锁原理 # 如果锁不存在 if (redis.call(exists, KEYS[1]) 0) then# hash结构,锁名称为key,线程唯一标识为itemKey,itemValue为一个计数器。支持相同客户端线程可重入,每次加锁计数器1.redis.call(hincrby, KEYS[1], ARGV[2], 1);# 设置过期时间redis.call(pexpi…...
LLM(5):了解 GPT 架构
1.6 对 GPT 架构的更深入了解 GPT 最初由 OpenAI 的 Radford 等人在论文《通过生成式预训练提高语言理解能力》 中提出。GPT-3 是该模型的扩展版本,具有更多的参数,并且使用了更大的数据集进行训练。此外,ChatGPT 中提供的原始模型是通过在大…...
Android Zygote 启动流程梳理
和你一起终身学习,这里是程序员Android 本篇文章主要介绍 Android Zygote 启动分析 知识点,通过阅读本篇文章,您将收获以下内容: 一、Android 系统基本服务二、虚拟机创建和第一个Java 程序引导三、Dalvik 虚拟机基本配置四、Zygote 启动流程…...
华为OD机试-绘图机器-双指针(Java 2025 A卷 100分)
题目描述 绘图机器的绘图笔初始位置在原点 (0, 0)。机器启动后按照以下规则绘制直线: 尝试沿着横坐标正向绘制直线,直到给定的终点 E。期间可以通过指令在纵坐标轴方向进行偏移,offsetY 为正数表示正向偏移,为负数表示负向偏移。给定的横坐标终点值 E 以及若干条绘制指令,…...
ESP32(1)基于ESP32的lwIP了解
ESP32-S3 是一款集成了 Wi-Fi 和蓝牙功能的微控制器,而 lwIP(轻量级 IP)是一个为嵌入式系统设计的开源 TCP/IP 协议栈。通过使用 lwIP 库, ESP32-S3 可以实现与外部网络的通信,包括发送和接收数据包、处理网络连接等。…...
C语言预处理详解
目录 (一)预处理符号 (二)define定义常量和宏 (三)#符号和##符号 (四)undef符号的条件编译 (五)头文件的包括 (一)预处理符号 在…...
python实现接口自动化
代码实现自动化相关理论 代码编写脚本和工具实现脚本区别是啥? 代码: 优点:代码灵活方便缺点:学习成本高 工具: 优点:易上手缺点:灵活度低,有局限性。 总结: 功能脚本:工…...
当Anaconda的安装路径与我想创建的conda虚拟环境路径不一致时,应该怎么操作?
我的anaconda安装在该路径:D:\Program\anaconda3 , 如果我想在F盘创建一个虚拟环境 应该怎么做呢? 若你想在 F 盘创建 Anaconda 虚拟环境,可使用 conda create 命令,并通过 --prefix 参数指定环境路径。以下是详细步骤࿱…...
MongoDB慢日志查询及索引创建
MongoDB 的慢日志(Slow Query Log)对于运维和程序员来说都非常重要,因为它直接关系到数据库的性能和应用程序的稳定性。以下分享介绍下MongoDB慢日志查询及索引创建相关的一些笔记。 一,准备 1. 使用 db.currentOp() 实时监控 …...
C语言指针(详细总结)
目录 1.初始C指针 几个重要的概念: 指针的加减 &与* 二级指针 2.指针与数组 指针数组 数组指针变量 一维数组与二维数组传参的本质 编辑编辑 编辑 3.指针与函数 函数指针数组 4.指针与结构体 5.野指针以及常见的内存管理错误 常见的内存错…...
服务器部署Kong和Konga过程
前言 最近在想怎么将一个接口给外部提供服务,并且可以根据和对放的关系,设置不同的期限或者服务大小?并且有友好的可视化页面! 这让我了解到了 API 网关,所以我开始研究 Kong 和 Konga 的使用。 实际上我最开始研究的apisix,但是部署了好久因为etcd不支持 http 无法连接…...
stm32第五天按键的基础知识
一:按键连接示意图 按键控制LED灯 软件设计流程 初始化系统 o 初始化GPIO外设时钟 o 初始化按键和LED的引脚 • 检测按键输入电平来控制LED灯 o SW2控制灯开 。 SW3控制灯关 1:key.c工程 #include"key.h" #include"stm32f10x.h"v…...
高主频CPU+RTX4090:AI生图性能优化超150%
概述:消费级高主频CPU搭配 RTX 4090显卡可以显著提高AI生图的性能,相比于企业级CPU具有更大的吞吐量和更优的成本效益。 引言:在AI图像生成过程中,CPU与GPU的协同效应对系统的整体性能至关重要。测试表明,与RTX 4090显…...
自学Python创建强大AI:从入门到实现DeepSeek级别的AI
人工智能(AI)是当今科技领域最热门的方向之一,而Python是AI开发的首选语言。无论是机器学习、深度学习还是自然语言处理,Python都提供了丰富的库和工具。如果你梦想创建一个像DeepSeek这样强大的AI系统,本文将为你提供…...
主流区块链
文章目录 主流链1. Solana特点:适用场景:工具链: 2. Binance Smart Chain (BSC)特点:适用场景:工具链: 3. Avalanche特点:适用场景:工具链: 4. Polkadot特点:…...
DevEco Studio的使用
目录 1.创建ArkTS工程 2.ArkTS工程目录结构(Stage模型) 构建第一个页面 构建第二个页面 实现页面间的跳转 1.创建ArkTS工程 若首次打开DevEco Studio,请点击Create Project创建工程。如果已经打开了一个工程,请在菜单栏选择…...
Oracle 公布 Java 的五大新功能
Java 增强提案包括语言增强和性能优化,从 JDK 25 中的稳定值 API 开始。 随着JDK(Java 开发工具包)24刚刚全面上市,Oracle 提前透露了不久的将来即将推出的 Java 功能,包括增强原始装箱到空限制值类类型。 3 月 18 日…...
checkpoint机制
1、什么是checkpoint 将缓冲池中的脏页刷新到磁盘,并更新redo log的checkpoint位点,确保数据库在发生故障时可以快速恢复到一致的状态。 2、checkpoint执行过程 确保需要刷新的脏页:从缓冲池中选取一部分需要刷新的页数据页刷新࿱…...