├── .gitignore ├── README.md └── notes ├── lecture01-intro.md ├── lecture02-data-structure-and-algorithm.md ├── lecture03-algorithm-complexity01.md ├── lecture04-algorithm-complexity02.md ├── lecture05-algorithm-complexity03.md ├── lecture06-sequential-array-list.md ├── lecture07-sequential-data-structures.md ├── lecture08-stack.md ├── lecture09-stack-calculator01.md ├── lecture10-stack-calculator02.md ├── lecture11-queue.md ├── lecture12-linked-list.md ├── lecture13-singly-linked-list01.md ├── lecture14-singly-linked-list02.md ├── lecture15-doubly-linked-list01.md ├── lecture16-doubly-linked-list02.md ├── lecture17-hash01.md ├── lecture18-hash02.md ├── lecture19-hash03.md ├── lecture20-tree.md ├── lecture21-heap01.md ├── lecture22-heap02.md ├── lecture23-heap03.md ├── lecture24-binary-tree.md ├── lecture25-binary-search-tree01.md ├── lecture26-binary-search-tree02.md ├── lecture27-balanced-binary-search-tree01.md ├── lecture28-balanced-binary-search-tree02.md ├── lecture29-balanced-binary-search-tree03.md └── lecture30-balanced-binary-search-tree04.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | temp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 한국외국어대학교 컴퓨터공학부 신찬수 교수님의 자료구조 강의(2020-1) 2 | 3 | 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 강의를 정리한 노트입니다. 4 | 5 | ## 안내 6 | 7 | 1. 학습한 날짜를 아래의 표에 정리합니다. 8 | 2. 본 노트는 학습 요약보다는 강의에 집중하기 위한 사적인 필기에 가깝습니다. 9 | 3. 총 55강으로 구성되어 있으나, 현재 저에게 필요한 내용인 AVL 트리에 대한 강의인 30강까지만 학습했습니다. 10 | 4. 항목 2,3 의 이유로, 수업의 내용에 비해 정리가 부족할 수 있습니다. 11 | 5. 노트의 코드는 파이썬 언어의 형태를 하고 있으나, 의사 코드(Psuedo code)가 섞여 있습니다. 12 | 6. 전체 동영상 강의와 교수님의 강의 노트를 기반으로 학습하는 것을 권장합니다. 13 | 14 | ## 동영상 강의 링크와 요약 노트 15 | 16 | 4월 30일부터 5월 6일까지 총 7일간 학습했습니다. 전체 강좌의 링크는 아래에서 확인할 수 있습니다. 17 | 18 | - "자료구조 - Data Structures with Python" 동영상 강의 목록: [https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK) 19 | 20 | | 연번 | 동영상 링크 | 노트 | 학습일 | 21 | | -: | --------------------------------- | ----------- | ----------- | 22 | | 1 | [자료구조(2020-1) 과목 소개](https://youtube.com/watch?v=PIidtIBCjEg) | [:memo: note01](./notes/lecture01-intro.md) | 2021.04.30. | 23 | | 2 | [자료구조와 알고리즘](https://youtube.com/watch?v=M2mcJvmYpWY) | [:memo: note02](./notes/lecture02-data-structure-and-algorithm.md) | 2021.05.01. | 24 | | 3 | [알고리즘 시간복잡도1](https://youtube.com/watch?v=jgWyu83DfO0) | [:memo: note03](./notes/lecture03-algorithm-complexity01.md) | 2021.05.01. | 25 | | 4 | [알고리즘 시간복잡도2](https://youtube.com/watch?v=ysn9dLDNLEU) | [:memo: note04](./notes/lecture04-algorithm-complexity02.md) | 2021.05.01. | 26 | | 5 | [알고리즘 시간복잡도 BigO](https://youtube.com/watch?v=0xGJx6qsNCY) | [:memo: note05](./notes/lecture05-algorithm-complexity03.md) | 2021.05.01. | 27 | | 6 | [순차적 자료구조: 배열과 리스트](https://youtube.com/watch?v=Lqd8o7vL2Z8) | [:memo: note06](./notes/lecture06-sequential-array-list.md) | 2021.05.01. | 28 | | 7 | [순차적 자료구조 소개](https://youtube.com/watch?v=buJBlTsWlW0) | [:memo: note07](./notes/lecture07-sequential-data-structures.md) | 2021.05.02. | 29 | | 8 | [스택(Stack)](https://youtube.com/watch?v=OzFXiukhv8o) | [:memo: note08](./notes/lecture08-stack.md) | 2021.05.02. | 30 | | 9 | [스택 활용 - 계산기(1/2)](https://youtube.com/watch?v=G9ujrSGEB4A) | [:memo: note09](./notes/lecture09-stack-calculator01.md) | 2021.05.02. | 31 | | 10 | [스택 활용 - 계산기(2/2)](https://youtube.com/watch?v=MYk4autDAJ0) | [:memo: note10](./notes/lecture10-stack-calculator02.md) | 2021.05.02. | 32 | | 11 | [큐(Queue)](https://youtube.com/watch?v=nqCNk_DmPio) | [:memo: note11](./notes/lecture11-queue.md) | 2021.05.02. | 33 | | 12 | [연결리스트 소개](https://youtube.com/watch?v=sMpsvA5O0xU) | [:memo: note12](./notes/lecture12-linked-list.md) | 2021.05.03. | 34 | | 13 | [한방향 연결리스트 - 삽입,삭제 연산](https://youtube.com/watch?v=kGZoEShMcSQ) | [:memo: note13](./notes/lecture13-singly-linked-list01.md) | 2021.05.03. | 35 | | 14 | [한방향 연결리스트 - 탐색 연산](https://youtube.com/watch?v=aCHwXmpuAkY) | [:memo: note14](./notes/lecture14-singly-linked-list02.md) | 2021.05.03. | 36 | | 15 | [양방향 연결리스트 - splice 연산](https://youtube.com/watch?v=nQhzNRmnmt8) | [:memo: note15](./notes/lecture15-doubly-linked-list01.md) | 2021.05.03. | 37 | | 16 | [양방향 연결리스트 - 삽입,삭제,탐색 연산](https://youtube.com/watch?v=zWrFVf9_YTQ) | [:memo: note16](./notes/lecture16-doubly-linked-list02.md) | 2021.05.04. | 38 | | 17 | [해시 테이블 - 소개, 해시 함수](https://youtube.com/watch?v=Bzmepm6pYQI) | [:memo: note17](./notes/lecture17-hash01.md) | 2021.05.04. | 39 | | 18 | [해시 테이블 - open addressing(linear probing)](https://youtube.com/watch?v=Bj4pd9rJp5c)| [:memo: note18](./notes/lecture18-hash02.md) | 2021.05.04. | 40 | | 19 | [해시 테이블 - 성능 평가, chaining](https://youtube.com/watch?v=ghjWopXXUeA) | [:memo: note19](./notes/lecture19-hash03.md) | 2021.05.04. | 41 | | 20 | [트리구조 소개](https://youtube.com/watch?v=w-1w4ood7Bc) | [:memo: note20](./notes/lecture20-tree.md) | 2021.05.04. | 42 | | 21 | [힙(heap) - 정의](https://youtube.com/watch?v=8XnPN6IB22Y) | [:memo: note21](./notes/lecture21-heap01.md) | 2021.05.04. | 43 | | 22 | [힙(heap) - make_heap 연산](https://youtube.com/watch?v=6VMSTOdHRfI) | [:memo: note22](./notes/lecture22-heap02.md) | 2021.05.04. | 44 | | 23 | [힙(heap) - insert](https://youtube.com/watch?v=gVRDc5NRjjw) | [:memo: note23](./notes/lecture23-heap03.md) | 2021.05.04. | 45 | | 24 | [이진트리 - 정의와 순회](https://youtube.com/watch?v=HDjqrmmpFdU) | [:memo: note24](./notes/lecture24-binary-tree.md) | 2021.05.05. | 46 | | 25 | [이진트리 - 이진탐색트리 정의와 탐색,삽입 연산](https://youtube.com/watch?v=Bhprzw_1kb0) | [:memo: note25](./notes/lecture25-binary-search-tree01.md) | 2021.05.05. | 47 | | 26 | [이진트리 - 이진탐색트리 삭제 연산](https://youtube.com/watch?v=VVhmgQIJCu8) | [:memo: note26](./notes/lecture26-binary-search-tree02.md) | 2021.05.05. | 48 | | 27 | [균형이진탐색트리 - 정의와 회전](https://youtube.com/watch?v=Kuw0f3-E-Hw) | [:memo: note27](./notes/lecture27-balanced-binary-search-tree01.md) | 2021.05.06. | 49 | | 28 | [균형이진탐색트리 - AVL 트리 정의](https://youtube.com/watch?v=dHHjrl6m5CE) | [:memo: note28](./notes/lecture28-balanced-binary-search-tree02.md) | 2021.05.06. | 50 | | 29 | [균형이진탐색트리 - AVL 트리 삽입 연산](https://youtube.com/watch?v=KkgN2xAzmG8) | [:memo: note29](./notes/lecture29-balanced-binary-search-tree03.md) | 2021.05.06. | 51 | | 30 | [균형이진탐색트리 - AVL 트리 삭제 연산](https://youtube.com/watch?v=W3uPlSCzAZM) | [:memo: note30](./notes/lecture30-balanced-binary-search-tree04.md) | 2021.05.06. | 52 | 53 | ## 기타 54 | 55 | - [강의 동영상 목록](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)의 24, 25번째 동영상은 "이진트리 - 정의와 순회"로, 동일한 강의입니다. 56 | 57 | ## 저작권 안내 58 | 59 | 이 저장소는 학습 목적으로 생성한 저장소입니다. 모든 예제 코드와 내용에 대한 저작권은 원저자에게 있습니다. 60 | -------------------------------------------------------------------------------- /notes/lecture01-intro.md: -------------------------------------------------------------------------------- 1 | # 2020학년도 1학기 - 자료구조 및 실습: 한국외대 컴퓨터공학부 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 1강 "[인트로](https://youtube.com/watch?v=PIidtIBCjEg)" 강의를 정리한 노트입니다. 4 | 5 | ## 개요 6 | 7 | - 강의 교수: 신찬수 교수(chansu@gmail.com) 8 | - 강의노트: 자료구조(130p) + 파이썬문법(120p) 총 258쪽 9 | - goorm(강의내용, 강의노트, 실습, 과제) + eclass(레포트, 숙제, 공지, 문자소식 등) 10 | 11 | ## Goorm(구름): hufs.goorm.io(수강 초대 이메일 발송 예정) 12 | 13 | - 이론강의: 일부 동영상 포함 + 객관식, 주관식 문제 14 | - 실습강의: 코딩 실습, 코딩 과제 제공 15 | - 채점 문제, 제출 문제, 실행 문제 16 | 17 | ## Python 코딩 18 | 19 | 1. 구름: Python 연습장(Script, interactive) 20 | 2. 개별 노트북 21 | - python.org, ver.3.8.x 22 | - 에디터: notepad, notepad++ 등에서 `test.py`를 만든다면, 23 | - 명령창: cmd에서 `python test.py`로 실행 가능 24 | - 에디터와 명령창을 한번에 사용할 수 있는 Atom이나 Visual Studio Code도 사용 가능 25 | 26 | > 생활코딩: Python&Ruby 강의에서 설정 참고 27 | 28 | ## 강의내용 - Python은 각자 공부 + 자료구조 코딩 과제로 훈련 29 | 30 | 1. 자료구조와 알고리즘: 가상머신, 언어, 코드, 알고리즘/자료구조 복잡도, Big-O 기호 31 | 2. Array(List), Stack, Queue, Dequeue 32 | 3. Linked List(한방향, 양방향) 33 | 4. Hash Table(*): 해시 함수, 해시 충돌 해결 방법 34 | 5. Tree 35 | 6. Graph 36 | 37 | Tree 38 | 39 | - Priority Queue: Heap + HeapSort 40 | - 이진트리: 순회 41 | - 이진타색트리: 삽입, 삭제 등 42 | - 균형이진탐색트리: AVL, 2-3-4, Red-Black, Splay 43 | 44 | Graph 45 | 46 | - 표현법, 순회법(DFS, BFS) 47 | - 최단경로알고리즘(Bellman-Ford, Dijkstra algorithms) 48 | -------------------------------------------------------------------------------- /notes/lecture02-data-structure-and-algorithm.md: -------------------------------------------------------------------------------- 1 | # 자료구조와 알고리즘 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 2강 "[자료구조와 알고리즘](https://youtube.com/watch?v=jgWyu83DfO0)" 강의를 정리한 노트입니다. 4 | 5 | ## 개요 6 | 7 | 자료구조 8 | 9 | - 자료(data)를 담기 위한 저장공간과 연산을 통칭 10 | - 자료구조 = 저장공간(memory) + 연산(읽기, 쓰기, 삽입, 삭제 탐색) 11 | 12 | 알고리즘 13 | 14 | - 자료를 입력받아 유한한 횟수의 연산들을 이용해 원하는 결과를 출력하는 것 15 | 16 | > 자료구조와 알고리즘은 뗄레야 뗄 수 없는 바늘과 실 같은 것 17 | 18 | ### 자료구조의 예 19 | 20 | #### 변수(variable) 21 | 22 | ```py 23 | a = 5 # 쓰기 연산 24 | print(a) # 읽기 연산 25 | ``` 26 | 27 | #### 배열(array), 리스트(list) 28 | 29 | ```py 30 | A = [3, -1, 5, 7] # 접근: 원소의 index를 이용 31 | # 읽기 쓰기 예: A[3] 32 | # 삽입: A.append(9) 마지막 자리(A[4])에 원소를 추가 33 | # A.intsert 34 | # 삭제: A.pop() 매개변수가 없으면 마지막 값을 반환 후 삭제 35 | # A.pop(2) 36 | ``` 37 | 38 | ### 알고리즘의 예 39 | 40 | - 100개의 정수를 리스트 A에 담는다(입력) 41 | - 오름차순 정렬(출력) 42 | 43 | ## 알고리즘 44 | 45 | 인류 최초의 알고리즘: 최대공약수(GCD) 계산 알고리즘(By Euclid) 46 | 47 | 9세기, 페르시아, Algebra 수학자 Al-Khwarizmi -> 유럽에 라틴어로 번역되는 과정에 Algorismus로 번역됨. 수학자의 이름과 라틴어 수인 "Arithmos"이 합쳐져 "Algorithm"이 됐다는 게 정설. 48 | 49 | gcd(8, 12) = max{1, 2, 4} = 4 50 | 51 | ```py 52 | gcd_sub(a, b): 53 | while a != 0 and b != 0: 54 | if a > b: a = a - b 55 | else: b = b - a 56 | return a + b 57 | 58 | # 개선 59 | gcd_mod(a, b): 60 | while a != 0 and b != 0: 61 | if a > b: a = a % b 62 | else: b = b % a 63 | return a + b 64 | 65 | # gcd_rec(recursive) 66 | ``` 67 | -------------------------------------------------------------------------------- /notes/lecture03-algorithm-complexity01.md: -------------------------------------------------------------------------------- 1 | # 알고리즘 시간복잡도 1 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 3강 "[알고리즘 시간복잡도 1](https://youtube.com/watch?v=M2mcJvmYpWY)" 강의를 정리한 노트입니다. 4 | 5 | ## 자료구조와 알고리즘 성능: 가상컴퓨터 + 가상언어 + 가상코드 6 | 7 | ### 현실의 문제점 8 | 9 | #### 문제점 1) 10 | 11 | - "자료구조 + 알고리즘"을 생각하여 코드(C, Java, Python 등)로 구현하고 나면 실제 컴퓨터에서 동작하게 된다. 12 | - 각 켬퓨터마다 HW/SW 환경이 다를 수 있으므로 다른 성능을 낼 수 있고, 이는 큰 문제가 될 수 있다. 13 | 14 | #### 문제점 2) 15 | 16 | - 입력의 크기가 다양할 수 있다. 다양한 크기의 입력에 대해 코드의 실행 속도를 어떻게 구할 수 있을까. 17 | 18 | ### 해결책 19 | 20 | 가상의 컴퓨터(Virtual Machine) + 가상언어(Pseudo language) + 가상코드(Pseudo Code) 를 가정한다. 21 | 22 | #### 가상 컴퓨터(Virtual Machine/Random Access Machine) 23 | 24 | Turing Machine -> von Neumann: RAM(Random Access Machine) 25 | 26 | RAM = CPU + Memory + 기본연산(단위시간에 수행되는 연산들의 모음) 27 | 28 | 기본연산이란: 배정, 대입, 북사연산 등으로 이들은 1 단위 시간이 드는 것으로 본다. 29 | 30 | - `A = B`(Read and Write) 31 | - 산술연산: +, -, *, / (%, 내림, 올림, 반올림 등은 기본 연산으로 보지 않으나, 수업에서는 이들도 단위시간에 들어가는 것으로 간주) 32 | - 비교연산: >, >=, <, <=, ==, != 33 | - 논리연산: AND, OR, NOT 34 | - 비트연산: bit-AND, OR, NOT 35 | 36 | #### 가상 언어(Pseudo/Virtual Languages) 37 | 38 | - 배정, 산술, 비교, 논리, bit-논리 연산 등 기본연산을 표현할 수 있으면 됨 39 | - 비교: if, if else, if elseif... else 등 40 | - 반복: for, while 등 41 | - 함수: 정의, 호출, 반환 문법 42 | 43 | #### 가상 코드(Pseudo Code) 44 | 45 | 실제 돌아가는 코드가 아닌 가상머신(RAM)에서 돌아가는 코드이므로 자유롭게 기술할 수 있다. 46 | 47 | ```py 48 | # input: n개의 정수를 갖는 배열 A 49 | # output: A의 수 중에서 최대값 리턴 50 | alogirithm arrayMax(A, n): 51 | currentMax = A[0] 52 | for i = 1 to n-1 do # 가상의 코드. 53 | if currentMax < A[i]: # C 언어: for(int i = 1; i < n; i++){} 54 | currentMax = A[i] # Python: for i in range(A, n): 55 | return currentMax 56 | ``` 57 | 58 | `A = [3, -1, 9, 2, 12], n = 5`일때 기본연산은 7회(`for` 선언 내부에서 쓰는 i와 관련한 연산은 제외) 59 | -------------------------------------------------------------------------------- /notes/lecture04-algorithm-complexity02.md: -------------------------------------------------------------------------------- 1 | # 알고리즘 시간 복잡도(time complexity) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 4강 "[알고리즘 시간 복잡도(time complexity)](https://youtube.com/watch?v=ysn9dLDNLEU)" 강의를 정리한 노트입니다. 4 | 5 | ## 알고리즘 시간 복잡도를 계산하는 방법 6 | 7 | 1. 모든 입력에 대해 기본연산 횟수를 더한 후 평균(__현실적으로 불가능__) 8 | 2. 가장 안 좋은 입력(Worstcase input)에 대한 기본연산 횟수를 측정(__worstcase time complexity__) 9 | 10 | 2번 방법의 경우 어떤 입력에 대해서도 W.T.C.보다 수행시간이 크지 않다. 즉 연산횟수가 보장된다. 11 | 12 | > 알고리즘의 시간 복잡도를 정의하는 방법 13 | > 14 | > 알고리즘의 수행시간 = 최악의 입력에 대한 기본연산 횟수 15 | 16 | ```py 17 | # input: n개의 정수를 갖는 배열 A 18 | # output: A의 수 중에서 최대값 리턴 19 | alogirithm arrayMax(A, n): 20 | currentMax = A[0] 21 | for i = 1 to n-1 do 22 | if currentMax < A[i]: 23 | currentMax = A[i] 24 | return currentMax 25 | ``` 26 | 27 | 위 코드에서 `currentMax < A[i]` 조건이 항상 참일 경우 가장 연산횟수가 크다. 28 | 29 | 예시: `A = [2, 5, 7, 9, 15, 26]` 30 | 31 | `for`문이 n - 1만큼 실행할 때 기본연산은 2번 수행씩 된다. 32 | 33 | - 대입연산: `1` 단위시간 34 | - 반복문: `(n - 1) * 2` = `2n - 2` 단위시간 35 | 36 | > T(n) = 2n - 1 37 | 38 | `n = 6` 일때, `T(6) = 12 - 1 = 11` 39 | 40 | ### 시간 복잡도 예시 41 | 42 | #### 예시 1 43 | 44 | ```py 45 | algorithm sum1(A, n): 46 | sum = 0 # 대입연산 (1 단위시간) 47 | for i = 0 to n - 1 do 48 | if A[i] % 2 == 0: # 짝수이면 (1 단위시간) 49 | sum += A[i] # 더하고 대입하라(2 단위시간) 50 | return sum 51 | ``` 52 | 53 | `A`의 모든 값이 짝수이면 worstcase 입력 54 | 55 | > T(n) = 4n + 1 56 | 57 | `n`이 커지면 `n`에 비례해 수행시간이 커진다. 58 | 59 | #### 예시 2 60 | 61 | ```py 62 | algorithm sum2(A, n) 63 | sum = 0 64 | for i = 0 to n - 1 do 65 | for j = i to n - 1 do 66 | sum += A[i] * A[j] 67 | return sum 68 | ``` 69 | 70 | | i | j | 71 | |------|-------| 72 | | 0 | n | 73 | | 1 | n - 1 | 74 | | 2 | n - 2 | 75 | | n -1 | 1 | 76 | 77 | ```txt 78 | 1 + 2 + ... + n = (n(n + 1) / 2) * 3 79 | 80 | T(n) = ((3/2)n)(n+1) + 1 81 | = (3/2)n^2 - (3/2)n + 1 82 | ``` 83 | 84 | `n`이 커지면 `n^2`에 비례해 수행시간이 커진다. 85 | -------------------------------------------------------------------------------- /notes/lecture05-algorithm-complexity03.md: -------------------------------------------------------------------------------- 1 | # 알고리즘 시간 복잡도(Big-O 표기법) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 5강 "[알고리즘 시간 복잡도(Big-O 표기법)](https://youtube.com/watch?v=0xGJx6qsNCY)" 강의를 정리한 노트입니다. 4 | 5 | ## 앞서 4강에서 본 3개의 함수의 시간 복잡도: 6 | 7 | - Algorithm1(arrayMax): T1(n) = 2n - 1 8 | - Algorithm2(sum1): T2(n) = 4n + 1 9 | - Algorithm3(sum2): T3(n) = (3/2)n2 - (3/2)n + 1 10 | 11 | ### 도출할 수 있는 명제: 12 | 13 | 1. Algorithm2가 Algorithm1보다 2배 느리다. 14 | 2. Algorithm3는 `n < (5/3) `이면 Algorithm2보다 빠르다. 15 | 3. Algorithm3는 모든 n에 대해서 Algorithm1보다 느리다. 16 | 4. Algorithm3는 `n > 5/3`이면 항상 Algorithm2보다 느리다. 17 | 18 | - T1(n), T2(n)은 n에 대해 선형적으로 증가. 최고차항이 1차항 19 | - T3(n)은 n에 대해 제곱으로 증가. 최고차항이 n의 2제곱 20 | 21 | > n에 대한 최고차항 = 단위 시간의 증가율을 결정 22 | 23 | ## Big-O 표기법 24 | 25 | 이를 이용해 알고리즘의 수행시간을 간단하게 표기할 수 있다. 함수값을 결정하는 가장 중요한 최고차항만 표시하여 함수의 수행시간을 대략적으로 표현한다. 26 | 27 | - T1(n) = 2n - 1 은 T1(n) = O(n) 28 | - T2(n) = 4n + 1은 T2(n) = O(n) 29 | - T3(n) = (3/2)n2 - (3/2)n + 1은 T2(n) = O(n2) 30 | 31 | ### 표기방법 32 | 33 | 1. 최고차항만 남긴다 34 | 2. 최고차항 계수(상수)는 생략 35 | 3. Big-O(최고차항) 36 | 37 | ### 집합으로 이해하기 38 | 39 | - T1(n) = O(n) 40 | - T2(n) = O(n) 41 | 42 | 위 두 함수는 O(n) 집합 안에 들어있는 것으로 보면 된다. 즉, 43 | 44 | - T1(n) ∈ O(n) 45 | - T2(n) ∈ O(n) 46 | 47 | ### 예시 48 | 49 | #### 예시1 50 | 51 | ```py 52 | def inclrement_one(a): 53 | return a + 1 54 | ``` 55 | 56 | T(n) = 1 는 O(1) = O(n0) 57 | 58 | #### 예시2 59 | 60 | ```py 61 | def number_of_bits(n): 62 | count = 0 63 | while n > 0 : 64 | n = n // 2 65 | count += 1 66 | return count 67 | ``` 68 | 69 | n / (2count) = 1 는 log2N = count 70 | 71 | T(n) = c·log2N + 1 = O(log2N) 72 | -------------------------------------------------------------------------------- /notes/lecture06-sequential-array-list.md: -------------------------------------------------------------------------------- 1 | # 순차적 자료구조: 배열과 리스트 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 6강 "[순차적 자료구조: 배열과 리스트](https://youtube.com/watch?v=Lqd8o7vL2Z8)" 강의를 정리한 노트입니다. 4 | 5 | ## 배열(array) VS. 리스트(list) <- Python 6 | 7 | - 가장 기본적인 순차적(sequential) 자료구조(매우 기본, 중요) 8 | 9 | ```c 10 | int A[4] = {2, 4, 0, 5}; 11 | ``` 12 | 13 | `A[0]`에는 첫번째 요소의 바이트 주소가 저장되어 있다. 14 | 15 | `A[2] = A[2] + 1`: `A[2]`에 대한 읽기/쓰기 수행. `A[2]`의 값이 업데이트(산술연산), 대입(`=`) 연산. 이 연산은 모두 상수시간(O(1))이 걸림. 계산의 편의상 읽기/쓰기를 카운트하지 않았으나 원래는 1 단위시간이 걸린다. 16 | 17 | RAM이므로 `A[2]`의 주소가 있으면 값을 바로 가져올 수 있다. 18 | 19 | `A[0]`의 주소에서 + (4bytes * 2개) 20 | 21 | `A[0]`의 주소가 `100`이라면 `A[2]`는 `108`. 산술연산 두 번으로 구할 수 있으므로, `O(1)`로 볼 수 있다. 22 | 23 | > 배열은 `index`를 이용해 특정 위치의 값을 상수시간 내에 읽고 쓸 수 있게 해주는 자료구조이다. 24 | 25 | ### Python의 list 26 | 27 | `index`를 이용해 값을 읽고 쓸 수 있으며, 더 많은 함수를 제공한다. 28 | 29 | ```py 30 | A = [2, 4, 0, 5] 31 | ``` 32 | 33 | 파이썬의 리스트는 각각의 값을 다른 메모리에 저장한다. `2`, `4`, `0`, `5`는 객체가 되고, 따로 저장되며, `A[0]`은 `2`가 저장된 곳의 주소를 가리키고, `A[1]`은 `4`가 저장된 곳의 주소를 가리킨다. 34 | 35 | `A[2] = A[2] + 1`을 수행하면 `A[2]`가 가리키던 `0` 객체를 더이상 가리키지 않고, `1`을 갖고 있는 객체를 가리키게 된다. 36 | 37 | #### Python list의 함수들 38 | 39 | - `A.append(6)`: list의 맨 뒤에 `6`을 삽입(이전과 마찬가지로 마지막 위치에 `6`을 가리키는 주소를 저장) 40 | - `A.pop()`: list 맨 뒤의 값을 삭제하고 반환 41 | - `A.pop(1)`: `A[1]`을 제거하고 반환. `A[1]` 이후의 요소 `A[n]`은 `A[n -1]`으로 이동 42 | 43 | `append()`와 `pop()`은 기본으로 제공되는 삽입/삭제 연산 44 | 45 | - `A.insert(1, 10)`: `A[1]`에 `10`을 삽입. 기존의 `A[1]` 이후의 요소 `A[n]`은 `A[n + 1]`로 이동 46 | - `A.remove(value)`: `A`에서 왼쪽으로부터 첫번째 `value`를 찾아 제거. 삭제한 `A[v]` 이후의 `A[n]`은 `A[n - 1]`로 이동 47 | - `A.index(value)`: `A`를 왼쪽부터 검색해 첫번째 `value`의 인덱스 반환 48 | - `A.index(value)`: `A`를 왼쪽부터 검색해 해당 `value`의 개수 반환 49 | 50 | 파이썬의 list는 다양한 연산을 제공해 편의성이 높다. 51 | 52 | #### list의 이점 53 | 54 | - 용량(capacity)를 자동으로 조절한다. 공간이 부족하면 더 큰 용량을 할당하여 값을 저장. 55 | - `pop()`에 의해 요소들이 삭제가 되면 더 작은 용량을 할당하여 작은 리스트를 유지. 56 | 57 | > dynamic array: 자동으로 용량을 조정하는 배열. C 언어에서는 제공하지 않는다. 58 | 59 | ```c 60 | int A[4] = {2, 4, 0, 5} 61 | A[4] = 10; // error 62 | 63 | // 배열의 크기를 바꾸려면 64 | int *b = (int *)malloc(sizeof(int) * 4); // 동적 할당 65 | int *c = (int *)malloc(sizeof(int) * 6); // 동적 할당 66 | free (b); 67 | b = c; 68 | free(c); 69 | ``` 70 | 71 | 아래 코드로 배열의 크기가 변경되는 것을 확인할 수 있다. 72 | 73 | ```py 74 | import sys 75 | 76 | a = [] # empty list 77 | print(sys.getsizeof(A)) # 28bytes(운영체제에 따라 다를 수 있음) 78 | A.append(10) 79 | print(sys.getsizeof(A)) # 44bytes(운영체제에 따라 다를 수 있음) 80 | ``` 81 | 82 | #### list는 클래스(class) 83 | 84 | list A가 있다면 그 안에는: 85 | 86 | - `capacity`: 용량 87 | - `n`: 현재 저장된 값의 개수 88 | 89 | `capacity`가 `100`일 때, `append()` 연산이 반복되면 `n`이 증가. 90 | 91 | `A.append(x)`의 동작을 개념적으로 살펴보면: 92 | 93 | ```py 94 | if A.n < A.capacity: 95 | A[n] = x 96 | A.n = n + 1 97 | else: A.n == A.capacity 98 | B = A.capacity * 2 # 2배 크기의 리스트 새로 할당 99 | for i in range(n): 100 | B[i] = A[i] # A의 값을 B로 복사 O(n)만큼의 시간 소모 101 | del A 102 | A = B 103 | A[n] = x 104 | A.n = n + 1 105 | ``` 106 | 107 | #### list의 연산별 시간복잡도(Big-O) 108 | 109 | - `A.append()`, `A.pop`: O(1) 평균 110 | - `A.insert()`, `A.remove()`: O(n) w.c 111 | - `A.index()`, `A.count()`: O(n) w.c 112 | -------------------------------------------------------------------------------- /notes/lecture07-sequential-data-structures.md: -------------------------------------------------------------------------------- 1 | # 순차적 자료구조 소개 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 7강 "[순차적 자료구조 소개](https://youtube.com/watch?v=buJBlTsWlW0)" 강의를 정리한 노트입니다. 4 | 5 | ## 1. array, list 6 | 7 | - `index`로 임의의 원소에 접근할 수 있다. 8 | - 연산자 `[]`로 접근할 수 있다. 상수시간(O(1))에 값을 알 수 있다. 9 | - 삽입(`append`, `insert`) 10 | - 삭제(`pop`, `remove`) 11 | 12 | `append`와 `pop`은 O(1)이지만, `insert`와 `remove`는 O(n) 13 | 14 | ## 2. stack, queue, dequeue 15 | 16 | - 제한된 접근(삽입, 삭제)만 허용한다. 17 | 18 | ### stack 19 | 20 | - LIFO(Last In First Out) 21 | - 자루처럼 먼저 넣은 것이 밑에 있고, 꺼낼 때 나중에 넣은, 가장 위의 것이 나온다. 22 | - `push`: 삽입. 아래에서부터 차곡차곡 삽입된다. 23 | - `pop`: 삭제. 맨 위 값(가장 나중에 들어온 값)에서부터 삭제한다. 24 | 25 | 제한된 연산을 가진 자료구조가 많이 쓰인다. 26 | 27 | ### queue 28 | 29 | - FIFO(First In First Out) 30 | - 선착순(가장 먼저 온 사람이 가장 먼저 서비스를 받는다) 31 | - 삽입은 아래서부터 차곡차곡 쌓인다. 32 | - 삭제 역시 아래의 값(가장 먼저 들어온 값)부터 수행한다. 33 | 34 | ### dequeue 35 | 36 | - stack + queue 37 | - 삽입은 위, 아래로 수행할 수 있다. 38 | - 삭제 역시 위, 아래로 수행할 수 있다. 39 | 40 | ## 3. linked list(연결 리스트) 41 | 42 | - 파이썬의 list는 배열과 유사. linked list와는 다르다. 43 | - 값이 연속된 공간이 아닌 메모리 공간에 독립적으로 저장된다. 44 | - 다음 값이 저장된 주소를 갖고 있다(link). 45 | - 각각의 값은 자기자신의 값과 함께 다음 값의 주소를 갖고 있다. 46 | - 마지막 요소는 다음 값의 주소 대신 `NULL`(Python에서는 `None`)을 갖고 있다. 47 | - `index`로 접근할 수 없음. 48 | - n번째 값을 가져오고 싶다면 첫번째로부터 순차적으로 조회해야한다. 49 | -------------------------------------------------------------------------------- /notes/lecture08-stack.md: -------------------------------------------------------------------------------- 1 | # 스택(stack) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 8강 "[스택(stack)](https://youtube.com/watch?v=OzFXiukhv8o)" 강의를 정리한 노트입니다. 4 | 5 | 자료구조는 삽입, 삭제, 탐색, 이 세 가지 연산은 어느 자료구조든지 제공을 해야한다. 6 | 7 | ## 스택의 연산 8 | 9 | - 삽입: `push` 10 | - 삭제: `pop` 11 | 12 | 스택은 아래에서부터 저장이 되고, 최근에 들어온 값부터 제거가 되는 형식이다(LIFO) 13 | 14 | 스택은 `push`와 `pop` 연산으로만 값을 변경할 수 있다. 따라서 파이썬 리스트를 간접적으로 사용하면서, 두 연산으로만 값을 변경하도록 한다. 15 | 16 | ### 편의상 제공하는 연산 17 | 18 | - `top`: 가장 위에 있는 값을 반환. `pop`과 달리 삭제를 수행하지 않는다. 19 | - `len`: 요소의 개수를 반환하는 함수 20 | 21 | ## 스택 클래스 구현 22 | 23 | ```py 24 | class Stack: 25 | def __init__(self): 26 | self.items = [] # 데이터 저장을 위한 리스트 준비 27 | 28 | def push(self, val): 29 | self.items.append(val) 30 | 31 | def pop(self): 32 | try: # pop할 아이템이 없으면 33 | return self.items.pop() 34 | except IndexError: # indexError 발생 35 | print("Stack is empty") 36 | 37 | def top(self): 38 | try: 39 | return self.items[-1] 40 | except IndexError: 41 | print("Stack is empty") 42 | 43 | def __len__(self): # len() 호출하면 stack의 item 수 반환 44 | return len(self.items) 45 | ``` 46 | 47 | 사용 예시 48 | 49 | ```py 50 | S = Stack() 51 | S.push(10) 52 | S.push(2) 53 | print(S.pop()) # 2 54 | print(S.top()) # 10 55 | print(len(S)) # 1 -> 이렇게 호출하면 파이썬은 S.__len__()을 호출함 56 | ``` 57 | 58 | ### 연산의 수행시간 59 | 60 | - `push`: O(1)(상수 시간) 61 | - `pop`: O(1) 62 | - `top`: O(1) 63 | - `len`: O(1) (리스트에서 개수를 항상 알고 있으므로 값만 리턴한다) 64 | 65 | ## 스택의 사용예 1) 괄호 맞추기 66 | 67 | > 보기1: (2 + 5) * 7 - ((3 - 1) / 2 + 7) # 쌍이 맞음 68 | > 69 | > 보기2: (()()) # 쌍이 맞음 70 | > 71 | > 보기3: (()))( # 쌍이 맞지 않음 72 | 73 | - 문제: 74 | - 입력: 왼쪽과 오른쪽 괄호의 문자열 75 | - 출력: 쌍이 맞으면 True, 아니면 False 반환 76 | 77 | 보기2를 보면, 78 | 79 | > ( ( ) ( ) ) 80 | > 1 2 2 3 3 1 81 | 82 | 좌측 괄호를 만나면 `push`, 우측 괄호를 만나면 `pop`을 수행한다. 끝까지 수행한 후 스택에 아무것도 남지 않았다면 짝이 맞는 것. True. 83 | 84 | > ( ) ) 85 | > 1 1 2 86 | 87 | 위 경우, 마지막에 `pop`을 수행하려 할 때 에러가 발생. 왼쪽괄호가 하나 모자른 것이다. False. 88 | 89 | > ( ) ( 90 | > 1 1 2 91 | 92 | 끝까지 수행한 후 무언가 남아있다면 오른쪽 괄호 하나가 모자른 경우. False. 93 | 94 | ```py 95 | for p in parSeq: 96 | if p == '(': S.push(p) 97 | elif p == ')': S.pop() # 에러 발생시 오른쪽 괄호가 더 많은 것으로 보고 return False 98 | else: print("Not allowed Symbol") 99 | if len(S) > 0: return False 100 | else: return True 101 | ``` 102 | -------------------------------------------------------------------------------- /notes/lecture09-stack-calculator01.md: -------------------------------------------------------------------------------- 1 | # 스택(stack) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 9강 "[스택(stack)](https://youtube.com/watch?v=G9ujrSGEB4A)" 강의를 정리한 노트입니다. 4 | 5 | 6 | 7 | ## 스택의 사용예 2) 계산기 코드 작성 8 | 9 | ```txt 10 | "2 + 3 * 5" - > '2', '+', '3', '*', '5' 11 | ^연산자operator ^피연산자operand 12 | ``` 13 | 14 | - 이항연산자(binary operator): 2 + 3, 3 * 5 15 | - 단항연산자(unary operator): +3, -6 16 | 17 | 예시에서는 이항연산자만 다룸. 18 | 19 | - infix 수식: "2 + 3 * 5" 처럼 연산자가 두 피연산자 사이에 있는 형식 20 | - postfix로 변경하면: "2 3 5 * +" 21 | 22 | infix를 postfix로 변경하는 규칙 23 | 24 | 1. 우선순위에 따라 괄호를 친다: "(2 + (3 * 5))" 25 | 2. 연산자의 오른쪽 활호 다음으로 연산자 이동: (2 (3 5) * ) + 26 | 3. 괄호 지우기: 2 3 5 * + 27 | 28 | 연습 해보기 29 | 30 | - infix: `3 * (2 + 5) * 4` 31 | - postfix: `3 2 5 + * 4 *` 32 | 33 | 1. 괄호치기: `((3 * (2 + 5)) *4)` 34 | 2. 연산자 이동: `((3 (2 5)+)* 4)*` 35 | 3. 괄호 지우기: `3 2 5 + * 4 *` 36 | -------------------------------------------------------------------------------- /notes/lecture10-stack-calculator02.md: -------------------------------------------------------------------------------- 1 | # 스택(stack) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 10강 "[스택(stack)](https://youtube.com/watch?v=MYk4autDAJ0)" 강의를 정리한 노트입니다. 4 | 5 | ## 스택의 사용예 2) 계산기 코드 작성2 6 | 7 | - 문제: 8 | - 입력: +, -, *, /, (, ), 숫자(영문자)로 구성된 infix 수식 9 | - 출력: postfix 수식 10 | 11 | - `A + B * C -> A B C * +` 12 | 13 | 분석 14 | 15 | 1. 피연산자의 순서는 그대로 16 | 2. 자신보다 연산자 우선순위가 낮은 연산자가 나올 때까지 대기(이때 스택에서 대기) 17 | 18 | - `A * B + C -> A B * C +` 19 | - `A + B + C -> A B C * +` 20 | 21 | 괄호가 있으면 괄호도 스택에 담는다. 오른쪽 괄호는 우선순위가 가장 높고 왼쪽 괄호는 우선순위가 가장 낮다.왼쪽괄호가 나오면 무조건 스택에 연산자를 담다가, 오른쪽 괄호가 오는 순간 왼쪽 괄호가 나올 때까지 `pop`을 한다. 22 | 23 | ### 구현하기 24 | 25 | - 리스트: outstack 26 | - 스택: opstack 27 | 28 | ```py 29 | # psuedo code 30 | 31 | for each token in expr: 32 | if token == operand: 33 | outstack.append(token) 34 | if token == '(': 35 | opstack.push(token) 36 | if token == ')': 37 | opstack에서 '('가 나올때까지 pop하면서 outstack에 `append` 38 | if token in '+*-/`: 39 | opstack에 token보다 우선순위 높은 연산자는 모두 `pop`한 후 자신을 `push` 40 | opstack에 남은 연산자 모두 `pop`한 후 outstack에 `append` 41 | ``` 42 | 43 | 위 psuedo code에 따라 아래 수식을 postfix 수식으로 변환하기 44 | 45 | > `6 + (3 - 2) * 4` 46 | 47 | 왼쪽 괄호가 나오면 무조건 스택에 넣었다가 오른쪽 괄호가 나오는 시점에 왼쪽 괄호가 나올 때까지 pop 해준다. 이 부분 중요. 48 | 49 | > `6 3 2 - 4 * +` 50 | 51 | ### postfix 수식을 이용해 계산하기 52 | 53 | ```py 54 | # psuedo code 55 | 56 | if token == operand 57 | S.push(token) 58 | if token in '+-*/': 59 | a = S.pop() 60 | b = S.pop() 61 | S.push(a token b) 62 | ``` 63 | 64 | 위 puedo code를 이용해 계산하면 10의 결과를 얻을 수 있다. 65 | -------------------------------------------------------------------------------- /notes/lecture11-queue.md: -------------------------------------------------------------------------------- 1 | # 큐(queue) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 11강 "[큐(queue)](https://youtube.com/watch?v=nqCNk_DmPio)" 강의를 정리한 노트입니다. 4 | 5 | FIFO(First in First out) 규칙의 순차적 자료 구조 6 | 7 | - 삽입: `enqueue` 8 | - 삭제: `dequeue` 9 | 10 | ```py 11 | enqueue(5) 12 | enqueue(-2) 13 | dequeue() # 5 14 | enqueue(10) 15 | dequeue() # -2 16 | ``` 17 | 18 | queue는 두 개의 인덱스가 필요하다. 19 | 20 | - `enqueue`는 현재 어디까지 쌓여있는지 알아야 한다. (rear index) 21 | - `dequque`는 다음에 나갈 인덱스를 알아야 한다. (front index) 22 | 23 | ```py 24 | class Queue: 25 | def __init__(self): 26 | self.items = [] 27 | self.front_index = 0 28 | def enque(self, val): 29 | self.items.append(val) 30 | def dequeue(self): 31 | if self.front_index == len(self.items): # 현재 dequeue할 수 있는 값이 없음 32 | print("Queue is empty") 33 | return None 34 | else: 35 | x = self.items[front_index] 36 | self.front_index += 1 37 | return x 38 | ``` 39 | 40 | ## 큐 활용 예시: Josephus Problem 41 | 42 | 여섯 명(n)중에 2번째(k) 사람마다 죽는다면 몇 번의 사람이 살아남는가? 43 | 44 | ```txt 45 | n = 6, k = 2 46 | 1 47 | 6 2 48 | 5 3 49 | 4 50 | ``` 51 | 52 | 아래처럼 동작하면 된다. 53 | 54 | ```py 55 | Josephus(n, k): 56 | return 최종 생존자의 번호 57 | ``` 58 | 59 | `n = 9, k = 3`이라면, `k - 1 = 2`는 `dequeue` 하자마자 다시 `enqueue`를 한다. `k` 번째가 나오면 `dequeue`를 하여 삭제한다. 이 로직을 마지막 살아남는 사람이 나올 때까지 반복한다. 코드는 강의 노트를 참고할 것. 60 | 61 | ## 덱(deque) 62 | 63 | stack + queue 형태. 덱은 양쪽 끝에서 insert와 delete가 이루어진다. 64 | 65 | - 삽입: `append`, `append_left` 66 | - 삭제: `pop`, `pop_left` 67 | 68 | 파이썬에서는 `deque` 클래스를 제공한다. 덱 관련한 내용은 온라인 QnA에서 따로 설명한다. 69 | -------------------------------------------------------------------------------- /notes/lecture12-linked-list.md: -------------------------------------------------------------------------------- 1 | # 연결리스트(Linked List) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 12강 "[연결리스트(Linked List)](https://youtube.com/watch?v=sMpsvA5O0xU)" 강의를 정리한 노트입니다. 4 | 5 | 한방향 vs. 양방향 6 | 7 | ## 배열과 연결리스트 비교 8 | 9 | 배열은 메모리 공간에 순차적으로 배치되므로 `[]` 연산자로 상수시간 접근이 가능하다. 하지만 연결리스트는 메모리상에서 흩어져 있어서 상수시간 접근이 불가능하다. 10 | 11 | 연결리스트의 마지막 요소에는 None(C에서의 NULL)이 있다. 이는 다음 값이 없다는 것을 의미하고 그 앞의 값이 마지막 값이 된다. 12 | 13 | 연결리스트의 각 노드는 data 값(key)과 다음 주소의 값(link) 쌍을 항상 갖고 있어야 한다. 이 쌍을 노드(node)라고 부른다. 노드와 링크로 연결된 구조를 연결리스트라고 부른다. 14 | 15 | 가장 앞의 노드를 헤드 노드(head node)라고 부른다. n번째의 값을 가져오려면 헤드 노드로부터 링크를 따라가야만 한다. 16 | 17 | 연결리스트의 장점 18 | 19 | - 배열에서 값과 값 사이에 새 값을 넣으려면 삽입한 값 이후의 모든 요소를 한 칸씩 이동해야 한다. 하지만 연결리스트는 중간에 새 노드를 넣고 다시 연결해주면 된다. 두 개의 링크 주소만 바꾸면 되므로 insert 작업에는 상수시간이 걸린다. 단 교체해야할 노드를 알고 있을 때에 상수시간이다. 20 | 21 | ## 한 방향 연결 리스트 vs 양 방향 연결 리스트 22 | 23 | - 한 방향: 링크가 한쪽 방향으로만 연결되어 있으므로, 한 방향으로만 갈 수 있고 반대 방향으로는 갈 수 없다. 24 | - 양 방향: 양쪽 방향으로 링크가 있어서 노드의 양쪽 방향으로 모두 이동할 수 있다. 25 | 26 | ## 구현해보기 27 | 28 | ```py 29 | class Node: 30 | def __init__(self, key = None): 31 | self.key = key 32 | self.next = None 33 | def __str__(self): 34 | return str(self.key) # print(v.key) 대시 print(v)로 쓸 수 있다. 35 | ``` 36 | 37 | 사용 예시 38 | 39 | ```py 40 | a = Node(3) 41 | b = Node(9) 42 | c = Node(-1) 43 | 44 | a.next = b 45 | b.next = c 46 | ``` 47 | 48 | `c` 노드를 테일 노드(tail node)라고 부른다. 49 | 50 | 헤드 노드의 앞에 `head`와 `size`를 갖고 있는 객체를 만들어서 헤드 노드 앞에 붙이려 한다. 51 | 52 | ```py 53 | class SinglyLinkedList: 54 | def __init__(self): 55 | self.head = None 56 | self.size = 0 57 | . 58 | . 59 | . 60 | ``` 61 | 62 | 한방향 연결리스트에 대해서는 다음 동영상에서 본격적으로 살펴본다. 63 | -------------------------------------------------------------------------------- /notes/lecture13-singly-linked-list01.md: -------------------------------------------------------------------------------- 1 | # 한방향 연결리스트 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 13강 "[한방향 연결리스트](https://youtube.com/watch?v=kGZoEShMcSQ)" 강의를 정리한 노트입니다. 4 | 5 | ## `pushFront`와 `pushBack` 6 | 7 | ```py 8 | class SinglyLinkedList: 9 | def __init__(self): 10 | self.head = None 11 | self.size = 0 12 | def __len__(self): 13 | return self.size 14 | def pushFront(self, key): 15 | new_node = Node(key) 16 | new_node.next = self.head; 17 | self.head = new_node 18 | self.size += 1 19 | def pushBack(self, key): 20 | v = Node(key) 21 | if len(self) == 0: 22 | self.head = v 23 | else: 24 | tail = self.head 25 | while tail.next != None: 26 | tail = tail.next 27 | tail.next = v 28 | self.size += 1 29 | 30 | ``` 31 | 32 | 사용 예시 33 | 34 | ```py 35 | L = SinglyLinkedList() 36 | L.pushFront(-1) # [-1]->0 37 | L.pushFront(9) # [9]->[-1]->0 38 | L.pushFront(3) # [3]->[9]->[-1]->0 39 | L.pushFront(5) # [5]->[3]->[9]->[-1]->0 40 | L.pushBack(4) # [5]->[3]->[9]->[-1]->[4]->0 41 | ``` 42 | 43 | ## 삭제 연산 `popFront`와 `popBack` 44 | 45 | > [5]->[3]->[9]->[1]->0 46 | 47 | `popFront()` 48 | 49 | ```py 50 | def popFront(self): 51 | if len(self) == 0: 52 | return None 53 | else: 54 | x = self.head 55 | key = x.key 56 | self.head = x.next 57 | self.size -= 1 58 | del x 59 | return key 60 | ``` 61 | 62 | `popBack()` 63 | 64 | ```py 65 | def popBack(self): 66 | if len(self) == 0: return None 67 | else: # running techinque 68 | prev, tail = None, self.head 69 | while tail.next != None: 70 | prev = tail 71 | tail = tail.next 72 | if len(self) == 1: 73 | self.head = None 74 | else 75 | prev.next = tail.next # None 76 | key = tail.key 77 | del tail 78 | self.size -= 1 79 | return key 80 | ``` 81 | 82 | ## 한방향 연결리스트의 수행 시간 83 | 84 | - `pushFront`: O(1) 85 | - `popFront`: O(1) 86 | - `pushBack`: O(n) 87 | - `popBack`: O(n) 88 | -------------------------------------------------------------------------------- /notes/lecture14-singly-linked-list02.md: -------------------------------------------------------------------------------- 1 | # 한방향 연결리스트 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 14강 "[한방향 연결리스트](https://youtube.com/watch?v=aCHwXmpuAkY)" 강의를 정리한 노트입니다. 4 | 5 | ## 추가 연산들: 탐색(search) + 제너레이터(generator) 6 | 7 | ### 서치 8 | 9 | ```py 10 | def search(self, key): 11 | # key 값의 노드를 리턴, 없으면 None 리턴 12 | v = self.head 13 | while v.next != None: 14 | if v.key == key: 15 | return v 16 | v = v.next 17 | return None # or return v (== None) 18 | ``` 19 | 20 | ### 제너레이터 21 | 22 | ```py 23 | A = [3, 5, -1, 9] 24 | for x in A: # A의 요소를 순서대로 x에 대입해준다. 25 | print(x) 26 | ``` 27 | 28 | 리스트처럼 `for` 루프에서 사용할 수 있게 해주는 것이 제너레이터. 29 | 30 | ```py 31 | def __iterator__(self): 32 | v = self 33 | while v != None: 34 | yield v 35 | v = v.next 36 | ``` 37 | 38 | `yeild`가 있는 함수를 가리켜 제너레이터라고 한다. `L`이라는 이름의 연결리스트에 대해 아래처럼 쓸 수 있다. 39 | 40 | ```py 41 | for x in L: 42 | print(x) 43 | ``` 44 | 45 | 연결리스트의 이터레이터가 종료되면 `StopIterator`라는 에러 메시지가 자동으로 생성된다. 이 에러 메시지가 발생하면 자동으로 이터레이터를 빠져 나온다. 46 | -------------------------------------------------------------------------------- /notes/lecture15-doubly-linked-list01.md: -------------------------------------------------------------------------------- 1 | # 양방향 연결리스트(Doubly Linked List) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 15강 "[양방향 연결리스트(Doubly Linked List)](https://youtube.com/watch?v=nQhzNRmnmt8)" 강의를 정리한 노트입니다. 4 | 5 | ## 한방향 연결리스트의 단점 6 | 7 | - 한쪽으로만 연결이 되어 있기 때문에 테일 노드에 대한 조작을 하고 싶을 때 직전의 노드(previous node)를 알고 있어야 한다. 그래서 O(n)의 연산 시간을 사용한다. 8 | 9 | 양방향 연결리스트를 이용해 이 문제를 해결할 수 있다. 10 | 11 | ## 양방향 연결리스트 12 | 13 | 테일 노드와 헤드 노드가 각각 서로를 가리키도록 하면 원형 양방향 연결리스트(Circulary Doubly L.L)라고 부른다. 본 강좌에서는 양방향 연결리스트라고 하면 원형 연결리스트를 지칭하는 것으로 한다. 14 | 15 | ## 원형 연결리스트 16 | 17 | 원형 연결리스트의 빈 리스트는 더미 노드(dummy node)라고 부른다. 더미 노드는 원형 연결리스트의 시작을 알리는 마커로서, key로 None을 넣어준다. 더미 노드는 헤드 역할을 하므로 헤드 노드라고 부르기도 한다. 18 | 19 | ## 구현 20 | 21 | ```py 22 | class Node: 23 | def __init__(self, key = None): 24 | self.key = key 25 | self.next = self 26 | self.prev = self 27 | def __str__(self): 28 | # implement 29 | def __len__(self): 30 | # implement 31 | ``` 32 | 33 | ```py 34 | class DoublyLinkedList: 35 | def __init__(self): 36 | self.head = Node() 37 | self.size = 0 38 | def __iter__(self): 39 | # implement 40 | def __str__(self): 41 | # implement 42 | def __len__(self): 43 | # implement 44 | ``` 45 | 46 | ## 양방향 연결리스트의 다양한 삽입 삭제 연산 47 | 48 | ### splice 연산 49 | 50 | ```py 51 | def splice(self, a, b, x): # 세 개의 노드 a, b, x 52 | ap = a.prev, bn = b.next, xn = x.next 53 | ap.next = bn # cut 54 | bn.prev = ap # cut 55 | xn.next = a 56 | a.prev = x 57 | b.next = xn 58 | xn.prev = b 59 | ``` 60 | 61 | a와 b를 포함하는 노드를 cut 해서 x의 다음에 연결한다. 이때 x는 양방향 연결리스트 자기 자신일 수도 있고 다른 양방향 연결리스트일 수도 있다. 62 | 63 | - 조건1: a -> ... -> b 형태로 배치 64 | - 조건2: a와 b 사이에 head 노드가 없어야 함 65 | - 조건2: a와 b 사이에 x 노드가 없어야 함 66 | 67 | 6개의 링크를 변경하면 cut과 재연결을 할 수 있다. 68 | -------------------------------------------------------------------------------- /notes/lecture16-doubly-linked-list02.md: -------------------------------------------------------------------------------- 1 | # 양방향 연결리스트 삽입-삭제-탐색 연산 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 16강 "[양방향 연결리스트 삽입-삭제-탐색 연산](https://youtube.com/watch?v=zWrFVf9_YTQ)" 강의를 정리한 노트입니다. 4 | 5 | ```txt 6 | |------------------v 7 | head [.]->[0]<->[3]<->[-1]<->[9] 8 | size [3] ^------------------| 9 | ``` 10 | 11 | ## 이동 연산 12 | 13 | - `moveAfter(self, a, x)` -> `splice(a, a, x)` 14 | - `moveBefore(a, x)` -> `splice(a, a, x.prev)` 15 | 16 | ## 삽입 연산 17 | 18 | 삽입 19 | 20 | - `insertAfter(x, key)` -> `moveAfter(Node(key), x)` 21 | - `insertBefore(x, key)` -> `moveBefore(Node(key), x)` 22 | 23 | 푸시 24 | 25 | - `pushFront(key)` -> `insertAfter(self.head, key)` 26 | - `pushBack(key)` -> `insertBefore(self.head, key)` 27 | 28 | ## 탐색 연산 29 | 30 | 탐색 31 | 32 | ```py 33 | def search(self, key): 34 | v = self.head # dummy node 35 | while v.next != self.head: 36 | if v.key == key: 37 | return v 38 | v = v.next 39 | return None 40 | ``` 41 | 42 | ## 삭제 연산 43 | 44 | 삭제 45 | 46 | ```py 47 | def remove(x) # node x를 삭제 48 | if x == None or x == self.head: 49 | return 50 | x.prev.next = x.next 51 | x.next.prev = x.prev 52 | del x 53 | ``` 54 | 55 | popFront 56 | 57 | ```py 58 | def popFront() 59 | if self.size == 0: 60 | return None 61 | remove(self.head.next) 62 | ``` 63 | 64 | popBack 65 | 66 | ```py 67 | def popBack() 68 | if self.size == 0: 69 | return None 70 | remove(self.head.prev) 71 | ``` 72 | 73 | ## 그외의 함수 74 | 75 | - `join`: 두 연결리스트를 하나로 합침 76 | - `split`: 하나의 연결리스트를 어떤 노드 x를 기준으로 나눔 77 | 78 | ## 양방향 연결리스트 연산의 수행시간 79 | 80 | - `moveAfter/moveBefore`: O(1) # splice 활용 81 | - `insertAfter/insertBefore`: O(1) # splice 활용 82 | - `pushFront/pushBack`: O(1) # splice 활용 83 | - `remove(x)`: O(1) 84 | - `popFront/popBack`: O(1) 85 | - `search(key)`: O(n) 86 | - `splice(a, b, x)`: O(1) 87 | 88 | `remove`는 `search`를 먼저 호출해야하므로 시간이 더 걸리 수 있다. 한 방향 연결리스트는 테일 노드를 수정하는 연산이 O(n)이었지만 원형 양방향 연결리스트는 O(1)이다. 89 | -------------------------------------------------------------------------------- /notes/lecture17-hash01.md: -------------------------------------------------------------------------------- 1 | # 해시 테이블 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 17강 "[해시 테이블](https://youtube.com/watch?v=Bzmepm6pYQI)" 강의를 정리한 노트입니다. 4 | 5 | ## 해시테이블 소개 및 해시 함수 6 | 7 | 해시 테이블(Hash Table): 매우 빠른 평균 삽입, 삭제, 탐색 연산 제공 8 | 9 | 딕셔너리 예시 10 | 11 | ```py 12 | D = {} 13 | D['2019317'] = "신찬수" 14 | D['2019209'] = "홍길동" 15 | D = { 2019317: "신찬수", 2019209: "홍길동" } 16 | ``` 17 | 18 | ### 파이썬에서 key/value를 저장하는 방식 19 | 20 | - "key: 2019317, value: "신창수""를 받으면 2019317 % 10의 위치에 저장 21 | - "key: 2019209, value: "홍길동""을 받으면 2019209 % 10의 위치에 저장 22 | 23 | "%10"은 열 칸 중에 하나에 저장하기 위해서임. 10칸은 임의의 예시. 24 | 25 | 만약 "2019235"를 찾으면 2019235 % 10 위치에 찾아가 확인한 후, 없음을 반환한다. 26 | 27 | ### 해시 함수(hash function) 28 | 29 | 저장하는 장소를 정할 때 key 값을 index 로 매핑한다. 이 매핑 과정에서 함수(여기서는 나머지 연산)가 쓰인다. 이 함수를 해시 함수라고 부른다. 30 | 31 | 새로 들어온 값을 저장하려 할 때, 그 곳에 기존의 값이 있으면 충돌(collistion) 발생. 이를 해결하는 걸 충돌 해결(collision resolustion)이라고 부르며 이 해결함수를 충돌 해결 방법이라고 부른다. 32 | 33 | 세 가지 주요 요소 34 | 35 | 1. Table: list 36 | 2. Hash function 37 | 3. Collision resolution method 38 | 39 | #### 해시 함수 특징과 종류 40 | 41 | ```py 42 | H, (H) = m # m은 slots 개수 43 | ``` 44 | 45 | 앞서 예시처럼 나머지 연산으로 구분하는 해시 함수를 Division hash function이라고 부른다. 46 | 47 | ```py 48 | f(k) = (k % P) % m # P는 소수(Prime) 49 | ``` 50 | 51 | 이 방식은 간단하지만 충돌이 많이 발생한다. 각 슬롯에 골고루 분산되도록 해주는 해시 함수가 좋은 함수인데 이런 함수를 perfect hash function이라고 부른다. 가장 이상적인 형태의 해시 함수 52 | 53 | ```txt 54 | key ----> slot 55 | 1:1 56 | ``` 57 | 58 | Universal hash function: 충돌이 일어날 확률이 1/m인 해시 함수 59 | 60 | ```py 61 | Pr(f(x) == f(y)) = 1/m 62 | ``` 63 | 64 | C-universal hash function: 충돌이 일어날 확률이 c/m인 해시 함수 65 | 66 | ```py 67 | Pr(f(x) == f(y)) = c/m 68 | ``` 69 | 70 | 다양한 해시 함수 종류 71 | 72 | - Division 73 | - Multiplication 74 | - Folding 75 | - Mid-squares 76 | - Extraction 77 | 78 | 키 값이 문자열일 경우 79 | 80 | - Addictive: key[i] 번째 문자를 전부 더해서 m으로 나머지 연산 81 | - Rotating: 값 h를 계산하는데 initial_value를 임의로 준 후 for문으로 키값 문자열의 길이만큼 `h = (h << 4) ^ (h >> 28) ^ key[i]`한후 `h%P%m`을 반환 82 | - Universal: 값 h 계산에서 `((h * a) + key[i])) % P`를 한후 `h%m`을 반환. `a`는 임의의 값. C++ STL 해시 함수로 사용되고 있음. 자바에서도 활용. 83 | 84 | #### 좋은 해시 함수 85 | 86 | 1. Less collision 87 | 2. Fast computation 88 | 89 | 이 두 가지는 트레이드오프인 측면이 있다. 적절하게 만드는 게 중요하다. 90 | -------------------------------------------------------------------------------- /notes/lecture18-hash02.md: -------------------------------------------------------------------------------- 1 | # 해시 테이블 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 18강 "[해시 테이블](https://youtube.com/watch?v=Bj4pd9rJp5c)" 강의를 정리한 노트입니다. 4 | 5 | ## 충돌회피방법(Collision resolution method) 6 | 7 | ### Open addressing 8 | 9 | 가고자하는 슬롯이 비어 있지 않을 경우 주위를 살펴보는 방식 세 가지 10 | 11 | - linear probing: 바로 아래의 슬롯부터 확인해서 빈칸을 발견하면 저장 12 | - quadratic probing 13 | - double hashing 14 | 15 | 오픈 어드레싱과 대비되는 방법: Chaining 16 | 17 | 클러스터: 비슷한 키 값이 모여있는 단위를 말함. 클러스터가 크면 삽입/탐색에 시간이 많이 소요됨. 클러스터는 가급적 피해야 한다. 18 | 19 | ### 연산 20 | 21 | #### set 22 | 23 | 1. key값이 H에 있으면 value를 업데이트 24 | 2. key값이 H에 없으면 (key, value)를 insert 25 | 26 | `set`에 사용할 `find_slot` 함수 27 | 28 | ```py 29 | find_slot(key): # key값이 있으면 slot 번호 리턴 30 | # key값이 없으면 key값이 삽입될 slot 번호 리턴 31 | i = f(key) 32 | start = i 33 | while ( H[i] == occupied) and (H[i].key != key) 34 | i = (i + 1)%m 35 | if i == start: return Full 36 | return i 37 | ``` 38 | 39 | set 함수 40 | 41 | ```py 42 | set(key, value = None): 43 | i = find_slot(key) 44 | if i == Full: return None # 반환 전에 H를 키워야 함! 45 | if H[i].is_occupied: 46 | H[i].value = value 47 | else: 48 | H[i].key, H[i].value = key, value 49 | return key 50 | ``` 51 | 52 | #### search 53 | 54 | ```py 55 | 56 | search(key): 57 | ``` 58 | 59 | set과 비슷하게 구현하면 된다. 교재 참고 60 | 61 | #### remove 62 | 63 | 클러스터에서 사이에 있는 값이 사라지면 클러스터가 끊기지 않도록 아래에 있는 값을 올려줘야 한다. 이때 원래 자기 자리에 있는 값은 올려선 안 된다. 64 | 65 | 올려야 하는 값을 검색할 때는 클러스터의 끝까지 확인해줘야 한다.빈칸 또는 끝칸을 만날 때까지 작업을 한다. 66 | 67 | `k = f(H[j].key)`일 때, 현재 타겟 i가 k와 j 사이에 있다면 (`k < i <= j`)라면 올릴 수 있다. 68 | 69 | ```py 70 | remove(key): 71 | i = find_slot(key) 72 | if H[i].is_occupied: return None 73 | j = i # H[i]: 빈 slot, H[j]: 이사해야할 slot 74 | while True: 75 | H[i] = None 76 | while True: # H[j] 찾기 77 | j = (j + 1) % m 78 | if H[j].is_occupied: return key 79 | k = f(H[j].key) 80 | if (k < i <= j):break 81 | H[i] = H[j] 82 | i = j 83 | ``` 84 | 85 | ### 성능 86 | 87 | - 성능은 클러스터의 길이에 비례한다. 88 | - 클러스터의 길이를 결정하는 것은 해시 함수. 89 | - 충돌 해소를 어떤 방식으로 하는지 여부 90 | -------------------------------------------------------------------------------- /notes/lecture19-hash03.md: -------------------------------------------------------------------------------- 1 | # 해시 테이블 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 19강 "[해시 테이블](https://youtube.com/watch?v=ghjWopXXUeA)" 강의를 정리한 노트입니다. 4 | 5 | ## Open addressing 6 | 7 | - linear probing: 해시 충돌시 한칸씩 내려가는 방식 8 | - quadratic probing: k + 12, k + 22, k + 32 순으로 내려가는 방식. remove 함수가 복잡해진다. 9 | - double hashing: 해시 함수를 두 개 쓰는 방식. f(key) -> f(key) + g(key) -> f(key) + 2g(key).. 순으로 내려간다. 10 | 11 | ## 성능 평가 12 | 13 | - set, remove, search: 클러스터 사이즈의 영향을 직접적으로 받는다. 클러스터 사이즈는 해시 함순 충돌 해결 방식에 영향을 받는다. 또한, 로드 팩터(load factor)의 영향도 받는다. 14 | 15 | 로드 팩터 확인 16 | 17 | - load factor: n / m (M: |H|=slot갯수, n: H에 저장된 item 갯수) 18 | - 0 <= n/m < 1 19 | - load factor가 1에 가까울수록 더 많은 연산이 필요하다. 20 | 21 | 충돌 비율 확인 22 | 23 | - (collision 횟수) / n = 충돌 비율 24 | 25 | 오픈 어드레싱 방식은 평균적으로 `m >= 2n`(최소 50% 이상 빈 슬롯이라면) 데이터 이사비용을 합치더라도 클러스터의 평균 사이즈가 O(1)이 되도록 유지할 수 있다. 26 | 27 | ## Chaining 28 | 29 | 하나의 슬롯에 여러 개의 아이템을 저장하는 방식. 각 슬롯에 한방향 연결리스트를 만든다. 꼭 한방향일 필요는 없다. 30 | 31 | `set` 함수가 항상 O(1) 시간 보장. `search`, `remove` 함수는 각 슬롯내의 노드 갯수만큼 O(n) 시간. O(충돌 key의 평균 갯수) 32 | 33 | c-universal 해시 함수를 사용하면 `set`, `search`, `remove`가 O(1). C-universal 해시 함수는 c/n만큼의 충돌을 보장하는 함수. 34 | 35 | ## 결론 36 | 37 | 해시 함수는 C-universal을 쓰고 충분한 로드 팩터를 확보하면 상수시간 내에 `set`, `remove`, `search`를 수행할 수 있다. 38 | -------------------------------------------------------------------------------- /notes/lecture20-tree.md: -------------------------------------------------------------------------------- 1 | # 트리(tree) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 20강 "[트리(tree)](https://youtube.com/watch?v=w-1w4ood7Bc)" 강의를 정리한 노트입니다. 4 | 5 | ## 순차적 자료구조 6 | 7 | 배열, 인덱스, i = 0, 1..., 연결리스트 링크(next, prev) 등이 있는 자료구조. 8 | 9 | ## 트리 10 | 11 | 부모, 자식 노드를 갖고 데이터를 표현하는 자료구조. 자식이 하나만 있거나 없는 경우를 연결리스트라고 부른다. 연결리스트는 트리의 특별한 유형이라고 할 수도 있다. 12 | 13 | 자식이 최대 두 개인 트리를 이진 트리(binary tree)라고 부른다. 삼진 트리도 가능하지만 이진 트리가 가장 간단하고 많이 쓰이는 구조이다. 14 | 15 | - 노드: 각각의 요소 16 | - 링크: 노드를 연결하는 선, 엣지라고 부르기도 한다. 17 | - 루트 노드: 최고의 조상 18 | - 리프 노드(leaf node): 자식이 하나도 없는 노드 19 | - 레벨: 루트 노드가 레벨 0. 루트로부터 각각 레벨 1, 2, 3... 20 | - 높이: 루트부터 리프까지 가장 깊은 레벨까지의 통과하는 노드의 수. 레벨 수 == 트리 높이 21 | - 경로(path): 노드 v에서 w까지 거치는 노드들. 출발노드-경유노드-도착노드 22 | - 경로길이(path length): 경로에 포함되는 링크(엣지)의 개수 23 | - 자식(child) 노드와 부모(parent) 노드, 형제 노드 24 | 25 | ## 구현 26 | 27 | ```txt 28 | a 29 | / \ 30 | b c 31 | \ / \ 32 | d e f 33 | ``` 34 | 35 | - 표현법 1) 리스트: `A = [a, b, c None, d, e, f, None, None, h, i, g]` 36 | - 표현법 2) 리스트(재귀적): `[a, [ [b, [], [d, [], []] ], [c, [e, [], []] [f, [], [] ]]]` 37 | - 표현법 3) 노드 클래스를 이용: 다음에 다시 살펴볼 것 38 | -------------------------------------------------------------------------------- /notes/lecture21-heap01.md: -------------------------------------------------------------------------------- 1 | # 힙(heap) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 21강 "[힙(heap)](https://youtube.com/watch?v=8XnPN6IB22YU)" 강의를 정리한 노트입니다. 4 | 5 | 힙 성질(heap property) 만족하는 이진트리 6 | 7 | ```txt 8 | a - 0 level 9 | / \ 10 | b c - 1 11 | \ / \ 12 | d e f - 2 13 | ``` 14 | 15 | ```py 16 | H = [a, b, c, None, d, e, f] 17 | # ^ ^--^ ^----------^ 18 | # 0 1 2 19 | ``` 20 | 21 | - H[0]의 왼쪽 자식노드 H[1] 22 | - H[0]의 오른쪽 자식노드 H[2] 23 | - H[2]의 왼쪽 자식노드 H[2 * 2 + 1] = H[5] = e 24 | - H[2]의 오른쪽 자식노드 H[2 * 2 + 2] = H[6] = f 25 | 26 | > H[k]의 왼쪽 자식노드: H[2 * k + 1] 27 | > 28 | > H[k]의 오른쪽 자식노드: H[2 * k + 2] 29 | > 30 | > H[k]의 부모노드: H[(k - 1) // 2] 31 | 32 | 모두 상수시간에 계산 가능. 단점은 비어있는 곳을 None으로 표시하기 위해 자리를 차지하는 불필요한 공간이 생긴다. 트레이드오프. 레벨을 꽉꽉 채워주면 이 낭비를 없앨 수 있다. 꽉꽉 채워둔 이진트리를 힙이라고 부른다. 33 | 34 | ## 예시 35 | 36 | ```py 37 | A = [2,8,6,1,10,15,3,12,11] 38 | ``` 39 | 40 | 위 리스트를 이진트리로 변환하면 41 | 42 | ```txt 43 | 2 44 | / \ 45 | 8 6 46 | / \ / \ 47 | 1 10 15 3 48 | / \ 49 | 12 11 50 | ``` 51 | 52 | 힙을 만족하려면, 53 | 54 | - 모양: 레벨마다 꽉 채워진 상태 55 | - 힙 성질: 모든 부모노드의 키값은 자식노드의 키값보다 작지 않다. 56 | 57 | 위의 이진트리는 힙 성질을 만족하지 못 한다. 58 | 59 | 아래 노드는 힙 성질을 만족한다. 아래 A는 힙이다. 60 | 61 | ```py 62 | A = [15,12,6,11,10,2,3,1,5] 63 | ``` 64 | 65 | ```txt 66 | 15 67 | / \ 68 | 12 6 69 | / \ / \ 70 | 11 10 2 3 71 | / \ 72 | 1 5 73 | ``` 74 | 75 | 힙의 특징: 힙의 루트노드에는 가장 큰 값이 들어있다. A[0]가 가장 큰 값. 76 | 77 | ## 힙의 연산 78 | 79 | 1. `insert` # O(logN) 80 | 2. `find_max`: `return A[0]` # O(1) 81 | 3. `delete_max`: # O(logN) 82 | 83 | `make_heap`: 힙 성질을 만족하도록 요소를 재배치 하는 함수 84 | -------------------------------------------------------------------------------- /notes/lecture22-heap02.md: -------------------------------------------------------------------------------- 1 | # 힙(Heap) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 22강 "[힙(Heap)](https://youtube.com/watch?v=6VMSTOdHRfI)" 강의를 정리한 노트입니다. 4 | 5 | ```py 6 | A = [2,8,6,1,10,15,3,12,11] 7 | ``` 8 | 9 | ```txt 10 | 2 11 | / \ 12 | 8 6 13 | / \ / \ 14 | 1 10 15 3 15 | / \ 16 | 12 11 17 | ``` 18 | 19 | 위와 같은 힙 성질을 만족하지 않는 리스트를 힙 성질을 만족하도록 변경하는 것을 make_heap이라고 한다. 이를 위해서는 `heapify_down`을 반복적으로 수행 20 | 21 | 1, 12, 11을 보면, 22 | 23 | - 1: A[3] 24 | - 12: A[3 * 2 + 1] = A[7] 25 | - 11: A[3 * 2 + 2] = A[8] 26 | 27 | 셋중 12가 가장 크므로, 1과 12를 바꾼다. A[3]과 A[7]을 바꾸면, 28 | 29 | ```py 30 | A = [2,8,6,12,10,15,3,1,11] 31 | ``` 32 | 33 | 6, 15, 3을 보면, 15가 가장 크므로 6과 15를 바꾼다. 34 | 35 | ```py 36 | A = [2,8,15,12,10,6,3,1,11] 37 | ``` 38 | 39 | 8, 12, 10을 보면 12가 가장 크므로 8과 12를 바꾼다. 40 | 41 | ```py 42 | A = [2,12,15,8,10,6,3,1,11] 43 | ``` 44 | 45 | 8, 1, 11을 보면 11이 가장 크므로 8과 11을 바꾼다. 46 | 47 | ```py 48 | A = [2,12,15,11,10,6,3,1,8] 49 | ``` 50 | 51 | A[0]인 2를 자기자리를 찾으려면 2, 15를 바꾸고, 또 6과 2를 바꾼다. 52 | 53 | ```py 54 | A = [15,12,6,11,10,2,3,1,8] 55 | ``` 56 | 57 | 이렇게 하면 힙 성질을 만족한다. 58 | 59 | 결론: 자식노드와 비교하여 가장 큰 값을 부모 노드 자리로 옮기면 make_heap이 된다. 60 | 61 | ```py 62 | make_heap(A): 63 | n = len(A) 64 | for k in range(n-1, -1, -1): 65 | #A[k] -> heap 성질 만족하는 곳으로 내려보낸다. 66 | heapify_down(k, n) 67 | ``` 68 | 69 | ```py 70 | heapify_down(k, n): 71 | # A[k], n값 72 | while A[k] != leaf: 73 | L, R = 2*k+1, 2*k+2 74 | m = index_max(A[k], A[L], A[R]) 75 | if k != m: 76 | # A[K], A[m]을 swap 77 | else: 78 | break 79 | ``` 80 | 81 | ## 성능 82 | 83 | `heapify_down`은 `A[k]`를 `n`번 부른다. 상수 연산을 루트로부터 가장 깊은 리프노드까지 가는, 트리의 높이(height)만큼 반복하면 최악의 케이스이다. 84 | 85 | > `T = O(nh) # h = height` 86 | 87 | n개의 노드를 가진 힙의 높이 h는? 88 | 89 | - level0 : 1 90 | - level1 : 2 91 | - level2 : 22 92 | - level3 : 23 93 | - level h - 1 : 2h - 1 94 | - level h : < 2h 95 | 96 | 다 더하면 1 + 2 + 2^2 +..... + 2^(k-1) + 1 <= n 97 | 98 | 1 + 2 + 2^2 +.....+ 2^(k-1) = (2h -1 / 2 - 1) + 1 = 2h <= n 99 | 100 | `h <= log2n 101 | 102 | - heapify_down: O(h) = O(logN) 103 | - make_heap: o(nh) = O(nlogN) (사실은 O(n) 시간에도 된다) 104 | -------------------------------------------------------------------------------- /notes/lecture23-heap03.md: -------------------------------------------------------------------------------- 1 | # 힙(Heap) 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 23강 "[힙의 insert 연산](https://youtube.com/watch?v=gVRDc5NRjjw)" 강의를 정리한 노트입니다. 4 | 5 | ## 힙의 insert 연산 6 | 7 | ```py 8 | A = [15,12,6,11,10,2,3,1,8] 9 | ``` 10 | 11 | ```txt 12 | 15 13 | / \ 14 | 12 6 15 | / \ / \ 16 | 11 10 2 3 17 | / \ 18 | 1 8 19 | ``` 20 | 21 | `insert(14)` -> 10의 왼쪽 끝에 들어가므로, 삽입하고 나면, 22 | 23 | ```py 24 | A = [15,12,6,11,10,2,3,1,8,14] 25 | ``` 26 | 27 | ```py 28 | insert(14) 29 | A.append(14) 30 | ``` 31 | 32 | 자리를 두번 바꿔야 한다. 33 | 34 | ```py 35 | A = [15,12,6,11,14,2,3,1,8,10] 36 | ``` 37 | 38 | ```py 39 | A = [15,14,6,11,12,2,3,1,8,10] 40 | ``` 41 | 42 | 14가 자기자리를 찾도록 재배치하면 전체적으로 힙이 완성된다. 부모노드로 가면서 자신의 자리를 찾도록 만드는 함수를 `heapify_up`이라고 부른다. 43 | 44 | ```py 45 | insert(14) 46 | A.append(14) 47 | A.heapify_up(9) 48 | # A[k]를 root 방향으로 이동하면서 heapify를 해준다. 49 | 50 | heapify(k): 51 | while k > 0 and A[(k -1)//2)] < A[k]: 52 | A[k], A[(k-1) //2] = A[(k-1) //2], A[k] 53 | k = (k - 1) // 2 54 | ``` 55 | 56 | 최악의 경우 리프에서 루트까지 연산. `insert`와 `heapify_up` 모두 h = O(logN). 57 | 58 | ## 힙의 find_max 연산 59 | 60 | ```py 61 | A = [15,12,6,11,10,2,3,1,8] 62 | ``` 63 | 64 | ```txt 65 | 15 66 | / \ 67 | 12 6 68 | / \ / \ 69 | 11 10 2 3 70 | / \ 71 | 1 8 72 | ``` 73 | 74 | ```py 75 | find_max: 76 | return A[0] # O(1) 77 | ``` 78 | 79 | ## 힙의 delete_max 연산 80 | 81 | A[0] 을 없애면서 가장 마지막 리프노드를 루트로 옮긴다. 그 후 A[0]를 `heapify_down(0, n)` 해준다. 82 | 83 | ```txt 84 | 15 85 | / \ 86 | 12 6 87 | / \ / \ 88 | 11 10 2 3 89 | / \ 90 | 1 8 91 | ``` 92 | 93 | ```py 94 | delete_max: # O(logN) 95 | if len(A) == 0: return None 96 | key = A[0] 97 | A[0], A[len(A) - 1] = A[len(A) - 1], A[0] 98 | A.pop() 99 | heapify_down(0, len(A)) 100 | return key 101 | ``` 102 | 103 | ### 성능 104 | 105 | - `make_heap`: O(n), O(nlogN) 106 | - `insert`: O(logN) 107 | - `find_max`: O(1) 108 | - `delete_max`: O(logN) 109 | - `heapify_down`: O(h) = O(logN) 110 | - `heapify_up`: O(h) = O(logN) 111 | 112 | ### 기타 113 | 114 | 힙에는 `search` 함수가 없다. 정리가 되어 있는 게 아니다보니 알 방법이 없어서 무조건 모두 탐색해야한다. `search`는 효율이 없고 `insert`, `find_max`, `delete_max`를 이용하는 경우에 힙이 좋다. 115 | 116 | `find_min`과 `delete_min`을 갖는 힙인 min_heap을 만들 수 있다. 우리가 만든 힙은 max_heap이다. 117 | 118 | 정렬을 하는 경우, 힙 소트를 할 수 있다. `make_heap()`은 O(nlogN)이므로 정렬 후 `delete_max()`의 반환과 유사한 형식을 이용해 할 수 있다. (자세한 내용은 나중에 다시 확인할 것!) 119 | -------------------------------------------------------------------------------- /notes/lecture24-binary-tree.md: -------------------------------------------------------------------------------- 1 | # 이진트리 - 정의와 순회 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 24강 "[이진트리 - 정의와 순회](https://www.youtube.com/watch?v=HDjqrmmpFdU)" 강의를 정리한 노트입니다. 4 | 5 | ## 이진트리(Binary Tree) 6 | 7 | 이진트리의 표현법 8 | 9 | - 배열과, 리스트를 이용한 표현법 10 | - 노드와 링크를 클래스로 직접 표현. Node 클래스와 BT class를 이용한 표현법 11 | 12 | ### 클래스를 이용한 표현 13 | 14 | ```py 15 | class Node: 16 | def __init__(self, key): 17 | self.key = key 18 | self.parent = self.left = self.right = None 19 | def __str__(self): 20 | return str(self.key) 21 | ``` 22 | 23 | 사용 예시 24 | 25 | ```py 26 | a = Node(6) 27 | b = Node(9) 28 | c = Node(1) 29 | d = Node(5) 30 | a.left = b 31 | a.right = c 32 | b.parent = c.parent = a 33 | b.left = d 34 | d.paren = b 35 | ``` 36 | 37 | ```txt 38 | 6 39 | / \ 40 | 9 1 41 | \ 42 | 5 43 | ``` 44 | 45 | ### 이진트리 순회(traversal) 46 | 47 | 이진트리의 노드를 빠짐없이 출력하는 세가지 방법 48 | 49 | ```txt 50 | F 51 | / \ 52 | B G 53 | / \ \ 54 | A D I 55 | /\ / 56 | C E H 57 | ``` 58 | 59 | - preorder(MLR): F B A D C E G I H 60 | - inorder(LMR): A B C D E F G H I 61 | - postorder(LRM): A C E D B I H G F 62 | 63 | M: 현재 노드, L: 좌측 서브트리, R: 우측 서브트리 64 | 65 | ### 구현 66 | 67 | ```py 68 | class Node: 69 | def __init__(self, key): 70 | self.key = key 71 | self.parent = self.left = self.right = None 72 | def __str__(self): 73 | return str(self.key) 74 | def preorder(self): #현재 방문중인 노드 == self 75 | if self != None: # MLR 76 | print(self.key) 77 | if self.left: 78 | self.left.preorder() 79 | if self.right: 80 | self.right.preorder() 81 | def inorder(self): 82 | if self != None: # LMR 83 | if self.left: 84 | self.left.inorder() 85 | print(self.key) 86 | if self.right: 87 | self.right.inorder() 88 | def postorder(self): 89 | if self != None: # LRM 90 | if self.left: 91 | self.left.postorder() 92 | if self.right: 93 | self.right.postorder() 94 | print(self.key) 95 | ``` 96 | 97 | ### reconstruct 98 | 99 | `preorder`, `inorder`, `postorder` 중 두 개 이상의 출력된 결과를 기반으로 이진트리의 모양을 추측할 수 있다. 100 | -------------------------------------------------------------------------------- /notes/lecture25-binary-search-tree01.md: -------------------------------------------------------------------------------- 1 | # 이진탐색트리 - 정의와 탐색, 삽입 연산 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 25강 "[이진탐색트리 - 정의와 탐색, 삽입 연산](https://www.youtube.com/watch?v=Bhprzw_1kb0)" 강의를 정리한 노트입니다. 4 | 5 | ## 이진탐색트리 6 | 7 | 특정 키 값을 찾는 연산을 효율적으로 할 수 있도록 조직화한 트리 8 | 9 | 이진탐색트리의 정의 10 | 11 | - 각 노드의 왼쪽 서브트리의 key 값은 노드의 key 값보다 작거나 같아야 한다. 12 | - 각 노드의 오른쪽 서브트리의 key 값은 노드의 key 값보다 커야 한다. 13 | 14 | 이진탐색트리의 예 15 | 16 | ```txt 17 | 15 18 | / \ 19 | 4 20 20 | / / \ 21 | 2 17 32 22 | \ 23 | 19 24 | ``` 25 | 26 | 모든 노드에 대해서 위 조건이 만족되어 있어야 한다. 27 | 28 | - 15의 왼쪽 노드: 2, 4 29 | - 15의 오른쪽 노드: 20, 17, 32, 19 30 | - 20의 왼쪽 노드: 17, 19 31 | - 20의 오른쪽 노드: 32 32 | 33 | 이렇게 구성하면 현재의 key 값을 기준으로 검색해야할 노드의 방향을 결정할 수 있다. 34 | 35 | 탐색 시 level by level로 내려가므로 위 예시에서는 height가 3이고, 수행시간은 O(h)이다. 36 | 37 | ## 구현 38 | 39 | ### BST 클래스 40 | 41 | ```py 42 | class BST: 43 | def __init__(self): 44 | self.root = None 45 | self.size = 0 46 | def __len__(self): 47 | return self.size 48 | def __iter__(self): 49 | return self.root.__iter__() # Node클래스에 구현된대로 방문. 50 | # generator에 대해 별도 학습할 것 51 | ``` 52 | 53 | 사용 예시 54 | 55 | ```py 56 | T = BST() 57 | T.insert(15) # 아직은 구현 전인 함수 58 | T.insert(4) 59 | ``` 60 | 61 | ### 탐색 62 | 63 | `find_loc()` 64 | 65 | ```py 66 | def find_loc(self, key): 67 | if self.size == 0: return None 68 | p = None # p is parent of v 69 | v = self.root 70 | while v: # while v != None 71 | if v.key == key: return v 72 | elif v.key < key: 73 | p = v 74 | v = v.right 75 | else: 76 | p = v 77 | v = v.left 78 | retern p 79 | ``` 80 | 81 | `search()` 82 | 83 | ```py 84 | def search(self, key): 85 | v = self.find_loc(key) 86 | if v == None: 87 | return None 88 | else: 89 | return v 90 | ``` 91 | 92 | ### 삽입 93 | 94 | - 탐색트리는 규칙을 갖고 있으므로 key가 들어갈 위치가 정해진다. 95 | 96 | `insert()` 97 | 98 | ```py 99 | def insert(self, key): 100 | p = self.find_loc(key) # find_loc() 은 O(h) 101 | if p == None or p.key != key: 102 | v = Node(key) 103 | if p == None: 104 | self.root = v 105 | else: 106 | v.parent = p 107 | if p.key >= key: # left/right 108 | p.left = v 109 | else: 110 | p.right = v 111 | self.size = self.size + 1 112 | return v 113 | else: 114 | print("key is already in the tree") 115 | return p # 중복 key를 허용하지 않으면 None 리턴 p == None 116 | ``` 117 | 118 | `insert()` 함수는 O(h) 119 | -------------------------------------------------------------------------------- /notes/lecture26-binary-search-tree02.md: -------------------------------------------------------------------------------- 1 | # 이진탐색트리 - 삭제 연산 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 26강 "[이진탐색트리 - 삭제 연산](https://www.youtube.com/watch?v=VVhmgQIJCu8)" 강의를 정리한 노트입니다. 4 | 5 | ## 삭제 연산 6 | 7 | 삭제 연산의 두 종류 8 | 9 | - deleteByMerging 10 | - deleteByCopying 11 | 12 | ### deleteByMerging 13 | 14 | 강의에서는 deleteByMerging을 살펴 본다. 15 | 16 | x노드를 지울 때 x의 왼쪽노드를 L, 오른쪽 노드를 R이라고 했을 때, x 자리에 L을 두고, L의 가장 큰 노드의 자식 노드로 R을 연결하는 방식이다. 17 | 18 | 경우들: 19 | 20 | 1. L == None : R을 x 로 대체한다. 21 | 2. x == root : root 노드를 업데이트 해줘야 한다. 22 | 23 | ```py 24 | deleteByMerging(self, x): # 노드 x 를 삭제 25 | a = x.left, b = x.right 26 | pt = x.parent 27 | # c == x를 대체할 노드 28 | # m == L에서 가장 큰 노드 29 | if a != None: 30 | c = a 31 | m = a 32 | while m.right: 33 | m = m.right 34 | if b != None: 35 | b.parent = m 36 | m.right = b 37 | else: # a == None 38 | c = b 39 | if pt != None: 40 | if c: c.parent = pt 41 | if pt.key < c.key: 42 | pt.right = c 43 | else: 44 | pt.left = c 45 | else: # pt == None (root == x) 46 | self.root = c 47 | if c: c.parent = None 48 | self.size -= 1 49 | # return은 다음에 다시 설명하기로 함 50 | ``` 51 | 52 | 강의 노트의 코드를 보면 충분히 이해할 수 있다. 53 | 54 | ### deleteByCopying 55 | 56 | x 노드를 지울 때 x의 왼쪽노드를 L, 오른쪽 노드를 R이라고 했을 때, x를 지우는 게 아니라 값을 복사하는 방식으로 변경해주는 방법. L에서 가장 큰 값(m)을 찾아서 x에 복사해준다. 원래 m의 자리에는 m의 왼쪽 서브트리가 들어온다. 57 | 58 | deletebyCopying은 직접 구현해볼 것. 59 | 60 | ### 삭제 연산의 수행 시간 61 | 62 | - deleteByMerging: O(h) 63 | - deleteByCopying: O(h) 64 | 65 | 두 함수 모두 m을 찾는데 드는 수행시간이 절대적으로 대부분을 차지. 66 | 67 | - insert 68 | - search(find_loc) 69 | - delete(merging, copying) 70 | 71 | 위 함수 모두 O(h) 시간이 든다. 72 | 73 | ```py 74 | # case 1 75 | insert(1) 76 | insert(2) 77 | insert(3) 78 | insert(4) 79 | 80 | # case 2 81 | insert(2) 82 | insert(1) 83 | insert(3) 84 | insert(4) 85 | ``` 86 | 87 | 같은 키 값을 저장해도 height의 크기가 전혀 다를 수 있다. 이는 수행시간의 큰 차이를 만들기 때문에 어떤 순서로 키값이 삽입되는지와 상관없이 가능하면 작게 유지하기 위한 서치트리를 균형이진탐색트리(balanced binary search tree)라고 부른다. 88 | -------------------------------------------------------------------------------- /notes/lecture27-balanced-binary-search-tree01.md: -------------------------------------------------------------------------------- 1 | # 균형이진탐색트리(Balanced BST) - 정의와 회전 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 27강 "[균형이진탐색트리 - 정의와 회전](https://www.youtube.com/watch?v=Kuw0f3-E-Hw)" 강의를 정리한 노트입니다. 4 | 5 | ## 이진탐색트리 6 | 7 | - 각 노드 x의 좌측에는 x보다 작거나 같은 값이, 우측에는 x보다 큰 값이 있다는 조건을 만족해야 한다 8 | - 탐색, 삽입, 삭제에 O(height) 시간만큼 소요된다. 삽입, 삭제는 탐색의 비용에 영향을 받는다. 9 | - 비효율적으로(연결리스트처럼) 저장되면 "h <= n - 1"이 될 수 있다. 10 | 11 | ```txt 12 | 1 13 | \ 14 | 10 15 | / 16 | 8 17 | / 18 | 5 19 | \ 20 | 7 21 | ``` 22 | 23 | 반면, 힙의 높이는 항상 "log(n+1) <= h <= n - 1"을 보장. 24 | 25 | ## 균형이진탐색트리 26 | 27 | 힙처럼 항상 트리의 높이가 O(logN) 정도로 보장이 되는 트리를 균형이진탐색트리라고 한다. 28 | 29 | ### 균형이진탐색트리의 예 30 | 31 | - AVL트리 32 | - 레드블랙(Red-Black) 트리 33 | - (2,3,4)-트리 34 | - 스프레이(splay) 트리 35 | 36 | ### 균형을 맞추는 방법 37 | 38 | - 트리의 높이가 커질 때 트리를 조정해 항상 높이를 O(logN) 정도로 유지 39 | - 이때 사용하는 함수가 회전(Rotation) 40 | 41 | ```txt 42 | z <-------- x 43 | / \ left / \ 44 | x c [rotation] a z 45 | / \ right / \ 46 | a b ---------> b c 47 | ``` 48 | 49 | ### 균형이진탐색트리의 회전 50 | 51 | 우측 회전 52 | 53 | - 왼쪽의 일부가 높이 레벨 1만큼 증가하고, 오른쪽의 일부가 높이 레벨 1만큼 감소한다 54 | - x: level 1 증가, z: level 1 감소 55 | - 총 6개의 링크를 변경해야 한다 56 | 57 | 좌측 회전 58 | 59 | - 왼쪽의 일부가 높이 레벨 1만큼 감소하고, 오른쪽의 일부가 높이 레벨 1만큼 증가한다 60 | - x: level 1 감소, z: level 1 증가 61 | - 총 6개의 링크를 변경해야 한다 62 | 63 | ### 회전 구현 64 | 65 | ```py 66 | def rotateRight(self, z): 67 | if not z: return 68 | x = z.left 69 | if x == None: return 70 | b = x.right 71 | x.parent = z.parent # 1st update 72 | if z.parent: 73 | if z.parent.left == z: 74 | z.parent.left = x # 2nd update 75 | else: 76 | z.parent.right.x 77 | x.right = z # 3rd update 78 | z.parent = x # 4th update 79 | x.left = b # 5th update 80 | if b: 81 | b.parent = z # 6th update 82 | if self.root == z: 83 | self.root = x 84 | ``` 85 | 86 | `rotateLeft`는 `rotateRight`에 대칭적으로 구현하면 된다. 87 | 88 | ### 회전 연산의 수행 시간 89 | 90 | 균형이진탐색트리의 회전은 O(1) 시간이 든다. 91 | -------------------------------------------------------------------------------- /notes/lecture28-balanced-binary-search-tree02.md: -------------------------------------------------------------------------------- 1 | # 균형이진탐색트리(Balanced BST) - AVL 트리 정의 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 28강 "[균형이진탐색트리 - AVL 트리 정의](https://www.youtube.com/watch?v=dHHjrl6m5CE)" 강의를 정리한 노트입니다. 4 | 5 | ## AVL(Adelson-Velsky, Londis) 트리 개요 6 | 7 | - 모든 노드에 대해서, 노드의 왼쪽 서브트리와 오른쪽 서브트리의 높이차가 1 이하인 BST 8 | - 1964년에 발표 9 | 10 | ```txt 11 | 10 12 | / \ 13 | 3 12 14 | / \ 15 | 1 5 16 | / \ 17 | 4 7 18 | ``` 19 | 20 | 10을 기준으로, '왼쪽 서브트리의 높이'는 2, '오른쪽 서브트리의 높이'는 0이며, 이 경우에는 AVL 트리의 조건을 만족하지 못한다. 반면 3을 기준으로 보면 좌우 서브트리의 높이가 각각 0, 1로 AVL 트리의 조건을 만족한다. 12를 기준으로 했을 때 역시 좌우가 None이므로 조건을 만족한다(리프 노드는 모두 조건을 만족한다). 21 | 22 | 만약 12 노드 좌측 하단 노드에 11이 있다면 AVL 트리의 조건을 만족한다. 23 | 24 | ```txt 25 | # 높이(h)가 0인 AVL트리 (자식 노드가 하나도 없는 트리) : 1개 유형 26 | 27 | # h = 1: 3개 유형 28 | 29 | node node node 30 | / \ / \ 31 | node node node node 32 | 33 | # h = 2(아래 모양에서 유형별로 만들어 볼 것) 34 | 35 | node 36 | / \ 37 | node node 38 | / \ / \ 39 | node node node node 40 | ``` 41 | 42 | ## AVL 트리의 최소 노드 갯수 Nh 43 | 44 | 위 경우에서 노드수가 가장 적은 경우들을 보면, 45 | 46 | - h = 0일 때, 노드 수는 1개 47 | - h = 1일 때, 노드 수는 2개 48 | - h = 2일 때, 노드 수는 4개 49 | 50 | AVL 노드에서 각 서브트리가 최소의 노드를 가진 경우를 본다면, 51 | 52 | h = 3 일때, 최소 노드를 가진 경우를 만들려고 하면 좌우 서브트리의 노드가 최소여야 한다. 53 | 54 | - 좌측(또는 우측) 서브트리는 h = 2, 따라서 최소 노드 갯수는 4개 55 | - 우측(또는 좌측) 서브트리는 h = 1, 최소 노드 갯수는 2개 56 | 57 | Nh: 높이가 h인 AVL 트리 중에서 최소 노드 갯수 58 | 59 | - N0 = 1 60 | - N1 = 2 61 | - N2 = 4 62 | - N3 = 7 63 | 64 | ## AVL 트리의 탐색 시간 65 | 66 | 그림을 그려서 점화식을 얻어보면, 67 | 68 | > Nh = Nh-1 + Nh-2 + 1 69 | 70 | 위의 점화식으로부터, 71 | 72 | > Nh >= 2Nh-2 + 1 73 | > 74 | > Nh >= Nh-2 75 | > 76 | > Nh >= 2h/2 * N0 77 | > 78 | > Nh >= 2h/2 79 | 80 | 높이가 h이고 노드 갯수가 n인 AVL트리를 가정했을 때, 81 | 82 | "h <= c·log2N"을 증명 83 | 84 | ```txt 85 | N >= Nh >= 2h/2 86 | h / 2 <= log2N 87 | h <= 2·log2N 88 | 89 | ∴ h = O(logN) 90 | ``` 91 | -------------------------------------------------------------------------------- /notes/lecture29-balanced-binary-search-tree03.md: -------------------------------------------------------------------------------- 1 | # 균형이진탐색트리(Balanced BST) - AVL 트리 삽입 연산 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 29강 "[균형이진탐색트리 - AVL 트리 삽입 연산](https://youtube.com/watch?v=KkgN2xAzmG8)" 강의를 정리한 노트입니다. 4 | 5 | ## AVL(Adelson-Velsky, Londis) 트리 구현 6 | 7 | ### AVL 트리에 새 노드 삽입 8 | 9 | - AVL 트리: 모든 노드에 대해서, 노드의 왼쪽 서브트리와 오른쪽 서브트리의 높이차 <= 1 10 | 11 | - class Node: BST 와 동일 <- 각 노드에는 key, left, right, parent에 height 변수 추가 12 | - class BST: 사용. insert, deleteByMerging, deleteByCopying, search 13 | - class AVL(BST) 14 | 15 | > insert, delete 함수는 연산에 따라 height가 변하므로 수행시 height 정보를 업데이트 해야 한다. 16 | 17 | ```py 18 | class AVL(BST): 19 | def insert(self, key): 20 | super(AVL, self).insert(key) 21 | v = super(AVL, self).insert(key) # step.1 22 | . 23 | . 24 | ``` 25 | 26 | - `AVL` 클래스에 `__init__`이 없으면 부모 클래스 `BST`의 생성자가 호출된다. 27 | - `insert`는 BST와 똑같이 동작한 후, 높이차 조건에 위배되는 경우에는 조치를 취한다. 28 | - 위배되는 경우를 바로잡는 것을 rebalance라고 한다. 29 | 30 | > `super` 키워드: 첫번째인자(클래스)의 객체(self)의 부모를 호출 31 | 32 | ```txt 33 | # insert(9) 34 | 35 | 5 <- z 36 | \ 37 | 7 <- y 38 | \ 39 | 9 <- x 또는 v 40 | ``` 41 | 42 | - 9: 삽입된 새로운 값. v라고 부름 43 | - 5: v로부터 올라가다가 AVL 트리의 조건을 만족하지 않는 첫번째 노드. z라고 부름 44 | - 7: z의 자식노드. y라고 부른다. y의 자식노드인 v는 x라고 부른다. 45 | 46 | > rebalance(x,y,z) 47 | 48 | z에서 밸런스가 깨졌을 때, y는 z의 자식, x는 y의 자식이다. 49 | 50 | ### insert 51 | 52 | > insert(self, key): 53 | 54 | 1. `v = super(AVL, self).insert(key)` 55 | 2. `find x, y, z` 56 | 3. `w = rebalance(x, y, z)`. w는 원래 z 위치에 있었던 노드 57 | 4. `if w.parent == Node: self.root = w` 58 | 59 | `insert` 이후의 두 가지 케이스 60 | 61 | 1. z, y, x가 왼쪽 자식으로만 구성되거나, 오른쪽 자식으로만 구성되는 경우 62 | 2. z, y, x가 일직선이 아니고 각각 다른 방향의 자식일 경우, 세 점이 삼각형 모양으로 되는 경우. 63 | 64 | ### rebalance 65 | 66 | 1. z, y, x가 왼쪽으로 구성된 경우: `rotateRight(z)` 67 | 2. z, y, x가 일직선이 아니고 각각 왼쪽자식, 오른쪽 자식일 경우: `rotateLeft(y)`, `rotateRight(z)` 68 | 69 | ### 수행시간 70 | 71 | 1. `v = super(AVL, self).insert(key)` -> O(h) = O(logN) 72 | 2. `find x, y, z` -> O(h) 73 | 3. `w = rebalance(x, y, z)` -> O(1) 74 | 4. `if w.parent == Node: self.root = w` -> O(1) 75 | 76 | 삽입에는 O(logN) 시간이 소요된다. 77 | -------------------------------------------------------------------------------- /notes/lecture30-balanced-binary-search-tree04.md: -------------------------------------------------------------------------------- 1 | # 균형이진탐색트리(Balanced BST) - AVL 트리 삭제연산 2 | 3 | > 본 포스팅은 한국외대 컴퓨터공학부 신찬수 교수님의 "[자료구조 - Data Structures with Python](https://www.youtube.com/playlist?list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK)" 중 30강 "[균형이진탐색트리 - AVL 트리 삭제연산](https://www.youtube.com/watch?v=W3uPlSCzAZM)" 강의를 정리한 노트입니다. 4 | 5 | ## AVL(Adelson-Velsky, Londis) 트리 구현 6 | 7 | ### AVL 트리의 노드 삭제 8 | 9 | ```txt 10 | # 1. u 삭제(불균형 상태) 11 | 12 | o 13 | / \ 14 | o z 15 | /| / \ 16 | oo y o 17 | || / \ / 18 | oo x o u 19 | || |\ 20 | oo oo 21 | || 22 | oo 23 | 24 | # 2. rotateRight(z) 25 | 26 | o 27 | / \ 28 | o y 29 | /| / \ 30 | oo x z 31 | || |\ / \ 32 | oo oo o o 33 | || 34 | oo 35 | || 36 | oo 37 | ``` 38 | 39 | x y z를 정할 때는 height가 높은 쪽으로 간다. `rotateRight(z)`는 z의 부모노드(이 경우 root)의 균형을 깨뜨리게 된다. 이렇게 균형을 맞추려 하면 계속 부모의 균형에 영향을 주므로, 부모로 올라가면서 계속 맞춰야 한다. 40 | 41 | `insert`의 경우에는 1회 또는 2회면 가능했으나 `delete`는 최악의 경우 모든 높이 레벨에서 다 회전을 수행해야 한다. O(logN) 회전이 필요할 수 있다. 42 | 43 | ### delete 구현 44 | 45 | ```py 46 | delete(self, u): 47 | v = super(AVL, self).deleteByCopying(u) 48 | # v는 u를 삭제함으로써 균형이 깨질 수 있는(가능성이 있는) 가장 깊은 곳의 노드 49 | # 앞에서 살펴본 케이스에서는 u의 부모였던 노드가 v로 반환 50 | # 반환된 v로부터 균형이 깨진 노드를 다시 찾음 51 | # 강의 노트도 참고할 것 52 | while v != None: 53 | if v is_not_balanced: 54 | z = v 55 | if z.left.height >= z.right.height: 56 | y = z.left 57 | else: 58 | y = z.right 59 | if y.left.height >= y.right.height: 60 | x = y.left 61 | else: 62 | x = y.right 63 | v = rebalance(x, y, z) # 64 | w = v # w는 예전에 v가 있었던 자리. 즉 v의 자식 65 | v = v.parent 66 | # while을 빠져나오면 v == None, w == root 67 | selft.root = w 68 | ``` 69 | 70 | ### delete 수행시간 71 | 72 | - 높이: <= 2logN : O(logN) 73 | - insert: O(logN) 74 | - 노드 삽입 : O(logN) 75 | - rebalance: 1회 또는 2회 회전 O(1) 76 | - delete 77 | - 노드 제거: O(logN) 78 | - rebalance: 매 level에서 O(logN) 회전 79 | 80 | AVL트리는 insert, delete 를 모두 O(logN)만에 수행하는 균형이진 탐색트리의 대표적인 예 중 하나이다. 81 | --------------------------------------------------------------------------------