图的存储之邻接表

1、稀疏矩阵

有一个稀疏因子,这是节省空间的一种存储方式。

2、邻接表

以邻接矩阵存储图结构的话,当实际边数远远小于图的最大边数时,将会存储很多0,势必造成存储空间的巨大浪费;这时,就必须将邻接矩阵该用为邻接表;将邻接矩阵各行组织为一个单链表,类哈希的存储结构。

存储结构(控制头):

int maxVertices;  //最大顶点数
int curVertices;  //当前顶点数
int curEdges;  //当前边数

template<typename Type>
class Edge{  //边的存储结构
public:
    Edge(int num) : dest(num), link(NULL){}
public:
    int dest;  //是另一个顶点的下标
    Edge *link;
};
template<typename Type>
class Vertex{  //顶点的存储结构
public:
    Type data;  //存放的顶点
    Edge<Type> *adj;
};

Vertex<Type> *vertexTable;  //指向顶点的指针,是申请数组用的

存储模型:

3、核心方法

    均由C++实现,无向图的邻接表;

(1)、删除边(是链表的删除操作,相对简单):

bool removeEdge(const Type &v1, const Type &v2){  //删除边
    int i = getVertexIndex(v1);
    int j = getVertexIndex(v2);

    if(i==-1 || j==-1){  //保证顶点的保存在
        return false;
    }
    //v1-->v2
    Edge<Type> *p = vertexTable[i].adj;
    if(p == NULL){  //判断有没有边
        return false;
    }

    if(p->link == NULL && p->dest == j){ //删除的是第一个边,其后没有边了;
        vertexTable[i].adj = NULL;
        delete p;    
    }else if(p->dest == j){  //删除的是第一个边,并且其后还有边
        vertexTable[i].adj = p->link;
        delete p;
    }else{
        while(p->link != NULL){
            if(p->link->dest == j){
                Edge<Type> *q = p->link;
                p->link = q->link;
                delete q;
            }
            p = p->link;
        }
    }
    //v2-->v1
    Edge<Type> *s = vertexTable[j].adj;
    if(s == NULL){  //判断有没有边
        return false;
    }

    if(s->link == NULL && s->dest == i){ //删除的是第一个边,其后没有边了;
        vertexTable[j].adj = NULL;
        delete s;
        curEdges--;
        return false;
    }else if(s->dest == i){  //删除的是第一个边,并且其后还有边
        vertexTable[j].adj = s->link;
        delete s;
        curEdges--;
        return true;
    }else{
        while(s->link != NULL){
            if(s->link->dest == i){
                Edge<Type> *q = s->link;
                s->link = q->link;
                delete q;
                curEdges--;
                return true;
            }
            s = s->link;
        }
    }

    return true;
}

(2)、删除顶点:

这个算法相对复杂,但是思路比较清晰:

i>、首先找到要删除的顶点,将其后上的边所对应的边和这个边都得删除;

ii>、将最后一个顶点的data和adj都覆盖到这个地方;

iii>、找到其后边上的dest,更改为当下位置的下标;

大致模型如下:

bool removeVertex(const Type &v){  //删除顶点
    int i = getVertexIndex(v);
    if(i == -1){
        return false;
    }

    Edge<Type> *p = vertexTable[i].adj;  //先删除边上的dest和此边
    while(p != NULL){
        vertexTable[i].adj = p->link;
        int k = p->dest;
        Edge<Type> *q = vertexTable[k].adj;
        if(q->dest == i){
            vertexTable[k].adj = q->link;
            delete q;
        }else{
            while(q->link != NULL && q->link->dest != i){
                q = q->link;
            }
            Edge<Type> *t = q->link;
            q->link = t->link;
            delete t;
        }
        delete p;
        p = vertexTable[i].adj;
        curEdges--;
    }

    curVertices--;  //下面实行覆盖,指针和最后的那个顶点的adj相等;
    vertexTable[i].data = vertexTable[curVertices].data;
    vertexTable[i].adj = vertexTable[curVertices].adj;
    vertexTable[curVertices].adj = NULL;

    int k = curVertices;
    p = vertexTable[i].adj;
    while(p != NULL){  //修改其它顶点的dest.
        Edge<Type> *s = vertexTable[p->dest].adj;
        while(s != NULL){
            if(s->dest == k){
                s->dest = i;
                break;
            }
            s = s->link;
        }
        p = p->link;
    }
    return true;
}

4、邻接表完整代码、测试代码、测试结果

(1)完整代码(用的是继承,方便写其它的存储结构代码):

#ifndef _GRAPH_H_
#define _GRAPH_H_

#include<iostream>
using namespace std;

#define VERTEX_DEFAULT_SIZE        10

template<typename Type>    
class Graph{
public:
    bool isEmpty()const{
        return curVertices == 0;
    }
    bool isFull()const{
        if(curVertices >= maxVertices || curEdges >= curVertices*(curVertices-1)/2)
            return true;  //图满有2种情况:(1)、当前顶点数超过了最大顶点数,存放顶点的空间已满
        return false;     //(2)、当前顶点数并没有满,但是当前顶点所能达到的边数已满
    }
    int getCurVertex()const{
        return curVertices;
    }
    int getCurEdge()const{
        return curEdges;
    }
public:
    virtual bool insertVertex(const Type &v) = 0;  //插入顶点
    virtual bool insertEdge(const Type &v1, const Type &v2) = 0; //插入边
    virtual bool removeVertex(const Type &v) = 0;  //删除顶点
    virtual bool removeEdge(const Type &v1, const Type &v2) = 0; //删除边
    virtual int getFirstNeighbor(const Type &v) = 0; //得到第一个相邻顶点
    virtual int getNextNeighbor(const Type &v, const Type &w) = 0; //得到下一个相邻顶点
public:
    virtual int getVertexIndex(const Type &v)const = 0; //得到顶点下标
    virtual void showGraph()const = 0;  //显示图
protected:
    int maxVertices;  //最大顶点数
    int curVertices;  //当前顶点数
    int curEdges;  //当前边数
};

template<typename Type>
class Edge{  //边的存储结构
public:
    Edge(int num) : dest(num), link(NULL){}
public:
    int dest;
    Edge *link;
};
template<typename Type>
class Vertex{  //顶点的存储结构
public:
    Type data;
    Edge<Type> *adj;
};
template<typename Type>
class GraphLnk : public Graph<Type>{
#define maxVertices  Graph<Type>::maxVertices  //因为是模板,所以用父类的数据或方法都得加上作用域限定符
#define curVertices  Graph<Type>::curVertices
#define curEdges     Graph<Type>::curEdges
public:
    GraphLnk(int sz = VERTEX_DEFAULT_SIZE){
        maxVertices = sz > VERTEX_DEFAULT_SIZE ? sz : VERTEX_DEFAULT_SIZE;
        vertexTable = new Vertex<Type>[maxVertices];
        for(int i = 0; i < maxVertices; i++){
            vertexTable[i].data = 0;
            vertexTable[i].adj = NULL;
        }

        curVertices = curEdges = 0;
    }
public:
    bool insertVertex(const Type &v){
        if(curVertices >= maxVertices){
            return false;
        }
        vertexTable[curVertices++].data = v;
        return true;
    }
    bool insertEdge(const Type &v1, const Type &v2){
        int v = getVertexIndex(v1);
        int w = getVertexIndex(v2);

        if(v==-1 || w==-1){
            return false;
        }

        Edge<Type> *p = vertexTable[v].adj;
        while(p != NULL){  //这里主要判断边是否已经存在
            if(p->dest == w){   //无向图,判断一边即可;
                return false;
            }
            p = p->link;
        }
        //v1-->v2  //采用头插
        Edge<Type> *s = new Edge<Type>(w);
        s->link = vertexTable[v].adj;
        vertexTable[v].adj = s;

        //v2-->v1  //采用头插
        Edge<Type> *q = new Edge<Type>(v);
        q->link = vertexTable[w].adj;
        vertexTable[w].adj = q;
        
        curEdges++;
        return true;
    }
    bool removeVertex(const Type &v){
        int i = getVertexIndex(v);
        if(i == -1){
            return false;
        }

        Edge<Type> *p = vertexTable[i].adj;
        while(p != NULL){
            vertexTable[i].adj = p->link;
            int k = p->dest;
            Edge<Type> *q = vertexTable[k].adj;
            if(q->dest == i){
                vertexTable[k].adj = q->link;
                delete q;
            }else{
                while(q->link != NULL && q->link->dest != i){
                    q = q->link;
                }
                Edge<Type> *t = q->link;
                q->link = t->link;
                delete t;
            }
            delete p;
            p = vertexTable[i].adj;
            curEdges--;
        }

        curVertices--;  //下面实行覆盖
        vertexTable[i].data = vertexTable[curVertices].data;
        vertexTable[i].adj = vertexTable[curVertices].adj;
        vertexTable[curVertices].adj = NULL;

        int k = curVertices;
        p = vertexTable[i].adj;
        while(p != NULL){
            Edge<Type> *s = vertexTable[p->dest].adj;
            while(s != NULL){
                if(s->dest == k){
                    s->dest = i;
                    break;
                }
                s = s->link;
            }
            p = p->link;
        }
        return true;
    }
    bool removeEdge(const Type &v1, const Type &v2){
        int i = getVertexIndex(v1);
        int j = getVertexIndex(v2);

        if(i==-1 || j==-1){  //保证顶点的保存在
            return false;
        }
        //v1-->v2
        Edge<Type> *p = vertexTable[i].adj;
        if(p == NULL){  //判断有没有边
            return false;
        }

        if(p->link == NULL && p->dest == j){ //删除的是第一个边,其后没有边了;
            vertexTable[i].adj = NULL;
            delete p;    
        }else if(p->dest == j){  //删除的是第一个边,并且其后还有边
            vertexTable[i].adj = p->link;
            delete p;
        }else{
            while(p->link != NULL){
                if(p->link->dest == j){
                    Edge<Type> *q = p->link;
                    p->link = q->link;
                    delete q;
                }
                p = p->link;
            }
        }
        //v2-->v1
        Edge<Type> *s = vertexTable[j].adj;
        if(s == NULL){  //判断有没有边
            return false;
        }

        if(s->link == NULL && s->dest == i){ //删除的是第一个边,其后没有边了;
            vertexTable[j].adj = NULL;
            delete s;
            curEdges--;
            return false;
        }else if(s->dest == i){  //删除的是第一个边,并且其后还有边
            vertexTable[j].adj = s->link;
            delete s;
            curEdges--;
            return true;
        }else{
            while(s->link != NULL){
                if(s->link->dest == i){
                    Edge<Type> *q = s->link;
                    s->link = q->link;
                    delete q;
                    curEdges--;
                    return true;
                }
                s = s->link;
            }
        }

        return true;
    }
    int getFirstNeighbor(const Type &v){
        int i = getVertexIndex(v);
        if(i != -1){
            Edge<Type> *p = vertexTable[i].adj;
            if(p != NULL){
                return p->dest;
            }
        }

        return -1;
    }
    int getNextNeighbor(const Type &v, const Type &w){
        int i = getVertexIndex(v);
        int j = getVertexIndex(w);

        if(i==-1 || j==-1){
            return -1;
        }
        Edge<Type> *p = vertexTable[i].adj;
        while(p != NULL){
            if(p->dest == j && p->link != NULL){
                return p->link->dest;
            }
            p = p->link;
        }

        return -1;
    }
public:
    int getVertexIndex(const Type &v)const{
        for(int i = 0; i < curVertices; i++){
            if(vertexTable[i].data == v){
                return i;
            }
        }

        return -1;
    }
    void showGraph()const{
        for(int i = 0; i < curVertices; i++){
            cout<<vertexTable[i].data<<":-->";
            Edge<Type> *p = vertexTable[i].adj;
            while(p != NULL){
                cout<<p->dest<<"-->";
                p = p->link;
            }
            cout<<"Nul. "<<endl;
        }    
    }
private:
    Vertex<Type> *vertexTable;  //指向顶点的指针,是申请数组用的
};

#endif

(2)、测试代码:

#include"Graph.h"

int main(void){
    GraphLnk<char> gl;
    gl.insertVertex(‘A‘);
    gl.insertVertex(‘B‘);
    gl.insertVertex(‘C‘);
    gl.insertVertex(‘D‘);
    gl.insertEdge(‘A‘,‘B‘);
    gl.insertEdge(‘A‘,‘D‘);
    gl.insertEdge(‘B‘,‘C‘);
    gl.insertEdge(‘C‘,‘D‘);
    gl.showGraph();

    cout<<gl.getFirstNeighbor(‘A‘)<<endl;
    cout<<gl.getNextNeighbor(‘A‘,‘B‘)<<endl;
    gl.removeEdge(‘B‘,‘C‘);
    cout<<"---------------------"<<endl;
    gl.removeVertex(‘B‘);
    gl.showGraph();

    return 0;
}

(3)、测试结果:

测试的图:

时间: 2024-10-15 22:03:02

图的存储之邻接表的相关文章

图的存储形式——邻接表

邻接表:邻接表是图的一种链式存储结构.在邻接表中,对图中每个顶点建立一个单链表,第i个单链表中的节点表示依附于顶点vi的边(对有向图是以顶点vi为尾的弧).每个结点有三个域组成,其中邻接点域指示与顶点vi邻接的点在途中的位置,链域指示下一条边或者弧的结点:数据域存储和边或者弧相关的信息,如权值等.每个链表上附设一个表头结点.在表头结点中,除了设置链域指向链表第一个结点之外,还设置有存储顶点vi的名.如下所示: 实现: /**************************************

算法导论--图的存储(邻接表与邻接矩阵)

转载请注明出处:勿在浮沙筑高台http://blog.csdn.net/luoshixian099/article/details/51888031 图的存储方法有邻接表.邻近矩阵.邻接多重表.十字链表等.本篇文章介绍两种简单且比较常用的两种方法:邻接表与邻接矩阵方法. 以下面的无向图为例,介绍两种存储方法.有向图的存储方法类似,只是边是单方向,无向图的边可以看做双向. 1.邻接链表法 邻接链表表示法对图中的每个顶点建立一个带头的边链表:第i条链表代表依附于顶点vi所有边信息,若为有向图,则表示

图的深度优先遍历--邻接表实现

这里用邻接表实现图的深度优先遍历,采用递归实现. #include<iostream> using namespace std; #define VERTEXNUM 5//结点数 struct edgenode { int to; int weight; // 边的权值 edgenode *next; }; struct vnode { int from; edgenode *first; }; void createGraph(vnode *adjilist, int start, int

图的邻接矩阵与邻接表

一.如何创建邻接表和邻接矩阵? 如图,根据上图建立一个无向图的邻接矩阵和邻接表~ 输入的数据,第一行为两个整数n,e(0<n<=1000,0<e<=5000),表示有n个点和e条边,接下来的e行,每行包含2个整数,表示每条边所连接的两个点. 然后输出图.(无硬性要求) 输入数据: 5 81 01 21 32 32 43 43 04 0 输出数据(示例): 0->1->3->4->NULL1->0->2->3->NULL2->1-

以邻接表作为存储结构的图的深度优先遍历和广度优先遍历(c++版)

一.图的存储 用邻接表法存储图,存储结构分为两部分,一部分为存储图的所有顶点的数组,另一部分为挂载在数组的每个元素后面的用来表示顶点的邻接点的链表. 1.存储顶点的结构单元为: class vnode { public: string nodename; bool visted;//进行图的遍历时用于标记图是否被访问过 node *next; vnode() { visted = false; next = NULL; } }; 链表的结构单元为: class node { public: st

图的存储结构之邻接表(详解)

之前我们介绍过图的邻接矩阵存储法,它的空间和时间复杂度都是N2,现在我来介绍另外一种存储图的方法:邻接表,这样空间和时间复杂度就都是M.对于稀疏图来说,M要远远小于N2.先上数据,如下. 1 2 3 4 5 6 4 5 1 4 9 4 3 8 1 2 5 2 4 6 1 3 7 第一行两个整数n m.n表示顶点个数(顶点编号为1~n),m表示边的条数.接下来m行表示,每行有3个数x y z,表示顶点x到顶点y的边的权值为z.下图就是一种使用链表来实现邻接表的方法. 上面这种实现方法为图中的每一个

图的邻接表表示与无环图的拓扑排序

一.  图的最常用的表示方法是邻接矩阵和邻接表. 1,邻接矩阵 邻接矩阵其实就是一个二维数组,对于每条边<u,v>,我们就令A[u][v] = 1,如果图为有权图,我们也可以令A[u][v]等于该权,这么表示的优点是非常简单,但是它的空间需求很大,如果图是稠密的,邻接矩阵是合适的表示方法,如果图是稀疏的,那这种方法就太浪费空间了,下面给出图的邻接矩阵表示例子. 2 邻接表 邻接表是图的常用储存结构之一.邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点.如下图

浅谈数据结构之图的邻接表深度和广度优先遍历(九)

邻接矩阵是一种不错的图存储结构,但是我们发现,对于边数相对较少的图,这种结构是存在对存储空间的极大浪费的.我们知道,顺序存储结构存在预先分配内存可能造成空间浪费的问题,于是引出了链式存储的结构.同样的,我们也可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题.因此,对于图的存储结构,我们同样引入了一种数组与链表相组合的存储方法,我们一般称之为邻接表. 邻接表的处理方法是这样的:(1).图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点的信息,更加方便:另

数据结构学习笔记05图 (邻接矩阵 邻接表--&gt;BFS DFS)

数据结构之图 图(Graph) 包含 一组顶点:通常用V (Vertex) 表示顶点集合 一组边:通常用E (Edge) 表示边的集合 边是顶点对:(v, w) ∈E ,其中v, w ∈ V 有向边<v, w> 表示从v指向w的边(单行线) 不考虑重边和自回路 无向图:边是无向边(v, w) 有向图:边是有向边<v, w> 连通:如果从V到W存在一条(无向)路径,则称V和W是连通的 连通图(Connected Graph):如果对于图的任一两个顶点v.w∈V,v和w都是连通的,则称