初试811-数据结构(C语言)

图✨

知识点:

  • 🤩

    • 基本概念

      • 主要性质

        • 1、线形表可以是空表,树可以是空树,但图不能是空图。图中顶点集V一定非空,但边集E可以为空 2、完全图中,无向图n(n-1)/2条边,有向图n(n-1)条边 3、子图中,并非V和E的任何子集都构成G的子图,因为这样的子集可能不是图,即某些边,关联的顶点不在子图中 4、无向图的连通图,有向图的强连通图,不要主观臆断

          • 针对2性质:考察需固定多少边,非连通图无向图至少需要多少个顶点,即求出该边数完全图的顶点+1即可; 若一个图有n个顶点,若边数小于n-1,则此图必是非连通图;

            • 确保为n个顶点的图为一个连通图,先去保证n-1为强连通图,然后加一条边即必连通了

          • 针对4性质:如果强连通图,某顶点只有出度,一定为单独一个强连通分量

          • 难题,求森林的树的个数,即根的个数,由于每棵树除根节点外的每个节点对应一条边连接双亲节点,即用结点数-边数=根结点数

    • 存储结构

      • 邻接矩阵

        • 主要性质,王道P211,6题

      • 邻接表

        • 主要性质

          • 1、若邻接表中有奇数个边表节点,则图为有向图 2、在有向图邻接表中,顶点v在边表出现的次数,等于v的入度

    • 图的遍历

      • 深度优先遍历

        • 类比树

          • 类似于树的先根遍历 不同于,有重复访问,需要设置visited[](只要多边就有可能出现多次访问)

        • 性质

          • 形成的深度生成树个数等于连通分量,等于调用DFS的次数

          • 具有工作栈的性质,深度为V

          • 可以用于判断有向图中是否存在回路(通过每次递归查看栈中是否存在相同元素)

      • 广度优先遍历

        • 类比树

          • 类似于树的层次遍历 不同于,有重复访问,需要设置visited[](只要多边就有可能出现多次访问)

        • 性质

          • 形成的广度生成树个数等于连通分量,等于调用BFS的次数

          • 需要内存为V的辅助队列

          • 当各边的权值相等时,可以解决单源最短路径问题

    • 图的应用

      • MST最小生成树

        • prim算法

          • 性质

            • 1、只能使用带权无向图,而带权有向图由于两个节点之间来和回的权重不一样,无法确定

          • 思路

            • 1、从一个点出发,寻找一颗权值和最小的树,即每次选者权值最小的边,直至所有的顶点都已经加入

        • Kruskal算法

          • 思路

            • 1、每次从最小边出发,所有顶点不形成回路

        • 性质⚠️

          • 1、只要无向连通图中没有权值相同的边,则其最小生成树唯一 2、若最小生成树不唯一,一定存在权值相等的边,但未必是权值最小的边(设想图有n-1条边且权值最小,即唯一),逆命题:如果存在权值相等的边,最小生成树不一定不唯一(即n-1条相等的边,MST唯一) 3、MST的点n-1条边不能保证是图中的权值最小的n-1条边,因为该n-1条边未必能使得图连通

      • 最短路径

        • Dijkstra

        • prime算法的比较:都是基于贪心算法 。得到的最小生成树,从某点出发到达另外一点的路径并不是最短的

          • 思路

            • 每次找出到原点距离最近且未加入集合的点,把它归入集合,同时以这个点为基础更新从源点到其他各点的距离

          • 性质

            • 1、适用于带权有向图,带权无向图 2、单源最短路径,(扩展:访问每个顶点即可得到每队顶点的路径长度,时间复杂度为v的3次方)

        • Floyd

          • 每次更新数组

            • 可求带权图各顶点的最短路径长度,时间复杂度v的3次方

        • BFS

          • 单源最短路径

      • 关键路径

        • AOE网中的边有权值

        • 性质

          • 1、由于关键路径运行前提,必须是无环图,并且在求关键路径的第一步,是拓扑排序,即可以判断是否存在环路(存在争议) 2、是从源点到汇点的路径长度最长的路径

        • 注意⚠️

          • 掌握步骤,然后认真算,可能出现多条关键路径

      • 有向无环图(DAG)

        • 描述含有公共子式的表达式

          • 在表达式的有向无环图表示中,不可能出现重复的操作数顶点

      • 拓扑序列

        • 概念

          • 1、从AOV网中选者一个没有前驱的顶点(入度为0)输出。(生成的序列不唯一) 2、从网中删除该顶点和所有以它为起点的有向边 3、重复1、2,直到当前AOV网为空或当前的网中不存在无前驱的顶点为止。后一种情况说明图中有环。

        • 注意⚠️

          • 可以使用DFS实现逆拓扑排序 P258,7题

          • 1、强连通图一定存在环,不能拓扑排序 2、有向图顶点不能排成一个拓扑排序,可以判断这个有向图,含有顶点数大于1的强连通分量

          • 有向无环图的拓扑排序唯一,并不能确定该图唯一

          • 使用邻接矩阵存储,上(下)三角矩阵,证明该拓扑序列存在,不一定唯一

Q&A:

1、设计一个算法计算邻接表表示的图中各个顶点的出度

//利用一个数组对应各个结点,保存出度的个数
void OutDegree(int *outdegree,graph *g){
    int i;
    ENode *p
    for(i=0;i<g->n;i++){
        outdegree[i]=0;        //初始化
    }

    for(i=0;i<g->n;i++){
        for(p=g->a[i];p;p=p->nextArc)
            outdegree[i]++;
    }
    
    

2、设计一个算法计算邻接表表示的图中各个顶点的入度

void InDegree(int *inDegree,LGraph *g)
{
    int i;
    ENode *p;
    for(i=0;i<g->n;i++){ 
        inDegree[i]=0;
    }
    for(i=0;i<g->n;i++)
    for(p=g->a[i];p;p=p->nextArc)
        inDegree[p->adjVex]++;
}
    
    

3、设计一个算法计算邻接表表示的图中任意顶点u的入度

int InDegree(int u,LGraph *g)
{
    int i;
    ENode *p;
    int count;
    if(u<0||u>g->n-1) return -1;
    for(i=0;i<g->n;i++){
        for(p=g->a[i];p;p=p->nextArc){
            if(p->adjVex==u)
                count++;
        }
    }
    return count;
}
    
    

4、设计一个算法计算邻接表表示的图中任意顶点u的出度 

struct ENode {
    int adjVex;      // 指向邻接点的编号
    ENode* nextArc;  // 指向下一个邻接点
};

struct LGraph {
    int n;          // 图的顶点数
    ENode* a[];     // 邻接表数组
};

int OutDegree(int u, LGraph *g) {
    if (u < 0 || u >= g->n-1) {
        return -1;  // 错误的顶点编号
    }

    int count = 0;
    ENode* p = g->a[u];

    while (p != null) {
        count++;            //遍历该数组,即是特定顶点的出度
        p = p->nextArc;
    }

    return count;
}

5、设图G以邻接矩阵表示,设计一个算法根据图G的邻接矩阵构建图G的邻接表 

// 向邻接表中添加边  
void AddEdge(GraphAdjList *G, int i, int j) {  
    EdgeNode *e = (EdgeNode *)malloc(sizeof(EdgeNode));  
    e->adjvex = j;  
    e->next = G->adjList[i].firstedge;  //头插法
    G->adjList[i].firstedge = e;  
    // 注意:如果是无向图,还需要添加G->adjList[j].firstedge到i的边  
    // 这里只处理有向图或单向邻接表的情况  
    //e->adjvex = i;
    //e->next = G->adjList[j].firstedge;
    //G->adjList[j].firstedge = e;
}  
  
// 根据邻接矩阵构建邻接表  
void BuildAdjList(GraphAdjList *G, int adjMatrix[100][100], int numVertices) {  
    InitGraph(G, numVertices);  
    for (int i = 0; i < numVertices; i++) {  
        for (int j = 0; j < numVertices; j++) {  
            if (adjMatrix[i][j] == 1) { // 假设1表示存在边  
                AddEdge(G, i, j);  
                // 如果是无向图,还需要AddEdge(G, j, i);  
            }  
        }  
    }  
}  

6、设图G以邻接表表示,设计一个算法根据图G的邻接表构建图G的邻接矩阵 

void BuildAdjMatrix(EdgeNode adjList[][MAX_VERTICES], int numVertices, int adjMatrix[MAX_VERTICES][MAX_VERTICES]) {  
    for (int i = 0; i < numVertices; i++) {  
        for (int j = 0; j < numVertices; j++) {  
            adjMatrix[i][j] = 0; // 初始化邻接矩阵  
        }  
    }  
  
    for (int i = 0; i < numVertices; i++) {  
        EdgeNode *p = adjList[i]; // 获取顶点i的边表头指针  
        while (p != NULL) {  
            adjMatrix[i][p->adjvex] = 1; // 标记边i->p->adjvex  
            // 对于无向图,还需要执行:adjMatrix[p->adjvex][i] = 1;  
            p = p->next; // 移动到下一个邻接点  
        }  
    }  
}  

7、设计一个算法求给定无向图的全部连通分量 

void findConnectedComponents(Graph *graph) {  
    int *visited = (int*)malloc(graph->numVertices, sizeof(int));  
    if (!visited) return;  //申请数组出错,直接返回
  
    for (int i = 0; i < graph->numVertices; i++) {  
        if (!visited[i]) {  
            printf("Connected component: ");  //输出连通分量
            DFS(graph, i, visited);      //DFS调用
            for (int j = 0; j < graph->numVertices; j++) {  
                if (visited[j]) {  
                    printf("%d ", j);  //在遍历一次输出一趟DFS后visited为true的数
                    visited[j] = 0; //重置visited数组,以便复用(可选,本就从未访问的循环)  
                }  
            }  
            printf("\n");  
        }  
    }  
    free(visited);  
}  

8、设图G以邻接矩阵表示,设计一个算法实现对图G的深度优先遍历

void DFS1(Graph *graph, int vertex, int *visited) {  
    visited[vertex] = 1;  
    EdgeNode *temp = graph->adjLists[vertex];  
    while (temp != NULL) {  //不等于NULL即有边
        int neighbor = temp->vertex;  
        // 如果该邻接顶点没有被访问过,则递归进行DFS
        if (!visited[neighbor]) {  
            DFS(graph, neighbor, visited);  
        }  
        // 移动到下一个邻接顶点
        temp = temp->next;  
    }  
}  //为邻接表存储结构


void DFS2(int G[MaxVertices][MaxVertices], int visited[], int vertex, int numVertices) {
    // 访问当前顶点并标记为已访问
    visited[vertex] = 1;
    
    // 遍历与当前顶点相邻的所有顶点
    for (int i = 0; i < numVertices; i++) {
        // 如果存在一条边并且邻接顶点还没有被访问
        if (G[vertex][i] == 1 && !visited[i]) {
            DFS(G, visited, i, numVertices); // 递归调用 DFS 访问该顶点
        }
    }
}

9、设图G以邻接矩阵表示,设计一个算法实现对图G的宽度优先遍历


void BFS(int adjMatrix[][100], int startVertex, int numVertices) {  
    // visited 数组,用于标记顶点是否已经访问过
    int visited[100] = {0};  // 假设最多有100个顶点,也可以动态分配
    queue q;       // 定义队列,存放待访问的顶点

    // 标记起始顶点已访问,并将其加入队列
    visited[startVertex] = 1;  
    q.Enqueue(startVertex);  

    // 当队列不为空时,继续进行广度优先搜索
    while (!q.empty()) {  
        // 取出队列头的顶点进行处理
        int currentVertex = q.Dequeue();

        // 遍历当前顶点的所有邻接顶点
        for (int i = 0; i < numVertices; ++i) {  
            // 如果有一条边连接当前顶点和顶点 i,并且 i 还没有被访问过
            if (adjMatrix[currentVertex][i] && !visited[i]) {  
                visited[i] = 1;       // 标记顶点 i 为已访问
                q.Enqueue(i);            // 将顶点 i 加入队列
            }  
        }  
    }  
}

10、设带权无向图G以邻接矩阵表示,设计一个算法实现Prim算法

#include <iostream>
#include <climits> // for INT_MAX
using namespace std;

const int MaxVertices = 100; // 假设图的最大顶点数为 100

// 查找未访问顶点中,离已生成树最近的顶点
int findMinVertex(int key[], bool mstSet[], int numVertices) {
    int minKey = INT_MAX, minVertex = -1;

    // 遍历所有顶点,找到权值最小的顶点
    for (int v = 0; v < numVertices; v++) {
        if (!mstSet[v] && key[v] < minKey) {
            minKey = key[v];
            minVertex = v;
        }
    }
    return minVertex;
}

// Prim 算法实现,生成最小生成树并返回总权值
void primMST(int G[MaxVertices][MaxVertices], int numVertices) {
    int parent[MaxVertices];    // 记录最小生成树的父节点
    int key[MaxVertices];       // 记录每个顶点到生成树的最小边权值
    bool mstSet[MaxVertices];   // 标记顶点是否已经包含在 MST 中

    // 初始化
    for (int i = 0; i < numVertices; i++) {
        key[i] = INT_MAX;       // 初始化所有顶点权值为无穷大
        mstSet[i] = false;      // 初始时所有顶点都不在 MST 中
    }

    // 从第 0 个顶点开始构造 MST
    key[0] = 0;                // 将第一个顶点的权值设为 0
    parent[0] = -1;            // 第 0 个顶点没有父节点(即起始点)

    // 构建最小生成树
    for (int count = 0; count < numVertices - 1; count++) {
        // 找到未被加入 MST 的顶点中,key 值最小的顶点
        int u = findMinVertex(key, mstSet, numVertices);
        mstSet[u] = true;       // 将顶点 u 加入 MST

        // 更新所有与 u 相邻的顶点的权值
        for (int v = 0; v < numVertices; v++) {
            // G[u][v] != 0 表示 u 和 v 有边
            // mstSet[v] 为 false 表示 v 尚未加入 MST
            // G[u][v] < key[v] 表示 u-v 边的权值小于 v 当前的 key 值
            if (G[u][v] && !mstSet[v] && G[u][v] < key[v]) {
                parent[v] = u;
                key[v] = G[u][v];
            }
        }
    }

    // 输出最小生成树的边及其权值
    cout << "Edge \tWeight\n";
    for (int i = 1; i < numVertices; i++) {
        cout << parent[i] << " - " << i << " \t" << G[i][parent[i]] << "\n";
    }
}

int main() {
    // 例子:带权无向图的邻接矩阵表示
    int G[MaxVertices][MaxVertices] = {
        {0, 2, 0, 6, 0},
        {2, 0, 3, 8, 5},
        {0, 3, 0, 0, 7},
        {6, 8, 0, 0, 9},
        {0, 5, 7, 9, 0}
    };

    int numVertices = 5; // 图的顶点数
    primMST(G, numVertices); // 运行 Prim 算法

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值