【邻接表】邻接表的静态实现(链式前向星)及vector实现

一.链表(数组模拟)

1.引入

链表相比于数组:

优势:
插入删除操作非常快,并且因为动态分配内存的缘故,即便事先不知道数据范围的大小也没关系,对内存的使用效率较高;

劣势:
查询速度慢,且可能内存泄漏。而且,在ACM比赛中用链表写的程序不容易调试

所以,可以使用数组来模拟链表,因为ACM的题目都会事先给出数据的范围。

2.模板

用数组模拟链表,就是给每一个结点编一个号,用这个号码来代替指针指向结点

结点的建立:

const int maxn=10000000;

typedef struct{
  int data;
  int pre,next;
}Linklist;

Linklist node[maxn];

初始化:
设立头尾结点,以减少程序编写时对链表边界的判断

int tot,head,rear;

void init(){
   head=1,rear=2;
   tot=2;
   node[head].next=rear;
   node[rear].pre=head;
}

插入:

基本思想和链表一样


void L_insert(int p,int value){
   int q=tot++;
   node[node[p].next].pre=q;
   node[q].next=node[p].next;
   node[q].data=value;
   node[q].pre=p;
   node[p].next=q;
}

删除:
可以发现删除结点后,被删除结点的编号并没有类似“释放内存”的操作,这块空间永远还是存在,只是从链表中被“删除”了

void L_remove(int p){
    node[node[p].pre].next=node[p].next;
    node[node[p].next].pre=node[p].pre;
}

二.链式前向星

1.引入

邻接表是图的一种存储方式。它的思想是用N条链表储存一个有N个顶点的有向图,其中第i条链表储存以第i个顶点为始点的所有边,注意邻接表储存的是边的信息所以适用于点稀疏图。

若要储存无向图,则将一条边看成两条有向边储存即可。

但是用链表来写很麻烦,所以我们同样可以用数组来进行模拟,实现静态的邻接表,也就是所谓的链式前向星

邻接矩阵是用一个N*N的矩阵来存放任意两点间的权值(没有通路则为inf),空间复杂度为 O ( N 2 ) O(N^2) O(N2);邻接表的空间复杂度为 O ( M + N ) O(M+N) O(M+N),其中M为边的条数,N为点的个数。

即便是N个点的完全图,其边数也只有N(N-1)/2,所以邻接表在简单图中显然是更好的选择。

2.模板

初始化

首先,我们用cnt来分配储存空间。规定0相当于NULL指针。

head数组,即表头数组,储存N个链表的表头,表头不需要存放数值,只要记录链表首元结点的指针即可。其值储存的其实相当于链表中表头的next指针。head数组因为是全局变量,所以自动初始化为0。代表每个表头的next初始化为NULL;

结点是用来储存边的信息的,我们需要两个值to和next。to代表的是边的终点,因为边的始点是由表头数组确定的,所以不需要再记录。next则指向链表中的下一个结点,如果为0则代表下一个结点为NULL。

vis数组标记是否访问,是在遍历时使用的(为了保证每个点只被访问一次)

value存放结点的权值

const int maxn=2e5+10;

typedef struct{
  int to;
  int next;
}edge;

edge e[maxn];
int cnt=1;
int head[maxn];
int vis[maxn];
long long  value[maxn];

加边

加边操作用add函数来实现
原理与链表建立的头插法相同,具体细节见注释

void add(int u,int v){
    e[cnt].to=v;//cnt指向该边
    e[cnt].next=head[u];//该边的next是表头的next,相当于头插法
    head[u]=cnt++;//表头的next等于该边
}

dfs遍历

访问u为始点,与之有边相连的所有结点
即访问第u条链表,对于这个for循环for(int i=head[u];i;i=e[i].next的个人理解:
head[u]即首元结点的指针,为循环的起点
中间的i其实是i!=0的意思,因为链表的结尾是NULL指针,即0
i=e[i].next即访问下一个结点的指针

i其实是指针,e[i]存放的才是内容

for循环的内部有一句dfs(v),即递归调用该函数

void dfs(int u){
  vis[u]=1;
  for(int i=head[u];i;i=e[i].next){ //i=0时链表遍历完毕
        int v=e[i].to;
        if(vis[v]) continue;
        //访问结点v
        dfs(v);
  }
}

bfs遍历

bfs遍历借助队列,是按层遍历,详情可见代码

void bfs(int u){
  queue<int>q;
  q.push(u);
  vis[u]=1;
  while(!q.empty()){
       int temp=q.front();
       q.pop();
       for(int i=head[temp];i;i=e[i].next){
           int v=e[i].to;
           if(vis[v]) continue;
           vis[v]=1;
           //访问结点v
           q.push(v);
       }
  }
}

三.vector 实现邻接表

vector实现邻接表写法应该是最简单的了
其实之所以用数组模拟而不是二维数组,就是因为在边稀疏图中将对空间储存造成较大的浪费,但是用vector来模拟不仅写法非常的简单,而且vector是不定长的数组,可以直接在尾端加入元素而不用事先声明数组的长度。(我不太懂vector的内存分配机制,但是香就完事了!)

stl永远滴神

const int maxn=1e5+10;
int N;
vector <int> ver[maxn];
long long  value[maxn];

void add(int u,int v){
     ver[u].push_back(v);
}

void dfs(int u){//起点u
  vis[u]=1;
  for(int i=0;i<ver[u].size();i++){
      int v=ver[u][i];
      if(vis[v]) continue;
      vis[v]=1;
      //访问结点v
      dfs(v);
  }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值