数据结构丨图

基本概念

顶点:图中的数据元素

如果顶点之间的连接是没有方向的,则称为无向图,用(v,w)来表示v和w之间的连接;

如果顶点之间的连接有方向,则称为有向图,<v,w>表示v到w存在一条边。

完全图:eq?%5Cfrac%7B1%7D%7B2%7Dn%28n-1%29条边的无向图

有向完全图:具有eq?n%28n-1%29条弧的有向图

路径的长度:路径上的边或弧的数目/权值之和

回路:第一个顶点和最后一个顶点相同的路径

简单路径:序列中顶点不重复出现的路径

连通:在无向图中,如果从顶点v到w有路径,则称v和w是连通的

连通图:图中任意两个顶点都连通

连通分量:无向图中的极大连通子图

强连通图:有向图中每一对顶点都存在路径

强连通分量:有向图中的极大强连通子图

图的存储结构

区分邻接矩阵关联矩阵

邻接矩阵描述的是点与点之间的关系,而关联矩阵是点与边。

ef3e22fb6c624b9280047941f1e4c573.png

邻接矩阵(关联矩阵类似)

//图的邻接矩阵存储结构
typedef struct MGraph_adjM
{
	char data[MAX_NUM];//顶点
	int arc[MAX_NUM][MAX_NUM];//邻接矩阵
	int vexnum, arcnum;//当前顶点数和边数
}MGraph_adjM;

邻接表:由表头结点和边表两部分组成。

每个单链表中的第一个结点存放有关顶点的信息以及指向的第一条边结点;

每个边界点存储指向的顶点的信息以及下一条边。

//图的邻接表存储结构
typedef struct ArcNode
{
	int adjvex;
	struct ArcNode* nextarc;
}ArcNode;//边结点结构

typedef struct VNode
{
	char data;
	ArcNode* firstArc;
}VNode;//顶点结点结构

typedef struct MGraph_adjL
{
	VNode adjList[MAX_NUM];
	int vexnum, arcnum;
}MGraph_adjL;

图的遍历 

/*
* 这里的DFS函数可以替换成DFS_stack/BFS函数,三者的算法都在后文
*/
void traverse(MGraph_adjL l)
{
    for (int i = 0; i < l.vexnum; i++)
    {
        if (!l.adjList[i].info)
        {
            DFS(l.adjList[i], &l);
           
        }
    }
}
51a2ee6b20b4407ba941a9023b3ac662.jpeg
为了避免同一个顶点被访问多次,在遍历图的过程中,必须标记每个已经访问过的顶点。为此,可以设置一个辅助数组visited[Vexnum],初始值为0,一旦访问了顶点 v_i,便将visited[i]的值赋为1。这里,我在表头结点的结构体VNode中添加了info这个信息来代替辅助数组:
 
typedef struct VNode
{
    char data;
    int info;
    ArcNode* firstArc;
}VNode;

深度优先搜索(Depth_First Search, DFS) 

c9b13f596dc54197bac3eb0fdd94438d.jpeg可以使用递归或者使用堆栈来实现深度优先搜索。

递归方法直接看下面的代码;
堆栈方法思路:
1、声明一个堆栈用来存储待遍历的节点。
2、将起始节点压入堆栈。
3、当堆栈不为空时,执行以下步骤:
弹出堆栈顶部节点,并将其标记为已访问;
遍历该节点的所有未访问的邻接节点,将其压入堆栈中。
4、重复步骤3直到堆栈为空。

递归代码实现: 

void DFS(VNode anode, MGraph_adjL* l)
{
    printf("%c ", anode.data);
    ArcNode* p = anode.firstArc;
    while (p != NULL)
    {
        if (!l->adjList[p->adjvex].info)
        {
            l->adjList[p->adjvex].info = 1;
            DFS(l->adjList[p->adjvex], l);
        }
        p = p->nextarc;
    }
}

堆栈代码实现: 

void DFS_stack(VNode anode, MGraph_adjL* l)
{
    VNode* stack = (VNode*)malloc(l->vexnum * sizeof(VNode));
    int top = -1;
    stack[++top] = anode;
    while (top != -1)
    {
        //1、弹出栈顶
        ArcNode* p = stack[top].firstArc;
        printf("%c ", stack[top].data);
        top--;

        //2、将未访问的所有邻接顶点压入栈
        while (p != NULL)
        {
            if (!l->adjList[p->adjvex].info)
            {
                l->adjList[p->adjvex].info = 1;
                stack[++top] = l->adjList[p->adjvex];
            }
            p = p->nextarc;
        }
    }
    free(stack);
}

广度优先搜索(Broadth_First Search, BFS)

1e781ac6453e45a3b28af5fe49118383.jpeg从某个顶点v出发,访问了v之后依次访问v的各个未被访问过的邻接点,然后再从这些邻接点出发依次访问它们的邻接点,直至图中所有已被访问的顶点的邻接点都被访问到。

若此时还有顶点未被访问到,则取图中未被访问过的一个顶点再次作为起点,重复上述操作。

代码实现: 

void BFS(VNode anode, MGraph_adjL* l)
{
    int front = 0, rear = 0;//front队头,rear队尾
    VNode* queue=(VNode*)malloc(l->vexnum*sizeof(VNode));
    queue[rear++] = anode;

    while (front != rear)
    {
        VNode node = queue[front++];
        node.info = 1;
        printf("%c ", node.data);

        ArcNode* p = node.firstArc;
        while (p != NULL)
        {
            if (!l->adjList[p->adjvex].info)
            {
                queue[rear++] = l->adjList[p->adjvex];
                l->adjList[p->adjvex].info = 1;
            }
            p = p->nextarc;
        }
    }
    free(queue);
}


c1f6bdaee02b4620828a9451fa0da80b.png以这张图为例:
DFS算法遍历结果为:
A >B >E >D >C 或 A >D >B >E >C
BFS算法遍历结果为:
A >B >D >E >C 或 A >D >B >E >C
注:这个例子不是太好,可以换一个顶点更多的图试试hhh

 最小生成树

在n个顶点之间建立连线,使得连通这n个顶点的n-1条路线权重值之和最短,即最小生成树

最小生成树的MST性质:假设N=(V,\{E\})是一个连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值(代价)的边,其中u\in U,v\in V-U,则必存在一棵包含边(u,v)的最小生成树。普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法是两个利用MST性质构造最小生成树的算法。

普里姆算法

Prim算法可以使用优先队列来实现,以便在每一步中快速找到权重最小的边。该算法的时间复杂度为O(ElogV),其中V是顶点的个数,E是边的个数。

void Prim(MGraph g)
{
    int lowcost[MAX_NUM]; // 存储顶点到集合U的最短边权值
    int adjvex[MAX_NUM]; // 存储最短边终点的下标
    for (int i = 1; i < g.vexnum; i++)
    {
        lowcost[i] = g.arc[0][i]; 
        adjvex[i] = 0;
    }
    for (int i = 1; i < g.vexnum; i++)
    {
        int min = INFINITY;
        int k = 0;
        for (int j = 1; j < g.vexnum; j++)
        {
            if (lowcost[j] != 0 && lowcost[j] < min)
            {
                min = lowcost[j];
                k = j; 
            }
        }// 找出剩余顶点中权值最小的边
        printf("%c -> %c : %d\n", g.data[adjvex[k]], g.data[k], min); // 打印最小生成树的边和权值    
        lowcost[k] = 0; // 将该顶点加入集合U
        // 更新lowcost和adjvex数组
        for (int j = 1; j < g.vexnum; j++)
        {
            if (lowcost[j] != 0 && g.arc[k][j] < lowcost[j])
            {
                lowcost[j] = g.arc[k][j];
                adjvex[j] = k;
            }
        }
    }
}

克鲁斯卡尔算法

克鲁斯卡尔算法是一种贪心算法,每次选择权重最小的边,确保了最终的最小生成树的总权重最小。该算法的时间复杂度为O(ElogE),E为边的数量。

void Kruskal(MGraph g)
{
    int parent[MAX_NUM];//用于标记顶点所在集合
    for (int i = 0; i < g.vexnum; i++)
    {
        parent[i] = i;
    }//最初每个顶点分别属于不同集合
    int edgeCount = 0;
    while (edgeCount < g.vexnum - 1)//最小生成树的边数为顶点数减一
    {
        int MIN = INFINITY;
        int row = 0, col = 0;
        for (int i = 0; i < g.vexnum; i++)
        {
            for (int j = i + 1; j < g.vexnum; j++)
            {
                if (g.arc[i][j] < MIN && g.arc[i][j] != 0)
                {
                    MIN = g.arc[i][j];
                    row = i;
                    col = j;
                }
            }
        }//遍历邻接矩阵找到权重值最小的边
        if (parent[row] != parent[col])
        {
            printf("%c -> %c : %d\n", g.data[row], g.data[col], MIN);
            for (int i = 0; i < g.vexnum; i++)
            {
                if (parent[i] == parent[col])
                {
                    parent[i] = parent[row];
                }
            }//合并顶点集
            edgeCount++;
        }
        g.arc[row][col] = g.arc[col][row] = 0;  // 标记已加入最小生成树
    }
}

总结:Prim算法对于稠密图,即边数非常多的情况下具有优势;而克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所以对于稀疏图有很大的优势。

最短路径

迪杰斯特拉算法

迪杰斯特拉算法是一种用于解决带有权重有向图中最短路径问题的算法。

void Dijkstra(MGraph g)
{
    int dist[MAX_NUM];//与第一个顶点的当前最小距离
    int pre[MAX_NUM];//各顶点的前驱
    for (int k = 0; k < g.vexnum; k++)
    {
        dist[k] = g.arc[0][k];
        pre[k] = -1;
    }

    for (int i = 0; i < g.vexnum; i++)
    {
        int MIN = INFINITY;
        int m = -1;
        for (int j = 1; j < g.vexnum; j++)
        {
            if (dist[j] < MIN && pre[j] == -1)
            {
                MIN = dist[j];
                m = j;
            }
        }//找与第一个顶点距离最小的且未处理过的顶点
        if (m == -1)
        {
            break;
        }//没找到则说明全部处理完,循环终止
        pre[m] = 0;
        for (int j = 0; j < g.vexnum; j++)
        {
            int temp_distance = dist[m] + g.arc[m][j];
            if (g.arc[m][j] != 0 && temp_distance < dist[j])
            {
                dist[j] = temp_distance;
                pre[j] = m; 
                m = j;//更换前驱顶点
            }
        }
    }//至此,所有顶点到第一个顶点的最小路径及其前驱已经找到

    for (int i = 1; i < g.vexnum; i++)
    {
        printf("%c的最短路径长度为:%d,", g.data[i], dist[i]);
        printf("具体路径为:");
        printf("%c", g.data[i]);
        int temp = i; // 新增变量存储 pre[i] 的值
        while (pre[temp] != -1)
        {
            printf("<-%c", g.data[pre[temp]]);
            temp = pre[temp];
        }
        printf("\n");
    }
}

注意:这里输出前驱时,需要借助temp实现前驱的一步步回溯,如果直接把pre[i]=pre[pre[i]]会改变pre数组,使得后面的结果输出错误。

 待改进:这里路径输出是反着来的,可以用栈实现顺序输出。

void printPath(MGraph g, int* dist, int* pre)
{
    for (int i = 1; i < g.vexnum; i++)
    {
        char* stack = (char*)malloc(g.vexnum * sizeof(char));
        memset(stack, 0, g.vexnum * sizeof(char));
        int top = -1;
        printf("%c到V0的最短路径长度为:%2d,", g.data[i], dist[i]);
        int temp = i;
        while (pre[temp] != -1)
        {
            stack[++top] = g.data[pre[temp]];
            temp = pre[temp];
        }
        printf("具体路径为:");
        while (top != -1)
        {
            printf("%c -> ", stack[top--]);
        }
        printf("%c\n", g.data[i]);
        free(stack);
    }
}

弗洛伊德算法

1. 初始化一个二维数组dist,用于记录任意两个节点之间的最短路径长度。初始时,将所有节点之间的距离设置为正无穷大,即表示没有直接连通的路径。
2. 对于图中的每个节点,将其与相邻节点的直接距离设置为dist数组中的对应值。若两个节点之间不存在直接连接的边,则将其距离设置为正无穷大。
3. 对于图中的每个节点,分别以其为中间节点进行遍历,尝试缩短dist数组中的路径长度。
4. 在遍历过程中,对于任意两个节点x和y,如果通过节点k可以使得从x到y的路径长度更短,即dist[x][k] + dist[k][y] < dist[x][y],则更新dist[x][y]的值为dist[x][k] + dist[k][y]。
5. 重复步骤4,直到遍历完所有节点,此时dist数组中的值即为图中任意两个节点之间的最短路径长度。

弗洛伊德算法的时间复杂度为O(N^3),其中N为图中节点的数量,比DIJ算法略快。

具体代码实现不用掌握。


 上机作业

1、读入邻接矩阵,转换为邻接表,再转换为关联矩阵,最后再将关联矩阵转换回邻接矩阵。

邻接矩阵/邻接表/关联表相互转换丨代码详解icon-default.png?t=N7T8https://blog.csdn.net/qq_74315738/article/details/138835809?spm=1001.2014.3001.5501

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 100

typedef struct MGraph_adjM
{
    char data[MAX_VERTEX_NUM];
    int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
}MGraph_adjM;

typedef struct ArcNode
{
    int adjvex;
    struct ArcNode* nextarc;
}ArcNode;
typedef struct VNode
{
    char data;
    ArcNode* firstArc;
}VNode;
typedef struct MGraph_adjL
{
    VNode adjList[MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
}MGraph_adjL;

typedef struct MGraph_relaM
{
    int RelaM[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int row, col;
}MGraph_relaM;

MGraph_adjM createMGraph(char* filename);
MGraph_adjL adjMtoList(MGraph_adjM g);
void printList(MGraph_adjL l);
MGraph_relaM adjMtoRelaM(MGraph_adjL l);
void printMatrix(MGraph_relaM m);
MGraph_adjM RelaMtoAdjM(MGraph_relaM m);
void printAdjM(MGraph_adjM g2);

int main()
{
    // 1、读入邻接矩阵文件
    char filename[] = "D:\\Cfiles\\graph.txt";
    MGraph_adjM g = createMGraph(filename);

    // 2、将邻接矩阵转换为邻接表并打印
    MGraph_adjL l = adjMtoList(g);
    printf("图的邻接表形式表现为:\n");
    printList(l);
    printf("\n");

    // 3、将邻接矩阵转换为关联矩阵并打印
    MGraph_relaM m = adjMtoRelaM(l);;
    printf("关联矩阵为:\n");
    printMatrix(m);
    printf("\n");

    // 4、将关联矩阵转换回邻接矩阵并打印
    MGraph_adjM g2 = RelaMtoAdjM(m);
    printf("转换回邻接矩阵为:\n");
    printAdjM(g2);

    return 0;
}

MGraph_adjM createMGraph(char* filename)
{
    MGraph_adjM g;
    g.vexnum = g.arcnum = 0;
    FILE* fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("打开文件错误");
        return g;
    }
    fscanf(fp, "%d\n", &g.vexnum);
    for (int i = 0; i < g.vexnum; i++)
    {
        g.data[i] = 'A' + i;
        for (int j = 0; j < g.vexnum; j++)
        {
            fscanf(fp, "%d ", &g.arc[i][j]);
            if (g.arc[i][j] == 1)
            {
                g.arcnum++;
            }
        }
        fscanf(fp, "\n");
    }
    fclose(fp);
    return g;
}

MGraph_adjL adjMtoList(MGraph_adjM g)
{
    MGraph_adjL l;
    l.vexnum = g.vexnum;
    l.arcnum = g.arcnum;
    for (int k = 0; k < g.vexnum; k++)
    {
        l.adjList[k].data = g.data[k];
        l.adjList[k].firstArc = NULL;
    }
    for (int i = 0; i < l.vexnum; i++)
    {
        for (int j = 0; j < l.vexnum; j++)
        {
            if (g.arc[i][j] == 1)
            {
                ArcNode* arc = (ArcNode*)malloc(sizeof(ArcNode));
                arc->adjvex = j;
                arc->nextarc = NULL;

                if (l.adjList[i].firstArc == NULL)
                {
                    l.adjList[i].firstArc = arc;
                }
                else
                {
                    ArcNode* current = l.adjList[i].firstArc;
                    while (current->nextarc != NULL)
                    {
                        current = current->nextarc;
                    }
                    current->nextarc = arc;
                }
            }
        }
    }
    return l;
}

void printList(MGraph_adjL l)
{
    for (int i = 0; i < l.vexnum; i++)
    {
        printf("%c ", l.adjList[i].data);
        ArcNode* current = l.adjList[i].firstArc;
        while (current != NULL)
        {
            printf("->%c ", l.adjList[current->adjvex].data);
            current = current->nextarc;
        }
        printf("\n");
    }
}

MGraph_relaM adjMtoRelaM(MGraph_adjL l)
{
    MGraph_relaM m;
    m.row = l.vexnum;
    m.col = l.arcnum;
    for (int i = 0; i < m.row; i++)
    {
        for (int j = 0; j < m.col; j++)
        {
            m.RelaM[i][j] = 0;
        }
    }
    int arc = 0;
    for (int i = 0; i < m.row; i++)
    {
        ArcNode* current = l.adjList[i].firstArc;
        while (current != NULL)
        {
            m.RelaM[i][arc] = 1;
            m.RelaM[current->adjvex][arc++] = -1;
            current = current->nextarc;
        }
    }
    return m;
}

void printMatrix(MGraph_relaM m)
{
    for (int i = 0; i < m.row; i++)
    {
        for (int j = 0; j < m.col; j++)
        {
            printf("%2d ", m.RelaM[i][j]);
        }
        printf("\n");
    }
}

MGraph_adjM RelaMtoAdjM(MGraph_relaM m)
{
    MGraph_adjM g2;
    g2.vexnum = m.row;
    g2.arcnum = m.col;
    for (int i = 0; i < m.col; i++)
    {
        g2.data[i] = 'A' + i;
        for (int j = 0; j < m.col; j++)
        {
            g2.arc[i][j] = 0;
        }
        int row, col = 0;
        for (int k = 0; k < m.row; k++)
        {
            if (m.RelaM[k][i] == 1)
            {
                row = k;
            }
            if (m.RelaM[k][i] == -1)
            {
                col = k;
            }
        }
        g2.arc[row][col] = 1;
    }
    return g2;
}

void printAdjM(MGraph_adjM g2)
{
    for (int i = 0; i < g2.vexnum; i++)
    {
        for (int j = 0; j < g2.vexnum; j++)
        {
            printf("%d ", g2.arc[i][j]);
        }
        printf("\n");
    }
}
输入文本:
5
0 1 0 1 0
0 0 0 0 1
0 1 0 1 1
0 0 0 0 0 
0 0 0 0 0

运行结果:
图的邻接表形式表现为:
A ->B ->D
B ->E
C ->B ->D ->E
D
E

关联矩阵为:
 1  1  0  0  0  0
-1  0  1 -1  0  0
 0  0  0  1  1  1
 0 -1  0  0 -1  0
 0  0 -1  0  0 -1

转换回邻接矩阵为:
0 1 0 1 0
0 0 0 0 1
0 1 0 1 1
0 0 0 0 0
0 0 0 0 0

D:\专业学习\大二下\DS\DSweek12\Debug\DSweek12.exe (进程 17180)已退出,代码为 0。
按任意键关闭此窗口. . .

2、分别基于DFS和BFS实现图的遍历。 

下面给出DFS递归算法的程序: 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 100

typedef struct MGraph_adjM
{
    char data[MAX_VERTEX_NUM];
    int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
}MGraph_adjM;

typedef struct ArcNode
{
    int adjvex;
    struct ArcNode* nextarc;
}ArcNode;
typedef struct VNode
{
    char data;
    int info;
    ArcNode* firstArc;
}VNode;
typedef struct MGraph_adjL
{
    VNode adjList[MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
}MGraph_adjL;

MGraph_adjM createMGraph(char* filename);
MGraph_adjL adjMtoList(MGraph_adjM g);
void traverse(MGraph_adjL l);
void DFS(VNode anode, MGraph_adjL* l);

int main()
{
    // 1、读入邻接矩阵文件
    char filename[] = "D:\\Cfiles\\graph.txt";
    MGraph_adjM g = createMGraph(filename);

    // 2、将邻接矩阵转换为邻接表
    MGraph_adjL l = adjMtoList(g);

    // 3、图的遍历
    traverse(l);

    return 0;
}

MGraph_adjM createMGraph(char* filename)
{
    MGraph_adjM g;
    g.vexnum = g.arcnum = 0;
    FILE* fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("打开文件错误");
        return g;
    }
    fscanf(fp, "%d\n", &g.vexnum);
    for (int i = 0; i < g.vexnum; i++)
    {
        g.data[i] = 'A' + i;
        for (int j = 0; j < g.vexnum; j++)
        {
            fscanf(fp, "%d ", &g.arc[i][j]);
            if (g.arc[i][j] == 1)
            {
                g.arcnum++;
            }
        }
        fscanf(fp, "\n");
    }
    fclose(fp);
    return g;
}

MGraph_adjL adjMtoList(MGraph_adjM g)
{
    MGraph_adjL l;
    l.vexnum = g.vexnum;
    l.arcnum = g.arcnum;
    for (int k = 0; k < g.vexnum; k++)
    {
        l.adjList[k].data = g.data[k];
        l.adjList[k].info = 0;
        l.adjList[k].firstArc = NULL;
    }
    for (int i = 0; i < l.vexnum; i++)
    {
        for (int j = 0; j < l.vexnum; j++)
        {
            if (g.arc[i][j] == 1)
            {
                ArcNode* arc = (ArcNode*)malloc(sizeof(ArcNode));
                arc->adjvex = j;
                arc->nextarc = NULL;

                if (l.adjList[i].firstArc == NULL)
                {
                    l.adjList[i].firstArc = arc;
                }
                else
                {
                    ArcNode* current = l.adjList[i].firstArc;
                    while (current->nextarc != NULL)
                    {
                        current = current->nextarc;
                    }
                    current->nextarc = arc;
                }
            }
        }
    }
    return l;
}

void traverse(MGraph_adjL l)
{
    for (int i = 0; i < l.vexnum; i++)
    {
        if (!l.adjList[i].info)
        {
            DFS(l.adjList[i], &l);
        }
    }
}

void DFS(VNode anode, MGraph_adjL* l)
{
    printf("%c ", anode.data);
    ArcNode* p = anode.firstArc;
    while (p != NULL)
    {
        if (!l->adjList[p->adjvex].info)
        {
            l->adjList[p->adjvex].info = 1;
            DFS(l->adjList[p->adjvex], l);
        }
        p = p->nextarc;
    }
}
运行结果:

A B E D C
D:\专业学习\大二下\DS\DSweek12\Debug\DSweek12.exe (进程 17944)已退出,代码为 0。
按任意键关闭此窗口. . .

非递归DFS以及BFS算法类似,只要换个函数就行,不再赘述。

3、编程实现普里姆算法和克鲁斯卡尔算法

Prim算法:

#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM 20
#define INFINITY 65535

// 邻接矩阵存储结构
typedef struct MGraph
{
    char data[MAX_NUM];
    int arc[MAX_NUM][MAX_NUM];
    int vexnum, arcnum;
} MGraph;

MGraph createGraph(char *filename);
void Prim(MGraph g);

int main()
{
    // 1、读入邻接矩阵文件,创建图
    char *filename = "MGraph_adjM.txt";
    MGraph g = createGraph(filename);

    // 2、Prim算法创建最小生成树
    Prim(g);

    return 0;
}

MGraph createGraph(char *filename)
{
    MGraph g;
    g.vexnum = g.arcnum = 0;
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("打开文件错误\n");
        return g;
    }
    fscanf(fp, "%d\n", &g.vexnum);
    for (int i = 0; i < g.vexnum; i++)
    {
        g.data[i] = 'A' + i;
        for (int j = 0; j < g.vexnum; j++)
        {
            fscanf(fp, "%d ", &g.arc[i][j]);
            if (g.arc[i][j] > 0)
            {
                g.arcnum++;
            }
        }
        fscanf(fp, "\n");
    }
    g.arcnum /= 2;
    fclose(fp);
    return g;
}

void Prim(MGraph g)
{
    int lowcost[MAX_NUM]; // 存储顶点到集合U的最短边权值
    int adjvex[MAX_NUM]; // 存储最短边终点的下标
    for (int i = 1; i < g.vexnum; i++)
    {
        lowcost[i] = g.arc[0][i]; 
        adjvex[i] = 0;
    }
    for (int i = 1; i < g.vexnum; i++)
    {
        int min = INFINITY;
        int k = 0;
        for (int j = 1; j < g.vexnum; j++)
        {
            if (lowcost[j] != 0 && lowcost[j] < min)
            {
                min = lowcost[j];
                k = j; 
            }
        }// 找出剩余顶点中权值最小的边
        printf("%c -> %c : %d\n", g.data[adjvex[k]], g.data[k], min); // 打印最小生成树的边和权值    
        lowcost[k] = 0; // 将该顶点加入集合U
        // 更新lowcost和adjvex数组
        for (int j = 1; j < g.vexnum; j++)
        {
            if (lowcost[j] != 0 && g.arc[k][j] < lowcost[j])
            {
                lowcost[j] = g.arc[k][j];
                adjvex[j] = k;
            }
        }
    }
}
输入邻接矩阵:
6
0 6 1 5 65535 65535
6 0 5 65535 3 65535
1 5 0 5 6 4
5 65535 5 0 65535 2
65535 3 6 65535 0 6
65535 65535 4 2 6 0
#65535用来表示无限大

运行结果:
A -> C : 1
C -> F : 4
F -> D : 2
C -> B : 5
B -> E : 3

kruskal算法不再赘述。 

4、编程实现迪杰斯特拉算法 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
#include<memory.h>
#define MAX_NUM 100
#define INFINITY 65535

typedef struct
{
    char data[MAX_NUM];
    int arc[MAX_NUM][MAX_NUM];
    int vexnum;
    int arcnum;
} MGraph;

MGraph createGraph(char* filename);
void Dijkstra(MGraph g);
void printPath(MGraph g, int* dist, int* pre);

int main()
{
    char* filename = "MGraph_adjM.txt";
    MGraph g = createGraph(filename);
    Dijkstra(g);
    return 0;
}

MGraph createGraph(char* filename)
{
    MGraph g;
    g.vexnum = g.arcnum = 0;
    memset(g.arc, 0, sizeof(g.arc));
    memset(g.data, 0, sizeof(g.data)); // 初始化 g.data 数组为 '\0'
    FILE* fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("打开文件错误\n");
        return g;
    }
    fscanf(fp, "%d\n", &g.vexnum);
    for (int i = 0; i < g.vexnum; i++)
    {
        g.data[i] = 'A' + i;
        for (int j = 0; j < g.vexnum; j++)
        {
            fscanf(fp, "%d ", &g.arc[i][j]);
            if (g.arc[i][j] > 0)
            {
                g.arcnum++;
            }
        }
        fscanf(fp, "\n");
    }
    g.arcnum /= 2;
    fclose(fp);
    return g;
}

void Dijkstra(MGraph g)
{
    int dist[MAX_NUM];
    int pre[MAX_NUM];
    for (int k = 0; k < g.vexnum; k++)
    {
        dist[k] = g.arc[0][k];
        pre[k] = -1;
    }

    for (int i = 0; i < g.vexnum; i++)
    {
        int MIN = INFINITY;
        int m = -1;
        for (int j = 1; j < g.vexnum; j++)
        {
            if (dist[j] < MIN && pre[j] == -1)
            {
                MIN = dist[j];
                m = j;
            }
        }
        if (m == -1)
        {
            break;
        }
        pre[m] = 0;
        for (int j = 0; j < g.vexnum; j++)
        {
            int temp_distance = dist[m] + g.arc[m][j];
            if (g.arc[m][j] != 0 && temp_distance < dist[j])
            {
                dist[j] = temp_distance;
                pre[j] = m; 
                m = j;
            }
        }
    }

    printPath(g, dist, pre);
}

void printPath(MGraph g, int* dist, int* pre)
{
    for (int i = 1; i < g.vexnum; i++)
    {
        char* stack = (char*)malloc(g.vexnum * sizeof(char));
        memset(stack, 0, g.vexnum * sizeof(char));
        int top = -1;
        printf("%c到V0的最短路径长度为:%2d,", g.data[i], dist[i]);
        int temp = i;
        while (pre[temp] != -1)
        {
            stack[++top] = g.data[pre[temp]];
            temp = pre[temp];
        }
        printf("具体路径为:");
        while (top != -1)
        {
            printf("%c -> ", stack[top--]);
        }
        printf("%c\n", g.data[i]);
        free(stack);
    }
}
输入矩阵:
7
1 13 8 65535 30 65535 32
65535 0 65535 65535 65535 9 7
65535 65535 0 5 65535 65535 65535
65535 65535 65535 0 6 65535 65535
65535 65535 65535 65535 0 2 65535
65535 65535 65535 65535 65535 0 17
65535 65535 65535 65535 65535 65535 0

运行结果:
B到V0的最短路径长度为:13,具体路径为:A -> B
C到V0的最短路径长度为: 8,具体路径为:A -> C
D到V0的最短路径长度为:13,具体路径为:A -> C -> D
E到V0的最短路径长度为:19,具体路径为:A -> C -> D -> E
F到V0的最短路径长度为:21,具体路径为:A -> C -> D -> E -> F
G到V0的最短路径长度为:20,具体路径为:A -> B -> G

D:\专业学习\大二下\DS\DSweek12\Debug\DSweek12.exe (进程 14216)已退出,代码为 0。
按任意键关闭此窗口. . .

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值