首页 > 技术文章 > 随机行走算法

chantmee 2021-10-01 17:15 原文

前言

本人对随机行走算法理解并不是非常透彻(甚至可以说是不理解),仅仅根据定义用python将随机行走进行实现出来,因此本文章一定漏洞百出,仅仅只能参考。

该程序是青岛科技大学PCB设计老师布置的作业,如果学弟学妹需要可以将本文写的程序作为参考。

我理解的定义

给定一张图,图中包含\(nv\)个点和\(ne\)条无向边,给出一个起始点\(s\),目的地\(t\)以及一个随机跳跃概率\(p\).

从点\(s\)开始行走,这里设点所在的当前位置为\(cur\).

每次行走有\(p\)的概率行走到图中任意一个点上(包括\(cur\)),有\(1-p\)的概率行走到与点\(cur\)直接相连的任意一个点上。重复上面操作直到找到了目的地\(t\),然后把路径打印出来。

思想

该算法要实现的是搜索,从起始点s开始找到目的地t

给定一个图,从起点开始走的每一步都让其有一定的概率\(\alpha\)跳转到图中的任意一个点上,还有\(1-\alpha\)的概率会行走到任意一个与该点相连的点上。不断的重复上述过程,直到找到目的地中。

该算法的思想也被应用到了pagerank算法中,pagerank算法中为了解决悬挂点和排他水槽的问题,也使得每个点有一定概率跳转到图中任意一个点中。

步骤

  1. 将实际问题抽象为图的形式,并用临接表、邻接矩阵等存图方式将图存储在计算机中;

  2. 用一个变量记录当前所处的位置(图中点的标号),每次随机一个[0, 1]之间的数,若其小于等于随机跳跃概率\(\alpha\)则随机一个[1, n]的数字并跳转,否则随机一个\(x,x\in S\),其中S为与当前点相连的点的集合;

  3. 不断重复第二步并记录路径,直到找到目的地t.

伪代码

read()								# 读取数据
cur = s
path = [cur]						# 记录路径
while s != t:
  gotP = random()					# 获得一个0-1.0的数
  if gotP < p:						# 行走到图中的任意一个点
    cur = getRandomPoint(ALL)
  else:								# 行走到与cur直接相连的点上
    edges = getEdges(cur)
    cur = getRadomPoint(edges)
  path.append(cur)
Print(path)

代码实现

前置知识

如果要理解这段代码,你可能需要的一些前置知识有:

  1. python的基本语法、类的基本操作以及random库的使用
  2. 数据结构:链式前向星

代码

import random

class Graph:
    def __init__(self, nv, ne):
        self.head = [-1 for i in range(nv + 1)]
        self.e = [0 for i in range(ne << 1)]
        self.ne = [0 for i in range(ne << 1)]
        self.tot = 0

    def add(self, u, v):
        self.e[self.tot] = v
        self.ne[self.tot] = self.head[u]
        self.head[u] = self.tot
        self.tot = self.tot + 1

    def getEdges(self, u):
        res = []
        cur = self.head[u]
        while cur != -1:
            res.append(self.e[cur])
            cur = self.ne[cur]
        return res

def readLine():
    line = input()
    line = line.split()
    size = len(line)
    for i in range(size):
        line[i] = int(line[i])
    return line

def Print(path):
    size = len(path)
    print('We finally find the position %d!' % (path[size - 1]))
    print('We took a total of %d steps!' % (size))
    print('The path we took were:')
    for i in range(size):
        print(path[i], end=' \n'[i == size - 1])
    print()

def solve():
    line = readLine()
    nv = line[0]
    ne = line[1]
    s = line[2]
    t = line[3]
    p = float(input())

    graph = Graph(nv, ne)
    for i in range(ne):
        line = readLine()
        u = line[0]
        v = line[1]
        graph.add(u, v)
        graph.add(v, u)

    path = [s]
    while s != t:
        gotP = random.random()
        if gotP < p:
            s = random.randint(0, nv - 1)
        else:
            edges = graph.getEdges(s)
            size = len(edges)
            s = edges[random.randint(0, size - 1)]
        path.append(s)

    Print(path)

if __name__=='__main__':
    solve()

输入格式

第一行有\(4\)个数字\(nv\), \(ne\), \(s\), \(t\),分别为点的数量,边的数量,起点,终点.
第二行有一个数字\(p\), 代表跳转发生概率.
从第\(3\)行到第\(ne + 2\)行,每行有两个数字\(u\), \(v\),表示\(u\), \(v\)之间有一条无向边连接.

代码分析

代码分为三大部分。

第一部分:\(Graph\)

\(Graph\)类内部是用链式前向星实现的。

支持的操作有:

  1. 添加一条有向边(\(add\));

  2. 获取与点\(u\)相连的点有哪些(\(getEdges\)),返回一个列表。

第二部分:IO(输入输出)

这书粉可以分为两个部分,输入和输出。由于输出部分实现很简单,功能望眼欲穿,因此不做解释。

input(输入)

由于python只支持一次性读入一行字符串,因此需要对字符串进行处理才能进行使用。这里通过python自带的函数split将读入的字符串以空格作为分割标志对字符串进行分割,分割之后将每个部分类型强制转换成int类型,将结果以列表的形式返回。

这个函数美中不足的是,它只能读取并转换int类型的数字,而不能读入float类型的数字。这个要修改其实也很简单:

读入字符串然后分割,对于分割后的每个子字符串用python自带的find函数看看这个字符串中是否存在'.',就是浮点数里面的点,如果存在就强制转换成浮点数,否则转换成整数.

由于该问题仅仅只需要读入一个浮点数,因此这里不对原程序进行修改了。

第三部分

第三部分就是\(solve\)函数。这部分代码前半部分是输入,很简单。后半部分是\(Random Walk\)算法的核心代码,完全是按照定义写的,这部分如果定义看懂了应该不会有太大的阅读困难,因此也不做过多的解释。

数据生成

输入格式

一共一行,有两个数字用空格隔开,分别是点的数量和边的数量

代码(c++)

#include <iostream>
#include <cstring>
#include <ctime>
#include <set>

#define pii pair<int, int>
#define mp(a, b) make_pair(a, b)
#define fr first
#define sc second

std::set<std::pii> inUse;

std::pii getRandomEdge(int nv) {
    int u = rand() % nv;
    int v = rand() % nv;
    while (u == v) v = rand() % nv;
    return std::mp(u, v);
}

void solve() {
    srand(time(0));
    int nv, ne;
    scanf("%d %d", &nv, &ne);
    std::pii ST = getRandomEdge(nv);
    printf("%d %d %d %d\n", nv, ne, ST.fr, ST.sc);
    for (int i = 0; i < ne; i++) {
        std::pii edge = getRandomEdge(nv);
        while (inUse.count(edge)) edge = getRandomEdge(nv);
        printf("%d %d\n", edge.fr, edge.sc);
    }
}

int main() {
    solve();
    return 0;
}

总结

该算法掌握定义之后,在实现上不会有太多的困难。

推荐阅读