การค้นหาแบบสองทิศทาง

การค้นหาแบบสองทิศทาง () คือวิธีหนึ่งที่ใช้สำหรับการค้นหาข้อมูลภายในกราฟระบุทิศทาง โดยมีจุดประสงค์เป็นการหาวิถีสั้นสุดจากจุดเริ่มต้นไปยังจุดสิ้นสุดบนกราฟ หลักการค้นหาที่เป็นเอกลักษณ์ของวิธีการนี้ก็คือเราจะทำการค้นหาจากจุดเริ่มไปยังจุดสิ้นสุดและจากจุดสิ้นสุดกลับมายังจุดเริ่มต้นไปพร้อม ๆ กัน และเมื่อการค้นหามาบรรจบพร้อมกันที่จุด ๆ หนึ่งระหว่างกลางก็จะถือเป็นอันสิ้นสุด นอกจากนี้การค้นหาแบบสองทิศทางนี้ยังสามารถนำเอาไปประยุกต์รวมเข้ากับการค้นหาแบบอื่น ๆ เพื่อให้ได้ประสิทธิภาพที่ดียิ่งขึ้นได้ ตัวอย่างของวิธีการค้นหาที่สามารถนำเอามาประยุกต์กับการค้นหาแบบสองทิศทาง เช่น การค้นตามแนวกว้าง, การค้นแบบดีที่สุด, ขั้นตอนวิธีเอสตาร์ เป็นต้น ทั้งนี้ก็เพื่อที่จะเพิ่มประสิทธิภาพในการค้นหาให้ดีที่สุดนั่นเอง

 นิยาม 
การค้นหาแบบสองทิศทางนั้นโดยนิยามแล้วก็คือขั้นตอนวิธีที่ใช้หลักการซึ่งคล้ายกับขั้นตอนวิธีแบ่งแยกเพื่อเอาชนะ ()ในกรณีที่เราทราบตำแหน่งของเป้าหมายที่จะค้นหาแล้ว แทนที่จะค่อย ๆ เริ่มจากจุดเริ่มต้นไปยังจุดปลายเราจะทำการค้นหาจากจุดปลายย้อนกลับมาหาจุดเริ่มต้นไปพร้อม ๆ กันแทน ด้วยวิธีนี้ความเร็วในการค้นหาของแต่ละเส้นทางจะอยู่ที O (bd/2) เมื่อ  คือจำนวนการแตกกิ่งก้าน (Branching factor) และ  คือระยะทางทั้งหมดจากจุดเริ่มต้นไปยังจุดสิ้นสุด ซึ่งเมื่อนำระยะเวลาการค้นหามารวมกันแล้วก็ยังถือว่าได้ลดเวลาในการค้นหาลงไปอย่างมากหากเทียบกับการค้นหาแบบปกติ O (bd)

อย่างไรก็ตามแม้ว่าวิธีการนี้จะดูเหมือนว่าสามารถที่จะลดเวลาการค้นหาไปได้อย่างมากก็ตาม ข้อเสียของมันก็ยังมีอยู่หลายข้อด้วยกันคือ
 วิธีการค้นหาแบบสองทิศทางมีลักษณะที่เป็นศึกษาสำนึกหรือก็คือไม่สามารถบอกได้อย่างแน่นอนว่าเส้นทางที่หาได้เป็นเส้นทางที่ดีที่สุดหรือไม่
 ในการออกแบบขั้นตอนวิธีจำเป็นที่จะต้องคิดหาวิธีการที่จะทำให้สามารถค้นหาจากจุดปลายให้ย้อนกลับมายังจุดเริ่มต้นได้
 ต้องออกแบบวิธีการหาจุดเชื่อมต่อที่มาจากการค้นของทั้งสองทิศทาง
 ต้องมีการกำหนดเป้าหมายว่าอยู่ที่ใดบนกราฟ

ด้วยสาเหตุทั้งปวงที่กล่าวมาทำให้การนำเอาวิธีการค้นหาแบบสองทิศทางไปใช้งานจริงนั้นจึงยุ่งยากพอสมควร

 ประวัติ 
Ira Pohl คือบุคคลแรกที่ออกแบบและนำเอาการค้นหาแบบสองทิศทางมาใช้ในปี ค.ศ. 1971 เริ่มแรกนั้นขั้นตอนวิธีดังกล่าวไม่มีประสิทธิภาพมากนักคือการค้นหาจากสองทางมักจะพลาดไม่ได้มาบรรจบกันทำให้ได้ผลลัพธ์ที่ผิดพลาด ต่อมาในปี ค.ศ. 1983 Des Champeaux ได้ออกแบบขั้นตอนวิธีใหม่เพื่อเข้ามาใช้แก้ปัญหาดังกล่าวด้วยวิธีแบบ BHFFA (Bidirectional heuristic front to front algorithm) แต่ก็ทำให้เกิดปัญหาในเรื่องพื้นที่หน่วยความจำ ต่อมาในปี ค.ศ. 1984 Pohl และ Politowisky ได้นำเสนอทางออกอีกแบบที่เขาเรียกว่า D-node retargeting ขึ้นมาซึ่งสามารถช่วยแก้ปัญหาที่มีมาแต่เดิมรวมถึงเรื่องของหน่วยความจำได้อย่างมีประสิทธิภาพกว่าเก่า

หลังจากนั้นวิธีการค้นหาแบบสองทิศทางก็ได้ผ่านการปรับปรุงเรื่อยมาอีกหลายครั้งจนถึงล่าสุด คือ ปี ค.ศ. 2009 โดย Wim และ Henk ซึ่งได้คิดค้นออกแบบการค้นหาแบบสองทิศทางของเอสตาร์ที่ได้รับการปรังปรุงให้ค้นหาได้อย่างมีประสิทธิภาพยิ่งขึ้น

 การทำงาน 
400px|center|thumb|ภาพตัวอย่างของการค้นหาแบบสองทิศทาง

จากกราฟด้านบนเราจะสามารถมองเห็นการทำงานคร่าว ๆ ได้โดย หากเราสมมติให้ วงกลมแต่ละวงคือปมของกราฟโดยที่แต่ละปมจะเก็บค่าตำแหน่งของปมนั้น ๆ เอาไว้ เส้นเชื่อมที่หนาจะหมายถึงค่าใช้จ่ายที่มากกว่าในการเดินทางผ่าน ส่วนปมที่ถูกทาด้วยสีฟ้าจะหมายถึงเป็นปมที่ถูกเลือกและปมสีแดงคือจุดที่คาดว่าการค้นหาจากสองทิศทางมาบรรจบกัน เส้นประใช้แสดงขอบเขตของการค้นหาแยกแต่ละฝั่งเอาไว้

ในแง่ของการนำเอาไปใช้งานจริงนั้น การค้นหาแบบสองทิศทางมักจะถูกนำไปรวมเข้ากับขั้นตอนวิธีแบบอื่นเสียมากกว่า ทั้งนี้ก็เพื่อที่จะให้ได้ผลลัพธ์ออกมาตามแต่ที่ต้องการ

 รหัสเทียม 
ขั้นตอนวิธีสามารถแสดงโดยสังเขปด้วยรหัสเทียมได้ดังนี้

 function BiderectionalSearch(xs,xg)

    define Qs,Qg as priority queue                  //กำหนดแถวคอยแบบลำดับความสำคัญ ให้ Qs คือ แถวคอยที่ใช้เก็บเริ่มจากปมเริ่มต้น และ Qg คือ แถวคอยที่ใช้เก็บเริ่มจากปมเป้าหมาย
    Qs.insert(xs) and mark xs as visited            //นำปมเริ่มต้น xs ใส่ลงใน Qs และเปลี่ยนสถานะของ xs ให้เป็นปมที่ถูกพิจารณาแล้ว
    Qg.insert(xg) and mark xg as visited            //นำปมเป้าหมาย xg ใส่ลงใน Qg และเปลี่ยนสถานะของ xg ให้เป็นปมที่ถูกพิจารณาแล้ว

    while Qs not empty and Qg not empty
       if(Qs not empty)
         x:=Qs.getFirst()
         if x = xg or x ɛ Qg                        //ถ้าหากว่าปมที่กำลังดูเป็นปมเป้าหมายหรือว่าเป็นปมที่อยู่ในแถวคอยของเป้าหมายแล้วก็ให้แสดงออกไปว่าหาพบและเลิกทำ
            return SUCCESS
         for each v ɛ adj(x)                        //สำหรับทุก ๆ ปมที่มีเส้นเชื่อมต่อออก x หากปม ๆ นั้นยังไม่เคยถูกพิจารณาก็ให้เอาเข้าแถวคอยไป
            if v is not visited
               Mark v status as visited
               Qs.insert(v)
       if(Qg not empty)
         xr:=Qg.getFirst()
         if xr = xs or xr ɛ Qs                     //ถ้าหากว่าปมที่กำลังดูเป็นปมเริ่มต้นหรือว่าเป็นปมที่อยู่ในแถวคอยของปมเริ่มต้นแล้วก็ให้แสดงออกไปว่าหาพบและเลิกทำ
            return SUCCESS
         for each vr ɛ inversed_adj(xr)            //สำหรับทุก ๆ ปมที่มีเส้นเชื่อมมายัง xr หากปม ๆ นั้นยังไม่เคยถูกพิจารณาก็ให้เอาเข้าแถวคอยไป
            if vr is not visited
               Mark vr status as visited
               Qs.insert(vr)

   return FAILURE                                  //ถ้าทำจนหมดแล้วยังเชื่อมเส้นกันไม่ได้ก็คือไม่มีเส้นเชื่อมระหว่าง xs กับ xg

 อ้างอิง 
 .
 .
 .
 .
 .
 .
 .

หมวดหมู่:ขั้นตอนวิธีการค้นหา
หมวดหมู่:ขั้นตอนวิธีกราฟ