diff --git a/.gitignore b/.gitignore index 6641244..17f394f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ obj/ Debug/ Release/ /.idea +*.cbp *.depend *.layout *.mk diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28c7742..bcdd56b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,31 @@ +#Contributing to this repository + +首先,感谢对这个项目进行贡献的人. + +###简介 +Graph是一个强调封装与资源安全的图的实现.用户不必为了使用这个图而显式地动态分配内存. + +用户也难以直接得到图内部的结构的指针从而做出一些令人意想不到的事情. + +例如: + + directed_graph graph; + vertex_id v0 = graph.add_vertex(); + vertex_id v1 = graph.add_vertex(); + edge_id e0 = graph.add_edge(v0,v1); + graph.value(v0) = "v0"; + graph.vaue(e0) = "e0"; + graph.remove_vertex(v1); + +用户完全通过`vertex_id`和`edge_id`来和图进行交互.用户也没有访问权限来*非拷贝/移动地创建*和修改`vertex_id`和`edge_id`类型的对象.这将有助于封装. +###贡献方式 +* 提出改进意见(欢迎!). +* 报告bug. +* 提出增强功能. +* 重构. +* 实现新的功能. +* 修补bug(万分感激). + ###编码规范 以下规则只是建议各位遵守,若无暇顾及或觉得繁琐也可无视掉. 若觉得有不妥之处请联系我. @@ -14,13 +42,14 @@ scope的结束花括号应当放在独立的新的一行,与对应的起始花 { std::cout << "This is code.\n"; } + #####类 类的所有类型别名的声明均放在类成员的最前面,并使用alias-declaration(using关键字)而不是typedef declaration. 其余的成员按 public protected private的顺序出现,其中成员变量尽量放在成员函数之后. #####指针,引用与const 最底层的const放置在右侧. 有const时,const右侧的 * 或&向左与const贴紧. -无const时, * 或&向右与标识符贴紧. +无const时, * 或&靠拢方向任意. 例子如下: int *p0 = nullptr; diff --git a/Graph.cbp b/Graph.cbp deleted file mode 100644 index 6d0f876..0000000 --- a/Graph.cbp +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - diff --git a/README.md b/README.md index 25515c6..cc6c4d7 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,26 @@ # Graph -这个项目提供 数据结构--[图](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)) 的一个实现,同时提供一部分图的算法.它被设计为在安全与效率中取得平衡. +这个项目提供 数据结构--[图](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)) 的一个实现,同时提供一部分图的算法. -目前源文件为./test/test.cpp ,头文件在./include下. +Graph强调封装与资源安全. -该项目目前有一个表示无向图的模板类:lzhlib::undirected_garph. +目前测试文件在./test下 ,头文件在./include下. +###接口简介 +该项目目前有两个表示图的模板类:`lzhlib::undirected_graph`和`lzhlib::directed_graph`,分别表示无向图和有向图. +####`lzhlib::undirected_garph`. +这是一个表示无向图的模板类. 该类接受两个模板参数,分别是图中顶点和边存储的数据的类型. 例: - lzhlib::undirected_graph g; + undirected_graph g; vertex_id v0 = g.add_vertex("v0"); vertex_id v1 = g.add_vertex("v1"); { - edge_id e0 = g.add_edge(v0, v1); - assert(g.value(e0) == ""); - g.value(e0) = "e0"; + edge_id e0 = g.add_edge(v0, v1,"e0"); + assert(g.get_edge(v0, v1) == e0); + assert(g.associated(v0, e0)); + assert(g.associated(v1, e0)); assert(g.adjacent(v0, v1)); assert(g.adjacent(v1, v0)); assert(g.get_edge(v0, v1) == e0); @@ -23,34 +28,93 @@ assert(g.value(v0) == "v0"); assert(g.value(v1) == "v1"); assert(g.value(e0) == "e0"); - - g.remove_edge(e0); + { + auto vertex = g.neighbors(v0); + assert(vertex.size() == 1); + assert(vertex[0] == v1); + } + { + auto vertex = g.neighbors(v1); + assert(vertex.size() == 1); + assert(vertex[0] == v0); + } + { + auto vertices = g.associated_vertices(e0); + assert(vertices.first == v0 && vertices.second == v1); + vector vertices0 = {vertices.first, vertices.second}; + vector vertices1; + for (auto v = g.first_vertex(); !g.vertex_end(v); v = g.next_vertex(v)) + vertices1.push_back(v); + assert(vertices0 == vertices1); + } + g.remove_edge(e0);//可选 + assert(!g.associated(v0, e0)); + assert(!g.associated(v1, e0)); assert(!g.adjacent(v0, v1)); assert(!g.adjacent(v1, v0)); } + g.remove_vertex(v0);//可选 + g.remove_vertex(v1);//可选 + +####`directed_graph` +这个模板类表示有向图. + +模板参数的意义与`undirected_graph`的相同. + +例: + + directed_graph g; + vertex_id v0 = g.add_vertex("v0"); + vertex_id v1 = g.add_vertex("v1"); { - edge_id e1 = g.add_edge(v0, v1, "e1"); - assert(g.value(e1) == "e1"); + edge_id e0 = g.add_edge(v0, v1); //不提供值初始化std::string(可以选择有) + assert(g.value(e0) == ""); //因而e0关联的值为空字符串 + assert(g.get_edge(v0, v1) == e0); + assert(g.associated(v0, e0)); + assert(!g.associated(v1, e0)); assert(g.adjacent(v0, v1)); - assert(g.adjacent(v1, v0)); - assert(g.get_edge(v0, v1) == e1); - assert(g.get_edge(v1, v0) == e1); + assert(!g.adjacent(v1, v0)); + assert(g.get_edge(v0, v1) == e0); + assert(g.value(v0) == "v0"); + assert(g.value(v1) == "v1"); + g.value(e0) = "e0"; + assert(g.value(e0) == "e0"); + { + auto vertices = g.neighbors(v0); + assert(vertices.size() == 1); + assert(vertices[0] == v1); + } + { + auto vertices = g.neighbors(v1); + assert(vertices.empty()); + } - g.remove_edge(e1); + { + auto vertices = g.associated_vertices(e0); + assert(vertices.first == v0 && vertices.second == v1); + vector vertices0 = {vertices.first, vertices.second}; + vector vertices1; + for (auto v = g.first_vertex(); !g.vertex_end(v); v = g.next_vertex(v)) //遍历所有vertex + vertices1.push_back(v); + assert(vertices0 == vertices1); + } + g.remove_edge(e0); //可选 + assert(!g.associated(v0, e0)); + assert(!g.associated(v1, e0)); assert(!g.adjacent(v0, v1)); assert(!g.adjacent(v1, v0)); } + g.remove_vertex(v0); //可选 + g.remove_vertex(v1); //可选 +###要求: -要求: - -* 使用GCC 支持C++14 或以上的编译器,并添加编译选项-std=c++14或更高. +* 使用支持C++14 或以上的编译器 -* 环境变量C_PLUS_INCLUDE_PATH 值:Graph文件夹所在的父目录的路径.(否则可能找不到头文件) - 或者使用GCC编译时添加-I选项后接Graph文件夹所在父目录的路径. +* 使编译器搜索头文件时能在Graph文件夹所在的父目录下搜索(添加编译选项,或修改环境变量等等),否则可能找不到头文件. -贡献: +###贡献: 期望各位为此供献出自己的力量.更多信息参见 [CONTRIBUTING](./CONTRIBUTING.md)和 [TODO](./TODO.md). diff --git a/TODO.md b/TODO.md index df1f9b3..a089651 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,8 @@ ###TODO -1. 重构.永远欢迎重构. -2. 完成 directed_graph .(已在 branch directed_graph 中开始) -3. 讨论添加新的功能 -4. 利用已有功能添加算法. +* 重构.永远欢迎重构. +* ~~完成directed_graph.~~(已在 branch directed_graph 中~~开始~~结束). +* 改进repository,提高其效率. +* 讨论添加新的功能 +* 利用已有功能添加算法. +* 以wiki页或其它方式补充文档. diff --git a/images/class_diagram.jpg b/images/class_diagram.jpg new file mode 100644 index 0000000..9b084e7 Binary files /dev/null and b/images/class_diagram.jpg differ diff --git a/images/file_include.jpg b/images/file_include.jpg new file mode 100644 index 0000000..27b0b94 Binary files /dev/null and b/images/file_include.jpg differ diff --git a/include/repository.h b/include/repository.h index af60f92..57b4088 100644 --- a/include/repository.h +++ b/include/repository.h @@ -52,7 +52,7 @@ namespace lzhlib stock_t& get_stock(id_t id) { #ifndef NDEBUG - if (is_not_found_in_living_stocks(id)) + if (is_not_valid(id)) { throw attempt_to_use_unassigned_stock(id); } @@ -62,7 +62,7 @@ namespace lzhlib stock_t const& get_stock(id_t id) const { #ifndef NDEBUG - if (is_not_found_in_living_stocks(id)) + if (is_not_valid(id)) { throw attempt_to_use_unassigned_stock(id); } @@ -72,47 +72,47 @@ namespace lzhlib template id_t add_stock(Args&& ... args) { - if (no_reusable_stocks()) + id_t ret = reusable_stock(); + if (ret == stocks.size()) //no reusable stock { return allocate_stock(std::forward(args)...); } else { - return reuse_stock(std::forward(args)...); + return reuse_stock(ret,std::forward(args)...); } } void remove_stock(id_t id) { #ifndef NDEBUG - if (is_not_found_in_living_stocks(id)) + if (is_not_valid(id)) throw attempt_to_remove_nonexistent_stock(id); #endif // NDEBUG stocks[id.id()].reset(); - ids_of_living_stocks.erase(id); - ids_of_reusable_stocks.insert(id); } id_t first_stock() const { - return *ids_of_living_stocks.begin(); + return stock_at_or_after(id_t{0}); } - bool stock_end(id_t current_stock) const + bool stock_end(id_t current) const { - return ids_of_living_stocks.find(current_stock) == ids_of_living_stocks.end(); + return current.id() == stocks.size(); } - id_t next_stock(id_t current_stock) const + id_t next_stock(id_t current) const //precondition: current.id() < stocks.size() { - return *++(ids_of_living_stocks.find(current_stock)); - } + return stock_at_or_after(++current); //postcondition: 设返回值为next,则stocks[next.id()]为current代表的位置或current代表的位置之后的位置上的有效stock的左值, + } // 或者next.id() == stocks.size()(current代表的位置之后的位置上均无有效stock + private: #ifndef NDEBUG - bool is_not_found_in_living_stocks(id_t id) const + bool is_not_valid(id_t id) const { - return ids_of_living_stocks.find(id) == ids_of_living_stocks.end(); + return stocks[id.id()] == nullptr; } #endif // NDEBUG bool no_reusable_stocks() { - return ids_of_reusable_stocks.empty(); + } template id_t allocate_stock(Args&& ... args) @@ -120,43 +120,37 @@ namespace lzhlib stocks.push_back(std::make_unique(std::forward(args)...)); id_t ret{stocks.size() - 1}; //the allocated stock is at the last position in the container. - ids_of_living_stocks.insert(ret); return ret; } template - id_t reuse_stock(Args&& ... args) + id_t reuse_stock(id_t reused, Args&& ... args)//precondition:stocks[reused.id()]必须为一个空指针 { - typename std::set::iterator i = iterator_of_a_reusable_stock(); - id_t ret = *i; - reusable_pointer(ret) = std::make_unique(std::forward(args)...); - ids_of_reusable_stocks.erase(i); - ids_of_living_stocks.insert(ret); - return ret; + reusable_pointer(reused) = std::make_unique(std::forward(args)...); + return reused; } - typename std::set::iterator iterator_of_a_reusable_stock() const - { - return ids_of_reusable_stocks.begin(); + id_t reusable_stock() const //postcondition: 设返回值为ret,则stocks[ret.id()]为一个空指针. + { //或者ret.id() == stocks.size()(current代表的位置及current代表的位置之后的位置上均无reusable stock + id_t current{0}; + while (current.id() != stocks.size() && stocks[current.id()] != nullptr) + ++current; + return current; } pointer_t& reusable_pointer(id_t id) //just a checker.The calling may be optimized out -- that's to say, may be inlined. { #ifndef NDEBUG - if (is_not_found_in_reusable_stocks(id)) - { + if(stocks[id.id()] != nullptr) throw attempt_to_reuse_unreusable_stock(id); - } #endif // NDEBUG return stocks[id.id()]; } -#ifndef NDEBUG - bool is_not_found_in_reusable_stocks(id_t id) const + id_t stock_at_or_after(id_t current) const //precondition: current.id() <= stocks.size() { - return ids_of_reusable_stocks.find(id) == ids_of_reusable_stocks.end(); - } -#endif // NDEBUG + while (current.id() != stocks.size() && stocks[current.id()] == nullptr) + ++current; + return current; //postcondition: 设返回值为next,则stocks[next.id()]为指向current代表的位置或current代表的位置之后的位置上的有效stock的指针的左值, + } //或者next.id() == stocks.size()(current代表的位置及current代表的位置之后的位置上均无有效stock private: std::vector stocks; - std::set ids_of_living_stocks; - std::set ids_of_reusable_stocks; }; } diff --git a/include/stock_id.h b/include/stock_id.h index 8fcb620..bab8eb9 100644 --- a/include/stock_id.h +++ b/include/stock_id.h @@ -26,6 +26,11 @@ namespace lzhlib { return id_; } + stock_id& operator++() + { + ++id_; + return *this; + } private: std::size_t id_; diff --git a/include/undirected_graph.h b/include/undirected_graph.h index bba2cd1..30f7c8c 100644 --- a/include/undirected_graph.h +++ b/include/undirected_graph.h @@ -42,8 +42,7 @@ namespace lzhlib template edge_id add_edge(vertex_id x, vertex_id y, Args&& ...args) { - edge_id result = base::to_edge_id(base::edge_repository - .add_stock(std::forward(args)...)); + edge_id result = base::to_edge_id(base::edge_repository.add_stock(std::forward(args)...)); base::get_edge(result).set_associated_vertices(x, y); base::get_vertex(x).add_associated_edge({result, y}); base::get_vertex(y).add_associated_edge({result, x}); diff --git a/test/test.cpp b/test/test.cpp index 22c7d80..9845ba3 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -94,7 +94,6 @@ class test_undirected_graph void test_directed_graph() { - directed_graph g; vertex_id v0 = g.add_vertex("v0"); vertex_id v1 = g.add_vertex("v1"); @@ -102,7 +101,6 @@ void test_directed_graph() edge_id e0 = g.add_edge(v0, v1); assert(g.value(e0) == ""); assert(g.get_edge(v0, v1) == e0); - g.value(e0) = "e0"; assert(g.associated(v0, e0)); assert(!g.associated(v1, e0)); assert(g.adjacent(v0, v1)); @@ -113,16 +111,17 @@ void test_directed_graph() assert(const_cast(g).value(v0) == "v0"); assert(g.value(v1) == "v1"); assert(const_cast(g).value(v1) == "v1"); + g.value(e0) = "e0"; assert(g.value(e0) == "e0"); assert(const_cast(g).value(e0) == "e0"); { - auto vertex = g.neighbors(v0); - assert(vertex.size() == 1); - assert(vertex[0] == v1); + auto vertices = g.neighbors(v0); + assert(vertices.size() == 1); + assert(vertices[0] == v1); } { - auto vertex = g.neighbors(v1); - assert(vertex.empty()); + auto vertices = g.neighbors(v1); + assert(vertices.empty()); } { diff --git a/test/white_box_test.cpp b/test/white_box_test.cpp index ea3bea5..1adea1f 100644 --- a/test/white_box_test.cpp +++ b/test/white_box_test.cpp @@ -78,25 +78,6 @@ void test_vertex_repository() std::to_string(i0.id()) + "!"); } - auto i1 = r0.add_stock(2); - assert(r0.ids_of_living_stocks.size() == 1); - assert(r0.ids_of_reusable_stocks.size() == 0); - assert(r0.get_stock(i1).vertex_value() == 2); - - auto i2 = r0.add_stock(3); - assert(r0.ids_of_living_stocks.size() == 2); - assert(r0.ids_of_reusable_stocks.size() == 0); - assert(r0.get_stock(i1).vertex_value() == 2); - assert(r0.get_stock(i2).vertex_value() == 3); - - r0.remove_stock(i1); - assert(r0.ids_of_living_stocks.size() == 1); - assert(r0.ids_of_reusable_stocks.size() == 1); - assert(r0.get_stock(i2).vertex_value() == 3); - - r0.remove_stock(i2); - assert(r0.ids_of_living_stocks.size() == 0); - assert(r0.ids_of_reusable_stocks.size() == 2); } void test_vertex_edge_and_repository()