From 25423916c012b2f8aaf5686f2d91de3ba5615f1f Mon Sep 17 00:00:00 2001 From: Didnelpsun <2675350965@qq.com> Date: Sun, 26 Sep 2021 23:39:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Code/Code/head/graph.h | 43 ++++++++ Code/Code/head/head.h | 9 +- Code/Code/head/link_tree.h | 110 +++++++++++++++----- Code/Code/head/thread_tree.h | 2 +- Code/Code/head/tree.h | 29 ++++++ Code/Code/source/test.cpp | 2 + Data-Structrue/7-tree-ex.md | 174 ++++++++++++++++++++++++++++++- Data-Structrue/7-tree.md | 192 ++++++++++++++++++++++------------- Data-Structrue/8-graph-ex.md | 113 +++++++++++++++++++++ Data-Structrue/8-graph.md | 88 +++++++++------- 10 files changed, 627 insertions(+), 135 deletions(-) create mode 100644 Code/Code/head/graph.h create mode 100644 Code/Code/head/tree.h create mode 100644 Data-Structrue/8-graph-ex.md diff --git a/Code/Code/head/graph.h b/Code/Code/head/graph.h new file mode 100644 index 0000000..21c74e8 --- /dev/null +++ b/Code/Code/head/graph.h @@ -0,0 +1,43 @@ +#include "head.h" + +// 邻接矩阵图 +typedef struct { + // 顶点表 + element_type* vex; + // 边表 + weight_type *edge; + // 定点数 + int vex_length; + // 边数 + int edge_length; +} AdjacentArrayGraph; + +// 邻接表图 +// 边表结点 +typedef struct EdgeNode{ + // 该弧指向顶点的位置 + int vex; + // 指向下条弧的指针 + struct EdgeNode *next; + // 边权值 + weight_type weigh; +} EdgeNode; + +// 顶点表结点 +typedef struct VexNode{ + // 顶点信息 + element_type data; + .. 指向第一条依赖该顶点的弧的指针 + EdgeNode* frist; +} VexNode, *AdjacentList; + +// 邻接表 +typedef struct { + // 邻接表数据 + AdjacentList data; + // 顶点数 + int vex_length; + // 边数 + int edge_length; +} AdjacentListGraph; + diff --git a/Code/Code/head/head.h b/Code/Code/head/head.h index 77b9a59..ddc4f97 100644 --- a/Code/Code/head/head.h +++ b/Code/Code/head/head.h @@ -7,4 +7,11 @@ // 崮ݳ #define DATASIZE 4 // Ĭ -typedef char element_type; \ No newline at end of file +typedef char element_type; +// Ȩֵ +typedef int weight_type; + +// ȽԪط +int CompareElem(element_type elem1, element_type elem2){ + return 0; +} \ No newline at end of file diff --git a/Code/Code/head/link_tree.h b/Code/Code/head/link_tree.h index 69dc152..ddf240d 100644 --- a/Code/Code/head/link_tree.h +++ b/Code/Code/head/link_tree.h @@ -9,9 +9,9 @@ typedef struct BinaryTreeNode { } BinaryTreeNode, *BinaryTree; // 前序遍历 -bool PreOrderBinaryTree(BinaryTree &tree, bool( *function)(BinaryTree&)){ - if(tree!= nullptr){ - if(!function(tree)) +bool PreOrderBinaryTree(BinaryTree &tree, bool( *function)(BinaryTree &)) { + if (tree != nullptr) { + if (!function(tree)) return false; PreOrderBinaryTree(tree->left_child, function); PreOrderBinaryTree(tree->right_child, function); @@ -20,10 +20,10 @@ bool PreOrderBinaryTree(BinaryTree &tree, bool( *function)(BinaryTree&)){ } // 中序遍历 -bool InOrderBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ - if(tree!= nullptr){ +bool InOrderBinaryTree(BinaryTree &tree, bool(*function)(BinaryTree &)) { + if (tree != nullptr) { PreOrderBinaryTree(tree->left_child, function); - if(!function(tree)) + if (!function(tree)) return false; PreOrderBinaryTree(tree->right_child, function); } @@ -31,28 +31,28 @@ bool InOrderBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ } // 后序遍历 -bool PostOrderBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ - if(tree!= nullptr){ +bool PostOrderBinaryTree(BinaryTree &tree, bool(*function)(BinaryTree &)) { + if (tree != nullptr) { PreOrderBinaryTree(tree->left_child, function); PreOrderBinaryTree(tree->right_child, function); - if(!function(tree)) + if (!function(tree)) return false; } return true; } // 非递归先序遍历 -bool PreOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ +bool PreOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(*function)(BinaryTree &)) { // 初始化栈,栈元素为tree指针 std::stack stack; // 赋值一个新树 BinaryTree new_tree = tree; // 如果栈不空或new_tree不空时循环 - while(new_tree||!stack.empty()){ + while (new_tree || !stack.empty()) { // 一直向左 - if(new_tree){ + if (new_tree) { // 访问当前结点 - if(!function(new_tree)){ + if (!function(new_tree)) { printf("PreOrderNonRecursive:访问结点失败!"); return false; } @@ -61,8 +61,8 @@ bool PreOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(* function)(BinaryTre // 左孩子不空则一直向左 new_tree = new_tree->left_child; } - // 出栈,并转向右子树 - else{ + // 出栈,并转向右子树 + else { // 返回栈顶的元素,但不删除该元素 new_tree = stack.top(); // 删除栈顶元素但不返回其值 @@ -76,28 +76,28 @@ bool PreOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(* function)(BinaryTre } // 非递归中序遍历 -bool InOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ +bool InOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(*function)(BinaryTree &)) { // 初始化栈,栈元素为tree指针 std::stack stack; // 赋值一个新树 BinaryTree new_tree = tree; // 如果栈不空或new_tree不空时循环 - while(new_tree||!stack.empty()){ + while (new_tree || !stack.empty()) { // 一直向左 - if(new_tree){ + if (new_tree) { // 当前结点入栈 stack.push(new_tree); // 左孩子不空则一直向左 new_tree = new_tree->left_child; } - // 出栈,并转向右子树 - else{ + // 出栈,并转向右子树 + else { // 返回栈顶的元素,但不删除该元素 new_tree = stack.top(); // 删除栈顶元素但不返回其值 stack.pop(); // 访问出栈结点 - if(!function(new_tree)){ + if (!function(new_tree)) { printf("InOrderNonRecursive:访问结点失败!"); return false; } @@ -110,30 +110,84 @@ bool InOrderNonRecursiveBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree } // 层序遍历 -bool LevelOrderBinaryTree(BinaryTree &tree, bool(* function)(BinaryTree&)){ +bool LevelOrderBinaryTree(BinaryTree &tree, bool(*function)(BinaryTree &)) { // 初始化辅助队列 std::queue queue; // 初始化树结点 - BinaryTree new_tree=tree; + BinaryTree new_tree = tree; // 根结点入队 queue.push(new_tree); // 若队列非空则循环 - while(!queue.empty()){ + while (!queue.empty()) { // 队头结点出队 new_tree = queue.front(); queue.pop(); // 访问出队结点 - if(!function(new_tree)){ + if (!function(new_tree)) { printf("LevelOrder:访问结点失败!"); return false; } // 如果左子树不空则其根结点入队 - if(new_tree->left_child!= nullptr){ + if (new_tree->left_child != nullptr) { queue.push(new_tree); } - if(new_tree->right_child!= nullptr){ + if (new_tree->right_child != nullptr) { queue.push(new_tree); } } return true; -} \ No newline at end of file +} + +// 二叉排序树查找 +BinaryTreeNode *SearchBinarySortTree(BinaryTree tree, element_type elem) { + BinaryTree node = tree; + // 若树空或等于根结点值就结束循环 + while (node != nullptr && elem != node->data) { + // 若小于则左子树查找 + if (CompareElem(elem, node->data) < 0) + node = node->left_child; + else + node = node->right_child; + } + return node; +} + +// 二叉排序树插入 +bool InsertBinarySortTree(BinaryTree &tree, element_type elem) { + if (elem == NULL){ + printf("InsertBinarySortTree:插入数据不应为空!\n"); + return false; + } + // 若原树为空,则将新插入的记录作为根结点 + if (tree == nullptr) { + tree = (BinaryTree) malloc(sizeof(BinaryTreeNode)); + tree->data = elem; + tree->left_child = tree->right_child = nullptr; + return true; + } + // 若存在相同关键字的结点则插入失败 + else if (CompareElem(elem, tree->data) == 0) + return false; + // 若小于则插入到左子树 + else if (CompareElem(elem, tree->data) < 0) + return InsertBinarySortTree(tree->left_child, elem); + // 否则插入到右子树 + else + return InsertBinarySortTree(tree->right_child, elem); +} + +// 构造二叉排序树 +bool CreateBinarySortTree(BinaryTree &tree, element_type* elem, int start, int length){ + // 将树初始化 + tree = nullptr; + int i = 0; + while(i + +**例题** 若度为$m$的哈夫曼树中,叶子结点个数为$n$,则非叶子结点的个数为()。 + +$A.n-1$ + +$B.\lfloor n/m\rfloor-1$ + +$C.\lceil(n-1)/(m-1)\rceil$ + +$D.\lceil n/(m-1)\rceil-1$ + +解:$C$。一棵度为$m$的哈夫曼树应只有度为$0$和$m$的结点,设度为$m$的结点(叶子结点)有$n_m$个,度为$0$的结点有$n_0$个,又设结点总数为$n$,$n=n_0+n_m$。因有$n$个结点的哈夫曼树有$n-1$条分支,则根据总结点与度数关系的公式($n=n_1+2n_2+\cdots+mn_m+1$)得到$mn_m=n-1=n_m+n_0-1$,整理得$(m-1)n_m=n_0-1$,$n_m=(n-1)/(m-1)$。 + +**例题** 在任意一棵非空平衡二叉树($AVL$树)$T_1$中,删除某结点$v$之后形成平衡二叉树$T_2$,再将$v$插入$T_2$,形成平衡二叉树$T_2$。下列关于$T_1$与$T_3$的叙述中,正确的是()。 + +Ⅰ.若$v$是$T$的叶结点,则$T_1$与$T_3$可能不相同 + +Ⅱ.若$v$不是$T$的叶结点,则$T_1$与$T_3$一定不相同 + +Ⅲ.若$v$不是$T$的叶结点,则$T_1$与$T_3$一定相同 + +$A.$仅Ⅰ + +$B.$仅Ⅱ + +$C.$仅Ⅰ、Ⅱ + +$D.$仅Ⅰ、Ⅲ + +解:$A$。在非空平衡二叉树中插入结点,在失去平衡调整前,一定插入在叶结点的位置。若删除的是$T_1$的叶结点,则删除后平衡二叉树不会失去平衡,即不会发生调整,再插入此结点得到的二叉平衡树$T_1$与$T_3$相同。若删除后平衡二叉树失去平衡而发生调整,再插入结点得到的二叉平衡树$T_1$与$T_3$可能不同,如一个平衡树的一个子树只有一个结点,删除这个结点则平衡树必然失衡,肯定会重新调整。Ⅰ正确。若$v$不是$T$的叶结点,则删除可能相同也可能不同。一般而言删除一个结点,然后重新加入是从叶子结点处加入,所以肯定不是在原来的根位置。但是如果删除会失衡,则会调整,所以可能会调整回原来的位置。 diff --git a/Data-Structrue/7-tree.md b/Data-Structrue/7-tree.md index 5cf148f..8494d61 100644 --- a/Data-Structrue/7-tree.md +++ b/Data-Structrue/7-tree.md @@ -204,6 +204,63 @@ + 若$p$有右孩子,则按照左右根的情况遍历,右在根的前面,所以$p->pre=p->rchild$。 + 若$p$没有右孩子,按照左根的顺序,则$p->pre=p->lchild$。 +## 树与森林 + +### 树的存储结构 + ++ 双亲表示法:是一种顺序存储方式,一般采用一组数组,每个结点中保存指向双亲的伪指针。查找双亲方便,但是查找孩子就只能从头遍历。 ++ 孩子表示法:是顺序加链式存储方法,顺序存储所有元素,添加一个$firstChild$域,指向第一个孩子结构体的指针,孩子结构体包括元素位置索引与指向下一个孩子结构体的$next$指针。寻找孩子比较方便,但是寻寻找双亲需要遍历$n$个节点$n$个孩子链表。 ++ 孩子兄弟表示法:是一种链式存储方式,定义了两个指针,分别指向第一个孩子与右兄弟,类似于二叉树,可以利用二叉树来实现对树的处理。也称为二叉树表示法。可以将树操作转换为二叉树的操作,但是查找双亲麻烦。 + +### 森林与树的转换 + +树与森林的转换,树与二叉树的转换都可以使用孩子兄弟表示法来实现,左孩子右兄弟,如果是森林则认为其根结点为兄弟。 + +树转换为二叉树的规则:每个结点左指针指向它的第一个孩子,右指针指向它在树中的相邻右兄弟,这个规则又称“左孩子右兄弟”。由于根结点没有兄弟,所以对应的二叉树没有右子树。 + +树转换成二叉树的画法: + +1. 在兄弟结点之间加一连线。 +2. 对每个结点,只保留它与第一个孩子的连线,而与其他孩子的连线全部抹掉。 +3. 以树根为轴心,顺时针旋转$45°$。 + +将森林转换为二叉树的规则与树类似。先将森林中的每棵树转换为二叉树,由于任何一棵和树对应的二叉树的右子树必空,若把森林中第二棵树根视为第一棵树根的右兄弟,即将第二棵树对应的二叉树当作第一棵二叉树根的右子树,将第三棵树对应的二叉树当作第二棵二叉树根的右子树……以此类推,就可以将森林转换为二叉树。 + +森林转换成二叉树的画法: + +1. 将森林中的每棵树转换成相应的二叉树。 +2. 每棵树的根也可视为兄弟关系,在每棵树的根之间加一根连线。 +3. 以第一棵树的根为轴心顺时针旋转$45°$。 + +二叉树转换为森林的规则:若二叉树非空,则二叉树的根及其左子树为第一棵树的二叉树形式,故将根的右链断开。二叉树根的右子树又可视为一个由除第一棵树外的森林转换后的二叉树,应用同样的方法,直到最后只剩一棵没有右子树的二叉树为止,最后再将每棵二叉树依次转换成树,就得到了原森林。 + +### 树的遍历 + ++ 先根遍历:若树非空,先访问根结点,再依次对每棵子树进行先根遍历。 ++ 后根遍历:若树非空,先依次对每棵子树进行后根遍历,最后访问根结点。 ++ 层次遍历:用辅助队列实现: + 1. 若树非空,根结点入队。 + 2. 若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队。 + 3. 重复步骤二直到队列为空。 + +### 森林的遍历 + +先序遍历森林: + +1. 访问森林中第一棵树的根结点。 +2. 先序遍历第一棵树中根结点的子树森林。 +3. 先序遍历除去第一棵树之后剩余的树构成的森林。 + +中序遍历森林: + +1. 先序遍历第一棵树中根结点的子树森林。 +2. 访问森林中第一棵树的根结点。 +3. 中序遍历除去第一棵树之后剩余的树构成的森林。 + +可以把每个树先按序遍历再合在一起,也可以先转换为二叉树再遍历。 + +## 树的应用 + ### 二叉排序树 即$BST$,是一种用于排序的二叉树 @@ -219,11 +276,11 @@ 1. 若树非空,目标值与根结点的值比较。 2. 若相等则查找成功,返回结点指针。 3. 若小于根结点,则在左子树上查找,否则在右子树上查找。 -4. 遍历结束后仍没有找到则返回NULL。 +4. 遍历结束后仍没有找到则返回$NULL$。 遍历查找的时间复杂度是$O(\log_2n)$,则递归查找的时间复杂度是$O(\log_2n+1)$,其中$\log_2n+1$代表二叉树的高度。 -查找成功的平均查找长度ASL,二叉树的平均查找长度为$O(\log_2n)$,最坏情况是每个结点只有一个分支,平均查找长度为$O(n)$。 +查找成功的平均查找长度$ASL$,二叉树的平均查找长度为$O(\log_2n)$,最坏情况是每个结点只有一个分支,平均查找长度为$O(n)$。 #### 二叉排序树的插入 @@ -234,25 +291,49 @@ #### 二叉排序树的删除 + 搜索到对应值的目标结点。 -+ 若被删除结点1p是叶子结点,则直接删除,不会破坏二叉排序树的结构。 ++ 若被删除结点$p$是叶子结点,则直接删除,不会破坏二叉排序树的结构。 + 若被删除结点只有一棵左子树或右子树,则让该结点的子树称为该结点父结点的子树,来代替其的位置。 -+ 若被删除结点有左子树和右子树,则让其结点的直接后继(中序排序该结点后一个结点,其右子树的最左下角结点)或直接前驱(中序排序该结点前一个结点,其左子树的最右下角结点)替代该结点,并从二叉排序树中删除该的结点直接后继后直接前驱,这就变成了第一种或第二种情况。 ++ 若被删除结点有左子树和右子树,则让其结点的直接后继(中序排序该结点后一个结点,其右子树的最左下角结点,不一定是叶子节点)或直接前驱(中序排序该结点前一个结点,其左子树的最右下角结点,不一定是叶子结点)替代该结点,并从二叉排序树中删除该的结点直接后继后直接前驱,这就变成了第一种或第二种情况。 + +二叉排序树删除或插入时得到的二叉排序树往往与原来的不同。 + +#### 二叉排序树查找效率 + +二叉排序树的查找效率主要取决于树的高度,若是平衡二叉树,则平均查找长度为$O(\log_2n)$,若最坏情况下只有一个单枝树,则平均查找长度为$O(n)$。 + +若按顺序输入关键字则会得到单枝树。 + +查找过程来看,二叉排序树和二分查找类似。但是二分查找的判定树唯一,而二叉排序树的查找不唯一,相关关键字插入顺序会极大影响到查找孝感。 + +从维护来看,二叉排序树插入删除操作只需要移动指针,时间代价为$O(\log_2n)$,而二分查找的对象是有序顺序表,代价是$O(n)$。 + +所以静态查找时使用顺序表进行二分查找,而动态查找时使用二叉排序树。 ### 平衡二叉树 -即AVL树,树上任意一结点的左子树和右子树的高度之差不超过1。 +即$AVL$树,树上任意一结点的左子树和右子树的高度之差不超过$1$。 + +树高即树的深度。 结点的平衡因子=左子树高-右子树高。 +即平衡二叉树的平衡因子只可能为$-1,0,1$。 + 在插入一个结点时,查找路径上的所有结点都可能收到影响。 从插入点往回(从下往上)找到第一个不平衡的结点,调整以该结点为根的子树。每次调整的对象都是最小不平衡树。 -### 调整最小不平衡树 +#### 平衡二叉树插入 最重要的是根据二叉排序树的大小关系算出从大到小的序列,然后把最中间的作为新根,向两侧作为左右子树。 -#### 从结点的左孩子的左子树中插入导致不平衡 +即先找到插入路径上离插入结点最近的平衡因子的绝对值大于$1$的结点$A$,再对以$A$为根的子树,在保持二叉排序树特性的前提下调整各节点位置。 + +每次调整的都是最小不平衡子树。 + +#### 右单旋转 + +从结点的左孩子的左子树中插入导致不平衡: ```terminal A(2) @@ -266,11 +347,11 @@ BL(H+1) BR(H) ``` -BL < B < BR < A < AR +$BL,其中v、w是顶点,v称为弧尾,w称为弧头,称为从顶点v到顶点w的弧,也称v邻接到w,或w邻接自v。。 ++ 无向图:若$E$是无向边(简称边)的有限集合时,则图$G$为无向图。边是顶点的无序对,记为$(v,w)$或$(w,v)$,因为$(v,w)=(w,v)$,其中$v$、$w$是顶点。可以说顶点$w$和顶点$v$互为邻接点。边$(v,w)$依附于顶点$w$和$v$,或者说边$(v,w)$和顶点$v$、$w$相关联。 ++ 有向图:若$E$是有向边(也称弧)的有限集合时,则图$G$为有向图。弧是顶点的有序对,记为$$,其中$v$、$w$是顶点,$v$称为弧尾,$w$称为弧头,$$称为从顶点$v$到顶点$w$的弧,也称$v$邻接到$w$,或$w$邻接自$v$。$\neq$。 + 简单图:不存在重复边,且不存在顶点到自身的边。一般的图默认是简单图。 -+ 多重图:图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边与自己关联。 -+ 无向完全图:无向图中任意两个顶点之间都存在边。 -+ 有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧。 ++ 多重图:图$G$中某两个结点之间的边数多于一条,又允许顶点通过同一条边与自己关联。 ++ 无向完全图:对于无向图$\vert E\vert\in[0,n(n-1)/2]$,无向图中任意两个顶点之间都存在边。 ++ 有向完全图:对于有向图$\vert E\vert\in[0,n(n-1)]$,有向图中任意两个顶点之间都存在方向相反的两条弧。 + 稀疏图:一般$\vert E\vert<\vert V\vert\log\vert V\vert$的图。 + 稠密图:一般$\vert E\vert>\vert V\vert\log\vert V\vert$的图。 + 树:不存在回路,且连通的无向图。 -+ 有向树:一个顶点的入度为0,其余顶点入度均为1的有向图。 ++ 有向树:一个顶点的入度为$0$,其余顶点入度均为$1$的有向图。 ### 顶点的度 -对于无向图,顶点v的度是指依附于该顶点的边的条数,记为TD(v)。$\sum_{i=1}^nTD(v_i)=2\vert E\vert$。 +对于无向图,顶点$v$的度是指依附于该顶点的边的条数,记为$TD(v)$。$\sum\limits_{i=1}^nTD(v_i)=2\vert E\vert$。 -对于有向图,入度是指以顶点v为终点的有向边的条数,记为ID(v);出度指以顶点v为起点的有向边的条数,记为OD(v);顶点v的度就是其入度和出度之和,即TD(v)=ID(v)+OD(v)。$\sum_{i=1}^nID(v_i)=\sum_{i=1}^nOD(v_i)=\vert E\vert$。 +对于具有$n$个顶点,$e$条边的无向图,$\sum\limits_{i=1}^nTD(v_i)=2e$,即无向图的全部顶点的度的和等于边数的两倍。 + +对于有向图,入度是指以顶点$v$为终点的有向边的条数,记为$ID(v)$;出度指以顶点$v$为起点的有向边的条数,记为$OD(v)$;顶点$v$的度就是其入度和出度之和,即$TD(v)=ID(v)+OD(v)$。$\sum\limits_{i=1}^nID(v_i)=\sum\limits_{i=1}^nOD(v_i)=\vert E\vert$。因为每条有向边都有一个起点和重点。 ### 顶点的关系 -+ 路径:从一个点到另一个点所经过的顶点序列。 ++ 路径:从一个点到另一个点所经过的顶点序列。由顶点和相邻顶点序偶构成的边所形成的序列。 + 回路(环):第一个顶点与最后一个顶点相同的路径。 + 长度(无权图):沿路径所经过的边数成为该路径的长度。 + 简单路径:路径中的顶点不重复出现。 -+ 简单回路:由简单路径组成的回路。 -+ 点到点的距离:从顶点u到顶点v的最短路径若存在,则此路径的长度就是从u到v的路径,若不存在路径,则记该路径为无穷。 -+ 连通:在无向图中,若从顶点v到顶点u有路径存在,则称uv是连通的。 -+ 强连通:在有向图中,若从顶点v到顶点u和从顶点u到顶点v之间都有路径,则称强连通。 ++ 简单回路:由简单路径组成的回路。(除第一个和最后一个顶点外其余顶点不重复出现的回路) ++ 点到点的距离:从顶点$u$到顶点$v$的最短路径若存在,则此路径的长度就是从$u$到$v$的路径,若不存在路径,则记该路径为无穷。 + +若一个图有$n$个顶点,有大于$n-1$条边,则此图一定有环。 ### 图的连通 -+ 连通图:无向图中任意两个顶点之间都是连通的。对于n个顶点的无向图,若其是连通图,则最少需要n-1条边,若其是非连通图,则最多有$C_{n-1}^2$条边。 -+ 强连通图:有向图中任意两个顶点之间都是强连通的。对于n个顶点的有相同,若其是强连通图,则最少需要n条边来形成环路。 -+ 子图:设有两个图G=(V,E)和G'=(V',E'),若V'是V的子集,E'是E的子集,则G'是G的子图。 -+ 生成子图:若有满足V(G')=V(G)的子图G',则G'是G的生成子图。 -+ 连通分量:无向图G中的极大连通子图称为G的连通分量;对任何连通图而言,连通分量就是其自身。 -+ 强连通分量:有向图G中的极大连通子图称为G的强连通分量;对任何强连通图而言,强连通分量就是其自身。 -+ 生成树:包含连通图中全部顶点的一个极小连通子图。若图的顶点为n,则其生成树包含n-1条边,若去掉生成树的一条边则会变成非连通图,若加上一条边则会形成一个回路。 ++ 连通:在无向图中,若从顶点$v$到顶点$u$有路径存在,则称$uv$是连通的。 ++ 强连通:在有向图中,若从顶点$v$到顶点$u$和从顶点$u$到顶点$v$之间都有路径,则称强连通。 ++ 连通图:无向图中任意两个顶点之间都是连通的。对于$n$个顶点的无向图,若其是连通图,则最少需要$n-1$条边,若其是非连通图,则最多有$C_{n-1}^2$条边。 ++ 强连通图:有向图中任意两个顶点之间都是强连通的。对于$n$个顶点的有相同,若其是强连通图,则最少需要$n$条边来形成环路。 ++ 子图:设有两个图$G=(V,E)$和$G'=(V',E')$,若$V'$是$V$的子集,$E'$是$E$的子集,则$G'$是$G$的子图。 ++ 生成子图:若有满足$V(G')=V(G)$的子图$G'$,则$G'$是$G$的生成子图。 ++ 连通分量:无向图$G$中的极大连通子图称为$G$的连通分量;对任何连通图而言,连通分量就是其自身。 ++ 强连通分量:有向图$G$中的极大连通子图称为$G$的强连通分量;对任何强连通图而言,强连通分量就是其自身。 ++ 生成树:包含连通图中全部顶点的一个极小连通子图。若图的顶点为$n$,则其生成树包含$n-1$条边,若去掉生成树的一条边则会变成非连通图,若加上一条边则会形成一个回路。 + 生成森林:在非连通图中,连通分量的生成树构成了非连通图的生成森林。 +无向图研究连通性,有向图研究强连通性。 + +区分极大连通子图和极小连通子图。极大连通子图是无向图的连通分量,极大即要求该连通子图包含其所有的边;极小连通子图是既要保持图连通又要使得边数最少的子图。 + ### 图的权 + 边的权:在一个图中,每条边都可以表上具有某种含义的数值,这就是该边的权值。 @@ -59,20 +69,28 @@ #### 无权图 -若v[i][j]=0,表示$v_{i+1}$到$v_{j+1}$是不连通的,若v[i][j]=1,表示$v_{i+1}$到$v_{j+1}$是连通的。 +若$v[i][j]=0$,表示$v_{i+1}$到$v_{j+1}$是不连通的,若$v[i][j]=1$,表示$v_{i+1}$到$v_{j+1}$是连通的。 #### 网 -若v[i][j]=∞,表示$v_{i+1}$到$v_{j+1}$是不连通的,若v[i][j]=某权值,表示$v_{i+1}$到$v_{j+1}$是连通的。 +若$v[i][j]=\infty$,表示$v_{i+1}$到$v_{j+1}$是不连通的,若$v[i][j]=$某权值,表示$v_{i+1}$到$v_{j+1}$是连通的。 ## 图的存储结构 ### 邻接矩阵 -使用一个长宽皆为$\vert v\vert$的二维矩阵v,从左上角到右上角,从左上角到左下角,分别标识表示$v_1,v_2\cdots,v_n$。假设矩阵的索引从0开始,而结点编号从1开始。 +用一个一维数组保存顶点,用一个二维数组保存边,这个二维数组就是邻接矩阵。 -+ 对于无向图,第i个结点的度=第i行或第i列的非零元素个数。 -+ 对于有向图,第i个结点的入度=第i行的非零元素个数;第i个结点的出度=第i列的非零元素个数;第i个结点的度=第i行的非零元素个数+第i列的非零元素个数。 +使用一个长宽皆为$\vert v\vert$的二维矩阵$v$,从左上角到右上角,从左上角到左下角,分别标识表示$v_1,v_2\cdots,v_n$。 + + + +若$(v_i,v_j)$是$E(G)$中的边矩阵$A[i][j]$就设为$1$,否则是$0$。 + +对于带权图而言,保存的是对应的权值。 + ++ 对于无向图,第$i$个结点的度=第$i$行或第$i$列的非零元素个数。 ++ 对于有向图,第$i$个结点的入度=第$i$行的非零元素个数;第$i$个结点的出度=第$i$列的非零元素个数;第$i$个结点的度=第$i$行的非零元素个数+第$i$列的非零元素个数。 空间复杂度是$O(\vert V\vert^2)$。 @@ -80,13 +98,15 @@ 对于无向图,因为没有方向,所以只有两点连接就是连通的,从而无向图的邻接矩阵都是主对角线对称的。因为对称,所以可以压缩存储。 -设图G的邻接矩阵为$A$,矩阵元素为0或1,则$A^n$的元素$A^n[i][j]$表示由顶点$v_{i+1}$到结点$v_{j+1}$的长度为n的路径的数目。 +设图$G$的邻接矩阵为$A$,矩阵元素为$0$或$1$,则$A^n$的元素$A^n[i][j]$表示由顶点$v_{i+1}$到结点$v_{j+1}$的长度为$n$的路径的数目。 ### 邻接表 +对于稀疏图临界矩阵浪费空间,使用邻接表回更方便。 + 邻接表存储方式是顺序存储与链式存储的结合,存储方式和树的孩子表示法类似。 -使用一个数组保存图的每一个结点,每一个结点元素包含一个指向后一条边的指针。 +使用一个数组顺序保存图的每一个结点,称为顶点表,使用链式存储让每一个结点元素包含一个指向后一条边的指针,称为边表。 对于无向图,因为同一条边两端的点会重复存储,所以空间复杂度为$O(\vert V\vert+2\vert E\vert)$,而对于有向图空间复杂度为$O(\vert V\vert+\vert E\vert)$。 @@ -124,11 +144,11 @@ + 数据域。 + 该顶点相连的第一条边。 + 边结点:被顶点结点指向的结点。 - + 边一端编号i。 - + 边另一端编号j。 + + 边一端编号$i$。 + + 边另一端编号$j$。 + 权值。 - + 依附于i的下一条边。 - + 依附于j的下一条边。 + + 依附于$i$的下一条边。 + + 依附于$j$的下一条边。 空间复杂度为$O(\vert V\vert+\vert E\vert)$。