数据结构复习

数据结构

第5章 树

5.1 树的基本概念

树的度: 是节点的度的最大值

树深度: 节点层次的最大值

节点的度:子树的个数

  • 树的性质:

      1. n 个结点,每个节点

        \[度为\ d_i,则 \ n=\sum_{i=1}^{n}d_i +1 \]

      1. 度为 k 的树第 i 层的结点个数最多为

        \[k^{i-1} \]

      1. 深度为h的k叉树,最多结点为

        \[\frac{k^{h}-1}{k-1} \]

      2. 具有n个结点的k叉树深度最小为

        \[\lceil log_k(n(k-1))+1 \rceil \]

  • 树的基本操作

    • bool CreateTree(position &t);
    • bool DelTree(position &t);
    • void DelSubTree(position &t,position p);
    • TElelmType GetData(position p);
  • //基本操作的代码实现
    InitTree(&T)
    DestroyTree(&T)
    CreateTree(&T,definition)
    TreeEmpty(T)
    TreeDepth(T)
    Root(T)
    Parent(T, x)
    FirstChild(T,x)
    Nextsibling(T,x)
    InsertChild(&T,x,i,p)
    DeleteChild(&T,x,i)
    Traverse(T,visit()) 
    
    
  • 设计算法

    • 递归计算树的深度

      若树空则返回 0 或者递归到树的叶子节点,则返回1

      否则递归遍历其他子树,取其最大值加上1 则是树的高度

    • 算法描述:

//递归计算树的深度
int height(Tree T){
    if(IsEmpty(T)) return 0;
    else if(Isleaf(T)) return 1;
    else  {
        int h = 0,k;
        for (Tree p=firstchild(T);P != NULL;p = Nextsibling(T,p))
            if((k = height(P)) > h) h = k;
        return h+1;
    }
}

5.2 二叉树的基本概念

结点的度: 非空子树的个数

叶子结点 : 左右子树均空,度为0

完全二叉树: 深度为h,h-1为满二叉树,h层的结点都集中在左侧

//二叉树的操作
InitBiTree(&T)
DestroyBiTree(&T)
CreateBiTree(&T,definition)
BiTreeEmpty(T)
BiTreeDepth(T)
Parent(T,e)
LeftChild(T,e)
RightChild(T,e)
LeftSibling(T,e)
RightSibling(T,e)
InsertChild(&T,p,LR,C)
DeleteChild(&T,p,LR)
Traverse(T)
    
//二叉树的结构
typedef struct Node{
    char data;
    struct Node *lchild,*rchild;
}*BiTree,BiNode;
void InitBitree(BiTree &T){
	char ch;
    cin >> ch;
    if(ch == '#'){
        T = NULL;
    }
    else{
        T = new BiNode;
        T -> data = ch;
        InitBitree(T->lchild);
        InitBitree(T->rchild);
    }
    
}
  • 二叉树的几个基本性质

    1. 在 二叉树的第\(i\)层的结点个数最多为

      \[2^{i-1} \]

    2. 深度为\(k\)的二叉树的最大结点数为

      \[2^k-1 \]

    3. 任一二叉树\(T\),如果其叶子结点数为\(n_0\), 度为2的结点数为\(n_2\),则

      \[n_0=n_2+1 \\n_0+n_1+n_2=n=2n_2+n_1+1 \]

    4. 具有\(n\)个结点的完全二叉树深度为

      \[\lceil log_2(n+1) \rceil 或\lfloor log_2n\rfloor+1 \]

    5. 如果对一个有\(n\)个结点的完全二叉树\(T\)的结点按层序(从第一层到第\([logn]+1\)层,层内从左到右从1开始编号,则对任意一个编号为\(i(1<=i<=n)\)的结点有:

      • 如果\(i=1\),则该结点是二叉树的根,无双亲;如果\(i>1\)则其双亲结点\(Parent(i)\)的编号为\([i/2]\)

      • 如果\(2i>n\),则编号为\(i\) 的结点没有左孩子,为叶子结点;否则其左孩子\(LChild(i)\)的编号为2i

      • 如果\(2i+1>n\),则编号为\(i\) 的结点没有右孩子;否则其右孩子\(RChild(i)\)的编号为\(2i+1\)

第6章 图

图遍历运用——迷宫问题

如何得到路径?

  • 广度遍历时,队列元素增加一个 指向“队头”元素的指针
//代码实现
typedef struct {
	int		xpos;
	int		ypos;
}PosType;//迷宫每个单元信息
typedef struct  DQNode{
	PosType			seat;
	struct  DQNode		*next, *pre;//pre用于反向指向,找得到邻结点
}DQNode,*Dqueueptr;
typedef struct {
	Dqueueptr		front;
	Dqueueptr		rear;
}DlinkQueue;
//入队列
void EnQueue(DLinkQueue &Q,PosType e){
	p=new DQNode;   
	p->seat.xpos=e.xpos;
	p->seat.ypos=e.ypos;
	p->next=NULL;
	if(!Q.rear){   //首个结点
		p->pre=NULL;//必须要写这句话
		Q.rear=p;
        Q.front=p;
	}else{
		p->pre=Q.front;  
		Q.rear->next=p;
        Q.rear=p;  
	}
//出队列 自己写一下 不销毁结点
/*因Dequeue时找出邻接点, 如果先GetHead,并且Dequeue不销毁,
这样可以建立当前结点与队首结点关系*/
void DeQueue(){
    
}
//下一个结点
PosType NextPos(PosType  cur, int v)
{
	Postype	npos;
	npos.xpos=cur.xpos+di[v];
	npos.ypos=cur.ypos+dj[v];
	return npos;
}
bool  Pass(Postype  npos)
{
	 return(0<=npos.xpos && npos.xpos<=m-1 &&
		   0<=npos.ypos && npos.ypos<=n-1 &&
	 	   maze[npos.xpos][npos.ypos]= =0 &&
         visited[npos.xpos][npos.ypos]= =FALSE)
 }
bool ShortestPath(int maze[][], int m, int n, Stack &s){
    //Stack起反向作用
	DLinkQueue Q; bool visited[m][n]; InitQueue(Q);
	for(i=0;i<m;i++)
		for(j=0;j<n;j++)visited[i][j]=FALSE;
	EnQueue(Q,(0,0)); visited[0][0]=TRUE;found=FALSE;
	while(!found&&!QueueEmpty(Q)){//广度优先遍历可以中途停止当作指标
		GetHead(Q,curp); //GetHead函数写出来一下,获得Q队首元素curp
		for(v=0;v<8 &&!found;v++){
		     npos=NextPos(curp,v);
		     if(Pass(npos)){
                 EnQueue(Q,npos);//入队列
		         visited[npos.xpos][npos.ypos]=TRUE;
		         if(npos.xpos==m-1 && npos.ypos==n-1)//!!!!!!
					found=TRUE;
		     }	
             }//for
	        DeQueue(Q,curp);
	}//while
	if(found){
		InitStack(S);
		p=Q.rear;
		while(!p){                  
		      Push(S,p->seat); //自栈顶至栈底为路径
		      p=p->pre;	
		}//while
		return TRUE;
	}//if
	else return FALSE;
}//ShortestPath

  • 总结:

    把问题抽象为图问题,怎么保存路径,销毁结点


6.4 最小生成树(MST)


  • 相关概念:

    极小连通子图

    n个结点的连通图中,包涵n个结点和n-1个边构成的连通子图

    连通图的生成树:即极小连通子图

    连通网的最小生成树:权值和最小的生成树

    求连通网最小生成树的算法

    – 克鲁斯卡尔(Kruskal)算法 复杂度:O(\(eloge\))

    – 普里姆(Prim)算法 复杂度:O( \(n^2\) )

    – 算法比较:当e(边)与\(n^2\) 差不多时,采用Prim算法快;当e远小于\(n^2\) 时,采用Kruskal算法快

  • Kruskal算法

    • 算法思想
    1. 构造只含n个结点的森林。
    1. 按权值从小到大选择边加入到森林中,并使森林不产生回路。

    2. 重复2直到森林变成一颗树

    • 算法描述
    1. \(G(V,E)\),把V={1,2,......n}看成孤立的n个连通子图。边按照权的非递减次序排列。

    2. 顺序查看边。对于第k条边(v,w),如果v,w分别属于两个连通字图\(T_1\)、T2,则用边(v、w)将T1、T2连成一个连通字图。

    3. 重复2,直至n个结点同属于一个连通图


太过复杂,本课不要求


  • Prim算法

    算法思想 复杂度O(\(n^2\))

    1. 将所有结点分为两类集合:一类是已经落在生成树上的结点集合,另一类是尚未落在生成树上的结点集合。

    2. 在图中任选一个结点v构成生成树的树根,形成生成树结点集合。

    3. 在连接两类结点的边中选出权值最小的边,将该边所连接的尚未落在生成树上的结点加入到生成树上。同时保留该边作为生成树的树枝。

    4. 重复3直至所有结点都加入生成树

    算法描述

    \[\begin{align} &1.设G=(V,E),权A[m,n],令U=\{1\}。\\ &2.if(U<V) 取min(A[i,j]),使i∈U,j∈V-U。\\ &3.将j加入U\\ &4.重复2、3,直至U=V\\ \end{align} \]


//代码实现
//利用数组记录权值然后遍历比较出最小权
void Prim(MGraph G, int v0, int adjvex[]){
	//从序号为v0的顶点出发,构造连通网G的最小生成树
	int lowcost[MAX_VERTEX_NUM];   //定义辅助数组
	for(j=0;j<G.vexnum;j++) //用邻接矩阵的v0行初始化lowcost
		if(j!=v0){lowcost[j]=G.arcs[v0][j];adjvex[j]=v0;}
	lowcost[v0]=0;   //将v0标记为红点
	for(i=1;i<G.vexnum;i++){
		k=MinEdge(lowcost, G.vexnum); //lowcost[]中非零最小值
        printf("(%d,%d),%d\n",k,adjvex[k],lowcost[k]);
		lowcost[k]= 0;	//vk变成红点
		for(j=0;j<G.vexnum;j++){  //调整紫边集
		      if(G.arcs[k][j]<lowcost[j]){
					adjvex[j]=k;//将vj的候选紫边所关联的红点改成vk
					lowcost[j]=G.arcs[k][j]; //调整vj候选紫边的权
		      }
		}
	}//Prim 
posted @ 2022-04-21 09:36  CarrieHEA  阅读(77)  评论(0)    收藏  举报