Bootstrap

数据结构与算法3-树和图

数据结构与算法-树与图

往期内容
1-链表
2-栈与队列
3-树与图
4-哈希表
5-查找
6-排序
7-贪心
8-递归与分治
9-动态规划



基本概念

树:一对多的存储结构


一、树基本概念

树的表示方法

1.1 树的表示方法

  • 双亲表示法

  • 孩子表示法

  • 双亲孩子表示法

  • 孩子兄弟表示法

//树的存储结构


//树的存储结构--顺序存储结构
//1.双亲表示法
//约定:根结点的位置域设置为-1
//优点:很容易找到节点的双亲,不容易找到节点的孩子节点,解决方法-> 增加一个孩子域firstchild和rightsib右兄弟域
#define MAX_TREE_SIZE 100
typedef int TElemType;
struct PTNode
{
    TElemType data;//当前节点的数据
    int parent;//当前节点的双亲节点的位置
};
struct PTree
{
    PTNode nodes[MAX_TREE_SIZE];//节点数组
    int r,n;//根的位置,节点数目
};

//2.孩子表示法
//方案一:按照最大的度来开辟数据域和孩子域
//方案二:专门取一个位置来存储结点指针域的个数->克服空间浪费的缺点,空间的利用率提高,但是每个结点链表的结构不同,导致时间上会带来一定的损耗
//方案三:孩子表示法->把每个结点放到一个顺序存储的数组中,然后对每个孩子结点建立单链表->很容易找到节点的孩子节点,或者某个节点的兄弟结点,双亲难
//                                                                          ------>解决方式:增加一个结构变量parent,用于存储结点的双亲信息
typedef struct CTNode//孩子结点
{
    int child;
    CTNode *next;
} *ChildPtr;

typedef struct//表头结点
{
    TElemType data;
    ChildPtr firstchild;
}CTBox;

typedef struct //树结构
{
    CTBox nodes[MAX_TREE_SIZE];//结点数组
    int r,n;//根节点位置,根节点的数目
}CTree;



//3.孩子兄弟表示法
//结点的结构:data-firstchild-rightsib
//同样不方便找到双亲,可以利用增加parent指针的方式解决
typedef struct CSNode
{
    TElemType data;//数据域
    CSNode *firstchild,*rightsib;//左孩子,右兄弟指针
}CSNode,*CSTree;

1.2 二叉树的表示

//二叉树
//满二叉树是理想型的完全二叉树
//完全二叉树:按照层序的编号,相同的结点一一对应,
/**************************************************完全二叉树的特点*********************************************/
/*
1.叶子结点只能出现再最下面两层
2.某个结点度为1,只有左孩子,没有右孩子
3.同样结点数的二叉树,完全二叉树的深度最小
*/
/**************************************************完全二叉树的性质*********************************************/
/*
见《大话数据结构》p169
*/

//二叉树的存储结构
typedef struct BiTNode
{
    TElemType data;
    BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

1.3 二叉树的常用性质

性质1:二叉树第i 至多有2^(i-1)个结点
性质2:有k层深度的二叉树,至多共有2^k-1个结点
性质3:对任何一个二叉树T,终端结点数为n1,度为2的结点数为n2,则n1=n2+1
性质4:n个结点完全二叉树的深度为[logn]+1,向下取整
性质5:
在这里插入图片描述


二、二叉树基本操作

2.1 前中后遍历

  • 前序遍历
//前序遍历:根->左->右
void PreOrderTraverse(BiTree T)
{
	//1.退出条件
	if (T == NULL)
		return;
	//2.先取出数据
	cout << T->data;
	//3.依次遍历左右子树
	PreOrderTraverse(T->lchild);
	PreOrderTraverse(T->rchild);
}
  • 中序遍历
//中序遍历:左->根->右
void InOrderTraverse(BiTree T)
{
	//1.退出条件
	if (T == NULL)
		return;

	//2.遍历左子树
	InOrderTraverse(T->lchild);
	//3.取出数据
	cout << T->data;
	//4.遍历右子树
	InOrderTraverse(T->rchild);
}
  • 后续遍历
//后续遍历:左->右->根
void PostOrderTraverse(BiTree T)
{
	//1.退出条件
	if (T == NULL)
		return;

	//2.遍历左子树
	PostOrderTraverse(T->lchild);
	//3.遍历右子树
	PostOrderTraverse(T->rchild);
	//4.取出数据
	cout << T->data;
}

2.2 线索二叉树

//线索二叉树->中序遍历,将所有空指针域的右孩子结点指向它的后继结点,左孩子节点指向前驱结点
//解决问题:普通的二叉树,再叶子结点上,会产生较多的空指针域。造成资源的浪费
//核心:1.遍历进行线索化,将空的指针域指向下一个遍历的结点
//线索二叉树的实质,是将一颗二叉树变成了一个双向链表

#include<iostream>
using namespace std;

#define TElemType char

//显示二叉树的结构:lchild-ltag(左孩子or前驱标志位)-data-rtag-rchild
typedef enum{Link,Thread}  PointerTag;//Link==0表示左右孩子指针,Thread==1表示前继或者后继的线索

typedef struct BiThrNode
{
    TElemType data;
    BiThrNode *rchild,*lchild;
    PointerTag LTag;
    PointerTag RTag;
}BiThrNode,*BiThrTree;

//线索化的过程:在遍历的时候修改空指针的指向
BiThrTree pre;//始终指向刚刚访问过的结点
void InThreading(BiThrTree p)
{
    if(p)//如果当前结点不为空
    {
        //对左空孩子指针操作
        InThreading(p->lchild);

        if(!p->lchild)//当前结点
        {
            p->LTag=Thread;//前驱线索
            p->lchild=pre;//指向刚刚访问过的结点
        }
        
        if(!pre->lchild)//前驱没有孩子
        {
            pre->RTag=Thread;//后继线索
            pre->rchild=p;//指向刚刚访问过的结点
        }
        pre=p;//pre保持始终指向p的前驱


        //对右边空孩子指针操作
        InThreading(p->rchild);
    }
}

2.3 树、二叉树、森林转换

参考:三者转换


三、图的基本概念

3.1 邻接矩阵

利用二维数组来表示图,一维度数组中存储图中顶点信息,二维数组中存储图中的边或弧的信息

  • 定义
typedef char VertexType;//用户定义的顶点类型
typedef int EdgeType;//边上的权值类型
#define MAXVEX 100//用户定义的最大顶点数
#define INFINITY 65535//用INFINITY表示不连通的边

typedef struct{
    VertexType vexs[MAXVEX];//顶点集合
    EdgeType arc[MAXVEX][MAXVEX];//用邻接矩阵表示边的集合

    int numVertex,numEdges;//图中当前的顶点数和边数
}MGraph;
  • 初始化
//建立无向图的邻接矩阵表示
void CreateMGraph(MGraph *G)
{
    int i,j,k,w;
    cout<<"请输入顶点数和边数"<<endl;
    cin>>G->numVertex;
    cin>>G->numEdges;

    cout<<"输入顶点的值";
    for(i=0;i<G->numVertex;i++)
    {
        //输入顶点信息
        cin>>G->vexs[i];
    }

    //初始化邻接矩阵表为空表
    for(i=0;i<G->numVertex;i++)
    {
        for(j=0;j<G->numVertex;j++)
        {
            G->arc[i][j]=INFINITY;
            cout<<G->arc[i][j]<<"  ";
        }
        cout<<endl;
    }

    cout<<"边数"<<G->numEdges<<endl;
    //填入边的信息
    for(k=0;k<G->numEdges;k++)
    {
        cout<<"请输入边的信息(i,j,w)"<<endl;
        cin>>i;
        cin>>j;
        cin>>w;
        G->arc[i][j]=w;
        //对称矩阵
        G->arc[j][i]=G->arc[i][j];
    }
}

3.2 邻接表

  • 定义
//2.邻接矩阵缺点->当边数相对顶点数小的时候,这种方法对存储空间会产生极大的浪费
//邻接表
//顶点用一位数组存储
//图中每个顶点vi的所有邻接点构成一个线性表

//边表结点
typedef struct EdgeNode{
    int adjvex;//用于存储该顶点对应的下标
    EdgeType weight;//边的参数
    EdgeNode *next;//指向下一边结点的指针域
}EdgeNode;

//顶点表结点
typedef struct VertexNode{
    VertexType data;//数值域
    EdgeNode *firstedge;//指向边结点的指针域
}VertexNode,AdjList[MAXVEX];

//邻接表
typedef struct{
    AdjList adjList;//顶点的(数组)
    int numVertexes,numEdges;//当前图中的顶点数和边数
}GraphAdjList;
  • 创建
//用邻接表创建图
void CreateALGraph(GraphAdjList *G)
{
    int i,j,k;
    //定义边结点
    EdgeNode *e;
    cout<<"输入当前图的顶点数和边数"<<endl;
    cin>>G->numVertexes>>G->numEdges;

    cout<<"输入顶点的值";
    //建立顶点表
    for(int i=0;i<G->numVertexes;i++)
    {
        cin>>G->adjList[i].data;//输入顶点表的数据
        G->adjList[i].firstedge=NULL;//初始顶点表的下一指针为空
    }

    //边表
    for(k=0;k<G->numEdges;k++)
    {
        cout<<"请输入边(vi,vj)上的顶点序号"<<endl;
        cin>>i>>j;

        //申请边顶点的空间
        e=(EdgeNode*)malloc(sizeof(EdgeNode));
        e->adjvex=j;//邻接序号为j
        //头插法写法
        e->next=G->adjList[i].firstedge;
        G->adjList[i].firstedge=e;



        //无向表要书写相同的两次
        e=(EdgeNode*)malloc(sizeof(EdgeNode));
        e->adjvex=i;//邻接序号为i
        //头插法写法
        e->next=G->adjList[j].firstedge;
        G->adjList[j].firstedge=e;

    }
}

void printGraphAdjList(GraphAdjList G)
{
    int i,j;
    EdgeNode* p;
    for(i=0;i<G.numVertexes;i++)
    {
        //打印顶点表的值
        cout<<G.adjList[i].data<<" ";
        p=G.adjList[i].firstedge;
        while(p!=NULL)
        {
            //打印边结点的值
            cout<<G.adjList[p->adjvex].data<<" ";
            p=p->next;
        }
        cout<<endl;
    }
}

3.3 边集数组

顶点数组
边数组 begin end weight


四、图的基本操作

4.1 深度优先遍历DFS

递归
1.定义一个visit数组,数组的大小与顶点表的长度一致,visit对应位置的值,表示该结点是否已经完成访问
2.依次循环遍历每一个顶点
3.访问当前结点,判断当前结点是否已经完成访问,如果访问,跳过该结点,否则将该结点对应的visit位置,置为访问状态,并进行递归遍历操作

  • 邻接矩阵
bool visit[MAXVEX];//访问标志数组
//邻接矩阵的深度优先遍历的递归算法
void DFS(MGraph G,int i)
{
    int j;
    visit[i]=true;
    cout<<G.vexs[i]<<" ";
    for(j=0;j<G.numVertex;j++)
    {
        if(G.arc[i][j]!=INFINITY && visit[j]!=true)//找到邻接顶点,并且该顶点未被访问
            DFS(G,j);
    }

    
}
void DFSTraverse(MGraph G)
{
    int i;
    for(i=0;i<G.numVertex;i++)//初始化所有的元素未被访问
    {
        visit[i]=false;
    }
    for(i=0;i<G.numVertex;i++)
    {
        //如果未被访问
        if(!visit[i])
            DFS(G,i);
    }
}
  • 邻接表
//邻接表的深度优先遍历的递归算法
void DFS(GraphAdjList G,int i)
{
    EdgeNode *p;
    cout<<G.adjList[i].data;
    visit[i]=true;
    p=G.adjList[i].firstedge;

    while(p)
    {
        if(!visit[p->adjvex])
            DFS(G,p->adjvex);
        p=p->next;
    } 
}
void DFSTraverse(GraphAdjList G)
{
    int i;
    for(i=0;i<G.numVertexes;i++)//初始化所有的元素未被访问
    {
        visit[i]=false;
    }
    for(i=0;i<G.numVertexes;i++)
    {
        //如果未被访问
        if(!visit[i])
            DFS(G,i);
    }
}

4.2 广度优先遍历BFS

/*********广度优先遍历************************/
bool visit[MAXVEX];
void BFSTraverse(MGraph G)
{
    int i,j;
    SqQueue Q;
    InitQueue(&Q);//队列


    for(i=0;i<G.numVertex;i++)
        visit[i]=false;
    for(i=0;i<G.numVertex;i++)
    {
        if(!visit[i])
        {
            visit[i]=true;
            cout<<G.vexs[i];//输出当前的结果
            EnQueue(&Q,i);//将i压入队列中,只要访问过的顶点都压入队列中
            while(Q.front!=Q.rear)//如果队列不为空
            {
                DeQueue(&Q,&i);//取出当前队列中的数据并赋值为i
                for(int j=0;j<G.numVertex;j++)
                {
                    if(G.arc[i][j]!=INFINITY && !visit[j])
                    {
                        visit[j]=true;
                        cout<<G.vexs[j];
                        EnQueue(&Q,j);
                    }
                }
            }
        }
    }  
}


/*****广度优先遍历-邻接************/
void BFSTraverse(GraphAdjList G)
{
    int i,j;
    SqQueue Q;
    InitQueue(&Q);//队列

    EdgeNode *p;
    for(i=0;i<G.numVertexes;i++)
        visit[i]=false;
    for(i=0;i<G.numVertexes;i++)
    {
        if(!visit[i])
        {
            visit[i]=true;
            cout<<G.adjList[i].data;//输出当前的结果
            EnQueue(&Q,i);//将i压入队列中,只要访问过的顶点都压入队列中
            while(Q.front!=Q.rear)//如果队列不为空
            {
                DeQueue(&Q,&i);//取出当前队列中的数据并赋值为i

                    p=G.adjList[i].firstedge;
                    while(p)
                    {
                        if(!visit[p->adjvex])
                        {
                            visit[p->adjvex]=true;
                            cout<<G.adjList[p->adjvex].data;
                            EnQueue(&Q,p->adjvex);
                        }    
                        p=p->next;
                    }
            }
        }
    }  
}

4.3 最小生成树

最小生成树->最小的带权无向图
最小生成树:对于无向连通图G=(V,E),G的所有的生成树当中,边的权值之和最小的生成树为G的最小生成树(MST)
所有边的权值都不相同,则最小生成树是唯一的,或者n个节点只有n-1条边,最小生成的边数是顶点数减一

4.3.1普利姆算法

参考博文
最小生成树
普利姆(Prlm)

  • ①从图中找第一个起始顶点v0,作为生成树的第一个顶点,然后从这个顶点到其他顶点的所有边中选一条权值最小的边。然后把这条边的另一个顶点v和这条边加入到生成树中。
  • ②对剩下的其他所有顶点,分别检查这些顶点与顶点v的权值是否比这些顶点在lowcost数组中对应的权值小,如果更小,则用较小的权值更新lowcost数组。
  • ③从更新后的lowcost数组中继续挑选权值最小而且不在生成树中的边,然后加入到生成树。
  • ④反复执行②③直到所有所有顶点都加入到生成树中。
    概要:
    双重循环,外层循环次数为n-1,内层并列的两个循环次数都是n。故普利姆算法时间复杂度为O(n2)
    而且时间复杂度只和n有关,所以适合稠密图
  • ⑤ 切记:adjvex数组:(i,adhvex[i])表示一条边。lowcost[i]表示当前(i,adhvex[i])的最小距离
/************************************************Prim算法的最小生成树*****************************************/
//prim:
//算法定义:
/*
无向连通图G=(V,E)
假设N=(P,{E})是连通图,TE是N上最小生成树中边的集合。
算法从U={u0},(u0∈V),TE={}开始。重复执行如下的操作:------->U是新加入的顶点集合,V是原始的顶点集合
在所有u∈U,v∈V-U的边(u,v)∈E中找到一条代价最小的边(u0,v0)并入集合TE,同时V0并入U,直至U=V为止。

此时TE中必有n-1条边,则T=(V,{te})为N的最小生成树。
*/
void MiniSpanTree_Prim(MGraph G)
{
    int adjver[MAXVEX];//保存相关的顶点下标
    int lowcost[MAXVEX];//记录顶点间边的权值

    lowcost[0]=0;//存放这U中的原始到所有结点的最小值
    adjver[0]=0;//存放这边信息,即索引和该索引里面的值构成了边。adjver[0]=0,第一个位置不存放值,因此有n-1条边

    for(int i=0;i<G.numVertex;i++)//初始化lowcost数组存放第一行邻接矩阵的值,初始话adjer边矩阵的所有值为0
    {
        lowcost[i]=G.arc[0][i];
        adjver[i]=0;
    }

    int min_arc;//用于找到lowcost数组中的最小值,默认为最大值
    int min_vex;//最小的边(adjver[min_vex],min_vex)

    for(int i=1;i<G.numVertex;i++)//表示需要循环的次数n-1,如果有n个结点的话
    {
        min_arc=INFINITY;//默认最小的值为一个很大的数
        for(int j=1;j<G.numVertex;j++)
        {
            if(lowcost[j]!=0 && lowcost[j]<min_arc)//在所有u∈U,v∈V-U的边(u,v)∈E中找到一条代价最小的边(u0,v0)并入集合TE
            {
                min_arc=lowcost[j];//将当前最小的值保存在min_arc中
                min_vex=j;
            }
        }

        lowcost[min_vex]=0;
        cout<<"最小的权值中最小的边"<<"("<<adjver[min_vex]<<","<<min_vex<<")"<<endl;
        
        //循环其他的点,并将其他的点加入进来
        for(int j=0;j<G.numVertex;j++)
        {
            if(lowcost[j]!=0 && G.arc[min_vex][j]<lowcost[j])
            {
                lowcost[j]=G.arc[min_vex][j];//更新lowcost的值,是的U中的所有点,到每个结点的值最小
                adjver[j]=min_vex;//表示是谁(min_arc)使得j位置上lowcost最小的?,故
            }
        }
    }
}

4.3.2 克鲁斯卡尔算法

将图中边按照权值从小到大排列,然后从最小的边开始扫描,设置一个边的集合来记录,如果该边并入不构成回路的话,则将该边并入当前生成树。直到所有的边都检测完为止。
概要:
概要: 克鲁斯卡尔算法操作分为对边的权值排序部分和一个单重for循环,它们是并列关系,由于排序耗费时间大于单重循环,所以克鲁斯卡尔算法的主要时间耗费在排序上。排序和图中边的数量有关系,所以适合稀疏图

/************************************************Kruskal算法的最小生成树*****************************************/
//Kruskal:
//算法定义:
/*
假设N={V,{E}}是连通图,令最小生成树的初始状态只有n个结点,没有边,结果集合中,初始化为T={V,{}},假设刚开始每个图中的顶点单独存在
在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上(新加入的边没有构成环),则将此边加入到T中,
否则舍去此边,寻找下一个代价最小的边
依次类推,直到T中所有的顶点都在同一个连通分量上为止。
*/


//定义一个边集数组结构 
typedef struct{
    int begin;
    int end;
    int weight;
}Edge;


//将图转化为排序好后的边集数组
/* 交换权值 以及头和尾 */
void swapn(Edge *edges, int i, int j)
{
    int temp;
    temp = edges[i].begin;
    edges[i].begin = edges[j].begin;
    edges[j].begin = temp;
    temp = edges[i].end;
    edges[i].end = edges[j].end;
    edges[j].end = temp;
    temp = edges[i].weight;
    edges[i].weight = edges[j].weight;
    edges[j].weight = temp;
}

/* 对权值进行排序 */
void sort(Edge edges[], MGraph *G)
{
    for(int i = 0; i < G->numEdges; i++)//简单选择排序
    {
        for(int j = i + 1; j < G->numEdges; j++)
        {
            if(edges[i].weight > edges[j].weight)
                swapn(edges, i, j);
        }
    }
    cout<<"排序后的边集数组为:\n";
    for (int i = 0; i < G->numEdges; i++)
    {
        cout<<edges[i].begin<<" "<<edges[i].end<<" "<<edges[i].weight<<endl;
    }
    cout<<"------------------------------------------------------"<<endl;
}


//查找连接线顶点的尾部下标
int find(int *parent,int f)
{
    while(parent[f]>-1)//只要父节点不为-1,一直向上查找,直到找到父节点为止,如果为-1,则将该结点作为父结点
        f=parent[f];
    return f;
}

void MiniSpanTree_Kruskal(MGraph G)
{
    //定义一个边集数组
    Edge edge[MAXVEX];
    //定义一个并查集
    int parent[MAXVEX];//用于判断是否构成了回路

    //G转换为排序好的边集数组,并排序
    /* 用来构建边集数组并排序********************* */

    int k=0;
    for(int i = 0; i < G.numVertex - 1; i++)
    {
        for(int j = i + 1; j < G.numVertex; j++)
        {
            if(G.arc[i][j] < INFINITY)
            {
                edge[k].begin = i;
                edge[k].end = j;
                edge[k].weight = G.arc[i][j];
                k++;
            }
        }
    }
    sort(edge, &G);
    /* ******************************************* */


    //初始化上述的两个数组
    for(int i=0;i<G.numVertex;i++)
    {
        parent[i]=-1;
    }
    for(int i=0;i<G.numEdges;i++)//遍历所有的边
    {
        int n=find(parent,edge[i].begin);//找到edge[i].begin的父结点
        int m=find(parent,edge[i].end);//找到edge[i].end的父节点
        if(n!=m)//现有的生成树,没有形成环
        {
            parent[n]=m;//将边加入集合中,m(end)为根,n(begin)为孩子
            cout<<edge[i].begin<<" "<<edge[i].end<<" "<<edge[i].weight<<endl;
        }
    }
}

4.4 最短路径

4.4.1 迪杰斯特拉算法

/****************************************Dijkstra(迪杰斯特拉算法)**************************************/
//Dijkstra 带权图单元最短路径-》最终找到v0没其余顶点的最短路径
/*
辅助数组:dist[]:记录从源点v0到其他各个顶点当前的最短路径长度,数组中的值初始化为源点到各个顶点边的权值,即dist[i]=arcs[0][i]
         s[]:标记已经计算完成的顶点。数组中的值全部初始化为0,源点下标初始化为1,计算完成赋值为1
         path[]:记录从最短路径中顶点的前驱顶点,path[i]表示v到vi最短路径上vi的前驱顶点
*/
//见王道数据结构考研:核心(min+G.arc[min_vex][j])<dist[j]
void ShortestPath_Dijkstra(MGraph G,int v0)
{
    int dist[MAXVEX];
    int s[MAXVEX];
    int path[MAXVEX];

    for(int i=0;i<G.numVertex;i++)//初始化数据
    {
        s[i]=0;//0表示该结点未被访问,1表示该结点已经被访问
        dist[i]=G.arc[v0][i];
        path[i]=0;
    }

    //修改第一个数据的值
    dist[v0]=0;
    s[v0]=1;

    int min;//寻找最小的值
    int min_vex;//最小的边(adjver[min_vex],min_vex)
    for(int i=1;i<G.numVertex;i++)//循环n-1次
    {
        min=INFINITY;
        for(int j=0;j<G.numVertex;j++)
        {
            if(!s[j] &&  dist[j]<min)
            {
                min=dist[j];
                min_vex=j;
            }
        }
        s[min_vex]=1;//该顶点已被访问

        //修正当前的最短路径和距离
        for(int j=0;j<G.numVertex;j++)
        {
            if(!s[j] && (min+G.arc[min_vex][j])<dist[j])
            {
                dist[j]=min+G.arc[min_vex][j];
                path[j]=min_vex;//j的父亲结点是min_vex
            }
        }
    }

    //算法到处结束,下面用于打印路径的代码
    for(int i=0;i<G.numVertex;i++)
    {
        cout<<path[i]<<" ";
    }
}

4.4.2 佛洛依德算法

/*****************************Floyd算法*************************************/
void ShortestPath_Floyd(MGraph G)
{
    int A[MAXVEX][MAXVEX];
    int P[MAXVEX][MAXVEX];
    for(int i=0;i<G.numVertex;i++)
        for(int j=0;j<G.numVertex;j++){
            A[i][j]=G.arc[i][j];//初始化A-1数组,保存某一点到另一点的最短路径长度和
            P[i][j]=j;//默认开始,每个点都指向自己
    }
    for(int k=0;k<G.numVertex;k++)//中间点k
        for(int i=0;i<G.numVertex;i++)
            for(int j=0;j<G.numVertex;j++)
            {
                if(A[i][j]>A[i][k]+A[k][j]){
                    A[i][j]=A[i][k]+A[k][j];
                    P[i][j]=P[i][k];//i->j的路径变为,i->k->j
                }
            }

    //算法到此处结束,后面展示的是打印和显示
    for(int i=0;i<G.numVertex;i++)
    {
        for(int j=0;j<G.numVertex;j++)
            cout<<P[i][j]<<" ";
        cout<<endl;
    }

    //最短路径的显示代码:以从v0到v8为例
    for(int v=1;v<G.numVertex;v++)//打印v0到所有顶点的最短路径
        for(int w=v+1;w<G.numVertex;w++)
        {
            //打印第一点的的路径
            cout<<v<<"到"<<w<<"的权值大小为:"<<A[v][w]<<endl;
            int k=P[v][w];
            cout<<"path="<<v<<"->";
            while(k!=w)
            {
                cout<<k<<"->";
                k=P[k][w];
            }
            cout<<w<<endl;
        }
        cout<<endl;
}

查找路径:以0-8为例

p[0][8]=1;
p[1][8]=2;
p[2][8]=4;
p[4][8]=3;
p[3][8]=6;
p[6][8]=7;
p[7][8]=8;
路径:v0->v1->v2->v4->v3->v7->v8

4.5 拓扑排序

4.6 关键路径


五、Leetcode刷题

5.1 路径之和2

Leetcode-113 路径之和2
思路:

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>> result;
        vector<int> item;//模拟栈的操作
        int sum=0;
        generate(root,targetSum,sum,item,result);
        return result;
    }

    void generate(TreeNode*root,int &targetSum,int sum,vector<int> &item,vector<vector<int>> &result)
    {
        if(!root) 
            return;
        item.push_back(root->val);//先序:加入结点
        sum=sum+root->val;
        if(root->left==nullptr && root->right==nullptr && sum==targetSum)
        {
            result.push_back(item);
        }

        //遍历左子树
        generate(root->left,targetSum,sum,item,result);
        //遍历右子树
        generate(root->right,targetSum,sum,item,result);

        sum-=root->val;//左右子树遍历完成:弹出结点
        item.pop_back();
    }

};

5.2 二叉树的最近公共祖先

Leetcode-236 二叉树的最近公共祖先
思路:用一个vector模拟栈,存储待查找结点,vector中保存所有的父亲结点。最后遍历两个数组找到,公共的最近祖先。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path_a, path_b;
        if (!get_path(root, p, path_a) || !get_path(root, q, path_b)) return nullptr;
        int i = 1;
        for (; i < path_a.size() && i < path_b.size(); i++) {
            if (path_a[i] != path_b[i]) return path_a[i-1];
        }
        return path_a[i-1];
    }
    bool get_path(TreeNode* root, TreeNode* a, vector<TreeNode*>& path) {
        if (!root) return false;
        path.emplace_back(root);
        if (root == a) return true;
        if (get_path(root->left, a, path)) return true;
        if (get_path(root->right, a, path)) return true;
        path.pop_back();
        return false;
    }
};

5.3 二叉树转链表

Leetcode-114 二叉树展开为链表
思路1:比较low的方法,遍历所有的元素并保存在vector数组中,然后从新构建一个链表
思路2:递归的思想,将整个是抽象为一个根节点、左子树和右子树,当前步骤构建二叉树只需要,改变指向:根节点指向左子树,左子树的最右叶子节点指向根结点的右子树,重点:记录链表的尾部指针。

class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode* last=nullptr;
        preorder(root,last);
         
    }

    void preorder(TreeNode* root ,TreeNode* &last)//关键信息:右子树的有结点
    {
        if(!root)
            return;
        //前序
        if(root->left==nullptr && root->right==nullptr)//最小子问题,单个结点的处理
        {
            last=root;
            return;
        }

        TreeNode *left=root->left;
        TreeNode *right=root->right;
        TreeNode *left_last=nullptr;
        TreeNode *right_last=nullptr;
        //中序
        if(left)//左子树
        {
            preorder(left ,left_last);
            root->left=nullptr;
            root->right=left;
            last=left_last;
        }
        if(right)
        {
            preorder(right ,right_last);
            if(left_last){
                left_last->right=right;
            }
            last=right_last;
        }
        //后续


    }


};

5.4 二叉树的右示图

Leetcode-199 二叉树的右视图
思路:宽度优先搜索,利用一个pair数据结构保存 结点和结点所在的层数,根据层数依次替换结点,知道每层的最后一个结点保存在结果集合中。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        //深度优先遍历
        vector<int> view;
        queue<pair<TreeNode *,int>> Q;

        if(root)
        {
            Q.push(make_pair(root,0));
        }

        while(!Q.empty())
        {
            TreeNode *node=Q.front().first;
            int depth=Q.front().second;
            Q.pop();

            if(depth==view.size())
            {
                view.push_back(node->val);
            }
            else
            {
                view[depth]=node->val;
            }

            if(node->left) Q.push(make_pair(node->left,depth+1));
            if(node->right) Q.push(make_pair(node->right,depth+1));
        }

        return view;
    }
};
;