วิธีการใช้โครงสร้างข้อมูลกราฟใน Golang
การนำทางความท้าทายเกี่ยวกับกราฟเป็นสิ่งที่เกิดขึ้นทั่วไปภายในขอบเขตของการพัฒนาซอฟต์แวร์ ไม่ว่าจะเป็นช่วงสัมภาษณ์ด้านเทคนิคหรือในขณะที่สร้างแอปพลิเคชันที่รวมโครงสร้างกราฟ
ทฤษฎีกราฟเป็นสาขาหนึ่งของคณิตศาสตร์ที่เกี่ยวข้องกับการศึกษากราฟ ซึ่งเป็นโครงสร้างข้อมูลพื้นฐานที่ใช้ในแอปพลิเคชันที่หลากหลาย เช่น แพลตฟอร์มเครือข่ายสังคม โครงสร้างพื้นฐานด้านการขนส่ง อัลกอริทึมคำแนะนำ และการวิเคราะห์เครือข่าย
ในขอบเขตของวิทยาการคอมพิวเตอร์ กราฟหมายถึงโครงสร้างข้อมูลที่ประกอบด้วยโหนดที่เชื่อมต่อกันด้วยขอบ โหนดเป็นตัวแทนของแต่ละองค์ประกอบหรือเอนทิตี ในขณะที่ขอบแสดงถึงความสัมพันธ์ระหว่างกัน จุดประสงค์ของการนำกราฟไปใช้คือเพื่อสร้างแบบจำลองระบบที่ซับซ้อนด้วยส่วนประกอบที่เชื่อมต่อกัน ซึ่งอำนวยความสะดวกในการแก้ปัญหาผ่านอัลกอริทึมต่างๆ เช่น การค้นหาเชิงลึกก่อน การค้นหาแนวกว้าง อัลกอริทึมของ Dijkstra และอื่นๆ ในภาษาโปรแกรม Go เราอาจใช้แพ็คเกจเช่น “github.com/gonum/graph” เพื่อการนำแนวคิดทฤษฎีกราฟไปใช้อย่างมีประสิทธิภาพ
กราฟคืออะไร?
กราฟสามารถกำหนดได้ว่าเป็นระบบที่ซับซ้อนซึ่งประกอบด้วยองค์ประกอบที่เชื่อมต่อถึงกัน ซึ่งเรียกว่าโหนดหรือจุดยอด ซึ่งเชื่อมต่อกันด้วยขอบที่แสดงถึงความสัมพันธ์หรือการเชื่อมโยง โครงสร้างข้อมูลนี้มีความโดดเด่นในโดเมนต่างๆ ซึ่งการเชื่อมต่อมีบทบาทสำคัญ เช่น เครือข่ายคอมพิวเตอร์ เครือข่ายสังคมออนไลน์ และแอปพลิเคชันอื่นๆ อีกมากมาย
ในฐานะโปรแกรมเมอร์ที่มีความเชี่ยวชาญ จำเป็นอย่างยิ่งที่คุณจะต้องทำความคุ้นเคยกับกราฟ ซึ่งเป็นโครงสร้างข้อมูลสำคัญที่นำเสนอวิธีการที่กว้างขวางและปรับเปลี่ยนได้ในการแสดงและตรวจสอบสถานการณ์ในชีวิตจริงที่หลากหลาย กราฟเป็นองค์ประกอบที่ขาดไม่ได้ของวิทยาการคอมพิวเตอร์ ทำหน้าที่เป็นรากฐานที่สำคัญสำหรับแอปพลิเคชันจำนวนมาก และถูกนำไปใช้อย่างแพร่หลายในอุตสาหกรรมต่างๆ เนื่องจากความเก่งกาจและประสิทธิภาพที่ไม่มีใครเทียบได้
สำรวจความซับซ้อนของเทคนิคการแก้ปัญหาโดยใช้กราฟที่แพร่หลายในแวดวงของการเขียนโปรแกรมคอมพิวเตอร์โดยเจาะลึกทรัพยากรที่ครอบคลุมของเราที่ให้การตรวจสอบเชิงลึกของโครงสร้างข้อมูลกราฟ นำเสนอข้อมูลเชิงลึกและการใช้งานจริงสำหรับผู้ที่ต้องการเพิ่มพูนความเข้าใจเกี่ยวกับ แนวคิดที่สำคัญนี้
การใช้กราฟใน Golang
เพื่อสร้างโครงสร้างข้อมูลตั้งแต่ต้น โดยทั่วไปจะใช้หลักการเขียนโปรแกรมเชิงวัตถุ อย่างไรก็ตาม ในขณะที่การเขียนโปรแกรมเชิงวัตถุเป็นส่วนสำคัญของการพัฒนาภาษา Go การนำไปใช้นั้นแตกต่างอย่างมากจากที่พบในภาษาต่างๆ เช่น Java และ C++
การใช้โครงสร้าง ประเภท และอินเทอร์เฟซ Go นำเสนอการนำหลักการเขียนโปรแกรมเชิงวัตถุมาใช้ ซึ่งช่วยให้สามารถสร้างโครงสร้างข้อมูลกราฟและการดำเนินการที่เกี่ยวข้องผ่านการใช้โครงสร้าง ประเภท และอินเทอร์เฟซเพียงอย่างเดียว
ในทฤษฎีกราฟ กราฟประกอบด้วยโหนดหรือจุดยอด ซึ่งแสดงถึงเอนทิตีหรือองค์ประกอบภายในโครงสร้างกราฟิก ตัวอย่างเช่น โหนดอาจหมายถึงอุปกรณ์ที่เชื่อมต่อกับเครือข่ายหรือบุคคลที่เข้าร่วมในแพลตฟอร์มโซเชียลมีเดีย ในทางกลับกัน ขอบหมายถึงความสัมพันธ์หรือความเชื่อมโยงที่เชื่อมโยงสองโหนดเข้าด้วยกัน โดยพื้นฐานแล้ว กราฟจะแสดงให้เห็นถึงความเชื่อมโยงระหว่างส่วนประกอบต่างๆ ผ่านการเชื่อมต่อหรือความสัมพันธ์เหล่านี้
ในการสร้างกราฟโดยใช้ภาษาโปรแกรม Go เริ่มแรกจำเป็นต้องสร้างโครงสร้างโหนดที่มีคุณสมบัติสะท้อนโหนดที่อยู่ติดกัน โหนดข้างเคียงเหล่านี้เป็นตัวแทนของโหนดที่เชื่อมต่อโดยตรงกับโหนดที่ระบุ
ในกราฟกำกับ ขอบมีการวางแนวที่เฉพาะโหนดที่โหนดใดโหนดหนึ่งมุ่งตรงไปเท่านั้นที่ถือว่าเป็นเพื่อนบ้าน ในทางกลับกัน ในกราฟที่ไม่มีการกำหนดทิศทาง โหนดทั้งหมดที่เข้าร่วมในขอบที่ใช้ร่วมกันกับโหนดหนึ่งๆ จะรับรู้เป็นโหนดข้างเคียง
รหัสที่กำหนดแสดงภาพประกอบของโครงสร้างและลักษณะของวัตถุโหนดอย่างครบถ้วน แสดงคุณลักษณะต่างๆ และค่าที่สอดคล้องกัน
type Node struct {
Neighbors []*Node
}
เพื่ออธิบายแนวคิดของกราฟที่ไม่มีการกำหนดทิศทาง เราจะอธิบายลักษณะเฉพาะโดยละเอียด เพื่อความชัดเจน ให้เรานำเสนอตัวอย่างโครงสร้างโหนดสำหรับกราฟโดยตรงที่แสดงให้เห็นถึงโครงสร้างและการจัดระเบียบ
type Node struct {
OutNeighbors []*Node // outgoing edges
InNeighbors []*Node // incoming edges
}
ด้วยการใช้เส้นแบ่งนี้ OutNeighborsarray จะรักษารายการของโหนดที่มีขอบกำกับภายนอกที่มาจากโหนดปัจจุบัน ในขณะที่ส่วนประกอบ InNeighbors จะเก็บชุดของโหนดที่ให้ขอบขาเข้าไปยังโหนดปัจจุบัน
ในการสร้างกราฟ เราจะใช้ตัวอย่างของโครงสร้างข้อมูลที่เรียกว่าแผนที่ ซึ่งแต่ละรายการประกอบด้วยคีย์จำนวนเต็มและค่าโหนดที่เกี่ยวข้อง การกำหนดค่านี้เรียกโดยทั่วไปว่าการแสดงรายการคำเชื่อม ซึ่งคีย์ทำหน้าที่เป็นตัวระบุเฉพาะสำหรับโหนดแต่ละโหนด ในขณะที่ค่าที่เกี่ยวข้องกำหนดโหนดเดียวกันเหล่านั้นภายในกราฟ
รหัสต่อไปนี้แสดงโครงสร้างกราฟ:
type Graph struct {
nodes map[int]*Node
}
แนวคิดของคีย์จำนวนเต็มสามารถคิดได้เทียบเท่ากับค่าที่เกี่ยวข้องกับโหนดที่เกี่ยวข้อง อย่างไรก็ตาม ในสถานการณ์จริง การแสดงโหนดอาจไม่จำกัดเฉพาะจำนวนเต็มอย่างง่ายเสมอไป โหนดอาจแสดงโครงสร้างข้อมูลที่ซับซ้อนมากขึ้นแทน เช่น โปรไฟล์สำหรับบุคคลหรือเอนทิตีอื่นๆ ที่คล้ายคลึงกัน ในกรณีเหล่านี้ จะเป็นการเหมาะสมที่จะรวมข้อมูลที่เกี่ยวข้องภายในโครงสร้างโหนดเองโดยการรวมเป็นหนึ่งในคุณสมบัติ
โค้ดที่ให้มาแสดงถึงเมธอดคอนสตรัคเตอร์สำหรับคลาส กราฟ
ซึ่งทำหน้าที่เริ่มต้นอินสแตนซ์ใหม่ของโครงสร้างข้อมูลกราฟโดยจัดสรรหน่วยความจำสำหรับการแสดงรายการคำเชื่อมและอนุญาตให้เพิ่มโหนดในกราฟ
func NewGraph() *Graph {
return &Graph{
nodes: make(map[int]*Node),
}
}
เพื่ออำนวยความสะดวกในการจัดการกับโครงสร้างข้อมูลกราฟ เราสามารถใช้ชุดของวิธีการที่เปิดใช้งานการดำเนินการของฟังก์ชันที่หลากหลาย เช่น การแทรกโหนด การสร้างขอบ ความสามารถในการค้นหา และงานอื่น ๆ ที่เกี่ยวข้อง ช่วงของการดำเนินการที่สามารถดำเนินการบนกราฟนั้นกว้างขวาง ครอบคลุมการรวมโหนดใหม่ การสร้างการเชื่อมต่อระหว่างโหนดที่มีอยู่ การดึงองค์ประกอบเฉพาะ และความเป็นไปได้เพิ่มเติมมากมาย
ในข้อความนี้ เราจะเจาะลึกถึงความสามารถในการจัดการกราฟผ่านการใช้งานฟังก์ชันที่เปิดใช้งานการเพิ่มและลบทั้งโหนดและขอบภายในกราฟ ตัวอย่างรหัสที่ตามมาเป็นตัวอย่างของการใช้งานจริงของการดำเนินการดังกล่าว
การเพิ่มโหนดในกราฟ
ในการรวมองค์ประกอบใหม่เข้ากับกราฟ ต้องใช้การดำเนินการแทรกซึ่งแสดงสูตรต่อไปนี้:
func (g *Graph) AddNode(nodeID int) {
if _, exists := g.nodes[nodeID]; !exists {
newNode := &Node{
Neighbors: []*Node{},
}
g.nodes[nodeID] = newNode
fmt.Println("New node added to graph")
} else {
fmt.Println("Node already exists!")
}
}
ฟังก์ชัน"AddNode"ออกแบบมาเพื่อรวมองค์ประกอบใหม่ภายในกราฟ และทำได้โดยยอมรับตัวระบุ ซึ่งทำหน้าที่เป็นข้อมูลอ้างอิงเฉพาะสำหรับส่วนประกอบดังกล่าว เป็นพารามิเตอร์อินพุต อย่างไรก็ตาม ก่อนที่จะรวมส่วนเพิ่มเติมที่เสนอใหม่นี้เข้ากับเครือข่าย ระบบจะตรวจสอบว่าเอนทิตีอื่นได้รับการกำหนดตำแหน่งที่เหมือนกันก่อนหน้านี้หรือไม่ เพื่อป้องกันการซ้ำซ้อนหรือความซ้ำซ้อนภายในโครงสร้างของกราฟ
การเพิ่มขอบให้กับกราฟ
เพื่อสร้างการเชื่อมต่อระหว่างสองโหนดในโครงสร้างข้อมูลกราฟแบบไม่มีทิศทาง เราใช้ฟังก์ชัน EdgeInsertion ซึ่งเพิ่มขอบ เนื่องจากกราฟไม่มีการเชื่อมต่อโดยตรง จึงไม่ต้องกังวลเกี่ยวกับการวางแนวในขณะที่สร้างลิงก์
โค้ดที่ให้มาใช้วิธีเพิ่มขอบกำกับระหว่างจุดยอดสองจุดในกราฟถ่วงน้ำหนัก โดยอิงจากระยะทางแบบยุคลิดที่คำนวณโดยใช้ฟังก์ชัน Distance
และน้ำหนักของขอบที่จัดเก็บไว้ในเมทริกซ์คำเชื่อม ขอบใหม่ถูกเพิ่มเข้าไปในชุดเพื่อนบ้านของจุดยอดต้นทางและเป้าหมาย ซึ่งเป็นอาร์เรย์ที่มีจุดยอดทั้งหมดที่เชื่อมต่อผ่านเมทริกซ์ที่อยู่ติดกัน
func (g *Graph) AddEdge(nodeID1, nodeID2 int) {
node1 := g.nodes[nodeID1]
node2 := g.nodes[nodeID2]
node1.Neighbors = append(node1.Neighbors, node2)
node2.Neighbors = append(node2.Neighbors, node1)
}
ในกราฟที่ไม่มีทิศทาง การเพิ่มขอบเกี่ยวข้องกับการกำหนดโหนดสองโหนดให้เป็นเพื่อนบ้านซึ่งกันและกัน ซึ่งสามารถทำได้ผ่านขั้นตอนที่ตรงไปตรงมา โดยใช้ตัวระบุโหนดที่กำหนดเพื่อผนวกทั้งสองโหนดเข้ากับรายการองค์ประกอบข้างเคียงตามลำดับของกันและกัน
การลบขอบออกจากกราฟ
ในการกำจัดจุดยอดออกจากกราฟในขณะที่รักษาความสมบูรณ์ของข้อมูลที่แสดงโดยกราฟดังกล่าว จำเป็นต้องลบจุดยอดนั้นออกจากรายการของโหนดข้างเคียงแต่ละรายการ สิ่งนี้ทำให้มั่นใจได้ว่าข้อมูลที่แสดงโดยกราฟจะไม่มีความคลาดเคลื่อน
ในการลบโหนดเฉพาะออกจากโหนดที่อยู่ติดกันแต่ละโหนด จำเป็นต้องสร้างวิธีการสำหรับกำจัดความสัมพันธ์ระหว่างโหนดในขั้นต้น ดังนั้น ก่อนที่จะกำหนดอัลกอริทึมสำหรับกำจัดโหนดแต่ละโหนด จำเป็นต้องมีการกำหนดวิธีการที่เป็นระบบสำหรับการตัดความสัมพันธ์ที่ผูกโหนดเหล่านี้เข้าด้วยกัน
ข้อมูลโค้ดที่ให้ไว้แสดงวิธีการลบขอบออกจากกราฟ ซึ่งแสดงเป็น removeEdge(edge)
ภายในคลาสกราฟ การใช้งานเกี่ยวข้องกับการวนซ้ำผ่านแต่ละโหนดในรายการโหนดและตรวจสอบว่าตรงกับหนึ่งในจุดสิ้นสุดของขอบที่ระบุหรือไม่ หากพบ โหนดทั้งสองจะถูกลบออกจากรายการโหนดตามลำดับ ต่อจากนั้น ข้อมูลของ Edge และการอ้างอิงถึงสองโหนดจะถูกลบด้วย
func (g *Graph) removeEdge(node, neighbor *Node) {
index := -1
for i, n := range node.Neighbors {
if n == neighbor {
index = i
break
}
}
if index != -1 {
node.Neighbors =
append(node.Neighbors[:index], node.Neighbors[index\+1:]...)
}
}
func (g *Graph) RemoveEdge(node, neighbor *Node) {
g.removeEdge(node, neighbor)
g.removeEdge(neighbor, node)
fmt.Println("Edge successfully removed")
}
ฟังก์ชัน removeEdge
รับสองโหนดเป็นอินพุตและค้นหาดัชนีของโหนดที่สองภายในรายการเพื่อนบ้านของโหนดแรก ต่อจากนั้น จะใช้วิธีที่เรียกว่า"การหั่นเป็นชิ้น"เพื่อกำจัดการเชื่อมต่อระหว่างกัน
กระบวนการเกี่ยวข้องกับการแยกองค์ประกอบภายในช่วงที่กำหนด ยกเว้นองค์ประกอบที่ดัชนีที่ระบุ จากนั้นจึงรวมองค์ประกอบเหล่านั้นเข้ากับองค์ประกอบที่เป็นไปตามดัชนีดังกล่าวอย่างราบรื่น ดังนั้น องค์ประกอบที่อยู่ในตำแหน่งที่กำหนดจึงถูกข้ามไป
ในสถานการณ์ที่มีกราฟที่ไม่มีทิศทางแสดงอยู่ กราฟนี้มีขอบแบบสองทิศทาง ทำให้ต้องใช้เมธอด removeEdge
สองครั้งภายในฟังก์ชัน RemoveEdge
หลักเพื่อลบเพื่อนบ้านทั้งสองออกจากรายการโหนดที่เกี่ยวข้อง
การลบโหนดออกจากกราฟ
เมื่อกำจัดขอบภายในกราฟได้สำเร็จ ก็จะสามารถกำจัดแต่ละโหนดได้เช่นกัน ต่อไปนี้แสดงถึงฟังก์ชันที่อำนวยความสะดวกในการลบโหนดออกจากกราฟ:
func (g *Graph) RemoveNode(nodeID int) {
node, exists := g.nodes[nodeID]
if !exists {
fmt.Println("Node doesn't exist")
return
}
for _, neighbor := range node.Neighbors {
g.RemoveEdge(node, neighbor)
}
delete(g.nodes, nodeID)
fmt.Println("Node deleted successfully")
}
ฟังก์ชันที่มีให้ใช้เป็นอินพุตของตัวระบุเฉพาะที่สอดคล้องกับโหนดเฉพาะภายในกราฟ และตรวจสอบว่ามีโหนดดังกล่าวอยู่ภายในโครงสร้างข้อมูลพื้นฐานหรือไม่ หากสามารถพบโหนดที่ระบุได้ ฟังก์ชันจะดำเนินการตัดการเชื่อมต่อจากขอบทุกด้านที่เชื่อมต่อกับโหนดนั้น หลังจากนั้นจะลบโหนดออกทั้งหมดโดยใช้กลไกการลบในตัวซึ่งมีอยู่ในภาษาการเขียนโปรแกรมของ Go
คุณมีตัวเลือกในการรวมเทคนิคเพิ่มเติมสำหรับการสำรวจกราฟ รวมถึงอัลกอริทึมการค้นหาเชิงลึกและการค้นหาแนวกว้าง นอกเหนือจากวิธีการแสดงกราฟ การใช้ฟังก์ชันเหล่านี้มีความยืดหยุ่นและปรับเปลี่ยนได้ตามความต้องการของคุณ
ในฐานะนักพัฒนา สิ่งสำคัญคือต้องคำนึงถึงประสิทธิภาพและประสิทธิผลของโครงสร้างข้อมูลกราฟในการจัดการกับข้อกำหนดกรณีการใช้งานเฉพาะ การใช้โครงสร้างเหล่านี้อย่างไม่เหมาะสมอาจนำไปสู่ผลเสียต่อสถาปัตยกรรมโดยรวมของแอปพลิเคชัน ดังนั้นจึงจำเป็นอย่างยิ่งที่จะต้องมีความเข้าใจอย่างรอบด้านในการเลือกโครงสร้างข้อมูลที่เหมาะสมตามความต้องการเฉพาะของแต่ละสถานการณ์
สร้างซอฟต์แวร์ที่ปรับให้เหมาะสมโดยใช้โครงสร้างข้อมูลที่เหมาะสม
Go เป็นรากฐานที่ยอดเยี่ยมในการสร้างโซลูชันซอฟต์แวร์ที่มีประสิทธิภาพสูง อย่างไรก็ตาม การไม่คำนึงถึงวิธีการเขียนโปรแกรมเสียงอาจทำให้เกิดความยุ่งยากเกี่ยวกับโครงสร้างและประสิทธิภาพของแอปพลิเคชันได้
การใช้โครงสร้างข้อมูลที่เหมาะสม รวมถึงอาร์เรย์ รายการที่เชื่อมโยง และกราฟ เป็นแนวทางปฏิบัติที่ดีที่สุดที่สำคัญเพื่อให้แน่ใจว่าแอปพลิเคชันทำงานได้อย่างเหมาะสมที่สุด ในขณะเดียวกันก็ลดปัญหาที่อาจเกิดขึ้นซึ่งเกี่ยวข้องกับปัญหาคอขวดของประสิทธิภาพหรือความล้มเหลวของระบบ