图
1,图的定义
多对多
无向图,顶点之间没有方向,边为无向边,表示方法(v,v),如果任何俩个顶点都有边,就叫完全无向图
有向图,顶点之间有方向,边为有向边,称为弧,<v1,v2>,弧尾到弧头,即v1到v2,任何俩个边都有正反俩条边,叫完全有向图
简单图就是不存在自己到自己或同一条边重复出现。
边少顶点少的叫稀疏图反之叫稠密图,没有意义因为过于主观化。
带权的图叫网
连通图就是任何俩个顶点都有路
无向圈中的极大连通子图称为连通分量。
1要是子图;
2 图要是连通的;
3 连通子图含有极大顶点数
4 具有极大顶点数的连通子图 含依附 这些顶点的所有边
3,图的存储结构
1,邻接矩阵
一个一维数组存储图中顶点信息,一个二维数组存储图中的边或弧的信息
二维数组存储顶点到顶点的距离,
下面展示一些 内联代码片
。
#include<stdio.h>
#include<stdlib.h>
#define size 100
typedef struct node
{
int a[size];//顶点数组
int b[size][size];//权值数组,
int asize,bsize;//顶点数和边数
}q;
int main()
{
scanf("%d%d",&q.asize,&q.bsize);//输入顶点数和边数
int i,j;
for(i=0;i<q.asize;i++)//输入顶点
{
scanf("%d",&a[size]);
}
for(i=0;i<q.asize;i++)//初始化,任何俩个顶点的距离都是无限大,即不联通
for(j=0;j<q.bsize;j++)
b[i][j]=999999;
for(i=0;i<q.bsize;i++)//输入顶点与顶点的距离
{
scanf("%d%d%d",&z,&x,&c);
b[z][x]=c;//写俩个的原因是该图为无向图,a到b的距离是c,那么b到c的距离也为c
b[x][z]=c;
}
}
缺点:如果该图顶点多但边少,则会造成很大的空间浪费,解决办法邻接表
2,邻接表
一维数组存顶点,线性表存邻接点。
#include<stdio.h>
#include<stdlib.h>
typedef struct Edgenode//表结点
{
int adjxi;
struct Edgenode *next;
} q;
typedef struct vertexnode//头节点
{
int ad;
struct Edgenode *fist;
} p,w[100];
typedef struct
{
w r;//之所以多此一举,是因为我们的创建函数里传递的是该结构体变量的地址。
int asize,bsize;
} t;
void creat(t *g)
{
int i,j;
scanf("%d%d",&g->asize,&g->bsize);//结点数,边数
for(i=0; i<g->asize; i++)//输入顶点,在为输入边的关系的时候,默认该点无邻接点,因此fist指向空
{
scanf("%d",&g->r[i].ad);
g->r[i].fist=NULL;
}
for(i=0;i<g->bsize;i++)//输入边的关系
{
scanf("%d%d",&o,&p);
q *e=(q*)malloc(sizeof(q));
e->adjxi=p;
e->next= g->r[o].fist;//先让它等于头结点的第一个邻接点,任何让头结点的第一个邻接点为该点,类似于栈
g->r[o].fist=e;
*e=(q*)malloc(sizeof(q));
e->adjxi=p;
e->next= g->r[p].fist;
g->r[p].fist=e;
}
}
邻接表是我指向谁,而逆邻接表是谁指向我
十字链表
4,图的遍历
dfs,基本思想就是从一个点出发,有路就走,一直走到无路可走,任何返回上一个结点,看有没有路,一直走一直返回。
下面展示一些 内联代码片
。
#include<stdio.h>
#include<stdlib.h>
#inlude<string.h>
void dfs(q *g,int i)//邻接矩阵的遍历
{
int j;
ver[i]=true;
printf("%d",q->data[i]);
for(j=0;j<q->asize;j++)
if(!ver[j]&&a[i][j]!=999)
dfs(g,i);
}void dfsq()//遍历一遍
{
int i,j;
for(i=0;i<q->asize;i++)
{
ver[i]=false;
}for(i=0;i<q->asize;i++)
{
if(!ver[i])
dfs(g,i);
}
}
typedef struct Edgenode//表结点
{
int adjxi;
struct Edgenode *next;
} q;
typedef struct vertexnode//头节点
{
int ad;
struct Edgenode *fist;
} p,w[100];
typedef struct
{
w r;//之所以多此一举,是因为我们的创建函数里传递的是该结构体变量的地址。
int asize,bsize;
} t;
void dfs(p *g,int i)//邻接表
{
q *qwer;
ver[i]=true;
printf("%d",g->r[i].ad);
qwer=g->r[i].fist;
while(qwer)
{
if(!ver[qwer.adjxi])
dfs(g,qwer->adjxi);
qwer=qwer->next;
}
}void dfsq(p *g)//遍历一遍
{
int i;
for(i=0;i<g->asize;i++)
{
ver[i]=false;
}for(i=0;i<g->asize;i++)
{
if(!ver[i])
dfs(g,i);
}
}
bfs,基本思想是从一个点出发,先把与该点相通的结点都走一遍,任何在从外面的一个点出发走完所有与该点相通的结点。
下面展示一些 内联代码片
。
#include<stdio.h>
#include<stdlib.h>
#inlcude<string.h>
typedef struct node
{
int a[size];
int b[size][size];
int asize,bsize;
} q;
void bfs(q *g)//邻接矩阵
{
queue w;//dingyi
ver[i]=true;
initqueue(&w);//初始
for(int i=0; i<q->asize)
{
ver[i]=false;
}
for(int i=0; i<g->asize; i++)
{
if(!ver[i])
{
inqueue(&w,i);
ver[i]=true;
printf("%d",g->a[size]);
while(!empty(&w))
{
enqueue(&q,i);
for(k=0; k<g->asize; k++)
{
if(!ver[k]&&g->b[i][k]!=99999)
{
printf("%d",g->a[k]);
ver[k]=true;
inqueue(&q,k);
}
}
}
}
}
}
typedef struct Edgenode//表结点
{
int adjxi;
struct Edgenode *next;
} q;
typedef struct vertexnode//头节点
{
int ad;
struct Edgenode *fist;
} p,w[100];
typedef struct
{
w r;//之所以多此一举,是因为我们的创建函数里传递的是该结构体变量的地址。
int asize,bsize;
} t;
void bfs(t *g)//邻接表
{
q *qwer;
queue dui;
innit(&dui);
for(int i=0; i<g->asize; i++)
ver[i]=false;
for(int i=0; i<g->asize; i++)
{
if(!ver[i])
{
ver[i]=true;
inqueue(&dui,i);
printf("%d",g->r[i].ad);
while(!empty(&dui))
{ enqueue(&q,i);
qwer=g->r[i].fist;
while(qwer)
if(!ver[qwer->adjxi])
{
printf("%d",qwer->adjxi);
ver[qwer->adjxi]=true;
inqueue(&dui,qwer);}
qwer=qwer->next;
}
}
}
5,最小生成树
经典的有两种算法,普里姆算法和克鲁斯卡尔算法。
1,普利姆算法
它的思想是从起点出发,找到离起点最近的点,然后从该起点出发,更新哪一个点到另一个点更近。
下面展示一些 内联代码片
。
#define inf 100
void prime(p* g)
{
int min,k,j,i;
int a[inf];//从哪一个结点来到该结点最短
int b[inf];//判断该点是否已经找到最短,并且临时存储已知的最小的路径
for(i=0;i<g->size;i++)//初始化
{
a[i]=0;//由于从0开始,所以所有的结点的上级都是0
b[inf]=g->c[0][i];//刚开始都没有找到最短,所以所有的结点的最短路径都是0到该点的路径
}a[0]=0;//0到0,距离最短
b[0]=0;//找到
for(i=1;i<g->size;i++)//找到所有的结点
{
min =9999;//初始最大
k=0;//
for(j=1;j<g->size;j++)//找一个最短
{
if(b[j]!=0&&b[j]<min)//
{
k=j;//
min=b[j];//
}
}
printf("%d%d",a[k],k);//
for(j=1;j<g->size;j++)//将找到的点为起点,更新该点到其他点的距离,如果该距离比b【k】小,就更新
{
if(b[j]!=0&&c[k][j]<b[j])//
{
b[j]=c[k][j];//
a[j]=[k];//
}
}
}
}
克鲁斯卡尔算法,就是先将各个边存储起来排序,让小的一个一个进去,前提是该点未到且不形成环(判断环的方法使用了并查集)
下面展示一些 内联代码片
。
typedef struct node
{
int end;//边的尾部
int front;//边的头部
int whight;//边的权值
}en;
void MiniSpanTree_Kruskal(qwe *q)
{
en a[10];//存储边
int b[10];//并查集数组,用来存放最后的boss
for(int i=0;i<q->size;i++)//初始化,每个人的boss都是0
{
b[i]=0;
}
for(int i=0;i<q->size;i++)//将排好序的数组按从小到大放进去
{
int n=find(b,a[i].front);//边头
int m=find(b,a[i].end);//边尾
if(n!=m)//如果不相等,说明boss不一样,说明没有形成循环
{
b[n]=m;
printf("(%d %d) %d",n,m,a[i].whight);//输出边和权值
}
}
}
int find(int *b,int f)//并查集函数
{
while(b[f]>0)//一直找到最终boss
f=b[f];
return f;
}
6,最短路径
1,迪杰斯特拉
该算法的核心就是从头结点开始,先找到离头结点最近的点,然后把该点当作头节点,看一看从原来的头结点经过该点在到其他点的距离是不是小于直接从原来的头结点到其他点的距离,如果小,就更新头节点到其他点的距离。
#define maxver 9
#define INFINITY 65535
typedef int pathmatirx[maxver];
typedef int shortpathtable[maxver];
void Dijkstra(MGraph q,int v0,pathmatirx *p,shortpathtable *d)//p尾前驱顶点,d为到该点的最短路径
{
int final[max];//final==1.说明已经找到最短路径,为0说明没找到
for(int i=0;i<q->size;i++)//初始
{
p[i]=0;//所有的点的前驱都是0
d[i]=q->a[0][i];//0到该点的距离
final[i]=0;
}
d[0]=0;//自己到自己的距离是0
final[0]=1;//
for(int i=1;i<q->size;i++)//找q-》size-1个结点,即除0以外的全部结点
{
min =INFINITY;
for(int w=1;w<q->size;w++)//先从头结点开始找离头节点最近的结点
{
if(!final[i]&&d[i]<min)//如果未找到最短路径且。。。
{
min=d[i];
k=i;
}
}
for(int j=0;j<q->size;j++)//将找到的结点当作头结点,遍历该点到其他点
{
if(!final[i]&&min+q->a[k][i]<d[i])//如果该点到其他点的距离加上头结点到该点的距离小于原来头结点直接到其他点的距离,就重新赋值
d[i]=min+q->a[k][i];
}
}
}
2,弗洛伊德
下面展示一些 内联代码片
。
void floyd
{
int d[10][10];//存储路径
int p[10][10];//存储i到j需要经过k,然后k到j需要经过什么。。。
for(int i=0;i<g->size;i++)//初始
for(int j=0;j<g->size;j++)//i到j的距离是几,刚开始i到j就是经过j
{
d[i][j]=g->a[i][j];
p[i][j]=j;
}
for(int i=0;i<g->size;i++)//动态规划思想,先假如只能从i=1处中转,然后可以从2,从3,从。。。n处中转。
for(int j=0;j<g->size;j++)
for(int w=0;w<g->size;w++)
{
if(d[j][w]>d[j][i]+d[i][w])
{d[j][w]=d[j][i]+d[i][w];
p[j][w]=p[j][i];}//i就是要经过的点,所以要等于p[j][i]
}
for(int i=0;i<g->size;i++)//
for(int j=i+1;j<g->size;j++)//i+1的原因是,当i为2的时候,2到1的路径已经在前面打印过了即1到2
{
printf("v%d w%d whight%d\n",i,j,d[i][j]);//打印是哪一个点到哪一个点
k=p[i][j];//记录i到j需要经过的第一个点
pirntf("%d",i);//输出起始点
while(k!=j)//如果需要经过的点不是终点,说明真的得经过其他点,然后就输出需要经过的该点,然后让k等于k到j需要经过的点
{
printf("-> %d",k);//输出要经过的点
k=p[k][j];
}
printf("-> %d",w);//while循环结束,输出终点
}printf("\n");
}
7,拓扑排序
基本思想:将每一个度为0的结点存在栈中,然后一个个出栈,每一个出栈时,遍历可以从该点的子点,如果该点消失后子点度为0,则将子点入栈。
用count存储入栈的数量,如果小于总点数,说明存在环。
下面展示一些 内联代码片
。
typedef struct node
{
int adjlist;//记录小结点
struct node *next;//该结点下一个
}engnode;
typedef struct st//结点
{
int in;//度
int data;//数据
engnode *fist;
}adnode[maxsize];
typedef struct stde//头
{
adnode a;//
int number;
}*qwer;
void tupo(qwer gl)
{
engnode *e;//小结点的指针,便于后面的减度操作
int top=0;//栈顶为0
int count=0;//共有0个数据
int *status;//栈
status=(* engnode)malloc(gl->numbersizeof(int ));//有几个结点,就开辟多大的空间
for(int i=0;i<gl->number;i++)//将度为0的点存入栈中
if(gl->a[i].in==0)
status[top++]=i;//
while(!top)//如果栈不空
{
int gettop=status[--top];//
count++;//
printf("%d ->",gl->a[gettop].data);//
for(e=gl->a[gettop].fist;e;e=e->next)//将
{
k=e->adjlist;//
if(!--gl->a[k].in)//
status[++top]=k;//
}
}
if(count<gl=>number)//
return eror;//
else
return true;
}