第三至五讲 树
node
1.掌握查找的概念。c++
Q1:静态查找和动态查找的区别是?算法
静态查找中,集合的记录是固定的;而动态查找中记录是动态变化的,除了查找,还可能发生插入和删除。数组
2.掌握技巧"哨兵"的设置。数据结构
Q1:哨兵的做用?ide
用于顺序表查找,所谓“哨兵”即用一个特殊值来做为数组的边界,能够减小一条判断语句(i<n),提升程序的效率。函数
3.掌握二分查找。性能
Q1:二分查找能够应用的前提?测试
①用于查找的数据元素的关键字知足有序ui
②而且连续存放(必须是数组,链表不能够)
Q2:二分查找的算法和时间复杂度?
二分查找的时间复杂度为O(log2N)
1 int BinarySearch(StaticTable*Tbl,ElementType K)//在静态表Tbl中查找关键字为K的数据元素 2 { 3 int left,right,mid,NoFound=-1; 4 left=1;//初始左边界 5 right=Tbl->Length;//初始右边界 6 while(left<=right) 7 { 8 mid=(left+right)/2; 9 if(K<Tbl->Element[mid]) 10 right=mid-1;//调整右边界 11 else if(K>Tbl->Element[mid]) 12 left=mid+1;//调整左边界 13 else return mid;//查找成功 14 } 15 return NotFound;//查找不成功,返回-1 16 }
Q3:二分查找对数据的存储有什么启示?
若是数据按照如图所示的方式存储,能够达到相同的查找效果。
4.掌握树的概念。
Q1:二叉树的性质及其证实?
对于任何非空二叉树T,若n0表示叶结点的个数、n2是度为2的非叶结点的个数,那么二者知足关系n0=n2+1,证实略。(这条性质很是不熟悉!)
应用该性质的一道课后题:
如有一二叉树的总结点数为98,只有一个儿子的结点数为48,则该树的叶结点数是多少?
这样的树不存在。总结点数=n0+n1+n2=98,已知n1=48,n2=n0-1,,代入得n0=51/2,故这样的树不存在。
其他性质略。
Q2: 课后思考题:一棵度为 m的树有n个节点。若每一个节点直接用m个链指向相应的儿子,则表示这个树所须要的总空间是n*(m+1) ,(假定每一个链以及表示节点的数据域都是一个单位空间).。当采用儿子/兄弟(First Child/Next Sibling)表示法时,所需的总空间是
由于每一个节点都得须要m个链来指向相应的儿子,再加上一共有n个节点,故一共有n*(m+1)个子节点。现考虑儿子/兄弟表示法,将n个节点串联,由于每一个节点都由两个部分组成,一个是指向下一个兄弟节 点,一个是指向子节点,因此在树中,这两个部分都是须要做为一个单位(即链)来存储的,加上n个节点自己,一共须要3n个空间。
5.掌握树的存储方式。
6. 掌握树的遍历方法(递归、非递归遍历)
二叉树遍历的核心问题是 二维结构的线性化,须要一个存储结构保暂时不访问的结点。
Q1: 树的非递归遍历如何实现?
非递归算法实现的基本思路:使用堆栈。
以中序遍历为例:
算法思想:①遇到一个结点,将其压入栈中,并遍历它的左子树。
②当左子树遍历结束后,从栈顶弹出这个节点并访问它
③按照右指针中序遍历该结点的右子树。
void InOrderTraversal(BinTree BT) { BinTree T=BT; Stack S=CreatStack(MaxSize);//建立并初始化堆栈 while(T||!IsEmpty(S)) { while(T) {//一直向左并将沿途结点压入堆栈 Push(S,T); T=T->Left; } if(!IsEmpty(S)) { T=Pop(S);//结点弹出堆栈 printf("%5d",T->Data);//访问打印结点 T=T->Right;//转向右子树 } } } 中序
Q2:如何实现树的层序遍历?
利用队列实现。
Step 1:先将根结点入队。
Step 2:从队列中取出一个元素。
Step 3:访问该元素所指的结点。
Step 4:若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队。
1 void LevelOrderTraversal(BinTree BT) 2 { 3 Queue Q;BinTree T; 4 if(!BT)return; 5 Q=CreateQueue(MaxSize); 6 AddQ(Q,BT); 7 while(!IsEmptyQ(Q)) 8 { 9 T=DeleteQ(Q); 10 printf("%d\n",T->data); 11 if(T->Left) AddQ(Q,T->Left); 12 if(T->Right) AddQ(Q,T->Right); 13 } 14 15 }
Q3: 如何求二叉树的高度?
1 int PostOrderGetHeight(BinTree BT) 2 { 3 int HL,HR,MaxH; 4 if(BT) 5 { 6 HL=PostOrderGetHeight(BT->Left); 7 HR=PostOrderGetHeight(BT->Right); 8 maxH=(HL>HR)?HL:HR; 9 return(MaxH+1); 10 } 11 else 12 return 0; 13 }
Q4:如何根据二元表达式树遍历获得算式?
经过三种顺序遍历获得三种表达式。
注意:中缀表达式会受到运算符优先级的影响。
Q5:若是由两种遍历序列肯定一棵二叉树,那么必需要有的遍历?
中序遍历。
Q6:课后思考题:假定只有四个结点A、B、C、D的二叉树,其前序遍历序列为ABCD,则下面哪一个序列是不可能的中序遍历序列?
A. ABCD
B. ACDB
C. DCBA
D. DABC
注意解题方法,容易思路混乱。逐一分析:
A
B
C
7.掌握二叉搜索树及其相关操做。
Q1:请给出二叉搜索树的定义?
二叉搜索树又称二叉排序树或者二叉查找树,能够为空,若是不为空,它知足如下条件:
①非空左子树的全部键值小于其根结点的键值;
②非空右子树的全部键值大于其根结点的键值;
③左右子树都是二叉搜索树。
Q2:如何进行树的查找操做?如何改进算法使效率更高?
1 Position Find(ElementType X,BinTree BST) 2 { 3 if(!BST)return NULL; 4 if(X>BST->Data) 5 return Find(X,BST->Right); 6 else if(X<BST->Data) 7 return Find(X,BST->Left); 8 else 9 return BST; 10 }
上述算法采用尾递归的方法(尾递归:即指在函数返回时用到递归)由于非递归函数的执行效率更高,所以将尾递归改成迭代函数。
1 Position IterFind(ElementType X,BinTree BST) 2 { 3 while(BST) 4 { 5 if(X>BST->Data) 6 BST=BST->Right; 7 else if(X<BST->Data) 8 BST=BST->Left; 9 else 10 return BST; 11 } 12 return NULL; 13 }
Q3:如何查找最大和最小元素?
基本原则:最大元素必定在树的最右分枝的端节点上;最小元素必定在树的最左分枝的端节点上。
1 Position FindMin(BinTree BST) 2 { 3 if(!BST)return NULL; 4 else if(!BST->Left) 5 return BST; 6 else 7 return FindMin(BST->Left); 8 } 9 Position FindMax(BinTree BST) 10 { 11 if(BST) 12 while(BST->Right) BST=BST->Right; 13 return BST; 14 }
Q4:如何进行二叉搜索树的插入?
1 BinTree Insert(ElementType X,BinTree BST) 2 { 3 if(!BST)//若原树为空,生成并返回一个结点的二叉搜索树 4 { 5 BST=malloc(sizeof(struct TreeNode)); 6 BST->Data=X; 7 BST->Left=BST->Right=NULL; 8 } 9 else 10 if(X<BST->Data) 11 BST->Left=Insert(X,BST->Left); 12 else if(X>BST->Data) 13 BST->Right=Insert(X,BST->Right); 14 return BST; 15 16 }
Q5:如何进行二叉搜索树的删除?
思路:分三种状况考虑:
①要删除的是叶结点:直接删除,而后将其父结点所指的指针置为NULL
②要删除的是只有一个孩子的结点:将其父结点的指针指向要删除结点的孩子结点。
③要删除的是有左右两子树的结点:用另外一结点(取右子树中的最小元素或者左子树中的最大元素)替代被删除的结点。
1 BinTree Delete(ElementType X,BinTree BST)//本算法由两部分组成,一是递归,二是上述讨论的删除操做 2 { 3 Position Tmp; 4 if(!BST) printf("要删除的元素未找到"); 5 else if(X<BST->Data) 6 BST->Left=Delete(X,BST->Left);//左子树递归删除 7 else if(X>BST->Data) 8 BST->Right=Delete(X,BST->Right);//右子树递归删除 9 else//找到要删除的结点 10 if(BST->Left&&BST->Right)//被删除结点有左右两个子结点 11 { 12 Tmp=FindMin(BST->Right);//在右子树中找最小的元素填充删除结点 13 BST->Data=Tmp->Data; 14 BST->Right=Delete(BST->Data,BST->Right);//在删除结点的右子树中删除最小元素 15 } 16 else 17 {//被删除结点有一个或无子结点 18 Tmp=BST; 19 if(!BST->Left)//有右孩子或无子结点 20 { 21 BST=BST->Right; 22 } 23 else if(!BST->Right)//有左孩子或无子结点 24 BST=BST->Left; 25 free(Tmp); 26 } 27 return BST; 28 }
8.掌握平衡二叉树及其相关操做。
Q1:请给出平衡因子以及平衡二叉树的定义?
平衡因子(Balance Factor,简称BF)BF(T)=hL-hR,hL和hR分别为T左右两棵子树的高度。
平衡二叉树:空树或者任一结点左右子树高度差的绝对值不超过1,即|BF(T)|≤1
Q2:有关平衡二叉树结点及高度的结论?
9.掌握堆。
Q1:为何要定义堆这一数据结构?
优先队列是一种特殊的队列,其取出元素的顺序是按照元素优先权(关键字)的大小,而不是元素进入队列的前后顺序。比较多种数据结构后,决定采用二叉树结构做为优先队列的存储方式,而且考虑树的结构和树的结点存储顺序:将最大关键字存储在树的根结点,为了保证在插入和删除元素时树能保持平衡,采用彻底二叉树的结构。
Q2:堆的两个特性?
结构性:用数组表示的彻底二叉树
有序性:任意结点的关键字是其子树全部结点的最大值(最小值)。最大堆又称大顶堆,最大值;最小堆又称小顶堆,最小值。(注:从根结点到任意结点路径上结点序列都存在有序性)
Q3:堆的数据结构及相关操做?
1 typedef struct HeapStruct *MaxHeap; 2 struct HeapStruct 3 { 4 ElementType *Elements;//存储堆元素的数组 5 int Size;//堆的当前元素个数 6 int Capacity;//堆的最大容量 7 }; 8 MaxHeap Create(int MaxSize) 9 {//建立容量为MaxSize的空的最大堆 10 MaxHeap H=malloc(sizeof(struct HeapStruct)); 11 H->Elements=malloc((MaxSize+1)*sizeof(ElementType)); 12 H->Size=0; 13 H->Capacity=MaxSize; 14 H->Elements[0]=MaxData;//定义“哨兵” 为大于堆中全部可能元素的值,便于之后更快操做 15 return H; 16 }
void Insert(MaxHeap H,ElementType item) { //将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵 int i; if(IsFull(H)) { printf("最大堆已满"); return; } i=++H->Size;//i指向插入后堆中的最后一个元素的位置 for(;H->Element[i/2]<item;i/=2) H->Elements[i]=H->Elements[i/2];//向根部过滤结点 H->Elements[i]=item;//将item插入 //H->Element[0]是哨兵元素,它不小于堆中的最大元素,控制循环结束 }
1 ElementType DeleteMax (MaxHeap H) 2 //从最大堆中取出键值为最大的元素并删除一个结点 3 { 4 int Parent,Child; 5 ElementType MaxItem,temp; 6 if(IsEmpty(H)) 7 { 8 printf("最大堆已为空"); 9 } 10 MaxItem=H->Elements[1];//取出根结点最大值 11 //用最大堆中最后一个元素从根结点开始向上过滤下层结点 12 temp=H->Elements[H->Size--];//将最大堆中最后一个元素存放在temp中,由于删除了根结点,因此H->Size自减1 13 for(Parent=1;Parent*2<=H->Size;Parent=Child) 14 {//从根结点的位置开始循环 15 //Parent*2<=H->Size是为了判断是否存在左孩子(根节点编号为1时,结点i的左右孩子分别为:2i和2i+1,若是不存在左孩子天然也不存在右孩子,循环就能够结束了) 16 //Parent=Child向下继续循环直到找到合适的位置 17 Child=Parent*2;//指针指向左孩子 18 if((Child))!=H->Size)&&(H->Elements[Child]<H->Elements[Child+1])//Child!=H->Size是左右孩子都存在的状况 19 Child++;//本段语句用来将指针指向左右孩子中最大的那个 20 if(temp>=H->Elements[Child])break;//若是temp的值大于等于左右孩子中最大的那个值,说明这里就是temp最合适的位置,循环结束 21 else 22 H->Elements[Parent]=H->Elements[Child];//继续向下寻找合适的位置 23 } 24 H->Elements[Parent]=temp;//将temp的值放在合适的位置上 25 return MaxItem;//返回最大值 26 }
Q4:如何创建最大堆:将已经存在的N个元素按照最大堆的要求存放在一个一维数组中?
Step 1:将N个元素按输入顺序存入,先知足彻底二叉树的结构特性。
Step 2:调整各结点的位置,以知足最大堆的有序特性。
Q5:课后错题:
建堆时,最坏状况下须要挪动元素次数是等于树中各结点的高度和。问:对于元素个数为12的堆,其各结点的高度之和是多少?
该题即求最坏状况下须要下沉的结点的挪动次数。最坏状况就是最小堆调整为最大堆:倒数第二层向下移动一次,倒数第三层向下移动两次,倒数第四层向下移动三次。即3*1+2*2+1*3=10
10.掌握哈夫曼树与哈夫曼编码。
Q1:请给出哈夫曼树的定义?
带权路径长度(WPL):设二叉树有n个叶子结点,每一个叶子结点带有权值wk,从根结点到每一个叶子结点的长度为Ik,则每一个叶子结点的带权路径长度之和就是:WPL=ΣwkIk
而最优二叉树(即哈夫曼树)就是WPL最小的树
Q2:哈夫曼树如何构造?
每次将权值最小的两棵树合并。而如何选取最小,能够用堆解决问题。
Q3:请给出哈夫曼树的特色?
①没有度为1的结点
②n个叶子结点的哈夫曼树共有2n-1个结点(推导过程:n0:叶结点数;n1:只有一个儿子的结点总数;n2:有两个儿子的结点总数;n2=n0-1)。
③哈夫曼树的任意非叶结点的左右子树交换后仍然为哈夫曼树。
Q4:课后错题:
1.为五个使用频率不一样的字符设计哈夫曼编码,下列方案中哪一个不多是哈夫曼编码?A.00,100,101,110,111 B.000,001,01,10,11 C.0000,0001,001,01,1 D.000,001,010,011,1
A画出来如图所示,不符合哈夫曼树的性质:没有度为1的结点。
2.一段文本中包含对象{a,b,c,d,e},其出现次数相应为{3,2,4,2,1},则通过哈夫曼编码后,该文本所占总位数为:
文本所占总位数=3X1+3X2+2X2+2X3+2X4=27(最后一步算错了,忘记乘以频率)
11.掌握集合及运算。(期中测试中该知识点出错)
Q1:什么是并查集?
并查集是数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操做:合并(Union):把两个不相交的集合合并为一个集合;查询(Find):查询两个元素是否在同一个集合中。并查集的重要思想在于,用集合中的一个元素表明集合。
Q2:并查集中集合存储如何实现?
Q3:请写出集合运算的算法?
第六讲 图
1.重点介绍图的邻接矩阵存储。
Q1:对无向图的邻接矩阵如何存储才能节省空间?
Q2:邻接矩阵表示的优势是?
① 简单直观 ②便于找出任一顶点全部的“邻接点”
③便于检查任意顶点之间是否存在边
④便于计算任一顶点的度(出度和入度都很明确)【注意:有向图的出度是对应行的非零元素的个数,入度是对应列的非零元素的个数】
Q3:邻接矩阵表示的缺点是?
①浪费空间,存稀疏图时有大量无效元素
②浪费时间,如想要统计图中共有多少条边
2.重点介绍图的邻接表存储。
Q1:用简练的语言描述一下邻接表存储如何实现?
G[N]为指针数组,对应矩阵每行一个链表,用来存储非零元素。
Q2:邻接表表示的优势是?
①便于找到任意顶点的全部“邻接点”。
②节约稀疏图的空间。须要N个头指针+2E个结点(每一个结点至少两个域)。
Q3:邻接表表示的缺点是?
①对于无向图来讲易计算度,而对于有向图来讲只能计算出度,还须要逆邻接表计算入度。
②不便于检查任意两个顶点间是否存在边。
3.重点掌握两种表示方法的C语言实现。
1 /* 图的邻接矩阵表示法(C语言实现) */
2 #define MaxVertexNum 100 /* 最大顶点数设为100 */
3 #define INFINITY 65535 /* ∞设为双字节无符号整数的最大值65535*/
4 typedef char VertexType; /* 顶点类型设为字符型 */
5 typedef int EdgeType; /* 边的权值设为整型 */
6 enum GraphType { DG, UG, DN, UN }; 7 /* 有向图,无向图,有向网图,无向网图*/
8
9 typedef struct { 10 VertexType Vertices[ MaxVertexNum ]; /* 顶点表 */
11 EdgeType Edges[ MaxVertexNum ][ MaxVertexNum ]; 12 /* 邻接矩阵,即边表 */
13 int n, e; /* 顶点数n和边数e */
14 enum GraphType GType; /* 图的类型分4种:UG、DG、UN、DN */
15 } MGraph; /* MGragh是以邻接矩阵存储的图类型 */
16
17 void CreateMGraph ( MGraph *G ) 18 { 19 int i, j, k, w; 20 G-> GType = UN; /* Undirected Network 无向网图 */
21 printf( "请输入顶点数和边数(输入格式为:顶点数, 边数):\n" ); 22 scanf( "%d, %d",&(G->n), &(G->e) ); /* 输入顶点数和边数 */
23 printf("请输入顶点信息(输入格式为:顶点号<CR>):\n"); 24 for ( i = 0; i < G->n; i++ ) 25 scanf( "%c",&(G-> Vertices[i]) ); /* 输入顶点信息,创建顶点表 */
26 for ( i = 0; i < G->n; i++ ) 27 for ( j = 0; j < G->n; j++ ) 28 G->Edges[i][j] = INFINITY; /* 初始化邻接矩阵 */
29 printf( "请输入每条边对应的两个顶点的序号和权值,输入格式为:i, j, w:\n" ); 30 for ( k = 0; k < G->e; k++ ) { 31 scanf("%d,%d,%d ",&i, &j, &w); /* 输入e条边上的权,创建邻接矩阵 */
32 G->Edges[i][j] = w; 33 G->Edges[j][i] = w; /* 由于无向网图的邻接矩阵是对称的 */
34 } 35 }
1 /* 图的邻接表表示法(C语言实现) */ 2 #define MaxVertexNum 100 /* 最大顶点数为100 */ 3 enum GraphType { DG, UG, DN, UN }; 4 /* 有向图,无向图,有向网图,无向网图*/ 5 typedef struct node{ /* 边表结点 */ 6 int AdjV; /* 邻接点域 */ 7 struct node *Next; /* 指向下一个邻接点的指针域 */ 8 /* 若要表示边上的权值信息,则应增长一个数据域Weight */ 9 } EdgeNode; 10 typedef char VertexType; /* 顶点用字符表示 */ 11 typedef struct Vnode{ /* 顶点表结点 */ 12 VertexType Vertex; /* 顶点域 */ 13 EdgeNode *FirstEdge; /* 边表头指针 */ 14 } VertexNode; 15 typedef VertexNode AdjList[ MaxVertexNum ]; /* AdjList是邻接表类型 */ 16 typedef struct{ 17 AdjList adjlist; /* 邻接表 */ 18 int n, e; /* 顶点数和边数 */ 19 enum GraphType GType; /* 图的类型分4种:UG、DG、UN、DN */ 20 } ALGraph; /*ALGraph是以邻接表方式存储的图类型 */ 21 22 void CreateALGraph( ALGraph *G ) 23 { 24 int i, j, k; 25 EdgeNode *edge; 26 G-> GType = DG; /* Directed Graph 有向图 */ 27 printf( "请输入顶点数和边数(输入格式为:顶点数,边数):\n" ); 28 scanf( "%d,%d", &(G->n), &(G->e) ); /* 读入顶点数和边数 */ 29 printf( "请输入顶点信息(输入格式为:顶点号<CR>):\n" ); 30 for ( i=0; i < G->n; i++ ) { /* 创建有n个顶点的顶点表 */ 31 scanf( " %c", &(G->adjlist[i].Vertex) ); /* 读入顶点信息 */ 32 G->adjlist[i].FirstEdge = NULL; /* 顶点的边表头指针设为空 */ 33 } 34 printf( "请输入边的信息(输入格式为: i, j <CR>):\n" ); 35 for ( k=0; k < G->e; k++ ){ /* 创建边表 */ 36 scanf( "\n%d,%d", &i, &j); /* 读入边<vi,vj>的顶点对应序号*/ 37 edge = (EdgeNode*)malloc(sizeof(EdgeNode)); /* 生成新边结点edge */ 38 edge->AdjV = j; /* 邻接点序号为j */ 39 edge->Next = G->adjlist[i].FirstEdge; 40 /* 将新边表结点edge插入到顶点vi的边表头部 */ 41 G->adjlist[i].FirstEdge = edge; 42 /* 如果无向图,还要生成一个结点,用来表示边< vj, vi> */ 43 } 44 }图的
4.重点掌握DFS.BFS
Q1:DFS的时间复杂度?
①采用邻接表存储:O(N+E)
②采用邻接矩阵存储:O(N2)
Q2:BFS的算法(c++函数实现)
1 void BFS(Graph G,int v) 2 { 3 cout<<v; 4 visited[v]=true;//访问第v个顶点 5 InitQueue(Q);//辅助队列Q初始化,置空 6 EnQueue(Q,v);//v进队 7 while(!QueueEmpty(Q))//队列非空 8 { 9 DeQueue(Q,u);//队头元素出队并置为u 10 for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)) 11 if(!visited[w])//w为u的还没有访问的邻接顶点 12 { 13 cout<<w; 14 visited[w]=true; 15 EnQueue(Q,w);//w进队 16 } 17 } 18 }
Q3:DFS算法和BFS算法(C语言实现)
1 /* 邻接表存储的图 – DFS(C语言实现) */ 2 /* Visited[]为全局变量,已经初始化为FALSE */ 3 void DFS( ALGraph *G, int i ) 4 { /* 以Vi为出发点对邻接表存储的图G进行DFS搜索 */ 5 EdgeNode *W; 6 printf( "visit vertex: %c\n", G->adjlist[i].Vertex ); 7 /* 至关于访问顶点Vi */ 8 Visited[i] = TRUE; /* 标记Vi已访问 */ 9 for( W = G->adjlist[i].FirstEdge; W; W = W->Next ) 10 if ( !Visited[ W->AdjV ] ) 11 DFS( G, W->AdjV ); 12 } 13 14 /* 邻接矩阵存储的图 – BFS(C语言实现) */ 15 void BFS ( MGraph G ) 16 { /* 按广度优先遍历图G。使用辅助队列Q和访问标志数组Visited */ 17 Queue *Q; 18 VertexType U, V, W; 19 for ( U = 0; U < G.n; ++U ) 20 Visited[U] = FALSE; 21 Q = CreatQueue( MaxSize ); /* 建立空队列Q */ 22 for ( U = 0; U<G.n; ++U ) 23 if ( !Visited[U] ) { /* 若U还没有访问 */ 24 Visited[U] = TRUE; 25 printf( "visit vertex: %c\n", G.Vertices[U] ); 26 /* 至关于访问顶点U */ 27 AddQ (Q, U); /* U入队列 */ 28 while ( ! IsEmptyQ(Q) ) { 29 V = DeleteQ( Q ); /* 队头元素出队并置为V */ 30 for( W = FirstAdjV(G, V); W; W = NextAdjV(G, V, W) ) 31 if ( !Visited[W] ) { 32 Visited[W] = TRUE; 33 printf( "visit vertex: %c\n", G.Vertices[W] ); 34 /* 至关于访问顶点W */ 35 AddQ (Q, W); 36 } 37 } /* while结束*/ 38 } /* 结束从U开始的BFS */ DFS
5.掌握图相关名词的概念
Q1:什么是图的连通份量?
无向图的极大连通子图
Q2:什么是无向彻底图?什么是有向彻底图?
在无向图中,若是任意两个顶点之间都存在边,则称该图为无向彻底图。
在有向图中,若是任意两个顶点之间都存在方向互为相反的两条弧,则称为有向彻底图。
6.掌握图的应用示例 :拯救007
在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑壳跳上岸去!
设鳄鱼池是长宽为100米的方形,中心坐标为 (0, 0),且东北角坐标为 (50, 50)。池心岛是以 (0, 0) 为圆心、直径15米的圆。给定池中分布的鳄鱼的坐标、以及007一次能跳跃的最大距离,你须要告诉他是否有可能逃出生天。
输入格式:
首先第一行给出两个正整数:鳄鱼数量 N(≤)和007一次能跳跃的最大距离 D。随后 N 行,每行给出一条鳄鱼的 ( 坐标。注意:不会有两条鳄鱼待在同一个点上)。
输出格式:
若是007有可能逃脱,就在一行中输出"Yes",不然输出"No"。
题目分析:
Step 1:从题目中抽象出图的模型。注意一个误区:可能认为图的顶点只是鳄鱼头,而忽略了岸边也是“图的顶点”。
Step 2:图的边如何创建?
①以孤岛为圆心,以007跳跃的最大距离为半径,观察有哪些顶点落入圆中
②选定一个顶点进行DFS,当发现该顶点没法靠岸,则返回,选取下一个顶点,进行递归调用刚才的函数
③直到找到能够靠岸的顶点为止
算法设计:
1 void Save007(Graph G) 2 { 3 for(each V in G) 4 { 5 if(!visited[V]&&FirstJump(V))//FirstJump函数是用来判断是否在跳跃范围内 6 { 7 answer=DFS(V); 8 if(answer==YES) break; 9 } 10 11 } 12 if(answer==YES)output("YES"); 13 else output("NO"); 14 } 15 int DFS(Vertex V)//在DFS算法的基础上进行改良 16 { 17 visited[V]=true; 18 if(IsSafe(V))answer=YES;//IsSafe函数用来判断该顶点是否能够一步到岸 19 else 20 { 21 for(each W in G) 22 { 23 if(!visited[W]&&Jump(V,W)) 24 { 25 answer=DFS(W); 26 if(answer==YES) break; 27 } 28 } 29 } 30 return answer; 31 }
7.掌握图的应用示例:六度空间
算法思路:对每一个结点进行广度优先搜索,搜素过程当中累计访问的结点数,须要记录层数:仅计算六层之内的结点数。
8.掌握最小生成树。
Q1:如何理解最小生成树?
是一棵树代表其没有回路,若是存在|V|个顶点则必定有|V-1|条边;是生成树代表其包含所有顶点且|V-1|条边均在树里;最小则是指边的权重最小。
Q2:如何理解Prim算法和Kruskal算法?
Prim算法“让一棵小树长大”是指选准一个顶点添加边;Krusal算法“将森林合并成树”是指直接按权值选取边。
9.掌握拓扑排序。
Q1:如何理解拓扑排序及相关算法?
Q2:如何理解及计算关键路径?(易出错)
Q3:课后错题
错因:误解题意。
从0到3的时间是12,是由0-2-3决定的,而不是0-1-3。而4也是由0-2影响的,因此加快之后,能提早完工。
期中测试错题汇总
1.有向图邻接矩阵中第i行非零元素的个数为第i个顶点的出度,第i列非零元素的个数为第i个顶点的入度。
2.设h为不带头结点的单向链表。在h的头上插入一个新结点t的语句是:t->next=h; h=t;
3.采用多项式的非零项链式存储表示法,若是两个多项式的非零项分别为N1和N2个,最高项指数分别为M1和M2,则实现两个多项式相加的时间复杂度是:O(N1+N2)
M1和M2为干扰选项,与本题无关。实现两个多项式相加,必须把每一个多项式都遍历一遍。
4.一棵共有 18 个结点的三叉树中,度为 2 的结点 3 个,度为 3 的结点 2 个,问该树度为 1 的结点有几个?
树中结点数 = 全部结点度数之和+1 故为5个
5.若一搜索树(查找树)也是棵有 n 个结点的彻底二叉树,则不正确的说法是:最大值必定在叶结点上,反例以下:
正确:中位值结点在根结点或根的左子树上。
第九至十讲 排序
1.掌握简单排序:冒泡排序和插入排序。
Q1:如何理解简单排序?
Q2:如何理解冒泡排序?
Q3:如何理解插入排序?
Q4:插入排序中时间复杂度的下界由什么决定?
2.掌握希尔排序。
Q1:请问希尔排序的原理是?
Q2:希尔排序的基本算法是?
Q3:希尔排序中要注意什么状况?
增量元素不互质的状况,会致使部分增量达不到排序效果。
3.掌握堆排序。
Q1:请给出堆排序的两种算法并分析优劣?
Q2:堆排序是不稳定的,请举个例子解释?
1 2(1) 3 2(2)
1
/ \
2(1) 3
/
2(2)
2(2)
/ \
2(1) 3
/
1
3
/ \
2(1) 2(2)
/
1
4.掌握归并排序。
Q1:归并排序的合并的核心是?
有序子列的合并。
Q2:如何用算法实现有序子列的合并?
Q3:如何用递归算法实现归并排序?
Q4:如何用非递归算法实现归并排序?
5.掌握快速排序。
Q1:如何描述快速排序?
Q2:子集划分时若是出现元素等于主元,如何解决?
第二种处理方法的时间复杂度如右图。
Q3:请给出快速排序的算法实现并解释部分设计的思路?
为何要设计Cutoff及Insertion_Sort()函数?
为何要设计void Quick_Sort()?
统一函数接口。
6.掌握表排序。
Q1:如何进行间接排序(不移动数据的位置,只改变数据的输出顺序)?
排序时采用插入排序。
Q2:1.如何进行物理排序(移动数据)?
N个数字的排列由若干个独立的环组成,只需在环的内部进行数据顺序的调整。如图:不一样颜色表示不一样的环。
2. 如何判断一个环的结束?
每访问一个空位i后,就令table[i]=i。当发现table[i]==i时,环结束。
3.该算法的时间复杂度分析?
7.掌握桶排序和基数排序。
Q1:请用简要描述桶排序?
把数据放入到多个桶里面,在对桶里面的数据进行排序,而后遍历各桶获得元素序列。
Q2:请简要描述基数排序?
将整数按位数切割成不一样的数字,而后按每一个位数分别比较。
Q3:请给出基数排序的例子?
Q4:请给出两种基数排序MSD和LSD的例子?
就本例来讲,LSD的方法只需放入桶后按照桶的顺序取出,不用再对桶中的数据进行排序,效率更高。
8.掌握排序算法的比较。
课后错题:
1.下列排序算法中,哪一种算法可能出现:在最后一趟开始以前,全部的元素都不在其最终的位置上?
A.堆排序 B.插入排序 C.冒泡排序 D.快速排序
解析:注意是全部的元素!插入排序可能使所有的元素发生位移,而堆排序使左右两个子树,某个子树上的元素发生位移。
2.数据序列(3,2,4,9,8,11,6,20)只能是下列哪一种排序算法的两趟排序结果?
解析:对于后三种排序方法,两趟排序后,序列的首部或尾部的两个元素应是有序的两个极值,而给定的序列不知足。
Q2:请简述散列函数的构造?
①数字关键词散列函数的构造:直接定址法(取关键词的某个线性函数值为散列地址)、除留余数法、数字分析法、折叠法、平方取中法。
②字符关键词散列函数的构造:
注:C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)。
散列表是一个包含关键字的具备固定大小的数组,表的大小记为 TableSize.
<<在C语言中表明左移运算符。
Q3:处理冲突的方法有哪些?
①换个位置:开放定址法 ②同一个位置的冲突对象组织在一块儿:链地址法
Q4:开放地址法分为哪些?
①线性探测 ②平方探测 ③双散列
ASLu的计算方法(以该散列表为例):
地址0,到第一个关键字为空的地址2须要比较3次,所以查找不成功的次数为3.
地址1,到第一个关键字为空的地址2须要比较2次,所以查找不成功的次数为2.
地址3,到第一个关键字为空的地址2须要比较1次,所以查找不成功的次数为1.
地址7,到第一个关键字为空的地址2须要比较9次,所以查找不成功的次数为9.(从地址6开始,再循环回去).
Q5:请写出平方探测法的算法?
Q6:什么是双散列探测法?
Q7:什么是再散列探测法?
Q8:请描述分裂连接法及其算法?
10.掌握散列表的性能分析。
Q1:影响散列表性能的因素有哪些?
Q2:请分析①线性探测 ②平方探测 ③双散列④分离连接的性能?
Q2:请给出散列方法、开放地址法和分离连接法的优劣势分析?
Q3:请给出散列表查找的应用实例?