该项目基于c++实现,运用数据结构知识完成
graph
graph 基类为该书中程序 16-1 部分代码
template<class T>
class edge {
public:
virtual ~edge() {
}
virtual int vertex1() const = 0;
virtual int vertex2() const = 0;
virtual T weight() const = 0;
};
template<class T>
class graph {
public:
virtual ~graph() {
}
virtual int numberOfVertices() const = 0;
virtual int numberOfEdges() const = 0;
virtual bool existsEdge(int, int) const = 0;
virtual void insertEdge(edge<T>*) = 0;
virtual void eraseEdge(int, int) = 0;
virtual int degree(int) const = 0;
virtual int inDegree(int) const = 0;
virtual int outDegree(int) const = 0;
virtual bool directed() const = 0;
virtual bool weighted() const = 0;
virtual void output() const = 0;
};
继承需要将父类中所有纯虚函数在子类中重新实现~
因为 insertEdge(edge
中参数为edge
类,故在继承 graph
类前要写出继承 edge
类的子类。
arrayGraph
_edge
没什么难度,照着父类写即可
无权边,肯定是int类,故返回权重的函数直接返回 0 即可
template<class T>
class _edge : public edge<int> {
public:
~_edge() {
}
_edge(int s = 0, int e = 0) {
v1 = s;
v2 = e;
}
int vertex1() const {
return v1;
}
int vertex2() const {
return v2;
}
T weight() const {
return 0;
}
private:
int v1, v2;
};
顶点的数量,边的数量,还有存储该图类的邻接数组。
private:
int num_vertice, num_edge;
vector<vector<int>> Array;
初始化三个私有成员变量即可
记得加一个判断顶点数是否合法的语句
arrayGraph(int nv = 0) {
if (nv < 0) //如果顶点数小于0则抛出异常
throw "number of vertices must be >= 0";
num_edge = 0;
num_vertice = nv;
for (int i = 0; i < num_vertice; ++i) {
vector<int> temp;
Array.push_back(temp);
}
}
为了避免用无参构造函数定义的图类的 num_vertice
属性在邻接数组声明大小后赋值
加一个判断语句,若邻接数组大小不合适,便重新初始化一个邻接数组
再进行插入新边的操作~
void insertEdge(edge<T> *e) {
if (Array.size() != num_vertice) {
Array.clear();
for (int i = 0; i < num_vertice; ++i) {
vector<int> temp;
Array.push_back(temp);
}
}
int a = e->vertex1();
int b = e->vertex2();
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
if (!existsEdge(a, b)) {
Array[a].push_back(b);
Array[b].push_back(a);
num_edge++;
}
}
因为是无向图,故添加新边 a~b 要在 a,b 所对应的邻接数组中分别添加 b 和 a 邻接点
最后一定要记得用 num_edge++
及时更新边的数量。
因为是无向图,故删除边 a~b 要在 a,b 所对应的邻接数组中分别删除 b 和 a 邻接点
//删除一条边
void eraseEdge(int a, int b) {
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
for (int i = 0; i < Array[a].size(); ++i) {
if (Array[a][i] == b)
Array[a].erase(Array[a].begin() + i);
}
for (int i = 0; i < Array[b].size(); ++i) {
if (Array[b][i] == a)
Array[b].erase(Array[b].begin() + i);
}
}
使用一个 bool
类型数组标记该顶点是否被访问过
然后利用深度优先搜索函数判环
//判环
bool cycle() const {
if (num_edge >= num_vertice)return true;
//边数大于顶点数必存在环
vector<bool> vis(num_vertice, false);
return dfs(vis, 0);
}
//深度优先搜索函数
bool dfs(vector<bool> vis, int u, int p = -1) const
//u表示当前访问的节点,p表示的是它的前继节点。
{
if (vis[u])//出现访问过的节点,说明有环。返回上一层节继续搜索
return true;
vis[u] = true;
int m = Array[u].size();
for (int i = 0; i < m; i++) {
int v = Array[u][i];
if (v != p)
//如果当前节点不等于它的前继的话,才访问。
if (dfs(vis, v, u))
return true;
}
return false;
}
文件流相关方法自行百度即可
插入新边要先构造一个 _edge
类对象,再进行插入
//读入
void in() {
ifstream ifs;
ifs.open(FILENAME, ios::in);//读文件
if (!ifs.is_open())//判断是否存在
{
cout << "data file was not found" << endl;
ifs.close();
return;
}
int ne, nv, x1, x2;
ifs >> nv >> ne;
this->num_vertice = nv;
for (int i = 0; this->num_edge < ne; ++i) {
ifs >> x1 >> x2;
_edge<T> t(x1, x2);
_edge<T> *e = &t;
this->insertEdge(e);
}
cout << "读取文件成功!" << endl;
ifs.close();
}
记得输出顶点数量及边的数量即可
//输出
void output() const {
ofstream ofs;
ofs.open(FILENAME2, ios::out);
ofs << this->num_vertice << " " << this->num_edge << endl;
for (int i = 0; i < this->num_vertice; ++i) {
for (int j = 0; j < this->Array[i].size(); ++j) {
ofs << i << " " << Array[i][j] << endl;
}
ofs << endl;
}
}
其他方法没什么坑了,知道函数名意思就能写出来~
//返回顶点个数
int numberOfVertices() const {
return num_vertice;
}
//返回边的条数
int numberOfEdges() const {
return num_edge;
}
//判断边是否存在
bool existsEdge(int a, int b) const {
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
for (int i = 0; i < Array[a].size(); ++i) {
if (Array[a][i] == b)return true;
}
}
//度数
int degree(int i) const {
if (i < 0 || i > num_vertice) {
std::ostringstream s;
s << "no vertex " << i;
throw s.str();
}
return Array[i].size();
}
//入度
int inDegree(int k) const {
if (k < 0 || k > num_vertice) {
std::ostringstream s;
s << "no vertex " << k;
throw s.str();
}
return 0;
}
//出度
int outDegree(int k) const {
if (k < 0 || k > num_vertice) {
std::ostringstream s;
s << "no vertex " << k;
throw s.str();
}
return 0;
}
//是否有向
bool directed() const {
return false;
}
//是否加权
bool weighted() const {
return false;
}
arrayWDigraph
WDedge
同样没什么难度,照着父类写即可
加权边,权值类型不确定,此处使用模板类
template<class T>
class WDedge : public edge<T> {
public:
~WDedge() {
}
WDedge(int s = 0, int e = 0, T we = 0) {
v1 = s;
v2 = e;
w = we;
}
int vertex1() const {
return v1;
}
int vertex2() const {
return v2;
}
T weight() const {
return w;
}
private:
int v1, v2;
T w;
};
依旧是顶点的数量,边的数量,还有存储该图类的邻接数组。
因为是加权图,所以邻接数组为 int,T 的数对类型,int 为邻接点,T 为该边权值
private:
int num_vertice, num_edge;
vector<vector<pair<int, T>>> Array;
初始化三个私有成员变量即可
//构造函数
arrayWDigraph(int nv = 0) {
if (nv < 0) //如果顶点数小于0则抛出异常
throw "number of vertices must be >= 0";
num_edge = 0;
num_vertice = nv;
Array.clear();
for (int i = 0; i < num_vertice; ++i) {
vector<pair<int, T>> temp;
Array.push_back(temp);
}
}
记得加一个判断顶点数是否合法的语句
为了避免用无参构造函数定义的图类的 num_vertice
属性在邻接数组声明大小后赋值
加一个判断语句,若邻接数组大小不合适,便重新初始化一个邻接数组
再进行插入新边的操作~
//插入一条边
void insertEdge(edge<T> *e) {
if (Array.size() != num_vertice) {
Array.clear();
for (int i = 0; i < num_vertice; ++i) {
vector<pair<int, T>> temp;
Array.push_back(temp);
}
}
int a = e->vertex1(), b = e->vertex2();
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
if (!existsEdge(a, b)) {
Array[a].push_back({
b, e->weight()});
num_edge++;
}
}
最后一定要记得用 num_edge++
及时更新边的数量。
因为是有向图,故删除边 a~b 只需删除顶点 a 的邻接点 b 即可
//删除一条边
void eraseEdge(int a, int b) {
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
for (int i = 0; i < Array[a].size(); ++i) {
if (Array[a][i].first == b)
Array[a].erase(Array[a].begin() + i);
}
}
文件流相关方法自行百度即可
插入新边要先构造一个 WDedge
类对象,再进行插入
void in() {
ifstream ifs;
ifs.open(WDFILENAME, ios::in);//读文件
if (!ifs.is_open())//判断是否存在
{
cout << "data file was not found" << endl;
ifs.close();
return;
}
int ne, nv, x1, x2;
T w;
ifs >> nv >> ne;
this->num_vertice = nv;
for (int i = 0; this->num_edge < ne; ++i) {
ifs >> x1 >> x2 >> w;
WDedge<T> t(x1, x2, w);
WDedge<T> *e = &t;
this->insertEdge(e);
}
cout << "读取文件成功!" << endl;
ifs.close();
}
记得输出顶点数量及边的数量即可
因为加权,一行输出三个数据分别表示起始点,终点及该边权值。
//输出
void output() const {
ofstream ofs;
ofs.open(WDFILENAME2, ios::out);
ofs << this->num_vertice << " " << this->num_edge << endl;
for (int i = 0; i < this->num_vertice; ++i) {
for (int j = 0; j < this->Array[i].size(); ++j) {
ofs << i << " " << Array[i][j].first << " " << Array[i][j].second << endl;
}
ofs << endl;
}
}
其他方法没什么坑了,知道函数名意思就能写出来~
//返回顶点个数
int numberOfVertices() const {
return num_vertice;
}
//返回边的条数
int numberOfEdges() const {
return num_edge;
}
//判断边是否存在
bool existsEdge(int a, int b) const {
if (a < 0 || a > num_vertice || b < 0 || b > num_vertice) {
std::ostringstream s;
s << "no vertex ";
throw s.str();
}
for (int i = 0; i < Array[a].size(); ++i) {
if (Array[a][i].first == b)return true;
}
return false;
}
//度数
int degree(int i) const {
return this->outDegree(i) + this->inDegree(i);
}
//入度
int inDegree(int k) const {
if (k < 0 || k > num_vertice) {
std::ostringstream s;
s << "no vertex " << k;
throw s.str();
}
int res = 0;
for (int i = 0; i < num_vertice; ++i) {
if (i != k) {
for (int j = 0; j < Array[i].size(); ++j) {
if (Array[i][j].first == k)
res++;
}
}
}
return res;
}
//出度
int outDegree(int a) const {
if (a < 0 || a > num_vertice) {
std::ostringstream s;
s << "no vertex " << a;
throw s.str();
}
return Array[a].size();
}
//是否有向
bool directed() const {
return true;
}
//是否加权
bool weighted() const {
return true;
}
arrayWGraph
arrayDigraph
至于剩下两种图类大同小异,通过前两种图类的学习再来完成简直有手就行