1
0
mirror of https://github.com/Didnelpsun/CS408.git synced 2026-02-13 23:55:58 +08:00

更新图

This commit is contained in:
Didnelpsun
2021-09-26 23:39:13 +08:00
parent f2617936b0
commit 25423916c0
10 changed files with 627 additions and 135 deletions

43
Code/Code/head/graph.h Normal file
View File

@@ -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;

View File

@@ -7,4 +7,11 @@
// 定义串块链数据长度
#define DATASIZE 4
// 定义默认数据类型
typedef char element_type;
typedef char element_type;
// 权值类型
typedef int weight_type;
// 比较元素方法
int CompareElem(element_type elem1, element_type elem2){
return 0;
}

View File

@@ -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<BinaryTree> 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<BinaryTree> 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<BinaryTree> 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;
}
}
// 二叉排序树查找
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<length){
if(!InsertBinarySortTree(tree,elem[start+i]))
return false;
i++;
}
return true;
}
// 删除二叉排序树
bool DeleteBinarySortTree()

View File

@@ -119,7 +119,7 @@ bool CreatePreOrderThreadBinaryTree(ThreadBinaryTree tree) {
return true;
}
// 先序后历线索化
// 后序遍历线索化
bool PostOrderThreadBinaryTree(ThreadBinaryTree &tree, ThreadBinaryTree &pre) {
if (tree != nullptr) {
// 递归线索化左子树

29
Code/Code/head/tree.h Normal file
View File

@@ -0,0 +1,29 @@
#include "head.h"
// 双亲表示法
// 结点
typedef struct {
element_type data;
// 双亲位置
int parent;
} ParentTreeNode;
// 数
typedef struct {
// 数组
ParentTreeNode* data;
// 长度
int length;
// 最大容量
int max_length;
} ParentTree;
// 孩子兄弟表示法
// 结点
typedef struct ChildSiblingTreeNode {
// 数据
element_type data;
// 第一个孩子
struct ChildSiblingTreeNode *frist_child;
// 下一个兄弟
struct ChildSiblingTreeNode *next_sibling;
} ChildSiblingTreeNode, *ChildSiblingTree;

View File

@@ -13,6 +13,8 @@
#include "../head/link_string.h"
#include "../head/link_tree.h"
#include "../head/thread_tree.h"
#include "../head/tree.h"
#include "../head/graph.h"
using namespace std;

View File

@@ -1,4 +1,4 @@
# 树
# 树习题
## 基本概念
@@ -68,6 +68,34 @@ $D.250$
### 二叉树遍历
#### 序列遍历概念
**例题** ()的遍历仍需要栈的支持。
$A.$前序线索树
$B.$中序线索树
$C.$后序线索树
$D.$所有线索树
解:$C$。前序遍历(中左右)、中序遍历(左中右)的最后访问的节点都是左或右叶节点,叶节点是没有子树的,所以两个指针域空出来了,可以存放线索指针用于回溯。但是后序遍历(左右中),最后访问的是子树的根节点,子树根节点的两个指针域都指向子树了,所以不能空出来存放线索信息,只能借助栈存储。
#### 二叉树个数
**例题** 先序序列为$a,b,c,d$的不同二叉树的个数是()。
$A.13$
$B.14$
$C.15$
$D.16$
解:由于只有先序序列和中序序列才能确定一个二叉树,所以现在已知先序序列,要求多少个二叉树就要求中序序列能有多少个。由于二叉树遍历的递归算法可以由带有栈的非递归算法实现,所以栈可以将序列转换为二叉树。所以这个问题就等于入栈先序序列,出栈中序序列。这就是卡特兰数$\dfrac{1}{n+1}C_{2n}^n=\dfrac{1}{n+1}\dfrac{(2n)!}{n!\times n!}=\dfrac{1}{5}\dfrac{8!}{4!\times4!}=14$。
#### 结点关系
**例题** 在二叉树中有两个结点$m$和$n$,若$m$是$n$的祖先,则使用()可以找到从$m$到$n$的路径。
@@ -82,6 +110,18 @@ $D.$层次遍历
解:$C$。首先无论是什么遍历方式都会是从左到右的顺序,所以最先找到的一定是左边的结点,最后找到的一定是右边的结点。$m$是$n$的祖先。先序遍历访问次序:左根右,当$n$在左边遍历时会找到$m$到$n$的路径,而当$n$在右边时,由于已经遍历完根节点和左子树,则根和左部分的结点没用了就会出栈(使用递归算法时函数数据由栈保存),则$m$就丢掉了,也就不能找到$m$到$n$的路径。同理中序遍历也是会丢掉根和左部分,若$n$在右边则会丢失路径。层次遍历每遍历都会丢掉根节点,所以也不能找到。而后序遍历最后遍历根结点,所以根结点会一直保留到子结点全部遍历完才会丢弃,则此时就能找到$n$到$m$的路径。
**例题** 若$X$是二叉中序线索树中一个有左孩子的结点,且$X$不为根,则$X$的前驱为()。
$A.X$的双亲
$B.X$的右子树中最左的结点
$C.X$的左子树中最右结点
$D.X$的左子树中最右叶结点
解:$C$。在二叉中序线索树中,某结点若有左孩子,则按照中序“左根右”的顺序,该结点的前驱结点为左子树中最右的一个结点(注意,并不一定是最右叶子结点)。
#### 遍历序列
**例题** 在二叉树的前序序列、中序序列和后序序列中,所有叶子结点的先后顺序()。
@@ -106,4 +146,134 @@ $C.3,2,4,1$
$D.4,3,2,1$
解:$C$。
解:$C$。前序序列为$NLR$,后序序列为$LRN$,由于前序序列和后序序列刚好相反,故不可能存在一个结点同时有左右孩子,即二叉树的高度为$4$。$1$为根结点,由于根结点只能有左孩子(或右孩子),因此在中序序列中,$1$或在序列首或在序列尾,$A,B,C,D$皆满足要求。仅考虑以$1$的孩子结点$2$为根结点的子树,它也只能有左孩子(或右孩子),因此在中序序列中,$2$或在序列首或在序列尾,$A, B,D$皆满足要求,故选$C$。
## 树与森林
### 森林与树的转换
**例题** 利用二叉链表存储森林时,根结点的右指针是()。
$A.$指向最左兄弟
$B.$指向最右兄弟
$C.$一定为空
$D.$不一定为空
解:$D$。森林与二叉树具有对应关系,因此,我们存储森林时应先将森林转换成二叉树,转换的方法就是“左孩子右兄弟”,与树不同的是,若存在第二棵树,则二叉链表的根结点的右指针指向的是森林中的第二棵树的根结点。若此森林只有一棵树,则根结点的右指针为空。因此,右指针可能为空也可能不为空。
### 转换结点关系
**例题** 将森林转换为对应的二叉树,若在二叉树中,结点$u$是结点$v$的父结点的父结点,则在原来的森林中,$u$和$v$可能具有的关系是()。
.父子关系
Ⅱ.兄弟关系
Ⅲ.$u$的父结点与$v$的父结点是兄弟关系
$A.$只有Ⅱ
$B.$Ⅰ和Ⅱ
$C.$Ⅰ和Ⅲ
$D.$Ⅰ、Ⅱ和Ⅲ
解:$B$。森林与二叉树的转换规则为“左孩子右兄弟”。在最后生成的二叉树中,父子关系在对应的森林关系中可能是兄弟关系或原本就是父子关系。情形Ⅰ:若结点$v$是结点$u$的第二个孩子结点,在转换时,结点$v$就变成结点$u$的第一个孩子的右孩子,符合要求。情形Ⅱ:结点$u$和$v$是兄弟结点的关系,但二者之中还有一个兄弟结点$k$,转换后结点$v$就变为结点$k$的右孩子,而结点$k$则是结点$u$的右孩子,符合要求。情形Ⅲ:结点$v$的父结点要么是原先的父结点,要么是兄弟结点。若结点$u$的父结点与$v$的父结点是兄弟关系,则转换之后不可能出现结点$u$是结点$v$的父结点的父结点的情形。
#### 结点数量
**例题** 设$F$是一个森林,$B$是由$F$变换来的二叉树。若$F$中有$n$个非终端结点,则$B$中右指针域为空的结点有()个。
$A.n-1$
$B.n$
$C.n+1$
$D.n+2$
解:$C$。根据森林与二叉树转换规则“左孩子右兄弟”。二叉树$B$中右指针域为空代表该结点没有兄弟结点。森林中每棵树的根结点从第二个开始依次连接到前一棵树的根的右孩子,因此最后一棵树的根结点的右指针为空。另外,每个非终端结点,其所有孩子结点在转换之后,最后一个孩子的右指针也为空,故树$B$中右指针域为空的结点有$n+1$个。
## 树的应用
### 二叉排序树
### 平衡二叉树
#### 结点数
**例题** 含有$20$个结点的平衡二叉树的最大深度为()。
$A.4$
$B.5$
$C.6$
$D.7$
解:$C$。平衡二叉树结点数的递推公式为$n_0=0$$n_1=1$$n_2=2$$n_h=1+n_{h-1}+n_{h-2}$$h$为平衡二叉树高度,$n$为构造此高度的平衡二叉树所需的最少结点数)。通过递推公式可得,构造$5$层平衡二叉树至少需$12$个结点,构造$6$层至少需要$20$个结点。
#### 平衡因子
**例题** 若将关键字$1,2,3,4,5,6,7$依次插入初始为空的平衡二叉树$T$,则$T$中平衡因子为$0$的分支结点的个数是()。
$A.0$
$B.1$
$C.2$
$D.3$
解:$D$。平衡因子为$0$的分支结点即树两边同样高度。根据平衡二叉树的构造过程可得这个平衡二叉树是满二叉树,结点为$2,4,6$。
### 哈夫曼树
**例题** 设哈夫曼编码的长度不超过$4$,若已对两个字符编码为$1$和$01$,则还最多可对()个字符编码。
$A.2$
$B.3$
$C.4$
$D.5$
解:$C$。在哈夫曼编码中,一个编码不能是任何其他编码的前缀。已对两个字符编码为$1$和$01$,则第一位第二位都只能是$0$。$3$位编码可能是$001$,对应的$4$位编码只能是$0000$和$0001$。$3$位编码也可能是$000$,对应的$4$位编码只能是$0010$和$0011$。若全采用$4$位编码,则可以为$0000,0001,0010,0011$。题中问的是最多,故选$C$。
<!-- 若哈夫曼编码的长度只允许小于等于$4$,则哈夫曼树的高度最高是$5$,已知一个字符编码为$1$,另一个字符编码是$01$这说明第二层和第三层各有一个叶子结点为使得该树从第3层起能够对尽可能多的字符编码余下的二叉树应该是满二叉树底层可以有$4$个叶结点,最多可以再对$4$个字符编码。 -->
**例题** 若度为$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$的叶结点,则删除可能相同也可能不同。一般而言删除一个结点,然后重新加入是从叶子结点处加入,所以肯定不是在原来的根位置。但是如果删除会失衡,则会调整,所以可能会调整回原来的位置。

View File

@@ -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<B<BR<A<AR$
由于在结点A的左孩子B的的左子树BL上插入了新结点A的平衡因子由1变成了2,导致以A为根的子树失去了平衡,需要进行一次向右的旋转操作。
由于在结点$A$的左孩子$B$的的左子树$BL$上插入了新结点,$A$的平衡因子由$1$变成了$2$,导致以$A$为根的子树失去了平衡,需要进行一次向右的旋转操作。
A的左孩子B向右上旋转代替A成为根结点,将A结点向右下选择为成B的右子树的根结点,而B的原右子树则作为A结点的左子树。
$A$的左孩子$B$向右上旋转代替$A$成为根结点,将$A$结点向右下选择为成$B$的右子树的根结点,而$B$的原右子树则作为$A$结点的左子树。
```terminal
B(0)
@@ -284,7 +365,9 @@ BL(H+1) A(0)
BR(H) AR(H)
```
#### 从结点的右孩子的右子树中插入导致不平衡
#### 左单旋转
从结点的右孩子的右子树中插入导致不平衡:
```terminal
A(-2)
@@ -298,11 +381,11 @@ AL(H) B(-1)
BL(H) BR(H+1)
```
AL < A < BL < B < BR
$AL<A<BL<B<BR$
由于在结点A的右孩子(R)的右子树(R)上插入了新结点,A的平衡因子由-1减至-2导致以A为根的子树失去平衡,需要一-次向左的旋转操作。
由于在结点$A$的右孩子$R$的右子树$R$上插入了新结点,$A$的平衡因子由$-1$减至$-2$,导致以$A$为根的子树失去平衡,需要一-次向左的旋转操作。
A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
$A$的右孩子$B$向左上旋转代替$A$成为根结点,将$A$结点向左下旋转成为$B$的左子树的根结点,而$B$的原左子树则作为$A$结点的右子树。
```terminal
B(0)
@@ -316,7 +399,9 @@ AL < A < BL < B < BR
AL(H) BR(H)
```
#### 从结点的左孩子的右子树中插入导致不平衡
#### 先左后右双旋转
从结点的左孩子的右子树中插入导致不平衡:
```terminal
A(2)
@@ -350,11 +435,11 @@ BL(H) C(-1)
CL(H) CR(H-1)
```
将BR拆分为C和CL、CR假设插入的是CR部分插入CL也同理
$BR$拆分为$C$和$CL$、$CR$,假设插入的是$CR$部分,插入$CL$也同理
BL < B < CL < C < CR < A < AR
$BL<B<CL<C<CR<A<AR$
由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
由于在$A$的左孩子$L$的右子树$R$上插入新结点,$A$的平衡因子由$1$增至$2$,导致以$A$为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转先将$A$结点的左孩子$B$的右子树的根结点$C$向左上旋转提升到$B$结点的位置,然后再把该$C$结点向右上旋转提升到$A$结点的位置。
```terminal
C(0)
@@ -380,7 +465,9 @@ BL(H) CL(H-1) CR(H) AR(H)
BL(H) CL(H) CR(H-1) AR(H)
```
#### 从结点的右孩子的左子树中插入导致不平衡
#### 先右后左双旋转
从结点的右孩子的左子树中插入导致不平衡:
```terminal
A(-2)
@@ -414,9 +501,9 @@ CL(H) CR(H-1)
CL(H-1) CR(H)
```
AL < A < CL < C < CR < B < BR
$AL<A<CL<C<CR<B<BR$
由于在A的右孩子(R)的左子树(L). 上插入新结点,A的平衡因子由-1减至-2导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
由于在$A$的右孩子$R$的左子树$L$. 上插入新结点,$A$的平衡因子由$-1$减至$-2$,导致以$A$为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将$A$结点的右孩子$B$的左子树的根结点$C$向右上旋转提升到$B$结点的位置,然后再把该$C$结点向左上旋转提升到$A$结点的位置。
```terminal
C(0)
@@ -442,6 +529,10 @@ AL(H) CL(H) CR(H-1) BR(H)
AL(H) CL(H-1) CR(H) BR(H)
```
#### 平衡二叉树查找
含有$n$个结点的平衡二叉树最大深度为$O(\log_2n)$,平均查找长度为$O(\log_2n)$。
### 哈夫曼树
#### 哈夫曼树的定义
@@ -454,63 +545,26 @@ AL(H) CL(H-1) CR(H) BR(H)
#### 构造哈夫曼树
给定n个权值分别为$w_1, w_2\cdots w_n$的结点,构造哈夫曼树的算法描述如下:
给定$n$个权值分别为$w_1, w_2\cdots w_n$的结点,构造哈夫曼树的算法描述如下:
1. 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F
2. 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
3.F中删除刚才选出的两棵树,同时将新得到的树加入F中。
4. 重复步骤二和三,直至F中只剩下一棵树为止。
1. 将这$n$个结点分别作为$n$棵仅含一个结点的二叉树,构成森林$F$
2. 构造一个新结点,从$F$中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
3.$F$中删除刚才选出的两棵树,同时将新得到的树加入$F$中。
4. 重复步骤二和三,直至$F$中只剩下一棵树为止。
+ 每个初始结点最终都会变成叶子结点,且权值越小到根结点的路径长度越长。
+ 哈夫曼树的结点总数为2n-1。
+ 构建哈夫曼树时,都是两个两个合在一起的,所以没有度为一的结点,即n1=0。
+ 哈夫曼树不唯一但是WPL必然最优。
+ 哈夫曼树的结点总数为$2n-1$
+ 构建哈夫曼树时,都是两个两个合在一起的,所以没有度为一的结点,即$n_1=0$
+ 哈夫曼树不唯一,但是$WPL$必然最优。
哈夫曼树适合采用顺序结构:已知叶子结点数n0且n1=0则总结点数为2n2+1或2n0-1且哈夫曼树构造过程需要不停地修改指针用链式存储的话很容易造成指针偏移。
哈夫曼树适合采用顺序结构:已知叶子结点数$n_0$,且$n_1=0$,则总结点数为$2n_2+1$(或$2n_0-1$),且哈夫曼树构造过程需要不停地修改指针,用链式存储的话很容易造成指针偏移。
#### 哈夫曼编码
哈夫曼编码基于哈夫曼树利用哈夫曼树对01的数据进行编码来表示不同的数据含义因为哈夫曼树必然权值最小所以对于越常使用的编码越短越少使用的编码越长所以发送信息的总长度是最小的。
哈夫曼编码基于哈夫曼树,利用哈夫曼树对$01$的数据进行编码,来表示不同的数据含义,因为哈夫曼树必然权值最小,所以对于越常使用的编码越短,越少使用的编码越长,所以发送信息的总长度是最小的。
将编码使用次数作为权值构建哈夫曼树,然后根据左0右1的原则,按根到叶子结点的路径就变成了哈夫曼编码。
将编码使用次数作为权值构建哈夫曼树,然后根据左$0$右$1$的原则,按根到叶子结点的路径就变成了哈夫曼编码。
哈夫曼编码是可变长度编码,即允许对不同字符用不等长的二进制表示,也是一个前缀编码,没有一个编码是另一个编码的前缀。
同样哈夫曼编码也可以用于压缩。
## 树与森林
### 树的存储结构
+ 双亲表示法:是一种顺序存储方式,每个结点中保存指向双亲的指针。查找双亲方便,但是查找孩子就只能从头遍历。
+ 孩子表示法是顺序加链式存储方法顺序存储所有元素添加一个firstChild域指向第一个孩子结构体的指针孩子结构体包括元素位置索引与指向下一个孩子结构体的$next$指针。
+ 孩子兄弟表示法:是一种链式存储方式,定义了两个指针,分别指向第一个孩子与右兄弟,类似于二叉树,可以利用二叉树来实现对树的处理 。
### 森林与树的转换
树与森林的转换,树与二叉树的转换都可以使用孩子兄弟表示法来实现,左孩子右兄弟,如果是森林则认为其根结点为兄弟。
### 树的遍历
+ 先根遍历:若树非空,先访问根结点,再依次对每棵子树进行先根遍历。
+ 后根遍历:若树非空,先依次对每棵子树进行后根遍历,最后访问根结点。
+ 层次遍历:用辅助队列实现:
1. 若树非空,根结点入队。
2. 若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队。
3. 重复步骤二直到队列为空。
### 森林的遍历
先序遍历森林:
1. 访问森林中第一棵树的根结点。
2. 先序遍历第一棵树中根结点的子树森林。
3. 先序遍历除去第一棵树之后剩余的树构成的森林。
中序遍历森林:
1. 先序遍历第一棵树中根结点的子树森林。
2. 访问森林中第一棵树的根结点。
3. 中序遍历除去第一棵树之后剩余的树构成的森林。
可以把每个树先按序遍历再合在一起,也可以先转换为二叉树再遍历。

View File

@@ -0,0 +1,113 @@
# 图习题
## 基本概念
### 连通边数
**例题** 若无向图$G=(V,E)$中含有$7$个顶点,要保证图$G$在任何情况下都是连通的,则需要的边数最少是()。
$A.6$
$B.15$
$C.16$
$D.21$
解:$C$。题干要求在“任何情况”下都是连通的,考虑最极端的情形,即图$G$的$6$个顶点构成一个完全无向图,即在前六个顶点加上所有能加的边,一共需要$6\times5\div2$条(六个顶点连接其他五条,并且每条边连两个顶点)。再加上一条边后,第$7$个顶点必然与此完全无向图构成一个连通图,所以最少边数$=6\times5\div2+1=16$。若边数$n$小于等于$15$,可以使这$n$条边仅连接图$G$中的某$6$个顶点,从而导致第$7$个顶点无法与这$6$个顶点构成连通图,不满足“任何情况”。
**例题** 一个有$28$条边的非连通无向图至少有()个顶点。
$A.7$
$B.8$
$C.9$
$D.10$
解:$C$。考查至少有多少个顶点的情形,我们考虑该非连通图最极端的情况,即它由一个完全图加一个独立的顶点构成,此时若再加一条边,则必然使图变成连通图。在$28=n(n-1)\div2=8\times7\div2$条边的完全无向图中,总共有$8$个顶点,再加上$1$个不连通的顶点,共$9$个顶点。
### 顶点数
对于具有$n$个顶点,$e$条边的无向图,$\sum\limits_{i=1}^nTD(v_i)=2e$。无向图的边数两倍等于图各顶点度数的总和。
**例题** 无向图$G$有$23$条边,度为$4$的顶点有$5$个,度为$3$的顶点有$4$个,其余都是度为$2$的顶点,则图$G$最多有()个顶点。
$A.11$
$B.12$
$C.15$
$D.16$
解:$D$。由于$\sum\limits_{i=1}^nTD(v_i)=2e$,则$2\times23=4\times5+3\times4+2\times v_2$,则$v_2=7$,所以最多有$5+4+7=16$个顶点。
### 度数
**例题** 在有$n$个顶点的有向图中,每个顶点的度最大可达()。
$A.n$
$B.n-1$
$C.2n$
$D.2n-2$
解:$D$。在有向图中,顶点的度等于入度与出度之和。$n$个顶点的有向图中,任意一个顶点最多还可以与其他$n-1$个顶点有一对指向相反的边相连,所以一共$2(n-1)=2n-2$。
### 树与森林
**例题** 若具有$n$个顶点的图是一个环,则它有()棵生成树。
$A.n^2$
$B.n$
$C.n-1$
$D.1$
解:$B$。由于具有$n$个顶点的图是一个环,所以一共有$n$条边,而一个环去掉一条边就可以变成一棵生成树,所以可以去掉$n$种边得到$n$棵生成树。
**例题** 若一个具有$n$个顶点、$e$条边的无向图是一个森林,则该森林中必有()棵树。
$A.n$
$B.e$
$C.n-e$
$D.1$
解:$C$。$n$个结点的树有$n-1$条边,假设森林中有$x$棵树,将每棵树的根连到一个添加的结点,则成为一棵树,结点数是$n+1$,边数是$e+x=n$,从而可知$x=n-e$。另解设森林中有$x$棵树,则再用$x-1$条边就能把所有的树连接成一棵树,此时,边数$+1=$顶点数,即$e+(x-1)+1=n$,故$x=n-e$。
## 图的存储结构
### 邻接表
**例题** 若邻接表中有奇数个边表结点,则()。
$A.$图中有奇数个结点
$B.$图中有偶数个结点
$C.$图为无向图
$D.$图为有向图
解:$D$。无向图采用邻接表表示时,每条边存储两次,所以其边表结点的个数为偶数。题中边表结点为奇数个,故必然是有向图,且有奇数条边。
**例题** 假设有$n$个顶点、$e$条边的有向图用邻接表表示,则删除与某个顶点$v$相关的所有边的时间复杂度为()。
$A.O(n)$
$B.O(e)$
$C.O(n+e)$
$D.O(ne)$
解:$C$。删除与某顶点$v$相关的所有边的过程如下:先删除下标为$v$的顶点表结点的单链表,出边数最多为$n-1$,对应时间复杂度为$O(n)$;再扫描所有边表结点,删除所有的顶点$v$的入边,对应时间复杂度为$O(e)$。故总的时间复杂度为$O(n+e)$。

View File

@@ -1,56 +1,66 @@
# 图
主要了解概念,算法具体实现不是重点。
## 基本概念
### 图的定义
图是顶点集和边集构成的二元组,即图G由顶点集V和边集E组成记为G=(V,E)其中V(G)表示图G中顶点的有限非空集E(G)表示图G中顶点之间的关系(边)集合。
图是顶点集和边集构成的二元组,即图$G$由顶点集$V$和边集$E$组成,记为$G=(V,E)$,其中$V(G)$表示图$G$中顶点的有限非空集,$E(G)$表示图$G$中顶点之间的关系(边)集合。
若V=$\{v_1,v_2\cdots,v_n\}$,则用|V|表示图G中顶点的个数,也称图G的阶E=$\{(u,v)|u\in V,v\in V\}$,用|E|表示图G中边的条数。
$V=\{v_1,v_2\cdots,v_n\}$,则用$\vert V\vert$表示图$G$中顶点的个数,也称图$G$的阶,$E=\{(u,v)\vert u\in V,v\in V\}$,用$\vert E\vert$表示图$G$中边的条数。
图一定是非空的,即V一定是非空集。
图一定是非空的,即$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>称为从顶点v到顶点w的弧也称v邻接到w或w邻接自v。<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>$称为从顶点$v$到顶点$w$的弧,也称$v$邻接到$w$,或$w$邻接自$v$。$<v,w>\neq<w,v>$
+ 简单图:不存在重复边,且不存在顶点到自身的边。一般的图默认是简单图。
+ 多重图:图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$
<!-- 假设矩阵的索引从$0$开始,而结点编号从$1$开始。 -->
若$(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)$。