126 lines
3.1 KiB
Go
126 lines
3.1 KiB
Go
// Package diagram provide diagram data structure and operation
|
|
package diagram
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/gofrs/uuid"
|
|
)
|
|
|
|
var GlobalTree *MultiBranchTreeNode
|
|
|
|
// MultiBranchTreeNode represents a topological structure using an multi branch tree
|
|
type MultiBranchTreeNode struct {
|
|
ID uuid.UUID // 节点唯一标识
|
|
Parent *MultiBranchTreeNode // 指向父节点的指针
|
|
Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
|
|
}
|
|
|
|
func NewMultiBranchTree(id uuid.UUID) *MultiBranchTreeNode {
|
|
return &MultiBranchTreeNode{
|
|
ID: id,
|
|
Children: make([]*MultiBranchTreeNode, 0),
|
|
}
|
|
}
|
|
|
|
func (n *MultiBranchTreeNode) AddChild(child *MultiBranchTreeNode) {
|
|
child.Parent = n
|
|
n.Children = append(n.Children, child)
|
|
}
|
|
|
|
func (n *MultiBranchTreeNode) RemoveChild(childID uuid.UUID) bool {
|
|
for i, child := range n.Children {
|
|
if child.ID == childID {
|
|
n.Children = append(n.Children[:i], n.Children[i+1:]...)
|
|
child.Parent = nil
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (n *MultiBranchTreeNode) FindNodeByID(id uuid.UUID) *MultiBranchTreeNode {
|
|
if n.ID == id {
|
|
return n
|
|
}
|
|
|
|
for _, child := range n.Children {
|
|
if found := child.FindNodeByID(id); found != nil {
|
|
return found
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *MultiBranchTreeNode) PrintTree(level int) {
|
|
for i := 0; i < level; i++ {
|
|
fmt.Print(" ")
|
|
}
|
|
|
|
fmt.Printf("-ID: %s\n", n.ID)
|
|
|
|
for _, child := range n.Children {
|
|
child.PrintTree(level + 1)
|
|
}
|
|
}
|
|
|
|
// FindPath returns the ordered node sequence from startID to endID using the
|
|
// supplied nodeMap for O(1) lookup. It walks each node up to the root to find
|
|
// the LCA, then stitches the two half-paths together.
|
|
// Returns nil when either node is absent from nodeMap or no path exists.
|
|
func FindPath(startID, endID uuid.UUID, nodeMap map[uuid.UUID]*MultiBranchTreeNode) []*MultiBranchTreeNode {
|
|
startNode, ok := nodeMap[startID]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
endNode, ok := nodeMap[endID]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// collect ancestors (inclusive) from a node up to the root sentinel
|
|
ancestors := func(n *MultiBranchTreeNode) []*MultiBranchTreeNode {
|
|
var chain []*MultiBranchTreeNode
|
|
for n != nil {
|
|
chain = append(chain, n)
|
|
n = n.Parent
|
|
}
|
|
return chain
|
|
}
|
|
|
|
startChain := ancestors(startNode) // [start, ..., root]
|
|
endChain := ancestors(endNode) // [end, ..., root]
|
|
|
|
// index startChain by ID for fast LCA detection
|
|
startIdx := make(map[uuid.UUID]int, len(startChain))
|
|
for i, node := range startChain {
|
|
startIdx[node.ID] = i
|
|
}
|
|
|
|
// find LCA: first node in endChain that also appears in startChain
|
|
lcaEndPos := -1
|
|
lcaStartPos := -1
|
|
for i, node := range endChain {
|
|
if j, found := startIdx[node.ID]; found {
|
|
lcaEndPos = i
|
|
lcaStartPos = j
|
|
break
|
|
}
|
|
}
|
|
|
|
if lcaEndPos < 0 {
|
|
return nil // disconnected
|
|
}
|
|
|
|
// path = startChain[0..lcaStartPos] reversed + endChain[lcaEndPos..0] reversed
|
|
path := make([]*MultiBranchTreeNode, 0, lcaStartPos+lcaEndPos+1)
|
|
for i := 0; i <= lcaStartPos; i++ {
|
|
path = append(path, startChain[i])
|
|
}
|
|
// append end-side (skip LCA to avoid duplication), reversed
|
|
for i := lcaEndPos - 1; i >= 0; i-- {
|
|
path = append(path, endChain[i])
|
|
}
|
|
return path
|
|
}
|