学习目标:图是我们学习的最复杂数据结构,一定要弄明白!
学习任务:
代码说明:
- 深度优先用递归.
- 广度优先用栈, 其实和二叉树的遍历很像, 队列也是从那边抄过来改巴改巴就可以了.
学习时间:
2022.6.5
1全部代码
#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测试结果
正在准备数据
数据准备完毕
节点个数为 5
图已被初始化
深度优先访问:
4 1 0 3 2
广度优先访问:
4 1 2 0 3
3遍历的异同
1,相同点:都是对图的遍历,都是一行一行的来,从第四行开始,输出值为1的点所的列
2,不同点:
2.1图的深度优先搜索和二叉树的先序遍历算法相似。
它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
显然,深度优先搜索是一个递归的过程
深度优先遍历特点是,选定一个出发点后进行遍历,能前进则前进,若不能前进,回退一步再前进,或再回退一步后继续前进。依此重复,直到所有与选定点相通的所有顶点都被遍历。
2.2图的广度优先搜素与二叉树的层次遍历算法相似。
它的思想:首先从起始顶点v出发,访问顶点v,并将其入队;顶点v出队,依次访问与v相邻接未被访问过的顶点w1,w2,…,wi,并将其依次入队;然后顶点w1出队,依次访问与w1相邻接未被访问的顶点,并将其入队;然后顶点w2出队,依次访问与w2相邻接未被访问的顶点,并将其入队;直到图中所有顶点均被访问过为止。
2.3图解,简单来说
深度优先搜索(优先行)就是遍历列时有值为1,就跳到那一列遍历
广度优先搜索(优先列)就是遍历完整列,才跳到值为1的下一列遍历
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}
};