# 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) ``` ```txt 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 ```txt { .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) ```