Files
2025-02-Algorithm/reviews/R5.md

20 KiB

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
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
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)
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

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)