随机行走算法
前言
本人对随机行走算法理解并不是非常透彻(甚至可以说是不理解),仅仅根据定义用python将随机行走进行实现出来,因此本文章一定漏洞百出,仅仅只能参考。
该程序是青岛科技大学PCB设计老师布置的作业,如果学弟学妹需要可以将本文写的程序作为参考。
我理解的定义
给定一张图,图中包含\(nv\)个点和\(ne\)条无向边,给出一个起始点\(s\),目的地\(t\)以及一个随机跳跃概率\(p\).
从点\(s\)开始行走,这里设点所在的当前位置为\(cur\).
每次行走有\(p\)的概率行走到图中任意一个点上(包括\(cur\)),有\(1-p\)的概率行走到与点\(cur\)直接相连的任意一个点上。重复上面操作直到找到了目的地\(t\),然后把路径打印出来。
思想
该算法要实现的是搜索,从起始点s开始找到目的地t。
给定一个图,从起点开始走的每一步都让其有一定的概率\(\alpha\)跳转到图中的任意一个点上,还有\(1-\alpha\)的概率会行走到任意一个与该点相连的点上。不断的重复上述过程,直到找到目的地中。
该算法的思想也被应用到了pagerank算法中,pagerank算法中为了解决悬挂点和排他水槽的问题,也使得每个点有一定概率跳转到图中任意一个点中。
步骤
-
将实际问题抽象为图的形式,并用临接表、邻接矩阵等存图方式将图存储在计算机中;
-
用一个变量记录当前所处的位置(图中点的标号),每次随机一个[0, 1]之间的数,若其小于等于随机跳跃概率\(\alpha\)则随机一个[1, n]的数字并跳转,否则随机一个\(x,x\in S\),其中S为与当前点相连的点的集合;
-
不断重复第二步并记录路径,直到找到目的地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)
代码实现
前置知识
如果要理解这段代码,你可能需要的一些前置知识有:
- python的基本语法、类的基本操作以及random库的使用
- 数据结构:链式前向星
代码
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\)类内部是用链式前向星实现的。
支持的操作有:
-
添加一条有向边(\(add\));
-
获取与点\(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;
}
总结
该算法掌握定义之后,在实现上不会有太多的困难。