数据结构:有向无环图题解

1. (程序题) 有向无环图

对有向无环图,按照有向图给出的次序关系,将图中顶点排成一个线性序列,对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系。由此所得顶点的线性序列称之为拓扑有序序列。

输入有向图的相关信息,若图中无环,输出其拓扑有序序列,否则输出“此有向图不是有向无环图”。

因拓扑有序序列不唯一,故要求如下:

(1)使用邻接表存储图。邻接表的每个链表中,要求按顶点的序号从大到小排列;

(2)使用栈辅助操作。初始时,入度为0的顶点入栈时,也按顶点的序号从大到小的顺序入栈。

输入说明:

输入有向图的顶点数、边数、各顶点的值,各边的信息。

输出说明:

若有向图中无环,输出其拓扑有序序列,否则输出“此有向图不是有向无环图”。
拓扑有序序列的每个顶点数据后有一个空格。

输入样例:
6 8
1 2 3 4 5 6
1 2
1 3
1 4
3 2
3 5
4 5
6 4
6 5
输出样例:
1 3 2 6 4 5

代码实现

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>

#define MAX_VERTICES 50 //最大顶点数
#define GraphNodeDataType char //顶点类型

//邻接表节点
typedef struct GraphNode {
	GraphNodeDataType vertex;//顶点
	struct GraphNode* next;
}GraphNode;

//图结构
typedef struct Graph {
	GraphNode* adjList[MAX_VERTICES];//邻接表数组
	GraphNodeDataType vertex[MAX_VERTICES];//顶点数组
	int indegree[MAX_VERTICES]; // 存储每个顶点的入度
	int numVertices; // 图中顶点数
	int numEdges; // 图中边数
}Graph;

//创建节点
GraphNode* createNode(char vertex) {
	GraphNode* newNode = (GraphNode*)malloc(sizeof(GraphNode));
	assert(newNode);

	newNode->next = NULL;
	newNode->vertex = vertex;

	return newNode;
}

//创建图
Graph* createGraph(int numVertices) {
	Graph* graph = (Graph*)malloc(sizeof(Graph));
	assert(graph);

	graph->numVertices = numVertices;
	graph->numEdges = 0;

	for (int i = 0; i < numVertices; i++) {
		graph->adjList[i] = NULL;// 邻接表数组初始化为空
		graph->indegree[i] = 0;// 入度数组初始化为0
	}

	return graph;
}

int FindIndex(Graph* graph, char val) {
	for (int i = 0; i < graph->numVertices; i++) {
		if (graph->vertex[i] == val) {
			return i;
		}
	}
	return -1;
}

void addEdge(Graph* graph, char src, char dest) {
	GraphNode* newNode = createNode(dest);//存储终点
	int srcIndex = FindIndex(graph,src);
	int destIndex = FindIndex(graph, dest);

	newNode->next = graph->adjList[srcIndex];
	graph->adjList[srcIndex] = newNode;

	graph->indegree[destIndex]++;// 更新终点的入度
	graph->indegree[destIndex];// 更新图的边数
}

//拓扑排序
void topologicalSort(Graph* graph) {
	int visited[MAX_VERTICES] = { 0 };// 记录顶点是否被访问过的数组
	int stack[MAX_VERTICES];//模拟栈
	int top = -1;//栈顶指针

	//将入度为0的顶点入栈
	for (int i = graph->numVertices - 1; i >= 0; --i) {
		if (graph->indegree[i] == 0) {
			stack[++top] = i;//顶点入栈
			visited[i] = 1;//标记顶点为已访问
		}
	}
	int count = 0;//记录已输出的顶点数
	while (top != -1) {
		int vertexIndex = stack[top--];//出栈
		printf("%c ", graph->vertex[vertexIndex]);
		count++;

		// 遍历该顶点的邻居节点
		GraphNode* tmp = graph->adjList[vertexIndex];//获取当前顶点的邻接链表头指针
		while (tmp != NULL) {
			GraphNodeDataType adjVertex = tmp->vertex;// 获取邻居顶点值
			int adjVertexIndex = FindIndex(graph,adjVertex);
			graph->indegree[adjVertexIndex]--;// 更新邻居顶点的入度

			// 如果邻居顶点入度为0且未访问过
			if (graph->indegree[adjVertexIndex] == 0 && !visited[adjVertexIndex]) {
				stack[++top] = adjVertexIndex;// 将邻居顶点入栈
				visited[adjVertexIndex] = 1;// 标记顶点为已访问
			}
			tmp = tmp->next;
		}
	}

	if (count != graph->numVertices) {
		printf("此有向图不是有向无环图\n");
	}

}



int main() {
	int numVertices = 0;
	int numEdges = 0;
	scanf("%d %d", &numVertices, &numEdges);

	Graph* graph = createGraph(numVertices);

	for (int i = 0; i < numVertices; i++) {
		char value = 0;
		scanf(" %c", &graph->vertex[i]);
	}

	for (int i = 0; i < numEdges; ++i) { // 输入每条边的起点和终点
		char src = 0, dest = 0;
		scanf(" %c %c", &src, &dest);
		addEdge(graph, src, dest); // 添加边,注意顶点值从a开始,转换为数组下标从0开始
	}
	topologicalSort(graph); // 进行拓扑排序
}

2. (程序题)

输入有向无环图的相关信息,求关键路径。

算法思路:

(1)使用邻接表存储图。邻接表的每个链表中,要求按顶点的序号从大到小排列;

(2)求DAG图的拓扑排序序列,使用栈辅助操作,初始时,入度为0的顶点入栈时,也按顶点的序号从大到小的顺序入栈;

(3)求每个事件的最早发生时间ve(i);

(4)求每个事件的最迟发生时间vl(i);

(5)求每个活动的最早开始时间e(i)和最迟开始时间l(i),若e(i)等于l(i),则此活动为关键活动。

输入说明:

输入有向图的顶点数、边数、各顶点的值,各边的信息。

输出说明:

输出所有关键活动,两个顶点用逗号间隔,且用尖括号括起来。所有标点符号为英文标点符号。

注意,因邻接表中链表内的按顶点的序号从大到小排列,所以会有如下输出顺序:

            ......

            <4,7>

            <4,6>

            ......

输入样例:
9 11
0 1 2 3 4 5 6 7 8
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4

输出样例:
<0,1>
<1,4>
<4,7>
<4,6>
<6,8>
<7,8>

代码实现

#include<stdio.h>
#include<stdlib.h>
#define MAX_NODES 100       // 定义最大节点数
#define STACK_SIZE 20       // 定义栈的最大容量
// 定义边节点结构

typedef struct EdgeNode {

    int targetIndex;        // 目标节点索引

    int weight;             // 权重

    struct EdgeNode* next;  // 指向下一个边节点

} EdgeNode;



// 定义顶点节点结构

typedef struct VertexNode {

    char label;             // 顶点的数据(标签)

    EdgeNode* edges;        // 指向第一个边节点

} VertexNode, AdjacencyList[MAX_NODES];



// 定义图结构

typedef struct {

    AdjacencyList vertices; // 顶点数组

    int numVertices, numEdges; // 顶点数和边数

} Graph;


// 定义栈结构

typedef struct {

    int* top, * base;       // 栈顶和栈底指针

} Stack;



// 定义关键路径边结构

typedef struct {

    char startVertex, endVertex; // 边的起点和终点

} CriticalEdge;



// 全局栈指针
Stack* globalStack = NULL;

// 查找节点索引

int findVertexIndex(Graph* graph, char label) {

    int i = 0;

    while (graph->vertices[i].label != label) i++;

    return i;

}



// 从栈中弹出一个元素

int pop(Stack* stack) {

    int value;

    stack->top--;

    value = *stack->top;

    globalStack = stack;

    return value;

}


// 将一个元素压入栈中

Stack* push(Stack* stack, int value) {

    *stack->top = value;

    stack->top++;

    return stack;

}

int main() {
    // 变量声明
    Graph* graph;
    EdgeNode* edge;
    CriticalEdge* criticalEdges;
    Stack* stack1, * stack2;


    // 初始化栈

    stack1 = (Stack*)malloc(sizeof(Stack));

    stack1->base = stack1->top = (int*)malloc(sizeof(int) * STACK_SIZE);

    stack2 = (Stack*)malloc(sizeof(Stack));

    stack2->base = stack2->top = (int*)malloc(sizeof(int) * STACK_SIZE);



    char tempChar, startLabel, endLabel;

    int i, j, k, * inDegrees, vertexCount = 0, tempArray[100] = { 0 }, z = 0, weight, * earliest, * latest, duration, earliestTime, latestTime, criticalEdgeCount = 0, tempCount = 0;
    // 初始化图

    graph = (Graph*)malloc(sizeof(Graph));

    scanf("%d %d", &graph->numVertices, &graph->numEdges);

    scanf("%c", &tempChar); // 读取换行符

    // 初始化入度数组和最早开始时间数组

    inDegrees = (int*)malloc(sizeof(int) * (graph->numVertices));

    earliest = (int*)malloc(sizeof(int) * (graph->numVertices));

    latest = (int*)malloc(sizeof(int) * (graph->numVertices));

    criticalEdges = (CriticalEdge*)malloc(sizeof(CriticalEdge) * (graph->numEdges));

    for (i = 0; i < graph->numVertices; i++) inDegrees[i] = earliest[i] = 0;

    // 读取顶点数据

    for (i = 0; i < graph->numVertices; i++) {

        scanf("%c%c", &graph->vertices[i].label, &tempChar);

        graph->vertices[i].edges = NULL;

    }

    // 读取边数据
    for (i = 0; i < graph->numEdges; i++) {
        scanf("%c %c %d", &startLabel, &endLabel, &weight);
        scanf("%c", &tempChar); // 读取换行符
        j = findVertexIndex(graph, startLabel); // 获取起点索引
        k = findVertexIndex(graph, endLabel);   // 获取终点索引

        edge = (EdgeNode*)malloc(sizeof(EdgeNode)); // 创建新边节点
        edge->targetIndex = k;
        edge->weight = weight;
        edge->next = graph->vertices[j].edges;
        graph->vertices[j].edges = edge;
    }



    // 计算每个节点的入度

    for (i = 0; i < graph->numVertices; i++) {

        for (edge = graph->vertices[i].edges; edge; edge = edge->next) {

            inDegrees[edge->targetIndex]++;
        }
    }



    // 初始化栈,将入度为0的节点入栈

    for (i = graph->numVertices - 1; i >= 0; i--) {

        if (inDegrees[i] == 0) stack1 = push(stack1, i);

    }

    // 拓扑排序

    while (stack1->base != stack1->top) {

        j = pop(stack1);

        stack1 = globalStack;

        stack2 = push(stack2, j);

        vertexCount++;



        // 更新最早开始时间

        for (edge = graph->vertices[j].edges; edge; edge = edge->next) {

            k = edge->targetIndex;

            if (--inDegrees[k] == 0) stack1 = push(stack1, k);

            if (earliest[j] + edge->weight > earliest[k]) earliest[k] = earliest[j] + edge->weight;

        }

    }



    // 检查是否为有向无环图

    if (vertexCount < graph->numVertices) {

        printf("此有向图不是有向无环图");

    }
    else {

        for (i = 0; i < graph->numVertices; i++) latest[i] = earliest[graph->numVertices - 1];



        // 计算最晚开始时间

        while (stack2->top != stack2->base) {

            for (j = pop(stack2), edge = graph->vertices[j].edges; edge; edge = edge->next) {

                k = edge->targetIndex;

                if (latest[k] - edge->weight < latest[j]) latest[j] = latest[k] - edge->weight;

            }

        }
        // 识别关键路径上的边
        for (j = 0; j < graph->numVertices; j++) {

            for (edge = graph->vertices[j].edges; edge; edge = edge->next) {

                k = edge->targetIndex;

                duration = edge->weight;

                earliestTime = earliest[j];

                latestTime = latest[k] - duration;

                if (earliestTime == latestTime) {

                    criticalEdges[criticalEdgeCount].startVertex = graph->vertices[j].label;

                    criticalEdges[criticalEdgeCount].endVertex = graph->vertices[k].label;

                    criticalEdgeCount++;

                }

            }

        }

    }



    // 打印关键路径

    j = 0;

    while (j != graph->numVertices - 1) {

        for (i = 0; i < criticalEdgeCount; i++) {

            if (criticalEdges[i].startVertex == graph->vertices[j].label) {

                printf("<%c,%c>\n", criticalEdges[i].startVertex, criticalEdges[i].endVertex);

                weight = i;

                tempCount++;

            }

        }

        j = findVertexIndex(graph, criticalEdges[weight].endVertex);

    }

    if (criticalEdgeCount == tempCount);

    else {

        printf("<%c,%c>", criticalEdges[criticalEdgeCount - 1].startVertex, criticalEdges[criticalEdgeCount - 1].endVertex);

    }

    return 0;
}


结语

请给自己些耐心,不要急于求成。
山外青山楼外楼,莫把百尺当尽头。
保持空杯心态加油努力吧!


都看到这里啦!真棒(*^▽^*)

可以给作者一个免费的赞吗,这将会鼓励我继续创作,谢谢大家

如有纰漏或错误,欢迎指正


  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
算法数据结构它们分别涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、结构(有向图、无向等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解 PTA数据结构算法题目集部分题解,补充blog创建前的题解

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值