图的三种表示方法 邻接矩阵,邻接表,邻接表改进版 及其优劣分析(附代码)

图有三种表示方法:
**1.邻接矩阵:**利用一个n*n大小的方阵,
A[i][j] = 1表示顶点 ij 是相邻的,即 ij 之间有一条边
A[i][j] = 0表示顶点 ij 是不相邻的,即 ij 之间有没有边

#include
#define max 10
using namespace std;

int V;//顶点个数
int E;//边的个数
int adjm[max][max] = {0};

void initE(int x,int y, int w);//初始化边,w为坐标

int main(){ 
 	for(int i = 0; i < 10; i++){//输出邻接矩阵 
 	 	for(int j = 0; j < 10; j++){
    			cout << adjm[i][j] << " ";
  		}
  		cout << endl;
 	}
 	return 0;
}
void initE(int x,int y, int w){ //w为坐标 
 	adjm[x][y] = w;
}

邻接矩阵的实现代码比较简单,下面分析一下其时间和空间的复杂度。

空间复杂度:
O(V^2) 即节点个数的平方,如果有3000个节点,那么需要开出的空间为9x10^6,需要改进

时间复杂度:
建图: O(E) 有E条边,那么E条边的信息都需要存储,这个时间复杂度无法继续提升
查询两点是否相邻: O(1) 即判断a[i][j]是否为1即可,这个时间复杂度也无法提升
**查询某一顶点的邻居:**O(V) 判断a[i]这个数组中有多少个元素为1

其空间复杂度能否修改为O(V+E)呢,即V个节点和E条边的信息存储空间大小为O(V+E)

邻接表解决了这个问题:
图的三种表示方法 邻接矩阵,邻接表,邻接表改进版 及其优劣分析(附代码)_第1张图片

邻接表的大致思想如上图所示,这里不再用文字叙述
代码实现(无向有权图):

typedef struct AdjNode{
 	int N;//Adjvex是该邻接点自己在顶点表中的位
 	//置(顶点表中的数组索引值),而不是与其相连的顶点在顶点表中的位置,更不是该节点保存的数据。
 	int w;//权重
 	AdjNode* next; 
}AdjN;

typedef struct VNode{
 	int data;
 	AdjN* first;
}VN;

typedef struct Graph{
 	int v;//顶点个数
 	int e;//边的个数 
 	VN ag[max];
}G;

void initG(G* g);
void printG(G* g);//打印节点信息 
void printLink(G* g);//打印链表信息 
bool hasedge();//暂未实现  快速查重 
int  nei_v();//暂未实现 

int main(){
 G* g = (G*)malloc(sizeof(G));
 initG(g);
 printG(g);
 printLink(g);
 
 return 0;
}

void initG(G* g){
 	cout<<"输入顶点和边的个数:";
 	int v,e; 
	cin >> v >> e;
 	g->v = v;
 	g->e = e;
 	cout<<"请输入个顶点数据:" ;
 	int data;
 	for(int i = 0; i < g->v; i++){
  		cin >> data;
  		g->ag[i].data = data;
  		g->ag[i].first = NULL; 
 	}
  	for(int i = 0; i < g->e; i++){
  		int p1, p2, w;
  		cout<<"输入边:"; 
  		cin >> p1 >> p2 >> w;
  		AdjN* node1 = (AdjN*)malloc(sizeof(AdjN)); 
  		node1->N = p2;
  		node1->w = w;
  		AdjN* temp = g->ag[p1].first;
  		g->ag[p1].first = node1;
  		node1->next = temp; 
    		//双向 
  		AdjN* node2 = (AdjN*)malloc(sizeof(AdjN)); 
  		node2->N = p1;
  		node2->w = w;
  		temp = g->ag[p2].first;
  		g->ag[p2].first = node2;
  		node2->next = temp; 
 	}
}

void printG(G* g){//打印节点信息 
 	for(int j = 0; j < g->v; j++){
  		cout << j << " " << g->ag[j].data <<endl;
 	}
}

void printLink(G* g){//打印链表信息 
 	for(int i = 0; i < g->v; i++){
  		cout << i <<" ";
  		AdjN * Node = g->ag[i].first;
  		while(Node != NULL){
   		cout<< Node->N << "(" << Node->w << ") ";
   		Node = Node->next;
  	}
  	cout << endl;
 	} 
} 

图的三种表示方法 邻接矩阵,邻接表,邻接表改进版 及其优劣分析(附代码)_第2张图片

空间复杂度分析:O(V+E)已经达到最优

时间复杂度:主要分析其查重能力,即查看一个顶点与另外一个顶点是否存在一条边时:O(degree(V)),取决于该顶点的度。
邻接表的问题
在快速查重的问题(任给两个节点,判断这两个节点之间是否有边)上的时间复杂度为O(degree(V))
而在V比较大时,其表现性能并不是那么完美
解决方法
针对其主要问题,
若使用链表,其查询的时间复杂度必然为线性的
若使用哈希表,其查询时间复杂度为O(1)
若使用红黑树,其查询时间复杂度为O(logV)
采用不同的数据结构,可降低其查询时间复杂度

下面为邻接表的升级版代码:

typedef struct VNode{
 int data;
 map<int,int> mp;//map key 是节点号,即该节点到key之间有一条边,权重是value 
}VN;

typedef struct Graph{
 int v,e;
 VN ag[max];
}G;

void initG(G* g);
void printG(G* g);//打印节点信息 
void printLink(G* g);//打印链表信息 

int main(){
 G* g = new G;
 initG(g);
 printG(g);
 printLink(g);
 
 return 0;
} 

void initG(G* g){
 cout<< "Input the amount of Node and Edge: ";
 cin >> g->v >> g->e ;
 
 //Input the data of Node
 cout<< "Input Data:" << endl;
 for(int i = 0; i < g->v; i++){
  cin >> g->ag[i].data ; 
 }
 
 //Input the edge and weight
 cout<< "Input Edge and Weight:"<<endl;
 for(int i = 0; i < g->e; i++){
  int node1, node2, w;
  cin >> node1 >> node2 >> w;
  g->ag[node1].mp[node2] = w; 
  g->ag[node2].mp[node1] = w; //无向图
 }
}

void printG(G* g){//打印节点信息
 cout<<"Output The Info of Node:"<<endl;
 
 for(int i = 0; i < g->v; i++){
  cout<< i <<" "<< g->ag[i].data << endl;
 } 
}

void printLink(G* g){//打印链表信息
 cout << "OutPut The Info of NLink:"<<endl;
 
 for(int i = 0; i < g->v; i++){
  cout << i << " ";
  for(map<int,int>::iterator it = g->ag[i].mp.begin(); it != g->ag[i].mp.end(); it++){
   cout << it->first << "(" << it->second << ")" << " ";
  }
  cout << endl;
 }
} 

图的三种表示方法 邻接矩阵,邻接表,邻接表改进版 及其优劣分析(附代码)_第3张图片在输出中可发现,每个顶点都是按顺序输出的,虽然我用的时hash_map,但是在STL中其map是用红黑树实现的,其在内部会自动排序,为以后的算法提高了效率。

你可能感兴趣的:(数据结构c++)