acm集训队课程设置--第三节课

acm集训队课程设置--第三节课

       本节大概内容:栈和队列,二叉树的遍历,图的遍历(深搜,广搜和最短路径问题),拓扑排序,哈夫曼树,理解这些数据结构的性质。

1.栈

原c++栈的方法的基本用法: 
push(): 向栈内压入一个成员;
pop(): 从栈顶弹出一个成员;
empty(): 如果栈为空返回true,否则返回false;
top(): 返回栈顶,但不删除成员;

size(): 返回栈内元素的大小;

#include
#include
using namespace std;

int main()
{
    stack  stk;
    //入栈
    for(int i=0;i<50;i++){
        stk.push(i);
    }
    cout<<"栈的大小:"<

2.队列

C++队列Queue类成员函数如下:

back()返回最后一个元素
       empty()如果队列空则返回真
       front()返回第一个元素
       pop()删除第一个元素
       push()在末尾加入一个元素
       size()返回队列中元素的个数

#include
#include

using namespace std;

int main()
{
    int e,n,m;  
    queue q1;  
    for(int i=0;i<10;i++)  
       q1.push(i);  
    if(!q1.empty()) {
    	cout<

3.二叉树的遍历

基本概念

树形结构是一类重要的非线性数据结构,其中以树和二叉树最为常用。

二叉树是每个结点最多有两个子树的有序树。通常子树的根被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用作二叉查找树和二叉堆或是二叉排序树。
二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。
二叉树的第i层至多有2的 i -1次方个结点;
深度为k的二叉树至多有2^(k) -1个结点;
对任何一棵二叉树T,如果其终端结点数(即叶子结点数)为n0,度为2的结点数为n2,则n0 = n2 + 1。

二叉树的三种递归的遍历方法:

先序遍历 访问根节点→先序遍历左子树→先序遍历右子树
中序遍历 中序遍历左子树→访问根节点→中序遍历右子树
后序遍历 后序遍历左子树→后序遍历右子树→访问根节点

acm集训队课程设置--第三节课_第1张图片

 先序遍历:

#include   
#include   
  
typedef char ElemType;  
typedef struct BiTNode {  
    char data;  
    struct BiTNode *lchild, *rchild;  
}BiTNode, *BiTree;  
  
//创建二叉树 遵循谦虚遍历法输入二叉树的结点的数据  
void CreatBiTree( BiTree *T )  
{  
    ElemType c;  
  
    scanf("%c", &c);  
    if( '^' == c ) {  
        *T = NULL;  
    }  
  
    else {  
        *T = (BiTree)malloc(sizeof(BiTNode));  
        (*T)->data = c;  
        CreatBiTree(&((*T)->lchild));  
        CreatBiTree(&((*T)->rchild));  
    }  
}  
  
//访问二叉树结点的具体操作  
void visit( BiTree T, int level )  
{  
    printf("%c 在第 %d 层\n", T->data, level);  
}  
  
  
//遍历二叉树  
void PreOrderTraverse( BiTree T, int level )  
{  
    if( T ) {  
        visit( T , level );  
        PreOrderTraverse( T->lchild, level+1 );  
        PreOrderTraverse( T->rchild, level+1 );  
    }  
}  
  
int main()  
{  
    BiTree T;  
    CreatBiTree(&T);  
    PreOrderTraverse( T, 1 );  
    return 0;  
}  

中序遍历:

#include   
#include   
  
typedef char Elemtype;  
  
//创建二叉树结点结构体  
typedef struct BiTNode {  
    Elemtype data;  
    struct BiTNode *lchild, *rchild;  
}BiTNode, *BiTree;  
  
//前序遍历一次输入二叉树的结点数据  创建二叉树  
void CreateBiTree( BiTree *t )  
{  
    Elemtype c;  
    scanf("%c", &c);  
  
    if( ' ' == c )   
        *t = NULL;  
  
    else {  
        (*t) = (BiTree)malloc(sizeof(BiTNode));  
        (*t)->data = c;  
        CreateBiTree(&(*t)->lchild);  
        CreateBiTree(&(*t)->rchild);  
    }  
}  
  
//访问二叉树结点数据函数  
void visit( BiTree t )  
{  
    printf("%c ", t->data );  
}  
  
//中序遍历二叉树函数  
void InOrderTraverse( BiTree t )  
{  
    if( t ) {  
        InOrderTraverse( t->lchild );  
        visit( t );  
        InOrderTraverse( t->rchild );  
    }  
}  
  
int main()  
{  
    BiTree t;  
    printf("请按照前序遍历顺序输入数据建立二叉树:\n");  
  
    CreateBiTree( &t );  
  
    InOrderTraverse( t );  
  
    printf("\n");  
    return 0;  
}  

后序遍历:

#include   
#include   
  
typedef char Elemtype;  
  
//创建二叉树结点结构  
typedef struct BiTNode {  
    Elemtype data;  
    struct BiTNode *lchild, *rchild;  
}BiTNode, *BiTree;  
  
//前序遍历输入创建二叉树  
void CreateBiTree( BiTree *t )  
{  
    Elemtype c;  
    scanf("%c", &c);  
  
    if( ' ' == c )  
        (*t) = NULL;  
    else {  
        (*t) = (BiTree)malloc(sizeof(BiTNode));  
        (*t)->data = c;  
        CreateBiTree( &(*t)->lchild );  
        CreateBiTree( &(*t)->rchild );  
    }  
}  
  
//访问函数  
void visit( BiTree t )  
{  
    printf("%c ", t->data);  
}  
  
//后序遍历二叉树  
void PostOrderTraverse( BiTree t )  
{  
    if( NULL == t )  
        return ;  
  
    else {  
        PostOrderTraverse( t->lchild );  
        PostOrderTraverse( t->rchild );  
        visit( t );  
    }  
}  
  
int main()  
{  
    BiTree t;  
    printf("请前序输入二叉树的结点序列:\n");  
  
    CreateBiTree( &t );  
  
    PostOrderTraverse( t );  
  
    printf("\n");  
    return 0;  
}  

4.图的存储结构

图的存储结构除了要存储图中各个顶点的本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多。常用的图的存储结构有邻接矩阵、邻接表、十字链表和邻接多重表。

4.1.1  邻接矩阵表示法

对于一个具有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系。图8.10和图8.11中,矩阵A(i,j)=1表示图中存在一条边(Vi,Vj),而A(i,j)=0表示图中不存在边(Vi,Vj)。实际编程时,当图为不带权图时,可以在二维数组中存放bool值,A(i,j)=true表示存在边(Vi,Vj),A(i,j)=false表示不存在边(Vi,Vj);当图带权值时,则可以直接在二维数组中存放权值,A(i,j)=null表示不存在边(Vi,Vj)。

 


 

图8.10所示的是无向图的邻接矩阵表示法,可以观察到,矩阵延对角线对称,即A(i,j)= A(j,i)。无向图邻接矩阵的第i行或第i列非零元素的个数其实就是第i个顶点的度。这表示无向图邻接矩阵存在一定的数据冗余。

图8.11所示的是有向图邻接矩阵表示法,矩阵并不延对角线对称,A(i,j)=1表示顶点Vi邻接到顶点Vj;A(j,i)=1则表示顶点Vi邻接自顶点Vj。两者并不象无向图邻接矩阵那样表示相同的意思。有向图邻接矩阵的第i行非零元素的个数其实就是第i个顶点的出度,而第i列非零元素的个数是第i个顶点的入度,即第i个顶点的度是第i行和第i列非零元素个数之和。

由于存在n个顶点的图需要n2个数组元素进行存储,当图为稀疏图时,使用邻接矩阵存储方法将出现大量零元素,照成极大地空间浪费,这时应该使用邻接表表示法存储图中的数据。

4.1.2 邻接表表示法

图的邻接矩阵存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。



 

有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图8.13所示,图(b)和(c)分别为有向图(a)的出边表和入边表。



 

以上所讨论的邻接表所表示的都是不带权的图,如果要表示带权图,可以在表结点中增加一个存放权的字段,其效果如图8.14所示。



4.2 图的遍历

和树的遍历类似,在此,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(TraversingGraph)。如果只访问图的顶点而不关注边的信息,那么图的遍历十分简单,使用一个foreach语句遍历存放顶点信息的数组即可。但如果为了实现特定算法,就需要根据边的信息按照一定顺序进行遍历。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。

图的遍历要比树的遍历复杂得多,由于图的任一顶点都可能和其余顶点相邻接,故在访问了某顶点之后,可能顺着某条边又访问到了已访问过的顶点,因此,在图的遍历过程中,必须记下每个访问过的顶点,以免同一个顶点被访问多次。为此给顶点附设访问标志visited,其初值为false,一旦某个顶点被访问,则其visited标志置为true。

图的遍历方法有两种:一种是深度优先搜索遍历(Depth-First Search 简称DFS);另一种是广度优先搜索遍历(Breadth_First Search 简称BFS)。

4.2.1  深度优先搜索遍历

图的深度优先搜索遍历类似于二叉树的深度优先搜索遍历。其基本思想如下:假定以图中某个顶点Vi为出发点,首先访问出发点,然后选择一个Vi的未访问过的邻接点Vj,以Vj为新的出发点继续进行深度优先搜索,直至图中所有顶点都被访问过。显然,这是一个递归的搜索过程。

现以图8.15为例说明深度优先搜索过程。假定V1是出发点,首先访问V1。因V1有两个邻接点V2、V3均末被访问过,可以选择V2作为新的出发点,访问V2之后,再找V2的末访问过的邻接点。同V2邻接的有V1、V4和V5,其中V1已被访问过,而V4、V5尚未被访问过,可以选择V4作为新的出发点。重复上述搜索过程,继续依次访问V8、V5 。访问V5之后,由于与V5相邻的顶点均已被访问过,搜索退回到V8,访问V8的另一个邻接点V6。接下来依次访问V3和V7,最后得到的的顶点的访问序列为:V1 → V2 → V4 → V8 → V5 → V6 → V3 → V7



4.2.2  广度优先搜索遍历

图的广度优先搜索遍历算法是一个分层遍历的过程,和二叉树的广度优先搜索遍历类同。它从图的某一顶点Vi出发,访问此顶点后,依次访问Vi的各个未曾访问过的邻接点,然后分别从这些邻接点出发,直至图中所有已有已被访问的顶点的邻接点都被访问到。对于图8.15所示的无向连通图,若顶点Vi为初始访问的顶点,则广度优先搜索遍历顶点访问顺序是:V1 → V2 → V3 → V4 → V5 → V6 → V7 → V8。遍历过程如图8.16的所示。


 

和二叉树的广度优先搜索遍历类似,图的广度优先搜索遍历也需要借助队列来完成

5.拓扑排序

1.概述

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。

2、拓扑排序的实现步骤

  1. 在有向图中选一个没有前驱的顶点并且输出
  2. 从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
  3. 重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

3、拓扑排序示例手动实现

如果我们有如下的一个有向无环图,我们需要对这个图的顶点进行拓扑排序,过程如下: 
这里写图片描述

首先,我们发现V6和v1是没有前驱的,所以我们就随机选去一个输出,我们先输出V6,删除和V6有关的边,得到如下图结果: 
这里写图片描述

然后,我们继续寻找没有前驱的顶点,发现V1没有前驱,所以输出V1,删除和V1有关的边,得到下图的结果: 
这里写图片描述

然后,我们又发现V4和V3都是没有前驱的,那么我们就随机选取一个顶点输出(具体看你实现的算法和图存储结构),我们输出V4,得到如下图结果: 
这里写图片描述

然后,我们输出没有前驱的顶点V3,得到如下结果: 
这里写图片描述

然后,我们分别输出V5和V2,最后全部顶点输出完成,该图的一个拓扑序列为:

v6–>v1—->v4—>v3—>v5—>v2

6.哈夫曼树

哈夫曼树又称最优二叉树,是带权路径长度最短的树,可用来构造最优编码,用于信息传输、数据压缩等方面,是一种应用广泛的二叉树。 
这里写图片描述

几个相关的基本概念:

1.路径:从树中一个结点到另一个结点之间的分支序列构成两个节点间的路径 
2.路径长度:路径上的分支的条数称为路径长度 
3.树的路径长度:从树根到每个结点的路径长度之和称为树的路径长度 
4.结点的权:给树中结点赋予一个数值,该数值称为结点的权 
5.带权路径长度:结点到树根间的路径长度与结点的权的乘积,称为该结点的带权路径长度 
6.树的带权路径长度:树中所有叶子结点的带权路径长度之和,通常记为WPL 
7.最优二叉树:在叶子个数n以及各叶子的权值确定的条件下,树的带权路径长度WPL值最下的二叉树称为最优二叉树。

哈夫曼树的建立

由哈夫曼最早给出的建立最优二叉树的带有一般规律的算法,俗称哈夫曼算法。描述如下: 
1):初始化:根据给定的n个权值(W1,W2,…,Wn),构造n棵二叉树的森林集合F={T1,T2,…,Tn},其中每棵二叉树Ti只有一个权值为Wi的根节点,左右子树均为空。

2):找最小树并构造新树:在森林集合F中选取两棵根的权值最小的树做为左右子树构造一棵新的二叉树,新的二叉树的根结点为新增加的结点,其权值为左右子树的权值之和。

3):删除与插入:在森林集合F中删除已选取的两棵根的权值最小的树,同时将新构造的二叉树加入到森林集合F中。

4):重复2)和3)步骤,直至森林集合F中只含一棵树为止,这颗树便是哈夫曼树,即最优二叉树。由于2)和3)步骤每重复一次,删除掉两棵树,增加一棵树,所以2)和3)步骤重复n-1次即可获得哈夫曼树。

下图展示了有4个叶子且权值分别为{9,6,3,1}的一棵最优二叉树的建立过程。


这里写图片描述

这里写图片描述

这里写图片描述




你可能感兴趣的:(Acm入坑)