Home Data Structure with Golang
Post
Cancel

Data Structure with Golang

이 글은 Tucker님의 고언어 강의 Go 언어가 온당을 듣고 정리한 내용을 토대로 작성하였습니다.

Go언어가 온당 Youtube Thumnail

연결 리스트(Linked List)


: 배열과 함께 가장 기본적인 선형 자료구조 중 하나

golang에서는 container라는 패키지에서 여러 자료구조를 다룬다.

1
2
3
4
5
type Element struct{
    Value interface{}
    Next *Element
    Prev *Element
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import(
	"container/list"
    "fmt"
)

func main(){
    v := list.New()
    e4 := v.PushBack(4)
    e1 := v.PushFront(1)
 	
    v.InsertBefore(3, e4)
    v.InsertAfter(2, e1)

    for e := v.Front(); e != nil; e = e.Next(){
        fmt.Println(e.Value, " ")
    }
    fmt.Println()
    for e := v.Back(); e!=nil; e = e.Prev(){
        fmt.Println(e.Value, " ")
    }
    
}

배열 vs 리스트


  • 맨 앞에 요소 삽입

    • 배열의 경우 배열 안의 모든 요소들을 한칸씩 뒤로 보내고 맨 앞에 새 값을 넣는다 O(n)
    • 리스트의 경우 요소 개수에 상관없이 맨 앞 리스트의 prev값을 새 노드의 주소로 넣어주면 되기때문에 O(1)

    삽입에서는 리스트 압승!

  • 특정 요소에 접근

    • 배열에서 인덱스 이동 공식 = 배열 시작 주소 + (인덱스 x 타입 크기) => O(1)
    • 리스트에서는 맨앞 노드에서 내가 원하는 노드까지 한칸한칸 이동해서 접근해야함 => 최악의 경우 n번 이동해야 하므로 O(n)

    접근에 있어서는 배열의 승리!

1
2
3
4
행위	   [배열, 슬라이스]	 [리스트]
삽입		O(n)		  O(1)
삭제		O(n)		  O(n)
접근		O(1)		  O(n)

데이터 지역성


데이터가 인접해 있을 수록 캐시 성공률이 올라가고 성능도 증가한다. (어떤 연산을 실행하려고 할 때 내가 실행하려는 연산 이후의 연산이 주변 메모리에서 일어날 확률이 높기 때문에 주변 데이터도 다 가져온다. ) => 캐시 성공

찾지 못한경우 = 캐시 실패,
캐시를 비우고 다시 캐시를 가져온다.

배열의 경우 데이터 지역성이 높아 캐시 성공률이 높아 더 성능이 좋다.
일반적으로 요소 수가 적은 경우 리스트보다 배열이 빠르다.
(보통 1000개정도 까지는 무난하게 배열이 더 빠르고 10,000개 정도 부터는 고민을 해봐야 되겠다.)

그러면 이진트리의 경우 배열로 구현하는 경우 데이터 지역성이 높아져 더 성능이 좋을지도..?

큐 Queue


선입선출 (First In First Out) 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import(
	"container/list"
	"fmt"
)

type Queue struct{
    v *list.List
}

func (q *Queue) Push(val interface{}){
    q.v.PushBack(val)
}

func (q *Queue) Pop() interface{}{
    front := q.v.Front()
    if front != nil{
        return q.v.Remove(front)
    }
    return nil
}

func NewQueue() *Queue{
    return &Queue{list.New()}
}

func main(){
    queue := NewQueue()
    
    for i := 1; i<5; i++{
        queue.Push(i)
    }
    
    v := queue.Pop()
    for v != nil{
        fmt.Printf("%v ->", v)
        v = queue.Pop()
    }
}

스택 Stack


선입후출(First In Last Out)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import(
	"container/list"
	"fmt"
)

type Stack struct{
    v *list.List
}

func NewStack() *Stack{
    return &Stack{list.New()}
}

func (s *Stack) Push(val interface{}){
    s.v.PushBack(val)
}

func (s *Stack) Pop() interface{}{
    back := s.v.Back()
    if back != nil{
        return s.v.Remove(back)
    }
    return nil
}

func main(){
    stack := NewStack()
    books := [5]string{"A", "B", "C", "D", "E"}
    
    for i := 0; i < 5; i++{
        stack.Push(books[i])
    }
    
    val := stack.Pop()
    for val != nil{
        fmt.Printf("%v ->", val)
        val = stack.Pop()
    }
}

보통 큐 -> 리스트, 스택 -> 배열로 구현한다.

링 ring


맨 뒤 노드의 Next()가 맨 첫 노드가 되는 리스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import(
	"container/ring"
	"fmt"
)

func main(){
    r := ring.New(5)
    
    n := r.Len()
    
    for i := 0; i< n; i++{
        r.Value = 'A' + i
        r = r.Next()
    }
    
    for j := 0; j<n; j++{
        fmt.Printf("%c", r.Value)
        r = r.Next()
    }
    
    fmt.Println()
    
    for j := 0; j<n; j++{
        fmt.Printf("%c", r.Value)
        r = r.Prev()
    }
}
This post is licensed under CC BY 4.0 by the author.

[Git] 형상관리 시스템과 Git, Github에 대해 알아보자

Handling Error in Golang