diff --git a/out/reviews/R1.pdf b/out/reviews/R1.pdf index dfa8ed8..f13f5a7 100644 --- a/out/reviews/R1.pdf +++ b/out/reviews/R1.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c065ff2e25382d47e4f4da00b8a300d2278beb58133be92741fb45f134a6cb4 +oid sha256:5917bf8de35dd726270f9d4f69587adeeaa653d64094b8abdafa94fb8c0dc3f7 size 67664 diff --git a/out/reviews/R2.pdf b/out/reviews/R2.pdf index 04e3887..6e0b22e 100644 --- a/out/reviews/R2.pdf +++ b/out/reviews/R2.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:310782e2e3f81f66389d4b62336af534ef59b00536ee687f46a615a0aae1ef5b -size 102909 +oid sha256:f963ee931023c455b7e64a9b7543386f7a2e454ec7f365822fba4cc82e9e6b92 +size 102912 diff --git a/out/reviews/R3.pdf b/out/reviews/R3.pdf new file mode 100644 index 0000000..c1879ee --- /dev/null +++ b/out/reviews/R3.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a25a757ea83a7ae9d2e96ceaeca3bda302bf79fa14fbc75c04da928af1cb966b +size 71667 diff --git a/out/reviews/R4-1.pdf b/out/reviews/R4-1.pdf new file mode 100644 index 0000000..6050d51 --- /dev/null +++ b/out/reviews/R4-1.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60e1f742ab5b577b2aa00732f7a7ada178f1d1ff60d067c00db4a05a8b474b89 +size 64621 diff --git a/out/reviews/R4-2.pdf b/out/reviews/R4-2.pdf new file mode 100644 index 0000000..c82a804 --- /dev/null +++ b/out/reviews/R4-2.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2773e0067f9467fc508437cd8dd8405c36b84ba7715652a50012fb4a4cfbe5eb +size 36204 diff --git a/out/reviews/R5.pdf b/out/reviews/R5.pdf new file mode 100644 index 0000000..396e44e --- /dev/null +++ b/out/reviews/R5.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c10b6b712dbeaba1d1bb2baa3167208628c770c09e65d5a5af735a949f0ed67 +size 149208 diff --git a/reviews/R1.md b/reviews/R1.md index 154a460..1904537 100644 --- a/reviews/R1.md +++ b/reviews/R1.md @@ -1,9 +1,4 @@ # Review 1 - * Hajin Ju, 2024062806 diff --git a/reviews/R2.md b/reviews/R2.md index 0207660..f69875e 100644 --- a/reviews/R2.md +++ b/reviews/R2.md @@ -1,4 +1,4 @@ -# Review 1 +# Review 2 * Hajin Ju, 2024062806 diff --git a/reviews/R3.md b/reviews/R3.md new file mode 100644 index 0000000..397b835 --- /dev/null +++ b/reviews/R3.md @@ -0,0 +1,85 @@ +# Review 3 + +* Hajin Ju, 2024062806 + +## Problem 1 + +Write `O` if an entry is true or `X` otherwise. + +### Solution 1 + +| | $$O(n \lg n)$$ | $$\Omega(n \lg n)$$ | $$\Theta(n\lg n)$$ | +| :-----------: | :------------: | :-----------------: | :----------------: | +| $$\lg n$$ | O | X | X | +| $$n$$ | O | X | X | +| $$n \lg n$$ | O | O | O | +| $$n \lg^2 n$$ | X | O | X | +| $$n^2$$ | X | O | X | + + +## Problem 2 + +Show $3n + 1 = O(n^2)$ by the definition of $O$. + + +### Solution 2 + +A function $f(n) = O(g(n))$ if there exist constants $c\geq 0$ and $n_0\geq 0$, s.t. + +$$n \geq n_0 \Rightarrow \leq |f(n)| \leq c |g(n)|$$ + +--- + +let $g(n) = n^2$ +let $f(n) = 3n+1$ + +suppose $c=4,\, n_0 = 1$ + +and then, for all $n \geq 1 \to |3n+1| \leq 4n^2$ + +therefore, $3n+1 = O(n^2)$ by the above definition. + + +## Problem 3 + +Write asymptotic notations that satisfy each relation and explain why. + +1. Transitivity +2. Reflexivity +3. Symmetry + +### Solution 3 + +1. Transitivity + +* $O$ is transitive +because $f(n) = O(g(n))$ and $g(n) = O(h(n))$ implies $f(n) = O(h(n))$ +there must exists $n_0\geq 0$, s.t. $n \geq n_0 \Rightarrow f(n) \leq c_0 g(n) \leq c_1 c_0 h(n)$ + +* $\Omega$ is transitive +because $f(n) = \Omega(g(n))$ and $g(n) = \Omega(h(n))$ implies $f(n) = \Omega(h(n))$ +there must exists $n_0\geq 0$, s.t. $n \geq n_0 \Rightarrow f(n) \geq c_0g(n) \geq c_1 c_0 h(n)$ + +* $\Theta$ is transitive +because $f(n) = \Theta(g(n))$ and $g(n) = \Theta(h(n))$ implies $f(n) = \Theta(h(n))$ +$$f(n) = O(h(n)) \land f(n) = \Omega(h(n))$$ + +2. Reflexivity + +* $O$ is reflexive +because $f(n) = O(f(n))$ where $c=1$ +* $\Omega$ is reflexive +because $f(n) = \Omega(f(n))$ where $c=1$ +* $\Theta$ is reflexive +* because $f(n) = \Theta(f(n))$ + +3. Symmetry + +* $O$ is **not** symmetric +because $f(n) = O(g(n))$ does not imply $g(n) = O(g(n))$ +for example, $n = O(n^2)$ cannot imply $n^2 = O(n)$ +* $\Omega$ is **not** symmetric +because $f(n) = \Omega(g(n))$ does not imply $g(n) = \Omega(g(n))$ +for example, $n^2 = \Omega(n)$ cannot imply $n = \Omega(n^2)$ +* $\Theta$ is symmetric +because $f(n) = \Theta(g(n))$ implies $g(n) = \Theta(g(n))$ diff --git a/reviews/R4-1.md b/reviews/R4-1.md new file mode 100644 index 0000000..797202c --- /dev/null +++ b/reviews/R4-1.md @@ -0,0 +1,68 @@ +# Review 4-1 + +* Hajin Ju, 2024062806 + +## Problem 1 + +Show that the solution of $T(n) = 2T(\lfloor n / 2 \rfloor) + n $ is $O(n \lg n)$ by the substitution method. (Show the inductive step only.) + +### Solution 1 + +* inductive step + +$$T(n) \leq c n \lg n, \quad (\text{for some}\; c > 0,\, n > n_0)$$ + +We can assume the hypothesis($T(n) = O(n\lg n)$) holds for all positive int smaller than $n$. +then, + +$$T(\lfloor n / 2 \rfloor) \leq c \lfloor n / 2 \rfloor \lg {\lfloor n / 2 \rfloor} \leq c (n / 2) \lg ( n / 2)\\= c(n/2)(\lg n - \lg 2)$$ + +$$\begin{align*} +T(n) &= 2T(\lfloor n / 2 \rfloor) + n \leq cn(\lg n - \lg 2) + n\\ +&=cn\lg n - cn \lg 2 + n\\ +&=cn \lg - cn + n\\ +&\leq cn\lg n +\end{align*}$$ + +therefore, $T(n) = O(n \lg n)$ + +## Problem 2 + +Use a recursion tree to determine a good asymptotic upper bound on the recurrence $T(n) = 3T(\lfloor n / 4 \rfloor) + \theta(n^2)$. + +### Solution 2 +```mermaid +flowchart TD + A["$$T(n):\;cn^2$$"] --> B1["$$T(n/4):\;cn^2/16$$"]; + A --> B2["$$T(n/4):\;cn^2/16$$"]; + A --> B3["$$T(n/4):\;cn^2/16$$"]; + + subgraph L0[ ] + A + end + + subgraph L1[ ] + B1 + B2 + B3 + end +``` + +* level 0 + * $cn^2$ +* level 1 + * $\frac{3}{16}cn^2$ +* level $k$ + * $(\frac{3}{16})^k cn^2$ +* level $\log_4 n$ + * $n^{\log_4 3}$ + + +therefore, total cost is + +$$\begin{align*} +T(n) &= \sum^{\log_4 n - 1}_{i = 0}(\frac{3}{16})^i cn^2 + \Theta(n^{\log_4 3})\\ +&< \sum^{\infty}_{i = 0}(\frac{3}{16})^i cn^2 + \Theta(n^{\log_4 3})\\ +&=\frac{16}{13}cn^2 +\Theta(n^{\log_4 3}) = O(n^2) +\end{align*} +$$ diff --git a/reviews/R4-2.md b/reviews/R4-2.md new file mode 100644 index 0000000..3bb0573 --- /dev/null +++ b/reviews/R4-2.md @@ -0,0 +1,32 @@ +# Review 4-2 + +* Hajin Ju, 2024062806 + +## Problem 1 + +Guess the solution to the recurrence $T(n) = T(n/3) + T(2n/3) + cn$, where $c$ is constant, is $\Theta(n\lg n) by applealing to a recursion tree.$ + +### Solution 1 + +* level 0 +$\Sigma = cn$ +* level 1 +$\Sigma = cn/3 + 2cn/3 = cn$ +* level 2 +$\Sigma = cn/9 + 2cn/9 + 2cn/9 + 4cn/9 = cn$ +* level k +$\Sigma = cn$ +* level h +$\Sigma = cn$ + +The shortest depth $n\to 1$ is $h = \lg_{3/2}{n}$ +The longest depth $n\to 1$ is $h = \lg_{3}{n}$ + +so $T(n) = \text{depth} * cn$ and + +$cn \lg_{3/2}{n} \leq T(n) \leq cn \lg_{3}{n} $ + +therefore $T(n) = \Theta(n\lg n)$ + + + diff --git a/reviews/R5.md b/reviews/R5.md new file mode 100644 index 0000000..8dde71e --- /dev/null +++ b/reviews/R5.md @@ -0,0 +1,478 @@ +# Review 5 + +* Hajin Ju, 2024062806 + +## Problem 1 + +Illustrate the operation of `HEAPSORT` on the array $A = \set{5, 13, 2, 25, 7, 17, 20, 8, 4}$ + +### Solution 1 + +* BUILD-MAX-HEAP + +```python { cmd, output='html' hide } +from bs4 import BeautifulSoup +import math + +# --- SVG 생성을 위한 설정값 --- +CONFIG = { + 'canvas_padding': 10, + 'node_radius': 12, + 'font_size': 10, + 'x_spacing': 30, # 형제 노드 간 최소 수평 간격 + 'y_spacing': 40, # 세대 간 수직 간격 + 'stroke_width': 1.5, + 'colors': { + 'node_border': '#aaa', + 'node_bg': '#fff', + 'node_text': '#333', + 'line': '#ccc', + 'active_border': '#c0392b', + 'active_bg': '#f2d7d5', + } +} + +def calculate_positions(arr): + """2-Pass 알고리즘을 사용하여 노드 좌표를 계산합니다.""" + positions = {} + if not arr: + return {}, 0, 0 + + initial_y = CONFIG['canvas_padding'] + CONFIG['node_radius'] + + # 1단계: In-order 순회로 겹치지 않는 초기 x 좌표 할당 + x_next = 0 + def assign_initial_x(i): + nonlocal x_next + if i >= len(arr) or arr[i] is None: + return + + assign_initial_x(2 * i + 1) # 왼쪽 서브트리 + + y = int(math.log2(i + 1)) * CONFIG['y_spacing'] + initial_y + positions[i] = (x_next, y) + x_next += CONFIG['x_spacing'] + + assign_initial_x(2 * i + 2) # 오른쪽 서브트리 + + assign_initial_x(0) + + # 2단계: Post-order 순회로 부모 노드를 자식들의 중앙으로 재조정 + def center_parents(i): + if i >= len(arr) or arr[i] is None: + return + + left_child, right_child = 2 * i + 1, 2 * i + 2 + + center_parents(left_child) + center_parents(right_child) + + has_left = left_child < len(arr) and left_child in positions + has_right = right_child < len(arr) and right_child in positions + + # 자식이 있는 경우에만 부모 위치 조정 + if has_left and has_right: + # 자식이 둘 다 있으면 그 중앙으로 이동 + left_x = positions[left_child][0] + right_x = positions[right_child][0] + positions[i] = ((left_x + right_x) / 2, positions[i][1]) + elif has_left and not has_right: + # 왼쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[left_child][0], positions[i][1]) + elif not has_left and has_right: + # 오른쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[right_child][0], positions[i][1]) + + center_parents(0) + + # 맨 왼쪽 노드가 잘리지 않도록 전체 트리 이동 + if not positions: return {}, 0, 0 + min_x = min(p[0] for p in positions.values()) + x_shift = 0 + if min_x < CONFIG['canvas_padding'] + CONFIG['node_radius']: + x_shift = -min_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + shifted_positions = {i: (x + x_shift, y) for i, (x, y) in positions.items()} + + max_x = max(p[0] for p in shifted_positions.values()) + max_y = max(p[1] for p in shifted_positions.values()) + canvas_width = max_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + canvas_height = max_y + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + return shifted_positions, canvas_width, canvas_height + +def create_single_tree_svg(soup, step_data): + # (이전과 동일, 변경 없음) + arr = step_data.get('heap', []) + highlighted = step_data.get('highlighted', set()) + positions, width, height = calculate_positions(arr) + svg = soup.new_tag('svg', attrs={'width': width, 'height': height, 'xmlns': "http://www.w3.org/2000/svg"}) + for i in range(len(arr)): + if i not in positions: continue + px, py = positions[i] + left_child, right_child = 2 * i + 1, 2 * i + 2 + if left_child < len(arr) and left_child in positions: + cx, cy = positions[left_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + if right_child < len(arr) and right_child in positions: + cx, cy = positions[right_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + for i, val in enumerate(arr): + if i not in positions: continue + x, y = positions[i] + is_active = i in highlighted + svg.append(soup.new_tag('circle', attrs={'cx': x, 'cy': y, 'r': CONFIG['node_radius'], 'stroke': CONFIG['colors']['active_border'] if is_active else CONFIG['colors']['node_border'], 'stroke-width': CONFIG['stroke_width'], 'fill': CONFIG['colors']['active_bg'] if is_active else CONFIG['colors']['node_bg']})) + svg.append(soup.new_tag('text', attrs={'x': x, 'y': y, 'font-family': 'monospace', 'font-size': CONFIG['font_size'], 'text-anchor': 'middle', 'dominant-baseline': 'central', 'fill': CONFIG['colors']['node_text'], 'font-weight': 'bold' if is_active else 'normal'})) + svg.find_all('text')[-1].string = str(val) + return svg + +def create_heap_visualization_div(steps_data): + # (이전과 동일, 변경 없음) + soup = BeautifulSoup("", "html.parser") + wrapper_div = soup.new_tag("div") + style = soup.new_tag("style") + style.string = """.heap-svg-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; justify-content: flex-start; } .heap-svg-flow .arrow { font-size: 24px; color: #555; }""" + wrapper_div.append(style) + flow_container = soup.new_tag("div", attrs={"class": "heap-svg-flow"}) + wrapper_div.append(flow_container) + for i, step in enumerate(steps_data): + svg_tree = create_single_tree_svg(soup, step) + flow_container.append(svg_tree) + if i < len(steps_data) - 1: + arrow = soup.new_tag("div", attrs={"class": "arrow"}) + arrow.string = "→" + flow_container.append(arrow) + return wrapper_div.prettify() + +# --- 🚀 메인 실행 부분 --- +if __name__ == "__main__": + MANUAL_STEPS = [ + {"heap": [5, 13, 2, 25, 7, 17, 20, 8, 4], "highlighted": set()}, + {"heap": [5, 13, 2, 25, 7, 17, 20, 8, 4], "highlighted": {8, 7, 3}}, + {"heap": [5, 13, 20, 25, 7, 17, 2, 8, 4], "highlighted": {2, 5, 6}}, + {"heap": [5, 25, 20, 13, 7, 17, 2, 8, 4], "highlighted": {1, 3, 4, 7, 8}}, + {"heap": [25, 13, 20, 8, 7, 17, 2, 5, 4], "highlighted": {0, 1, 2, 3, 4, 5, 6, 7, 8}}, + ] + + html_output = create_heap_visualization_div(MANUAL_STEPS) + print(html_output) +``` + +* EXTRACT-MAX + +```python { cmd, output='html' hide } +from bs4 import BeautifulSoup +import math + +# --- SVG 생성을 위한 설정값 --- +CONFIG = { + 'canvas_padding': 10, + 'node_radius': 12, + 'font_size': 10, + 'x_spacing': 30, # 형제 노드 간 최소 수평 간격 + 'y_spacing': 40, # 세대 간 수직 간격 + 'stroke_width': 1.5, + 'colors': { + 'node_border': '#aaa', + 'node_bg': '#fff', + 'node_text': '#333', + 'line': '#ccc', + 'active_border': '#c0392b', + 'active_bg': '#f2d7d5', + } +} + +def calculate_positions(arr): + """2-Pass 알고리즘을 사용하여 노드 좌표를 계산합니다.""" + positions = {} + if not arr: + return {}, 0, 0 + + initial_y = CONFIG['canvas_padding'] + CONFIG['node_radius'] + + # 1단계: In-order 순회로 겹치지 않는 초기 x 좌표 할당 + x_next = 0 + def assign_initial_x(i): + nonlocal x_next + if i >= len(arr) or arr[i] is None: + return + + assign_initial_x(2 * i + 1) # 왼쪽 서브트리 + + y = int(math.log2(i + 1)) * CONFIG['y_spacing'] + initial_y + positions[i] = (x_next, y) + x_next += CONFIG['x_spacing'] + + assign_initial_x(2 * i + 2) # 오른쪽 서브트리 + + assign_initial_x(0) + + # 2단계: Post-order 순회로 부모 노드를 자식들의 중앙으로 재조정 + def center_parents(i): + if i >= len(arr) or arr[i] is None: + return + + left_child, right_child = 2 * i + 1, 2 * i + 2 + + center_parents(left_child) + center_parents(right_child) + + has_left = left_child < len(arr) and left_child in positions + has_right = right_child < len(arr) and right_child in positions + + # 자식이 있는 경우에만 부모 위치 조정 + if has_left and has_right: + # 자식이 둘 다 있으면 그 중앙으로 이동 + left_x = positions[left_child][0] + right_x = positions[right_child][0] + positions[i] = ((left_x + right_x) / 2, positions[i][1]) + elif has_left and not has_right: + # 왼쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[left_child][0], positions[i][1]) + elif not has_left and has_right: + # 오른쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[right_child][0], positions[i][1]) + + center_parents(0) + + # 맨 왼쪽 노드가 잘리지 않도록 전체 트리 이동 + if not positions: return {}, 0, 0 + min_x = min(p[0] for p in positions.values()) + x_shift = 0 + if min_x < CONFIG['canvas_padding'] + CONFIG['node_radius']: + x_shift = -min_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + shifted_positions = {i: (x + x_shift, y) for i, (x, y) in positions.items()} + + max_x = max(p[0] for p in shifted_positions.values()) + max_y = max(p[1] for p in shifted_positions.values()) + canvas_width = max_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + canvas_height = max_y + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + return shifted_positions, canvas_width, canvas_height + +def create_single_tree_svg(soup, step_data): + # (이전과 동일, 변경 없음) + arr = step_data.get('heap', []) + highlighted = step_data.get('highlighted', set()) + positions, width, height = calculate_positions(arr) + svg = soup.new_tag('svg', attrs={'width': width, 'height': height, 'xmlns': "http://www.w3.org/2000/svg"}) + for i in range(len(arr)): + if i not in positions: continue + px, py = positions[i] + left_child, right_child = 2 * i + 1, 2 * i + 2 + if left_child < len(arr) and left_child in positions: + cx, cy = positions[left_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + if right_child < len(arr) and right_child in positions: + cx, cy = positions[right_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + for i, val in enumerate(arr): + if i not in positions: continue + x, y = positions[i] + is_active = i in highlighted + svg.append(soup.new_tag('circle', attrs={'cx': x, 'cy': y, 'r': CONFIG['node_radius'], 'stroke': CONFIG['colors']['active_border'] if is_active else CONFIG['colors']['node_border'], 'stroke-width': CONFIG['stroke_width'], 'fill': CONFIG['colors']['active_bg'] if is_active else CONFIG['colors']['node_bg']})) + svg.append(soup.new_tag('text', attrs={'x': x, 'y': y, 'font-family': 'monospace', 'font-size': CONFIG['font_size'], 'text-anchor': 'middle', 'dominant-baseline': 'central', 'fill': CONFIG['colors']['node_text'], 'font-weight': 'bold' if is_active else 'normal'})) + svg.find_all('text')[-1].string = str(val) + return svg + +def create_heap_visualization_div(steps_data): + # (이전과 동일, 변경 없음) + soup = BeautifulSoup("", "html.parser") + wrapper_div = soup.new_tag("div") + style = soup.new_tag("style") + style.string = """.heap-svg-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; justify-content: flex-start; } .heap-svg-flow .arrow { font-size: 24px; color: #555; }""" + wrapper_div.append(style) + flow_container = soup.new_tag("div", attrs={"class": "heap-svg-flow"}) + wrapper_div.append(flow_container) + for i, step in enumerate(steps_data): + svg_tree = create_single_tree_svg(soup, step) + flow_container.append(svg_tree) + if i < len(steps_data) - 1: + arrow = soup.new_tag("div", attrs={"class": "arrow"}) + arrow.string = "→" + flow_container.append(arrow) + return wrapper_div.prettify() + +# --- 🚀 메인 실행 부분 --- +if __name__ == "__main__": + MANUAL_STEPS = [ + {"heap": [25, 13, 20, 8, 7, 17, 2, 5, 4], "highlighted": set()}, + {"heap": [20, 13, 17, 8, 7, 4, 2, 5], "highlighted": set()}, + {"heap": [17, 13, 5, 8, 7, 4, 2], "highlighted": set()}, + {"heap": [13, 8, 5, 2, 7, 4], "highlighted": set()}, + {"heap": [8, 7, 5, 2, 4], "highlighted": set()}, + {"heap": [7, 4, 5, 2], "highlighted": set()}, + ] + + html_output = create_heap_visualization_div(MANUAL_STEPS) + print(html_output) +``` + +``` +out 25 -> out 20 -> out 17 -> out 13 -> out 8 +``` + +* MAX-HEAP-INSERT(A, 15) + +```python { cmd, output='html' hide } +from bs4 import BeautifulSoup +import math + +# --- SVG 생성을 위한 설정값 --- +CONFIG = { + 'canvas_padding': 10, + 'node_radius': 12, + 'font_size': 10, + 'x_spacing': 30, # 형제 노드 간 최소 수평 간격 + 'y_spacing': 40, # 세대 간 수직 간격 + 'stroke_width': 1.5, + 'colors': { + 'node_border': '#aaa', + 'node_bg': '#fff', + 'node_text': '#333', + 'line': '#ccc', + 'active_border': '#c0392b', + 'active_bg': '#f2d7d5', + } +} + +def calculate_positions(arr): + """2-Pass 알고리즘을 사용하여 노드 좌표를 계산합니다.""" + positions = {} + if not arr: + return {}, 0, 0 + + initial_y = CONFIG['canvas_padding'] + CONFIG['node_radius'] + + # 1단계: In-order 순회로 겹치지 않는 초기 x 좌표 할당 + x_next = 0 + def assign_initial_x(i): + nonlocal x_next + if i >= len(arr) or arr[i] is None: + return + + assign_initial_x(2 * i + 1) # 왼쪽 서브트리 + + y = int(math.log2(i + 1)) * CONFIG['y_spacing'] + initial_y + positions[i] = (x_next, y) + x_next += CONFIG['x_spacing'] + + assign_initial_x(2 * i + 2) # 오른쪽 서브트리 + + assign_initial_x(0) + + # 2단계: Post-order 순회로 부모 노드를 자식들의 중앙으로 재조정 + def center_parents(i): + if i >= len(arr) or arr[i] is None: + return + + left_child, right_child = 2 * i + 1, 2 * i + 2 + + center_parents(left_child) + center_parents(right_child) + + has_left = left_child < len(arr) and left_child in positions + has_right = right_child < len(arr) and right_child in positions + + # 자식이 있는 경우에만 부모 위치 조정 + if has_left and has_right: + # 자식이 둘 다 있으면 그 중앙으로 이동 + left_x = positions[left_child][0] + right_x = positions[right_child][0] + positions[i] = ((left_x + right_x) / 2, positions[i][1]) + elif has_left and not has_right: + # 왼쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[left_child][0], positions[i][1]) + elif not has_left and has_right: + # 오른쪽 자식만 있으면 그 위로 이동 + positions[i] = (positions[right_child][0], positions[i][1]) + + center_parents(0) + + # 맨 왼쪽 노드가 잘리지 않도록 전체 트리 이동 + if not positions: return {}, 0, 0 + min_x = min(p[0] for p in positions.values()) + x_shift = 0 + if min_x < CONFIG['canvas_padding'] + CONFIG['node_radius']: + x_shift = -min_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + shifted_positions = {i: (x + x_shift, y) for i, (x, y) in positions.items()} + + max_x = max(p[0] for p in shifted_positions.values()) + max_y = max(p[1] for p in shifted_positions.values()) + canvas_width = max_x + CONFIG['canvas_padding'] + CONFIG['node_radius'] + canvas_height = max_y + CONFIG['canvas_padding'] + CONFIG['node_radius'] + + return shifted_positions, canvas_width, canvas_height + +def create_single_tree_svg(soup, step_data): + # (이전과 동일, 변경 없음) + arr = step_data.get('heap', []) + highlighted = step_data.get('highlighted', set()) + positions, width, height = calculate_positions(arr) + svg = soup.new_tag('svg', attrs={'width': width, 'height': height, 'xmlns': "http://www.w3.org/2000/svg"}) + for i in range(len(arr)): + if i not in positions: continue + px, py = positions[i] + left_child, right_child = 2 * i + 1, 2 * i + 2 + if left_child < len(arr) and left_child in positions: + cx, cy = positions[left_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + if right_child < len(arr) and right_child in positions: + cx, cy = positions[right_child] + svg.append(soup.new_tag('line', attrs={'x1': px, 'y1': py, 'x2': cx, 'y2': cy, 'stroke': CONFIG['colors']['line'], 'stroke-width': CONFIG['stroke_width']})) + for i, val in enumerate(arr): + if i not in positions: continue + x, y = positions[i] + is_active = i in highlighted + svg.append(soup.new_tag('circle', attrs={'cx': x, 'cy': y, 'r': CONFIG['node_radius'], 'stroke': CONFIG['colors']['active_border'] if is_active else CONFIG['colors']['node_border'], 'stroke-width': CONFIG['stroke_width'], 'fill': CONFIG['colors']['active_bg'] if is_active else CONFIG['colors']['node_bg']})) + svg.append(soup.new_tag('text', attrs={'x': x, 'y': y, 'font-family': 'monospace', 'font-size': CONFIG['font_size'], 'text-anchor': 'middle', 'dominant-baseline': 'central', 'fill': CONFIG['colors']['node_text'], 'font-weight': 'bold' if is_active else 'normal'})) + svg.find_all('text')[-1].string = str(val) + return svg + +def create_heap_visualization_div(steps_data): + # (이전과 동일, 변경 없음) + soup = BeautifulSoup("", "html.parser") + wrapper_div = soup.new_tag("div") + style = soup.new_tag("style") + style.string = """.heap-svg-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; justify-content: flex-start; } .heap-svg-flow .arrow { font-size: 24px; color: #555; }""" + wrapper_div.append(style) + flow_container = soup.new_tag("div", attrs={"class": "heap-svg-flow"}) + wrapper_div.append(flow_container) + for i, step in enumerate(steps_data): + svg_tree = create_single_tree_svg(soup, step) + flow_container.append(svg_tree) + if i < len(steps_data) - 1: + arrow = soup.new_tag("div", attrs={"class": "arrow"}) + arrow.string = "→" + flow_container.append(arrow) + return wrapper_div.prettify() + +# --- 🚀 메인 실행 부분 --- +if __name__ == "__main__": + MANUAL_STEPS = [ + {"heap": [16, 14, 10, 8, 7, 9, 3, 2, 4, 1], "highlighted": set()}, + {"heap": [16, 14, 10, 8, 7, 9, 3, 2, 4, 1, 15], "highlighted": {10}}, + {"heap": [16, 15, 10, 8, 14, 9, 3, 2, 4, 1, 7], "highlighted": set()}, + ] + + html_output = create_heap_visualization_div(MANUAL_STEPS) + print(html_output) +``` + +## Problem 2 + +Fill in the blanks in the following pseudocode for Heapsort. + +### Solution 2 + +```{ .line-numbers } +HEAPSORT(A) + BUILD-MAX-HEAP + for i = A.length downto 2 + exchange A[1] with A[i] + A.heap-size = A.heap-size - 1 + MAX-HEAPIFY(A, 1) +``` +