前言
事情太多多多多了然后就没时间练题了,先在这写一下知识点叭。
一、树和图的存储以及相应的DFS、BFS(记忆化搜索)
-
树是n个结点的有限集合(可以是空集),在任一棵非空树中:
(1)有且仅有一个称为根的结点。
(2)其余结点可分为互不相交的子集,而且这些子集本身又是一棵树,称为根的子树。 -
图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G=(V,E)
其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。树是特殊的图,存储方式同图。而图可分为有向图和无向图,有向图又是特殊的无向图,所以主要应该掌握无向图的存储。图的存储分为邻接矩阵存储和邻接表存储。稠密图使用邻接矩阵存储,稀疏图使用邻接表存储。
-
邻接矩阵存储(数组表示法)
基本思想:
首先,先用一个一维数组存储图中顶点的信息,再用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
最近数据结构的课正好在上这一部分,就用数据结构的写法好了。
//邻接矩阵存储无向图的类
const int MaxSize=10;
template <class T>
class Mgraph{
public:
MGraph(T a[ ], int n, int e );
~MGraph( );
void DFSTraverse(int v);
void BFSTraverse(int v);
private:
T vertex[MaxSize];
int arc[MaxSize][MaxSize];
int vertexNum, arcNum;
};
//构造函数
template <class T>
MGraph::MGraph(T a[ ], int n, int e) {
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
vertex[i]=a[i];
for (i=0; i<vertexNum; i++) //初始化邻接矩阵
for (j=0; j<vertexNum; j++)
arc[i][j]=0;
for (k=0; k<arcNum; k++) {
cin>>i>>j; //边依附的两个顶点的序号
arc[i][j]=1; //arc[j][i]=1; //置有边标志
}
}
//DFS
template <class T>
void MGraph::DFSTraverse(int v)
{
cout<<vertex[v]; visited [v]=1;
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0)
DFSTraverse( j );
}
//BFS
int visited[MaxSize];
template <class T>
void MGraph::BFSTraverse(int v){
Queue Q; //假设采用顺序队列且不会发生溢出
cout<<vertex[v]; visited[v]=1; Q.EnQueue(v);
while (!Q.IsEmpty())
{
Q.DeQueuer(v);
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0 ) {
cout<<vertex[j];
visited[j]=1;
Q.EnQueue(j);
}
}
}
- 邻接表存储
基本思想:
对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
邻接表有两种结点结构:顶点表结点和边表结点。
vertex:数据域,存放顶点信息。
firstedge:指针域,指向边表中第一个结点。
adjvex:邻接点域,边的终点在顶点表中的下标。
next:指针域,指向边表中的下一个结点。
struct ArcNode
{
int adjvex;
ArcNode *next;
};
template <class T>
struct VertexNode
{
T vertex;
ArcNode *firstedge;
};
//邻接表存储有向图的类
const int MaxSize=10; //图的最大顶点数
template <class T>
class ALGraph
{
public:
ALGraph(T a[ ], int n, int e);
~ALGraph;
void DFSTraverse(int v);
void BFSTraverse(int v);
private:
VertexNode adjlist[MaxSize];
int vertexNum, arcNum;
};
//构造函数
template <class T>
ALGraph::ALGraph(T a[ ], int n, int e)
{
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
{
adjlist[i].vertex=a[i];
adjlist[i].firstedge=NULL;
}
for (k=0; k<arcNum; k++)
{
cin>>i>>j;
s=new ArcNode; s->adjvex=j;
s->next=adjlist[i].firstedge;
adjlist[i].firstedge=s;
}
}
//DFS
template <class T>
void ALGraph::DFSTraverse(int v)
{
cout<<adjlist[v].vertex; visited[v]=1;
p=adjlist[v].firstedge;
while (p!=NULL)
{
j=p->adjvex;
if (visited[j]==0) DFSTraverse(j);
p=p->next;
}
}
//BFS
template <class T>
void ALGraph::BFSTraverse(int v){
Queue Q;
cout<<adjlist[v].vertex; visited[v]=1; Q.EnQueuer(v);
while (!Q.IsEmpty( )) {
Q.DeQueue(v); p=adjlist[v].firstedge;
while (p!=NULL) {
j= p->adjvex;
if (visited[j]==0) {
cout<<adjlist[j].vertex;
visited[j]=1;
Q.EnQueuer(j);
}
p=p->next;
}
}
}
二、最短路迪杰斯特拉算法和其优化
基本思想:
- 设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v;
- 对vi∈V-S,假设从源点v到vi的有向边为最短路径。
- 以后每求得一条最短路径v, …, vk,就将vk加入集合S中,并将路径v, …, vk , vi与原来的假设相比较,取路径长度较小者为最短路径。
重复上述过程,直到集合V中全部顶点加入到集合S中。
void Dijkstra(MGraph g, int v){
for ( i =0; i<g.vexnum ; i++){
dist[i]=g.arcs[v][i];
if ( dist[i]!= MAX) path [i]=g.vertex[v]+g.vertex[i];
else path[i]=“”;
}
S[0]=g.vertex[v]; dist[v]=0;
num=1;
While (num<g.vextexNum;i++){
k=0;
for(i=0;i<G.vertexNum;i++){
if((dist[i]<dist[k] && dist[i]!=0) k=i;
cout<<dist[k]<<path[k];
s[num++]=G.vertex[k]; dist[k]=0;
for(i=0;i<G.vertexNum;i++)
if(dist[k]+g.arc[k][i]<dist[i] {
dist[i]=dist[k]+g.arc[k][i];
path[i]=path[k]+g.vertex[i];
}
}
}
优化
三、最小生成树算法
- Prim算法(加点法)
适合稠密图的最小生成树。
Void prime(MGraph G){
for(int i=1;i<G.vertexNum;i++){
lowcost[i]=G.arc[0][i];
adjvex[i]=0;
}
lowcost[0]=0;
for(i=1;i<G.vertexNum;i++){
k=MinEdge(lowcost,G.vertexNum);
cout<<K<<adjvex[k]<<lowcost[k];
lowcost[k]=0;
for(j=1;j<G.vertexNum;j++)
if(G.arc[k][j]<lowcost[j]){
lowcost[j]=G.arc[k][j];
arjvex[j]=k;
}
}
}
- Kruskal算法(加边法)
适合稀疏图的最小生成树。
在这里插入代码片
四、并查集原理以及应用
以2017年天梯赛决赛L2-4 部落 为例
AC代码
#include <bits/stdc++.h>
using namespace std;
int a[10010];
int vis[10010];
int fd(int x)
{
if(a[x]==x)
{
return x;
}
else
{
return a[x]=fd(a[x]);
}
}
void join(int x,int y)
{
int xx=fd(x);
int yy=fd(y);
if(xx!=yy)
{
a[xx]=yy;
}
}
int main()
{
int i,k,m,p,n,count1=0,count2=0;
int r,s,t;
cin>>n;
for(i=1;i<10010;i++)
{
a[i]=i;
vis[i]=0;
}
for(i=0;i<n;i++)
{
cin>>k;
cin>>m;
vis[m]=1;
k--;
while(k--)
{
cin>>p;
vis[p]=1;
join(m,p);
}
}
for(i=0;i<10010;i++)
{
if(vis[i]==1)
{
count1++;
}
}
cout<<count1<<" ";
for(i=1;i<=count1;i++)
{
if(a[i]==i)
{
count2++;
}
}
cout<<count2<<endl;
cin>>r;
while(r--)
{
cin>>s>>t;
if(fd(s)==fd(t))
{
cout<<"Y";
}
else
{
cout<<"N";
}
if(r!=0)
{
cout<<endl;
}
}
return 0;
}
,另外,对于路径可进行压缩
int found(int x)
{
int r=x;
while(r!=pre[r])
{
r=pre[r];
}
int i=x,j;
while(i!=r)//路径压缩
{
j=pre[i];
pre[i]=r;
i=j;
}
return r;
}
五、拓展:LCA最近公共祖先
总结
以上就是所学内容,本文仅仅简单介绍了相关算法,至于应用还需要更多练习。