往常我们常常会用邻接矩阵来储存一张图,但往往会浪费放大量的空间。
noip2016的一道题博主因为当时水平有限,用了邻接矩阵,毕竟当时只会深搜。
然而今天,Sim同学给大家科普一下这个神奇的存法。
适用范围 图较为疏密。
以下是 邻接表与邻接矩阵的优缺点比较;
邻接矩阵
优点
可以在常数时间判断两者之间是否有边
实现简单
缺点
空间花费很高,需要|V|²的空间,如果是稀疏图,空间被大量浪费
若有重边,则无法保存所有边的信息
邻接表
优点
有多少边就占用多少空间,空间利用效率高,且能存下所有边的信息
稀疏图时,遍历复杂度低,O(|V|+|E|)
缺点
不可以在常数时间判断两者之间是否有边
实现稍复杂
const int MAXN=1005;
struct edge{
int to,next;
int w;
}g[MAXN];
int ne,h[MAXN];
void add(int u,int v,int dis)//u--v的边;
{
ne++;
g[ne].to=v;
g[ne].w=dis;
g[ne].next=h[u];
h[u]=ne;
}
过去的代码太Naive了。好吧,就几个月前。
贴上新写法
#define MAXN 100005
int head[MAXN], ne;
class edge {
public:
int to, pre, w;
edge(int to = 0, int pre = 0, int w = 0) : to(to), pre(pre), w(w) { }
} g[MAXN << 1];
inline bool adde(int u, int v, int w) {
g[++ne] = edge(v, head[u], w), head[u] = ne;
return true;
}
关于遍历:
#define edges(u) for(register int i = head[u]; i; i = g[i].pre)
void dfs(int u, int f) {
edges(u) {
int v = g[i].to;
dfs(v, u);
}
}
Update : 2017.10.20
更新现在的写法,使用指针。
递归调用实现加双向边。
可以使用构造函数,但为了减少代码量,所以省去。
注意这种写法只在g++系列编译器是有效的。
# include
const int N = 123456 ;
struct edge {
int to, w ; edge* nxt ;
} g [N << 1], *NewEdge, *head [N] ;
inline void add_edge ( int u, int v, int w, bool db ) {
*( ++ NewEdge ) = ( edge ) { v, w, head [u] } ; head [u] = NewEdge ;
if ( db ) add_edge ( v, u, w, ! db ) ;
}
关于初始化:
void Init ( ) {
NewEdge = g ;
}
当然可以直接在声明NewEdge的时候把它指向指针池。
关于遍历:
for ( edge* it = head [u] ; it ; it = it -> nxt ) {
/*
some huaji things...
*/
}
Update : 2017.11.3
struct edge {
int to ; edge* nxt ;
} ;
# define N 500050
# define M 500050
struct Graph {
edge g [M], *head [N] ;
Graph ( ) { memset ( head, 0, sizeof head ) ; }
inline void add_edge ( int u, int v ) {
static edge* NewEdge ( g ) ;
*( ++ NewEdge ) = ( edge ) { v, head [u] } ; head [u] = NewEdge ;
}
inline edge*& operator [] ( const int& u ) { return head [u] ; }
} g1, g2 ;
再也不用担心重建图的时候不方便啦!!!
关于遍历:
for ( edge* it = g1 [u] ; it ; it = it -> nxt ) {
/*
do sth
*/
}