C++ 邻接数组 实现四种图类

C++ 邻接数组 实现四种图类

    • 基类 `graph`
    • 无权无向图 `arrayGraph`
      • 无权无向边 `_edge`
      • 该图类私有成员变量
      • 构造函数
      • 插入新边的函数
      • 删除一条边的函数
      • 判断有无回路的函数
      • 基于 c++ 文件流的读入输出函数
        • 读入函数
        • 输出
      • 其他函数方法
    • 加权有向图 `arrayWDigraph`
      • 加权有向边 `WDedge`
      • 该图类私有成员变量
      • 构造函数
      • 插入新边的函数
      • 删除一条边的函数
      • 基于 c++ 文件流的读入输出函数
        • 读入函数
        • 输出
      • 其他函数方法
    • 加权无向图 `arrayWGraph`
    • 无权有向图 `arrayDigraph`

该项目基于c++实现,运用数据结构知识完成

基类 graph

C++ 邻接数组 实现四种图类_第1张图片

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

基于 c++ 文件流的读入输出函数

读入函数

文件流相关方法自行百度即可

插入新边要先构造一个 _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);
    }
}

基于 c++ 文件流的读入输出函数

读入函数

文件流相关方法自行百度即可

插入新边要先构造一个 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

至于剩下两种图类大同小异,通过前两种图类的学习再来完成简直有手就行

四种图类我写到了一个项目里,分成四个头文件保存,
有需要参考的可以直接去公众号拿源码~~
C++ 邻接数组 实现四种图类_第2张图片

你可能感兴趣的:(实战,c++,数据结构,三级项目,课程设计,图)