Data Structure C Code 7.2: Adjacency List and Breadth-First Traversal & 7.3: Prim's Algorithm and Dijkstra's Algorithm

learning target:

Review these two things

The adjacency list is equivalent to the compressed storage of the graph, and the traversal is the same as the graph.

The difference between the minimum spanning tree algorithm and the single-source shortest path algorithm is only two lines of code.

Learning tasks:

  1. copy code
  2. Learning Outcome Catalog

    1 Data Structure C Code 7.2: Adjacency List and Breadth-First Traversal

    1.1 All codes

    1.2 Test results

    27.3: Prim's Algorithm and Dijkstra's Algorithm

    2.1 All codes

    2.2 Test results

    2.3 Diagram


Code description:

  (1)  Adjacency list and breadth-first traversal

  1. The queue-side code is copied from 7.1.
  2. The code to construct the graph is also readily available.
  3. Convert matrix form to adjacency list form.
  4. For is used when traversing neighbors, which is essentially a while.

  (2) Prim's algorithm and Dijkstra's algorithm

  1. Both graph and network storage can use matrix. The former stores boolean values, while the latter stores weights.
  2. Prim is a minimum spanning tree, which corresponds to road construction; Dijkstra's algorithm is a single-source shortest path, which is applied to map routing.
  3. The time complexity of O(n2) is achieved by a greedy selection strategy and sophisticated preprocessing.
  4. It will be faster if you use an adjacency list.
  5. In fact, the time complexity can be optimized to O(nlogn), see   Zhang Xingyi's blog .

study-time:

2022.5.19

1 Data Structure C Code 7.2: Adjacency List and Breadth-First Traversal

1.1 All codes

#include <stdio.h>
#include <malloc.h>

#define MAXSIZE 10

int* visited;  

typedef struct graph
{
	int** connections;
	int numNodes;
}*graphPtr;

typedef struct graphQueue
{
	int *node;
	int front;
	int rear;
}*graphQueuePtr;

typedef struct AdjacencyNode
{
	int column;
	struct AdjacencyNode* next;
}*AdjacencyNodePtr;

typedef struct AdjacencyList
{
	int numNodes;
	struct AdjacencyNode* headers;
}*AdjacencyListPtr;

graphQueuePtr queueInit()
{
	graphQueuePtr resultQueue = (graphQueuePtr)malloc(sizeof(struct graphQueue));
	resultQueue->node = (int*)malloc(MAXSIZE * sizeof(int));
	
	resultQueue->front = 0;
	resultQueue->rear = 0;
	return resultQueue;
}

void enqueue(graphQueuePtr paraQueue,int paraData)
{
	if((paraQueue->rear + 1) % MAXSIZE == (paraQueue->front) % MAXSIZE)
	{
		printf("队列满了,无法入队!\n");
		return ;
	}
	
	paraQueue->node[(paraQueue->rear) % MAXSIZE] = paraData;
	paraQueue->rear = (paraQueue->rear + 1) % MAXSIZE;
}

int dequeue(graphQueuePtr paraQueue)
{
	if(paraQueue->front == paraQueue->rear)
	{
		printf("队列为空,无法出队!\n");
		return 0;
	}
	
	int temp =  paraQueue->node[(paraQueue->front) % MAXSIZE];
	paraQueue->front = (paraQueue->front + 1) % MAXSIZE;
	
	return temp;
}

void visitedInit(AdjacencyListPtr paraPtr)
{
	int i;
	visited = (int*)malloc(paraPtr->numNodes * sizeof(int));
	
	for(i = 0;i < paraPtr->numNodes;i ++)
	{
		visited[i] = 0;
	}
}
/**
 * @brief 初始化图
 * 
 * @param paraConnections 
 * @param paraNumNodes 
 * 
 * @return 
 */
graphPtr graphInit(int **paraConnections,int paraNumNodes)
{
	int i,j;
	graphPtr resultGraph = (graphPtr)malloc(sizeof(struct graph));
	resultGraph->numNodes = paraNumNodes;
	
	//这里如何给二维数组动态分配空间如果不明白可以参考我以前的二维数组文章 
	resultGraph->connections = (int**)malloc(resultGraph->numNodes * sizeof(int*));
	for(i = 0;i < resultGraph->numNodes;i ++)
	{
		resultGraph->connections[i] = (int*)malloc(resultGraph->numNodes * sizeof(int));
	}
	
	for(i = 0;i < resultGraph->numNodes;i ++)
		for(j = 0;j < resultGraph->numNodes;j ++)
	{
		resultGraph->connections[i][j] = paraConnections[i][j];
	}
	
	return resultGraph;
}
/**
 * @brief 把图转化为接邻列表
 * 
 * @param paraGraph 
 * 
 * @return 
 */
AdjacencyListPtr graphToAdjacency(graphPtr paraGraph)
{
	int i,j;
	int tempNodes = paraGraph->numNodes;
	//分配空间 
	AdjacencyListPtr resultList = (AdjacencyListPtr)malloc(sizeof(struct AdjacencyList));
	resultList->numNodes = tempNodes;
	resultList->headers = (AdjacencyNodePtr)malloc(tempNodes * sizeof(struct AdjacencyNode));
	
	/*p是用来跟踪当前位置的,所有不用分配空间q是用来开辟新的结点的,要分配空间*/ 
	AdjacencyNodePtr p,q;
	
	for(i = 0;i < tempNodes;i ++)
	{
		//每一次都指向下一个元素,即下一个链表的头结点 
		p = &(resultList->headers[i]);
		p->column = -1;
		p->next = NULL;
		
		for(j = 0;j < tempNodes;j ++)
		{
			if(paraGraph->connections[i][j] == 1)
			{
				q = (AdjacencyNodePtr)malloc(sizeof(struct AdjacencyNode));
				q->column = j;
				q->next = NULL;
				
				//链接过去,并让p跟踪 
				p->next = q;
				p = q;
			}
		}
	}
	
	return resultList;
}
/**
 * @brief 邻接列表普通遍历
 * 
 * @param paraList 
 */
void printAdjacencyList(AdjacencyListPtr paraList)
{
	printf("打印邻接列表:\n");
	//用p来跟踪,一个一个打印就好
	AdjacencyNodePtr p;
	int i;
	for(i = 0; i < paraList->numNodes;i ++)
	{
		//当p跟踪玩当前链表后,记得要把p指向下一个链表头结点的next
		p = paraList->headers[i].next;
		while(p != NULL)
		{
			printf("%d ",p->column);
			p = p->next;
		}
		printf("\n");
	}
}
/**
 * @brief 邻接列表广度优先搜索,和图的广度优先搜索一样
 * 
 * @param paraList 
 * @param paraNum 
 */
void wideTranverse(AdjacencyListPtr paraList,int paraNum)
{
	printf("邻接列表的广度优先搜索:\n");
	int temp;
	//初始化visited 
	visitedInit(paraList);
	//创建队列 
	graphQueuePtr tempQueue = queueInit();
	enqueue(tempQueue,paraNum);
	visited[paraNum] = 1;
	printf("%d ",paraNum);
	
	//还是用p跟踪 
	AdjacencyNodePtr p;
	while(tempQueue->front != tempQueue->rear)
	{
		temp = dequeue(tempQueue);
		//出队了就让p指向对应的这个链表的头部的next 
		p = paraList->headers[temp].next;
		//p == NULL就代表这个链表循环完了 
		while(p != NULL)
		{
			//如果没被访问过,就直接入队 
			if(visited[p->column] == 0)
			{
				enqueue(tempQueue,p->column);
				printf("%d ",p->column);
				visited[p->column] = 1;
			}
			
			//注意这个要写在外面,无论是否被访问了都要往后走 
			p = p->next;
		}
	}
	
}
/**
 * 广度遍历测试类
 */
void testGraphTranverse() 
{
	int i, j;
	int myGraph[5][5] = 
	{ 
	{0, 1, 0, 1, 0},
	{1, 0, 1, 0, 1}, 
	{0, 1, 0, 1, 1}, 
	{1, 0, 1, 0, 0}, 
	{0, 1, 1, 0, 0}
	};
	int** tempPtr;
	printf("准备数据\n");
	
	tempPtr = (int**)malloc(5 * sizeof(int*));
	for (i = 0; i < 5; i ++) 
	{
		tempPtr[i] = (int*)malloc(5 * sizeof(int));
	}
	
	for (i = 0; i < 5; i ++) 
	{
		for (j = 0; j < 5; j ++) 
		{
			tempPtr[i][j] = myGraph[i][j];
		}
	}
	
	printf("准备完毕\r\n");
	
	graphPtr tempGraphPtr = graphInit(tempPtr,5);
	AdjacencyListPtr tempListPtr = graphToAdjacency(tempGraphPtr);
	
	printAdjacencyList(tempListPtr);
	
	wideTranverse(tempListPtr, 4);
}

int main(){
	testGraphTranverse();
	return 1;
}

1.2 Test results

准备数据
准备完毕
打印邻接列表:
1 3
0 2 4
1 3 4
0 2
1 2
邻接列表的广度优先搜索:
4 1 2 0 3

27.3: Prim's Algorithm and Dijkstra's Algorithm

2.1 All codes

#include <stdio.h>
#include <malloc.h>

//表示无穷 
#define MAX 100000

typedef struct net
{
	int **weights;
	int numNode;
}*netPtr;

/**
 * @brief 初始化图
 * 
 * @param paraArray 
 * @param paraNodes 
 * 
 * @return 
 */
netPtr netInit(int paraNodes,int **paraArray)
{
	int i,j;
	
	//全是动态的,都要malloc 
	netPtr resultNet = (netPtr)malloc(sizeof(struct net));
	resultNet->weights = (int**)malloc(paraNodes*sizeof(int*));
	for(i = 0;i < paraNodes;i ++)
	{
		resultNet->weights[i] = (int*)malloc(paraNodes*sizeof(int));
	}
	
	//纯纯拷贝 
	resultNet->numNode = paraNodes;
	for(i = 0;i < paraNodes;i ++)
		for(j = 0;j < paraNodes;j ++)
			resultNet->weights[i][j] = paraArray[i][j];
	
	return resultNet;
}

/**
 * @brief Prim算法
 * 
 * @param paraAlgorithm 后面一个参数单纯是为了选择用哪个算法而已 
 * @param paraNet 
 */
void PrimOrDijkstra(netPtr paraNet,int paraAlgorithm)
{
	int i,j,tempBestNode,minDistance,resultCost;
	int *visited,*parent,*distant;
	int numNodes = paraNet->numNode;
	
	//这里是用来存放是否访问过、父节点、最短距离的数组 
	visited = (int*)malloc(sizeof(int) * paraNet->numNode);
	parent = (int*)malloc(sizeof(int) * paraNet->numNode);
	distant = (int*)malloc(sizeof(int) * paraNet->numNode);
	
	//我们默认起始点为0开始构建。 
	int source = 0;
	
	//初始化
	for(i = 0;i < paraNet->numNode;i ++)
	{
		visited[i] = 0;
		parent[i] = source;
		distant[i] = paraNet->weights[source][i];
	}
	
	distant[source] = 0;
	visited[source] = 1;
	parent[source] = -1;
	
	//算法开始,第一个结点已经在网中了,我们只需循环n-1次 
	for(i = 0;i < numNodes-1;i ++)
	{
		/*第一步,即第一个循环,实现要找到与目前结点
		项链且路径最短的那个结点为最佳结点*/
		minDistance = MAX;
		for(j = 0;j < numNodes;j ++)
		{
			if(distant[j] < minDistance && visited[j] == 0)
			{
				minDistance = distant[j];
				tempBestNode = j;
			}
		}
		//找到之后就表示他以及被放问过了,即已经入网了 
		visited[tempBestNode] = 1;
		
		/*第二步,是最佳结点要帮组其他结点了*/
		for(j = 0;j < numNodes;j ++)
		{
			//在网中直接下一个 
			if(visited[j] == 1)
			{
				continue;
			}
			
			/*后面的话初始化的时候,我们把图里面的0表示不连通直接转换为MAX无穷大了
			所有,无穷大即不连通的话直接下一个*/ 
			if(paraNet->weights[tempBestNode][j] >= MAX)
			{
				continue; 
			}
			
			//这里算法出现一点点分支 0是最短路径,1是最小生成树 
			if(paraAlgorithm == 0)
			{
				/*最短路径呢,是这样的:如果从始结点到最佳结点加上最佳结点到j结点
				这两者的距离比始节点直接到j结点的距离还要短,那么就肯定选第一种方法啊
				所以说就开始改变到j的最小距离,改变j的父节点为当前最佳结点。*/ 
				for(j = 0;j < numNodes;j ++) 
				{
					if(distant[tempBestNode] + paraNet->weights[tempBestNode][j] < distant[j])
					{
						distant[j] = paraNet->weights[tempBestNode][j] + distant[tempBestNode];
						parent[j] = tempBestNode;
					}
				}
			}
			
			if(paraAlgorithm == 1)
			{
				/*最小生成唯一不同的,是不用加上从始结点到最佳结点的距离了,我们要的是最小代价构建网
				所以不用加上之前的距离了,有更小距离的就可以直接帮助*/ 
				for(j = 0;j < numNodes;j ++) 
				{
					if(paraNet->weights[tempBestNode][j] < distant[j])
					{
						distant[j] = paraNet->weights[tempBestNode][j];
						parent[j] = tempBestNode;
					}
				}
			}
			
		}
	}
	
	printf("所有节点的父节点依次为: \n");
	//打印父节点 
	for(i = 0; i < numNodes; i++) 
	{
		printf("%d ", parent[i]);
	}
	printf("\n");
	
	/**
	 * 俩算法的不同点
	 */
	if(paraAlgorithm == 0) 
	{
		/*最短路径算法distant里面存储的就是0到每各结点的最短路径
		所以直接打印就好*/ 
		printf("所有节点的路径长度依次为:\n ");
		for(i = 0; i < numNodes; i++) 
		{
			printf("%d ", distant[i]);
		}
	} 
	if(paraAlgorithm == 1) 
	{
		/*最小生成树里面distant存储的其实是每个结点到父节点的距离
		,除了source,每个结点都有唯一一个父节点,所以把他们加起来就是
		总的代价*/ 
		resultCost = 0;
		for(i = 0; i < numNodes; i++) 
		{
			resultCost += distant[i];
			printf("节点%d 的路径长度为 %d , 总路径长度为 %d\n", i, distant[i], resultCost);
		}
		printf("最后,总路径长度为 %d.\n ", resultCost);
	}
	
	printf("\r\n");
	
}
/**
 * @brief 构建样本网
 * 
 * @return 
 */
netPtr constructSampleNet() 
{
	int i, j;
	int myGraph[6][6] = 
	{ 
	{0, 6, 1, 5, 0, 0},
	{6, 0, 5, 0, 3, 0}, 
	{1, 5, 0, 5, 6, 4}, 
	{5, 0, 5, 0, 0, 2}, 
	{0, 3, 6, 0, 0, 6},
	{0, 0, 4, 2, 6, 0}
	};
	int** tempPtr;
	int numNodes = 6;
	printf("准备数据\n");
	
	tempPtr = (int**)malloc(numNodes * sizeof(int*));
	for (i = 0; i < numNodes; i ++) 
	{
		tempPtr[i] = (int*)malloc(numNodes * sizeof(int));
	}
	
	for (i = 0; i < numNodes; i ++) 
	{
		for (j = 0; j < numNodes; j ++) 
		{
			if (myGraph[i][j] == 0) 
			{
				tempPtr[i][j] = MAX;
			}
			else 
			{
				tempPtr[i][j] = myGraph[i][j];
			}
		}
	}
	
	printf("准备完毕\n");
	
	netPtr resultNetPtr = netInit(numNodes, tempPtr);
	return resultNetPtr;
}
/**
 * @brief 测试类
 */
void testPrim() 
{
	netPtr tempNetPtr = constructSampleNet();
	printf("=====Dijkstra 算法=====\n");
	PrimOrDijkstra(tempNetPtr, 0);
	printf("=====  Prim 算法  =====\n");
	PrimOrDijkstra(tempNetPtr, 1);
}
int main()
{
	testPrim();
} 

2.2 Test results

准备数据
准备完毕
=====Dijkstra 算法=====
所有节点的父节点依次为:
-1 0 0 0 2 2
所有节点的路径长度依次为:
 0 6 1 5 7 5
=====  Prim 算法  =====
所有节点的父节点依次为:
-1 2 0 5 1 2
节点0 的路径长度为 0 , 总路径长度为 0
节点1 的路径长度为 5 , 总路径长度为 5
节点2 的路径长度为 1 , 总路径长度为 6
节点3 的路径长度为 2 , 总路径长度为 8
节点4 的路径长度为 3 , 总路径长度为 11
节点5 的路径长度为 4 , 总路径长度为 15
最后,总路径长度为 15.

2.3 Diagram

Good picture I use [doge]

1. The distance array in the Dijkstra algorithm is the shortest path length from 0 to each node

2. The distance array in the Prim algorithm is the path length from this node to the parent node

 

 

Guess you like

Origin blog.csdn.net/qq_61649579/article/details/125132584