이 글은 Tucker님의 고언어 강의 Go 언어가 온당을 듣고 정리한 내용을 토대로 작성하였습니다.
연결 리스트(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()
}
}