GoベースTree, BST, Heap

18179 ワード

Tree



treeにはルートのRootノードがあり,その底部はChildノードである.
Childノードの真上のノードをParentノードと呼び,相対的である.
同じ段階にあるものをSiblingと言います.
最末端にChildノードを持たないノードをLeafノードと呼ぶ.
代表的な例はFolderです.
ex)
package dataStruct

type TreeNode struct {
	Val    int
	Childs []*TreeNode // 포인터형 배열
}

type Tree struct {
	Root *TreeNode
}

func (t *Tree) AddNode(val int) {
	if t.Root == nil {
		t.Root = &TreeNode{Val: val} //루트가 없을 때 루트를 하나 만든다
	} else {
		t.Root.Childs = append(t.Root.Childs, &TreeNode{Val: val}) //루트가 있으면 Childs에 노드를 추가한다.
	}
}

Tree DFS


Depth First Search


深度優先ナビゲーション

最初のサブノードからそのノードのサブノードまで延長し、サブノードがない場合は親ノードに戻り、他のサブノードがない場合は親ノードに戻ります.
Dijkstraアルゴリズム
DFS原理に基づいて、開始ノードからサブノードに沿って最小値を検索し、最短距離を検索するアルゴリズム.

1.再帰呼び出しを利用する方法


ex) package
package dataStruct

import "fmt"

type TreeNode struct {
	Val    int
	Childs []*TreeNode
}

type Tree struct {
	Root *TreeNode
}

func (t *TreeNode) AddNode(val int) { 
	t.Childs = append(t.Childs, &TreeNode{Val: val})
} // child 노드를 추가하는 매소드

func (t *Tree) AddNode(val int) {
	if t.Root == nil {
		t.Root = &TreeNode{Val: val} //루트가 없을 때 루트를 하나 만든다
	} else {
		t.Root.Childs = append(t.Root.Childs, &TreeNode{Val: val}) //루트가 있으면 Childs에 노드를 추가한다.
	}
}

func (t *Tree) DFS1() { // Tree의 매소드
	DFS1(t.Root) // t.Root를 인자로 아래의 함수 DFS1을 콜함
}

func DFS1(node *TreeNode) {
	fmt.Printf("%d->", node.Val) // Val 값 출력

	for i := 0; i < len(node.Childs); i++ { // child노드의 길이 만큼큼
		DFS1(node.Childs[i]) // 재귀 호출
	}
	// childs[0]번째 인덱스 Val = 2 를 출력하고
	// 그 child 노드의 [0]번 째 인덱스를 또 출력 하는 방식
	// 만약 없으면 한단계 위로 올라가서 다른 child 노드를 찾는다
}
ex) main
package main

import (
	"Golesson/dataStruct"
)

func main() {
	tree := dataStruct.Tree{}

	val := 1
	tree.AddNode(val) // Root 를 만듦
	val++             //Val을 증가 시킴 = 2

	for i := 0; i < 3; i++ { // Root의 child 노드에 3개의 노드를 추가
		tree.Root.AddNode(val) // Val = 2 ~ 4 까지의 child 노드가 만들어짐
		val++                  //Val을 증가 시킴 = 3
	}
	for i := 0; i < len(tree.Root.Childs); i++ { // Root의 child 노드를 돌면서
		for j := 0; j < 2; j++ {
			tree.Root.Childs[i].AddNode(val) // 2개 씩 child노드를 추가해줌
			val++
		}
	}

	tree.DFS1() // DFS 탐색을 진행
}
-----------------------------
1->2->5->6->3->7->8->4->9->10->

2.スタックを使用する方法


ex)
<생략>

func (t *Tree) DFS2() {
	s := []*TreeNode{}    // *TreeNode 슬라이스 생성
	s = append(s, t.Root) // 슬라이스에 Root의 주소 추가

	for len(s) > 0 {
		var last *TreeNode
		last, s = s[len(s)-1], s[:len(s)-1]
		// last = s의 길이 -1  즉 마지막 인덱스 를 last로 초기화
		// 처음 last에 들어가는건 Root
		// s = 0 ~ s의 길이 -1  즉 마지막 인덱스를 제거

		// 2번째 왔을때 s 는 3개가 되고 마지막 인덱스가 last{Val:4}

		// 3번째 왔을때 s 는 2개 마지막 인덱스 last{Val:10}

		fmt.Printf("%d->", last.Val) // 1 -> 4 -> 10

		for i := 0; i < len(last.Childs); i++ {
			s = append(s, last.Childs[i])
			// last.Childs = Root.Childs 만큼 반복 = 3번
			// child 노드를 다시 s로 집어 넣는다. 3개

			// 2번째 last.Childs = 4번째 노드의 Childs개수 만큼 반복 = 2번
			// child 노드를 다시 s로 집어 넣는다. 2개

			// 3번째 last.childs = 10번째 노드의 Childs개수 만큼 반복 = 0번
			// for문은 진행 되지 않고 다시 위로 넘어감
		}
------------------------------------    <생략>    
tree.DFS1()        
-----------------------------------
1->4->10->9->3->8->7->2->6->5->
順番に
<생략>
		for i := len(last.Childs)-1; i >= 0; i-- {
			s = append(s, last.Childs[i])
			// 뒤에서 부터가 아닌 순서대로 출력 가능
		}
	}
-------------------------------------
<생략>    
tree.DFS1()        
-----------------------------------
1->2->5->6->3->7->8->4->9->10->

Tree BFS


Breadth First Search


幅優先ナビゲーション

兄弟順に探索する.

Queueの使用方法


ex)
<생략>

func (t *Tree) BFS() {
	queue := []*TreeNode{}        //슬라이스 생성
	queue = append(queue, t.Root) // 루트를 넣음

	for len(queue) > 0 {
		var first *TreeNode
		first, queue = queue[0], queue[1:]
		// 맨처음 인덱스를 first로 넣고 queue에서 제거

		// 2번째 돌때 2번째 인덱스를 first로 {Val:2}

		// 3번째 돌때  3번째 인덱스를 first로 {Val:3}

		fmt.Printf("%d->", first.Val) //1->2->

		for i := 0; i < len(first.Childs); i++ {
			queue = append(queue, first.Childs[i])
			// first.Childs = root.childs 만큼 = 3번 반복
			// queue에 root의 child 노드 2번 노드 부터 4번 노드까지 들어감

			// 2번째 queue에 2번 노드의 child 노드 2개 들어감

			// 3번째 queue에 3번 노드의 child 노드가 2개 들어감

			//뒤에 쌓아놓고 선입 선출 하는 방식
		}
	}
}
---------------------------
1->2->3->4->5->6->7->8->9->10->

BST


Binary Search Tree


バイナリナビゲーションツリー



rootを基準に、左はrootより小さい値、右は大きな値です.
childに移動しても、親ノードの値よりも左側で、大きな値は右側です.
DFSまたはBFSの欠点は、特定のノードを検索するために、そのノードが現れる前のすべてのノードを巡回しなければならないことである.O(N)
BSTでは、特定のノードを検索するときに親ノードと比較し、小さい場合は左に大きくなると右に素早く検索できます.O(logN)
そくどず

赤色O(N)
青色O(logn)
ex) package
package dataStruct

import "fmt"

type BinaryTreeNode struct {
	Val   int
	left  *BinaryTreeNode
	right *BinaryTreeNode
} // 자식을 2개 밖에 같지 않는 이진 트리

type BinaryTree struct {
	Root *BinaryTreeNode
}

func NewBinaryTree(v int) *BinaryTree {
	tree := &BinaryTree{}
	tree.Root = &BinaryTreeNode{Val: v}
	return tree //Root를 생성하는 함수
}

func (n *BinaryTreeNode) AddNode(v int) *BinaryTreeNode {
	if n.Val > v { // 현재 노드의 Val 보다 작으면
		if n.left == nil { // 왼쪽이 nil 일때
			n.left = &BinaryTreeNode{Val: v} // 왼쪽에 노드 생성해서 주소 저장
			return n.left
		} else { // 왼쪽이 nil이 아닐때
			return n.left.AddNode(v)
			// 재귀 호출로 왼쪽 노드와 비교하여 작으면 해당 노드의 왼쪽에 집어 넣음
		}
	} else { // 현재 노드의 Val 보다 크면
		if n.right == nil { // 오른쪽 값이 nil 일때
			n.right = &BinaryTreeNode{Val: v} // 오른쪽에 노드 생성후 주소 저장
			return n.right
		} else {
			return n.right.AddNode(v)
			// 재귀 호출로 오른쪽 노드와 비교하여 크면 해당 노드의 오른쪽에 집어 넣음
		}
	}
}

type depthNode struct {
	depth int
	node  *BinaryTreeNode
} // 깊이(줄)를 나타내기 위해 만든 구조체

// BFS를 통해 출력하기
func (t *BinaryTree) Print() {
	q := []depthNode{}
	q = append(q, depthNode{depth: 0, node: t.Root})
	currentDepth := 0 // 현재 줄을 나타내기 위한 변수

	for len(q) > 0 {
		var first depthNode
		first, q = q[0], q[1:] // queue 방식을 이용 선입선출

		if first.depth != currentDepth { //현재 줄에 해당 되지 않으면
			fmt.Println() // sibling이 늘 때 마다 띄어쓰기
			currentDepth = first.depth
		}
		fmt.Print(first.node.Val, " ") // 현재 노드의 Val값 출력

		if first.node.left != nil {
			q = append(q, depthNode{depth: currentDepth + 1, node: first.node.left})
			// 현재 노드의 왼쪽 값이 nil이 아닐때 currentDepth +1
			// first.node.left = root.left 값을 집어 넣음
		}
		if first.node.right != nil {
			q = append(q, depthNode{depth: currentDepth + 1, node: first.node.right})
			// 현재 노드의 왼쪽 값이 nil이 아닐때 currentDepth +1
			// first.node.right = root.right 값을 집어 넣음
		}
	}
}
ex) main
package main

import (
	"Golesson/dataStruct"
)

func main() {
	tree := dataStruct.NewBinaryTree(5)
	tree.Root.AddNode(3)
	tree.Root.AddNode(2)
	tree.Root.AddNode(4)
	tree.Root.AddNode(8)
	tree.Root.AddNode(7)
	tree.Root.AddNode(6)
	tree.Root.AddNode(10)
	tree.Root.AddNode(9)

	tree.Print()

}
----------------------------
5
3 8
2 4 7 10
6 9

ノードの検索


ex) package
<생략>
func (t *BinaryTree) Search(v int) bool {
	return t.Root.Search(v) // bool 값으로 참 거짓을 반환
}

func (n *BinaryTreeNode) Search(v int) bool {
	if n.Val == v { // 찾는값이 현재노드와 같을 경우
		return true // true 반환
	} else if n.Val > v { // 찾는값이 현재 노드보다 작을 경우
		if n.left != nil { // 왼쪽값이 nil 이 아닐때
			return n.left.Search(v) // 계속해서 왼쪽값을 탐색함
		}
		return false // 왼쪽에 더이상 없을 경우 false 숫자가 없다는 얘기
	} else { // 찾는값이 현재노드 보다 클 경우
		if n.right != nil { //오른쪽 값이 nil 이 아닐때
			return n.right.Search(v) // 계속해서 오른쪽 값을 탐색
		}
		return false // 오른쪽에 더이상 없을 경우 false
	}
}
ex) main
<생략>

	if tree.Search(6) {
		fmt.Println("found 6")
	} else {
		fmt.Println("not found 6")
	}
---------------------------------
5 
3 8
2 4 7 10
6 9
found 6

何回見つけたの?


ex) package
func (t *BinaryTree) Search(v int) (bool, int) {
	return t.Root.Search(v, 1) // bool 값으로 참 거짓을 반환, 카운트 초기 값 1
}

func (n *BinaryTreeNode) Search(v int, cnt int) (bool, int) {
	//  검색할 숫자와 현재 카운트를 인자로 받음
	if n.Val == v { // 찾는값이 현재노드와 같을 경우
		return true, cnt // true,cnt 반환
	} else if n.Val > v { // 찾는값이 현재 노드보다 작을 경우
		if n.left != nil { // 왼쪽값이 nil 이 아닐때
			return n.left.Search(v, cnt+1) // 계속해서 왼쪽값을 탐색하고 카운트업
		}
		return false, cnt // 왼쪽에 더이상 없을 경우 false 숫자가 없다는 얘기
	} else { // 찾는값이 현재노드 보다 클 경우
		if n.right != nil { //오른쪽 값이 nil 이 아닐때
			return n.right.Search(v, cnt+1) // 계속해서 오른쪽 값을 탐색하고 카운트업
		}
		return false, cnt // 오른쪽에 더이상 없을 경우 false,cnt
	}
}
ex) main
<생략>
if found, cnt := tree.Search(6); found {
		// Search(6) 함수를 먼저 실행하고 반환값을
		// found, cnt 에 초기화시킨다.
		// ; found 의 값이 true일 경우에 출력한다.
		fmt.Println("found 6 cnt:", cnt)
	} else {
		fmt.Println("not found 6 cnt", cnt)
	}
----------------------------------
5 
3 8
2 4 7 10
6 9
found 6 cnt: 4

BSTバランス


もし二叉木の根が順番に並んでいたら?


1~10の数値を持つバイナリツリーを作成します.

右側にのみ続行します.
10~1個の数字を含むツリーを作成します.

左に進むしか見えません.
この場合,BSTの優勢O(logN)は消失し,O(N)となる.
したがって,BSTの中間値が高いほどよい.これにより高さは低下するが,BSTの構造は,高さが低ければ低いほどよい.これは最小身長木と呼ばれています.

Heap


heapには最大のお尻と最小のお尻があります.

  • 最大ヒップ:親ノード以上の子ノードです.

  • 最小臀部:親ノードは常に子ノード以下です.

  • Push:新しいノードを追加した後、最下層に入り、親ノードをその値と比較し、最大臀部の場合は大きな数字を上に、最小臀部の場合は小さい数字を上にする原理を得ます.

  • Pop:最大のお尻であれば、最大の数字が先に落ちて、最後にノードを一番上に置きます.次のサブノードに比べて、最も高い数値は上に、より低い数値は下になります.最小お尻の場合は逆です.
  • この原理によれば,heapを用いて自動的にソートすることができ,これをheapソートと呼ぶ.
    *priority queue
    優先キュー
    通常のキューは先入先出ですが、優先度キューは優先度のあるキューで、優先度から終了します.
    この優先キューを実装するときにheapを使用します.

    Max Heap実施


    heapを実装する場合は、上部ノードと上部ノードを知る必要があるため、sliceを使用します.

    rootからBFS順にsliceを入れます.
    sliceの利点は,現在のノードのインデックスを知るだけで親ノードと左右のサブノードを知ることができることである.
    N個のノードLeft=2 N+1
    N個のノードRight=2 N+2
    N番目の親=(N-1)/2
    ex) push package
    package dataStruct
    
    import "fmt"
    
    type Heap struct {
    	list []int
    }
    
    func (h *Heap) Push(v int) {
    	h.list = append(h.list, v) // 인자로 받은 값을 h.list에 append
    
    	idx := len(h.list) - 1 // 현재 인덱스 = 맨 마지막 인덱스
    	for idx >= 0 {
    		parentIdx := (idx - 1) / 2 //현재 인덱스의 부모 인덱스
    		if parentIdx < 0 {
    			break
    			//만약 부모 인덱스가 마이너스인 경우 즉 부모가 없을 경우 break
    		}
    		if h.list[idx] > h.list[parentIdx] {
    			h.list[idx], h.list[parentIdx] = h.list[parentIdx], h.list[idx]
    			// 부모노드와 비교하여 현재노드가 클경우 서로 의 값을 바꿔 줌
    			idx = parentIdx
    			// 현재 인덱스를 부모인 덱스로 바꿔준다.
    		} else {
    			break
    			// 크지 않을 경우 바꿀 필요가 없기 때문에 바로 break
    		}
    	}
    
    }
    
    func (h *Heap) Print() {
    	fmt.Println(h.list)
    }
    
    ex) push main
    package main
    
    import (
    	"Golesson/dataStruct"
    )
    
    func main() {
    	h := &dataStruct.Heap{}
    
    	h.Push(9)
    	h.Push(8)
    	h.Push(7)
    	h.Push(6)
    	h.Push(5)
    
    	h.Print()
    
    }
    -------------------------
    [9 8 7 6 5]
    ex) pop Package
    <생략>
    func (h *Heap) Pop() int {
    	if len(h.list) == 0 {
    		if len(h.list) == 0 {
    			return 0
    		}
    	}
    
    	top := h.list[0]                // 젤위의 노드
    	last := h.list[len(h.list)-1]   // 맨 마지막 노드
    	h.list = h.list[:len(h.list)-1] // 맨 마지막 노드를 잘라냄
    
    	h.list[0] = last // 맨뒤의 애를 맨위로 바꿈
    	idx := 0
    	for idx < len(h.list) { // 맨위 부터 끝까지 내려가면서 비교
    		swapIdx := -1 // 바꿀 인덱스
    		leftIdx := idx*2 + 1
    		if leftIdx >= len(h.list) {
    			break
    			// left 인덱스가 현재 길이보다 길때는 자식 노드가 없다는 말
    			// left 인덱스가 없으면 right는 당연히 없다.
    		}
    		if h.list[leftIdx] > h.list[idx] {
    			swapIdx = leftIdx
    			// left와 현재값을 비교했을때 left가 더 크면
    			// swapIdx 를 leftIdx로 넣고 right와 비교
    		}
    
    		rightIdx := idx*2 + 2
    		if rightIdx < len(h.list) { // right 노드가 있을 경우
    			if h.list[rightIdx] > h.list[idx] { // right 인덱스가 현재값보다 큰 경우
    				if swapIdx < 0 || h.list[swapIdx] < h.list[rightIdx] {
    					// swapIdx = leftIdx가 0보다 작 거나 , 그값이 right노드 보다 작을떄
    					swapIdx = rightIdx // left와 right를 바꿔준다.
    				}
    			}
    		}
    
    		if swapIdx < 0 { // 바꿀 노드가 없을때
    			break
    		}
    		h.list[idx], h.list[swapIdx] = h.list[swapIdx], h.list[idx]
    		// 바꿀 노드가 있을 때 스왑
    		idx = swapIdx // idx 갱신해서 for문으로
    	}
    	return top
    }
    ex) pop main
    package main
    
    import (
    	"Golesson/dataStruct"
    	"fmt"
    )
    
    func main() {
    	h := &dataStruct.Heap{}
    
    	h.Push(2)
    	h.Push(6)
    	h.Push(5)
    	h.Push(1)
    	h.Push(9)
    
    	h.Print()
    
    	fmt.Println(h.Pop())
    	fmt.Println(h.Pop())
    	fmt.Println(h.Pop())
    
    }
    -----------------------------
    [9 6 5 1 2] 
    9
    6
    5

    Heapで質問に答える

    정수 배열(int array)과 정수 N이 주어지면
    N번째로 큰 배열 원소를 찾으시오.
    
    예제)
    Input: [-1, 3, -1, 5, 4] , 2
    Output: 4
    
    Input: [2, 4, -2, -3, 8], 1
    Output: 8
    
    Input: [-5, -3, 1], 3
    Output: -5

  • 小要素配列検索時=Max Heap

  • 大型アレイ要素を検索する場合=Min Heap
  • 原理は、必要な要素がN番目の位置にある場合、N個の臀部を作成し、そこからtopの値を抽出することである.

    Min Heap


    上記の例は、N番目の大きな配列の要素を検索するためminheapによって展開されます.
    ex) min heap package
    package dataStruct
    
    import "fmt"
    
    type Heap struct {
    	list []int
    }
    
    func (h *Heap) Push(v int) {
    	h.list = append(h.list, v) // 인자로 받은 값을 h.list에 append
    
    	idx := len(h.list) - 1 // 현재 인덱스 = 맨 마지막 인덱스
    	for idx >= 0 {
    		parentIdx := (idx - 1) / 2 //현재 인덱스의 부모 인덱스
    		if parentIdx < 0 {
    			break
    			//만약 부모 인덱스가 마이너스인 경우 즉 부모가 없을 경우 break
    		}
    		if h.list[idx] < h.list[parentIdx] {
    			h.list[idx], h.list[parentIdx] = h.list[parentIdx], h.list[idx]
    			// 부모노드와 비교하여 현재노드가 작을 경우 서로의 값을 바꿔 줌
    			idx = parentIdx
    			// 현재 인덱스를 부모인 덱스로 바꿔준다.
    		} else {
    			break
    			// 크지 않을 경우 바꿀 필요가 없기 때문에 바로 break
    		}
    	}
    
    }
    
    func (h *Heap) Print() {
    	fmt.Println(h.list)
    }
    
    func (h *Heap) Count() int {
    	return len(h.list)
    } // 힙의 갯수를 카운트
    
    func (h *Heap) Pop() int {
    	if len(h.list) == 0 {
    		if len(h.list) == 0 {
    			return 0
    		}
    	}
    
    	top := h.list[0]                // 젤위의 노드
    	last := h.list[len(h.list)-1]   // 맨 마지막 노드
    	h.list = h.list[:len(h.list)-1] // 맨 마지막 노드를 잘라냄
    
    	if len(h.list) == 0 {
    		return top //자식노드가 없을 경우 리턴
    	}
    
    	h.list[0] = last // 맨뒤의 애를 맨위로 바꿈
    	idx := 0
    	for idx < len(h.list) { // 맨위 부터 끝까지 내려가면서 비교
    		swapIdx := -1 // 바꿀 인덱스
    		leftIdx := idx*2 + 1
    		if leftIdx >= len(h.list) {
    			break
    			// left 인덱스가 현재 길이보다 길때는 자식 노드가 없다는 말
    			// left 인덱스가 없으면 right는 당연히 없다.
    		}
    		if h.list[leftIdx] < h.list[idx] {
    			swapIdx = leftIdx
    			// left와 현재값을 비교했을때 left가 더 작으면
    			// swapIdx 를 leftIdx로 넣고 right와 비교
    		}
    
    		rightIdx := idx*2 + 2
    		if rightIdx < len(h.list) { // right 노드가 있을 경우
    			if h.list[rightIdx] < h.list[idx] { // right 인덱스가 현재값보다 작은 경우
    				if swapIdx < 0 || h.list[swapIdx] > h.list[rightIdx] {
    					// swapIdx = leftIdx가 0보다 작 거나 , 그값이 right노드 보다 클떄
    					swapIdx = rightIdx // left와 right를 바꿔준다.
    				}
    			}
    		}
    
    		if swapIdx < 0 { // 바꿀 노드가 없을때
    			break
    		}
    		h.list[idx], h.list[swapIdx] = h.list[swapIdx], h.list[idx]
    		// 바꿀 노드가 있을 때 스왑
    		idx = swapIdx // idx 갱신해서 for문으로
    	}
    	return top
    }
    
    ex) min heap main
    package main
    
    import (
    	"Golesson/dataStruct"
    	"fmt"
    )
    
    func main() {
    	h := &dataStruct.Heap{}
    
    	//Input: [-1, 3, -1, 5, 4] , 2
    	//Output: 4
    	nums := []int{-1, 3, -1, 5, 4}
    
    	for i := 0; i < len(nums); i++ {
    		h.Push(nums[i])
    		if h.Count() > 2 { // 힙의 갯수를 2개로 제한함
    			h.Pop() // 2개가 넘어가면 빼냄
    		}
    	}
    	fmt.Println(h.Pop())
    
    	// Input: [2, 4, -2, -3, 8], 1
    	// Output: 8
    	h = &dataStruct.Heap{}
    
    	nums = []int{2, 4, -2, -3, 8}
    
    	for i := 0; i < len(nums); i++ {
    		h.Push(nums[i])
    		if h.Count() > 1 { // 힙의 갯수를 1개로 제한함
    			h.Pop() // 1개가 넘어가면 빼냄
    		}
    	}
    	fmt.Println(h.Pop())
    
    	// Input: [-5, -3, 1], 3
    	// Output: -5
    	h = &dataStruct.Heap{}
    
    	nums = []int{-5, -3, 1}
    
    	for i := 0; i < len(nums); i++ {
    		h.Push(nums[i])
    		if h.Count() > 3 { // 힙의 갯수를 3개로 제한함
    			h.Pop() // 3개가 넘어가면 빼냄
    		}
    	}
    	fmt.Println(h.Pop())
    }