Data Structures C Code 7.1: Graph Traversal

Learning objectives: Graph is the most complex data structure we learn, so we must understand it!

Learning tasks:

  1. copy code
  2. Table of contents

    1 full code

    2 test results

    3 similarities and differences of traversal

    2.1 The depth-first search of the graph is similar to the pre-order traversal algorithm of the binary tree.

    2.2 The breadth-first search of the graph is similar to the hierarchical traversal algorithm of the binary tree.

    2.3 Diagram, in simple terms


Code description:

  1. Depth-first uses recursion.
  2. The breadth-first stack is actually very similar to the traversal of the binary tree, and the queue is also copied from there and modified.

study-time:

2022.6.5

1 full code

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

#define MAXSIZE 10

/*这个数组是用来记录某位置是否被访问过了,为什么要记录呢,
因为图不像二叉树那样,由上往下只能通过双亲到达儿子,并且每一个
结点只有一个双亲,图的话,可以很有可能可以通过多个结点访问到你。
所以是否被访问过了我们是很有必要记录的。这种在各个函数都会用
到,并且随时会更改的变量设置为全局变量很舒服*/
int* visited;  

/*这里我们用的是 n×n的布尔矩阵来表示每两个点之间的连通性
第n行就代表第n个元素分别与其他元素(包括自己)是否连通*/ 
typedef struct graph
{
	int** connections;
	int numNodes;
}*graphPtr;

/*这里是我们在广度遍历要用到的循环队列的相关操作*/
typedef struct graphQueue
{
	int *node;
	int front;
	int rear;
}*graphQueuePtr;
/**
 * @brief 队列初始化
 * 
 * @return 
 */
graphQueuePtr queueInit()
{
	graphQueuePtr resultQueue = (graphQueuePtr)malloc(sizeof(struct graphQueue));
	resultQueue->node = (int*)malloc(MAXSIZE * sizeof(int));
	
	resultQueue->front = 0;
	resultQueue->rear = 0;
	return resultQueue;
}
/**
 * @brief 入队
 * 
 * @param paraData 
 * @param paraQueue 
 */
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;
}
/**
 * @brief 出队
 * 
 * @param paraQueue 
 * 
 * @return 
 */
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;
}

/**
 * @brief 这里就是把记录是否访问的数组初始化一下 
 * 
 * @param paraGraph 
 */
void visitedInit(graphPtr paraGraph)
{
	int i;
	visited = (int*)malloc(paraGraph->numNodes * sizeof(int));
	
	for(i = 0;i < paraGraph->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 在我看到这个深度遍历的代码时候,我就想到了我前两天写的N皇后问题,两者其实有很多相似之处,即每一个遍历的元素都要走完一个for循环,如果满足条件,就先递归到下一个里面去,但是等他们走完了,终究还是要回到这里把剩下的for循环走完
 * 
 * @param paraGraph 
 * @param paraNum 
 */
void depthTranverse(graphPtr paraGraph,int paraNum)
{
	int i;
	visited[paraNum] = 1;
	printf("%d ",paraNum);
	
	for(i = 0;i < paraGraph->numNodes;i ++)
	{
		/*先得检查是否被访问过了,然后看两者是否连通
		都满足的话就可以递归了*/ 
		if(visited[i] == 0 && paraGraph->connections[paraNum][i] == 1)
		{
			depthTranverse(paraGraph,i);
		}
	}
}

/**
 * @brief 这个广度遍历和我们二叉树的层次遍历是很相似的,只多了一个检查是否被访问过,用队列来实现是比较简单的
 * 
 * @param paraGraph 
 * @param paraNum 
 */
void wideTranverse(graphPtr paraGraph,int paraNum)
{
	int i;
	int temp;
	//建立队列 
	graphQueuePtr tempQueue = queueInit();
	visited[paraNum] = 1;
	//入队的时候就打印出来 
	enqueue(tempQueue,paraNum);
	printf("%d ",paraNum);
	
	//循环结束条件就是队列为空 
	while(tempQueue->front != tempQueue->rear)
	{
		temp = dequeue(tempQueue);
		//出队一个元素就要入队与他连通且没有被访问过的元素
		for(i = 0;i < paraGraph->numNodes;i ++)
		{
			if(visited[i] == 0 && paraGraph->connections[temp][i] == 1)
			{
				visited[i] = 1;
				enqueue(tempQueue,i);
				printf("%d ",i);
			}
		}
	}
	
}
/**
 * @brief 测试类
 */
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("数据准备完毕\n");
	
	graphPtr tempGraphPtr = graphInit(tempPtr,5);
	printf("节点个数为 %d \n", tempGraphPtr -> numNodes);
	printf("图已被初始化\n");
	
	printf("深度优先访问:\n");
	visitedInit(tempGraphPtr);
	depthTranverse(tempGraphPtr, 4);
	
	printf("\n广度优先访问:\r\n");
	visitedInit(tempGraphPtr);
	wideTranverse(tempGraphPtr, 4);
}

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

2 test results

正在准备数据
数据准备完毕
节点个数为 5
图已被初始化
深度优先访问:
4 1 0 3 2
广度优先访问:
4 1 2 0 3

3 similarities and differences of traversal

1. The same point: they are all traversal of the graph, and they are all line by line. Starting from the fourth line, the output value is the column of the point with a value of 1.

2. Differences:

2.1 The depth-first search of the graph is similar to the pre-order traversal algorithm of the binary tree.

Its idea: Assuming that the initial state is that all vertices in the graph are unvisited, starting from a certain vertex v, first visit the vertex, and then start from its unvisited adjacent points in order to traverse the graph in depth-first search until All vertices in the graph that have a path with v are visited. If there are other vertices that have not been visited at this time, choose another vertex that has not been visited as the starting point, and repeat the above process until all vertices in the graph have been visited.

Obviously, depth-first search is a recursive process

The characteristic of depth-first traversal is that it traverses after selecting a starting point. If it can move forward, it will move forward. Repeat this until all vertices connected to the selected point have been traversed.

2.2 The breadth-first search of the graph is similar to the hierarchical traversal algorithm of the binary tree.

Its idea: first start from the starting vertex v, visit the vertex v, and put it into the queue; the vertex v goes out of the queue, and visit the unvisited vertices w1, w2, ..., wi adjacent to v in turn, and put It enters the queue in turn; then the vertex w1 goes out of the queue, visits the unvisited vertices adjacent to w1 in turn, and enqueues them; then the vertex w2 goes out of the queue, visits the unvisited vertices adjacent to w2 in turn, and Enqueue it; until all vertices in the graph have been visited.

2.3 Diagram, in simple terms

Depth-first search (row first) is to traverse a column with a value of 1, and then jump to that column to traverse

Breadth-first search (first column) is to traverse the complete column before jumping to the next column with a value of 1 to traverse

	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}
	};

 

Guess you like

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