Shortest maze exit

อันนี้แปะไว้ให้ต่ออ่าน เอาเป็นโจทย์ Lab ข้อกลางๆ แล้วกัน เดี๋ยวจะคิดข้อยาก และข้อง่ายให้อีก รวมเป็น 3 ข้อ หรือว่าใครอยากลองทำดูก็ได้ครับ ถ้าเขียนด้วย Turbo C++ จะต้องเสียเวลาเขียน Queue ก่อนก็เลยเขียนด้วย VB.NET แทนซึ่งคิดว่า Algorithm คงจะพอเข้าใจได้ไม่ยาก แม้ว่าอาจจะเป็นภาษาที่ไม่ค่อยคุ้นหูคุ้นตาสำหรับหลายๆ คน ลองอ่านดูแล้วกันครับ

โจทย์มีอยู่ว่า กำหนด Input ให้เป็นแผนที่เขาวงกต มีจุดเริ่มต้น และทางออกบอกไว้ให้ ให้คำนวณหาระยะทางที่สั้นที่สุดจากจุดเริ่มต้นไปยังทางออก โดยสองบรรทัดแรกใน Input File เป็นความกว้างและความสูงของแผนที่ตามลำดับ สองบรรทัดถัดมาเป็นค่า X และ Y ของจุดเริ่มต้น และบรรทัดที่เหลือเป็นข้อมูลแผนที่โดย 0 คือช่องว่าง 1 คือกำแพง และ 2 คือทางออก

Maze Problem

วิธีทำก็คือสร้าง Queue ขึ้นมา 1 อัน แล้วใส่จุดเริ่มต้นลงไป กำหนดค่าระยะทางเดินให้เป็น 0 ก้าว จากนั้นค่อยดึงๆ จุดออกมาจาก Queue เรื่อยๆ ทีละจุด นำมาประมวลผล

จากจุดที่ดึงออกมานั้นก็คำนวณระยะทางไปยังจุดข้างเคียง (นั่นคือเพิ่มจากเดิมอีก 1 ก้าว) พร้อมกับตรวจสอบว่าระยะทางจาดจุกที่กำลังประมวลผลนี้ กับระยะทางเดิมที่เคยคำนวณไว้ ณ จุดนั้น อันไหนมันน้อยกว่า ถ้าระยะทางที่คำนวณใหม่นั้นมีค่าน้อยกว่า ก็ใส่ระยะทางนั้นลงไป แล้วก็ใส่จุดนั้นลงไปใน Queue เพื่อทำการคำนวณต่อ ทำอย่างนี้ไปเรื่อยๆ จนกว่าจะเจอทางออก ระยะทางที่สั้นที่สุดจากจุดเริ่มค้นก็คือค่าระยะทางที่คำนวณได้ ณ จุดทางออกนั่นเอง

เฉลยที่ทำไว้ให้คำนวณแค่ระยะทางน่ะนะ แล้วก็ Assume ว่าทางออกสามารถไปถึงได้ แผนที่นั้นมีกำแพงล้อมรอบเสมอ และจุดเริ่มต้นจะเริ่มบนช่องว่างเท่านั้น

input.txt

10
10
8
8
1111111111
1200110101
1010100101
1010101001
1000001011
1110100011
1000101001
1011001011
1000011001
1111111111

Output

Loading input data ...
Calculating shortest path ...
Shortest path is 14 steps.

maze_exit.vb

Option Strict On
Imports System.IO
Imports System.Collections.Generic

Module modMain

    Enum MapCell As Integer
        BLANK = 0
        WALL = 1
        [EXIT] = 2
    End Enum

    Sub Main()

        Dim width, height As Integer
        Dim map As MapCell(,)
        Dim calc As Integer(,)
        Dim I%, J%
        Dim q As Queue(Of Point)
        Dim p As Point
        Dim length As Integer
        Dim shortestLength As Integer

        Console.WriteLine("Loading input data ...")

        ' open c:input.txt for reading
        ' wraps in a using block to auto-clean
        Using sr As New StreamReader("C:input.txt")

            ' read and parse map info
            width = CInt(sr.ReadLine())
            height = CInt(sr.ReadLine())
            p.X = CInt(sr.ReadLine()) 'starting point
            p.Y = CInt(sr.ReadLine())

            ' creates buffer
            map = New MapCell(width - 1, height - 1) {}
            calc = New Integer(width - 1, height - 1) {}

            ' read map data line by line
            ' and init calculation matrix with maximums
            Dim line As String
            For I = 0 To height - 1
                line = sr.ReadLine()
                For J = 0 To line.Length - 1
                    map(J, I) = CType(Integer.Parse(line.Chars(J)), MapCell)
                    calc(J, I) = Integer.MaxValue
                Next
            Next

        End Using

        Console.WriteLine("Calculating shortest path ...")

        ' initialize calculation algorithm
        q = New Queue(Of Point)()   ' queue for breadth-search
        q.Enqueue(p)                ' queue first point to calc
        calc(p.X, p.Y) = 0          ' set steps at starting point to 0

        ' while there are points left
        While q.Count > 0

            p = q.Dequeue()                         ' get a point from queue
            If map(p.X, p.Y) = MapCell.EXIT Then    ' check if is at exit
                shortestLength = calc(p.X, p.Y)     ' if so, save the length
                Exit While                          ' and exit loop
            End If

            ' if not, push surrounding points into the stack
            ' and update their walk length
            Dim pa As Point() = New Point() { _
                New Point(p.X - 1, p.Y), _
                New Point(p.X + 1, p.Y), _
                New Point(p.X, p.Y - 1), _
                New Point(p.X, p.Y + 1)}

            length = calc(p.X, p.Y) + 1             ' +1 step

            For Each p In pa
                ' do not process walls
                If map(p.X, p.Y) = MapCell.WALL Then Continue For
                If length < calc(p.X, p.Y) Then     ' if new path is shorter
                    calc(p.X, p.Y) = length         ' save new path length
                    q.Enqueue(p)                    ' enqueue point for further calc
                End If                              ' else just leave the point alone
            Next
        End While

        ' display result and halt for keypress
        Console.WriteLine("Shortest path is " + shortestLength.ToString + " steps.")
        While (Not Console.KeyAvailable) : End While

    End Sub

End Module

3 Comments

  1. อันนี้จะเรียกว่า brute force เลยได้รึเปล่าชา?

    เคยเห็นอ.ทำให้ดูใน DataStructure แสดงให้เห็นว่าการเลือกตัวที่ถูกทำให้เร็วกว่ามากๆ

    คราวนี้ มีวิธีแก้ shortest path แบบอื่นที่เร็วกว่าอีกรึเปล่า?

  2. แบบนี้ อารมณ์มันไม่เหมือน Brute Force นะ

    คือถ้าเป็น Brute Force มันจะออกแนว ลองเดินไปเรื่อยๆ เจอทางตันก็วกกลับแล้วลองใหม่ทุกทางจนกว่าจะเจอ หรือไม่ก็ลองเดินไปทุกทางที่จะไปถึงทางออกได้ แล้วเอามาเทียบดูว่าทางเดินไหนสั้นที่สุด ไม่ได้ค่อยๆ สร้าง Solution ขึ้นมา

    Algorithm ที่เราทำนี้ มันใช้วิธี “แผ่พลัง” คือใช้ Matrix คำนวณเริ่มจากจุดเริ่มต้น แล้วค่อยๆ แผ่ออกไปทีละ 1 จนถึงจุดทางออกน่ะ คือจะเรียกว่าเป็น Dynamic Programming แบบหนึ่งก็ไม่ผิด นึกภาพว่า Maze นี่เป็นถาดน้ำ มีร่องน้ำเป็นทางเดิน แล้วเราเทน้ำลงตรงจุดเริมต้นน่ะ น้ำที่ไปถึงทางออกได้ก่อนก็ต้องแปลว่ามันเป็น Shortest Path ถูกมะ ถ้า Assume ว่าไม่คิดปริมาณน้ำ คิดแค่ว่าน้ำมันไหลไปตามร่องด้วยความเร็วคงที่เสมอ

    อืมถ้าเรียกว่าเป็น Memorization หรือ Divide-and-Conquer แบบกลายๆ น่าจะถูกสุด เพราะว่าเราค่อยๆ คำนวณระยะทางสั้นที่สุดจากจุดเริ่มค้นไปยังจุดต่างๆ ทีละจุด จนกระทั่งถึงจุดทางออก

    เช่นมีจุด 4 จุดเรียงกัน A - B - C - D โดย A เป็นจุดเริ่ม วิธีที่เราทำก็คือ เริ่มที่จุด A กำหนดจำนวนก้าวเป็น 0 แล้วคำนวณต่อที่จุดรอบๆ พอถึงจุด B เราก็สามารถใช้ผลการคำนวณจากจุด A มา +1 ได้เป็นเส้นทางที่สั้นที่สุดได้เลย โดยไม่ต้องไปเริ่มใหม่ตั้งแต่เริ่มน่ะ พอถึงจุด C เราก็เอาข้อมูลจากจุด B มาต่อได้เลย ประมาณนี้ มัน Guarantee ว่าทำงานไม่เกินจำนวนก้าวที่มากที่สุดที่จะไปถึงจุดหมายได้แน่นอน แล้วจุดที่ประมวลผลไปแล้ว ก้ไม่เอามาทำซ้ำอีก

    ถ้าเทียบกับ Algorithm Shortest Path อย่าง Dijkstra’s Algorithm นี่ก็ไม่ผิดนะ คือคิดซะว่าจุดแต่ละจุดเป็น Node บน Graph และระยะทางแต่ละ Node ที่เชื่อมถึงกันมีค่าเท่ากันคือ 1 เริ่มจากจุดเริ่มต้น ค่อยๆ แผ่ออกไป แล้วถ้าแผ่ไปเจอ Node ที่เคยถูกแผ่ไปแล้ว แต่ว่าระยะทางมันยาวกว่าระยะทางจากจุดที่กำลังคำนวณอยู่ ก็บันทึุกระยะทางใหม่ไว้ แล้วก็ทำจุดต่อไปต่อ จนกว่าจะถึงปลายทางน่ะ ลองไปอ่านของ Dijkstra’s เขาดูนะ

    อืม… อธิบายเป็นตัวหนังสือคงไม่ค่อยเห็นภาพอ่ะนะ เอาไว้เดี๋ยวพรุ่งนี้จะทำรูปประกอบเพิ่มแล้วกัน

  3. ขอบคุณมากครับ ได้แนวคิดมากเลย

    kagi

Leave a Reply