...

пятница, 27 июня 2014 г.

[Из песочницы] Число Бейкона и Графы

Число Бейкона




Немного история, Кевин Бейкон американский актёр сыгравший во множествах фильмам, в 1994 отметил что актёры, с которыми снимался он, работали со всеми голливудскими (и не только) актёрами. Общественность тут же отреагировала и создала игру “назвать имя актёра и связать его с Кевином Бейконом”. Корпорация добра даже встроила игру в свой поисковик, например Число Бейкона для актёра Джона Траволты равно 2 (Джон снимался с Оливией Ньютон-Джон в фильме Бриолин, она же, в свою очередь, сыграла с Кевином Бейконом в фильме “У нее будет ребенок”).

А теперь давайте поговорим о том как это игру можно представить и как можно вычислить число Бейкона при помощи графа.



Граф




В данном случае, я буду рассматривать направленный невзвешенный граф, где вершинами будут актеры и ребрами фильмы (данные можно взять с IMDb), в которых они снимались, под стартовой(нулевой) вершиной будет Кевин Бейкон. Число Бейкона в таком графе будет равно количеству hop`ов от стартовой вершины до искомой или для всех остальных. Для примера я выбрал такой вот граф:

image

Алгоритм который производит поиск называется “Поиск в ширину” (Breadth-first search) и делает необходимые нам вычисления за O(n+m) операций, где n — количество вершин, а m — количество рёбер, то есть, за линейное время, что весьма неплохо. Для реализации алгоритма нам понадобится обычная очередь(FIFO), куда мы будет помещать все встретившиеся нам вершины.

И так первый шаг, взять стартовую вершину и поместить её в очередь. Далее у нас будет цикл, условием выхода из которого будет проверка очереди на пустоту. В каждой итерации достаём вершину из очереди, берём всех потомков этой вершины, при этом помечая ребро как исследованное, и если вершина все ещё не исследована, то помечаем её таковой и отправляем в очередь.


Как это выглядит на нашем примере:

— помечаем вершину0 как стартовую и помещаем её в очередь

Далее цикл пока очередь не пуста

— достаём нулевую вершину, по рёбрам (0,1) и (0,2) берём потомков, т.к. эти вершины встречаются нам первый раз, то помещаем их в очередь, отметив их исследованными и установив их HopLevel на единицы больше чем у родителя (в данном случае 1)

— следующая вершина, при её обработки в очередь попадут вершины 3 и 4, при этом уровень для них установится равный 2

— далее вершина2, в этом случаем потом 4 не попадёт в очередь т.к. он уже исследован выше, только 5 будет отравлена в очередь с уровнем равным 2

— вершина 3, которая отправит в очередь потомка 6 с уровнем 3

— остальные вершины будет просто изъяты из очереди.

— на вершине6 очередь опустеет и цикл завершится


Небольшой код на Go:



Код


package main

import (
"container/heap"
"fmt"
)

type Node struct {
HopLevel int
Explored bool
Edges []int
index int //internal variable
}
type Graph []*Node
type Queue []*Node

func (q Queue) Len() int { return len(q) }
func (q Queue) Less(i, j int) bool { return q[i].index < q[j].index }
func (q Queue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }

func (q *Queue) Push(x interface{}) {
*q = append(*q, x.(*Node))
}
func (q *Queue) Pop() interface{} {
old := *q
n := len(old)
x := old[n - 1]
*q = old[0 : n-1]
return x
}

var (
operationsNumber int = 0;
)

func main() {
g := createGraph()
bfs(*g, 0)
for index, node := range *g {
fmt.Printf(" node%d is on %d HopLevel \n", index, node.HopLevel)
}
fmt.Printf("Total number of operations is %d", operationsNumber)
}

func bfs(g Graph , startIndex int) {
queue := &Queue{}
heap.Init(queue)
start := g[startIndex]
start.index = 0
start.HopLevel = 0;
start.Explored = true;
index := 1;
heap.Push(queue, start)
for queue.Len() > 0 {
node := heap.Pop(queue).(*Node)
for _, childNodeIndex := range node.Edges {
childNode := g[childNodeIndex]
if !childNode.Explored {
childNode.Explored = true
childNode.HopLevel = node.HopLevel+1
childNode.index = index
index++
heap.Push(queue, childNode)
}
operationsNumber++
}
operationsNumber++
}
}

func createGraph() *Graph {
node0 := &Node{ Edges: []int{1, 2}, }
node1 := &Node{ Edges: []int{3, 4}, }
node2 := &Node{ Edges: []int{1, 4, 5}}
node3 := &Node{ Edges: []int{6}}
node4 := &Node{ Edges: []int{6}}
node5 := &Node{ Edges: []int{6}}
node6 := &Node{ Edges: []int{}}
return &Graph{node0, node1, node2, node3, node4, node5 , node6}
}





В реализации я использовал пакет heap, который позволяет создать нужную нам очередь определив 5 методов, так же, в структуре Node появилась внутренняя переменная index, необходимая нам для очереди.


Результаты:


node0 is on 0 HopLevel
node1 is on 1 HopLevel
node2 is on 1 HopLevel
node3 is on 2 HopLevel
node4 is on 2 HopLevel
node5 is on 2 HopLevel
node6 is on 3 HopLevel
Total number of operations is 17

Граф который указан на рисунке выше состоит из 7 вершин и 10 рёбер, и как мы видим количество операций равно 17 и каждой вершины определён уровень насколько она далека от стартовой.


Примечания:



— Вместо пакета heap можно было бы использовать chanel и это было бы более разумно

— Так как граф направленный, я не стал отмечать ребра как исследованные.

— Алгоритм можно использовать для конкретной вершины, тогда к условию выхода так же добавиться проверка не искомая ли вершина взята из очереди на обработку.

— Кевин Бейкон не является самым оптимальным актёром для такой игры, есть кандидаты значительно лучше, максимальное число Бейкона равно 9.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий