图的遍历

广度优先遍历(BFS) BFS(Breadth-First-Search),参考对树的层序遍历 对上面的图从①出发进行BFS得到序列: ①②⑤ ⑥ ③⑦ ④⑧ 若采用不同的储存结构,可能会得到不同的遍历结果(这个差异主要来自寻找邻接点的过程),对于邻接矩阵存储的图,由于邻接矩阵是唯一的,所以BFS序列也是唯一的;同理,邻接表存储的图BFS序列不唯一。 BFS算法 与树的层序遍历不同的是,由于图中存在回路,遍历过程中会出现重复访问的问题,故可构造visited数组,用来标记已访问过的数组。 此外,还应针对非连通图做额外的判断,遍历完一个连通分量(极大连通子图)后,遍历查找visited数组中是否还存在未遍历的,如果有即为另一连通分量,继续调用BFS即可。 void BFS(Graph G,int v); bool visited[MAX_VERTEX_NUM]; SqQueue Q;//辅助队列 void BFSTraverse(Graph G){ //初始化visited数组 for (int i = 0; i < G.vexnum; ++i) {//使下标从1开始 visited[i]=false; } //对非连通图的处理 for (int v = 0; v < G.vexnum; ++v) { if (!visited[v]) BFS(G,v); } } //从顶点v出发广度优先遍历图G void BFS(Graph G,int v){ visit(v); visited[v]=true; EnQueue(Q,v);//顶点v入队列Q while(!isEmpty(Q)){ DeQueue(Q,v);//顶点v出队列Q for (w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))//处理v的所有邻接点 if (!...

Jun 3, 2021 · 1 min · Archai

图的存储

邻接矩阵 Vextex/Vertices 顶点; Martix 矩阵; Arc 弧. 代码实现 #define MaxVextexNum 100//容许存储的最大顶点数 typedef struct{ char Vex[MaxVextexNum]; //可以将定点之间的关系用int 类型01表示,也可定义为boolean/枚举类型,占空间更小 bool Edge[MaxVextexNum][MaxVextexNum]; int vexnum,arcnum;//顶点数和弧|边数 }MGraph; 即 找度 根据邻接矩阵计算结点的度TD 无向图 有向图 $TD(V_i)$ 第i行(或i列)中非零元素的个数 $ID(V_i)$ : i行非零元素个数 $OD(V_i)$: i列非零元素个数 TD=ID+OD 对于带权图(网) #define MaxVextexNum 100//容许存储的最大顶点数 #define INIFINITY //宏定义,表示无穷 typedef char VextexType;//顶点 typedef int EdgeType;//权值 typedef struct{ VextexType Vex[MaxVextexNum]; EdgeType Edge[MaxVextexNum][MaxVextexNum]; int vexnum,arcnum; }MGraph; 复杂度 空间复杂度来自数组Vex[]跟Edge[],故空间复杂度为$|V|+|V|^2=O(|V|^2)$,即为顶点数量的二次方,故此方法更适合存储稠密图,不然有较多浪费。...

Jun 2, 2021 · 1 min · Archai

AVL树

平衡二叉树是Adelson-Velsky和 Landis发明,故命名为AVL树。也称平衡二叉查找树。 ✨特点: ①左子树<根<右子树; ②任一节点,左右子树高度之差不超过1. 平衡因子 $平衡因子=左子树高-右子树高$ AVL树的插入操作 AVL树插入新结点导致不平衡之后,只需将最小不平衡子树平衡,其他祖先结点会随之恢复平衡。 调整最小不平衡子树 注意:调整过后必须保证其BST的特性,即“左子树1.LL 即在以A为根节点的树的左孩子B的左子树上插入新结点,导致A成为最小不平衡子树。 调整过程如下: 2.RR 即在以A为根节点的树的右孩子B的右子树上插入新结点,导致A成为最小不平衡子树。 调整过程如下: 3.LR 即在以A为根节点的树的左孩子B的右子树上插入新结点,导致A成为最小不平衡子树。 观察得知,所进行的调整就是保证$|平衡因子|<=1$,因此若插入操作使得 左 - 右 > 1 => 右旋 右 - 左 > 1 =>左旋 而当进行了LR插入操作之后,导致以A为根节点的树 左-右>1,理应右旋但是,由上述结果可知,经过右旋之后: 可以看到,为了保证其左子树<根<右子树的特性,经过调整后,依然存在右-左>1的问题; 因此,对于LR型不能简单进行右旋调整,应该先将其转化为LL型 (左旋),再进行右旋; 为此,我们需要将BR结点展开,之后旋转成为LL型插入 可以看到,展开后又出现两种插入情况CL&CR,但其实两者处理大同小异: CR插入调整过程如下: 4.RL 即在以A为根节点的树的右孩子B的左子树上插入新结点,导致A成为最小不平衡子树。 参考LR型,其调整过程如下: 查找操作效率分析 Assuming that, $n_h$表示深度为h的平衡树中含有的最少结点,则 $n_0=0$,$n_1=1$,$n_2=2$…存在递归关系 $n_h=n_{h-1}+n_{h-2}+1$,即左右子树结点之和+根节点。 可以证明(AVL证明),n个结点的平衡二叉树最大深度数量级为$\log_2n$,则其查找操作的复杂度为$O(\log_2n)$

May 23, 2021 · 1 min · Archai

哈夫曼树

基本概念 1.结点的权 每个结点带有的具有某种现实意义的数值(比如代表重要性等) 2.结点的带权路径长度 从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积 3.树的带权路径长度 树中所有叶结点的带权路径长度之和(WPL, Weighted Path Length) $WPL=\sum_{i=1}^{n}w_il_i$ ✨4.哈夫曼树 在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树 构造哈夫曼树 给定n个权值分别为 $w_1,w_2,…,w_n$ 的结点,其构造过程描述如下: ① 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F ② 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。 ③ 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。 ④ 重复步骤②和③,直至F中只剩下一棵树为止。 假设给定结点在经过步骤①之后如下 则其②③④步骤为: $WPL_{min}=1*7+2*3+3*2+4*1+4*2=31$ 或者 $WPL_{min}=1*7+3*(1+2+2+3)=31$ 👇👇👇👇👇👇 哈夫曼树特点: ① 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。 ② 哈夫曼树的结点总数为2n-1。 ③ 哈夫曼树中不存在度为1的结点。 ④ 哈夫曼树并不唯一,但WPL必然相同且为最优。 哈夫曼编码 固定长度编码 如ASCII码 可变长度编码 前缀编码 没有一个编码是另一个编码的前缀 哈夫曼编码 由哈夫曼树得到哈夫曼编码——字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树。 ✨ 应用 可用于数据压缩 如图,为英文字母使用频率表,若不进行压缩,第 i 个字母频率用$l_i表示$,路径长至少为5($2^4<26<2^5$),则 $WPL=5×\sum_{i=1}^{n}l_i$=500 若通过构造哈夫曼树进行哈夫曼编码 则$WPL_{min}=$ $v=\frac{WPL_{min}}{WPL}$

May 23, 2021 · 1 min · Archai

普通树 对于一棵普通类型的树形结构,可将其转化为二叉树之后再参考二叉树的方法进行相关操作。 孩子兄弟表示法(链式结构) 通过此方法可将普通树转化为二叉树 //孩子兄弟即Child, Sibling typedef struct CSNode{ Elemtype data; struct CSNode *firstchild,*nextsibling;//第一个孩子和右兄弟指针,等价于*lchild,*rchild }CSNode,*CSTree 图示如下 树的遍历 1.深度优先遍历(先根遍历&后根遍历) 先根遍历 若树非空,先访问根结点,再依次对每棵子树进行先根遍历(递归)。 对如上图所示的树进行先根遍历: A B C D A (BE ) (CF) (DG) A (BEH) (CF) (DG) 即先根遍历序列为A B E H C F D G 发现与通过“孩子兄弟法”将树转为二叉树后的先序遍历序列相同 后根遍历 若树非空,先依次对每棵子树进行后根遍历,最后再访问根结点。 对如上图所示的树进行后根遍历: B C D A (E B) (F C) (G D) A (H E B) (F C) (GD) A...

May 22, 2021 · 2 min · Archai

线索二叉树

线索二叉树 WHY 方便从任一个结点出发,找到其前驱、后继;方便遍历 普通二叉树中,对任意一个结点,若想找到其前驱/后继结点,只能再进行一次相应的前/中/后序遍历才行,复杂度太高。 为此,我们引入前驱线索,后继线索的概念。其中,前驱线索由左孩子指针充当,后继线索由右孩子指针充当。 typedef struct BiTNode{ ElemType data; struct BiTNode *lchild,*rchild; }BiTNode,*BiTree; 构建出如下图所示的结构: 但是, *lchild(*rchild)有可能指向存在的结点,为此我们引入线索标志。当线索标志为1时,表示孩子指针指向前驱后继,线索标志为0时,表示孩子指针指向左右孩子。此时 typedef struct ThreadNode{ ElemType data; struct ThreadNode *lchild,*rchild; int ltag,rtag;//左右线索标志 }ThreadNode,*ThreadTree; 这样,每一个线索链表中的结点就可以图示为: HOW 如何分别用代码实现前中后序遍历下的线索链表 1.中序线索化 其实中序线索化的过程就是再进行一遍中序遍历,为每个节点添加额外的信息(lchild, rcild, ltag, rtag). 🍔初始定义结构体 typedef struct ThreadNode{ ElemType data; struct ThreadNode *lchild,*rchild; int ltag,rtag; }ThreadNode,*ThreadTree; 🍔定义前驱指针 ThreadNode *pre=NULL; 前驱指针还可以定义在局部,这里为了方便起见,将pre定义为全局。 🍔开始定义处理函数 void CreatInThread(ThreadTree T){ pre=NULL; if(T!=NULL){ InThread(T); if(pre->rchild==NULL){ pre->rtag=1; } } } 注意:最后一个节点单独处理...

May 15, 2021 · 1 min · Archai