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