数据结构与算法-树与图
往期内容
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
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;
}
};