邻接链表
- 为了进一步提高空间效率,可以考虑使用链表替换数组,将邻接矩阵变换为邻接链表;
- 占用空间就是因为邻接矩阵的问题,没有连接也要占用四个字节的空间,可以考虑无连接不占用空间的情况;
- 数组在定义的时候要指明有多少个元素,这样可能导致浪费,可以用链表,需要的时候再增加,不需要预定义一共有多少个元素;
邻接链表的表示方法
- 图中的所有顶点按照编号存储于同一个链表中
- 原来存储在数组中,这里存储在链表中;
- 每一个顶点对应一个链表,用于存储始发于该顶点的边;
- 链表中包含的信息见 3;
- 将数组改成链表;
3.每一条边的信息包含:起点,终值,权值;
#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;
}
总结
邻接链表法使用链表对图相关的数据进行存储;
- 从邻接矩阵改进而来,将数组改进为链表;
- 每一个顶点关联一个链表,用于存储边相关的数据;
- 所有顶点按照编号被组织在同一个链表中;
- 邻接链表法实现的图能够支持动态添加和删除顶点;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。