邻接矩阵转邻接表并使用bfs遍历
邻接矩阵转邻接表
算法思路
邻接矩阵转邻接表:先用邻接矩阵存图,然后遍历每个点,每个点可以直接相连的点放入对应的邻接表中,邻接表用一个结构体表示,结构体存直接相连的点个数n与n个点的编号与其所对应的边的权值
算法复杂度分析
邻接矩阵存图时间复杂度为m,空间复杂度为n^ 2,转邻接表时因为要遍历每个点,所以时间复杂度为n^ 2,空间复杂度为nm。
邻接表进行BFS遍历
算法思路
BFS遍历:先任意挑一个点出发,第一次遍历和它直接相邻的点,接着遍历和它直接相邻的点的相邻点,依次遍历下去,直到该点可以遍历的所有点都被遍历,注意避免重复遍历,所以需要一个vis数组标记是否被访问过。现在我们的问题就是如何存储被访问过但没有被访问过直接相邻点的点,我们可以采用队列这一数据结构来完成这一操作。
队列的实现:我们可以使用数组直接模拟队列,分别定义一个头指针和尾指针i和j就行了,i代表队列头的下标,尾代表队列尾的下标,而且我们还可以使用循环队列来提高空间利用率。但是这种做法就受限于数组的大小,因此我们使用链式队列来表示队列,它的本质就是一个链表,头部表示队头,尾部表示队尾,分别用两个指针变量存储,这样入队和出队都是O(1)的复杂度,但是为了更方便的进行入队出队,我们头部用一个哑节点表示,他没有任何实际意义,它的next才是队头。
队列的函数实现:对于一个链式队列,我们不仅仅只是封装入队和出队操作,我们还需要进行对队列进行空间释放避免内存泄露,对队列判断是否为空避免出队时发生内存段错误,而判空这个操作可以用哑节点的next是否为空判断,也可以用当前队列中元素个数判断
算法复杂度分析
队列入队出队复杂度都是1,bfs会遍历n个点,所以bfs遍历复杂度为n
代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define maxn 10001
int graph[maxn][maxn]={0};//graph[i][j]=0表示i,j之间没有边
int vis[maxn] = { 0 };
struct edge{
int num;
int to[maxn];
int w[maxn];
}v[maxn];
//链式队列
struct node {
int val;
struct node* next;
};
struct queue {
struct node* head, * to;
int size;
};
struct queue* creat_queue() {
struct node* head = malloc(sizeof(struct node));
head->val = -1;
head->next = NULL;
struct queue* q = malloc(sizeof(struct queue));
q->head = q->to = head;
q->size=0;
return q;
}
void push(int val, struct queue* q) {
struct node* p = malloc(sizeof(struct node));
p->val = val;
p->next = NULL;
q->size++;
q->to->next = p;
q->to = q->to->next;
}
void pop(struct queue*q) {
struct node* p = q->head->next;
q->head->next = p->next;
free(p);
if (q->head->next == NULL)
q->to = q->head;
q->size--;
}
int is_Empty(struct queue* q) {
return q->size == 0;
}
void clear(struct queue* q) {
while (!is_Empty(q))
pop(q);
free(q->head);
q->head=q->to=NULL;
free(q);
q = NULL;
}
int top(struct queue* q) {
return q->head->next->val;
}
int main() {
int n, m;
printf("请输入节点数和边数:");
scanf("%d%d", &n, &m);
int u, v1,w;
printf("请输入每条边对应的两个节点以及权值\n");
for (int i = 0; i < m; i++)
scanf("%d%d%d", &u, &v1, &w), graph[u][v1] = graph[v1][u] = w;
//转邻接表
for (int i = 1; i <= n; i++)
v[i].num = 0, memset(v[i].to, 0, sizeof(v[i].to));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (graph[i][j])
v[i].to[v[i].num] = j, v[i].w[v[i].num++] = graph[i][j];
//BFS
int bfs[maxn]={0},len=0;
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
vis[i] = 1;
struct queue* q = creat_queue();
push(i, q);
while (!is_Empty(q)) {
int val = top(q);
for(int j=0;j<v[val].num;j++)
if (!vis[v[val].to[j]]) {
push(v[val].to[j], q);
vis[v[val].to[j]] = 1;
}
bfs[len++] = top(q);
pop(q);
}
clear(q);
}
}
for (int i = 0; i < len; i++)
printf("%d ", bfs[i]);
putchar('\n');
return 0;
}
邻接表遍历验证六度空间理论
算法思路
因为需要判断距离是否超过6,所以我们队列中的数据不再只是点的编号,而应该是点的编号和到起始点的距离,因此我们需要再开一个结构体来存储数据。
BFS遍历的时候不再像前面一样遍历到底,而是发现距离超过6后就不再继续扩展,最后记录每个点可以扩展到的点个数,求占总点数的比例就行了
算法复杂度分析
时间复杂度取决于图的样式,最好情况下时间复杂度为n+m,因为要遍历n个点,每个点只能扩展到它本身,最坏情况下每个点都被遍历n次,复杂度为n^2+m,m是存图时的时间复杂度。空间复杂度为nm+n,nm是邻接表的空间复杂度,n是队列的空间复杂度。
代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define maxn 10001
int vis[maxn] = { 0 };
struct edge {
int num;
int to[maxn];
int w[maxn];
}v[maxn];//邻接表
struct database {
int step;
int val;
};
//链式队列
struct node {
struct database data;
struct node* next;
};
struct queue {
struct node* head, * to;
int size;
};
struct queue* creat_queue() {
struct node* head = malloc(sizeof(struct node));
head->data.step=head->data.val = -1;
head->next = NULL;
struct queue* q = malloc(sizeof(struct queue));
q->head = q->to = head;
q->size = 0;
return q;
}
void push(int val,int step, struct queue* q) {
struct node* p = malloc(sizeof(struct node));
p->data.val = val;
p->data.step = step;
p->next = NULL;
q->size++;
q->to->next = p;
q->to = q->to->next;
}
void pop(struct queue* q) {
struct node* p = q->head->next;
q->head->next = p->next;
free(p);
if (q->head->next == NULL)
q->to = q->head;
q->size--;
}
int is_Empty(struct queue* q) {
return q->size == 0;
}
void clear(struct queue* q) {
while (!is_Empty(q))
pop(q);
free(q->head);
q->head = q->to = NULL;
free(q);
q = NULL;
}
struct database top(struct queue* q) {
return q->head->next->data;
}
int main() {
int n, m;
printf("请输入节点数和边数:");
scanf("%d%d", &n, &m);
int u, v1, w;
printf("请输入每条边对应的两个节点以及权值\n");
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v1, &w);
v[u].to[v[u].num] = v1;
v[v1].to[v[v1].num] = u;
v[u].w[v[u].num++] = w;
v[v1].w[v[v1].num++] = w;
}
int ans[maxn]={0};
for (int i = 1; i <= n; i++) {
int vis[maxn] = { 0 };
vis[i] = 1;
struct queue* q = creat_queue();
push(i, 0, q);
while (!is_Empty(q)) {
int val = top(q).val, step = top(q).step;
for (int j = 0; j < v[val].num; j++) {
if (!vis[v[val].to[j]]&&step+v[val].w[j]<=6) {
ans[i]++;
push(v[val].to[j], step + v[val].w[j], q);
vis[v[val].to[j]] = 1;
}
}
pop(q);
}
clear(q);
}
for (int i = 1; i <= n; i++)
printf("%d: %%%.2lf\n", i,1.0*(ans[i]+1)/n*100);
return 0;
}
/*
10 18
1 2 4
1 3 3
2 3 5
2 5 3
2 4 5
3 4 5
3 10 3
5 4 4
4 10 4
5 6 3
9 10 3
4 6 4
4 7 4
4 8 4
4 9 4
6 7 3
7 8 3
8 9 3
*/