邻接链表

  • 为了进一步提高空间效率,可以考虑使用链表替换数组,将邻接矩阵变换为邻接链表;     
  • 占用空间就是因为邻接矩阵的问题,没有连接也要占用四个字节的空间可以考虑无连接不占用空间的情况;      
  • 数组在定义的时候要指明有多少个元素,这样可能导致浪费,可以用链表,需要的时候再增加,不需要预定义一共有多少个元素

邻接链表的表示方法

  1. 图中的所有顶点按照编号存储于同一个链表中             
  • 原来存储在数组中,这里存储在链表中;     
  1. 每一个顶点对应一个链表,用于存储始发于该顶点的边;             
  • 链表中包含的信息见 3;             
  • 将数组改成链表;      

3.每一条边的信息包含:起点,终值,权值;
image.png


#ifndef LISTGRAPH_H
#define LISTGRAPH_H
#include "Graph.h"
#include "LinkList.h"
#include "Exception.h"
#include "DynamicArray.h"
namespace DTLib
{
/* 适用于内存资源受限场合 */
template < typename V, typename E >
class ListGraph : public Graph<V, E>
{
protected:
    /* 定义顶点 */
struct Vertex : public Object    
    {
        V* data;  // 顶点的数据成员
        LinkList< Edge<E> > edge;  // 邻接链表保存边对象
        Vertex()
        {
            data = NULL;
        }
   };
    /* 定义实际邻接链表 */
    LinkList<Vertex*> m_list;  // 实际的邻接链表
public:
    /* 构造一个新的有 n 个顶点的 ListGraph 对象 */
    ListGraph(unsigned int n = 0)
    {
        for(unsigned int i=0; i<n; i++)  // 根据参数添加顶点
        {
            addVertex();  // 根据参数动态的增加顶点
        }
   }
    /* 动态的增加一个新的顶点 */
    int addVertex()  // O(n)
    {
        int ret = -1;
        Vertex* v = new Vertex();  // 创建堆空间对象
        if( v != NULL )
        {
            m_list.insert(v);  // 将顶点加入这个链表 O(n)
            ret = m_list.length() - 1;  // 返回新加结点在链表里面的编号,在最后一个位置
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new vertex object ...");
        }
        return ret;
   }
    /*动态的增加一个新的顶点的同时,将值设入 */
    int addVertex(const V& value)  // O(n)
    {
        int ret = addVertex();  // 添加结点
        if( ret >= 0 )  // 增加成功
        {
            setVertex(ret, value);  // 顶点所在 ret 处值为 value
        }
        return ret;
   }
    /* 设置顶点 i 处的值为 value */
    bool setVertex(int i, const V& value)  // O(n)
    {
        int ret = ( (0 <= i) && (i < vCount()) );  // 判断 i 的合法性
        if( ret )
        {
            Vertex* vertex = m_list.get(i);  // 将链表中 i 位置处元素取出来, O(n)
            V* data = vertex->data;  // data 指向具体的顶点数据元素
            if( data == NULL )  // 没有指向成功
            {
                data = new V();  // 动态的创建一个指向顶点相关的数据元素出来
            }
            if( data != NULL )  // 创建成功
            {
                *data = value;  // 创建成功就将参数值 value 传递过去
                vertex->data = data;  // 将 data 保存下来
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new vertex value ...");
            }
        }
        return ret;
   }
    /* 将 i 位置处相关联的顶点数据元素值返回 */
    V getVertex(int i)  // O(n)
    {
        V ret;
        if( !getVertex(i, ret) )
        {
            THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ...");
        }
        return ret;
   }
    /* 将 i 位置处相关联的顶点数据元素值赋值给引用 value */
    bool getVertex(int i, V& value)  // O(n)
    {
        int ret = ( (0 <= i) && (i < vCount()) );  // 判断 i 的合法性
        if( ret )
        {
            Vertex* v = m_list.get(i);  // 得到顶点相关数据 O(n)
            if( v->data != NULL )  //  顶点关联数据元素值
            {
                value = *(v->data);  // 关联就将数据值返回
            }
            else  // 顶点没有关联数据
            {
                THROW_EXCEPTION(InvalidOperationException, "No value assigned to this vertex ...");
            }
        }
        return ret;
   }
    /* 删除最近添加的顶点 */
    void removeVertex()  // O(n*n)
    {
        if( m_list.length() > 0 )  // 当前图中有顶点
        {
            int index = m_list.length() - 1;  // 删除最近添加的顶点,免得破坏图的结构
            Vertex* v = m_list.get(index);  // 取出顶点相关的数据元素 O(n)
            if( m_list.remove(index) )  // 首先删除最后一个顶点
            {  
            /* 这里循环条件的前后分别利用了逗号表达式 */
                for(int i=(m_list.move(0), 0); !m_list.end(); i++, m_list.next() )  // 看其他顶点当中有没有和这个顶点相关联的边,有了要删除
                {   
                 /* 当前结点临界链表里,查找与之关联的边,边起点为 i,终点为 index,存在则 pos 非负*/
                    int pos = m_list.current()->edge.find(Edge<E>(i, index));  // O(n),返回存在的下标
                    if( pos >= 0 )
                    {
                        m_list.current()->edge.remove(pos);  // 删除当前顶点的元素,即相关联的边
                    }
                }
                delete v->data;  // 释放对应顶点数据元素值空间
                delete v;  // 顶点自身占用的空间释放掉
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidOperationException, "No vertex in current graph ...");
        }
   }
    /* 获取从顶点 i 出发可以抵达的顶点编号,以一个数组的方式返回;遍历顶点 i 就可以得到与之相关的顶点了 */
    SharedPointer< Array<int> > getAdgacent(int i)  // O(n)
    {
        DynamicArray<int>* ret = NULL;
        if( (0 <= i) && (i < vCount()) )
        {
            Vertex* vertex = m_list.get(i);  // 从链表中获取与顶点相关的数据元素 O(n)
            ret = new DynamicArray<int>(vertex->edge.length());  // 创建返回值数组,个数是邻接链表中边的个数
            if( ret != NULL )
            {  
           /* 这里用了两个逗号表达式 */
                for(int k=(vertex->edge.move(0), 0); !vertex->edge.end(); k++, vertex->edge.next())  // O(n)
                {
                    ret->set(k, vertex->edge.current().e);  // 获取邻接顶点边的第二个元素,并将标号设置到要返回的数组中  O(1)
                }
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create ret object ...");
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ...");
        }
        return ret;
   }
    /* 判断 i 到 j 顶点边是否连接,能找到就是连接的 */
    bool isAdjacent(int i, int j)
    {
        return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0);
   }
    E getEdge(int i, int j)  // O(n)
    {
        E ret;
        if( !getEdge(i, j, ret) )
        {
            THROW_EXCEPTION(InvalidParameterException, "Edge <i, j> is invalid ...");
        }
        return ret;
   }
    /* 获取从 i 到 j 的边的权值,放在 value 中 */
    bool getEdge(int i, int j, E& value)
    {
        int ret = ( (0 <= i) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()) );
        if( ret )  // O(n)
        {
            Vertex* vertex = m_list.get(i);  // 得到顶点 O(n)
            int pos = vertex->edge.find(Edge<E>(i, j));  // 在顶点的邻接链表中找找是否存在从 i 到 j 的边,存在则 pos 非 O(n)
            if( pos >= 0 )
            {
                value = vertex->edge.get(pos).data;  // 取出值O(n)
            }
            else
            {
                THROW_EXCEPTION(InvalidOperationException, "No valid assigned to this edge ...");
            }
        }
        return ret;
   }
    /* 设置从 i 到 j 的邻接链表的值,存储在 value 中 */
    bool setEdge(int i, int j, const E& value)  // O(n)
    {
        int ret = ( (0 <= i) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()) );
        if( ret )
        {
            Vertex* vertex = m_list.get(i);  // 获得顶点 O(n)
            int pos = vertex->edge.find(Edge<E>(i, j));  // 查找边是否存在,存在则 pos 非零 O(n)
            if( pos >= 0 )
            {
                ret = vertex->edge.set(pos, Edge<E>(i, j, value));  // 设置邻接链表当中对应位置处的值,用边的构造函数直接赋值
            }
            else
            {
                ret = vertex->edge.insert(0, Edge<E>(i, j, value));  // 没有边时,插入一条边和边的权值,至于插入到那个位置,无所谓
            }
        }
        return ret;
   }
    /* 删除从 i 开始抵达 j 的边;查找对应邻接链表并删除 */
    bool removeEdge(int i, int j)  // O(n)
    {
        int ret = ( (0 <= j) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()) );
        if( ret )
        {
            Vertex* vertex = m_list.get(i);  // 取出感兴趣的边O(n)
            int pos = vertex->edge.find(Edge<E>(i, j));  // 相应顶点中的边是否存在,存在则 pos 非负 O(n)
            if( pos >= 0 )
            {
                ret = vertex->edge.remove(pos);  // 删除对应的点 O(n)
            }
        }
        return ret;
   }
    /* 获取图中顶点个数,即链表中元素个数 */
    int vCount()  // O(1)
    {
        return m_list.length();  // O(1)
   }
    /* 获取边的个数,获取所有顶点的邻边个数之和*/
    int eCount()  // O(n)
    {
        int ret = 0;
        for(m_list.move(0); !m_list.end(); m_list.next())
        {
            ret += m_list.current()->edge.length();  // 累加当前顶点的邻边个数
        }
        return ret;
   }
    /* 实现顶点 i 的入度函数 */
    int ID(int i)  // O(n*n)
    {
        int ret = 0;
        if( (0 <= i) && (i < vCount()) )
        {
            for(m_list.move(0); !m_list.end(); m_list.next())
            {
                LinkList< Edge<E> >& edge = m_list.current()->edge;  // 定义当前邻接链表别名,方便编程
                for(edge.move(0); !edge.end(); edge.next())
                {
                    if( edge.current().e == i )  // 多少条终止顶点是顶点 i
                    {
                        ret++;
                        break;  // 每个顶点到另一个顶点的边的个数一般的只有一个,除非两个顶点见有两个权值不一样的有向边
                    }
                }
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ...");
        }
        return ret;
   }
    /* 获取顶点 i 的出度 */
    int OD(int i)  // O(n)
    {
        int ret = 0;
        if( (0 <= i) && (i < vCount()) )
        {
            ret = m_list.get(i)->edge.length();  // O(n)
        }
        else
        {
             THROW_EXCEPTION(InvalidParameterException, "Index i is invalid ...");
        }
        return ret;
   }
    ~ListGraph()
    {
        while( m_list.length() > 0 )
        {
            Vertex* toDel = m_list.get(0);
            m_list.remove(0);
            delete toDel->data;
            delete toDel;
        }
    }
};
}
#endif // LISTGRAPH_H

测试代码


#include <iostream>
#include "ListGraph.h"

using namespace std;
using namespace DTLib;

int main()
{
   ListGraph<char, int> g(4);

    g.setVertex(0, 'A');
    g.setVertex(1, 'B');
    g.setVertex(2, 'C');
   g.setVertex(3, 'D');

    for(int i=0; i<g.vCount(); i++)
    {
        cout << i << " : " << g.getVertex(i) << endl;
   }

   ListGraph<char, int> g1;

    g1.addVertex('A');
    g1.addVertex('B');
    g1.addVertex('C');
   g1.addVertex('D');

   //    g1.removeVertex();

    for(int i=0; i<g1.vCount(); i++)
    {
        cout << i << " : " << g1.getVertex(i) << endl;
   }

    g1.setEdge(0, 1, 5);
    g1.setEdge(0, 3, 5);
    g1.setEdge(1, 2, 8);
    g1.setEdge(2, 3, 2);
   g1.setEdge(3, 1, 9);

    cout << "W(0, 1) : " << g1.getEdge(0, 1) << endl;
    cout << "W(0, 3) : " << g1.getEdge(0, 3) << endl;
    cout << "W(1, 2) : " << g1.getEdge(1, 2) << endl;
    cout << "W(2, 3) : " << g1.getEdge(2, 3) << endl;
   cout << "W(3, 1) : " << g1.getEdge(3, 1) << endl;

   cout << "eCount : " << g1.eCount() << endl;

   //    g1.removeEdge(3, 1);
   //    cout << "W(3, 1) : " << g1.getEdge(3, 1) << endl;

   cout << "eCount : " << g1.eCount() << endl;

   SharedPointer< Array<int> > aj = g1.getAdgacent(0);

    for(int i=0; i<aj->length(); i++)
    {
        cout << (*aj)[i] << endl;
   }

    cout << "ID(1) : " << g1.ID(1) << endl;
    cout << "OD(1) : " << g1.OD(1) << endl;
   cout << "TD(1) : " << g1.TD(1) << endl;

   g1.removeVertex();

   cout << "eCount : " << g1.eCount() << endl;

    cout << "W(0, 1) : " << g1.getEdge(0, 1) << endl;
   cout << "W(1, 2) : " << g1.getEdge(1, 2) << endl;

    return 0;
}

总结

 邻接链表法使用链表对图相关的数据进行存储;             

  • 从邻接矩阵改进而来,将数组改进为链表;      
  • 每一个顶点关联一个链表,用于存储边相关的数据;      
  • 所有顶点按照编号被组织在同一个链表中;     
  • 邻接链表法实现的图能够支持动态添加和删除顶点;

huu红山竹
4 声望2 粉丝

study


« 上一篇
c++内存申请