【数据结构】图的邻接表表示(GNU C++实现)

图G=(V,E)有两种标准的方法,即邻接表和邻接矩阵,它们即可以表示有向图,又可以表示无向图;其中邻接表,通常表示的是稀疏图;稠密图通常用邻接矩阵表示;


复杂度分析

(1)邻接矩阵的空间复杂度为O(V*V),针对有向图;

(2)邻接表的空间复杂度为O(E),针对有向图;


本实验的图为稀疏的有环有向带权图;随机产生;

方案:

(1)一般我们用自己的C语言的指针和动态内存来构建邻接表,但是还是有所弊端,动态内存,如果用户一直不显示释放,将会一直驻留内存,无法释放;

(2)如果使用C++语言中的智能指针,对本场景也会有问题;

(2.1)动态内存使用智能指针来管理,节点的内部指向仍旧使用普通指针来管理,那么当使用的动态内存没有计数指向(use_count为0)时,就会自动释放,造,普通指针指向错误;

(2.2)动态内存使用智能指针来管理,节点的内部指向也使用智能指针来管理;由于内部的智能指针代表的是一个新的指向计数,失去了普通指针操作内存的作用;

(3)虽然(1)可以实现,但是是自己造轮子,效率和可靠性不够,因此采用STL标准库的vector来管理智能指针;


程序实现:

Directed_Graph.h

#include <vector>
#include <random>
#include <memory>
#include <iostream>
#include <ctime>
using namespace std;

struct V_Nodes{				//循环链表表示
	unsigned id;			//顶点的编号,从0开始到vertex_nums-1;
	unsigned weight;		//权值
};

class Directed_Graph
{
private:
	unsigned vertex_nums;		//顶点的个数
	vector<vector<shared_ptr<V_Nodes>>> vnodes_arrays;	//存放邻接表的顶点表向量,vnodes_arrays中存放每一个顶点的双向循环链表,使用list速度较慢;

	/*
		本函数主要是构造邻接表的边的过程,成功返回true,失败返回false
	*/
	bool insert_edge(unsigned source, unsigned dest, unsigned weight)	
	{
		vector<shared_ptr<V_Nodes>>& vs = vnodes_arrays[source];
		
		auto iter = vs.cbegin();
		for(; iter != vs.cend(); iter++){
			if((*iter)->id == dest){		//看是否已经构造过该边了
				return false;
			}else if((*iter)->id > dest){		//找到插入节点的后节点
				break;
			}
		}
		
		shared_ptr<V_Nodes> p = make_shared<V_Nodes>();	//构造该边
		p->id = dest;
		p->weight = weight; 
		
		vs.insert(iter, p);				//在iter之前插入新的节点,即边
		
		return true;
	}
public:
	Directed_Graph()			//构造函数初始化时将会随机建立有向有环带权有向图
	{
		default_random_engine e(time(0));		//设置种子			
		uniform_int_distribution<unsigned> u(5, 9);	//均匀分布,u为随机数源
		vertex_nums = u(e);				//产生顶点数目
	
		for(size_t i = 0; i < vertex_nums; i++){
			vector<shared_ptr<V_Nodes>> vectors;			
			vnodes_arrays.push_back(vectors);		//放入循环链表
		}

		uniform_int_distribution<unsigned> u2(0, vertex_nums-1);	//顶点的随机
		uniform_int_distribution<unsigned> u3(3, 15);			//边值的随机
		
		unsigned source, dest, weight;			//源顶点,目的顶点,权值
		unsigned max_edges_nums	= vertex_nums * vertex_nums;	//由于有环,故打印的总边数并不是无环有向图的vertex_nums * (vertex_nums  - 1);
		unsigned count = 0;
		unsigned total_count = 0;

		while(count < max_edges_nums / 3){		//当边数小于一半时就继续产生
			source = u2(e);				//产生随机边
			dest = u2(e);
			weight = u3(e);
			if(insert_edge(source, dest, weight)){	
				count++;
			}		
			total_count++;
		}

		//cout<<vertex_nums<<" "<<max_edges_nums<<" "<<count<<" "<<total_count<<endl;
	}

	void printf_graph()		//打印图
	{
		int count = 0;
		int counts = 0;
		for(auto vs : vnodes_arrays){
			for(auto r : vs){
				cout<<"<"<<count<<","<<r->id<<">("<<r->weight<<") ";
				counts++;			
			}
			cout<<endl;
			count++;
		}
	}
};

main.cpp

#include "Directed_Graph.h"

int main(void)
{
	Directed_Graph directed_graph;

	cout<<"---------------------------------------------------------\n";
	cout<<"随机产生的有向有环带权图如下:\n";
	directed_graph.printf_graph();
	cout<<"---------------------------------------------------------\n";
	return 0;
}

程序输出:



注,方案1的实现;由于内存释放得自己实现,后续图的操作将不会使用此方案,因为我们要实现动态内存的自动管理;

Directed_Graph.h

#include <vector>
#include <random>
#include <memory>
#include <iostream>
#include <ctime>
using namespace std;

struct V_Nodes{ //循环链表表示
	unsigned id; //顶点的编号,从0开始到vertex_nums-1;
	unsigned weight; //权值
	V_Nodes *p_next; //邻接表指向下一个顶点
	V_Nodes *p_pre; //邻接表指向上一个顶点
};


class Directed_Graph
{
private:
	unsigned vertex_nums; //顶点的个数
	vector<V_Nodes *> vnodes_lists; //存放邻接表的顶点表向量,只是存放指针

	/*
	本函数主要是构造邻接表的边的过程,成功返回true,失败返回false
	*/
	bool insert_edge(unsigned source, unsigned dest, unsigned weight) 
	{
		V_Nodes* temp = vnodes_lists[source]; //如果不是引用,也就是拷贝,获得顶点向量
		if(dest == source) //源顶点等于目的顶点时
		{
			if(temp->weight == 0){
				temp->weight = weight; //自身到自身也有全值,有环
				return true;
			}else{
			return false;
			}
 		}


		V_Nodes *p_next = temp->p_next; //指向下一个节点 

		while(p_next != temp->p_next->p_pre){ //当到了队列头时
			if(p_next->id == dest){ //已经生成过该条边了,返回flase
	 			return false;
		 	}else if(p_next->id > dest){ //邻接表中,邻接的边数递增
				break; //没有找到 
			}
 
			p_next = p_next->p_next;
		};
 
		//在一个顶点的后面插入一个节点,只能有两种情况,初始化的时候会行不通
		V_Nodes* current = new V_Nodes(); //创建新节点,使前节点指向新节点
		current->id = dest; //改变新节点的值
		current->weight = weight; 

		current->p_next = p_next; //改变新节点的next
 		current->p_pre = p_next->p_pre; //改变新节点的pre
 		p_next->p_pre->p_next = current; //改变前节点的p_next
 		p_next->p_pre = current; //改变后节点的pre


 		return true;
	}
public:
 	Directed_Graph() //构造函数初始化时将会随机建立有向有环带权有向图
 	{
		default_random_engine e(time(0)); //设置种子 
		uniform_int_distribution<unsigned> u(7, 12); //均匀分布,u为随机数源
		vertex_nums = u(e); //产生顶点数目
 
		for(size_t i = 0; i < vertex_nums; i++){
			V_Nodes* v_nodes = new V_Nodes(); 
 			v_nodes->id = i;
 			v_nodes->weight = 0; //初始化,权重为0
 			v_nodes->p_next = v_nodes;
 			v_nodes->p_pre = v_nodes; 
 			//cout<<i<<" "<<v_nodes<<endl;
 			vnodes_lists.push_back(v_nodes); //copy,p_next内部指针指向仍是以前的内存,存放这几个顶点的邻接表向量
 		}


 		uniform_int_distribution<unsigned> u2(0, vertex_nums-1); //顶点的随机
		uniform_int_distribution<unsigned> u3(3, 15); //边值的随机
 
 		unsigned source, dest, weight; //源顶点,目的顶点,权值
 		unsigned max_edges_nums = vertex_nums * vertex_nums; //由于有环,故打印的总边数并不是无环有向图的vertex_nums * (vertex_nums  - 1);
 		unsigned count = 0;
 		unsigned total_count = 0;
 		while(count < max_edges_nums / 3){ //当边数小于一半时就继续产生
 			source = u2(e); //产生随机边
 			dest = u2(e);
 			weight = u3(e);
 			if(insert_edge(source, dest, weight)){ 
 				count++;
 			} 
 			total_count++;
 		}
 	}


	void printf_graph()
 	{
		for(auto iter = vnodes_lists.cbegin(); iter != vnodes_lists.cend(); iter++){
			V_Nodes *p_next = (*iter)->p_next;
			if((*iter)->weight > 0){ //打印有环的顶点
 				cout<<"<"<<(*iter)->id<<","<<(*iter)->id<<">("<<(*iter)->weight<<") ";
 			}
 
 			while(p_next != (*iter)->p_next->p_pre){ //打印邻接的顶点和边
 				cout<<"<"<<(*iter)->id<<","<<p_next->id<<">("<<p_next->weight<<") "; //打印
				p_next = p_next->p_next; 
		 	}
 			cout<<endl;
 		}
 	}
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值