邻接表的实现
这个实现比某些C/C++数据结构书上的简洁, 我没有看别的代码, 只针对够用的需求;邻接表的实现使用了模板,用non-type template技巧塞入图的结点个数,为的不想用一个常量或者宏定义最大的vertex个数, 然后声明静态数组;用模板实现, 类的member method等成员定义需要放在.h文件中, 与类的声明放在一起. 这个地方我结结实实的摔了一跤,详见踩坑部分;拓扑排序算解释:
设定in_degree数组, 从边集和中, 设定好每个结点入度;对vertex number循环
查找入度为0的结点, 作为当前节点
若失败, 退出排序, 说明非DAG, 存在环; 处理当前节点出边影响到的节点的入度, 在in_degree数组中更新计数;=
#ifndef __ADJ_GRAPH__
#define __ADJ_GRAPH__
#include <iostream>
#include <vector>
struct ArchNode
{
int adj_vertex
;
ArchNode
* next
;
};
struct VertexNode
{
int vertex_id
;
ArchNode
* first
;
ArchNode
* tail
;
};
struct Arch
{
int beg_vnode_idx
, end_vnode_idx
;
int weight
;
Arch(int i
, int j
, int w
):
beg_vnode_idx(i
), end_vnode_idx(j
),
weight(w
) {}
Arch(const Arch
& other
) {
if (this != &other
) {
beg_vnode_idx
= other
.beg_vnode_idx
;
end_vnode_idx
= other
.end_vnode_idx
;
weight
= other
.weight
;
}
}
std
::pair
<int, int> GetPair() {
return std
::make_pair(beg_vnode_idx
, end_vnode_idx
);
}
int GetWeight() {
return weight
;
}
};
enum GraphType
{
UDG
,
DG
};
template <unsigned T
>
class AdjacentListGraph {
public:
AdjacentListGraph(GraphType graph_type
, int e
, int n
= T
);
AdjacentListGraph(GraphType graph_type
, int e
, const std
::vector
<Arch
>& arch_list
);
void CreateAdjacentList(GraphType g
);
void AddToArchList(Arch
* arch
) {
arch_list_
.push_back(arch
);
}
void InsertArch(int vertex_idx
, int adjacent_node_idx
);
void MakeArchList(const std
::vector
<Arch
>& arch_list
);
void MakeArchList(const std
::pair
<int, int>*, size_t
);
bool TopologicalSort();
private:
VertexNode
* vertex_nodes_
;
std
::vector
<Arch
*> arch_list_
;
int vertex_num_
;
int arch_num_
;
GraphType graph_type_
;
};
template <unsigned T
>
AdjacentListGraph
<T
>::AdjacentListGraph(GraphType graph_type
, int e
, int n
) :
vertex_num_(n
),
arch_num_(e
),
graph_type_(graph_type
) {
vertex_nodes_
= new VertexNode
[vertex_num_
];
for (int i
= 0; i
< vertex_num_
; ++i
) {
vertex_nodes_
[i
].vertex_id
= i
+ 1;
vertex_nodes_
[i
].first
= nullptr;
vertex_nodes_
[i
].tail
= nullptr;
}
if (arch_num_
<= 0) return;
std
::cout
<< "input the arch data: (v1, v2, weight)" << std
::endl
;
int i
, j
, w
;
int count
= 0;
while (count
++ < arch_num_
) {
std
::cin
>> i
>> j
>> w
;
Arch
* arch
= new Arch(i
, j
, w
);
AddToArchList(arch
);
}
CreateAdjacentList(graph_type_
);
}
template <unsigned T
>
AdjacentListGraph
<T
>::AdjacentListGraph(GraphType graph_type
, int e
, const std
::vector
<Arch
>& arch_list
) :
vertex_num_(T
),
arch_num_(e
),
graph_type_(graph_type
) {
vertex_nodes_
= new VertexNode
[vertex_num_
];
for (int i
= 0; i
< vertex_num_
; ++i
) {
vertex_nodes_
[i
].vertex_id
= i
+ 1;
vertex_nodes_
[i
].first
= nullptr;
vertex_nodes_
[i
].tail
= nullptr;
}
if (arch_num_
<= 0) return;
MakeArchList(arch_list
);
CreateAdjacentList(graph_type_
);
}
template <unsigned T
>
void AdjacentListGraph
<T
>::MakeArchList(const std
::vector
<Arch
>& arch_list
) {
for (auto archItem
: arch_list
) {
auto arch
= new Arch(archItem
);
AddToArchList(arch
);
}
}
template <unsigned T
>
void AdjacentListGraph
<T
>::MakeArchList(const std
::pair
<int, int>* arch_list
, size_t size
) {
for (unsigned int i
= 0; i
< size
; ++i
) {
auto arch
= new Arch(arch_list
[i
].first
, arch_list
[i
].second
, 0);
AddToArchList(arch
);
}
}
template <unsigned T
>
void AdjacentListGraph
<T
>::CreateAdjacentList(GraphType t
) {
for (auto arch
: arch_list_
) {
auto pair
= arch
->GetPair();
int head
= pair
.first
, end
= pair
.second
;
InsertArch(head
, end
);
if (t
== UDG
) {
InsertArch(end
, head
);
}
}
}
template <unsigned T
>
void AdjacentListGraph
<T
>::InsertArch(int vertex_idx
, int adjacent_node_idx
) {
ArchNode
* a
= new ArchNode
{adjacent_node_idx
, nullptr};
VertexNode
& v
= vertex_nodes_
[vertex_idx
- 1];
v
.tail
== nullptr ? v
.first
= a
: v
.tail
->next
= a
;
v
.tail
= a
;
}
template <unsigned T
>
bool AdjacentListGraph
<T
>::TopologicalSort() {
const size_t n
= vertex_num_
;
int in_degree
[n
] = {0};
std
::vector
<int> result
;
for (int i
= 0; i
< vertex_num_
; ++i
) {
auto& m
= vertex_nodes_
[i
];
ArchNode
* x
= m
.first
;
if (x
== nullptr)
continue;
int j
= x
->adj_vertex
;
++in_degree
[j
- 1];
while ((x
= x
->next
)) {
j
= x
->adj_vertex
;
++in_degree
[j
- 1];
}
}
int count
= 0;
while (count
++ < vertex_num_
) {
int start_idx
= -1;
for (int j
= 0; j
< vertex_num_
; ++j
) {
if (in_degree
[j
] == 0) {
start_idx
= j
;
break;
}
}
if (start_idx
== -1)
break;
result
.push_back(start_idx
);
ArchNode
* a
= vertex_nodes_
[start_idx
].first
;
while (a
) {
in_degree
[a
->adj_vertex
- 1]--;
a
= a
->next
;
}
in_degree
[start_idx
] = -1;
}
if (count
< vertex_num_
) {
std
::cout
<< "不能完成拓扑排序, 存在环" << std
::endl
;
return false;
}
std
::cout
<< "Topological sort result is:";
for (auto it
: result
) {
int k
= it
;
std
::cout
<< k
+ 1 << " ";
}
std
::cout
<< std
::endl
;
return true;
}
#endif
测试:
#include <iostream>
#include <vector>
#include "adj_graph.h"
int main() {
Arch dag_arches
[] = {
{1, 2, 0}, {1, 4, 0}, {2, 4, 0}, {2, 3, 0},
{3, 5, 0}, {4, 3, 0}, {4, 5, 0}
};
AdjacentListGraph
<5> dag(DG
, sizeof(dag_arches
), std
::vector
<Arch
>(std
::begin(dag_arches
), std
::end(dag_arches
)));
dag
.TopologicalSort();
return 0;
}
邻接矩阵的实现
使用模板需要把成员方法放在和模板声明的同一个编译单元, 不利于模块化; 邻接矩阵的实现用matrix_graph.h, matrix_graph.cc分开组织代码;
二维数组: 一个麻烦的但是可行的方法
由于需要在对象创建时刻确定vertex number; 在写代码时无法确定数组的维度, 我用void*引用vertex结点2维数组。在创建数组时用malloc返回2维int数组正确大小的内存空间, 把void* 转成int (*)[n][n]指向数组的指针,再根据边集的情况设定2维数组的每个元素(weight值); 每次使用void* 指针时, 需要把它转成int (*)[n][n], 例如在CreateADJMatrixGraph()和TopologicalSort()当中;
void* matrix_ = malloc(sizeof(int) * n * n);
// 转成指向二维数组的指针
int (matrix_arr*)[n][n] = static_cast<int (*)[n][n]>(matrix_arr);
// *(*matrix_array + i) + j => 指到每个元素的指针;
// 设定矩阵每个元素:
*(*(*matrix_array + i) + j) = (archIt->second).GetWeight();
后来在看到一本数据结构的文章时发现了一个简便的方法
二维数组第二个方法: 简便的形式
matrix_指针int** matrix
int** matrix_ = new int*[n];
for (int i = 0; i < n; i++) {
matrix_[i] = new int[n];
}
// 这样在使用时不需要转换类型, 可以直接使用matrix[i][j]的形式;
matrix[i][j] = 1;
以下使用了第一种形式的代码
#include <iostream>
#include <unordered_map>
enum GraphType
{
DG
,
UDG
};
struct Arch
{
int beg_vertex_idx
;
int end_vertex_idx
;
int weight
;
Arch(int i
, int j
, int w
):
beg_vertex_idx(i
), end_vertex_idx(j
),
weight(w
) {}
Arch(const Arch
& other
) {
if (this != &other
) {
beg_vertex_idx
= other
.beg_vertex_idx
;
end_vertex_idx
= other
.end_vertex_idx
;
weight
= other
.weight
;
}
}
std
::pair
<int, int> GetPair() {
return std
::make_pair(beg_vertex_idx
, end_vertex_idx
);
}
int GetWeight() {
return weight
;
}
};
struct VertexNode
{
int vertex_id
;
};
class ADJMatrixGraph {
public:
ADJMatrixGraph(GraphType type
, const int vertex_num
, struct Arch arch_list
[], const int arch_num
);
void print_matrix();
void TopologicalSort();
private:
void AddToArchList(Arch arch
) {
std
::pair
<int, int> arch_pair
= arch
.GetPair();
std
::string s
= std
::to_string(arch_pair
.first
- 1) + "_" + std
::to_string(arch_pair
.second
- 1);
arch_list_
.insert(std
::make_pair(s
, arch
));
}
void MakeArchList(struct Arch arch_list
[], size_t arch_num
);
void CreateADJMatrixGraph(GraphType
);
GraphType graph_type_
;
int vertex_num_
;
int arch_num_
;
VertexNode
* vertex_nodes_
;
std
::unordered_map
<std
::string
, Arch
> arch_list_
;
void* matrix_
;
};
#include <stdio.h>
#include <vector>
#include "matrix_graph.h"
ADJMatrixGraph
::ADJMatrixGraph(GraphType type
, const int vertex_num
, struct Arch arch_list
[], const int arch_num
):
graph_type_(type
),
vertex_num_(vertex_num
),
arch_num_(arch_num
),
vertex_nodes_(new VertexNode
[vertex_num
]) {
MakeArchList(arch_list
, arch_num
);
CreateADJMatrixGraph(type
);
}
void ADJMatrixGraph
::MakeArchList(struct Arch arch_list
[], size_t arch_num
) {
for (unsigned int i
= 0; i
< arch_num
; ++i
) {
auto arch
= arch_list
[i
];
AddToArchList(arch
);
if (graph_type_
== UDG
) {
}
}
}
void ADJMatrixGraph
::print_matrix() {
int n
= vertex_num_
;
int (*matrix_arr
)[n
][n
] = static_cast<int (*)[n
][n
]>(matrix_
);
for (int i
= 0; i
< n
; ++i
) {
std
::cout
<< "\ni=" << i
<< ":";
for (int j
= 0; j
< n
; ++j
) {
std
::cout
<< *(*(*matrix_arr
+ i
) + j
) << " ";
}
}
std
::cout
<< std
::endl
;
}
void ADJMatrixGraph
::CreateADJMatrixGraph(GraphType type
) {
int n
= vertex_num_
;
void* matrix
= malloc(sizeof(int) * n
* n
);
int (*matrix_arr
)[n
][n
] = static_cast<int (*)[n
][n
]>(matrix
);
for (int i
= 0; i
< n
; ++i
) {
for (int j
= 0; j
< n
; ++j
) {
std
::string s
= std
::to_string(i
) + "_" + std
::to_string(j
);
auto archIt
= arch_list_
.find(s
);
if (archIt
!= arch_list_
.end()) {
*(*(*matrix_arr
+ i
) + j
) = (archIt
->second
).GetWeight();
} else {
*(*(*matrix_arr
+ i
) + j
) = 0;
}
}
}
matrix_
= matrix
;
}
void ADJMatrixGraph
::TopologicalSort() {
const size_t n
= vertex_num_
;
int in_degree
[n
] = {0};
std
::vector
<int> result
;
int (*matrix
)[n
][n
] = static_cast<int (*)[n
][n
]>(matrix_
);
for (unsigned int i
= 0; i
< n
; ++i
) {
for (unsigned int j
= 0; j
< n
; ++j
) {
if (*(*(*matrix
+ i
) + j
) > 0) {
in_degree
[j
]++;
}
}
}
unsigned int count
= 0;
while (count
++ < n
) {
int start_idx
= -1;
for (unsigned int j
= 0; j
< n
; ++j
) {
if (in_degree
[j
] == 0) {
start_idx
= j
;
break;
}
}
if (start_idx
== -1)
break;
result
.push_back(start_idx
);
for (unsigned int k
= 0; k
< n
; ++k
) {
if (*(*(*matrix
+ start_idx
) + k
) > 0) {
in_degree
[k
]--;
}
}
in_degree
[start_idx
] = -1;
}
if (count
< n
) {
std
::cout
<< "非DAG, 可能存在环" << std
::endl
;
return false;
}
std
::cout
<< "Topological sort result is:";
for (auto it
: result
) {
int k
= it
;
std
::cout
<< k
+ 1 << " ";
}
std
::cout
<< std
::endl
;
return true;
}
测试
#include "matrix_graph.h"
int main() {
struct Arch dag_arches
[] = {
{1, 2, 1}, {1, 4, 1}, {2, 4, 1}, {2, 3, 1},
{3, 5, 1}, {4, 3, 1}, {4, 5, 1}
};
ADJMatrixGraph
matrix_graph(DG
, 5, dag_arches
, 7);
matrix_graph
.print_matrix();
matrix_graph
.TopologicalSort();
return 0;
}
踩坑
用nontype template实现邻接表。 注意template的类实现,在真正出创建template实例的对象之前,不会真正实例化template类, 也就没有符号表中某些元素, 如template类的构造函数等成员函数; 只有在创建template类的定义时, 才会在.o文件中出现该符号; 忽略此点, 很容易出现不理解的undefined reference to xxx错误;
并不是常见的LD_LIBRARY等so文件搜索路径的问题;用nm看, 符号确实不出现在o文件中, 但确实源代码中却有成员定义;在调用template .h文件的外部测试文件中,有时必须将需要其他header引入, 即使在template的实现h文件中已经引入过;
由于以上此点约束, 使用template类常常只有把全部实现写在.h文件中; 这样不利于模块化代码;
邻接矩阵实现使用malloc; 在gdb中输出using过的type是有问题的; 需要使用原始的指针定义;
结果
优化
查找入度为0的节点的查找过程O(n)的, 可以最小堆, 每次在取得入度为0的节点时, 取走堆顶元素; 调整堆把下一个最小的为0的节点调整到堆顶; 增加的时间是调整堆的开销O(logN), 而查找入度0的节点则是O(1); (todo)