浙大数据结构MOOC 陈越/何钦铭 笔记整理(梳理知识点 查漏补缺_)

第三至五讲 树
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 }
BinarySearch

   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 }
BFS

   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
DFS/BFS

 

 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.采用多项式的非零项链式存储表示法,若是两个多项式的非零项分别为N​1​​和N​2​​个,最高项指数分别为M​1​​和M​2​​,则实现两个多项式相加的时间复杂度是:O(N​1​​+N​2​​)

   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的例子?

   

      

  • MSD:先从高位开始进行排序。
  • LSD:先从低位开始进行排序。

       就本例来讲,LSD的方法只需放入桶后按照桶的顺序取出,不用再对桶中的数据进行排序,效率更高。

8.掌握排序算法的比较。

 

    课后错题:

    1.下列排序算法中,哪一种算法可能出现:在最后一趟开始以前,全部的元素都不在其最终的位置上?

      A.堆排序  B.插入排序  C.冒泡排序  D.快速排序

      解析:注意是全部的元素!插入排序可能使所有的元素发生位移,而堆排序使左右两个子树,某个子树上的元素发生位移。

    2.数据序列(3,2,4,9,8,11,6,20)只能是下列哪一种排序算法的两趟排序结果?

       A.快速排序   B.冒泡排序   C.选择排序    D.插入排序   

      解析:对于后三种排序方法,两趟排序后,序列的首部或尾部的两个元素应是有序的两个极值,而给定的序列不知足。

9.掌握散列查找。
   Q1:什么是装填因子?
   设散列表的空间大小为m,填入表中元素的个数为n,则称a=n/m为散列表的装填因子。

   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:请给出散列表查找的应用实例?

相关文章
相关标签/搜索