图的存储结构:
1.邻接矩阵
#include<stdio.h>
#include<stdlib.h>
#define MAXINT 32767
#define MVNum 100
#define OK 1
#define ERROR 0
typedef int Status;
typedef char VerTextType;//定义顶点数据类型全为char型
typedef int ArcType;//定义边的权值为整型
typedef struct{
VerTextType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum, arcnum;//顶点数和路径数
}AMGraph;
int LocateVex(AMGraph G, VerTextType u){
//存在则返回u在顶点表中的下标,否则返回-1
for(int i = 0;i<G.vexnum; i++)
{
if(u==G.vexs[i]) return i;
}
return -1;
}
//采用邻接矩阵表示法,创建无线网(UDN算法)
Status CreatUDN(AMGraph &G){
VerTextType v1, v2;//定义顶点
ArcType w;//定义权重(只能为0或1)
printf("请输入顶点数和边数: \n");
scanf("%d %d",&G.vexnum,&G.arcnum);//输入邻接矩阵的顶点数和路径数
getchar();
printf("请输入顶点: \n");
for(int i = 0; i < G.vexnum; i++){
scanf("%c", &G.vexs[i]);
} //off for i.
getchar();
for(int i = 0;i < G.vexnum; i++){
for(int j = 0; j<G.vexnum; j++){
G.arcs[i][j] = MAXINT;//初始化矩阵中的每一个位置为最大值
}//off for j.
}//off for i.
printf("请输入边的两个顶点及权重: \n");
for(int k = 0; k<G.arcnum; k++){
scanf("%c %c %d",&v1, &v2, &w);//输入一条边的顶点和权重.
getchar();
int i = LocateVex(G,v1);
int j = LocateVex(G,v2);//确定v1,v2在邻接矩阵中的位置
G.arcs[i][j] = w;
G.arcs[j][i]=G.arcs[i][j];
}
return OK;
}//off creatUDN
void printGraph(AMGraph G){
int i, j;
for(i = 0;i<G.vexnum; i++)
{
for(j = 0; j<G.vexnum;j++)
{
printf("%-5d ",G.arcs[i][j]);
if(j==G.vexnum-1) printf("\t\n");
}
}
}
//Entrance
int main(){
AMGraph G;
CreatUDN(G);
printGraph(G);
return 0;
}
2.邻接表:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
/*data:数据域,存储顶点信息
firstarc:指针域,指向边表中的第一个节点
adjvex:邻接点域
nextarc:指针域,指向边表的下一结点
info:存储和边或狐有关的信息,如权值
vex:顶点
arc:边*/
#define OK 1
#define ERROR 0
#define MVnum 100
typedef char VexTextType;//定义顶点数据类型全为char型
typedef int InfoType;//定义其他信息为int型,如权值等
typedef int Status;
//定义邻接表
//定义边表
typedef struct ArcNode{
int adjvex;//该边所指的顶点位置
struct ArcNode *nextarc;//指向下一条边的指针
InfoType info;//该边的相关信息
}ArcNode;
//定义顶点表
typedef struct VNode{
VexTextType data;//顶点信息
ArcNode *firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVnum];//AdjList表示邻接表类型
//图
typedef struct{
AdjList vertices;//邻接表 vertices[i] = VNode[i].data;
int vexnum, arcnum;//顶点数和边数
}ALGraph;
//Locate
int Locate(ALGraph G, VexTextType u){
//存在则返回u在顶点表中的下标,否则返回-1
for(int i = 0;i<G.vexnum; i++)
{
if(u==G.vertices[i].data)
return i;
}
return -1;
}
//图邻接表存储建立算法(UDG)
Status CreatUDG(ALGraph &G){
int v1, v2;
printf("请输入顶点个数和边数: ");
scanf("%d %d",&G.vexnum, &G.arcnum);
printf("请输入顶点: ");
for(int i = 0;i<G.vexnum;i++){ //输入各点,构造表头结点表
scanf("%d",&G.vertices[i].data); //输入顶点值
G.vertices[i].firstarc = NULL;//初始化表头结点指针域为空
}
for(int k = 0;k<G.arcnum;k++){
printf("请输入一对连接点: ");
scanf("%d %d",&v1,&v2);
int i = Locate(G,v1);
int j = Locate(G,v2);
ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));
p1->adjvex = j;//邻接点序号为j
p1->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = p1;//将新节点p1插入顶点vi的边表头部
ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));
p2->adjvex = i;//邻接点序号为i
p2->nextarc = G.vertices[j].firstarc;
G.vertices[j].firstarc = p2;//将新节点p2插入顶点vj的边表头部
}
return OK;
}
void printGraph(ALGraph G){
ArcNode *p;
for(int i = 0;i<G.vexnum;i++){
printf("%d->",G.vertices[i].data);
p = G.vertices[i].firstarc;
while(p){
printf("%d->",G.vertices[p->adjvex].data);
p = p->nextarc;
}
if (p==NULL)
printf("NULL\n");
}
}
int main(){
ALGraph G;
CreatUDG(G);
printGraph(G);
return 0;
}
运行结果:
邻接表和邻接矩阵的区别:
区别 | 邻接矩阵 | 邻接表 |
空间复杂度 | O(N^2)(N为顶点数) | O(N+E)(N为顶点数,E为边数) |
遍历时间复杂度 | O(N^2) | O(N+E) |
增删顶点时间复杂度 | O(N^2) | O(1) |
增删边时间复杂度 | O(1) | O(1) |
图的遍历:
遍历也分两种:深度优先遍历(DFS)以及广度优先遍历(BFS)
DFS
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#define MVNum 100
#define OK 1
#define ERROR 0
typedef char VerTexType;
typedef struct{
VerTexType vexs[MVNum];//顶点表
int vexnum,arcnum;//顶点数
int arcs[MVNum][MVNum];
}AMGraph;//邻接矩阵
int visited[MVNum];//已遍历过的节点以1存储在此数组中
void DFS(AMGraph G, int v){
printf("%c ",G.vexs[v]);
visited[v] = 1;
for(int i = 0;i<G.vexnum; i++){
printf("%d ",visited[i]);
}
printf("\n");
for(int w = 0; w<G.vexnum; w++){
if((G.arcs[v][w] != 0)&&(!visited[w]))
DFS(G,w);
}
}
void DFSTraver(AMGraph G){
for(int i = 0; i<G.vexnum; i++){
visited[i] = 0;
}
for(int i = 0; i<G.vexnum; i++){
if(visited[i] == 0){
DFS(G,i);//递归查找
}
}
}
int LocateVex(AMGraph G, VerTexType u){
//存在则返回u在顶点表中的下标,否则返回-1
for(int i = 0;i<G.vexnum; i++)
{
if(u==G.vexs[i]) return i;
}
return -1;
}
void creat(AMGraph &G)
{
char v1,v2;
scanf("%d %d",&G.vexnum,&G.arcnum);
getchar();
for(int i=0;i<G.vexnum;i++)
scanf("%c",&G.vexs[i]);
getchar();
for(int i=0;i<G.vexnum;i++)
for(int j=0;j<G.vexnum;j++)
G.arcs[i][j]=0;
for(int k=0;k<G.arcnum;k++){
scanf("%c %c", &v1, &v2);
getchar();
int i = LocateVex(G,v1);
int j = LocateVex(G,v2);//确定v1,v2在邻接矩阵中的位置
G.arcs[i][j] = 1;
G.arcs[j][i]=G.arcs[i][j];
}
}
int main(){
AMGraph G;
creat(G);
DFSTraver(G);
return 0;
}
BFS:
#define MVNum 100 //最大顶点数
typedef struct{ //图的结构体定义
char vexs[MVNum]; //存放顶点的一维数组
int arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的顶点数和边数
}Graph;
void BFS(Graph G,int v){ // 按照广度优先非递归遍历连通图G
cout << v;
visited[v] = true; // 访问第v个顶点
InitQueue(Q); // 使用队列辅助遍历,初始化队列Q
EnQueue(Q,v); // v进队
while(!QueueEmpty(Q)){ // 队列非空
DeQueue(Q,u); // 队头元素出队并赋值给u
for(w = FirstAdjVex(G,u);w>=0;w = NextAdjVex(G,u,w)) // 依次找弧
if(!visited[w]){ // w为u的尚未访问过的邻接顶点
cout << w;
visited[w] = true;
EnQueue(Q,w); // w进队
}
}
}
DFS与BFS算法效率分析:
时间复杂度:一般情况下都为O(N+E),N为顶点数,E为边数;
空间复杂度:都为O(N),因为需要借助辅助数组来标记是否被访问过;
3.最小生成树:
普利姆算法:
普利姆算法(Prim's algorithm)是一种用于生成最小生成树的算法,它的基本思想是从一个点开始,逐步扩展生成树,每次选择与生成树相邻的最小边加入生成树中,直到所有的点都被加入生成树为止。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MAX_VERTICES 100
#define INF INT_MAX
int graph[MAX_VERTICES][MAX_VERTICES];
int visited[MAX_VERTICES];
int parent[MAX_VERTICES];
int distance[MAX_VERTICES];
int minDistance(int n) {
int min = INF, minIndex;
for (int i = 0; i < n; i++) {
if (!visited[i] && distance[i] < min) {
min = distance[i];
minIndex = i;
}
}
return minIndex;
}
void prim(int n) {
for (int i = 0; i < n; i++) {
visited[i] = 0;
distance[i] = INF;
}
distance[0] = 0;
parent[0] = -1;
for (int i = 0; i < n - 1; i++) {
int u = minDistance(n);
visited[u] = 1;
for (int v = 0; v < n; v++) {
if (graph[u][v] && !visited[v] && graph[u][v] < distance[v]) {
parent[v] = u;
distance[v] = graph[u][v];
}
}
}
printf("Edge Weight\n");
for (int i = 1; i < n; i++) {
printf("%d - %d %d\n", parent[i], i, graph[i][parent[i]]);
}
}
int main() {
int n, m;
printf("Enter the number of vertices: ");
scanf("%d", &n);
printf("Enter the number of edges: ");
scanf("%d", &m);
printf("Enter the edges and weights:\n");
for (int i = 0; i < m; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
graph[u][v] = w;
graph[v][u] = w;
}
prim(n);
return 0;
}
克鲁斯卡尔算法:
克鲁斯卡尔算法(Kruskal's algorithm)是一种用于生成最小生成树的算法,它的基本思想是先将所有边按照权重从小到大排序,然后从小到大依次加入边,如果加入一条边会形成环,则不加入,直到所有的点都被加入生成树为止。
首先定义了一个常量MAX_VERTICES
表示图中最大的顶点数,一个常量MAX_EDGES
表示图中最大的边数。
然后定义了一个结构体Edge
表示边,包含起点、终点和权重。
接着定义了一个一维数组parent
表示每个顶点的父节点,一个一维数组edges
表示所有的边。
定义了一个比较函数cmp
用于边的排序,按照权重从小到大排序。
定义了函数find
和unionSet
用于并查集的实现,用于判断加入一条边是否会形成环。
最后定义了函数kruskal
实现了克鲁斯卡尔算法,首先初始化parent
数组,然后对所有的边按照权重排序,依次加入边并判断是否会形成环,如果不会,则将其加入生成树中,并将两个顶点合并。最后输出生成树的边和权重。
在main
函数中读入图的信息,调用kruskal
函数生成最小生成树。
需要注意的是,这里的图是无向图,因此在边的输入中需要同时输入起点和终点。如果是有向图,则只需要输入起点和终点即可。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100
#define MAX_EDGES 10000
struct Edge {
int u, v, w;
};
int parent[MAX_VERTICES];
struct Edge edges[MAX_EDGES];
int cmp(const void *a, const void *b) {
return ((struct Edge*)a)->w - ((struct Edge*)b)->w;
}
int find(int u) {
if (parent[u] == u) {
return u;
}
return parent[u] = find(parent[u]);
}
void unionSet(int u, int v) {
parent[find(u)] = find(v);
}
void kruskal(int n, int m) {
for (int i = 0; i < n; i++) {
parent[i] = i;
}
qsort(edges, m, sizeof(struct Edge), cmp);
printf("Edge Weight\n");
for (int i = 0; i < m; i++) {
int u = edges[i].u, v = edges[i].v, w = edges[i].w;
if (find(u) != find(v)) {
unionSet(u, v);
printf("%d - %d %d\n", u, v, w);
}
}
}
int main() {
int n, m;
printf("Enter the number of vertices: ");
scanf("%d", &n);
printf("Enter the number of edges: ");
scanf("%d", &m);
printf("Enter the edges and weights:\n");
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &edges[i].u, &edges[i].v, &edges[i].w);
}
kruskal(n, m);
return 0;
}
最短路径:
迪杰斯特拉算法:
首先定义了一个常量MAX_VERTICES
表示图中最大的顶点数,一个常量MAX_EDGES
表示图中最大的边数。
然后定义了一个结构体Edge
表示边,包含终点和权重。
定义了一个一维数组dist
表示每个顶点到源点的距离,一个一维数组visited
表示每个顶点是否已经被访问过,一个二维数组edges
表示所有的边,一个一维数组numEdges
表示每个顶点的边数。
定义了函数dijkstra
实现了迪杰斯特拉算法,首先初始化dist
和visited
数组,将源点的距离设为0,然后依次加入未确定最短路径的顶点集合中的距离最小的顶点,并更新其相邻的顶点到源点的距离。最后输出每个顶点到源点的距离。
在main
函数中读入图的信息,调用dijkstra
函数求解最短路径。
需要注意的是,这里的图是有向图,因此在边的输入中只需要输入起点、终点和权重即可。如果是无向图,则需要将边的权重同时赋值给对称位置的元素。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MAX_VERTICES 100
#define MAX_EDGES 10000
struct Edge {
int v, w;
};
int dist[MAX_VERTICES];
int visited[MAX_VERTICES];
struct Edge edges[MAX_EDGES][MAX_VERTICES];
int numEdges[MAX_VERTICES];
void dijkstra(int n, int s) {
for (int i = 0; i < n; i++) {
dist[i] = INT_MAX;
visited[i] = 0;
}
dist[s] = 0;
for (int i = 0; i < n; i++) {
int u = -1;
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
u = j;
}
}
visited[u] = 1;
for (int j = 0; j < numEdges[u]; j++) {
int v = edges[u][j].v, w = edges[u][j].w;
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
}
int main() {
int n, m, s;
printf("Enter the number of vertices: ");
scanf("%d", &n);
printf("Enter the number of edges: ");
scanf("%d", &m);
printf("Enter the source vertex: ");
scanf("%d", &s);
printf("Enter the edges and weights:\n");
for (int i = 0; i < m; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
edges[u][numEdges[u]++] = (struct Edge){v, w};
}
dijkstra(n, s);
printf("Vertex Distance from Source\n");
for (int i = 0; i < n; i++) {
printf("%d %d\n", i, dist[i]);
}
return 0;
}
拓扑排序:
拓扑排序是一种对有向无环图进行排序的算法,它将图中的所有顶点按照一定的顺序排列,使得对于每条有向边 (u,v),都有 u 在排列中出现在 v 的前面。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100
int indegree[MAX_VERTICES];
int adj[MAX_VERTICES][MAX_VERTICES];
int n;
void topologicalSort() {
int count = 0;
int queue[MAX_VERTICES];
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
queue[count++] = i;
}
}
while (count > 0) {
int u = queue[--count];
printf("%d ", u);
for (int v = 0; v < n; v++) {
if (adj[u][v]) {
indegree[v]--;
if (indegree[v] == 0) {
queue[count++] = v;
}
}
}
}
}
int main() {
int m;
printf("Enter the number of vertices: ");
scanf("%d", &n);
printf("Enter the number of edges: ");
scanf("%d", &m);
printf("Enter the edges:\n");
for (int i = 0; i < m; i++) {
int u, v;
scanf("%d %d", &u, &v);
adj[u][v] = 1;
indegree[v]++;
}
printf("Topological order: ");
topologicalSort();
return 0;
}
首先定义了一个常量MAX_VERTICES
表示图中最大的顶点数。
然后定义了一个一维数组indegree
表示每个顶点的入度,一个二维数组adj
表示图的邻接矩阵,一个整数n
表示顶点的数量。
定义了函数topologicalSort
实现了拓扑排序,首先将入度为0的顶点加入队列中,然后依次取出队列中的顶点,并将其相邻的顶点的入度减1,如果减1后入度为0,则将其加入队列中。最后输出排序结果。
在main
函数中读入图的信息,调用topologicalSort
函数进行拓扑排序。需要注意的是,在输入边的时候需要将邻接矩阵中相应的元素设为1,并将终点的入度加1。