0
0

Golangでキューを使った幅優先探索の練習をしてみた

Last updated at Posted at 2024-08-22

概要

最近、アルゴリズムの基礎を学ぼうと思って なっとく!アルゴリズム って書籍を読んでます。この中にあるサンプルコードはPythonで書かれてるんですが、Goを触ることが多いので自分でGolangに書き直して勉強しようと、そのアウトプットです。

幅優先探索はキューを使って、まず隣接ノードを探索しそのノードに対象がなければ、そのノードの隣接ノードをキューに追加していくというアルゴリズムのため、下記2点を確認することができます。

  • 検索対象が存在するか
  • 存在する場合は、その存在までの最短経路

やってみた

問題は「自分の友達に名前の末尾がyの人がいるか、いなければ友達の友達にいるか、いなければ友達の友達の友達に…」ってな感じです。

グラフを作る

まずはグラフになる友達のリストを作ります。

var graph map[string][]string

func init() {
	graph = make(map[string][]string)
	graph["you"] = []string{"alice", "bob", "claire"}
	graph["bob"] = []string{"tom", "peggy"}
	graph["alice"] = []string{"peggy"}
	graph["claire"] = []string{"olivia", "jonny"}
	graph["tom"] = []string{}
	graph["peggy"] = []string{}
	graph["olivia"] = []string{}
	graph["jonny"] = []string{}
}

あなた(you)には alice, bob, claire の3人の友達がいて、さらに
bob には tom, peggy って友達がいて、さらに…
という関係ですね。

あなたは3人のことを友達と思っていますが、彼らの友達リストにあなたが入っていないという点はツッコミなしで。そういう殺伐とした世界観ということで。

キューを作る

次にキューの構造体と機能を作ります。

type Queue struct {
	items []string
}

func NewQueue() *Queue {
	return &Queue{
		items: []string{},
	}
}

// キューに追加する
func (q *Queue) Enqueue(items []string) {
	q.items = append(q.items, items...)
}

// キューから取り出してその値を返す
func (q *Queue) Dequeue() string {
	person := q.items[0]
	q.items = q.items[1:]

	return person
}

Pythonの場合は deque() で簡単にキューを作ることができるそうですが、Goにはないので簡単な形で自作します。

処理を作る

続いてこのグラフに対する幅優先探索の処理です。

func breadthFirstSearch(name string) (bool, string) {
	q := NewQueue()
	q.Enqueue(graph[name])
	searched := []string{}

	for len(q.items) != 0 {
		person := q.Dequeue()
  		// 探索済みの人だった場合はスキップ
		if slices.Contains(searched, person) {
			continue
		}
  
		if person[len(person)-1:] == "y" {
			return true, person
		} else {
			q.Enqueue(graph[person])
			searched = append(searched, person)
		}
	}

	return false, "none"
}

探索済みの人はスキップしないと無限ループに陥る可能性があります。
例えば bob のリストに tom がいて、tom のリストに alice がいて、 alice のリストに bob がいる時、末尾にyがつかないので、この3人でキューをぐるぐる回ることになります。

最終的なコード

こんな感じになりました。

package main

import (
	"fmt"
	"slices"
)

var graph map[string][]string

func init() {
	graph = make(map[string][]string)
	graph["you"] = []string{"alice", "bob", "claire"}
	graph["bob"] = []string{"tom", "peggy"}
	graph["alice"] = []string{"peggy"}
	graph["claire"] = []string{"olivia", "jonny"}
	graph["tom"] = []string{}
	graph["peggy"] = []string{}
	graph["olivia"] = []string{}
	graph["jonny"] = []string{}
}

func main() {
	result, person := breadthFirstSearch("you")
	fmt.Println(result, person)
}

func breadthFirstSearch(name string) (bool, string) {
	q := NewQueue()
	q.Enqueue(graph[name])
	searched := []string{}

	for len(q.items) != 0 {
		person := q.Dequeue()
		if slices.Contains(searched, person) {
			continue
		}
		if person[len(person)-1:] == "y" {
			return true, person
		} else {
			q.Enqueue(graph[person])
			searched = append(searched, person)
		}
	}

	return false, "none"
}

type Queue struct {
	items []string
}

func NewQueue() *Queue {
	return &Queue{
		items: []string{},
	}
}

func (q *Queue) Enqueue(items []string) {
	q.items = append(q.items, items...)
}

func (q *Queue) Dequeue() string {
	person := q.items[0]
	q.items = q.items[1:]

	return person
}

これを実行すると true peggy が出力されます。
友達リストのpeggyを、末尾がyでない形にすれば true jonny が出力されます。
末尾がyの人がいなければ false none が出力されます

あとがき

このケースのグラフは配列形式でしたが、二分木の問題なんかでも使われている印象です。

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

このアルゴリズムを実際に使うか使わないかではなく、こういった学習を通して柔軟な発想や複雑な仕様を理解できるようになりたいですね😌

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0