You are on page 1of 13

데이터구조 중간고사 정리 ■ Data class

• 해석
--------------------2~3주차. Kotlin-------------------- - println(o1) 명령어는 println(o1.toString()) 과 동일
■ Variable& Data type - 즉, 클래스에 특별히 정의해두지 않아도 toString() 에서
• 변수 선언: var, val 는 멤버변수 ‘이름=값’의 형태로 만들어진 String을 리턴
• Data types 해주는 동작을 수행
- Number: Double, Float, Long, Int, Short, Byte - TestDataClass는 상속받은 클래스가 존재하지 않음에도
- Character: Char, String 불구하고, toString()을 포함한 몇 가지 멤버함수들이 존재
- Other: Boolean, Array
• NULL • Why? 왜 이런 특이한 Data Class 라는 게 존재할까?
- 객체지향프로그래밍 (OOP) 관점에서 클래스 = {멤버변수,
■ Conditional statement 멤버함수} 라고 볼 수 있으며, 결국 대부분의 ‘객체’는
• if-else 문 서로 밀접한 값/데이터를 멤버변수로 모아놓은 것
- else 문은 필요시에만 작성 -> 이러한 객체들을 사용하는 개발자들이 반복적으로 만들
• if – else if - else 던 함수들이 존재
• when -> else 문 -> 이러한 함수들을 기본적으로 포함하는 클래스를 제공하
- 뒤에는 반드시 1문장 또는 { … } 면 편리

■ Loop • Data Class에 자동 포함되는 멤버함수


• for 문 - hashCode()
• while 문 - copy()
• do-while 문 - equals()
- toString()
■ Function - componentsN()
• function
- 자주 사용되는 ‘기능’을 function으로 정의하여, 필요 • Data Class 만들 때 유의 사항
시마다 호출 - 기본 생성자에 1개 이상의 멤버변수를 선언해야 함
- 매개변수의 type 명시해주어야 함 (생성자에 전달되는 매개변수로 해석할 수도 있으나, 멤버변수로
- 리턴 값이 존재할 경우, 리턴 값의 type을 명시 생각할 것)
- 리턴 타입을 명시했다면 반드시 return을 통해 실제로 - 다른 클래스를 상속받는 것이 불가능
리턴 값이 존재해야 함 (Kotlin 버전에 따라, 일부 클래스는 상속 가능)

- 리턴 값은 1개만 존재 가능 - 다른 종류의 class (abstract, open, sealed, inner 등)로 만


- 리턴 type과 실제로 리턴하는 값의 type이 다르면 Error 들 수 없음
• 함수의 매개변수: val (매개변수의 값을 직접 변경하는
것은 불가) • 멤버함수 override가 가능함

■ Class ■ Abstract class


• class (클래스 이름+멤버변수+멤버함수) • 추상 클래스
- var/val 여부를 지정하지 않은 것들은 val로 취급되며, - 멤버변수 & 함수 일부는 구현되고 나머지는 미완성인
init 생성자에서만 사용 가능 특수한 클래스
- 그 자체로는 객체 생성이 불가능
■ Generics -‘미완성’ 변수 & 함수를 만들어두고, 이를 상속받아서
• 특정 데이터 타입에 국한하지 않고 프로그램 구현 다른 클래스를 구현 가능
- 모든 클래스의 super-class인 “Any” 클래스를 사용
-> 임의의 객체를 담을 수 있는 Box 클래스 구현 가능 Q. 매개변수 없어도 생성이 되는 이유는?
-> 어떤 클래스 객체가 들어있을지 알 수가 없게 됨 - 상속받은 클래스에 b, c의 기본 생성자가 있기 때문
-> 클래스에 정의되어있는 적절한 멤버변수/함수를 접근하 Q. 여기서 왜 컴파일 에러가 날까?
기 어려워짐 - o1은 ChildOfAbstClass 가 아니라 TestAbstClass 이기 때
-> 템플릿 사용(Template를 활용해 구현 가능) 문에 f3에 접근할 수 없다.
Q. 어떤 경우에 추상클래스가 요긴하게 사용될까?
- 변수와 함수는 동일하지만, 서로 다른 변수/함수가 존재
하는 클래스들을 만들 때 유용
• Abstract class (추상클래스)가 요긴하게 사용되는 경우 • Why we use ‘complexity’?
- 변수/함수 일부는 동일하지만, 서로 다른 변수/함수가 존 - 아래와 같이 알고리즘 A, B의 Time complexity가 다르다
재하는 클래스들을 만들 경우 면, 어떤 알고리즘을 선택해야 하는가?
- 변수는 동일하지만, 동일한 이름의 함수의 동작이 다르
도록 할 경우

■ Interface
• Abstract class와 유사하게, ‘미완성’인 함수와 그렇지 - Time과 Space는 “두 마리 토끼”같은 존재이나, 둘 모
않은 함수들을 가짐, 함수들이 기본적으로 미완성이라는 두 놓치는 많은 알고리즘이 존재
전제가 깔려있어서, abstract 키워드를 붙이지 않아도 됨 (둘 중의 하나라도 확실히 이점을 가지거나, 적절히 균형을 맞춘
알고리즘들이 널리 사용됨)
■ Class 상속
• Kotlin에서는, 일반 class (추상 클래스, Interface가 아닌) ■ Big O notation
는 기본적으로 상속이 불가능 (final 클래스라고 지칭) • 알고리즘의 효율을 표현하는 표기법 중 하나
•‘open’ 키워드를 통해 상속 가능하도록 만들 수 있음 • Big O: 상한선 표기
(해당 알고리즘 실행 효율의 최악의 case를 표기)

■ Generics with constraints

■ Getter/Setter

*class Person(name:String)는 init에서만 접근 가능

■ 문제풀이
1. Data class 는 객체 생성을 할 수 없는 특수한 클래스이
다. X
■ Time Complexity
2. Abstract class 는 객체 생성을 할 수 없는 특수한 클래
• Constant time
스이다. O
- 데이터양에 관계없이 동일한 실행시간
3. Abstract class 의 변수들 중에 abstract 키워드가 붙은
- 리스트의 첫 번째 item이 존재할 경우에만 출력
변수의 초기값을 설정할 수 있다. X
- Time Complexity: O(1)
4. Interface 는 초기값이 주어진 멤버변수를 가질 수 없다.
O
• Linear time
- 데이터양이 증가함에 따라 실행시간도 선형 비례 증가
----------3주차. Complexity, Linked List, Stack----------
- Time Complexity: O(n) (n: 데이터 개수)
■ Complexity Dimensions
- 참고: O(2n + 3) à O(2n) à O(n) 이라고 표기
• Dimension ‘Time’
·Time complexity: Expensive 알고리즘 (Time 효율이 안
• Quadratic time
좋은)은 데이터양이 많아짐에 따라 “cost” (실행에 걸리
- 데이터양이 증가함에 따라 실행시간도 “제곱”(n
는 시간) 크게 증가
squared) 비례 증가
-‘실행을 마치기까지 걸리는 시간’이라고 지칭할 수
- Time Complexity: O(n^m)
있긴 하지만, 엄밀히 말하면 시간을 지칭한다기보다는
- 예시: 아래 프로그램의 경우, multiplicationBoard 함수는
operation의 횟수(수량)를 기반으로 실행시간을 정량화한
것 O(   ) (n: 데이터 개수)

• Dimension ‘Memory usage’


·Space complexity: 알고리즘이 사용하는 메모리
- Expensive 알고리즘 (Space 효율이 안 좋은)은 실행을
위한 “cost” (사용하는 메모리 크기) 큼
• Logarithm time ■ Linked List
- 데이터에 대한 임의의 assumption/condition/knowledge 를 • Linked List: 선형 방향으로 배열된 “값”들의 모음
바탕으로 데이터의 일부만 access/read 할 수 있다면 반복 • Node: Linked List를 이루는 각 개체
횟수(Cost)를 줄일 수 있지 않겠는가? • 코틀린 “문자열”문법에서, $변수, ${변수} 뿐만 아니라,
- 예시 코드: 모두 1번씩 access/read 하는 “선형 ${함수호출의 결과값} 형태도 사용 가능
complexity” • Node 개수가 많아지면 매우 비효율적인 방식
- Time Complexity: O(log N) (이진탐색)
• push
• Quasilinear time - Head-first insertion: 리스트의 가장 앞쪽에 item 한 개를
- Time Complexity: O(nlogN) 추가
- Linear time 보다는 안 좋지만, Quadratic에 비해서는 좋
음 • append
- 가장 뒤에 item을 추가(“Tail-end insertion”)
• Time complexity ≠ 알고리즘의 “절대적인 실행속도”
- 데이터 양이 많아짐에 따른 알고리즘의 특정 operation • insert
횟수 (데이터 access/read 등)를 의미 - item(또는 node)을 “임의의 위치”에 추가
-> 데이터의 양, 머신 스펙 등에 따라서 O(n2) 알고리즘이
실제로는 O(nlogN) 보다 적은 시간에 실행이 완료될 수도 • nodeAt
있음 - 임의의 node를 먼저 찾아내고, 해당 node를 바로 뒤에
+) 심지어, “작은 규모의 데이터”에 대해서는 O(n) 알고리즘의 새로운 node를 추가
수행시간이 O(nlogN) 알고리즘보다 더 길어질 수도 있음 (비교 연
산자 등의 다른 명령어 수행시간으로 인해) • pop
- 맨 앞에 위치한 1개의 item을 제거 (및 값 리턴)
• 동일한 문제를 풀기 위한 알고리즘은 여러 가지가 있을
수 있으며, 서로 complexity가 다를 수 있음.
• removeLast
-> O(1)가 가장 선호될 알고리즘일 것
- 가장 뒤에 있는 item을 삭제(및 값 리턴)

■ Space Complexity
• removeAfter
• 정수 정렬 알고리즘: Space complexity O(1)
- 임의 위치의 item을 삭제(및 값 리턴)
• Space complexity를 줄이기 위해, Time complexity를 희
생하기도 함.
• Time complexity

■ 문제 풀이
1. 아래 프로그램의 Time complexity를 Big O notation으로
나타내보자.

정답: O(   )

■ 요약
- Time complexity와 Space complexity는 알고리즘의
scalability에 대한 measure이다.
- Time complexity: Expensive 알고리즘 (Time 효율이 안
좋은)은 데이터 양이 많아짐에 따라 “cost” (실행에 걸리
는 시간) 크게 증가
- Time complexity가 작다고 해서, 무조건적으로 실행시간
이 짧게 되지는 않는다.
- Space complexity: 알고리즘이 사용하는 메모리

■ Array
• Array: 동일한 유형의 “값”이 순서대로 모인 집합
• Q> 만약, removeAt(Int) 함수가 있었다면, removeAt 함수 • 양방향 Linked List
의 복잡도는? O(n)

• For 문에서 사용 가능해졌다!


Q> 여기서 변수(반복자) ‘item’의 자료형은 뭘까?
- 힌트1: println(변수.javaClass)
- 힌트2: LinkedListIterator 살펴보기 - 장점: 노드 탐색 시에 걸리는 시간 단축 가능

à 답: Int - 단점: 메모리 공간이 추가적으로 필요

• Q> containsAll 함수의 Time Complexity는? • 원형 Linked List(이중도 가능)


à contains() 함수가 O(n)인데, 이에 대하여
2번 반복하고 있으므로 O(   )
k번 반복 시엔 O(   ) 라고 할 수도 있음

• Linked List vs Array

- 장점:임의의 노드부터 시작해서 모든 노드로 접근 가능

• printInReverse
- item들을 역순으로 화면에 출력하는 함수
- Time complexity = O(n)

• getMiddle
- 순서상 가운데에 위치한 item을 화면에 출력하는 함수
- Time complexity = O(n)

• reversed
- item들을 역순으로 가진 새로운 LikedList를 생성해서 리
턴하는 함수
- Time complexity = O(n)

• mergeSorted
- A객체에서 B객체에 대하여, B객체의 item들 사이에 A객
체의 item들을 (대소비교하여) 적절한 위치에 삽입한 결과
(즉, merge한 결과)를 가지는 새로운 LinkedList 객체를 리

• 배열에 만약 push() 함수가 존재한다면, time complexity - Time complexity = O(m+n)
가 어떨까? (참고: push() 함수는 맨 앞에 Node 1개를 추가 (m: # of nodes (this)
해주는 함수) n: # of nodes (otherList))
-> Array: “동일한 크기를 가진 item”들이 “메모리 상에
서 실제로 연속”된다는 특징 ■ Stack
-> time complexity: O(n) • LIFO: Last-in First-out
• push, pop의 Time complexity: O(1)
• 존재하는 top 위치의 item 을 pop하지 않고 엿보는(?) 함

• 괄호 개수가 짝이 맞는지 체크 하는 함수
-> Time complexity: O(n) (n: 주어진 문자열의 길이)
■ 문제 풀이 • Sealed 클래스 (interface로도 가능)
1. inkedList에서 제일 뒤에 item을 추가하는 작업의 Time - 여러 다른 클래스를 묶어서 사용할 목적의 클래스
complexity는 O(n) 이다. (X) - Sealed class 를 상속받은 클래스들을 묶음
-> 추가하는 작업은 tail의 next에 할당해 주기만 하면 된다. 그래 - 어떤 클래스들이 sealed class를 상속받았는지를 명시적
서 답은 O(1) 으로 천명하는 효과
2. Stack의 pop() 기능의 Time complexity는 O(1) 이다. (O) - 상속받는 클래스들은 같은 파일에 존재해야만 함
3. Stack 에 가장 나중에 넣은 item은 가장 먼저 나오게 된 - 추상 클래스이므로 객체 생성이 불가능(생성자는 기본적
다. (O) 으로 private)
-`sealed` 키워드를 사용해서 정의
■ 요약 • Enum class
- LinkedList는 임의 위치의 item들에 대한 접근을 허용함 상수들의 모음
- LinkedList는 Node들을 순서에 따라서 가지고 있는 구조 Q. “그냥 사용하고자 하는 상수(const val)를 만들어 쓰면
- Stack은 item을 넣는 위치와 빼는 위치가 동일 되는 거 아닌가?”
- Stack에 가장 먼저 넣은 item이 가장 나중에 나옴 A. “ㅇㅇ 맞음. 근데 불편”
밀접한 상수들을 모아서 관리하자!

--------------4주차. Kotlin, Queue, Deque-------------- ■ Secondary constructor


■ Companion • 여러 버전의 생성자를 만들 수 있음
• 클래스 내에 존재하는 ‘객체’ (만들지 않아도 처음부 - 매개변수가 다른 생성자들
터 존재) -‘this’ 키워드를 통해 서로 호출 가능
- 자신이 속한 클래스의 생성자에 권한 상관없이 접근 가
능 ■ Standard library: functions
- Companion object는 Outer class 객체의 변수/함수에 접 - 여러가지 유용한 class, function 등을 모아서 제공
근 불가능
- Nested & Inner와는 달리, Companion object는 클래스 • 함수 ‘let’
내에 딱 1개밖에 못 만듦(-> 객체 이름 생략하고 객체 내 - 주어진 객체에 대한 “안전한 operation” 수행을 가능하
부 멤버 사용 가능) 게 함

■ Class types • 함수 ‘with’


• Nested class - 주어진 객체에 대한 멤버변수/함수 접근을 편리하게 함
- Companion object와 유사하게, Outer 클래스의 객체 생
성없이 사용 가능 • 함수 ‘run’
- Nested class의 객체 생성은 필요함 - 앞서 살펴본 ‘with’와 유사하게 멤버변수/함수에 편리
- 자신을 감싼 outer class로의 접근은 불가능 (outer의 생 하게 접근하게 해줌
성자는 호출 가능) ·‘let’처럼 ‘?’를 붙임으로써 안전하게(null 이 아닐 때만)
- Outer class에서는 Nested class 객체 생성 가능 실행되게 할 수 있으나, 주어진 객체(예: c2 객체)를 ‘this’로 표
- Outer class의 companion object에는 접근 가능 기함
· 즉, 자신이 속한 클래스의 객체에는 접근이 안 됨

• Inner class
• 함수 ‘also’: ‘let’과 매우 유사하나, 객체 자신 리턴
- Nested 클래스와 달리, Outer 객체 생성해야 함
- (참고) 객체의 멤버변수 수정 시, 원본의 값이 변함
- Nested 클래스와는 달리, ‘inner’ 키워드를 사용하여
클래스가 정의됨
• 함수 ‘apply’
- Nested 클래스와는 달리, Outer class에 접근 가능
-‘also’와 유사하게 객체 자신을 리턴
- 심지어, private으로 보호되는 member 변수/함수에게도,
-‘run’처럼 주어진 객체를 this로 표기
companion object에게도 접근 가능
(즉, 소속된 객체에 대한 접근은 불가능)
■ 요약 • Queue: using LinkedList
- Companion object는 Outer 클래스의 ‘동반자’ - LinkedList 기반의 Queue
- Outer 객체 생성 없이도 사용 가능
- Companion object 자체도 별도의 생성 없이 사용 가능
- Nested 클래스는 거의 남남
- Inner 클래스는 Outer 객체 생성해야 함
- 한 클래스 안에 여러 버전의 생성자를 만들 수 있음

■ Queue
• FIFO (First-In First-Out)

- LinkedListQueue의 멤버함수 별 Time complexity


-> isEmpty(): O(1)
-> peek(): O(1)
• Queue 문제 풀기 -> enqueue(): O(1)
- 아래 명령어를 순서대로 수행할 때, 다양한 구현 방법에 -> dequeue(): O(1)
따른 각 명령어 수행 결과 확인하기 (왜 O(1): Array와 달리 앞쪽의 아이템이 빠져나가도 뒤의 배열들
을 앞으로 당겨주는 작업이 필요 없기 때문)
- LinkedListQueue의 멤버함수는 ArrayListQueue에 비해
Time complexity가 개선되었으나, 각 Node에서 유지해야될
부가 정보(예: next)가 많다는 단점이 존재
(새로운 item 추가 시마다 메모리 Heap에 Node 생성을 위해 필요
• Queue: using Array-based list 한 부가작업들이 존재)
- ArrayList 기반의 Queue - 연결리스트를 구현하기 위해 추가적인 데이터 값을 가지
므로 Array에 비해 용량이 커지는 단점이 존재

• Queue: using Ring buffer


- RingBuffer 기반의 Queue

- 메모리 공간 상에서 각 아이템이 실제로 옮겨짐


- ArrayListQueue의 멤버함수 별 Time complexity
-> isEmpty(): O(1)
-> peek(): O(1)
-> enqueue(): O(1)
(단, item이 꽉 차면 k배(예: 2배) 크기의 공간을 새로 할당해서 복
제하므로, 이런 경우에는 O(n))
-> dequeue(): O(n)
(왜 O(n): Array로 큐를 작성하면 아이템을 빼낸 후 배열 뒤에 있
는 값들을 앞으로 당겨야 하기 때문) - Queue 최대 용량이 존재한다는 가정(즉, 최대 용량보다
더 많은 item이 들어오지 않는다는 상황을 가정) 하에,
Ring buffer 를 적용함으로써 효율성 극대화
- 링크드 리스트로 구현한 큐에 비해 동일한 양을 저장할
때 사용되는 메모리의 양이 적음
- Q> 꽉 차 있을 경우, Read, Write 위치가 어떻게 될까?
-> (비어있을 경우와 동일)Read와 Write가 같은 곳에 위치
• queue 구현에 사용된 array vs linkedlist vs ringbuffer ------------------5주차. Kotlin, Trees------------------
■ Type alias
• 타입 명이 자주 사용되는데 너무 길거나, 간간이 변경될
소지가 있을 때 사용

• 종류 별로 alias 존재
- Class에 대한 type
• Queue를 반대로 출력하고 싶다면?
(예) typealias NodeSet = Set<Tree.Node>
- Stack에 Queue 내부의 값을 모두 push하고 다시 반대로
(예) typealias FileTable<T> = MutableMap<T, MutableList<File>>
pop한 값을 enque하면 됨
(예) typealias ManInner = Man.Inner
Inner 클래스를 ‘지칭’할 수 있음 (객체를 생성한 것이 아니므
■ Stack vs. Queue 로, Outer 클래스의 이름인 ‘Man’을 사용하여 Man.Inner 라고
• Q> Queue와 Stack의 차이점은? 지칭)
- Queue는 FIFO이고, Stack인 LIFO이다. - Function에 대한 type
• Q> 실생활에서 Queue, Stack 개념에 해당되는 예시? (예) typealias TheFunc = (Int, String, Any) -> Unit
- Stack: 총 탄창, 그림판에서 ‘되돌리기’ 리턴 타입이 Unit 인 것은, 리턴 값이 없다는 의미
- Queue: 자동차 세차장, 영화관에서 표 사려고 줄서기 (예) typealias TheFunc<T> = (T) -> Boolean

■ Deque ■ forEach
• Deque (double-ended queue) • 주어진 item sequence (예: List, Collection 등) 객체에서
- 덱(deck)이라고 발음 item 들을 하나씩 꺼내주는 구문
- Queue와 유사, 양쪽 끝 부분에서 삽입/삭제가 모두 가능
(Queue와 Stack의 특징을 모두 가지고 있다고도 볼 수 있음) • return@NAME
- 구현한 방식이 Array 기반인지, List 기반인지에 따라서 -> return@forEach: for문의 continue와 비슷
한 쪽 방향에 item들이 꽉 차게 되면 새로운 공간을 할당 -> return@run: for문의 break로 활용하고 싶을 때 사용
할 지 여부가 결정됨
■ Elvis operator
• Deque을 이용한 알고리즘 문제 풀기 - 기호 ?: 를 사용하여, 앞에 등장한 객체가 null일 경우에
- Sliding window 방식 취할 default 값을 명시할 수 있음
: Window를 왼쪽에서 오른쪽으로 한 칸씩 옮긴다는 것은,
기존 Window에서 맨 왼쪽 item을 제거하고 맨 오른쪽에 ■ Naming convention
새로운 item을 추가하는 것 - Class: Camel case 사용, 첫 글자는 대문자이고 나머지는
à Deque로 구현 가능 소문자이되, 단어 시작도 대문자
- 1번째 풀이 - Function, Variable: Camel case 사용 (단, 첫 글자는 소문
Sliding window 이 옮겨갈 때마다(O(N)), Deque 안의 L개의 자)
item 에 대하여 최소값을 찾기 (O(L)) - Constant: 모두 대문자이고, 단어 사이를 언더바(_)로 구
à 따라서, 결국 O(NL) 분
- 2번째 풀이: “최소값 Chain” 이용하기
맨 왼쪽부터 sliding window 를 옮겨가면서 (O(N)) 최소값의 ■ Java import
index (위치)를 chain 으로 관리 à 따라서, 결국 O(N) - 코틀린에서 Java 패키지를 import 가능
- Java에서도 코틀린 패키지 사용 가능
■ 문제 풀이
1. 코틀린 ArrayList를 사용해서 Queue를 구현하면, ■ 요약
enqueue 기능의 Time complexity는 항상 O(n) 이다. (X) - Type alias를 통해 자주 사용되는 타입을 손쉽게 다룰 수
2. Ring buffer 구조를 사용하여 Queue를 구현하면, 있다.
LinkedList의 장점을 취하면서도 LinkedList의 단점이었던 - Elvis operator를 이용하여 단순 null 체크 하는 코드를
“부가적인 작업으로 인한 load”를 감소할 수 있게 된다. 단순화할 수 있다.
(O) - 코틀린은 기본적으로 Java 언어와 유사한 Naming
convention을 따른다. (Camel case 사용)
- 코틀린에서 Java 패키지를 import 하여 사용할 수 있다.
■ Traversal algorithms ■ Binary search trees
• (Tree의 Node에는 값 O)값을 write 하거나 read 하기 위 • Binary Search Tree (BST)
해서는, 해당 노드(노드 위치)를 찾기 위해 Tree의 Node들 - 빠른 lookup (찾기), insertion (삽입), removal (삭제)
을 어떤 순서로 visit(access) 할지 결정해야 함 - 조건: Left child < Parent < Right child

• Depth-First Traversal(DFT) • 동일한 data를 Array로 다루는 경우와 BST로 다루는 경


- 재귀적인 동작으로 구현하지 않고자 할 경우에는, 대안 우 비교(worst case로 가정)
으로 ‘stack’을 활용 가능
->Why? 재귀함수도 결국엔 콜스택을 사용하게 되므로(가
장 나중의 함수가 가장 먼저 종료됨)

• Level-Order Traversal(LOT or BFT)


- visit 함수로 자신을 출력 -> 자식 노드를 queue에 넣고
-> enqueue/dequeue 반복
• Unbalanced tree는 lookup, insertion, removal 등의
operation의 Time complexity가 O(logN)이 아니라 O(n)이 되
• Search
어버리므로 좋지 않은 구조라고 볼 수 있음 (Insertion 순서
를 임의로 바꾸면 되지만 BST 자체에는 이러한 구조를 피하도록
• toString 하는 방법 없음)

• 지금까지 구현한 모든 Tree 관련 Traversal, search 함수 ■ contains


들의 Time complexity = O(n) (n = 노드 개수) - value가 존재하는지에 대한 여부를 리턴하는 함수의
Time complexity = O(n) (DFT 또는 LOT 사용 시)
■ Binary trees - 굳이 visit이 필요하지 않은 Node들은 skip하도록 개선
- ‘Binary’ tree: Children node 개수가 최대 2개인 Tree 후 Time complexity = O(logN) (BST 특성 사용 시)

• In-order traversal ■ remove


- 사이에 visit한다고 해서 in-order - BST의 item을 찾아서 삭제하는 함수
- Depth-first traversal과 유사해보이지만, traversal의 결과 - 삭제할 item이 Tree의 어느 위치에 있느냐에 따라서 삭
물이 다르다. 제 방법이 달라짐
- Left child node를 재귀적으로 traverse 한다. - Case 1: Leaf node
- Left child node가 없거나 이미 traverse 한 뒤일 경우, 자신을 Parent 노드에서의 children 목록에서 해당 node 를 없애면 끝
‘visit’ 한다. Root node 였다면 root 지정 없애면 끝
- Right child node를 재귀적으로 traverse 한다. - Case 2: Internal node (with 1 child)
- Tree의 item들이 특정규칙에 의해 배열되어 있을 경우, Parent 노드와 child 노드를 연결
“순서”(예: 오름차순)대로 item들을 구할 수 있음 Root node 였다면, child 노드가 root 가 됨
- Case 3: Internal node (with 2 children)

• Pre-order traversal
- 동작은 DFT와 동일함 ■ BinaryNode::isBinarySearchTree
- 자신을 ‘visit’ 한다. - 지금까지 살펴본 Binary Search Tree (BST)는 멤버변수로
- Left child node를 재귀적으로 traverse 한다. BinaryNode(root 노드)를 가지고 있으며, insert, remove 등
- Right child node를 재귀적으로 traverse 한다. 의 함수를 제공하되 BST로서의 구조를 유지하도록 구현되
어 있었음
• Post-order traversal -> 따라서, BinaryNode로 이루어진 Binary Tree도 BST로서
- Left child node 를 재귀적으로 traverse 한다. 의 구조를 가지고 있을 가능성이 있음
- Right child node 를 재귀적으로 traverse 한다.
-> BinaryNode 클래스 안에, 이를 체크하는 함수의 Time
- 자신을 ‘visit’ 한다.
complexity = O(n)

• 지금까지 살펴본 Binary tree의 모든 함수의 Time


■ BinaryNode::equals
complexity = O(n) (n = 노드 개수)
- 함수 equals() 를 override 해서, 2개의 Binary Tree 가 동
일한 구조와 값을 가졌는지 체크
■ height
- Time complexity = O(n)
- Tree의 높이(깊이)를 리턴해주는 함수
■ BinarySearchTree::contains ■ HashMap
- 현재의 BST 객체가 “다른 BST 객체에 포함된 모든 • Hash 함수: 효율적인 자료구조 또는 보안 목적으로 주로
element를 가지고 있는지” 여부를 체크하는 함수 활용
- 다른 BST 객체의 구조와 동일할 필요는 없고, element - Input으로 임의의 값을 받아서, 항상 동일한 byte 크기를
들만 모두 포함하는지 여부 체크 갖는 Output 생성
- BST 클래스에 아래와 같이 contains 함수 추가 - Input이 동일하면 Output도 동일하며, Input이 달라지면
- 기존에 존재하는 contains(value: T) 와는 매개변수가 다 Output도 달라짐 (단, 서로 다른 Input이 동일한 Output을
름 (override 함수가 아님) 가질 수 있음 (collision))
- Time complexity = O(n) - Function에 의해 계산(연산)된 결과물이므로 Output 생성
속도가 매우 빠르고 균등
■ 문제 풀이 - Output으로부터 Input을 얻어낼 수 없음
1. Binary Tree 는 1개의 노드가 child 노드를 최소 1개, 최
대 2개를 가져야 한다. (X) • Map: key-value 쌍으로 구성, (대부분의 경우는 key에
2. Binary Search Tree (BST) 를 이용하면, 특정 item 을 의해 value를 찾는 것), 탐색 매우 빠름
find 하는 작업을 Time complexity O(logN)으로 가능하다. -> 결국, Map에 존재하는 key가 많아질수록, 작업의 효율
(O) 이 떨어지게 됨
- Hash 함수 Map에 적용: Time complexity = O(1)
■ 요약
- Binary Tree는 최대 2개의 child 노드를 가질 수 있는 특 ■ Extension function/property
수한 Tree이다. - 확장 함수/변수: 정의가 완료된 클래스에 추가로 함수/변
- Depth-First traversal는 Tree에서 child 노드를 따라서 아 수를 정의하는 것
래쪽으로 먼저 traverse 하며, Level-order traversal은 위쪽 - 함수 overloading 은 가능하지만, 완전히 동일한 함수가
level부터 시작하여 아래쪽 level까지 traverse 한다. 존재 시, 확장 함수는 무시됨
- Binary Search Tree를 이용함으로써 find, insert, remove - 정적 바인딩 -> 어떤 클래스의 확장 함수가 호출될 지는
등의 동작을 Time complexity O(log N)에 가능하다. 컴파일 타임에 결정됨
- 정의 방법: fun 클래스이름.함수이름(…매개변수…): 리턴
타입 {…}
----------6주차. Kotlin, ATL Trees, RB Trees----------
■ List & Set & Map ■ assert
- List: 임의 item 의 sequence, 중복된 item 허용됨 - 매개변수로 주어진 부분이 True 또는 False 냐에 따라서,
- Set: 임의 item 들의 ‘모음’ (순서 없음), 중복된 item False일 경우에는 에러 발생
허용하지 않음 - assert() 가 제대로 동작하게 해주려면, JVM 에 ‘-ea’
- Map: Key-value 쌍을 item으로 가짐, Key는 중복될 수 옵션을 주어야 함
없음
- 위 클래스들은 여러 구현 방식이 있음 (예- List의 경우, ■ 요약
ArrayList, LinkedList 등이 있음) - List와는 달리 Set은 item의 중복을 허용하지 않는다.
- 구현 방식에 따라서 operation의 Time complexity가 달라 - Map은 key-value 쌍을 1개의 item으로 취급한다.
짐 - HashMap은 Hash 함수를 이용한 Map이다.
- Extension function/property를 통해 클래스 바깥에서
■ Mutable, Immutable function/property를 추가 정의할 수 있다.
- Set, Map을 생성하는 방식에 따라서 Mutable 이냐, - assert() 사용 시, JVM 옵션에 ‘-ea’를 주어야 한다.
Immutable 이냐가 결정됨
-> Immutable: 값의 변경이 허용되지 않음 ■ ATL Trees
-> Mutable: 값의 변경이 허용됨 - “Self-balancing”이 가능한 Tree(BST 와 거의 동일)
- ‘Balancing’한다는 것은 |Balance factor| <= 1 을 만족
하도록 만드는 것이라고 보면 됨
- Left child와 Right child의 height의 차이가 클수록
unbalance 되었다고 말할 수 있음
• Rotations ■ 문제 풀이
- Left rotation: Target node ‘X’의 오른쪽 child 1. AVL Tree에 새로운 item을 insert 하는 작업은 Time
(subtree)가 무거울 때 사용, Y가 피벗 노드 complexity = O(logN)이다. (O)
- Right rotation: Target node ‘X’의 왼쪽 child (subtree) 2. AVL Tree의 Right-left rotation의 Time complexity =
가 무거울 때 사용, Y가 피벗 노드 O(logN) 이다. (X)
- Right-left rotation: Pivot을 Target으로 삼아서, Right 3. RB Tree는 항상 완벽하게 balancing 된 Tree 구조를 유
rotation을 수행 -> 기존의 Target node에 대하여 Left 지하게 해준다. (X)
rotation 수행
- Left-right rotation: Pivot을 Target으로 삼아서, Left ■ 요약
rotation을 수행 -> 기존의 Target node에 대하여 Right 1. AVL Tree와 RB Tree는 self-balancing 특징을 가지고
rotation 수행 있다.
- 수행 후 Tree Depth가 1 줄어들게 됨 2. AVL Tree와 RB Tree는 insert, remove, find 등의 기능
- 4가지의 rotation 함수들의 Time complexity = O(1) (노드 에 대한 Time complexity = O(logN)이 되도록 해준다.
개수 상관 x) 3. AVL Tree의 rotation의 Time complexity = O(1) 이다.
4. RB Tree의 Recoloring의 Time complexity = O(logN) 이
• balanced 다.
- 적절한 상황에 각 rotation들을 호출하는 함수

• insert -----------7주차. Kotlin, Tries, Binary search-----------


- AVLNode 를 직접 생성하던 방식으로 balancing 하기보 ■ Range
다는, Tree 차원에서 item을 추가하는 insert() 함수 - [시작점 ~ 끝지점] 범위 안에서 “1 step” 씩 전진하는
개념
• Self-balancing을 통해 AVL Tree는 insert, remove 작업 - IntRange, CharRange 등이 있음
의 Time complexity = O(logN)을 보장 - rangeTo() 함수가 존재, but “1..10”과 같은 문법을 써
도 알아서 range로 해줌
• Perfectly-balanced leaf count - 출력시 숫자 사이에 “..” 를 코틀린 자체에서 포함시켜
- Perfectly-balanced 상태인 AVL Tree의 leaf node 개수를 출력해줌(시각화 제공)
리턴해주는 함수 - ‘until’은 [시작점 ~ 끝지점) 과 같이, 끝 지점에 해당
- Time Complexity = O(1) 되는 부분은 미포함
-> Perfectly-balanced AVL Tree의 leaf node 개수 리턴 함 - forEach, filter 등을 적용 가능, List로 만들기 가능

수:  개라는 점을 이용
■ Progression
• Perfectly-balanced node count - ‘Step’이 존재하는 Range
- Perfectly-balanced 상태인 AVL Tree의 ‘전체 node’ - [시작점 ~ 끝지점] 범위 안에서 “K step”씩 전진하는
개수를 리턴해 주는 함수 개념
- 앞서 구현한 leafNodesOfPerfect()를 이용하여, 0~height - Range와 기본적인 개념은 동일하고 조작법도 동일(일반
사이의 node 개수를 모두 더하는 방식의 Time Complexity 화된 개념이라고 볼 수 있음)
= O(height)
- 
   
  개 라는 점을 이용 ■ Tries

-> 개선 후 Time Complexity = O(1) • Trie: Collection(sequence가 가까운 표현) 형태로 저장될
수 있는 데이터를 저장하는 특수한 Tree

■ Red-Black Tree - 여러 단어의 collection을 표현 가능

- self-balancing 속성을 가진 Binary Tree - 특히, Prefix matching에 효과적으로 활용될 수 있음


(Prefix matching: 앞부분만 주어져 있을 때, 그 앞부분에 일치하
- AVL Tree는 완벽에 가까운 balancing을 제공하지만, RB
는 sequence를 다 찾아내는 것)
Tree는 비교적 빠르고 rough한 balancing을 제공
- 점(.) 표시는 단어의 끝을 의미
- Node insertion: 만약, 4번 조건(빨간색 노드의 자식은 빨
간색이면 안 됨)을 위배하는 상황이 되면, Uncle 노드에 따
• 단순 String Array로 prefix 매칭을 시도: Word 개수가 많
라서 Restructuring 또는 Recoloring을 수행하여 balancing
을수록 효율성 떨어짐
-> Restructuring : Time complexity = O(1)
- Time complexity = O(kn) (k: 가장 긴 word 길이, n:
-> Recoloring: Time complexity = O(log N)
word 총개수)
• TrieNode 클래스: 나중에 거슬러 올라가는 과정이 필요 ■ Binary search
-> 부모노드도 정의 필요 • Binary Search를 사용하기 위한 조건
- Constant time (time complexity = O(1)) 으로 아이템
• Trie를 구현할 때 Hashmap을 사용하면 효과적으로 연산 indexing이 가능, 아이템들이 정렬되어 있어야 함
을 수행 -> Time complexity = O(1) - 절반씩 줄여나가면서 찾는 알고리즘

• insert 함수 • indices: arraylist 내에서 정의된 전체 아이템


- Trie 클래스에 word 1개를 추가(insert)하는 함수
- List<Key>를 인자로 받고 list 안의 각 item들을 순서대로 • BS를 이용한 search (검색) 작업의 Time complexity =
Root node부터 시작해서 아래 방향으로 찾아내려감 (없으 O(log n) (n: 아이템 개수)
면 추가)
- Time complexity = O(k) (k: word를 구성하는 item 개수) • indexOf() 의 Time complexity = O(n) (코틀린에서는 주먹
구구식으로 찾아냄)
• contains 함수
- 임의 word를 포함하고 있는지 여부를 리턴하는 함수 • binarySearch() 의 Time complexity = O(log n)
- Root node부터 시작해서 체크하면서 내려감. 주어진
word의 가장 끝 item이 Trie에 존재하는 것까지 확인한 경 • findIndices 함수
우, 해당 item의 노드가 word 끝에 해당되는 것이었는지 - 임의 item들의 등장 ‘범위’(Range)를 리턴
여부 (isTerminating)를 리턴
- Time complexity = O(k) ■ 문제 풀이
1. Trie를 구현할 때, 각 노드의 자식노드를 HashMap 을
• Kotlin의 String은 Collection이 아니어서, String을 명시적 사용하지 않고 ArrayList로 구현하였다. 이 Trie에 100개의
으로 List<Char>로 변환하여 Trie에 insert해줘야 함 영어 word를 넣었다. (Trie 의 각 노드는 영어 알파벳) 이
때, 특정 word X 가 Trie 에 존재하는지 여부를 알아내고
• extension function 작성으로 String 뒤에 붙는 .toList() 호 자 할 경우, 최악의 경우 100개의 word 를 모두 체크해야
출을 더 이상 하지 않아도 됨 X 가 존재하는지 여부를 알 수 있을 것이다. (O)
2. 숫자 1~100을 ArrayList에 ‘내림차순’으로 정렬해놓았
• remove 함수 다. 이에 대하여 Binary Search를 사용하는 것은 불가능하
- Trie의 임의의 node를 삭제하는 함수 다. (X)
- 다른 child가 존재하지 않는 것만 지움(CUTE 지운다치면 E
만 지움, 그럼 다시 CUT 지우면 다 지울까? NO. UT만 지움) ■ 요약
- Prefix Matching 종류의 문제를 풀고자 할 경우, Trie는
• Prefix matching 함수 최적의 자료구조일 것이다.
- 임의의 Prefix를 가진 word들이 존재하는지 체크하여, 해 - 아이템들이 정렬되어 있다는 가정하에, Binary search를
당 word들의 모음(collection)을 리턴 통해 임의의 item을 찾는 것은 Time complexity = O(log n)
- Tries 기반의 Prefix matching: Word 개수가 많을수록 효
율성 더욱 커짐
- Time complexity = O(km) (k: 가장 긴 word 길이, m: (최
소한 prefix 부분까지는) 매칭되는 word 개수(이후로 더 탐
색할 필요가 있는 word개수))

• lists property
- Trie에 들어있는 모든 word들을 모은 collection을 리턴
- `lists’에 대한 getter 를 정의할 때, 내부 함수를 사용하
여 얻은 결과값(리턴값)을 그대로 리턴하도록 구현

• count property
- Trie에 존재하는 Unique word 개수를 리턴하는 property

• isEmpty property
- Trie에 word가 존재하는지 여부를 리턴하는 property
중간고사까지의 과제(퀴즈) 5. (단반향)Linked List에서 맨 앞 노드(head), 맨 뒤 노드
(tail)를 직접 접근이 가능한 상황일 때 Linked List를 사용
1. 구구단 2단부터 9단까지를 화면에 출력하려고 한다. 이 해서 Stack을 구현할 경우, 틀린 것은? ①
를 for { ... for { ... } ... }와 같이 중첩 for문으로 구현하였 ① Linked List의 맨 앞에 push, pop이 되도록 구현하면
다. ‘곱셈’ opreration을 complexity 대상으로 생각했을 O(n)이 되므로 비효율적이다.
때, 이 코드의 Time complexity는? -> 둘 다 O(1)이므로 비효율적이라 할 수 없다.

-> 정답: O(  ) ② Push되는 아이템 개수가 갑작스럽게 커지는 경우가 많
을 경우에는, Array 기반의 Stack보다는 Linked List 기반의
2. Linked List에 대한 설명으로 틀린 것은? ② Stack이 메모리를 재할당하지 않아도 된다는 면에서 장점
① 맨 앞에 노드를 1개 추가하는 것은 O(1)이다. 을 지닐 것이다.
② 맨 뒤(tail) 노드가 주어져 있을 경우, 이 노드 뒤에 새 ③ Linked List의 맨 뒤에 push, pop이 되도록 구현이 가능
로운 노드를 1개 추가하는 것은 O(n)이다. 할 것이다.
-> O(n)이 아니라 O(1) ④ Array 기반의 Stack보다 전반적으로 메모리 공간을 더
③ 주어진 임의의 노드의 뒤에 새로운 노드 1개를 추가하 많이 소비하게 될 것이다.
는 것은 O(1)이다.
④ 임의의 i번째에 위치한 노드에 접근하는 것은 O(i)(또는, 6. Linked List를 사용하여 Queue를 구현하였다. 특히,
O(n))이다. Linked List의 head 부분에서 dequeue가 수행되도록 구현
하였다. 이러한 Queue의 Time complexity 중에 옳지 않은
3. Linked List와 Array에 대한 설명으로 틀린 것은? ② 것은? ②
① (단방향) Linked List의 맨 뒤(tail)에 있는 노드를 삭제하 ① Queue가 비어있는지 여부 체크: O(1)
는 것은 O(n)이다. ② Queue의 가장 앞쪽(dequeue 하는 곳)에 있는 아이템 엿

② Array의 맨 앞(head)에 있는 아이템을 삭제하는 것은 보기: O(n)


O(1)이다. -> O(n)이 아니라 O(1)
-> O(1)이 아니라 O(n) ③ Queue에 아이템 1개 넣기(enqueue): O(1)
③ Array의 맨 뒤(tail)에 아이템을 추가하는 것은 O(1)이다. ④ Queue에서 아이템 1개 빼기(dequeue): O(1)
④ Linked List와 Array가 동일한 개수의 데이터를 저장할
경우, 상대적으로 Linked List가 메모리를 더 많이 소비하 7. Ring buffer를 구현하여 Queue를 구현하고자 한다. 이에
게 된다. 대한 설명으로 옳지 않은 것은? ③
① enqueue, dequeue 동작에 대한 Time complexity는 O(1)

4. Array를 사용하여 구현된 Stack에 대한 설명으로 틀린 ② Ring buffer의 최대 용량을 넘어서는 개수의 element
것은? ① 저장은 불가능할 것이다.
① Stack에 새로운 아이템을 추가하는 작업은 O(n)이다. ③ 동일한 개수의 element를 저장하는 Queue에 대하여,
-> O(n)이 아니라 O(1) Ring buffer에 비해 Linked List를 사용했을 때 더 적은 양

② Stack에서 아이템을 1개 삭제하는 작업은 O(1)이다. 의 메모리를 소모할 것이다.


③ Array의 맨 뒤(tail)에 아이템을 추가하는 것은 O(1)이다. -> Linked List보다 Ring buffer 사용이 메모리 소모가 적
④ Linked List와 Array가 동일한 개수의 데이터를 저장할 다.
경우, 상대적으로 Linked List가 메모리를 더 많이 소비하 ④ Ring buffer의 read point, write point가 동일한 위치를
게 된다. 가리킬 때에는, Ring buffer가 비어있거나 꽉 차 있는 경우
⑤ Stack은 가장 나중에 push된 아이템이 가장 처음으로 이다.
pop된다.
8. Queue와 Stack의 차이점에 대하여 틀린 것은? ②
① Queue는 FIFO이고, Stack은 LIFO이다.
② Queue는 아이템의 삽입, 삭제되는 위치가 동일하다.
-> 위치가 다르다.
③ Stack은 ‘그림판의 되돌리기’기능과 밀접하다.
④ Queue는 아이스크림 가게에서 줄 서서 기다리는 상황
에 빗댈 수 있다.
9. Tree Traversal 알고리즘에 대한 설명으로 옳지 않은 것
은? ②
① Level-order traversal은 Tree의 Root 노드부터 시작해
서 각 Level 별로 노드들을 Traversal하는 알고리즘이다.
② Depth-first traversal은 Level-order traversal에 비해
Time complexity가 우수하다.
-> O(n)로 모두 같다.
③ Depth-first traversal을 사용하여 임의의 element를 찾
는 search 함수를 구현할 경우, search 함수의 Time
complexity는 O(n)일 것이다.
④ Depth-first traversal을 ‘재귀함수’없이 Stack을 사용
함으로써 구현할 수도 있다.

10. ATL Tree에 대한 설명으로 옳지 않은 것은? ③


① left-right rotation 동작은 target 노드의 balancing
factor 값이 2이고 pivot 노드의 balance factor가 –1일 때
수행한다.
② Right rotation 동작은 target 노드의 left child (subtree)
쪽으로 치우쳐있을 때 (left child 쪽이 더 무거울 때) 수행
할 수 있다.
③ Left rotation 동작의 Time complexity는 O(n)이다.
-> O(n)이 아니라 O(1)이다.
④ AVL tree에 새로운 element를 추가하는 작업의 Time
complexity는 O(logN)이다.

11. Red-Black Tree에 대한 설명으로 옳지 않은 것은? ①


① ATL Tree에 비해 더욱 완벽하게 Balancing을 유지하는
Tree이다.
-> ATL Tree는 Red-Black Tree보다 rough한 Balancing을
제공한다.
② Red node는 Red node를 자식으로 가질 수 없다.
③ Black node는 Black node를 자식으로 가질 수 있다.
④ ATL Tree에 비해 efficient하게 동작하는 Tree로 알려져
있다.

You might also like