<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jung_ji_in02.log</title>
        <link>https://velog.io/</link>
        <description>인천대학교 멋쟁이사자</description>
        <lastBuildDate>Thu, 13 Nov 2025 04:00:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. jung_ji_in02.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jung_ji_in02" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Algorithm Study : 3weeks - 그래프]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B7%B8%EB%9E%98%ED%94%84</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-%EA%B7%B8%EB%9E%98%ED%94%84</guid>
            <pubDate>Thu, 13 Nov 2025 04:00:38 GMT</pubDate>
            <description><![CDATA[<p>범위가 조금 넓어서 이번주 스터디에선 그래프의 표현 , 유니온 파인드 , 위상 정렬에 대해서 스터디를 해봤습니다!</p>
<hr>
<h2 id="그래프의-개념">그래프의 개념</h2>
<ul>
<li><strong>정의</strong>: 정점(Vertex, Node)과 간선(Edge)을 모아 <strong>객체 간의 관계</strong>를 표현하는 자료구조.</li>
<li><strong>예시</strong>: 지도/지하철 최단경로, 전기 회로, 도로망(교차점-일방통행), 선수 과목 관계 등</li>
<li><strong>특징적 구조</strong>: 여러 개의 <strong>고립된 부분 그래프</strong>(isolated subgraphs)로 구성될 수 있음.</li>
<li><strong>트리와 차이</strong>: 트리는 루트·부모-자식 관계·비순환 구조가 필수지만, 그래프는 그 제한이 없음.</li>
</ul>
<aside>
💡

<ol>
<li>그래프는 여러 개로 떨어져 있을 수 있다</li>
</ol>
<p>그래프는 전체가 하나로 이어져 있을 필요가 없다. 즉, 완전히 연결되지 않은 여러 덩어리로 나뉘어 있을 수 있다.</p>
<p>예를 들어,</p>
<p>친구 그룹 A(서로 다 알고 있음)</p>
<p>친구 그룹 B(서로 다 알고 있음)</p>
<p>A와 B는 서로 모름 → 연결 X</p>
<p>이런 식으로 연결된 덩어리가 여러 개 존재할 수 있는데, 이것을 고립된 부분 그래프라고 한다.</p>
<ol>
<li>그래프는 트리보다 규칙이 훨씬 적다</li>
</ol>
<p>트리는 꼭 지켜야 할 규칙이 있지만, 그래프는 그런 제한이 없다.</p>
<p>트리는 반드시: 루트가 존재해야 하고, 부모–자식 관계가 있어야 하고, 사이클(순환)이 없어야 한다</p>
<p>반면 그래프는: 루트가 없어도 되고, 부모/자식 같은 위계가 없어도 되고, 사이클이 있든 없든 상관없다</p>
<p>즉, 트리는 규칙이 많은 특수한 그래프이고, 그래프는 훨씬 자유로운 형태의 연결 구조라고 이해하면 된다.</p>
</aside>

<hr>
<h2 id="핵심-용어">핵심 용어</h2>
<ul>
<li><strong>정점(vertex)</strong>: 위치/개체</li>
<li><strong>간선(edge)</strong>: 정점 간 연결(= link, branch)</li>
<li><strong>인접 정점(adjacent)</strong>: 간선 하나로 직접 연결된 정점</li>
<li><strong>차수(degree, 무방향)</strong>: 정점에 인접한 간선 수<ul>
<li>무방향 그래프에서 모든 정점 차수 합 = <strong>2E</strong></li>
</ul>
</li>
<li><strong>진입 차수(in-degree), 진출 차수(out-degree, 유향)</strong><ul>
<li>유향 그래프에서 모든 정점의 진입 차수 합 = 모든 정점의 진출 차수 합 = <strong>E</strong></li>
</ul>
</li>
<li><strong>경로 길이(path length)</strong>: 경로에 포함된 간선 수</li>
<li><strong>단순 경로(simple path)</strong>: 정점 중복 없음</li>
<li><strong>사이클(cycle)</strong>: 단순 경로의 시작·종료 정점이 동일</li>
</ul>
<blockquote>
<p><em>오일러 경로(Eulerian path)</em></p>
<ul>
<li><p><em>오일러 경로(Eulerian path)</em>: 모든 <strong>간선</strong>을 정확히 한 번씩 지나는 경로(시작점으로 돌아올 필요 없음).</p>
</li>
<li><p><em>오일러 회로/투어(Eulerian circuit/tour)</em>: 모든 <strong>간선</strong>을 한 번씩 지나 <strong>시작 정점으로 돌아오는</strong> 경로.</p>
</li>
<li><p><strong>존재 조건(무방향, 연결 가정)</strong></p>
<ul>
<li><p>오일러 <strong>회로</strong>: 모든 정점의 차수가 <strong>짝수</strong>.</p>
</li>
<li><p>오일러 <strong>경로</strong>: 차수가 홀수인 정점이 <strong>정확히 0개(=회로)</strong> 또는 <strong>2개</strong>.</p>
<p>  <em>(질문 본문에는 “오일러 경로가 시작점으로 되돌아온다/모든 차수 짝수”라고 되어 있으나, 이는 오일러 <strong>회로</strong>의 조건입니다 — 위와 같이 정정합니다.)</em></p>
</li>
</ul>
</li>
</ul>
</blockquote>
<hr>
<h2 id="그래프의-일반적-특징">그래프의 일반적 특징</h2>
<ul>
<li><strong>네트워크 모델</strong>: 정점-간선으로 관계망을 표현</li>
<li><strong>경로 다중성</strong>: 두 정점 사이에 2개 이상 경로가 있을 수 있음</li>
<li><strong>루트/부모-자식 개념 없음</strong>(트리와 차별)</li>
<li><strong>순회 방식</strong>: DFS, BFS</li>
<li><strong>순환/비순환</strong>: 사이클이 있을 수도(사이클릭) 없을 수도(비사이클릭)</li>
<li><strong>방향성</strong>: 방향/무방향 그래프로 구분</li>
<li><strong>셀프 루프/사이클</strong> 가능 여부는 그래프 정의에 따름</li>
</ul>
<hr>
<h2 id="그래프의-종류">그래프의 종류</h2>
<h3 id="1-방향성-기준">(1) 방향성 기준</h3>
<ul>
<li><strong>무방향 그래프</strong>: 간선을 <code>(A, B)</code>로 표기, <code>(A, B) = (B, A)</code><ul>
<li>예: 양방향 통행 도로</li>
</ul>
</li>
<li><strong>방향 그래프</strong>: 간선을 <code>&lt;A, B&gt;</code>로 표기, <code>&lt;A, B&gt; ≠ &lt;B, A&gt;</code><ul>
<li>예: 일방통행, 의존성</li>
</ul>
</li>
</ul>
<h3 id="2-가중치-기준">(2) 가중치 기준</h3>
<ul>
<li><strong>가중치 그래프(네트워크)</strong>: 간선에 비용/거리/용량/요금 등 가중치 부여<ul>
<li>예: 도시 간 거리, 통신요금, 회로 용량</li>
</ul>
</li>
</ul>
<h3 id="3-연결성-기준무방향">(3) 연결성 기준(무방향)</h3>
<ul>
<li><strong>연결 그래프</strong>: 모든 정점쌍 사이에 경로 존재<ul>
<li>트리(Tree): <strong>사이클 없는 연결 그래프</strong></li>
</ul>
</li>
<li><strong>비연결 그래프</strong>: 경로가 없는 정점쌍 존재</li>
</ul>
<h3 id="4-사이클-유무">(4) 사이클 유무</h3>
<ul>
<li><strong>사이클 그래프</strong>: 사이클 존재</li>
<li><strong>비순환 그래프(Acyclic)</strong>: 사이클 없음<ul>
<li>예: DAG(Directed Acyclic Graph, 유향 비순환)</li>
</ul>
</li>
</ul>
<h3 id="5-완전성">(5) 완전성</h3>
<ul>
<li><strong>완전 그래프(Kₙ)</strong>: 모든 정점쌍이 간선으로 연결<ul>
<li>무방향의 간선 수: <code>n(n−1)/2</code></li>
</ul>
</li>
</ul>
<hr>
<h2 id="그래프-표현-방법">그래프 표현 방법</h2>
<ul>
<li>그래프 알고리즘을 배우기 전에 반드시 알아야함</li>
<li>그래프 관련 문제를 해결하기 위해서는 주어진 그래프를 표현해야 하고, 그 데이터를 바탕으로 그래프 알고리즘을 적용시켜 해결해야 함</li>
</ul>
<hr>
<h2 id="그래프를-표현하는-3가지-방법">그래프를 표현하는 3가지 방법</h2>
<p><img src="attachment:2cc96032-f1f7-4a63-a041-f911bb602fe2:image.png" alt="image.png"></p>
<pre><code>              [그래프1 - 방향이 없는 그래프]</code></pre><p><img src="attachment:f3eb9891-3b6c-4d1b-a368-033be5462a33:image.png" alt="image.png"></p>
<pre><code>              [그래프2 - 방향이 있는 그래프]</code></pre><h3 id="1-인접-행렬adjacency-matrix">1. 인접 행렬(Adjacency matrix)</h3>
<p><img src="attachment:5996dd98-773c-42c8-aa09-0998436d5d7e:image.png" alt="image.png"></p>
<p><img src="attachment:0ab3fb2f-84b4-42c6-8cb5-fece359daa19:image.png" alt="image.png"></p>
<p><strong>정의 :</strong> 정점 수가 <code>V</code>일 때 <code>V × V</code> 크기의 행렬 <code>M</code>으로 간선을 표시.</p>
<ul>
<li>무가중치: <code>M[i][j] ∈ {0,1}</code></li>
<li>가중치: <code>M[i][j] = 가중치(없으면 0 또는 ∞)</code></li>
<li>유향 그래프: <code>M[i][j]</code>만 채움</li>
<li>무향 그래프: <code>M[i][j] = M[j][i]</code> 대칭</li>
</ul>
<p><strong>메모리</strong>: <code>Θ(V^2)</code></p>
<p><strong>주요 연산</strong></p>
<ul>
<li>두 정점 간 간선 존재 확인: <code>O(1)</code></li>
<li>정점 <code>u</code>의 모든 이웃 탐색: <code>O(V)</code></li>
<li>간선 추가/삭제: <code>O(1)</code> (값 대입)</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>매우 빠른 간선 존재 질의(<code>O(1)</code>).</li>
<li>구현이 단순, 행렬 기반 알고리즘(플로이드–워셜 등)에 유리.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>희소 그래프(간선이 적은 그래프)에서 메모리 낭비가 큼.</li>
<li>한 정점의 이웃을 나열하는 데 <code>O(V)</code>가 필요.</li>
</ul>
<p><strong>언제 쓰나</strong></p>
<ul>
<li><strong>밀집 그래프</strong>(E ≈ V²), 간선 존재 여부를 자주 물을 때,</li>
<li>모든 정점 쌍을 다루는 <strong>전쌍 최단경로</strong> 같은 행렬 기반 알고리즘.</li>
</ul>
<aside>
💡

<h2 id="이해를-돕는-비유확실함">이해를 돕는 비유(확실함)</h2>
<p><strong>친구 목록표라고 생각하면 된다.</strong></p>
<ul>
<li>가로줄 = “내가”</li>
<li>세로줄 = “상대”</li>
<li>칸 = “친구면 1, 아니면 0”</li>
</ul>
<p>즉, <strong>모든 사람을 상대로 ‘친구 여부’를 0/1로 체크한 친구표</strong>가 바로 인접행렬.</p>
<hr>
<h2 id="3-장점이론적으로-확실함">3. 장점(이론적으로 확실함)</h2>
<ul>
<li><p><strong>연결 여부 확인이 초고속</strong></p>
<p>  → 두 노드 A, B가 연결됐는지 보려면 표에서 하나만 보면 끝 → O(1)</p>
</li>
<li><p><strong>표 형태라 구현이 직관적</strong></p>
</li>
</ul>
<hr>
<h2 id="4-단점이론적으로-확실함">4. 단점(이론적으로 확실함)</h2>
<ul>
<li>노드가 많아지면 표가 엄청 커짐 → <strong>공간 낭비</strong></li>
<li>연결이 거의 없으면 대부분 0만 있음 → <strong>비효율적</strong></li>
</ul>
<p>예: 10,000개의 노드 → 10,000×10,000 = 1억 칸 필요</p>
<hr>
<h2 id="5-한-문장-요약">5. 한 문장 요약</h2>
<blockquote>
<p>인접행렬은 그래프의 모든 노드 쌍을 표로 만들어, 연결 여부를 0/1로 저장하는 방식이다.</p>
</blockquote>
<hr>
</aside>

<h3 id="2-인접-리스트adjacency-list">2. 인접 리스트(Adjacency list)</h3>
<p><img src="attachment:6d3426a6-fc7a-4a44-9a07-47303bfb2bfc:image.png" alt="image.png"></p>
<p><img src="attachment:bd017e95-0f4f-4dfb-a8a8-321819c54c53:image.png" alt="image.png"></p>
<p><strong>정의 :</strong> 각 정점 <code>u</code>에 대해, <code>u</code>와 연결된 (목적지, 가중치) 쌍의 리스트를 저장.</p>
<ul>
<li>유향: <code>u → v</code>는 <code>u</code>의 리스트에만 저장</li>
<li>무향: <code>u ↔ v</code>는 <code>u</code>와 <code>v</code> 양쪽 리스트에 저장</li>
</ul>
<p><strong>메모리</strong>: <code>Θ(V + E)</code></p>
<p><strong>주요 연산</strong></p>
<ul>
<li>두 정점 간 간선 존재 확인: <code>O(deg(u))</code> (u의 차수만큼 탐색)</li>
<li>정점 <code>u</code>의 모든 이웃 탐색: <code>O(deg(u))</code></li>
<li>간선 추가: 평균 <code>O(1)</code>(리스트 push)</li>
<li>간선 삭제: <code>O(deg(u))</code>(리스트에서 찾아 제거)</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>희소 그래프에서 <strong>메모리 효율</strong> 최고(<code>V+E</code>만큼 저장).</li>
<li>이웃 순회가 간선 수에 비례하므로 <strong>BFS/DFS</strong>에 매우 적합.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>간선 존재 여부를 즉시(<code>O(1)</code>) 확인할 수 없음(리스트 검색 필요).</li>
</ul>
<p><strong>언제 쓰나</strong></p>
<ul>
<li><strong>희소 그래프</strong>, 순회/탐색(BFS/DFS), 단일/단소수 소스 최단경로(Dijkstra의 힙 버전 등).</li>
</ul>
<aside>
💡

<h1 id="인접리스트-adjacency-list">인접리스트 (Adjacency List)</h1>
<p><strong>“친구 이름만 메모하는 방식”</strong></p>
<ul>
<li>각 노드가 <strong>연결된 친구만</strong> 리스트에 적는다.</li>
<li>존재하는 간선만 기록 → 메모리 절약.</li>
</ul>
<p>예</p>
<p>A-B, B-C 연결이면:</p>
<pre><code>A: B
B: A, C
C: B</code></pre><p><strong>장점:</strong> 공간 효율 높음</p>
<p><strong>단점:</strong> A-B 연결됐나 확인하려면 리스트를 뒤져야 함 → O(연결된 친구 수)</p>
</aside>

<h3 id="3-간선-리스트edge-list">3. 간선 리스트(Edge list)</h3>
<p><strong>정의 :</strong> 모든 간선을 <strong>배열/리스트</strong> 하나에 <code>(u, v[, w])</code> 형태로 나열.</p>
<ul>
<li>무향: <code>(u, v)</code>와 <code>(v, u)</code> 중 하나만 저장하는 것이 일반적(표현 방식에 따라 다름)</li>
<li>가중치가 있으면 <code>w</code> 포함</li>
</ul>
<p><strong>메모리</strong>: <code>Θ(E)</code> (+정점 메타데이터)</p>
<p><strong>주요 연산</strong></p>
<ul>
<li>두 정점 간 간선 존재 확인: <code>O(E)</code>(전체를 훑어야 함)</li>
<li>특정 정점의 이웃 탐색: <code>O(E)</code>(필터 필요)</li>
<li>간선 추가: <code>O(1)</code>(push)</li>
<li>간선 삭제: <code>O(E)</code>(검색 후 제거)</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>구현이 가장 단순, 입력/출력 포맷으로 <strong>가장 흔함</strong>.</li>
<li>간선을 정렬하기 쉬워 <strong>크루스칼(MST)</strong> 같은 <strong>간선 정렬 기반 알고리즘</strong>에 최적.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>인접 정점 탐색이 비효율적(<code>O(E)</code>).</li>
<li>간선 존재 여부 질의가 느림.</li>
</ul>
<p><strong>언제 쓰나</strong></p>
<ul>
<li><strong>MST(크루스칼)</strong>처럼 간선 정렬·필터링이 핵심인 알고리즘,</li>
<li>데이터 교환/저장 포맷, 그래프를 한 번 훑는 전처리 단계.</li>
</ul>
<aside>
💡

<h1 id="간선리스트-edge-list">간선리스트 (Edge List)</h1>
<p><strong>“연결된 쌍만 모아둔 명단”</strong></p>
<ul>
<li>간선 하나하나를 <strong>“(출발, 도착, 가중치)”</strong> 형태로 저장</li>
</ul>
<p>예</p>
<p>A-B, B-C 연결이면:</p>
<pre><code>(A, B)
(B, C)</code></pre><p>가중치가 있다면:</p>
<pre><code>(A, B, 3)
(B, C, 5)</code></pre><p><strong>장점:</strong> 가장 단순, 간선만 모아두면 됨</p>
<p><strong>단점:</strong> 두 노드 연결 여부 확인이 느림 → 하나하나 검사해야 함</p>
</aside>

<hr>
<h2 id="선택-가이드">선택 가이드</h2>
<ul>
<li><strong>정점 수가 크고 간선이 적다(희소)</strong> → <strong>인접 리스트</strong>가 기본값.</li>
<li><strong>간선 존재 여부를 빈번히 O(1)로 확인</strong>해야 하거나 <strong>매우 밀집</strong> → <strong>인접 행렬</strong>.</li>
<li><strong>간선을 정렬/스캔하는 작업이 핵심</strong>(예: 크루스칼) → <strong>간선 리스트</strong>.</li>
</ul>
<blockquote>
<p>참고:</p>
<p><a href="https://gmlwjd9405.github.io/2018/08/13/data-structure-graph.html">https://gmlwjd9405.github.io/2018/08/13/data-structure-graph.html</a></p>
<p><a href="https://godgod732.tistory.com/15">https://godgod732.tistory.com/15</a></p>
</blockquote>
<hr>
<p><a href="https://www.acmicpc.net/problem/2606">https://www.acmicpc.net/problem/2606</a></p>
<h3 id="bfs-사용">BFS 사용</h3>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static int count, num, connections;
    static boolean[] visited;
    static List[] computers;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        num = Integer.parseInt(br.readLine());
        connections = Integer.parseInt(br.readLine());
        visited = new boolean[num+1];
        computers = new List[num+1];
        count = 0;
        for(int i=1; i&lt;num+1; i++) {
            computers[i] = new ArrayList&lt;Integer&gt;();
        }

        StringTokenizer st;
        for(int i=0; i&lt;connections; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            computers[a].add(b);
            computers[b].add(a);
        }

        bfs(1);

        System.out.println(count-1); // 시작 지점 카운트 빼주기
        br.close();
    }

    private static void bfs(int start) {
        Queue&lt;Integer&gt; queue = new LinkedList&lt;Integer&gt;();
        queue.add(start);

        while(!queue.isEmpty()) {
            int now = queue.poll();
            if(!visited[now]) {
                count++;
                visited[now] = true;
                for(int i=0; i&lt;computers[now].size(); i++) {
                    queue.add((int)computers[now].get(i));
                }
            }
        }
    }
}</code></pre>
<h2 id="dfs-사용">DFS 사용</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static int count, num, connections;
    static boolean[] visited;
    static List[] computers;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        num = Integer.parseInt(br.readLine());
        connections = Integer.parseInt(br.readLine());
        visited = new boolean[num+1];
        computers = new List[num+1];
        count = 0;
        for(int i=1; i&lt;num+1; i++) {
            computers[i] = new ArrayList&lt;Integer&gt;();
        }

        StringTokenizer st;
        for(int i=0; i&lt;connections; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            computers[a].add(b);
            computers[b].add(a);
        }

        dfs(1);

        System.out.println(count-1); // 시작 지점 카운트 빼주기
        br.close();
    }

    private static void dfs(int start) {
        if(!visited[start]) {
            visited[start] = true;
            count++;
            for(int i=0; i&lt;computers[start].size(); i++) {
                dfs((int)computers[start].get(i));
            }
        }
    }
}
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 : Ch.05 Dynamic Programing]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.05-Dynamic-Programing</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.05-Dynamic-Programing</guid>
            <pubDate>Mon, 13 Oct 2025 11:27:11 GMT</pubDate>
            <description><![CDATA[<h1 id="동적-계획-알고리즘">동적 계획 알고리즘</h1>
<ul>
<li>Dynamic Programming(DP) 알고리즘</li>
<li>입력 크기가 작은 부분 문제들을 해결한 후에</li>
<li>그 해들을 이용하여 보다 큰 크기의 부분 문제들을 해결하여</li>
<li>최종적으로 원래 주어진 입력의 문제를 해결</li>
</ul>
<h3 id="dp-vs-분할-정복-알고리즘">DP vs 분할 정복 알고리즘</h3>
<ul>
<li>분할 정복 알고리즘과 DP 알고리즘의 전형적인 부분 문제들 사이의 관계</li>
</ul>
<p><img src="attachment:3d58866a-51b7-4e62-8ea0-123e3b0fd532:0a1edade-5691-43ff-9f15-5821a1db7390.png" alt="image.png"></p>
<ul>
<li>분할 정복 알고리즘<ul>
<li>A는 B와 C로 분할되고, B는 D와 E로 분할되는데, D와 E의 해를 취합하여 B의 해를 구한다.<ul>
<li>단, D, E, F, G는 각각 더 이상 분할할 수 없는 (또는 가장 작은 크기의) 부분 문제들이다.</li>
</ul>
</li>
<li>마찬가지로 F와 G의 해를 취합하여 C의 해를 구하고, 마지막으로 B와 C의 해를 취합하여 A의 해를 구한다.</li>
</ul>
</li>
</ul>
<h3 id="dp-vs-분할-정복-알고리즘-1">DP vs 분할 정복 알고리즘</h3>
<ul>
<li>DP 알고리즘<ul>
<li>먼저 최소 단위의 부분 문제 D, E, F, G의 해를 각각 구한다.</li>
<li>그 다음 D, E, F의 해를 이용하여 B의 해를 구한다.</li>
<li>E, F, G의 해를 이용하여 C의 해를 구한다.</li>
<li>B와 C의 해를 계산하는데 E와 F의 해 모두를 이용한다.</li>
</ul>
</li>
</ul>
<p><img src="attachment:3ec09c5e-5c79-40b4-a0fa-2408d72b63d0:image.png" alt="image.png"></p>
<ul>
<li>DP 알고리즘에는 부분 문제들 사이에 의존적 관계가 존재<ul>
<li>작은 부분 문제의 해가 보다 큰 부분 문제를 해결하는데 사용되는 관계가 있다.</li>
<li>이러한 관계는 문제 또는 입력에 따라 다르고, 대부분의 경우 뚜렷이 보이지 않아서 ‘함축적 순서’ (implicit order)라고 함</li>
</ul>
</li>
<li>분할 정복 알고리즘은 부분 문제의 해를 중복 사용하지 않음</li>
</ul>
<hr>
<h1 id="51-모든-쌍-최단-경로--all-pairs-shortest-paths-">5.1 모든 쌍 최단 경로 ( All Pairs Shortest Paths )</h1>
<ul>
<li>각 쌍의 점 사이의 최단 경로를 찾는 문제</li>
</ul>
<p><img src="attachment:faa0239f-5019-4b0f-ab2b-5c236b477000:image.png" alt="image.png"></p>
<ul>
<li>다익스트라의 최단 경로 알고리즘 이용하면<ul>
<li>각 점을 시작점으로 정하여 다익스트라 알고리즘 수행</li>
<li>시간 복잡도는 n x O(n^2) = O(n3), 단, n은 점의 수</li>
</ul>
</li>
<li>플로이드-워샬 알고리즘<ul>
<li>간단히 플로이드 알고리즘으로 부르자.</li>
<li>플로이드 알고리즘의 시간 복잡도는 O(n^3)으로 다익스트라 알고리즘을 n번 사용할 때의 시간 복잡도와 동일</li>
<li>플로이드 알고리즘은 매우 간단하여 다익스트라 알고리즘보다 효율적</li>
</ul>
</li>
</ul>
<h3 id="아이디어">아이디어</h3>
<ul>
<li>작은 그래프에서 부분 문제들을 찾아보자.<ul>
<li>3개의 점이 있는 경우, a에서 c까지의 최단 경로를 찾으려면 2가지 경로, 즉, a에서 c로 직접 가는 경로와 점 b를 경유하는 경로 중에서 짧은 것을 선택</li>
</ul>
</li>
<li>경유 가능한 점이<ul>
<li>점 1인 경우</li>
<li>점 1, 2인 경우,</li>
<li>점 1, 2, 3인 경우
…</li>
<li>점 1, 2, …, n, 즉 모든 점을 경유 가능한 점들로 고려하면서, 모든 쌍의 최단 경로의 거리를 계산</li>
</ul>
</li>
</ul>
<p><img src="attachment:e5b9ff79-de8b-4f2d-a15f-2a4b75298377:image.png" alt="image.png"></p>
<h3 id="부분-문제의-정의">부분 문제의 정의</h3>
<ul>
<li>그래프의 점이 1, 2, 3, ⋯, n일 때<ul>
<li>Dij^k = 점 {1, 2, ⋯, k}를 경유 가능한 점으로 고려하여, 점 i 에서 점 j까지의 모든 경로 중에서 가장 짧은 경로의 거리</li>
<li>[주의] 점 1에서 점 k까지의 모든 점을 반드시 경유하는 경로를 의미하는 것이 아니다.</li>
<li>Dij^k는 {1, 2, ⋯, k}을 하나도 경유하지 않으면서 점 i에서 직접 점 j에 도달하는 간선 (i, j)가 가장 짧은 거리일수도</li>
<li>단, k≠i, k≠j이고 k=0인 경우, 점 0은 그래프에 없으므로 어떤 점도 경유하지 않는다는 것을 의미. 즉, Dij^0 = 간선 (i,j)의 가중치</li>
</ul>
</li>
<li>Dij^1<ul>
<li>점 i에서 점 j까지 점 1을 경유하는 경우와 직접 가는 경로 중에서 짧은 경로의 거리</li>
<li>모든 점 i와 j에 대해 Dij^1를 계산하는 것이 가장 작은 부분 문제</li>
<li>i ≠ 1, j ≠ 1</li>
</ul>
</li>
</ul>
<p><img src="attachment:88c1d067-9fc7-4abb-8d1d-5c000cd61e49:image.png" alt="image.png"></p>
<ul>
<li><p>Dij^2</p>
<ul>
<li><p>점 i에서 점 2를 경유하여 j로 가는 경로의 거리와 Dij^1 중에서 짧은 경로의 거리</p>
</li>
<li><p>단, 점 2를 경유하는 경로의 거리는 Di2^1 + D2j^1</p>
</li>
<li><p>i ≠ 2, j ≠ 2</p>
<p><img src="attachment:667b6ca0-0ec8-4d65-aecc-bf2c0be2d2f9:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>Dij^k</p>
<ul>
<li><p>점 i에서 점 k를 경유하여 j로 가는 경로의 거리와 Dij^(k-1) 중에서 짧은 경로의 거리</p>
</li>
<li><p>점 k를 경유하는 경로의 거리는 Dik^(k-1) + Dkj^(k-1) 이고, i≠k, j≠k</p>
<p><img src="attachment:3a01d32f-2c36-4c4b-8a75-b27607d8ab9e:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>Dij^n</p>
<ul>
<li>모든 점을 경유 가능한 점들로 고려한 모든 쌍 i와 j의 최단 경로의 거리</li>
<li>플로이드의 모든 쌍 최단 경로 알고리즘은 k가 1에서 n이 될 때 까지 Dij^k를 계산해서, Dij^n을 찾는다.</li>
</ul>
</li>
<li><p>의사 코드</p>
</li>
</ul>
<pre><code class="language-c">AllPairsShortest
입력: 2차원 배열 D, 단, D[i, j] = 간선 (i, j)의 가중치, 만일 간선 (i, j)
가 없으면 D[i, j] = ∞, 모든 i에 대하여 D[i, i] = 0
출력: 모든 쌍 최단 경로의 거리를 저장한 2차원 배열 D
1. for k = 1 to n
2. for i = 1 to n (i≠k)
3. for j = 1 to n ( j≠k, j≠i)
4. D[i, j] = min{ D[i, k]+D[k, j], D[i, j] }</code></pre>
<h3 id="부분-문제-간의-함축적-순서">부분 문제 간의 함축적 순서</h3>
<ul>
<li>D[i, j]를 계산하기 위해서 미리 계산되어 있어야 할 부분 문제는 D[i, k]와 D[k, j]이다.</li>
</ul>
<p><img src="attachment:25ab6e36-0859-4edd-a1bf-4bdd487561a5:image.png" alt="image.png"></p>
<h3 id="수행-과정">수행 과정</h3>
<p><img src="attachment:1e4ac6c9-f09d-4cb1-a40d-e9302bc0831a:image.png" alt="image.png"></p>
<p><img src="attachment:d7c2bacf-8cb3-4c91-ab81-0cb0610941b0:61be23d9-60ee-431e-8839-09adba0a3ced.png" alt="image.png"></p>
<p><img src="attachment:32bf0815-1f52-4a47-b469-601813101101:image.png" alt="image.png"></p>
<p><img src="attachment:d0f54e54-941c-4eaa-a480-dcc3619eea9d:image.png" alt="image.png"></p>
<p><img src="attachment:58d37b6b-358c-4cf6-afa3-f569c6b4ad45:image.png" alt="image.png"></p>
<p><img src="attachment:b9148077-d798-4981-84ba-ffcb98b5d132:image.png" alt="image.png"></p>
<p><img src="attachment:d9cb425f-55ef-4073-98c1-6db57836d78c:f55c2afb-1b53-47e7-813d-4a77aa6bff01.png" alt="image.png"></p>
<p><img src="attachment:375977f0-723b-439d-b4de-df393f35ebf7:image.png" alt="image.png"></p>
<h3 id="시간-복잡도">시간 복잡도</h3>
<ul>
<li>각 k에 대해서 모든 i, j 쌍에 대해 계산되므로, 총 n x n x n = (n^3) 회 계산이 이루어지고, 각 계산은 O(1) 시간 소요</li>
<li>시간 복잡도는 O(n^3)</li>
</ul>
<h3 id="응용">응용</h3>
<ul>
<li>맵퀘스트(MapQuest)와 구글 맵</li>
<li>자동차 네비게이션</li>
<li>지리 정보 시스템 (GIS)에서의 네트워크 분석</li>
<li>통신 네트워크와 모바일 통신 분야</li>
<li>게임</li>
<li>산업 공학/경영 공학의 운영 (Operation) 연구</li>
<li>로봇 공학</li>
<li>교통 공학</li>
<li>VLSI 디자인 분야 등</li>
</ul>
<h3 id="간단한-코드-구현">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define INF 1000000000 // 매우 큰 값 (무한대 대체)

int main() {
    int n = 4; // 노드 수
    int graph[4][4] = {
        {0,   5,  INF, 8},
        {7,   0,  9,   INF},
        {2,   INF, 0,  4},
        {INF, INF, 3,  0}
    };

    int dist[4][4];

    // 초기화: 그래프의 값을 그대로 dist로 복사
    for (int i = 0; i &lt; n; i++) {
        for (int j = 0; j &lt; n; j++) {
            dist[i][j] = graph[i][j];
        }
    }

    // 플로이드-워샬 수행
    for (int k = 0; k &lt; n; k++) {           // 중간 노드
        for (int i = 0; i &lt; n; i++) {       // 출발 노드
            for (int j = 0; j &lt; n; j++) {   // 도착 노드
                if (dist[i][k] + dist[k][j] &lt; dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }

    // 결과 출력
    printf(&quot;=== 모든 쌍 최단 거리 ===\n&quot;);
    for (int i = 0; i &lt; n; i++) {
        for (int j = 0; j &lt; n; j++) {
            if (dist[i][j] == INF)
                printf(&quot;%7s&quot;, &quot;INF&quot;);
            else
                printf(&quot;%7d&quot;, dist[i][j]);
        }
        printf(&quot;\n&quot;);
    }

    return 0;
}
</code></pre>
<hr>
<h2 id="52-연속-행렬-곱셈--chained-matrix-multiplications-">5.2 연속 행렬 곱셈 ( Chained Matrix Multiplications )</h2>
<ul>
<li>연속된 행렬들의 곱셈에 필요한 원소 간의 최소 곱셈 횟수를 찾는 문제</li>
<li>10x20 행렬 A와 20x5 행렬 B를 곱하는데 원소 간의 곱셈 횟수는 10x20x5 = 1,000.</li>
<li>두 행렬을 곱한 결과 행렬 C는 10x5</li>
</ul>
<p><img src="attachment:48a0d37c-c665-4cad-896f-e2ba6f9cc5fb:image.png" alt="image.png"></p>
<h3 id="행렬-곱셈">행렬 곱셈</h3>
<ul>
<li>3개의 행렬을 곱해야 하는 경우</li>
<li>연속된 행렬의 곱셈에는 결합 법칙 허용</li>
<li>AxBxC = (AxB)xC = Ax(BxC)</li>
</ul>
<p><img src="attachment:098099e3-10b7-4cc9-94b2-b3a0562ad19c:image.png" alt="image.png"></p>
<h3 id="axb를-계산한-후에-c를-곱하기">AxB를 계산한 후에 C를 곱하기</h3>
<ul>
<li>AxB를 계산하는데 10x20x5 = 1,000번</li>
<li>결과 행렬의 크기가 10x5이고, 이에 행렬 C를 곱하면 10x5x15 = 750번</li>
<li>1,000 + 750 = 1,750회의 원소의 곱셈이 필요</li>
</ul>
<h3 id="bxc를-계산한-후에-a를-곱하기">BxC를 계산한 후에 A를 곱하기</h3>
<ul>
<li>BxC를 계산하는데 20x5x15 = 1,500번</li>
<li>그 결과 20x15 행렬이 만들어지고, 이를 행렬 A와 곱하면 10x20x15 = 3,000번</li>
<li>1,500 + 3,000 = 4,500회의 곱셈이 필요</li>
</ul>
<h3 id="주의-주어진-행렬의-순서를-지켜서-반드시-이웃하는-행렬끼리-곱해야">[주의] 주어진 행렬의 순서를 지켜서 반드시 이웃하는 행렬끼리 곱해야</h3>
<ul>
<li>AxBxCxDxE일 때 , AxC를 수행하거나, AxD를 먼저 수행할 수 없음</li>
</ul>
<h3 id="부분-문제">부분 문제</h3>
<p><img src="attachment:949e21bf-4477-4c86-ab1c-f181f462257b:image.png" alt="image.png"></p>
<ul>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">입력: 연속된 행렬 A1xA2x⋯xAn,
            단, A1은 d0xd1, A2는 d1xd2, ⋯, An은 dn-1xdn이다.
출력: 입력의 행렬 곱셈에 필요한 원소의 최소 곱셈 횟수
1. for i = 1 to n
2. C[i, i] = 0
3. for L = 1 to n-1 // L은 부분 문제의 크기를 조절하는 인덱스이다.
4. for i = 1 to n-L
5. j = i + L
6. C[i, j] = ∞
7. for k = i to j-1
8. temp = C[i, k] + C[k+1, j] + di-1dkdj
9. if (temp &lt; C[i, j])
10. C[i, j] = temp
11. return C[1,n]</code></pre>
<p><img src="attachment:4af5af88-c748-4497-8be0-9df30777f98b:image.png" alt="image.png"></p>
<p><img src="attachment:7ccdd033-fd23-4d82-a621-020fec822a29:image.png" alt="image.png"></p>
<p><img src="attachment:5bbdec64-afa9-4e9b-a588-8b88f9dc88c9:image.png" alt="image.png"></p>
<p><img src="attachment:3761898b-4872-44b0-b9a0-e4f2f5a0227e:5f518f73-447a-4e0c-98f9-33f9492dee8b.png" alt="image.png"></p>
<p><img src="attachment:2c332959-75a4-4aff-8251-d6d5051b285c:image.png" alt="image.png"></p>
<p><img src="attachment:3bf37b0e-26ee-407d-865c-a24faf5619e1:image.png" alt="image.png"></p>
<p><img src="attachment:7fa79fa0-3271-43df-b926-7953e4f406a5:image.png" alt="image.png"></p>
<p><img src="attachment:b67a2f44-99db-423f-bd91-6a0848ea541f:image.png" alt="image.png"></p>
<p><img src="attachment:c7177cd4-4d0e-4399-9749-c6fd2eb86304:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-1">시간 복잡도</h3>
<ul>
<li>총 부분 문제 수: (n-1) + (n-2) + … + 2 + 1 = n(n-1)/2</li>
<li>하나의 부분 문제는 k-루프가 최대 (n-1)번 수행</li>
<li>시간 복잡도: O(n2^) x O(n) = O(n^3)</li>
</ul>
<h3 id="간단한-코드-구현-1">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define INF 1000000000  // 무한대 대체값

int minMatrixChain(int d[], int n) {
    int C[100][100];  // 최소 곱셈 횟수를 저장할 배열

    // 1. 대각선 (자기 자신) 초기화
    for (int i = 1; i &lt;= n; i++)
        C[i][i] = 0;

    // 2. L: 부분 문제의 크기 (chain length)
    for (int L = 1; L &lt;= n - 1; L++) {
        for (int i = 1; i &lt;= n - L; i++) {
            int j = i + L;
            C[i][j] = INF;

            // 3. 가능한 분할 지점 k를 모두 시도
            for (int k = i; k &lt; j; k++) {
                int temp = C[i][k] + C[k + 1][j] + d[i - 1] * d[k] * d[j];
                if (temp &lt; C[i][j])
                    C[i][j] = temp;
            }
        }
    }

    return C[1][n];
}

int main() {
    // 예시: A1(10x20), A2(20x5), A3(5x15)
    // 따라서 d = {10, 20, 5, 15}
    int d[] = {10, 20, 5, 15};
    int n = 3;  // 행렬의 개수

    int result = minMatrixChain(d, n);
    printf(&quot;최소 곱셈 횟수: %d\n&quot;, result);

    return 0;
}</code></pre>
<hr>
<h2 id="53-편집-거리-문제--edit-distance-">5.3 편집 거리 문제 ( Edit Distance )</h2>
<ul>
<li>삽입 (insert), 삭제 (delete), 대체 (substitute) 연산을 사용하여 스트링(문자열) S를 수정하여 다른 스트링 T로 변환하고자 할 때 필요한 최소의 편집 연산 횟수</li>
</ul>
<h3 id="strong--→--stone">‘strong’  →  ‘stone’</h3>
<p><img src="attachment:8b702b7b-74d3-4e19-8d0d-8e1f29566a63:image.png" alt="image.png"></p>
<ul>
<li>위에서는 ‘s’와 ‘t’는 그대로 사용하고, ‘o’를 삽입하고, ‘r’과 ‘o’를 각각 삭제 그리고 ‘n’을 그대로 사용하고, 마지막으로 ‘g’를 ‘e’로 대체하여, 총 4회의 편집 연산 수행</li>
</ul>
<h3 id="다른-시도">다른 시도</h3>
<ul>
<li>‘s’와 ‘t’는 그대로 사용한 후, ‘r’을 삭제하고, ‘o’와 ‘n’을 그대로 사용한 후, ‘g’를 ‘e’로 대체하여, 총 2회의 편집 연산만이 수행되고, 이는 최소 편집 횟수이다.</li>
<li>S를 T로 바꾸는데 어떤 연산을 어느 문자에 수행하는가에 따라서 편집 연산 횟수가 달라진다.</li>
</ul>
<h3 id="부분-문제들을-어떻게-표현해야-할까">부분 문제들을 어떻게 표현해야 할까?</h3>
<ul>
<li><strong>접두부(prefix)를 살펴보자.</strong></li>
<li>‘strong’ ‘stone’에 대해</li>
<li>예를 들어, ‘stro’를 ‘sto’로 바꾸는 편집 거리를 미리 알면, ‘ng’ 를 ‘ne’로 바꾸는 편집 거리를 찾음으로써 주어진 입력에 대한 편집 거리를 구할 수 있다.</li>
</ul>
<p><img src="attachment:9d455bbf-f2b5-440b-a579-d67633c35ca3:image.png" alt="image.png"></p>
<h3 id="부분-문제-정의">부분 문제 정의</h3>
<ul>
<li>|S| = m, |T| = n</li>
</ul>
<p>S = s1 s2 s3 s4 ⋯ sm
T = t1 t2 t3 t4 ⋯ tn</p>
<ul>
<li>E[i, j] = S의 처음 i개 문자를 T의 처음 j개 문자로 바꾸는데 필요한 편집 거리</li>
<li>‘strong’ ‘stone’에 대해서, ‘stro’를 ‘sto’로 바꾸기 위한 편집 거리는 E[4, 3]이다.</li>
</ul>
<p><img src="attachment:0a4704af-b169-4a16-99c5-f42adf0ca41e:image.png" alt="image.png"></p>
<ul>
<li>‘strong’ ‘stone’에 대해<ul>
<li>s1→t1<ul>
<li>[‘s’ → ‘s’]: s1 = t1 = ‘s’이므로 E[1, 1] = 0</li>
</ul>
</li>
<li>s1→t1t2<ul>
<li>[‘s’ → ‘st’]: s1 = t1 = ‘s’이고, ‘t’를 삽입하므로 E[1, 2] = 1</li>
</ul>
</li>
<li>s1s2→t1<ul>
<li>[‘st’ → ‘s’]: s1 = t1 = ‘s’이고, ‘t’를 삭제하여 E[2, 1] = 1</li>
</ul>
</li>
<li>s1s2→t1t2<ul>
<li>[‘st’ → ‘st’]: s1 = t1 = ‘s’이고, s2 = t2 = ‘t’이므로 E[2, 2] = 0</li>
<li>이 경우에는 E[1, 1] = 0이라는 결과를 이용하고 s2 = t2= ‘t’이므로,
E[2, 2] = E[1,1] + 0 = 0</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="e4-3은-어떻게-계산하여야-할까">E[4, 3]은 어떻게 계산하여야 할까?</h3>
<ul>
<li>s1s2s3s4→t1t2t3<ul>
<li>[‘stro’ → ‘sto’]</li>
</ul>
</li>
</ul>
<p><img src="attachment:dbf42d66-c95a-49e4-9517-f33595640e5a:image.png" alt="image.png"></p>
<p>➢ E[4, 2]의 해를 알면, t3=‘o’를 삽입하면 E[4, 2] + 1
➢ E[3, 3]의 해를 알면, s4=‘o’를 삭제하면 E[3, 3] + 1
➢ E[3, 2]의 해를 알면, s4=‘o’과 t3=‘o’이 같으므로 E[3, 2] + 0</p>
<p><img src="attachment:9e1ba731-9f25-4724-9563-b4e6112b1bb6:image.png" alt="image.png"></p>
<h3 id="초기화">초기화</h3>
<p>➢ 0번 행이 0, 1, 2, ⋯, n으로 초기화된 의미: S의 첫 문자를 처리하기 전에, 즉, S가 ε (공 문자열)인 상태에서 T의 문자를 좌에서 우로 하나씩 만들어 가는데 필요한 삽입 연산 횟수를 각각 나타낸다.
➢ 0번 열이 0, 1, 2, ⋯, m으로 초기화된 의미: 스트링 T를 ε로 만들기 위해서, S의 문자를 위에서 아래로 하나씩 없애는데 필요한 삭제 연산 횟수를 각각 나타낸다.</p>
<ul>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">EditDistance
입력: 스트링 S, T, 단, S와 T의 길이는 각각 m과 n이다.
출력: S를 T로 변환하는 편집 거리, E[m, n]
1. for i=0 to m E[i, 0] = i // 0번 열의 초기화
2. for j=0 to n E[0, j] = j // 0번 행의 초기화
3. for i=1 to m
4. for j=1 to n
5. E[i, j] = min{E[i, j-1]+1, E[i-1, j]+1, E[i-1, j-1]+α}
6. return E[m, n]</code></pre>
<p><img src="attachment:e3853bb2-3e24-465e-a170-43cbd091d5a2:image.png" alt="image.png"></p>
<ul>
<li>E[1, 1] = min{E[1, 0]+1, E[0, 1]+1, E[0, 0]+α}
= min{(1+1), (1+1), (0+0)}
= 0</li>
</ul>
<p><img src="attachment:fb7c316c-e965-4628-8cda-271a7b5ae1ac:image.png" alt="image.png"></p>
<ul>
<li>E[2, 2] = min{E[2, 1]+1, E[1, 2]+1, E[1, 1]+α}
= min{(1+1), (1+1), (0+0)}
= 0</li>
</ul>
<p><img src="attachment:ef52dee0-0bca-4ad7-b8f9-9bc5b8b0b218:image.png" alt="image.png"></p>
<ul>
<li>E[3, 2] = min{E[3, 1]+1, E[2, 3]+1, E[2, 2]+α}
= min{2+1, 0+1, 1+1}
= 1</li>
</ul>
<p><img src="attachment:4d4b379b-d98e-41a0-a5b1-342ccc46321b:image.png" alt="image.png"></p>
<ul>
<li>E[4, 3] = min{E[4, 2]+1, E[3, 3]+1, E[3, 2]+α}
= min{(2+1), (1+1), (1+0)}
= 1</li>
</ul>
<p><img src="attachment:751c75eb-1746-47c0-a8b2-dce8b69ca61a:image.png" alt="image.png"></p>
<ul>
<li>E[5, 4] = min{E[5, 3]+1, E[4, 4]+1, E[4, 3]+α}
= min{(2+1), (1+1), (1+0)}
= 1</li>
</ul>
<p><img src="attachment:906cc54b-58b4-43d6-be24-eae41439a746:image.png" alt="image.png"></p>
<ul>
<li>E[6, 5] = min{E[6, 4]+1, E[5, 5]+1, E[5, 4]+α}
= min{(2+1), (2+1), (1+1)}
= 2</li>
</ul>
<p><img src="attachment:afe2670c-0c37-4075-b139-7f9d7b16f2a9:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-2">시간 복잡도</h3>
<ul>
<li>EditDistance 알고리즘의 시간 복잡도는 O(mn). 단, m과 n은 두 스트링의 각각의 길이이다.</li>
<li>총 부분 문제의 수가 배열 E의 원소 수인 mxn이고, 각 부분 문제 (원소)를 계산하기 위해서 주위의 3개의 부분 문제들의 해 (원소)를 참조한 후 최솟값을 찾는 것이므로 O(1) 시간 소요</li>
</ul>
<h3 id="응용-1">응용</h3>
<ul>
<li>2개의 스트링 사이의 편집 거리가 작으면, 이 스트링들이 서로 유사하다고 판단할 수 있으므로, 생물 정보 공학 (Bioinformatics) 및 의학 분야에서 두 개의 유전자가 얼마나 유사한가를 측정하는데 활용</li>
<li>환자의 유전자 속에서 암 유전자와 유사한 유전자를 찾아내어 환자의 암을 미리 진단하는 연구와 암세포에만 있는 특징을 분석하여 항암제를 개발하는 연구에 활용되며, 좋은 형질을 가진 유전자들 탐색 등의 연구에도 활용</li>
<li>그 외에도 철자 오류 검색(Spell Checker), 광학 문자 인식 (Optical Character Recognition) 에서의 보정시스템 (Correction System), 자 연 어 번 역 (Natural Language Translation) 소프트웨어 등에 활용</li>
</ul>
<h3 id="간단한-코드-구현-2">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#define MIN(a, b) ((a) &lt; (b) ? (a) : (b))
#define INF 1000000

int EditDistance(char S[], char T[]) {
    int m = strlen(S);
    int n = strlen(T);
    int E[101][101];  // 문자열 길이 제한 100 이하라고 가정

    // 1. 초기화
    for (int i = 0; i &lt;= m; i++)
        E[i][0] = i;  // S를 공백으로 만드는 데 필요한 삭제 횟수

    for (int j = 0; j &lt;= n; j++)
        E[0][j] = j;  // 공백에서 T를 만드는 데 필요한 삽입 횟수

    // 2. DP 점화식 계산
    for (int i = 1; i &lt;= m; i++) {
        for (int j = 1; j &lt;= n; j++) {
            int cost = (S[i - 1] == T[j - 1]) ? 0 : 1; // 문자가 같으면 0, 다르면 1
            int insert = E[i][j - 1] + 1;   // 삽입
            int del = E[i - 1][j] + 1;      // 삭제
            int replace = E[i - 1][j - 1] + cost; // 대체 or 그대로
            E[i][j] = MIN(MIN(insert, del), replace);
        }
    }

    return E[m][n]; // S를 T로 바꾸는 최소 편집 거리
}

int main() {
    char S[] = &quot;strong&quot;;
    char T[] = &quot;stone&quot;;

    int result = EditDistance(S, T);
    printf(&quot;&#39;%s&#39; → &#39;%s&#39; 의 최소 편집 거리: %d\n&quot;, S, T, result);

    return 0;
}</code></pre>
<hr>
<h2 id="54-0-1-배낭-문제--zero-one-knapsack-">5.4 0-1 배낭 문제 ( Zero-One Knapsack )</h2>
<ul>
<li>n개의 물건과 각 물건 i의 무게 wi와 가치 vi가 주어지고, 배낭의 용량이 C일 때, 배낭에 담을 수 있는 물건의 최대 가치는?</li>
<li>단, 배낭에 담은 물건의 무게의 합이 C를 초과하지 말아야하고, 각 물건은 1개씩만 있다.</li>
<li>이러한 배낭 문제를 0-1 배낭 문제라고 한다.</li>
</ul>
<h3 id="부분-문제-1">부분 문제</h3>
<ul>
<li>문제에는 물건, 물건의 무게, 물건의 가치, 배낭의 용량, 모두 4가지의 요소가 있다.</li>
<li>물건과 물건의 무게는 부분 문제를 정의하는데 필요</li>
</ul>
<aside>
💡

<p>K[i, w] = 물건 1∼i까지만 고려하고, (임시) 배낭의 용량이 w일 때의 최대 가치
단, i = 1, 2, ⋯, n이고, w = 1, 2, 3, ⋯, C
• 문제의 최적해 = K[n, C]</p>
</aside>

<ul>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">Knapsack
입력: 배낭의 용량 C, n개의 물건과 각 물건 i의 무게 wi와 가치 vi
,
단, i = 1, 2, ⋯, n
출력: K[n, C]
1. for i = 0 to n K[i,0]=0 // 배낭의 용량이 0일 때
2. for w = 0 to C K[0,w]=0 // 물건 0이란 어떤 물건도 고려하지 않을 때
3. for i = 1 to n
4. for w = 1 to C // w는 배낭의 (임시) 용량
5. if ( wi &gt; w ) // 물건 i의 무게가 임시 배낭 용량을 초과하면
6. K[i, w] = K[i-1, w]
7. else // 물건 i를 배낭에 담지 않을 경우와 담을 경우 고려
8. K[i, w] = max{K[i-1, w], K[i-1, w-wi
]+vi
}
9. return K[n, C]</code></pre>
<p><img src="attachment:aab5465e-0493-402b-bea5-e2e1b510e52a:image.png" alt="image.png"></p>
<p><img src="attachment:3fe5bde6-5fcc-491a-8425-7e85a6d0c19b:image.png" alt="image.png"></p>
<p><img src="attachment:c3cd0699-ccbb-47c4-9917-e34c861cba38:7a2cb1b9-9b04-40b8-a3b0-d72625bf544f.png" alt="image.png"></p>
<h3 id="수행-과정-1">수행 과정</h3>
<ul>
<li>Line 1~2: 0번 행과 0번 열의 각 원소를 0으로 초기화</li>
</ul>
<p><img src="attachment:8e8b041f-1f3b-4cb3-91c1-5a2a5ab1f0c5:0fe47f18-3e06-46af-905b-9536a136082f.png" alt="image.png"></p>
<ul>
<li>w =1, 2, 3, 4일 때, 각각 물건 1을 담을 수 없다.
K[1, 1] = 0, K[1, 2] = 0, K[1, 3] = 0, K[1, 4] = 0</li>
<li>W = 5 (배낭의 용량이 5kg)일 때,
K[1, 5] = = 10</li>
<li>w = 6, 7, 8, 9, 10일 때
K[1, 6] = K[1, 7] = K[1, 8] = K[1, 9] = K[1, 10] = 10</li>
</ul>
<h3 id="물건-1만-고려했을-때">물건 1만 고려했을 때</h3>
<p><img src="attachment:4afc11f8-c325-43c4-a219-5ca5aed46eff:225e3c05-0289-4d2f-96fa-b4afaf39205f.png" alt="image.png"></p>
<h3 id="물건-2를-고려해보자">물건 2를 고려해보자</h3>
<ul>
<li>w = 1, 2, 3 (배낭의 용량이 각각 1, 2, 3kg)일 때
K[2, 1] = 0, K[2 ,2] = 0, K[2, 3] = 0</li>
<li>w = 4 (배낭의 용량이 4kg)일 때
K[2,4] = 40</li>
<li>w = 5 (배낭의 용량이 5kg)일 때
K[2,5] = max{K[i-1,w], K[i-1,w-wi]+vi}
= max{K[2-1,5], K[2-1,5-4]+40}
= max{K[1,5], K[1,1]+40}
= max{10, 0+40}
= max{10, 40} = 40<ul>
<li>물건 1을 배낭에서 빼낸 후, 물건 2를 담는다.</li>
<li>그 때의 가치가 40이다.</li>
</ul>
</li>
<li>w = 6, 7, 8일 때<ul>
<li>각각의 경우도 물건 1을 빼내고 물건 2를 배낭에 담는 것이 더 큰 가치를 얻는다.
K[2,6] = K[2,7] = K[2,8] = 40</li>
</ul>
</li>
<li>w = 9 (배낭의 용량이 9kg)일 때
K[2,9] = max{K[i-1,w], K[i-1,w-wi]+vi}
= max{K[2-1,9], K[2-1,9-4]+40}
= max{K[1,9], K[1,5]+40}
= max{10, 10+40}
= max{10, 50} = 50<ul>
<li>물건 1, 2 둘 다를 담을 수 있다.</li>
</ul>
</li>
<li>w = 10 (배낭의 용량이 10kg)일 때<ul>
<li>물건 1, 2를 배낭에 둘 다 담을 수 있다.</li>
</ul>
</li>
</ul>
<p><img src="attachment:fb5e4a45-f84d-4e1c-9a46-1ed565755531:image.png" alt="image.png"></p>
<h3 id="수행-결과">수행 결과</h3>
<p><img src="attachment:d0c96a36-382d-4557-b4cf-528213c2e22d:7913158d-1c4a-40d0-8814-9ef2beca5daa.png" alt="image.png"></p>
<p>• 최적해 K[4, 10] = 90</p>
<h3 id="시간-복잡도-3">시간 복잡도</h3>
<ul>
<li>1개의 부분 문제에 대한 해를 구할 때의 시간 복잡도<ul>
<li>line 5에서의 무게를 1번 비교한 후 line 6에서는 1개의 부분 문제의 해를 참조하고, line 8에서는 2개의 해를 참조한 계산이므로 O(1) 시간</li>
</ul>
</li>
<li>부분 문제의 수는 배열 K의 원소 수인 n x C<ul>
<li>C = 배낭의 용량</li>
</ul>
</li>
<li>Knapsack 알고리즘의 시간 복잡도<ul>
<li>O(1) x n x C = O(nC)</li>
</ul>
</li>
</ul>
<h3 id="응용-2">응용</h3>
<ul>
<li>배낭 문제는 다양한 분야에서 의사 결정 과정에 활용</li>
<li>원자재의 버리는 부분을 최소화 시키기 위한 자르기/분할</li>
<li>금융 포트폴리오와 자산 투자의 선택</li>
<li>암호 생성 시스템 (Merkle–Hellman Knapsack Cryptosystem) 등에 활용</li>
</ul>
<h3 id="간단한-코드-구현-3">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

#define MAX_N 100
#define MAX_C 100
#define MAX(a, b) ((a) &gt; (b) ? (a) : (b))

int Knapsack(int n, int C, int w[], int v[]) {
    int K[MAX_N + 1][MAX_C + 1];

    // 1. 초기화 (0행, 0열)
    for (int i = 0; i &lt;= n; i++)
        K[i][0] = 0;
    for (int wgt = 0; wgt &lt;= C; wgt++)
        K[0][wgt] = 0;

    // 2. DP 테이블 채우기
    for (int i = 1; i &lt;= n; i++) {
        for (int wgt = 1; wgt &lt;= C; wgt++) {
            if (w[i] &gt; wgt)
                K[i][wgt] = K[i - 1][wgt];  // 물건 i를 담지 못함
            else
                K[i][wgt] = MAX(K[i - 1][wgt],
                                K[i - 1][wgt - w[i]] + v[i]); // 담는 경우 고려
        }
    }

    // 3. 결과 출력
    return K[n][C];
}

int main() {
    // 예시 데이터 (교재 예시 기반)
    // 물건: 4개
    // 무게: 5, 4, 6, 3
    // 가치: 10, 40, 30, 50
    // 배낭 용량: 10
    int n = 4;
    int C = 10;
    int w[] = {0, 5, 4, 6, 3};  // 0번 인덱스는 사용 안 함
    int v[] = {0, 10, 40, 30, 50};

    int result = Knapsack(n, C, w, v);
    printf(&quot;최대 가치: %d\n&quot;, result);

    return 0;
}</code></pre>
<hr>
<h2 id="55-동전-거스름돈-문제--dp-coin-change-">5.5 동전 거스름돈 문제 ( DP Coin Change )</h2>
<ul>
<li>대부분의 경우 그리디 알고리즘으로 해결되나, 해결 못하는 경우도 있다.</li>
<li>DP 알고리즘은 모든 동전 거스름돈 문제에 대하여 항상 최적해를 찾는다.</li>
</ul>
<h3 id="부분-문제-2">부분 문제</h3>
<ul>
<li>문제에 주어진 요소들<ul>
<li>동전의 종류, d1, d2, ⋯, dk, 단, d1&gt; d2&gt; ⋯ &gt; dk=1</li>
<li>거스름돈 n원</li>
</ul>
</li>
<li>배낭 문제의 DP 알고리즘에서 배낭의 용량을 1kg씩 증가시켜가며 문제를 해결<ul>
<li>1원씩 증가시켜가며 문제를 해결하자.</li>
<li>거스름돈을 배낭의 용량과 같이 생각하자.</li>
</ul>
</li>
<li>1차원 배열 C<ul>
<li>C[1] = 1원을 거슬러 받을 때 사용되는 최소의 동전 수</li>
<li>C[2] = 2원을 거슬러 받을 때 사용되는 최소의 동전 수
⋮</li>
<li>C[n] = n원을 거슬러 받을 때 사용되는 최소의 동전 수</li>
</ul>
</li>
<li>C[j]를 계산하는데 어떤 부분 문제가 필요할까?<ul>
<li>500원 동전이 필요하면 (j-500)원의 해, 즉, C[j-500]에다가 500원 동전 1개 추가</li>
<li>100원 동전이 필요하면 (j-100)원의 해, 즉, C[j-100]에다가 100원 동전 1개 추가</li>
<li>50원 동전이 필요하면 (j-50)원의 해, 즉, C[j-50]에다가 50원 동전 1개 추가</li>
<li>10원 동전이 필요하면 (j-10)원의 해, 즉, C[j-10]에다가 10원 동전 1개 추가</li>
<li>1원 동전이 필요하면 (j-1)원의 해, 즉, C[j-1]에다가 1원 동전 1개 추가</li>
</ul>
</li>
</ul>
<p><img src="attachment:23a0e679-ceca-4612-b028-84094e7cb93c:image.png" alt="image.png"></p>
<ul>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">DPCoinChange
입력: 거스름돈 n원, k개의 동전의 액면, d1&gt; d2&gt; ⋯ &gt; dk=1
출력: C[n]
1. for i = 1 to n C[i]=∞
2. C[0]=0
3. for j = 1 to n // j는 1원부터 증가하는 (임시) 거스름돈 액수
4. for i = 1 to k
5. if (di ≤ j) and (C[ j-di
]+1&lt;C[ j])
6. C[ j] = C[ j-di
] + 1
7. return C[n]</code></pre>
<h3 id="dpcoinchange-알고리즘-수행-과정">DPCoinChange 알고리즘 수행 과정</h3>
<ul>
<li>d1=16, d2=10, d3=5, d4=1이고, 거스름돈 n=20일 때</li>
<li>Line 1~2: 배열 C 초기화</li>
</ul>
<p><img src="attachment:a2622a53-63d0-4b3c-bc74-5972f8dc16a1:image.png" alt="image.png"></p>
<p><img src="attachment:20d453d1-649c-4bb6-9ca5-75ed4fae4a12:image.png" alt="image.png"></p>
<p><img src="attachment:d8a1a877-7ac9-4677-8692-605b69d14c5f:image.png" alt="image.png"></p>
<p><img src="attachment:869d0e17-2c6b-4afb-a5bc-6da014436ca5:image.png" alt="image.png"></p>
<p><img src="attachment:c36ee894-c678-44fe-bc4b-481f86c9ebde:image.png" alt="image.png"></p>
<p><img src="attachment:83cb1a09-2753-4d38-bf18-a862a6e78160:image.png" alt="image.png"></p>
<h3 id="수행-결과-1">수행 결과</h3>
<ul>
<li>거스름돈 20원에 대한 최종해 = C[20]=2</li>
<li>그리디 알고리즘은 20원에 대해 16원 동전을 먼저 ‘욕심 내어’ 취하고, 4원이 남게 되어, 1원 4개를 취하여, 모두 5개의 동전이해라고 답한다.</li>
</ul>
<p><img src="attachment:8deb7f93-a496-474f-a85d-15c079f51e53:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-4">시간 복잡도</h3>
<ul>
<li>O(nk)<ul>
<li>거스름돈 j가 1원n원까지 변하며, 각각의 j에 대해서 최대 모든 동전(d1, d2, ⋯, dk)을 (즉, k개를) 1번씩 고려하기 때문</li>
</ul>
</li>
</ul>
<h3 id="간단한-코드-구현-4">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define INF 1000000
#define MAX_N 1000
#define MAX_K 100

int DPCoinChange(int n, int k, int d[]) {
    int C[MAX_N + 1];

    // 1. 초기화
    for (int i = 1; i &lt;= n; i++)
        C[i] = INF;
    C[0] = 0;

    // 2. 동적 계획법 수행
    for (int j = 1; j &lt;= n; j++) {          // 거스름돈 1원 ~ n원
        for (int i = 0; i &lt; k; i++) {       // 모든 동전 액면 고려
            if (d[i] &lt;= j &amp;&amp; C[j - d[i]] + 1 &lt; C[j]) {
                C[j] = C[j - d[i]] + 1;
            }
        }
    }

    return C[n];
}

int main() {
    // 예시: 동전 종류 {16, 10, 5, 1}, 거스름돈 n = 20
    int d[] = {16, 10, 5, 1};
    int k = 4;
    int n = 20;

    int result = DPCoinChange(n, k, d);
    printf(&quot;거스름돈 %d원에 필요한 최소 동전 수: %d\n&quot;, n, result);

    return 0;
}</code></pre>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li><p>동적 계획(Dynamic Programming) 알고리즘은 최적화 문제를 해결하는 알고리즘으로서 입력 크기가 작은 부분 문제들을 모두 해결한 후에 그 해들을 이용하여 보다 큰 크기의 부분 문제들을 해결하여, 주어진 입력의 문제를 해결하는 알고리즘</p>
</li>
<li><p>DP 알고리즘에는 부분 문제들 사이에 함축적 순서가 존재한다.</p>
</li>
<li><p>모든 쌍 최단 경로(All Pairs Shortest Paths) 문제를 위한 FloydWarshall 알고리즘은 O(n^3) 시간에 해를 찾는다.</p>
</li>
<li><p>경유 가능한 점들을 점 1로부터 시작하여, 점 1과 2, 그 다음엔 점 1,2, 3으로 하나씩 추가하여, 마지막에는 점 1에서 점 n까지의 모든 점을 경유 가능한 점들로 고려하면서, 모든 쌍의 최단 경로의 거리를 계산한다.</p>
</li>
<li><p>연속 행렬 곱셈(Chained Matrix Multiplications) 문제를 위한 O(n^3) DP 알고리즘은 이웃하는 행렬들끼리 곱하는 모든 부분 문제들을 해결하여 최적해를 찾는다.</p>
</li>
<li><p>편집 거리(Edit Distance) 문제를 위한 DP 알고리즘은 E[i, j]를 3개의 부분 문제 E[i, j-1], E[i-1, j], E[i-1, j-1]만을 참조하여 계산한다. 시간 복잡도는 O(mn)이다. 단, m과 n은 두 스트링의 길이이다.</p>
</li>
<li><p>배낭(Knapsack) 문제를 위한 DP 알고리즘은 부분 문제 K[i, w]를 물건 1∼i까지만 고려하고, (임시) 배낭의 용량이 w일 때의 최대 가치로 정의하여, i를 1 ∼ 물건 수인 n까지, w를 1 ∼ 배낭 용량 C까지 변화시키며 해를 찾는다. 시간 복잡도는 O(nC)이다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 : Ch.04 Greedy]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.04-Greedy</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.04-Greedy</guid>
            <pubDate>Mon, 13 Oct 2025 11:26:26 GMT</pubDate>
            <description><![CDATA[<ul>
<li>그리디 알고리즘은 최적화 문제를 해결하는 알고리즘<ul>
<li>최적화 (optimization) 문제<ul>
<li>가능한 해들 중에서 가장 좋은 (최대 또는 최소) 해를 찾는 문제</li>
</ul>
</li>
<li>욕심쟁이 방법, 탐욕적 방법, 탐욕 알고리즘 등으로 불림</li>
<li>그리디 알고리즘은 (입력) 데이터 간의 관계를 고려하지 않고 수행 과정에서 ‘욕심내어’ 최소값 또는 최대값을 가진 데이터를 선택<ul>
<li>이러한 선택을 ‘근시안적’인 선택이라고 말하기도 함</li>
</ul>
</li>
</ul>
</li>
<li>그리디 알고리즘은 근시안적인 선택으로 부분적인 최적해를 찾고, 이들을 모아서(축적하여) 문제의 최적해를 얻는다.</li>
</ul>
<p><img src="attachment:de2de190-0374-40ac-8eda-edca974ab713:image.png" alt="image.png"></p>
<ul>
<li>그리디 알고리즘은 일단 한 번 선택하면, 이를 절대로 번복하지 않는다.<ul>
<li>즉, 선택한 데이터를 버리고 다른 것을 취하지 않는다.</li>
</ul>
</li>
<li>이러한 특성 때문에 대부분의 그리디 알고리즘들은 매우 단순하며, 또한 제한적인 문제만이 그리디 알고리즘으로 해결된다.</li>
</ul>
<h1 id="41-동전-거스름돈-coin-change-문제">4.1 동전 거스름돈 (Coin Change) 문제</h1>
<ul>
<li>동전 거스름돈 (Coin Change) 문제를 해결하는 가장 간단하고 효율적인 방법<ul>
<li>남은 액수를 초과하지 않는 조건하에 ‘욕심내어’ 가장 큰 액면의 동전을 취하는 것</li>
</ul>
</li>
<li>동전 거스름돈 문제의 최소 동전 수를 찾는 그리디 알고리즘<ul>
<li>동전의 액면은 500원, 100원, 50원, 10원, 1원</li>
</ul>
</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">CoinChange
입력: 거스름돈 액수 W
출력: 거스름돈 액수에 대한 최소 동전 수
1. change=W, n500=n100=n50=n10=n1=0
// n500, n100, n50, n10, n1은 각각의 동전 카운트
2. while ( change ≥ 500 )
change = change-500, n500++ // 500원 동전 수 1 증가
3. while ( change ≥ 100 )
change = change-100, n100++ // 100원 동전 수 1 증가
4. while ( change ≥ 50 )
change = change-50, n50++ // 50원 동전 수 1 증가
5. while ( change ≥ 10 )
change = change-10, n10++ // 10원 동전 수 1 증가
6. while ( change ≥ 1 )
change = change-1, n1++ // 1원 동전 수 1 증가
7. return (n500+n100+n50+n10+n1) // 총 동전 수 리턴</code></pre>
<ul>
<li>그리디 알고리즘의 근시안적인 특성<ul>
<li>CoinChange 알고리즘은 남아있는 거스름돈인 change에 대해 가장 높은 액면의 동전을 거스르며,</li>
<li>500원 동전을 처리하는 line 2에서는 100원, 50원, 10원, 1원 동전을 몇 개씩 거슬러 주어야 할 것인지에 대해서는 전혀 고려하지 않는다.</li>
</ul>
</li>
<li>760원의 거스름돈에 대해</li>
</ul>
<p><img src="attachment:de9b0a19-e1ce-47ea-9a45-e320de40909f:image.png" alt="image.png"></p>
<ul>
<li>CoinChange 알고리즘의 문제점<ul>
<li>한국은행에서 160원 동전을 추가로 발행한다면, CoinChange 알고리즘이 항상 최소 동전 수를 계산할 수 있을까?</li>
<li>거스름돈이 200원이라면, CoinChange 알고리즘은 160원 동전 1개와 10원 동전 4개로서 총 5개를 리턴</li>
<li>200원에 대한 최소 동전 수는 100원짜리 동전 2개</li>
<li>CoinChange 알고리즘은 항상 최적의 답을 주지 못함<ul>
<li>그러나 실제로는 거스름돈에 대한 그리디 알고리즘이 적용되도록 동전이 발행됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="attachment:67a54329-0360-4734-b9a7-d4584bd4046a:image.png" alt="image.png"></p>
<hr>
<h1 id="42-최소-신장-트리-minimum-spanning-tree">4.2 최소 신장 트리 (Minimum Spanning Tree)</h1>
<ul>
<li>주어진 가중치 그래프에서 사이클이 없이 모든 점들을 연결시킨 트리들 중 간선들의 가중치 합이 최소인 트리</li>
</ul>
<p><img src="attachment:160fca1f-0276-4758-a700-20f0ecd0ad6d:7cbdebd9-d02d-4360-ab88-7c5e20ac4f1f.png" alt="image.png"></p>
<ul>
<li>주어진 그래프의 신장 트리를 찾는 방법<ul>
<li>사이클이 없도록 모든 점을 연결시킨다.</li>
<li>그래프의 점의 수 = n</li>
<li>신장 트리에는 정확히 (n-1)개의 간선이 있다.</li>
<li>트리에 간선을 하나 추가시키면, 반드시 사이클이 만들어진다.</li>
</ul>
</li>
</ul>
<p><img src="attachment:a3c3c983-f342-4ae0-91c8-fb27f0087b83:image.png" alt="image.png"></p>
<ul>
<li>크러스컬(Kruskal) 알고리즘<ul>
<li>가중치가 가장 작은 간선이 사이클을 만들지 않을 때에만 ‘욕심내어’ 그 간선을 추가시킨다.</li>
</ul>
</li>
<li>프림(Prim) 알고리즘<ul>
<li>임의의 점 하나를 선택한 후, (n-1)개의 간선을 하나씩 추가시켜 트리를 만든다.</li>
</ul>
</li>
<li>알고리즘의 입력은 1개의 연결 성분(connected component)로 된 가중치 그래프</li>
<li>크러스컬(Kruskal) 의사 코드</li>
</ul>
<pre><code class="language-c">KruskalMST(G)
입력: 가중치 그래프 G=(V,E), |V|=n , |E|=m
출력: 최소 신장 트리 T
1. 가중치의 오름차순으로 간선들을 정렬: L = 정렬된 간선 리스트
2. T=∅ // 트리 T를 초기화
3. while ( T의 간선 수 &lt; n-1 )
4. L에서 가장 작은 가중치를 가진 간선 e를 가져오고, e를 L에서 제거
5. if (간선 e가 T에 추가되어 사이클을 만들지 않으면)
6. e를 T에 추가
7. else // e가 T에 추가되어 사이클이 생기는 경우
8. e를 버린다.
9. return 트리 T // T는 최소 신장 트리</code></pre>
<h3 id="kruskalmst-알고리즘-수행-과정">KruskalMST 알고리즘 수행 과정</h3>
<p><img src="attachment:51c12e08-1e7c-4320-a957-9d325b20bb39:5b35feb3-1048-4c39-b41a-bf20e60acff6.png" alt="image.png"></p>
<p><img src="attachment:5e65a6f8-40d9-41a1-ae30-ac35f2da43f6:image.png" alt="image.png"></p>
<p><img src="attachment:6d49916b-9718-4059-b4be-4e30585ecdef:image.png" alt="image.png"></p>
<p><img src="attachment:a6f7a7d1-3dd8-44b8-ad4b-28d97ae21383:image.png" alt="image.png"></p>
<p><img src="attachment:6ea9db0d-30ac-4980-92f2-702b46999c84:c743813e-ccb4-4f2e-9354-d99d579cf6af.png" alt="image.png"></p>
<p><img src="attachment:9755e475-3ec9-4055-8c1e-1bb5303b89da:image.png" alt="image.png"></p>
<p><img src="attachment:904152cf-0af1-4b9e-99fe-6f4573408d69:image.png" alt="image.png"></p>
<p><img src="attachment:9c2ef69e-46b8-4e90-b898-f6fbe2879490:image.png" alt="image.png"></p>
<h3 id="시간-복잡도">시간 복잡도</h3>
<ul>
<li>Line 1 : 간선 정렬하는데 O(mlogm) 시간</li>
<li>단, m은 입력 그래프에 있는 간선의 수</li>
<li>Line 2 : T를 초기화하는 것이므로 O(1) 시간</li>
<li>Line 3~8<ul>
<li>while-루프는 최대 m번 수행 → 그래프의 모든 간선이 while-루프 내에서 처리되는 경우</li>
<li>while-루프 내에서는 L로부터 가져온 간선 e가 사이클을 만드는지를
검사하는데 거의 O(1) 시간</li>
</ul>
</li>
<li>Kruskal 알고리즘의 시간복잡도: O(mlogm)</li>
<li>프림 (Prim)의 MST 알고리즘<ul>
<li>주어진 가중치 그래프에서 임의의 점 하나를 선택한 후, (n-1) 개의 간선을 하나씩 추가시켜 트리 생성</li>
<li>추가되는 간선은 현재까지 만들어진 트리에 연결시킬 때 ‘욕심 내어’ 항상 최소의 가중치로 연결되는 간선</li>
</ul>
</li>
<li>프림 (Prim) 의사 코드</li>
</ul>
<pre><code class="language-c">PrimMST(G)
입력: 가중치 그래프 G=(V,E), |V|=n, |E|=m
출력: 최소 신장 트리 T
1. G에서 임의의 점 p를 시작점으로 선택 D[p] = 0
// D[v]는 T에 있는 u와 v를 연결하는 간선의 최소 가중치를 저
장하기 위한 원소
2. for (점 p가 아닌 각 점 v에 대하여) { // 배열 D의 초기화
3. if ( 간선 (p, v)가 그래프에 있으면 )
4. D[v] = 간선 (p, v)의 가중치
5. else
6. D[v]=∞
}
7. T= {p} // 초기에 트리 T는 점 p만을 가진다.
8. while (T에 있는 점의 수 &lt; n) {
9. T에 속하지 않은 각 점 v에 대하여, D[v]가 최소인 점 vmin과 연
결된 간선 (u, vmin)을 T에 추가, 여기서 u는 T에 속한 점이고, 점
vmin도 T에 추가
10. for (T에 속하지 않은 각 점 w에 대해서) {
11. if (간선 (vmin, w)의 가중치 &lt; D[w])
12. D[w] = 간선 (vmin, w)의 가중치 // D[w]를 갱신
    }
}
13. return T // T는 최소 신장 트리</code></pre>
<ul>
<li>D[v] 설명<ul>
<li>Line 1<ul>
<li>임의로 점 p를 선택하고, D[p]=0으로 놓는다.</li>
<li>D[v]에는 점 v와 T에 속한 점들을 연결하는 간선들 중에서 최소 가중치를 가진 간선의 가중치를 저장</li>
<li>그림에서 D[v]에는 10, 7, 15 중에서 최소 가중치인 7이 저장</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="attachment:41907fd9-046c-45d0-bc14-89423f0f4ef5:image.png" alt="image.png"></p>
<p><img src="attachment:2ea55f44-2caf-4bd1-bffd-dcc3a5cff87f:image.png" alt="image.png"></p>
<p><img src="attachment:0ebb4baf-81de-4cab-a7f7-12e75706f1a1:image.png" alt="image.png"></p>
<p><img src="attachment:142da481-2196-42a4-9548-3adc2e09630b:image.png" alt="image.png"></p>
<p><img src="attachment:e5cc8ca4-4f00-401c-b8d0-1d22df3b6920:b56feac2-f4a1-4bb4-a064-cc7f0bcb689b.png" alt="image.png"></p>
<p><img src="attachment:1b3b2bf4-cbd9-4b11-bff1-f95919345da7:f2f2a6b4-0d4e-47c3-b64b-e5e77f472a9c.png" alt="image.png"></p>
<p><img src="attachment:004fa907-9b4f-4b1f-963e-0afc92234057:image.png" alt="image.png"></p>
<p><img src="attachment:8402da56-ab01-4ee6-b49d-144dc4ae46f2:image.png" alt="image.png"></p>
<p><img src="attachment:e3dac36c-4237-4f3a-b400-548a85ab71ab:image.png" alt="image.png"></p>
<p><img src="attachment:10599900-b247-4083-9b6e-6a9bfa9bf77e:image.png" alt="image.png"></p>
<p><img src="attachment:4d94ea77-799a-410c-81bc-77b4ffa0a88c:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-1">시간 복잡도</h3>
<ul>
<li>while-루프가 (n-1)회 반복되고,<ul>
<li>1회 반복될 때 line 9에서 T에 속하지 않은 각 점 v에 대하여, D[v]가 최소인 점 vmin을 찾는데 O(n) 시간 소요</li>
<li>배열 D에서 (현재 T에 속하지 않은 점들에 대해서) 최솟값을 찾는 것이고, 배열의 크기는 n이기 때문</li>
</ul>
</li>
<li>프림 알고리즘의 시간 복잡도<ul>
<li>(n-1) x O(n) = O(n2)</li>
<li>최소 힙(Binary Heap)을 사용하여 vmin을 찾으면 O(mlogn), m은 간선 수. 따라서 간선 수가 O(n)이면 O(nlogn)</li>
</ul>
</li>
<li>Kruskal과 Prim 알고리즘의 수행 과정 비교<ul>
<li>크러스컬 알고리즘<ul>
<li>간선이 1개씩 T에 추가되는데, 이는 마치 n개의 점들이 각각의 트리인 상태에서 간선이 추가되면 2개의 트리가 1개의 트리로 합쳐지는 것과 같음</li>
<li>크러스컬 알고리즘은 이를 반복하여 1개의 트리인 T를 생성</li>
<li>n개의 트리들이 점차 합쳐져서 1개의 신장 트리가 만들어진다.</li>
</ul>
</li>
<li>프림 알고리즘<ul>
<li>T가 점 1개인 트리에서 시작되어 간선을 1개씩 추가</li>
<li>1개의 트리가 자라나서 신장 트리가 된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="응용">응용</h3>
<ul>
<li>최소 비용으로 선로 또는 파이프 네트워크 (인터넷 광 케이블 선로, 케이블 TV선로, 전화선로, 송유관로, 가스관로, 배수로 등) 를 설치하는데 활용</li>
</ul>
<hr>
<h1 id="43-최단-경로-shortest-path-찾기"><strong>4.3 최단 경로 (Shortest Path) 찾기</strong></h1>
<ul>
<li>주어진 가중치 그래프에서 어느 한 출발점에서 또 다른 도착점까지의 최단 경로를 찾는 문제</li>
<li>최단 경로를 찾는 가장 대표적인 알고리즘</li>
<li>다익스트라(Dijkstra) 최단 경로 알고리즘<ul>
<li>주어진 출발점에서 시작</li>
<li>출발점으로부터 최단 거리가 확정되지 않은 점들 중에서 출발점으로부터 가장 가까운 점을 추가하고, 그 점의 최단 거리를 확정</li>
</ul>
</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">ShortestPath(G, s)
입력: 가중치 그래프 G=(V,E), |V|=n , |E|=m
출력: 출발점 s로부터 (n-1)개의 점까지 각각 최단 거리를 저장한 배
열 D
1. 배열 D를 ∞로 초기화. 단, D[s]=0으로 초기화
// 배열 D[v]에는 출발점 s로부터 점 v까지의 거리를 저장
2. while (s로부터의 최단 거리가 확정되지 않은 점이 있으면)
3. 현재까지 최단 거리가 확정되지 않은 각 점 v에 대해서 최소의
D[v]의 값을 가진 점 vmin을 선택하고, s로부터 점 vmin까지의
최단 거리 D[vmin]을 확정
4. s로부터 현재보다 짧은 거리로 점 vmin을 통해 우회 가능한 각
점 w에 대해서 D[w]를 갱신 // 간선 완화
5. return D</code></pre>
<h3 id="간선-완화edge-relaxation">간선 완화(Edge Relaxation)</h3>
<p><img src="attachment:f954e884-9043-420c-bedf-d5e5ccc7540c:image.png" alt="image.png"></p>
<p><img src="attachment:578e8a0d-aa84-4c19-ac46-62aa711f5021:image.png" alt="image.png"></p>
<p><img src="attachment:d9ae5b93-ae2e-49cd-a76d-39aef2e23f95:image.png" alt="image.png"></p>
<p><img src="attachment:088f5f49-9def-47a6-8630-4017b322842f:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-2">시간 복잡도</h3>
<ul>
<li>while-루프가 (n-1)회 반복되고, 1회 반복될 때<ul>
<li>line 3에서 최소의 D[v]를 가진 점 vmin을 찾는데 O(n) 시간 소요 → 왜냐하면 배열 D에서 최솟값을 찾기 때문</li>
<li>line 4에서도 vmin에 연결된 점의 수가 최대 (n-1)개이므로, 각 D[w]를 갱신하는데 걸리는 시간은 O(n)</li>
</ul>
</li>
<li>ShotestPath의 시간 복잡도는<ul>
<li>(n-1) x {O(n)+O(n)} = O(n2)</li>
<li>프림 알고리즘과 같이 최소 힙(Binary Heap)을 사용하면 O(mlogn), m은 간선 수. 따라서 간선 수가 O(n)이면 O(nlogn)</li>
</ul>
</li>
</ul>
<h3 id="응용-1">응용</h3>
<ul>
<li>맵퀘스트 (MapQuest)와 구글 (Google) 맵</li>
<li>자동차 네비게이션</li>
<li>네트워크와 통신 분야</li>
<li>모바일 네트워크</li>
<li>산업 공학/경영 공학의 운영 (Operation) 연구</li>
<li>로봇 공학</li>
<li>교통 공학</li>
<li>VLSI 디자인 분야 등</li>
</ul>
<hr>
<h1 id="44-부분-배낭-knapsack-문제">4.4 부분 배낭 (Knapsack) 문제</h1>
<ul>
<li>n개의 물건이 각각 1개씩 있고, 각 물건은 무게와 가치를 가지고 있으며, 배낭이 한정된 무게의 물건들을 담을 수 있을 때, 최대의 가치를 갖도록 배낭에 넣을 물건들을 정하는 문제</li>
<li>부분 배낭 (Fractional Knapsack) 문제<ul>
<li>물건을 부분적으로 담는 것을 허용</li>
<li>그리디 알고리즘으로 해결</li>
</ul>
</li>
<li>0-1 배낭 문제<ul>
<li>부분 배낭 문제의 원형으로 물건을 통째로 배낭에 넣어야 한다.</li>
<li>동적 계획 알고리즘, 백트래킹 기법, 분기 한정 기법으로 해결</li>
</ul>
</li>
</ul>
<h3 id="아이디어">아이디어</h3>
<ul>
<li>부분 배낭 문제에서는 물건을 부분적으로 배낭에 담을 수 있으므로, 최적해를 위해서 ‘욕심을 내어’ 단위 무게 당 가장 값나가는 물건을 배낭에 넣고, 계속해서 그 다음으로 값나가는 물건을 넣는다.</li>
<li>만일 물건을 ‘통째로’ 배낭에 넣을 수 없으면, 배낭에 넣을 수 있을 만큼만 물건을 부분적으로 배낭에 담는다.</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">FractionalKnapsack
입력: n개의 물건, 각 물건의 무게와 가치, 배낭의 용량 C
출력: 배낭에 담은 물건 리스트 L과 배낭 속의 물건 가치의 합
v
1. 각 물건에 대해 단위 무게 당 가치를 계산한다.
2. 물건들을 단위 무게 당 가치를 기준으로 내림차순으로 정렬
하고, 정렬된 물건 리스트를 S라고 하자.
3. L=∅, w=0, v=0
// L은 배낭에 담을 물건 리스트, w는 배낭에 담긴 물건들의 무게
의 합, v는 배낭에 담긴 물건들의 가치의 합
4. S에서 단위 무게 당 가치가 가장 큰 물건 x를 가져온다.
5. while w + (x의 무게) ≤ C
6. x를 L에 추가
7. w = w + (x의 무게)
8. v = v + (x의 가치)
9. x를 S에서 제거
10. S에서 단위 무게 당 가치가 가장 큰 물건 x를 가져온다.
11. If C-w &gt; 0 // 배낭에 물건을 부분적으로 담을 여유가 있으면
12. 물건 x를 (C-w) 만큼만 L에 추가
13. v = v + (C-w) 만큼의 x의 가치
14. return L, v</code></pre>
<h3 id="수행-과정">수행 과정</h3>
<ul>
<li>배낭의 최대 용량 = 40그램</li>
<li>단위 무게 당 가치로 정렬: S=[백금, 금, 은, 주석]</li>
<li>물건 단위 그램당 가치</li>
</ul>
<table>
<thead>
<tr>
<th>백금</th>
<th>6만원</th>
</tr>
</thead>
<tbody><tr>
<td>금</td>
<td>5만원</td>
</tr>
<tr>
<td>은</td>
<td>4천원</td>
</tr>
<tr>
<td>주석</td>
<td>1천원</td>
</tr>
<tr>
<td>- 백금을 통째로 담는다.</td>
<td></td>
</tr>
<tr>
<td>- 배낭에 담긴 물건(들)의 무게</td>
<td></td>
</tr>
<tr>
<td>w = 10, 얻는 가치 v = 60</td>
<td></td>
</tr>
<tr>
<td>- 금을 통째로 담는다.</td>
<td></td>
</tr>
<tr>
<td>- 배낭에 담긴 물건(들)의 무게</td>
<td></td>
</tr>
<tr>
<td>w = 25, v = 60+ 75 = 135</td>
<td></td>
</tr>
<tr>
<td>- 은을 통째로 담으려 하지만</td>
<td></td>
</tr>
<tr>
<td>- 배낭에 담긴 물건(들)의 무게 w = 25 + 25 = 50이 되어 배낭 용량 초과</td>
<td></td>
</tr>
<tr>
<td>- 은을 40 -25 = 15 만큼만 담는다.</td>
<td></td>
</tr>
<tr>
<td>- 배낭에 담긴 물건(들)의 무게</td>
<td></td>
</tr>
<tr>
<td>w = 40, v = 135 + (0.4x15) = 141 만원</td>
<td></td>
</tr>
</tbody></table>
<h3 id="시간-복잡도-3">시간 복잡도</h3>
<ul>
<li>Line 1: n개의 물건 각각의 단위 무게 당 가치를 계산하는 데는 O(n) 시간 소요</li>
<li>Line 2: 물건의 단위 무게 당 가치에 대해서 정렬하기 위해 O(nlogn) 시간 소요</li>
<li>Line 5~10: while-루프의 수행은 n번을 넘지 않으며, 루프 내부의 수행은 O(1) 시간 소요</li>
<li>Line 11~14: 각각 O(1) 시간 소요</li>
<li>알고리즘의 시간 복잡도: O(n)+O(nlogn)+nxO(1)+O(1) = O(nlogn)</li>
</ul>
<h3 id="응용-2">응용</h3>
<ul>
<li>0-1 배낭 문제는 최소의 비용으로 자원을 할당하는 문제로서, 조합론, 계산이론, 암호학, 응용 수학 분야에서 기본적인 문제로 다뤄진다.</li>
<li>‘버리는 부분 최소화하는’ 원자재 자르기</li>
<li>자산투자 및 금융 포트폴리오에서의 최선의 선택</li>
<li>Merkle–Hellman 배낭 암호 시스템에 사용</li>
</ul>
<hr>
<h1 id="45-집합-커버-set-cover--문제">4.5 집합 커버 (Set Cover)  문제</h1>
<ul>
<li>n개의 원소를 가진 집합 U가 있고, U의 부분집합들을 원소로 하는 집합 F가 주어질 때, F의 원소들인 집합들 중에서 어떤 집합들을 선택하여 합집합하면 U와 같게 되는가?</li>
<li>집합 F에서 선택하는 집합들의 수를 최소화하는 문제</li>
<li>신도시 학교 배치<ul>
<li>신도시를 계획하는 데 있어서 학교 배치의 예</li>
<li>10개의 마을이 신도시에 만들어질 계획이다.</li>
<li>다음 조건이 만족되도록 학교 위치를 선정해야 한다.</li>
<li>학교는 마을에 위치해야 한다.</li>
<li>등교 거리는 걸어서 15분 이내이어야 한다.</li>
<li>어느 마을에 학교를 신설해야 학교의 수가 최소가 되는가?</li>
</ul>
</li>
</ul>
<p><img src="attachment:39757381-cd0d-42ec-940a-4bb1df71ebd0:image.png" alt="image.png"></p>
<h3 id="최적해">최적해</h3>
<ul>
<li><p>어느 마을에 학교를 신설해야 학교의 수가 최소가 되는가?</p>
<ul>
<li><p>2번 마을에 학교를 만들면</p>
<p>→ 1, 2, 3, 4, 8 마을의 학생들이 15분 이내에 등교 가능</p>
<p>→ 즉, 마을 1, 2, 3, 4, 8이 커버된다.</p>
</li>
<li><p>6번 마을에 학교를 만들면</p>
<p>→ 마을 5, 6, 7, 9, 10이 커버된다.</p>
</li>
<li><p>2번과 6번 마을에 학교를 배치하면 모든 마을이 커버된다.</p>
<p>→ 최소의 학교 수 = 2개</p>
</li>
</ul>
</li>
</ul>
<p><img src="attachment:2ae93a2b-b7d1-4001-818e-e1019272d402:image.png" alt="image.png"></p>
<h3 id="집합-커버">집합 커버</h3>
<ul>
<li>신도시 계획 문제를 집합 커버 문제로 변환
U = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 신도시의 마을 10개
F = {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10} // Si는 마을 i에 학교를 배치했을 때 커버되는 마을의 집합
S1={1, 2, 3, 8} S5={4, 5, 6, 7} S9 ={6, 9}
S2={1, 2, 3, 4, 8} S6={5, 6, 7, 9, 10} S10={6, 10}
S3={1, 2, 3, 4} S7={4, 5, 6, 7}
S4={2, 3, 4, 5, 7, 8} S8={1, 2, 4, 8}
• Si 집합들 중에서 어떤 집합들을 선택해야 그들의 합집합이 U와 같은가?
• 단, 선택된 집합의 수는 최소이어야</li>
<li>최적해
• S2∪S6 = {1, 2, 3, 4, 8} ∪ {5, 6, 7, 9, 10}
= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} = U</li>
<li>단순한 해결 방법<ul>
<li>집합 커버 문제의 최적해는 어떻게 찾아야 할까?<ul>
<li>F에 n개의 집합들이 있다고 가정해보자.</li>
</ul>
</li>
<li>가장 단순한 방법<ul>
<li>F에 있는 집합들의 모든 조합을 1개씩 합집합하여 U가 되는지 확인하고,</li>
<li>U가 되는 조합의 집합 수가 최소인 것을 찾는다.</li>
<li>F={S1, S2, S3}일 경우 모든 조합<ul>
<li>S1, S2, S3, S1∪S2, S1∪S3, S2∪S3, S1∪S2∪S3</li>
<li>집합이 1개인 경우 3개 = 3C1</li>
<li>집합이 2개인 경우 3개 = 3C2</li>
<li>집합이 3개인 경우 1개 = 3C3</li>
<li>총합은 3+3+1= 7 = 2^3-1 개</li>
</ul>
</li>
</ul>
</li>
<li>n개의 원소가 있을 경우<ul>
<li>최대 (2n-1)개를 검사하여야</li>
<li>n이 커지면 최적해를 찾는 것은 실질적으로 불가능</li>
</ul>
</li>
<li>이를 극복하기 위한 방법<ul>
<li>최적해를 찾는 대신에 최적해에 근접한 근사해 (Approximation solution)를 찾는다.</li>
</ul>
</li>
</ul>
</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">SetCover
입력: U, F = {Si
}, i=1,⋯,n
출력: 집합 커버 C
1. C = ∅
2. while U ≠ ∅
3. U의 원소를 가장 많이 가진 집합 Si를 F에서 선택
4. U = U - Si
5. Si를 F에서 제거하고, Si를 C에 추가
6. return C</code></pre>
<h3 id="수행-과정-1">수행 과정</h3>
<p>U = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}</p>
<p>F = {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10}</p>
<p>S1={1, 2, 3, 8}                       S6={5, 6, 7, 9, 10}
S2={1, 2, 3, 4, 8}                  S7={4, 5, 6, 7}
S3={1, 2, 3, 4}                      S8={1, 2, 4, 8}
S4={2, 3, 4, 5, 7, 8}             S9={6, 9}
S5={4, 5, 6, 7}                     S10={6, 10}</p>
<p><img src="attachment:d80f616a-67d8-4d71-81e4-1ef93f38e5a6:image.png" alt="image.png"></p>
<p><img src="attachment:59bf67f8-beb2-48e0-ba43-56f08f0cb157:d13b5620-416c-4885-9647-323ec3e86f32.png" alt="image.png"></p>
<p><img src="attachment:307b0c83-a2a5-47e0-85d0-2e0c6f7599f8:image.png" alt="image.png"></p>
<p><img src="attachment:b4d4606c-a9de-412d-b5ca-e78ec395050f:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-4">시간 복잡도</h3>
<ul>
<li>먼저 while-루프가 수행되는 횟수는 최대 n회<ul>
<li>루프가 1회 수행될 때마다 집합 U의 원소 1개씩만 커버된다면, 최악의 경우 루프가 n번 수행되어야 하기 때문</li>
</ul>
</li>
<li>루프가 1회 수행될 때<ul>
<li>Line 3: U의 원소들을 가장 많이 포함하고 있는 집합 S를 찾으려면, 현재 남아있는 Si들 각각을 U와 비교하여야</li>
<li>Si들의 수가 최대 n이라면, 각 Si와 U의 비교는 O(n) 시간이 걸리므로, line 3은 O(n2) 시간</li>
<li>집합 U에서 집합 Si의 원소를 제거하므로 O(n) 시간</li>
<li>Si를 F에서 제거하고, Si를 C에 추가하는 것은 O(1) 시간</li>
</ul>
</li>
<li>시간 복잡도: n x O(n^2) = O(n^3)</li>
</ul>
<h3 id="응용-3">응용</h3>
<ul>
<li>도시 계획 (City Planning)에서 공공 기관 배치하기</li>
<li>경비 시스템: 경비가 요구되는 장소의 CCTV 카메라의 최적 배치</li>
<li>컴퓨터 바이러스 찾기</li>
<li>대기업의 구매 업체 선정</li>
<li>기업의 경력 직원 고용</li>
<li>그 외에도 비행기 조종사 스케줄링, 조립 라인 균형화, 정보 검색 등에 활용</li>
</ul>
<hr>
<h1 id="46-작업-스케줄링-job-scheduling-문제">4.6 작업 스케줄링 (Job Scheduling) 문제</h1>
<ul>
<li>작업의 수행 시간이 중복되지 않도록 모든 작업을 가장 적은 수의 기계에 배정하는 문제<ul>
<li>학술대회에서 발표자들을 강의실에 배정하는 문제와 같다.</li>
<li>발표= ‘작업’, 강의실= ‘기계’</li>
</ul>
</li>
<li>작업 스케줄링 문제에 주어진 문제 요소<ul>
<li>작업의 수<ul>
<li>입력의 크기이므로 알고리즘을 고안하기 위해 고려되어야 하는 직접적인 요소는 아니다.</li>
</ul>
</li>
<li>각 작업의 시작시간과 종료시간</li>
<li>작업의 길이<ul>
<li>작업의 시작시간과 종료시간은 정해져 있으므로 작업의 길이도 주어진 것</li>
</ul>
</li>
</ul>
</li>
<li>시작시간, 종료시간, 작업 길이에 대한 그리디 알고리즘<ul>
<li>빠른 시작시간 작업 우선 (Earliest start time first) 배정</li>
<li>빠른 종료시간 작업 우선 (Earliest finish time first) 배정</li>
<li>짧은 작업 우선 (Shortest job first) 배정</li>
<li>긴 작업 우선 (Longest job first) 배정</li>
</ul>
</li>
<li>위 4가지 중 첫 번째 알고리즘을 제외하고 나머지 3가지는 항상 최적해를 찾지 못함</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">JobScheduling
입력: n개의 작업 t1
, t2
, ⋯, tn
출력: 각 기계에 배정된 작업 순서
1. 시작 시간으로 정렬한 작업 리스트: L
2. while L ≠ ∅
3. L에서 가장 이른 시작 시간 작업 ti를 가져온다.
4. if ti를 수행할 기계가 있으면
5. ti를 수행할 수 있는 기계에 배정
6. else
7. 새 기계에 ti를 배정
8. ti를 L에서 제거
9. return 각 기계에 배정된 작업 순서</code></pre>
<h3 id="수행-과정-2">수행 과정</h3>
<ul>
<li>t1=[7,8], t2=[3,7], t3=[1,5], t4=[5,9], t5=[0,2], t6=[6,8], t7=[1,6]<ul>
<li>[s, f]에서, s는 시작 시간, f는 종료 시간</li>
</ul>
</li>
<li>정렬: L = {[0,2], [1,6], [1,5], [3,7], [5,9], [6,8], [7,8]}</li>
</ul>
<p><img src="attachment:69227fcd-1835-4efa-a2d7-d942a4365593:image.png" alt="image.png"></p>
<p><img src="attachment:267fcb98-af6f-4c28-8c92-e8e2fcf45590:image.png" alt="image.png"></p>
<p><img src="attachment:a61b769e-1e8d-41aa-ab0a-a63d3cebdb4c:image.png" alt="image.png"></p>
<p><img src="attachment:6ec07e95-475d-4e24-9ce0-639d3175ca71:image.png" alt="image.png"></p>
<h2 id="시간-복잡도-5">시간 복잡도</h2>
<ul>
<li>Line 1: 정렬 시간 O(nlogn)</li>
<li>while-루프<ul>
<li>작업을 L에서 가져다 수행 가능한 기계를 찾아서 배정하므로 O(m) 시간 소요, 단, m은 사용된 기계의 수</li>
<li>while-루프가 수행된 총 횟수는 n번이므로, line 2~9까지는 O(m) x n = O(mn) 시간 소요</li>
</ul>
</li>
<li>시간 복잡도: O(nlogn)+O(mn)</li>
</ul>
<h2 id="응용-4">응용</h2>
<ul>
<li>비즈니스 프로세싱</li>
<li>공장 생산 공정</li>
<li>강의실/세미나 룸 배정</li>
<li>컴퓨터 태스크 스케줄링 등</li>
</ul>
<hr>
<h1 id="47-허프만-파일-압축-file-compression-문제">4.7 허프만 파일 압축 (file compression) 문제</h1>
<ul>
<li>파일의 각 문자가 8 bit 아스키 (ASCII) 코드로 저장되면, 그 파일의 bit 수는 8 x (파일의 문자 수)</li>
<li>파일의 각 문자는 일반적으로 고정된 크기의 코드로 표현</li>
<li>고정된 크기의 코드로 구성된 파일을 저장하거나 전송할 때 파일의 크기를 줄이고, 필요시 원래의 파일로 변환할 수 있으면, 메모리 공간을 효율적으로 사용할 수 있고, 파일 전송 시간을 단축</li>
<li>파일의 크기를 줄이는 방법을 파일 압축 (file compression)이라 함</li>
</ul>
<h2 id="아이디어-1">아이디어</h2>
<ul>
<li>허프만 (Huffman) 압축은 파일에 빈번히 나타나는 문자에는 짧은 이진 코드를 할당하고, 드물게 나타나는 문자에는 긴 이진 코드를 할당</li>
<li>허프만 압축 방법으로 변환시킨 문자 코드들 사이에는 접두부 특성 (prefix property)이 존재<ul>
<li>각 문자에 할당된 이진 코드는 어떤 다른 문자에 할당된 이진 코드의 접두부 (prefix)가 되지 않는다.</li>
<li>[예제] 문자 ‘a’에 할당된 코드가 ‘101’이라면, 모든 다른 문자의 코드는 ‘101’로 시작되지 않으며 또한 ‘1’이나 ‘10’도 아니다.</li>
<li>접두부 특성의 장점은 코드와 코드 사이를 구분할 특별한 코드가 필요 없다.<ul>
<li>101#10#1#111#0#⋯에서 ‘#’가 인접한 코드를 구분 짓고 있는데, 허프만 압축에서는 이러한 특별한 코드 없이 파일을 압축/해제 가능</li>
</ul>
</li>
<li>허프만 압축은 입력 파일에 대해 각 문자의 빈도수 (문자가 파일에 나타나는 횟수)에 기반을 둔 이진 트리를 만들어서, 각 문자에 이진 코드를 할당<ul>
<li>이러한 이진 코드를 허프만 코드라고 함</li>
</ul>
</li>
</ul>
</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">HuffmanCoding
입력: 입력 파일의 n개의 문자에 대한 각각의 빈도수
출력: 허프만 트리
1. 각 문자 당 노드를 만들고, 그 문자의 빈도수를 노드에 저장
2. n 노드의 빈도수에 대해 우선 순위 큐 Q를 만든다.
3. while Q에 있는 노드 수 ≥ 2
4. 빈도수가 가장 적은 2개의 노드 (A와 B)를 Q에서 제거
5. 새 노드 N을 만들고, A와 B를 N의 자식 노드로 만든다
6. N의 빈도수 = A의 빈도수 + B의 빈도수
7. 노드 N을 Q에 삽입
8. return Q // 허프만 트리의 루트를 리턴</code></pre>
<h3 id="수행-과정-3">수행 과정</h3>
<ul>
<li><p>각 문자의 빈도수에 대해</p>
<p>  A: 450 T: 90 G: 120 C: 270</p>
</li>
<li><p>Line 2를 수행한 후의 Q</p>
<ul>
<li><p>우선 순위 큐 Q를 생성</p>
<p><img src="attachment:3c2f454d-f4bc-427b-b0dc-49dc3e3e83a1:image.png" alt="image.png"></p>
</li>
</ul>
</li>
</ul>
<p><img src="attachment:2f8b58ed-59e0-401d-b133-b95ee587c344:image.png" alt="image.png"></p>
<p><img src="attachment:d949b671-5c6b-4132-8299-24e452a089fe:4c87e699-421a-4d29-b92d-881a142369cf.png" alt="image.png"></p>
<p><img src="attachment:0f0417af-9d67-4cb6-8475-979275fb9a7b:image.png" alt="image.png"></p>
<ul>
<li>반환된 트리를 살펴보면 각 이파리 (단말) 노드에만 문자가 있다.<ul>
<li>루트로부터 왼쪽 자식 노드로 내려가면 ‘0’을, 오른쪽 자식 노드로 내려가면 ‘1’을 부여하면서, 각 이파리에 도달할 때까지의 이진수를 추출하여 문자의 이진 코드를 얻는다.</li>
</ul>
</li>
</ul>
<p><img src="attachment:52332ce7-eac2-426b-9977-b5047ab90335:image.png" alt="image.png"></p>
<h3 id="압축률">압축률</h3>
<ul>
<li>예제에서 ‘A’는 ‘0’, ‘T’는 ‘100’, ‘G’는 ‘101’, ‘C’는 ‘11’의 코드가 각각 할당된다.<ul>
<li>할당된 코드들을 보면, 가장 빈도수가 높은 ‘A’가 가장 짧은 코드를 가지고, 따라서 루트의 자식이 되어 있고, 빈도수가 낮은 문자는 루트에서 멀리 떨어지게 되어 긴 코드를 가진다.</li>
<li>이렇게 얻은 코드는 접두부 특성을 가진다.</li>
</ul>
</li>
<li>압축된 파일의 bit 수<ul>
<li>(450x1)+(90x3)+(120x3)+(270x2) = 1,620 bits</li>
</ul>
</li>
<li>아스키 코드로 된 파일 크기<ul>
<li>(450+90+120+270)x8 = 7,440 bits</li>
</ul>
</li>
<li><strong>파일 압축률</strong><ul>
<li>(1,620/7,440)x100 = 21.8%이며, 원래의 약 1/5 크기로 압축</li>
</ul>
</li>
</ul>
<h3 id="복호화">복호화</h3>
<ul>
<li><p>예제에서 얻은 허프만 코드로 아래의 압축된 부분에 대해서 압축을 해제하여 보자.</p>
<pre><code>                                            10110010001110101010100</code></pre></li>
</ul>
<p>101 / 100 / 100 / 0 / 11 / 101 / 0 / 101 / 0 / 100</p>
<p>→                 G T T A C G A G A T</p>
<h3 id="시간-복잡도-6">시간 복잡도</h3>
<ul>
<li>Line 1: n개의 노드를 만들고, 각 빈도수를 노드에 저장하므로 O(n) 시간</li>
<li>Line 2: n개의 노드로 우선순위 큐 Q를 만든다.<ul>
<li>여기서 우선 순위 큐로서 이진 힙 자료구조를 사용하면 O(n) 시간</li>
</ul>
</li>
<li>Line 3~7<ul>
<li>최소 빈도수를 가진 노드 2개를 Q에서 제거하는 힙의 삭제 연산과 새 노드를 Q에 삽입하는 연산을 수행하므로 O(logn) 시간 소요</li>
<li>while-루프는 (n-1)번 반복<ul>
<li>왜냐하면 루프가 1번 수행될 때마다 Q에서 2개의 노드를 제거하고 1개를 Q에 추가하기 때문</li>
</ul>
</li>
<li>(n-1) x O(logn) = O(nlogn)</li>
</ul>
</li>
<li>Line 8<ul>
<li>트리의 루트를 반환하는 것이므로 O(1) 시간</li>
</ul>
</li>
<li>시간 복잡도는 O(n)+O(n)+O(nlogn)+ O(1) = O(nlogn)</li>
</ul>
<h3 id="응용-5">응용</h3>
<ul>
<li>팩스(FAX), 대용량 데이터 저장, 멀티미디어 (Multimedia), MP3 압축 등에 활용</li>
<li>정보 이론 (Information Theory) 분야에서 엔트로피 (Entropy)를 계산하는데 활용<ul>
<li>이는 자료의 불특정성을 분석하고 예측하는데 이용</li>
</ul>
</li>
</ul>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li><p>그리디 알고리즘은 (입력) 데이터 간의 관계를 고려하지 않고 수행 과정에서 ‘욕심내어’ 최적값을 가진 데이터를 선택하며, 선택한 값들을 모아서 문제의 최적해를 찾는다.</p>
</li>
<li><p>그리디 알고리즘은 문제의 최적해 속에 부분 문제의 최적해가 포함되어 있고, 부분 문제의 해 속에 그 보다 작은 부분 문제의 해가 포함되어 있다. 이를 최적 부분 구조 (Optimal Substructure) 또는 최적성 원칙 (Principle of Optimality)이라고 한다.</p>
</li>
<li><p>동전 거스름돈 문제를 해결하는 가장 간단한 방법은 남은 액수를 초과하지 않는 조건하에 가장 큰 액면의 동전을 취하는 것이다. 단, 일반적인 경우에는 최적해를 찾으나 항상 최적해를 찾지는 못한다.</p>
</li>
<li><p>크러스컬의 알고리즘은 가중치가 가장 작으면서 사이클을 만들지 않는 간선을 추가시키어 트리를 만든다. 시간 복잡도는 O(mlogm). 단, m은 그래프의 간선의 수</p>
</li>
<li><p>프림의 알고리즘은 최소의 가중치로 현재까지 만들어진 트리에 연결되는 간선을 트리에 추가시킨다. 시간 복잡도는 O(n^2)</p>
</li>
<li><p>다익스트라의 알고리즘은 출발점으로부터 최단 거리가 확정되지 않은 점들 중에서 출발점으로부터 가장 가까운 점을 추가하고, 그 점의 최단 거리를 확정한다. 시간 복잡도는 O(n^2)</p>
</li>
<li><p>부분 배낭(Fractional Knapsack) 문제에서는 단위 무게 당 가장 값나가는 물건을 계속해서 배낭에 담는다. 마지막엔 배낭에 넣을 수 있을 만큼만 물건을 부분적으로 배낭에 담는다. 시간 복잡도는 O(nlogn)</p>
</li>
<li><p>집합 커버(Set Cover) 문제는 근사(Approximation) 알고리즘을 이용하여 근사해를 찾는 것이 보다 실질적이다. U의 원소들을 가장 많이 포함하고 있는 집합을 항상 F에서 선택한다. 시간 복잡도는 O(n^3)</p>
</li>
<li><p>작업 스케줄링(Job Scheduling) 문제는 빠른 시작시간 작업 먼저(Earliest start time first) 배정하는 그리디 알고리즘으로 최적해를 찾는다 . 시간 복잡도는 O(nlogn)+O(mn). n은 작업의 수이고, m은 기계의 수</p>
</li>
<li><p>허프만 압축은 파일에 빈번히 나타나는 문자에는 짧은 이진 코드를 할당하고, 드물게 나타나는 문자에는 긴 이진 코드를 할당</p>
<ul>
<li>n이 문자의 수일 때, 시간 복잡도는 O(nlogn)</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 : Ch.03 Divide And Conquer]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.03-Divide-And-Conquer</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.03-Divide-And-Conquer</guid>
            <pubDate>Mon, 13 Oct 2025 11:25:45 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>주어진 문제의 입력을 분할하여 문제를 해결(정복) 하는 방식의 알고리즘</p>
<ul>
<li>분할한 입력에 대하여 동일한 알고리즘을 적용하여 해를 계산</li>
<li>이들의 해를 취합하여 원래 문제의 해를 얻음</li>
</ul>
</li>
<li><p>부분 문제와 부분 해</p>
<ul>
<li>분할된 입력에 대한 문제를 부분 문제 ( subproblem)</li>
<li>부분 문제의 해를 부분 해</li>
<li>부분 문제는 더 이상 분할할 수 없을 때까지 분할</li>
</ul>
</li>
<li><p>분할 과정</p>
<ul>
<li><p>크기가 n인 입력을 3개로 분할하고, 각각 분할된 부분 문제의 크기가 n/2일 경우의 분할 예</p>
<p><img src="attachment:3352b5c9-4246-45a2-b537-a141dec289a4:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>입력 크기가 n일 때 총 분할 횟수</p>
<ul>
<li>총 분할한 횟수 = k라면</li>
<li>1번 분할 후 각 입력 크기 n/2</li>
<li>2번 분할 후 각 입력 크기 n/2^2
⋮</li>
<li>k번 분할 후 각 입력 크기 n/2^k</li>
<li>n/2^k = 1일 때 분할 못함</li>
<li>k = log2n</li>
</ul>
</li>
<li><p>정복 과정</p>
<ul>
<li>대부분의 분할 정복 알고리즘 문제의 입력을 단순히 분할만 해서는 해를 구할 수 없다.
→ <strong><em>분할 된 부분 문제들을 정복 해야 함</em></strong></li>
<li><strong>부분 해</strong>를 찾아야 한다.<ul>
<li>정복하는 방법은 문제에 따라 다르다.</li>
<li>일반적으로 부분 문제들의 해를 취합하여 보다 큰 부분 문제의 해를 구한다.</li>
</ul>
</li>
</ul>
</li>
<li><p>분할 정복 알고리즘의 분류</p>
<ul>
<li>분할 정복 알고리즘은 분할 되는 부분 문제의 수와 부분 문제의 크기에 따라서 다음과 같이 분류</li>
<li>문제가 a개로 분할 되고, 부분 문제의 크기가 1/b로 감소하는 알고리즘<ul>
<li>a=b=2인 경우: 합병 정렬, 최근접 점의 쌍 찾기, 공제선 문제</li>
<li>a=3, b=2인 경우: 큰 정수의 곱셈</li>
<li>a=4, b=2인 경우: 큰 정수의 곱셈</li>
<li>a=7, b=2인 경우: 스트라센(Strassen)의 행렬 곱셈 알고리즘</li>
</ul>
</li>
</ul>
</li>
<li><p>분할 정복 알고리즘의 분류</p>
<ul>
<li>문제가 2개로 분할되고, 부분 문제의 크기가 일정하지 않은 크기로 감소하는 알고리즘<ul>
<li>퀵 정렬</li>
</ul>
</li>
<li>문제가 2개로 분할되나, 그 중에 1개의 부분 문제는 고려할 필요가 없으며, 부분 문제의 크기가 1/2로 감소하는 알고리즘<ul>
<li>이진 탐색</li>
</ul>
</li>
<li>문제가 2개로 분할되나, 그 중에 1개의 부분 문제는 고려할 필요가 없으며, 부분 문제의 크기가 일정하지 않은 크기로 감소하는 알고리즘<ul>
<li>선택 문제 알고리즘</li>
</ul>
</li>
<li>부분 문제의 크기가 1, 2개씩 감소하는 알고리즘<ul>
<li>삽입 정렬, 피보나치 수의 계산</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h1 id="31-합병-정렬--merge-sort-">3.1 합병 정렬 ( Merge Sort )</h1>
<ul>
<li><p>합병 정렬은 입력이 2개의 부분 문제로 분할되고, 부분문제의 크기가 1/2로 감소하는 분할 정복 알고리즘</p>
<ul>
<li>n개의 숫자들을 n/2개씩 2개의 부분 문제로 분할</li>
<li>각각의 부분 문제를 순환으로 합병 정렬</li>
<li>2개의 정렬된 부분을 합병하여 정렬(정복)</li>
</ul>
</li>
<li><p><strong>합병 과정이 문제를 정복하는 것</strong></p>
</li>
<li><p>합병 (merge)</p>
<ul>
<li>2개의 각각 정렬된 숫자들을 1개의 정렬된 숫자로 합치는 것
EX) 두 배열 AB를 합병 ?
배열 A: 6 14 18 20 29
배열 B: 1 2 15 25 30 45
⇨ 배열 C: 1 2 6 14 15 18 20 25 29 30 45 ( 오름차순 정리 )</li>
</ul>
</li>
<li><p>의사 코드</p>
<pre><code class="language-c">  MergeSort(A,p,q)
  입력: A[p]~A[q]
  출력: 정렬된 A[p]~A[q]
  1. if ( p &lt; q ) { // 배열의 원소의 수가 2개 이상이면
  2. k = ⌊(p+q)/2⌋ // k는 중간 원소의 인덱스
  3. MergeSort(A,p,k) // 앞부분 순환 호출
  4. MergeSort(A,k+1,q) // 뒷부분 순환 호출
  5. A[p]~A[k]와 A[k+1]~A[q]를 합병
  }</code></pre>
</li>
</ul>
<p><img src="attachment:7552f560-45b2-40a3-8755-e0c5e37438a1:image.png" alt="image.png"></p>
<h3 id="시간-복잡도">시간 복잡도</h3>
<ul>
<li><p>분할하는 부분은 배열의 중간 인덱스 계산과 2회의 순환 호출이므로 O(1) 시간 소요</p>
</li>
<li><p>합병의 수행 시간은 입력의 크기에 비례.</p>
<ul>
<li><p>2개의 정렬된 배열 A와 B의 크기가 각각 m과 n이라면, 최대 비교 횟수
= (m+n-1)</p>
</li>
<li><p>합병의 시간 복잡도 = O(m+n)</p>
<p><img src="attachment:36b0518b-00d8-4e6e-80e7-2049e816e4e7:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>합병 정렬에서 수행되는 총 비교 횟수</p>
<ul>
<li>각각의 합병에 대해서 몇 번의 비교를 수행 한 지를 계산하여 이들을 모두 합한 수</li>
</ul>
</li>
<li><p>층별 비교 횟수</p>
<ul>
<li><p>각 층을 살펴보면 모든 숫자(즉, n=8개의 숫자)가 합병에 참여</p>
</li>
<li><p>합병은 입력 크기에 비례하므로 각 층에서 수행된 비교 횟수는 O(n)</p>
<p><img src="attachment:d3bcce26-14d2-4020-9e73-021abd3e9b9f:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>층수의 계산</p>
<ul>
<li><p>층수를 세어보면, 8개의 숫자를 반으로, 반의 반으로 반의 반의 반으로 나눈다.</p>
</li>
<li><p>이 과정을 통하여 세 층이 만들어진다.</p>
<p><img src="attachment:54f4b828-e339-41c8-b1b6-9894f861f21e:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>입력의 크기가 n일 때 몇 개의 층이 만들어질까?</p>
<ul>
<li>n을 계속하여 1/2로 나누다가, 더 이상 나눌 수 없는 크기인 1이 될 때 분할을 중단한다.</li>
<li>따라서 k번 1/2로 분할했으면 k개의 층이 생기는 것이고, k는 2^k=n으로부터 log2n임을 알 수 있다.</li>
</ul>
</li>
<li><p>합병 정렬의 시간 복잡도:</p>
<ul>
<li>(층수) x O(n) = log2n x O(n) = O(nlogn)</li>
</ul>
</li>
<li><p>합병 정렬의 단점</p>
<ul>
<li>대부분의 정렬 알고리즘들은 입력을 위한 메모리 공간과 O(1) 크기의 메모리 공간 만을 사용하면서 정렬 수행<ul>
<li>O(1) 크기의 메모리 공간이란 입력 크기 n과 상관없는 크기의 공간(예를 들어, 변수, 인덱스 등)을 의미</li>
</ul>
</li>
<li>합병 정렬의 공간 복잡도: O(n)<ul>
<li>입력을 위한 메모리 공간 (입력 배열)외에 추가로 입력과 같은 크기의 공간 (임시 배열)이 별도로 필요.</li>
<li>2개의 정렬된 부분을 하나로 합병하기 위해, 합병된 결과를 저장할 곳이 필요하기 때문</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="응용">응용</h3>
<ul>
<li>합병 정렬은 외부 정렬의 기본이 되는 정렬 알고리즘</li>
<li>연결 리스트에 있는 데이터를 정렬할 때에도 퀵 정렬이나 힙 정렬 보다 훨씬 효율적</li>
<li>멀티코어(Multi-Core) CPU와 다수의 프로세서로 구성된 그래픽 처리 장치(Graphic Processing Unit)의 등장으로 정렬 알고리즘을 병렬화하는 데에 합병 정렬 알고리즘이 활용</li>
</ul>
<h3 id="간단한-코드-구현">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

void merge(int A[], int p, int k, int q) {
    int n = q - p + 1;
    int* tmp = (int*)malloc(sizeof(int) * n);
    int i = p, j = k + 1, t = 0;

    while (i &lt;= k &amp;&amp; j &lt;= q) {
        if (A[i] &lt;= A[j]) tmp[t++] = A[i++];
        else              tmp[t++] = A[j++];
    }
    while (i &lt;= k) tmp[t++] = A[i++];
    while (j &lt;= q) tmp[t++] = A[j++];

    for (int m = 0; m &lt; n; ++m) A[p + m] = tmp[m];
    free(tmp);
}

void MergeSort(int A[], int p, int q) {
    if (p &lt; q) {
        int k = (p + q) / 2;
        MergeSort(A, p, k);
        MergeSort(A, k + 1, q);
        merge(A, p, k, q);
    }
}

int main(void) {
    int A[] = { 37, 10, 22, 30, 35, 13, 25, 24 };
    int n = sizeof(A) / sizeof(A[0]);

    MergeSort(A, 0, n - 1);

    for (int i = 0; i &lt; n; ++i) printf(&quot;%d &quot;, A[i]);
    printf(&quot;\n&quot;);
    return 0;
}</code></pre>
<hr>
<h1 id="32-퀵-정렬--quick-sort-">3.2 퀵 정렬 ( Quick Sort )</h1>
<ul>
<li><p><strong>퀵 정렬은 분할 정복 알고리즘으로 분류</strong></p>
<ul>
<li>사실 알고리즘이 수행되는 과정을 살펴보면 <strong>정복 후 분할하는 알고리즘</strong></li>
</ul>
</li>
<li><p>퀵 정렬 알고리즘은 문제를 <strong>2개의 부분 문제로 분할</strong></p>
<ul>
<li>각 부분 문제의 크기가 <strong><em>일정하지 않은 형태의 분할 정복 알고리즘</em></strong></li>
</ul>
</li>
<li><p>퀵 정렬의 아이디어</p>
<ul>
<li><p>퀵 정렬은 <strong>피봇 (pivot)</strong> 이라 일컫는 배열의 원소(숫자)를 기준으로 피봇보다 작은 숫자들은 왼편으로, 피봇보다 큰 숫자들은 오른편에 위치하도록 분할하고, 피봇을 그 사이에 놓는다.</p>
</li>
<li><p>퀵 정렬은 분할된 부분문제들에 대해서도 위와 동일한 과정을 순환으로 수행하여 정렬</p>
<p><img src="attachment:9b6ca4aa-fc76-41b5-acd3-c358423353f9:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p><strong>피봇은 분할된 왼편이나 오른편 부분에 포함되지 않음</strong></p>
<ul>
<li><p>피봇이 60이라면, 60은 [20 40 10 30 50]과 [70 90 80] 사이에 위치한다.</p>
<p><img src="attachment:e212fdf4-31ad-4bb8-af86-1a96990b719e:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>의사 코드</p>
</li>
</ul>
<pre><code class="language-c">QuickSort(A, left, right)
입력: 배열 A[left]~A[right]
출력: 정렬된 배열 A[left]~A[right]
1. if (left &lt; right) {
2. 피봇을 A[left]~A[right]에서 선택하고,
피봇을 A[left]와 자리를 바꾼 후, 피봇과 배열의 각
원소를 비교하여 피봇보다 작은 숫자들은
A[left]~A[p-1]로 옮기고, 피봇보다 큰 숫자들은
A[p+1]~A[right]로 옮기며, 피봇은 A[p]에 놓는다.
3. QuickSort(A, left, p-1) // 피봇보다 작은 그룹
4. QuickSort(A, p+1 right) // 피봇보다 큰 그룹
}</code></pre>
<ul>
<li>수행 과정</li>
</ul>
<p><img src="attachment:a865b6de-e82b-49a5-adf1-896c591b052a:image.png" alt="image.png"></p>
<p>Line 2: 피봇을 제자리로 이동</p>
<p><img src="attachment:a3f5d780-182b-494e-b1e0-7c294b46f92d:image.png" alt="image.png"></p>
<p><img src="attachment:210726e8-cc41-4078-9646-0409dc2f7e1e:image.png" alt="image.png"></p>
<p><img src="attachment:df2688c5-54eb-4811-83d8-26a0f233d22d:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-1">시간 복잡도</h3>
<ul>
<li>퀵 정렬의 성능은 피봇 선택이 좌우한다. 피봇으로 가장 작은 숫자 또는 가장 큰 숫자가 선택되면, 한 부분으로 치우치는 분할을 야기</li>
<li>피봇으로 항상 가장 작은 숫자가 선택되는 경우</li>
</ul>
<p><img src="attachment:4405b0c3-bb56-4d84-b7c9-5cb0b0c254d7:image.png" alt="image.png"></p>
<ul>
<li><p>최악 경우 시간 복잡도</p>
<ul>
<li>피봇=1일 때: 8회 - [17 42 9 18 23 31 11 26]과 각각 1회 비교</li>
<li>피봇=9일 때: 7회 - [42 17 18 23 31 11 26]과 각각 1회 비교</li>
<li>피봇=11일 때: 6회 - [17 18 23 31 42 26]과 각각 1회 비교
…</li>
<li>피봇=31일 때: 1회 - [42]와 1회 비교</li>
<li>총 비교 횟수는 8 + 7 + 6 + … + 1 = 36</li>
<li>퀵 정렬의 최악 경우 시간 복잡도
(n-1)+(n-2)+(n-3)+…+2+1 = n(n-1)/2 = O(n2)</li>
</ul>
</li>
<li><p>최선 경우 시간 복잡도</p>
<ul>
<li><p>최선 경우의 분할</p>
<p><img src="attachment:da249d8e-80df-43ec-a392-d8a75bfef823:image.png" alt="image.png"></p>
</li>
<li><p>각 층에서는 각각의 원소가 각 부분의 피봇과 1회씩 비교된다. 따라서 비교 횟수 = O(n)</p>
<ul>
<li>총 비교 횟수 = O(n)x(층수) = O(n)x(logn)</li>
<li>n/2k=1일 때 k=logn이므로</li>
<li>퀵 정렬의 최선 경우 시간 복잡도: O(nlogn)</li>
</ul>
</li>
</ul>
</li>
<li><p>평균 경우 시간 복잡도</p>
<ul>
<li>피봇을 항상 랜덤하게 선택한다고 가정하면, 퀵 정렬의 평균 경우 시간 복잡도를 계산할 수 있다.</li>
<li>최선 경우와 동일한 O(nlogn)이다.</li>
</ul>
</li>
<li><p>피봇 선정 방법</p>
<ul>
<li><p>랜덤하게 선정하는 방법</p>
</li>
<li><p>3 숫자의 중앙값으로 선정하는 방법(Median-of-Three)</p>
<ul>
<li><p>가장 왼쪽 숫자, 중간 숫자, 가장 오른쪽 숫자 중에서 중앙값으로 피봇을 정한다.</p>
</li>
<li><p>아래의 예제를 보면, [31, 1, 26] 중에서 중앙값인 26을 피봇으로 사용</p>
<p><img src="attachment:2d9ed499-8d87-46de-bfdc-c20c4742b6aa:image.png" alt="image.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Median-of-Medians(Tukey’s Ninther)</p>
<ul>
<li>3 등분한 후 각 부분에서 가장 왼쪽 숫자, 중간 숫자, 가장 오른쪽 숫자 중에서 중앙값을 찾은 후, 세 중앙값들 중에서 중앙값을 피봇을 정한다.</li>
</ul>
</li>
</ul>
<p><img src="attachment:4b4240c9-631d-4b86-824b-883c1a99812f:image.png" alt="image.png"></p>
<ul>
<li><p>성능 향상 방법</p>
<ul>
<li><p>입력의 크기가 매우 클 때, 퀵 정렬의 성능을 더 향상시키기 위해서, 삽입 정렬을 동시에 사용</p>
<ul>
<li><p>입력의 크기가 작을 때에는 퀵 정렬이 삽입 정렬보다 빠르지만은 않다.</p>
<p>  → 퀵 정렬은 순환 호출로 수행되기 때문</p>
</li>
<li><p>부분 문제의 크기가 작아지면 (예를 들어, 25에서 50이 되면), 더 이상의 분할(순환 호출)을 중단하고 삽입 정렬을 사용</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="응용-1">응용</h3>
<ul>
<li>퀵 정렬은 커다란 크기의 입력에 대해서 가장 좋은 성능을 보이는 정렬 알고리즘이다.</li>
<li>퀵 정렬은 실질적으로 어느 정렬 알고리즘보다 좋은 성능을 보인다.</li>
<li>생물 정보 공학(Bioinformatics)에서 특정 유전자를 효율적으로 찾는데 접미 배열(suffix array)과 함께 퀵 정렬이 활용된다.</li>
</ul>
<h3 id="간단한-코드-구현-1">간단한 코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void swap(int* a, int* b) { 
    int t = *a; *a = *b; *b = t; 
}

/* 피봇을 A[left]로 두고 Hoare 방식으로 분할 */
int partition(int A[], int left, int right) {
    int pivot = A[left];
    int i = left + 1, j = right;

    while (1) {
        while (i &lt;= right &amp;&amp; A[i] &lt;= pivot) i++;
        while (j &gt;= left + 1 &amp;&amp; A[j] &gt; pivot) j--;
        if (i &gt; j) break;
        swap(&amp;A[i], &amp;A[j]);
    }
    swap(&amp;A[left], &amp;A[j]);  // 피봇을 최종 위치로
    return j;               // 피봇 인덱스 p
}

void QuickSort(int A[], int left, int right) {
    if (left &lt; right) {
        int p = partition(A, left, right);
        QuickSort(A, left, p - 1);   // 피봇보다 작은 그룹
        QuickSort(A, p + 1, right);  // 피봇보다 큰 그룹
    }
}

int main(void) {
    int A[] = { 1, 17, 42, 9, 18, 23, 31, 11, 26 };
    int n = sizeof(A) / sizeof(A[0]);

    QuickSort(A, 0, n - 1);

    for (int i = 0; i &lt; n; ++i) printf(&quot;%d &quot;, A[i]);
    printf(&quot;\n&quot;);
    return 0;
}
</code></pre>
<hr>
<h1 id="33-선택--selection--문제">3.3 선택 ( Selection ) 문제</h1>
<ul>
<li><p>선택 문제는 n개의 숫자들 중에서 k 번째로 작은 숫자를 찾는 문제</p>
<ul>
<li>최소 숫자를 k 번 찾는다. ( 단, 최소 숫자를 찾은 뒤에는 입력에서 최소 숫자를 제거한다. )</li>
<li>숫자들을 정렬한 후, k번째 숫자를 찾는다.</li>
<li>위의 알고리즘들은 각각 최악의 경우 O(kn)과 O(nlogn)의 수행시간이 걸린다.</li>
</ul>
</li>
<li><p>아이디어</p>
<ul>
<li><p>이진 탐색</p>
<ul>
<li>정렬된 입력의 중간에 있는 숫자와 찾고자 하는 숫자를 비교함으로써, 입력을 1/2로 나눈 두 부분 중에서 한 부분만을 검색</li>
</ul>
</li>
<li><p>선택 문제</p>
<ul>
<li><p>입력이 정렬되어 있지 않으므로, 입력 숫자들 중에서 (퀵 정렬과 같이) 피봇을 선택하여 분할</p>
<p><img src="attachment:b94963f0-6b51-4c6d-b4c0-f1b1274bcef1:image.png" alt="image.png"></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="아이디어">아이디어</h3>
<ul>
<li>Small group은 피봇보다 작은 숫자의 그룹이고, Large group은 피봇보다 큰 숫자의 그룹</li>
<li>이렇게 분할했을 때 알아야 할 것은 각 그룹의 크기, 즉, 숫자의 개수<ul>
<li>각 그룹의 크기를 알면,</li>
<li>k번째 작은 숫자가 어느 그룹에 있는 지를 알 수 있고,</li>
<li>그 다음에는 그 그룹에서 몇 번째로 작은 숫자를 찾아야 하는 지를 알 수 있다.</li>
</ul>
</li>
<li>Small group에 k번째 작은 숫자가 속한 경우<ul>
<li>k번째 작은 숫자를 Small group에서 찾는다.</li>
<li>Large group에 k번째 작은 숫자가 있는 경우</li>
<li>(k-|Small group|-1)번째로 작은 숫자를 Large group에서 찾아야 한다.</li>
<li>|Small group|은 Small group에 있는 숫자의 개수이고, 1은 피봇에 해당된다.</li>
</ul>
</li>
<li>의사 코드</li>
</ul>
<pre><code class="language-c">Selection(A, left, right, k)
입력: A[left]~A[right]와 k, 단, 1≤k≤|A|, |A|=right-left+1
출력: A[left]~A[right]에서 k 번째 작은 원소
1. 피봇을 A[left]~A[right]에서 랜덤하게 선택하고, 피봇과 A[left]의
자리를 바꾼 후, 피봇과 배열의 각 원소를 비교하여 피봇보다 작은 숫자
는 A[left]~A[p-1]로 옮기고, 피봇보다 큰 숫자는 A[p+1]~ A[right]
로 옮기며, 피봇은 A[p]에 놓는다.
2. S = (p-1)-left+1 // S = Small group의 크기
3. if ( k ≤ S ) Selection(A, left, p-1, k) // Small group에서 찾기
4. else if ( k = S + 1 ) return A[p] // 피봇 = k번째 작은 숫
자
5.else Selection(A, p+1, right, k-S-1) // Large group에서 찾기</code></pre>
<ul>
<li>수행 과정</li>
</ul>
<p><img src="attachment:6f8c0a21-695c-43b5-a696-a0eff86ec599:image.png" alt="image.png"></p>
<p><img src="attachment:16c3eff4-54b0-42e2-ae69-bea6a4a51790:47a7279e-0395-43a0-bffb-865b2d4881c9.png" alt="image.png"></p>
<p><img src="attachment:f7469649-2001-475f-be1a-5cada519d55b:image.png" alt="image.png"></p>
<p><img src="attachment:71181930-53ce-4e67-b52c-109243c15a3c:image.png" alt="image.png"></p>
<p><img src="attachment:ae4b487d-1551-448c-ac18-18d94e5974c3:image.png" alt="image.png"></p>
<p><img src="attachment:633d656a-0f22-47ab-9c5c-bce5d36b8de6:image.png" alt="image.png"></p>
<h3 id="selection-알고리즘-고려-사항">Selection 알고리즘 고려 사항</h3>
<ul>
<li>Selection 알고리즘은 분할 정복 알고리즘이기도 하지만 랜덤(random) 알고리즘이기도 하다.<ul>
<li>선택 알고리즘의 line 1에서 피봇을 랜덤하게 정하기 때문</li>
</ul>
</li>
<li>• 피봇이 입력을 너무 한쪽으로 치우치게 분할하면 
즉, |Small group| &lt;&lt; |Large group| 또는 |Small group| &gt;&gt; |Large group|일 때에는 알고리즘의 수행 시간이 길어진다.</li>
<li>선택 알고리즘이 호출될 때마다 line 1에서 입력을 한쪽으로 치우치게 분할될 확률은?<ul>
<li>마치 동전을 던질 때 한쪽 면이 나오는 확률과 동일</li>
</ul>
</li>
</ul>
<h3 id="goodbad-분할-정의">good/bad 분할 정의</h3>
<ul>
<li>분할된 두 그룹 중의 하나의 크기가 입력 크기의 3/4과 같거나 그 보다 크게 분할하면 나쁜 (bad) 분 이라고 정의하자.</li>
<li>좋은 (good) 분할은 그 반대의 경우이다.</li>
</ul>
<p><img src="attachment:94f561cd-4aec-4bfc-89f3-a0343f161cc4:image.png" alt="image.png"></p>
<ul>
<li>다음과 같이 16개의 숫자가 있다면</li>
</ul>
<p><img src="attachment:8a6a4579-e04c-4d0f-afbb-1acdb8841234:image.png" alt="image.png"></p>
<ul>
<li>good 분할이 되는 피봇을 선택할 확률과 bad 분할이 되는 피봇을 선택할 확률이 각각 1/2로 동일</li>
</ul>
<p><img src="attachment:eec78c62-d22d-402c-aaf1-d43bfe13bfc7:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-2">시간 복잡도</h3>
<ul>
<li>피봇을 랜덤하게 정했을 때 good 분할이 될 확률이 1/2이므로 평균 2회 연속해서 랜덤하게 피봇을 정하면 good 분할을 할 수 있다.</li>
<li>매 2회 호출마다 good 분할이 되므로, good 분할만 연속하여 이루어졌을 때만의 시간 복잡도를 구하여, 그 값에 2를 곱하면 평균 경우 시간 복잡도를 얻을 수 있다.</li>
</ul>
<h3 id="연속된-good-분할">연속된 good 분할</h3>
<p><img src="attachment:3f418008-3b9b-4686-a1b4-3b10605134d1:image.png" alt="image.png"></p>
<ul>
<li><p>평균 경우 시간 복잡도</p>
<ul>
<li><p>입력 크기가 n에서부터 3/4배로 연속적으로 감소되고, 크기가 1일 때에는 더 이상 분할할 수 없다.</p>
<p><img src="attachment:c8b4c41f-18d0-4b25-b8ac-c7b730d67e64:image.png" alt="image.png"></p>
</li>
<li><p>평균 2회에 good 분할이 되므로 2xO(n) = O(n)</p>
</li>
</ul>
</li>
<li><p>선택 알고리즘과 이진 탐색</p>
<ul>
<li>[유사성]<ul>
<li>이진 탐색은 분할과정을 진행하면서 범위를 1/2씩 좁혀가며 찾고자 하는 숫자를 탐색</li>
<li>선택 알고리즘은 피봇으로 분할하여 범위를 좁혀감</li>
</ul>
</li>
<li>[공통점]<ul>
<li>부분 문제들을 취합하는 과정이 별도로 필요 없다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="응용-2">응용</h3>
<ul>
<li>선택 알고리즘은 데이터 분석을 위한 중앙값 (median) 을 찾는데 활용<ul>
<li>데이터 분석에서 평균값도 유용하지만, 중앙값이 더 설득력 있는 데이터 분석을 제공</li>
<li>예를 들어, 대부분의 데이터가 1이고, 오직 1개의 숫자가 매우 큰 숫자 (노이즈 (noise), 잘못 측정된 데이터)이면, 평균값은 매우 왜곡된 분석이 된다.</li>
</ul>
</li>
</ul>
<h3 id="간단한-구현">간단한 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;time.h&gt;

static inline void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }

/* pivot = A[left] 로 두고 Lomuto 스타일로 분할 */
int partition_left_pivot(int A[], int left, int right) {
    int pivot = A[left];
    int i = left + 1;
    for (int j = left + 1; j &lt;= right; ++j) {
        if (A[j] &lt; pivot) {
            swap(&amp;A[i], &amp;A[j]);
            ++i;
        }
    }
    swap(&amp;A[left], &amp;A[i - 1]);   // 피봇을 최종 위치로
    return i - 1;                // 피봇 인덱스 p (A[left..p-1] &lt; pivot, A[p+1..right] &gt;= pivot)
}

/* Selection(A, left, right, k): A[left..right]에서 k번째(1-based) 작은 원소 반환 */
int Selection(int A[], int left, int right, int k) {
    // 전제: 1 &lt;= k &lt;= right-left+1
    // (필요하면 호출부에서 검증)
    if (left == right) return A[left];

    // 1) 랜덤 피봇 선택 후 A[left]와 교환
    int r = left + rand() % (right - left + 1);
    swap(&amp;A[left], &amp;A[r]);

    // 2) 분할 수행 (피봇을 A[p]로 이동)
    int p = partition_left_pivot(A, left, right);

    // 3) Small group 크기 S = p - left
    int S = p - left;

    if (k &lt;= S) {
        return Selection(A, left, p - 1, k);
    }
    else if (k == S + 1) {
        return A[p];
    }
    else {
        return Selection(A, p + 1, right, k - S - 1);
    }
}

/* 사용 예시 */
int main(void) {
    srand((unsigned)time(NULL));

    int A[] = { 9, 1, 5, 3, 7, 2, 8, 6, 4, 5 };
    int n = sizeof(A) / sizeof(A[0]);

    int k;
    printf(&quot;k (1..%d): &quot;, n);
    if (scanf_s(&quot;%d&quot;, &amp;k) != 1 || k &lt; 1 || k &gt; n) {
        fprintf(stderr, &quot;유효한 k를 입력하세요.\n&quot;);
        return 1;
    }

    int ans = Selection(A, 0, n - 1, k);
    printf(&quot;%d번째 작은 원소 = %d\n&quot;, k, ans);
    return 0;
}
</code></pre>
<hr>
<h1 id="34-최근접-점의-쌍-찾기--closest-pair-">3.4 최근접 점의 쌍 찾기 ( Closest Pair )</h1>
<ul>
<li>2차원 평면상의 n개의 점이 입력으로 주어질 때, 거리가 가장 가까운 한 쌍의 점을 찾는 문제</li>
</ul>
<p><img src="attachment:ed9c3d76-eb19-46a5-bf03-e0ca8ed51062:image.png" alt="image.png"></p>
<h3 id="간단한-방법">간단한 방법</h3>
<ul>
<li><p>모든 점에 대하여 각각의 두 점 사이의 거리를 계산하여 가장 가까운 점의 쌍을 찾는다.</p>
</li>
<li><p>예를 들어, 5개의 점이 아래의 [그림]처럼 주어지면, 1-2, 1-3, 1-4, 1-5, 2-3, 2-4, 2-5, 3-4, 3-5, 4-5 사이의 거리를 각각 계산하여 그 중에 최소 거리를 가진 쌍이 최근접 점의 쌍이 된다.</p>
</li>
<li><p>비교해야 할 쌍은 몇 개인가?</p>
<ul>
<li><p>nC2 = n(n-1)/2</p>
</li>
<li><p>n=5이면, 5(5-1)/2 = 10</p>
</li>
<li><p>n(n-1)/2 = O(n2)</p>
</li>
<li><p>한 쌍의 거리 계산은 O(1)</p>
</li>
<li><p>시간 복잡도는 O(n2)xO(1) = O(n2)</p>
<p><img src="attachment:df476586-15a0-4243-a9f8-fc79636871cc:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>O(n2)보다 효율적인 분할 정복 이용</p>
<ul>
<li><p>n개의 점을 1/2로 분할하여 각각의 부분 문제에서 최근접 점의 쌍을 찾고, 2개의 부분 해 중에서 짧은 거리를 가진 점의 쌍을 일단 찾는다.</p>
<p><img src="attachment:4b02ce02-68d9-4b51-b4da-67e73ee093c6:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>취합할 때 중간 영역을 고려해야함</p>
</li>
</ul>
<p><img src="attachment:43370d00-9d11-4c0d-aba0-8b1e8defd3ec:image.png" alt="image.png"></p>
<ul>
<li><p>중간 영역 안의 점들</p>
<ul>
<li><p>10과 15 중에서 짧은 거리인 10 이내의 중간 영역 안에 있는 점들 중에 더 근접한 점의 쌍이 있는지 확인해야함</p>
<p><img src="attachment:d33c71e5-ac95-4707-a697-90c680ce36a1:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>d = min{왼쪽 부분의 최근접 점의 쌍 사이의 거리, 오른쪽 부분의 최근접 점의 쌍 사이의 거리}</p>
</li>
<li><p>배열에는 점들이 x-좌표의 오름차순으로 정렬되어 있고, 각 점의 y-좌표는 생략</p>
</li>
</ul>
<p><img src="attachment:8ed25d07-0873-452b-aa3e-ee02ccb71afe:image.png" alt="image.png"></p>
<ul>
<li><p>중간 영역에 있는 점들을 찾는 방법</p>
<ul>
<li><p>중간 영역에 속한 점 = {왼쪽 부분 문제의 가장 오른쪽 점(왼쪽 중간점)의 x-좌표에서 d를 뺀 값과 오른쪽 부분 문제의 가장 왼쪽 점 (오른쪽 중간점)의 x-좌표에 d를 더한 값 사이의 x-좌표 값을 가진 점들}</p>
</li>
<li><p>d=10이면, 점 (25,-), (26,-), (28,-), (30,-), (37,-)이 중간 영역에 속한다.</p>
<p><img src="attachment:6d6e2234-c265-4072-85b4-a968608e05a2:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>의사 코드</p>
</li>
</ul>
<pre><code class="language-c">ClosestPair(S)
입력: x-좌표의 오름차순으로 정렬된 배열 S에 있는 i개의 점. 단, 각 점은
(x,y)로 표현
출력: S에 있는 점들 중 최근접 점의 쌍의 거리
1. if (i ≤ 3) return (2 또는 3개의 점들 사이의 최근접 쌍)
2. 정렬된 S를 같은 크기의 SL과 SR로 분할한다. |S|가 홀수이면, |SL| =
|SR|+1이 되도록 분할
3. CPL = ClosestPair(SL) // CPL은 SL에서의 최근접 점의 쌍
4. CPR = ClosestPair(SR) // CPR은 SR에서의 최근접 점의 쌍
5. d = min{dist(CPL), dist(CPR)}일 때, 중간 영역에 속하는 점들 중에
서 최근접 점의 쌍을 찾아서 이를 CPC라고 하자. 단, dist()는 두 점 사
이의 거리
6. return (CPL, CPC, CPR 중에서 거리가 가장 짧은 쌍)</code></pre>
<h3 id="수행-과정">수행 과정</h3>
<ul>
<li><p>ClosestPair(S)로 호출 [1]</p>
<ul>
<li><p>Line 1: S의 점의 수 &gt; 3이므로 다음 line을 수행</p>
</li>
<li><p>Line 2: S를 SL과 SR로 분할</p>
<p><img src="attachment:c5912976-8346-4b7f-906e-22ad45dc90bd:image.png" alt="image.png"></p>
</li>
</ul>
</li>
</ul>
<p><img src="attachment:588b0c67-23e9-4ea3-9a28-79456dfea901:image.png" alt="image.png"></p>
<p><img src="attachment:8bc2da31-bc1a-4625-a27e-ad1784c1a17d:image.png" alt="image.png"></p>
<p><img src="attachment:7751329d-2d85-453a-8c78-3e07b13228d2:985f95e8-8e31-43a6-aaaa-187ac5004ad6.png" alt="image.png"></p>
<p><img src="attachment:7984b815-ee27-4fce-82ae-df7edf25eb88:image.png" alt="image.png"></p>
<p><img src="attachment:aa3cd0c5-c2e9-4a55-8cc6-4f2dd2869f37:image.png" alt="image.png"></p>
<p><img src="attachment:0c5a54eb-8a65-4e55-9e8c-ddbadae803a1:image.png" alt="image.png"></p>
<p><img src="attachment:201a548f-c48f-42cb-ad35-325b1c89aba2:image.png" alt="image.png"></p>
<h3 id="시간-복잡도-3">시간 복잡도</h3>
<ul>
<li>S에 n개의 점이 있으면 전처리 (preprocessing) 과정으로서 S의 점을 x-좌표로 정렬: O(nlogn)</li>
<li>Line 1: S에 3개의 점이 있는 경우에 3번의 거리 계산이 필요하고, S의 점의 수가 2이면 1번의 거리 계산이 필요하므로 O(1) 시간이 걸린다.</li>
<li>Line 2: 정렬된 S를 SL과 SR로 분할하는데, 이미 배열에 정렬되어 있으므로, 배열의 중간 인덱스로 분할하면 된다. 이는 O(1) 시간 걸린다.</li>
<li>Line 3~4: SL과 SR에 대하여 각각 ClosestPair를 호출하는데, 분할하며 호출되는 과정은 합병 정렬과 동일</li>
<li>Line 5<ul>
<li>d = min{dist(CPL), dist(CPR)}일 때 중간 영역에 속하는 점들 중에서 최근접 점의 쌍을 찾는다.</li>
<li>이를 위해 먼저 중간 영역에 있는 점들을 y-좌표 기준으로 정렬한 후에, 아래에서 위로 각 점을 기준으로 거리가 d이내인 주변의 점들 사이의 거리를 각각 계산하며, 이 영역에 속한 점들 중에서 최근접 점의 쌍을 찾는다.</li>
<li>y-좌표로 정렬하는데 O(nlogn) 시간이 걸리고, 아래에서 위로 올라가며 각 점에서 주변의 점들 사이의 거리를 계산하는데 O(1) 시간이 걸린다. 왜냐하면 각 점과 거리 계산해야 하는 주변 점들의 수는 O(1)개이기 때문</li>
</ul>
</li>
<li>Line 6: 3개의 점의 쌍 중에 가장 짧은 거리를 가진 점의 쌍을 리턴하므로 O(1) 시간이 걸린다.</li>
<li>ClosestPair 알고리즘의 분할과정은 합병 정렬의 분할 과정과 동일<ul>
<li>그러나 ClosestPair 알고리즘에서는 해를 취합하여 올라가는 과정인 line 5~6에서 O(nlogn) 시간이 필요</li>
</ul>
</li>
<li>k층까지 분할된 후, 층별로 line 5~6이 수행되는 (취합) 과정을 보여준다. (다음 슬라이드)<ul>
<li>각 층의 수행 시간은 O(nlogn) → 여기에 층 수인 logn을 곱하면 O(nlog2n)</li>
</ul>
</li>
</ul>
<p><img src="attachment:9e4ee290-71df-40db-9019-9ea96cf09fe5:1ed8a318-c4c2-46dc-9fce-b56bfcce4073.png" alt="image.png"></p>
<h3 id="응용-3">응용</h3>
<ul>
<li>컴퓨터 그래픽스</li>
<li>컴퓨터 비전 (Vision)</li>
<li>지리 정보 시스템 (Geographic Information System, GIS)</li>
<li>분자 모델링 (Molecular Modeling)</li>
<li>항공 트래픽 조정 (Air Traffic Control)</li>
<li>마케팅 (주유소, 프랜차이즈 신규 가맹점 등의 위치 선정) 등</li>
</ul>
<h3 id="간단한-구현-1">간단한 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;math.h&gt;

typedef struct { double x, y; } Point;

static inline double dist(Point a, Point b) {
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
}

static int cmp_y(const void *a, const void *b) {
    double dy = ((Point*)a)-&gt;y - ((Point*)b)-&gt;y;
    return (dy &gt; 0) - (dy &lt; 0);
}

static double brute(Point *S, int l, int r) {
    double best = INFINITY;
    for (int i = l; i &lt; r; ++i)
        for (int j = i + 1; j &lt;= r; ++j)
            if ((best = fmin(best, dist(S[i], S[j]))) == 0.0) return 0.0;
    return best;
}

/* strip[]는 |x - midx| &lt; d 범위의 점들을 담고, y로 정렬 후
   각 점에서 최대 다음 7개까지만 확인 */
static double stripClosest(Point *strip, int m, double d) {
    qsort(strip, m, sizeof(Point), cmp_y);
    double best = d;
    for (int i = 0; i &lt; m; ++i) {
        for (int j = i + 1; j &lt; m &amp;&amp; (strip[j].y - strip[i].y) &lt; best &amp;&amp; j &lt;= i + 7; ++j) {
            double dij = dist(strip[i], strip[j]);
            if (dij &lt; best) best = dij;
        }
    }
    return best;
}

/* S는 x-좌표 기준 오름차순 정렬되어 있다고 가정 */
double ClosestPair(Point *S, int l, int r) {
    int n = r - l + 1;
    if (n &lt;= 3) return brute(S, l, r);

    int mid = (l + r) / 2;
    double midx = S[mid].x;

    double dl = ClosestPair(S, l, mid);
    double dr = ClosestPair(S, mid + 1, r);
    double d = fmin(dl, dr);

    // 중간 스트립 구성
    Point *strip = (Point*)malloc(sizeof(Point) * n);
    int m = 0;
    for (int i = l; i &lt;= r; ++i)
        if (fabs(S[i].x - midx) &lt; d) strip[m++] = S[i];

    double ds = stripClosest(strip, m, d);
    free(strip);

    return fmin(d, ds);
}

/* 데모: 입력이 x로 정렬되어 있지 않다면 x 기준 정렬 후 호출 */
static int cmp_x(const void *a, const void *b) {
    double dx = ((Point*)a)-&gt;x - ((Point*)b)-&gt;x;
    return (dx &gt; 0) - (dx &lt; 0);
}

int main(void) {
    Point S[] = {
        {2.1, 3.4}, {12.3, 30.2}, {40.0, 50.0}, {5.0, 1.0}, {12.0, 10.0}, {3.0, 4.0}
    };
    int n = sizeof(S)/sizeof(S[0]);

    qsort(S, n, sizeof(Point), cmp_x);  // x 오름차순 정렬 (요구조건 맞추기)
    double ans = ClosestPair(S, 0, n - 1);
    printf(&quot;최근접 쌍 거리 = %.6f\n&quot;, ans);
    return 0;
}
</code></pre>
<hr>
<h1 id="35-분할-정복-적용에-있어-주의할-점">3.5 분할 정복 적용에 있어 주의할 점</h1>
<ul>
<li><p>분할 정복이 부적절한 경우</p>
<ul>
<li>입력이 분할될 때마다 분할된 부분 문제의 입력 크기의 합이 분할되기 전의 입력 크기보다 커지는 경우</li>
</ul>
</li>
<li><p>n 번째의 피보나치 수를 구하기</p>
<ul>
<li>F(n) = F(n-1) + F(n-2)로 정의되므로 순환 호출을 사용하는 것이 자연스러워 보이나, 이 경우의 입력은 1개이지만, 사실상 n의 값 자체가 입력 크기인 것이다.</li>
<li>2개의 부분 문제인 F(n-1)과 F(n-2)의 입력 크기는 (n-1) + (n-2) = (2n-3)이 되어서, 분할 후 입력 크기가 거의 2배로 증가함</li>
</ul>
</li>
<li><p>피보나치 수 F(6)을 구하기 위해 분할된 부분 문제들</p>
<ul>
<li><p>F(2)를 5번이나 중복하여 계산해야 하고, F(3)은 3번 계산된다.</p>
<p><img src="attachment:9e65274d-4f21-4df9-af4b-ed46425695f6:image.png" alt="image.png"></p>
</li>
</ul>
</li>
<li><p>피보나치 수 계산을 위한 O(n) 알고리즘</p>
</li>
</ul>
<pre><code class="language-c">FibNumber(n)
1. F[0]=0
2. F[1]=1
3. for i=2 to n
4. F[i] = F[i-1]+ F[i-2]</code></pre>
<ul>
<li>분할 정복 적용에 있어 주의할 점<ul>
<li>주어진 문제를 분할 정복 알고리즘으로 해결하려고 할 때에 주의해야 하는 또 하나의 요소는 취합(정복) 과정이다.</li>
<li>입력을 분할만 한다고 해서 효율적인 알고리즘이 만들어지는 것은 아니다.</li>
<li>기하(geometry)에 관련된 다수의 문제들이 효율적인 분할 정복 알고리즘으로 해결되는데, 이는 기하 문제들의 특성상 취합 과정이 문제 해결에 잘 부합되기 때문</li>
</ul>
</li>
</ul>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li>분할 정복 (Divide-and-Conquer) 알고리즘: 주어진 문제의 입력을 분할하여 문제를 해결 (정복)하는 방식의 알고리즘이다.</li>
<li>합병 정렬 (Merge sort): n개의 숫자들을 n/2개씩 2개의 부분 문제로 분할하고, 각각의 부분 문제를 재귀적으로 합병 정렬한 후, 2개의 정렬된 부분을 합병하여 정렬 (정복)한다. 시간 복잡도는 O(nlogn)이다.</li>
<li>합병 정렬의 공간 복잡도는 O(n)이다.</li>
<li>퀵 정렬 (Quick sort): 피봇 (pivot)이라 일컫는 배열의 원소를 기준으로 피봇보다 작은 숫자들은 왼편으로, 피봇보다 큰 숫자들은 오른편에 위치하도록 분할하고, 피봇을 그 사이에 놓는다. 퀵 정렬은 분할된 부분 문제들에 대하여서도 위와 동일한 과정을 순환으로 수행하여 정렬</li>
<li>퀵 정렬의 평균 경우 시간 복잡도는 O(nlogn), 최악 경우 시간 복잡도는 O(n2), 최선 경우 시간 복잡도는 O(nlogn)</li>
<li>선택 (Selection) 문제: k 번째 작은 수를 찾는 문제로서, 입력에서 퀵 정렬에서와 같이 피봇을 선택하여 피봇보다 작은 부분과 큰 부분으로 분할한 후에 k 번째 작은 수가 들어있는 부분을 순환으로 탐색한다. 평균 경우 시간 복잡도는 O(n)</li>
<li>최근접 점의 쌍 (Closest Pair) 문제: n개의 점들을 1/2로 분할하여 각각의 부분 문제에서 최근접 점의 쌍을 찾고, 2개의 부분 해 중에서 짧은 거리를 가진 점의 쌍을 일단 찾는다. 그리고 2개의 부분해를 취합할 때, 중간 영역 안에 있는 점들 중에 최근접 점의 쌍이 있는지도 확인해야 한다. 시간 복잡도는 O(nlog2n)</li>
<li>분할 정복이 부적절한 경우는 입력이 분할될 때마다 분할된 부분 문제들의 입력 크기의 합이 분할되기 전의 입력 크기보다 커지는 경우이다. 또 하나 주의해야 할 요소는 취합 (정복) 과정이다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 : Ch.02 Get Ready]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.02-Get-Ready</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Ch.02-Get-Ready</guid>
            <pubDate>Mon, 13 Oct 2025 11:24:06 GMT</pubDate>
            <description><![CDATA[<h1 id="21-알고리즘이란-">2.1 알고리즘이란 ?</h1>
<ul>
<li>알고리즘<ul>
<li>문제를 해결하는 단계적 절차 또는 방법</li>
<li>여기서 주어지는 문제는 컴퓨터를 이용하여 해결</li>
<li>알고리즘에는 입력이 주어지고, 알고리즘은 수행한 결과인 “해”</li>
</ul>
</li>
<li>알고리즘의 일반적 특성<ul>
<li>정확성 : 주어진 입력에 대하여 올바른 해를 주어야 ( 랜덤 알고리즘 제외 )</li>
<li>수행성 : 컴퓨터에서 수행 가능</li>
<li>유한성 : 유한 시간 내에 종료 되어야</li>
<li>효율성 : 효율적일 수록 가치가 높다</li>
</ul>
</li>
</ul>
<hr>
<h1 id="22-최초의-알고리즘">2.2 최초의 알고리즘</h1>
<ul>
<li>유클리드 ( Eucild ) 의 최대 공약수 알고리즘</li>
<li>2개의 자연수의 공약수들 중에서 가장 큰 수<ul>
<li>2개의 자연수의 최대 공약수는 큰 수에서 작은 수를 뺀 수와 작은 수와의 최대공약수와 같다.</li>
<li>뺄셈 대신에 나눗셈을 사용하면 빠르게 해를 찾음</li>
</ul>
</li>
<li>의사 코드 ( Pseudo Code )</li>
</ul>
<pre><code class="language-c">Euclid(a,b)
입력: 정수 a , b; 단 ,  a &gt;= b &gt;= 0

1. if b == 0 return a 
2. return Eucild(b,a mod b); // 재귀 호출</code></pre>
<h3 id="코드-구현">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int Euclid(int a, int b) {
    if (b == 0) return a;
    return Euclid(b, a % b);
}

int main() {
    int a, b;

    scanf_s(&quot;%d %d&quot;, &amp;a, &amp;b);
    printf(&quot;%d\n&quot;, Euclid(a, b));
    return 0;
}</code></pre>
<hr>
<h1 id="23-알고리즘의-표현-방법">2.3 알고리즘의 표현 방법</h1>
<ul>
<li>알고리즘의 형태는 단계별 절차이므로 , 마치 요리책의 요리를 만드는 절차와 유사</li>
<li>알고리즘의 각 단계는 보통 말로 서술할 수 있으며, 컴퓨터 프로그래밍 언어로만 표현할 필요 없음</li>
<li>일반적으로 알고리즘은 프로그래밍 언어와 유사한 의사 코드 ( Pseudo Code ) 로 표현</li>
<li>Ex) 최대 숫자 찾기 문제를 위한 알고리즘</li>
<li>의사 코드 ( Pseudo Code )</li>
</ul>
<pre><code class="language-c">배열 A에 10개의 숫자가 있다면
1. max = A[0]
2. for i = 1 to 9
3. if (A[i] &gt; max ) max = A[i]
4. return max</code></pre>
<hr>
<h1 id="24-알고리즘의-분류">2.4 알고리즘의 분류</h1>
<ul>
<li>문제의 해결 방식에 따른 분류<ul>
<li><strong>분할 정복 ( Divide and Conquer )</strong> 알고리즘</li>
<li><strong>그리디 ( Greedy )</strong> 알고리즘</li>
<li><strong>동적 계획 ( Dynamic Programing )</strong> 알고리즘</li>
<li><strong>근사 ( Approximaiton )</strong> 알고리즘</li>
<li><strong>백트래킹 ( Backtracking )</strong> 알고리즘</li>
<li><strong>분기 한정 ( Branch-and-Bound)</strong> 알고리즘</li>
</ul>
</li>
<li>문제에 기반한 분류<ul>
<li><strong>정렬</strong> 알고리즘</li>
<li><strong>탐색</strong> 알고리즘</li>
<li><strong>그래프</strong> 알고리즘</li>
<li><strong>기하</strong> 알고리즘</li>
</ul>
</li>
<li>특정 환경에 따른 분류<ul>
<li><strong>병렬 ( Parallel )</strong> 알고리즘</li>
<li><strong>분산 ( Distributed )</strong> 알고리즘</li>
<li>양자 ( Quantum ) 알고리즘</li>
</ul>
</li>
<li>패러다임에 의한 분류<ul>
<li>기존 규칙 기반 알고리즘 → 우리가 알고리즘 수업에서 다루는 것들</li>
<li>학습 기반 알고리즘 → <strong>Transformer</strong>, Deep ML, shallow ML …. etc</li>
</ul>
</li>
</ul>
<hr>
<h1 id="25-알고리즘의-효울성-표현">2.5 알고리즘의 효울성 표현</h1>
<ul>
<li>알고리즘의 효율성<ul>
<li>알고리즘의 수행 시간 또는 알고리즘이 수행하는 동안 사용되는 메모리 크기로 나타낼 수 있다.</li>
<li>시간 복잡도 , 공간 복잡도</li>
<li>일반적으로 알고리즘들을 비교할 때에는 시간 복잡도가 주로 사용</li>
</ul>
</li>
<li>시간 복잡도<ul>
<li>알고리즘이 실행되는 동안에 사용된 기본적인 연산 횟수를 입력 크기의 함수로 나타낸다.</li>
<li>기본연산 :단순한 연산</li>
</ul>
</li>
<li>알고리즘의 복잡도 표현 방법<ul>
<li>최악의 경우 분석 ( Worst-case Analysis )<ul>
<li>어떤 입력이 주어지더라도 알고리즘의 수행시간이 얼마 이상은 넘지 않는다 라는 상한 ( Upper Bound ) 의 의미</li>
</ul>
</li>
<li>평균 경우 분석 ( Average-case Analysis )<ul>
<li>일반적으로 균등 분포 ( Uniform Distributed ) 를 가정</li>
</ul>
</li>
<li>최선 경우 분석 ( Best-case Analysis )<ul>
<li>가장 빠른 수행 시간을 분석 → 최적 ( Optimal ) 알고리즘을 찾는데 활용</li>
</ul>
</li>
</ul>
</li>
<li>상각 분석 ( Amortized Analysis )<ul>
<li>일련의 연산을 수행 → 총 수행 시간 합 → 이를 연산 횟수로 나누어 수행 시간을 분석</li>
<li>[조건] 알고리즘에서 적어도 두 종류의 연산이 수행되고, 그 중 하나는 수행 시간이 길고, 다른 하나는 짧으며 , 수행 시간이 짧은 연산은 많이 수행되고 수행 시간이 긴 연산은 적게 수행되어야 상각 분석이 의미를 갖는다.</li>
<li>“상각”은 ‘보상하여 갚아주다’라는 뜻</li>
</ul>
</li>
<li>일반적으로 알고리즘의 수행 시간은 최악 경우 분석으로 표현</li>
</ul>
<hr>
<h1 id="26-복잡도의-점근적-표기">2.6 복잡도의 점근적 표기</h1>
<ul>
<li>시간 복잡도는 입력 크기에 대한 함수로 표기<ul>
<li>함수는 여러 개의 항을 가지는 다항식</li>
<li>이를 입력의 크기에 대한 함수로 표현하기 위해 점근적 표기 ( Asymptotic Notation ) 를 사용</li>
</ul>
</li>
<li>점근적 표기<ul>
<li>입력 크기 N이 무한대로 커질 때의 복잡도를 간단히 표현하기 위해 사용</li>
<li><strong>O ( Bic-Oh) 표기</strong></li>
<li>Ω ( Big-Omega ) 표기 ← 하한 ( Lower Bound )</li>
<li>Θ ( Theta) 표기 ← 유사한 증가율을 가지고 있다</li>
</ul>
</li>
<li>O ( Bic-Oh) 표기<ul>
<li>f(n) ≤ cg(n)</li>
<li>g(n)을 f(n)의 상한 ( Upper Bound ) 이라고 한다.</li>
<li>점근적 상한</li>
<li>다항식에서 최고 차수의 항만을 취한 뒤, 그 항의 계수를 제거하여 표현</li>
</ul>
</li>
</ul>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li>알고리즘이란 문제를 해결하는 단계적 절차 또는 방법이다.</li>
<li>알고리즘의 일반적인 특성<ul>
<li><strong>정확성</strong>: 주어진 입력에 대해 올바른 해를 주어야</li>
<li><strong>수행성</strong>: 각 단계는 컴퓨터에서 수행 가능하여야.</li>
<li><strong>유한성</strong>: 유한 시간 내에 종료되어야</li>
<li><strong>효율성</strong>: 효율적일수록 그 가치가 높다.</li>
</ul>
</li>
<li>알고리즘은 대부분 의사 코드(pseudo code) 형태로 표현된다.</li>
<li>알고리즘의 효율성은 주로 시간 복잡도 (Time Complexity)가 사용된다.</li>
<li>시간 복잡도는 알고리즘이 수행하는 기본적인 연산 횟수를 입력 크기에 대한 함수로 표현</li>
<li>알고리즘의 복잡도 표현 방법:<ul>
<li>최악 경우 분석(Worst case Analysis)</li>
<li>평균 경우 분석(Average case Analysis)</li>
<li>최선 경우 분석(Best case Analysis)</li>
</ul>
</li>
<li>점근적 표기(Asymptotic Notation): 입력 크기 n이 무한대로 커질 때의 복잡도를 간단히 표현하기 위해 사용하는 표기법<ul>
<li>O-(Big-Oh) 표기: 점근적 상한</li>
<li>Ω-(Big-Omega) 표기: 점근적 하한</li>
<li>Θ-(Theta) 표기: 동일한 증가율</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 : Ch.01 First Step]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-First-Step</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-First-Step</guid>
            <pubDate>Mon, 13 Oct 2025 11:23:31 GMT</pubDate>
            <description><![CDATA[<p>학교에서 진행하는 알고리즘 수업을 정리해보려고 한다.</p>
<h1 id="11-최대-숫자-찾기">1.1 최대 숫자 찾기</h1>
<ul>
<li>카드의 숫자를 하나씩 비교하면서 본 숫자들 중에서 가장 큰 숫자를 기억해가며 찾기</li>
<li>마지막 카드의 숫자를 본 후에 , 머릿속에 기억된 가장 큰 숫자가 적힌 카드를 바닥에서 집어든다.</li>
<li>O(n)</li>
<li>이런 방법을 순차 탐색 ( Sequential Search ) 라고 한다.<ul>
<li>즉, 카드를 한 장씩 차례대로 ( 주어진 대로) 읽어 가며 찾는 방법</li>
</ul>
</li>
</ul>
<h3 id="코드-구현">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void main() {
    int arr[10] = { 1,2,3,4,15,6,7,8,9,11 };
    int tmp = 0;
    for(int i = 0; i &lt; 10; i++) {
        if (arr[i] &gt; tmp ) {
            tmp = arr[i];
        }
    }
    printf(&quot;Max is %d\n&quot;, tmp);
}</code></pre>
<hr>
<h1 id="12-임의의-숫자-찾기--find-random-number-">1.2 임의의 숫자 찾기 ( Find Random Number )</h1>
<ul>
<li>배열에서 임의의 숫자를 찾는 알고리즘</li>
<li>최대 숫자 찾기처럼 정한 숫자를 기억하고 바닥에 펼처진 카드를 차례대로 한 장씩 읽으며 숫자와 비교</li>
</ul>
<h3 id="코드-구현--정한-숫자는-9-">코드 구현 ( 정한 숫자는 9 )</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void main() {
    int arr[10] = { 1,2,3,4,15,6,7,8,9,11 };
    int num;
    int found = 0;

    printf(&quot;찾고 싶은 숫자를 입력하세요: &quot;);
    scanf_s(&quot;%d&quot;, &amp;num);

    for (int i = 0; i &lt; 10; i++) {
        if (arr[i] == num) {
            printf(&quot;배열에 %d가 있습니다. 그 숫자의 위치는 배열의 %d번째에 있습니다.\n&quot;, num, i + 1);
            found = 1;
            break;
        }
    }

    if (!found) {
        printf(&quot;%d는 배열에 없습니다.\n&quot;, num);
    }
}</code></pre>
<ul>
<li>문제<ul>
<li>10장의 카드가 만약 오름 차순으로 미리 정렬되어 있다면 ?</li>
<li>9를 순차탐색으로 찾으면 위로부터 7장의 카드를 읽은 후에나 9를 찾는다. ← 효율 down</li>
</ul>
</li>
</ul>
<h3 id="그러면-순차-탐색보다-효율적인-방법은-">그러면 순차 탐색보다 효율적인 방법은 ?</h3>
<ul>
<li>정렬되어 있다는 정보를 어떻게 활용할 수 있을까.<ul>
<li>중간에 있는 5와 9를 먼저 비교</li>
</ul>
</li>
</ul>
<hr>
<h3 id="이진-탐색--binary-search---k-찾기">이진 탐색 ( Binary Search ) , K 찾기</h3>
<ul>
<li>오름차순으로 정렬</li>
<li>중간 숫자와 K 비교</li>
<li>같으면 탐색 성공</li>
<li>K가 작으면 앞부분 반에서 같은 방법으로 K 찾고 , K가 크면 뒷부분 반에서 같은 방법으로 찾는다 ← 재귀탐색</li>
</ul>
<hr>
<h2 id="13-동전-거스름-돈">1.3 동전 거스름 돈</h2>
<ul>
<li>물건을 사고 거스름돈을 동전으로 받는다<ul>
<li>가장 적은 수의 동전을 받을라면?</li>
<li>어떻게 해야지 가장 적은 수의 동전을 찾을까?</li>
</ul>
</li>
<li>가장 큰 액면의 동전부터 차례로  남은 거스름돈 액수를 넘지 않는 한도에서 ( 욕심 내어) 선택한다</li>
<li>그리디 (Greedy)  알고리즘</li>
</ul>
<h3 id="코드-구현-1">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void main() {
    int coin = 0;
    int rest = 730;

    while (rest &gt; 0) {
        if (rest &gt;= 500) {
            rest -= 500;
            coin++;
        }
        else if (rest &gt;= 100) {
            rest -= 100;
            coin++;
        }
        else if (rest &gt;= 50) {
            rest -= 50;
            coin++;
        }
        else if (rest &gt;= 10) {
            rest -= 10;
            coin++;
        }
        else {
            break;
        }
    }
    printf(&quot;필요한 동전의 개수: %d\n&quot;, coin);
}</code></pre>
<ul>
<li>주의할 점 : 그리디 알고리즘은 항상 최적의 해를 가져다주진 않는다.</li>
</ul>
<hr>
<h2 id="14-한붓그리기--eulerian-path-">1.4 한붓그리기 ( Eulerian Path )</h2>
<ul>
<li>종이에서 연필을 떼지 않고 그리는 한붓그리기 문제</li>
<li>어느 한 점에서 출발하여 모든 간선을 한 번만 지나서 출발점으로 돌아오되 , 궤적을 그리는 동안 연필이 종이에서 떨어지면 안됨. ( 단, 점은 여러번 방문 가능 )</li>
<li>현재 점으로 돌아오는 싸이클이 있으면 진행한다.</li>
<li>단 ,  외길이면 , 즉 , 인접한 점이 하나 밖에 없으면 사이클 체크 없이 인접한 점으로 진행한다.</li>
</ul>
<h3 id="코드-구현-2">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define MAX 10

int graph[MAX][MAX];
int degree[MAX];
int n;

void initGraph() {
    n = 4;
    graph[1][2] = graph[2][1] = 1;
    graph[2][3] = graph[3][2] = 1;
    graph[3][4] = graph[4][3] = 1;
    graph[4][1] = graph[1][4] = 1;
}

void calcDegree() {
    for (int i = 1; i &lt;= n; i++) {
        degree[i] = 0;
        for (int j = 1; j &lt;= n; j++) {
            if (graph[i][j] == 1)
                degree[i]++;
        }
    }
}

void checkEuler() {
    int oddCount = 0;
    for (int i = 1; i &lt;= n; i++) {
        if (degree[i] % 2 == 1)
            oddCount++;
    }

    if (oddCount == 0)
        printf(&quot;Eulerian Circuit 가능\n&quot;);
    else if (oddCount == 2)
        printf(&quot;Eulerian Path 가능\n&quot;);
    else
        printf(&quot;한붓그리기 불가능\n&quot;);
}

void dfs(int u) {
    for (int v = 1; v &lt;= n; v++) {
        if (graph[u][v]) {
            graph[u][v] = graph[v][u] = 0;
            dfs(v);
        }
    }
    printf(&quot;%d &quot;, u);
}

void main() {
    initGraph();
    calcDegree();
    checkEuler();
    printf(&quot;한붓그리기 경로 (역순): &quot;);
    dfs(1);
    printf(&quot;\n&quot;);
}
</code></pre>
<hr>
<h2 id="15-미로-찾기">1.5 미로 찾기</h2>
<ul>
<li>오른손 법칙<ul>
<li>현 위치에서 한 방향을 선택하고 , 오른손을 벽에 댄다.</li>
<li>그리고 출구가 나올 때 까지 계속 오른손을 벽에서 떼지 않고 계속 걸어간다.</li>
</ul>
</li>
</ul>
<h3 id="코드-구현-3">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

#define N 7

int maze[N][N] = {
    {1,1,1,1,1,1,1},
    {1,0,0,0,1,0,1},
    {1,0,1,0,1,0,1},
    {1,0,1,0,0,0,1},
    {1,0,1,1,1,0,1},
    {1,0,0,0,0,0,1},
    {1,1,1,1,1,1,1}
};

// 북, 동, 남, 서
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};

// 오른손 법칙 미로 탐색
void rightHandMaze(int startX, int startY, int endX, int endY) {
    int x = startX, y = startY;
    int dir = 1; // 시작 시 오른쪽(동쪽)을 바라본다고 가정
    int step = 0;

    while (1) {
        printf(&quot;(%d, %d)\n&quot;, x, y);
        if (x == endX &amp;&amp; y == endY) {
            printf(&quot;출구 도착! 총 %d걸음\n&quot;, step);
            break;
        }

        // 오른쪽 방향
        int rightDir = (dir + 1) % 4;
        int rightX = x + dx[rightDir];
        int rightY = y + dy[rightDir];

        if (maze[rightX][rightY] == 0) {
            // 오른쪽이 길이면 오른쪽으로 회전 후 전진
            dir = rightDir;
            x += dx[dir];
            y += dy[dir];
        }
        else if (maze[x + dx[dir]][y + dy[dir]] == 0) {
            // 정면이 길이면 그대로 전진
            x += dx[dir];
            y += dy[dir];
        }
        else {
            // 둘 다 벽이면 왼쪽으로 회전
            dir = (dir + 3) % 4;
        }

        step++;
        if (step &gt; 1000) { // 무한루프 방지
            printf(&quot;출구를 찾지 못했습니다.\n&quot;);
            break;
        }
    }
}

int main() {
    rightHandMaze(1, 1, 5, 5);
    return 0;
}
</code></pre>
<hr>
<h2 id="16-가짜-동전-찾기">1.6 가짜 동전 찾기</h2>
<ul>
<li>여러 개의 동전 속에 1개의 가짜 동전이 있음</li>
<li>가짜 동전의 무게는 정상적인 동전보다 약간 가벼움</li>
<li>최소의 양팔 저울을 사용하는 횟수로 가짜 동전을 찾아내기</li>
</ul>
<ol>
<li><p>동전 1개를 저울 왼편에 올리고 , 나머지 동전을 하나씩 비교</p>
<ul>
<li>1 ~ (n-1)회</li>
<li>운이 좋으면 1번만에</li>
<li>최악은 가장 마지막에</li>
<li>총 동전 수가 n이라면 n-1번 저울 재야 함.</li>
</ul>
</li>
<li><p>동전을 2개씩 짝을 지어 ,  n/2 짝을 각 각 저울에 달아서 가짜 동전을 찾아내기</p>
<ul>
<li>1 ~ n/2 회</li>
<li>최악의 경우의 수가 n/2로 감소</li>
</ul>
</li>
<li><p>동전 더미를 반으로 나누어 저울 양편에 놓아서 찾기</p>
<ul>
<li><p>동전 더미를 반으로 나누어 저울에 달고, 가벼운 쪽의 더미를 계속 반으로 나누어 저울에 단다.</p>
</li>
<li><p>분하뢴 더미의 동전 수가 1개씩이면 마지막으로 저울을 달아 가벼운 쪽의 동전이 가짜</p>
</li>
<li><p><strong>이 알고리즘은 운이 좋을 때가 없다.</strong></p>
<p>  <em>→ 왜냐하면 항상 마지막에 가짜 동전을 찾기 때문이다.</em></p>
</li>
<li><p>1024개가 있을 때 몇 번 저울에 달아야할까 ?</p>
<p>  → 10번</p>
</li>
<li><p>O(log2n)</p>
</li>
</ul>
</li>
</ol>
<hr>
<h2 id="17-독이-든-술단지">1.7 독이 든 술단지</h2>
<ul>
<li>많은 술단지들 중에 독이 든 술단지를 찾아야</li>
<li>가능한 조사하는 신하를 줄이고 싶음</li>
<li>독을 먹으면 일주일 뒤에 죽음</li>
<li><strong>적은 수의 술단지에 대해서 생각해보고 술단지 수를 늘려가며 일반적인 규칙을 찾는게 핵심</strong></li>
<li>만약 술단지가 2개라면<ul>
<li>1명의 신하가 한 술단지를 먹고 죽으면 그 술단지에 독이, 죽지 않는다면 다른 술단지에 독이 있다는 것</li>
</ul>
</li>
<li>4개라면 ?<ul>
<li>아까와 같은 방법으로 한다면 3명의 신하가 필요하다.</li>
<li>여기서 신하를 3명에서 2명으로 줄일 수 있는 방법은 없을까 ?</li>
<li>문제의 핵심은 신하가 “ 하나의 술단지만 먹으라는 조건은 없었다”</li>
<li>4개의 술단지 중 2개는 A와 B 신하 한명씩 마시고 , 하나는 둘 다 마시게 한다면?<ul>
<li>만약 A가 죽는다면 → A가 먹은 술단지에</li>
<li>B가 죽는다면 → B가 먹은 술단지에</li>
<li>A와 B 모두 죽는다면 → 같이 마신 술단지에</li>
<li>A와 B 모두 살았다면 → 먹지 않은 남은 술단지에.</li>
</ul>
</li>
</ul>
</li>
<li>이러한 원리로 문제를 풀어가면 된다.</li>
</ul>
<h3 id="코드-구현-4">코드 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main(void) {
    int N;
    printf(&quot;술단지 개수 N을 입력하세요: &quot;);
    if (scanf_s(&quot;%d&quot;, &amp;N) != 1 || N &lt;= 0) {
        printf(&quot;유효한 양의 정수를 입력하세요.\n&quot;);
        return 0;
    }

    // 최소 신하 수 k = ceil(log2(N))을 반복문으로 계산
    int k = 0;
    int cap = 1;                 // 2^k
    while (cap &lt; N) { cap &lt;&lt;= 1; k++; }

    printf(&quot;\n[요약]\n&quot;);
    printf(&quot;- 술단지 개수 N = %d\n&quot;, N);
    printf(&quot;- 최소 신하 수 k = %d (2^%d = %d &gt;= %d)\n&quot;, k, k, cap, N);
    if (k == 0) {
        // N == 1인 경우: 신하가 필요 없음
        printf(&quot;- 술단지가 1개뿐이므로 신하가 필요 없습니다. 그 1개가 독일 수밖에 없습니다.\n&quot;);
        return 0;
    }

    // 신하별 할당표 출력
    // 신하 s(0=LSB)는 (b-1)의 s번째 비트가 1인 배럴을 마신다.
    printf(&quot;\n[신하별 할당표]\n&quot;);
    for (int s = 0; s &lt; k; s++) {
        printf(&quot;신하 %d (비트 %d): &quot;, s, s);
        int first = 1;
        for (int b = 1; b &lt;= N; b++) {
            if (((b - 1) &gt;&gt; s) &amp; 1) {
                if (!first) printf(&quot;, &quot;);
                printf(&quot;%d&quot;, b);
                first = 0;
            }
        }
        if (first) printf(&quot;(해당 없음)&quot;);
        printf(&quot;\n&quot;);
    }
    return 0;
}
</code></pre>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li><p>순차 탐색 ( Sequential Search ) : 주어진 순서에 따라 차례로 탐색</p>
</li>
<li><p>이진 탐색 ( Binary Search ) : 정렬된 데이터에 대해서 중간에 있는 데이터를 비교하여 그 결과에 따라 같으면 탐색을 마치고 , 다르면 작은 쪽 또는 큰 쪽을 같은 방식으로 탐색</p>
</li>
<li><p>동전 거스름돈 문제에서 항상 욕심 내어 가장 큰 동전을 선택 → 그리디 알고리즘</p>
</li>
<li><p>한붓그리기 문제는 오일러 서킷 ( Euler Circuit ) 문제와 같다. 알고리즘의 핵심은 현재 점에서 사이클이 존재하면 진행.</p>
</li>
<li><p>가짜 동전 찾기에서 동전 더미를 반으로 분할하여 저울에 달고, 가짜 동전이 있는 더미를 계속해서 반으로 나누어 저울에 단다. ← 분할 정복 ( Divide And Conquer ) 알고리즘</p>
</li>
<li><p>독이든 술 단지 문제는 2진수를 활용 ( Binary Tree ) 하여 해를 찾는다.</p>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Algorithm Study : 2weeks - 분할 정복 알고리즘]]></title>
            <link>https://velog.io/@jung_ji_in02/Algorithm-Study-2weeks-%EB%B6%84%ED%95%A0-%EC%A0%95%EB%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@jung_ji_in02/Algorithm-Study-2weeks-%EB%B6%84%ED%95%A0-%EC%A0%95%EB%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Mon, 15 Sep 2025 05:50:56 GMT</pubDate>
            <description><![CDATA[<h1 id="분할-정복-알고리즘---divide-and-conquer-algorithm-이란-">[분할 정복 알고리즘 - Divide and Conquer Algorithm] 이란 ?</h1>
<ul>
<li>주어진 문제의 입력을 분할하여 문제를 해결(정복)하는 방식의 알고리즘<ul>
<li>분할한 입력에 대하여 동일한 알고리즘을 적용하여 해를 계산</li>
<li>이들의 해를 취합하여 원래 문제의 해를 얻음</li>
</ul>
</li>
<li>부분 문제와 부분 해<ul>
<li>분할된 입력에 대한 문제를 부분 문제 (subproblem)</li>
<li>부분 문제의 해를 부분 해</li>
<li>부분 문제는 더 이상 분할할 수 없을 때까지 분할</li>
</ul>
</li>
</ul>
<p>참고:  <a href="https://olrlobt.tistory.com/45">https://olrlobt.tistory.com/45</a></p>
<hr>
<h1 id="분할-정복-알고리즘과-dp의-차이점">[분할 정복 알고리즘과 DP의 차이점]</h1>
<ul>
<li>분할정복 알고리즘과 동적 계획법은 모두 <strong>대규모 문제를 해결</strong>하기 위한 알고리즘</li>
<li><strong>한 문제를 작게 나눈다는 점에서 유사한 접근 방법</strong>을 가지고 있다. 하지만, 목적과 사용 방법에 분명한 차이가 있다.</li>
<li>분할정복:<ul>
<li>문제를 작게 나누어 해결하고, 이 해결을 결합하여 원래의 문제를 해결.</li>
<li>재귀를 이용하여 구현 (부분 문제들이 서로 독립적일 때 사용하기 좋다.)</li>
<li>예) 정렬 알고리즘인 합병 정렬(Merge Sort)은 분할정복 알고리즘을 이용하여 구현.</li>
</ul>
</li>
<li>동적 계획법(DP)<ul>
<li>하위 문제들을 한 번씩만 계산, 이를 이용하여 상위 문제를 효율적으로 계산하는 것에 목적을 둔다.</li>
<li>반복문을 이용하여 구현하는 것이 일반적 (부분 문제들이 서로 종속적일 때 사용하기 좋다.)</li>
<li>예) 최단 경로 문제는 동적 계획법을 이용하여 해결.</li>
</ul>
</li>
</ul>
<aside>
💡

<p>문제의 특성에 따라 분할정복 알고리즘과 동적 계획법 중 적합한 알고리즘을 선택하여 사용하는 것이 중요하다.</p>
</aside>

<hr>
<h1 id="분할정복-알고리즘의-종류-및-탐색과정"><strong>[분할정복 알고리즘의 종류 및 탐색과정]</strong></h1>
<ul>
<li>이 프로세스는 작게 분할한 문제가 직접 해결 가능할 만큼 충분히 작아질 때까지 재귀적으로 계속된다.</li>
</ul>
<p>일반적으로 분할정복 알고리즘은 다음 세 단계로 구성된다.</p>
<ol>
<li>합병 정렬 (Merge Sort)</li>
<li>퀵 정렬 (Quick Sort)</li>
<li>이진 탐색 (Binary search)</li>
<li>선택 문제 (Selection) 알고리즘<ul>
<li>문제가 2개로 분할되나, 그 중에 1개의 부분 문제는 고려할 필요가
없으며, 부분 문제의 크기가 일정하지 않은 크기로 감소하는 알고리즘</li>
</ul>
</li>
<li>삽입 정렬, 피보나치 수의 계산<ul>
<li>부분 문제의 크기가 1, 2개씩 감소하는 알고리즘</li>
</ul>
</li>
</ol>
<hr>
<h2 id="분할-정복-설계">[<strong>분할 정복 설계</strong>]</h2>
<ol>
<li><strong>분할(Divide)</strong>: 문제를 작은 하위 문제로 분할.</li>
<li><strong>정복(Conquer)</strong>: 각 하위 문제는 재귀적(반복되는 패턴을 이용하여)으로 해결.</li>
<li><strong>결합(Combine)</strong>: 하위 문제의 해결책을 결합하여 원래 문제를 해결.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/a545ac0c-7620-4c0e-85bc-c736bf28b8dc/image.png" alt=""></p>
<ul>
<li>Divide를 제대로 나누면 Conquer과정은 자동으로 쉬워진다. 그래서 Divide를 잘 설계하는 것이 중요!</li>
<li>분할정복 알고리즘은 재귀 알고리즘이 많이 사용되는데, 이 부분에서 분할정복 알고리즘의 효율성을 깎아내릴 수 있다.</li>
</ul>
<hr>
<h2 id="1-합병-정렬-merge-sort-알고리즘-동작-원리">1. [합병 정렬 (Merge Sort) 알고리즘 동작 원리]</h2>
<ul>
<li>하나의 리스트를 두 개의 균등한 크기로 분할하고 분할된 부분 리스트를 정렬한 다음, 두 개의 정렬된 부분 리스트를 합하여 전체가 정렬된 리스트가 되게 하는 방법.<ul>
<li>n개의 숫자들을 n/2개씩 2개의 부분 문제로 분할</li>
<li>각각의 부분 문제를 순환으로 합병 정렬</li>
<li>2개의 정렬된 부분을 합병하여 정렬(정복)</li>
</ul>
</li>
<li>합병 과정이 문제를 정복하는 것</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/4b8e74ca-96af-41d3-b159-d397a42e6e7e/image.png" alt=""></p>
<ol>
<li><strong>분할(Divide)</strong>: 입력 배열을 같은 크기의 2개의 부분 배열로 분할한다.</li>
<li><strong>정복(Conquer)</strong>: 부분 배열을 정렬한다. 부분 배열의 크기가 충분히 작지 않으면(left&lt;right) 순환 호출을 이용하여 다시 분할 정복 방법을 적용한다.</li>
<li><strong>결합(Combine)</strong>:  정렬된 부분 배열들을 하나의 배열에 합병한다.</li>
</ol>
<h3 id="시간-복잡도">시간 복잡도</h3>
<ul>
<li><p><strong>분할 단계:</strong> 중간 인덱스 계산 + 두 번 재귀 호출 준비 → <strong>O(1)</strong></p>
</li>
<li><p><strong>합병 단계:</strong> 정렬된 길이 <em>m</em>, <em>n</em> 배열 합치기</p>
<p>  → <strong>최대 비교 횟수:</strong> <em>m + n − 1</em></p>
<p>  → <strong>시간:</strong> <strong>O(m + n)</strong></p>
</li>
<li><p><strong>층수(레벨):</strong> 크기 <em>n</em>을 1이 될 때까지 2로 나눔 → <strong>log₂ n</strong></p>
</li>
<li><p><strong>레벨당 작업량:</strong> 모든 원소가 합병에 참여 → <strong>O(n)</strong></p>
</li>
<li><p><strong>전체 시간 복잡도:</strong> 레벨 수 × 레벨당 작업량 = <strong>O(n log n)</strong></p>
<p>  (점화식: <em>T(n) = 2T(n/2) + O(n) ⇒ T(n) = O(n log n)</em>)</p>
</li>
</ul>
<h3 id="간단한-구현">간단한 구현</h3>
<pre><code class="language-java">import java.util.Arrays;

public class Main {
    private static int[] tmp;

    public static void mergeSort(int[] a) {
        if (a == null || a.length &lt; 2) return;
        tmp = new int[a.length];
        mergeSort(a, 0, a.length - 1);
    }

    private static void mergeSort(int[] a, int l, int r) {
        if (l &gt;= r) return;
        int m = (l + r) &gt;&gt;&gt; 1;      // (l+r)/2 overflow 방지
        mergeSort(a, l, m);
        mergeSort(a, m + 1, r);
        merge(a, l, m, r);
    }

    private static void merge(int[] a, int l, int m, int r) {
        int i = l, j = m + 1, k = l;
        while (i &lt;= m &amp;&amp; j &lt;= r) {
            if (a[i] &lt;= a[j]) tmp[k++] = a[i++];
            else              tmp[k++] = a[j++];
        }
        while (i &lt;= m) tmp[k++] = a[i++];
        while (j &lt;= r) tmp[k++] = a[j++];
        System.arraycopy(tmp, l, a, l, r - l + 1);
    }

    public static void main(String[] args) {
        int[] src = {1, 9, 8, 5, 4, 2, 3, 7, 6};
        mergeSort(src);
        System.out.println(Arrays.toString(src));
    }
}
</code></pre>
<p>출처:</p>
<p><a href="https://yunmap.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Java%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%89%AC%EC%9A%B4-Merge-Sort-%EB%B3%91%ED%95%A9-%EC%A0%95%EB%A0%AC-%ED%95%A9%EB%B3%91-%EC%A0%95%EB%A0%AC">https://yunmap.tistory.com/entry/알고리즘-Java로-구현하는-쉬운-Merge-Sort-병합-정렬-합병-정렬</a></p>
<p>[현미와 백미는 섞어먹자.:티스토리]</p>
<h3 id="합병-정렬의-단점">합병 정렬의 단점</h3>
<ul>
<li>대부분의 정렬 알고리즘들은 입력을 위한 메모리 공간과 O(1) 크기의 메모리 공간만을 사용하면서 정렬 수행<ul>
<li>O(1) 크기의 메모리 공간이란 입력 크기 n과 상관없는 크기의 공간(예를 들어, 변수, 인덱스 등)을 의미</li>
</ul>
</li>
<li>합병 정렬의 공간 복잡도: O(n)<ul>
<li>입력을 위한 메모리 공간 (입력 배열)외에 추가로 입력과 같은 크기의 공간 (임시 배열)이 별도로 필요.</li>
<li>2개의 정렬된 부분을 하나로 합병하기 위해, 합병된 결과를 저장할 곳이 필요하기 때문</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-퀵-정렬-quick-sort-알고리즘-동작-원리">2. [퀵 정렬 (Quick Sort) <strong>알고리즘 동작 원리]</strong></h2>
<ul>
<li>퀵 정렬은 분할 정복 알고리즘으로 분류<ul>
<li>사실 알고리즘이 수행되는 과정을 살펴보면 정복 후 분할하는 알고리즘</li>
</ul>
</li>
<li>퀵 정렬 알고리즘은 문제를 2개의 부분 문제로 분할<ul>
<li>각 부분 문제의 크기가 일정하지 않은 형태의 분할 정복 알고리즘</li>
</ul>
</li>
<li>퀵 정렬은 피봇(pivot)이라 일컫는 배열의 원소(숫자)를 기준으로 피봇보다 작은 숫자들은 왼편으로, 피봇보다 큰 숫자들은 오른편에 위치하도록 분할하고, 피봇을 그 사이에 놓는다.<ul>
<li>퀵 정렬은 분할된 부분문제들에 대해서도 위와 동일한 과정을 순환으로 수행하여 정렬</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/eb3d6ac3-1f15-440c-926e-d89a45cf942f/image.png" alt=""></p>
<ol>
<li><strong>분할(Divide)</strong>:  피봇 하나를 선택하여 피봇을 기준으로 2개의 부분 배열로 분할한다.</li>
<li><strong>정복(Conquer)</strong>:  피봇을 기준으로 피봇보다 큰 값, 혹은 작은 값을 찾는다. 왼쪽에서 부터 피봇보다 큰 값을 찾고 오른쪽에서 부터는 피봇보다 작은 값을 찾아서 두 원소를 교환한다. 분할된 부분 배열의 크기가 0이나 1일 될때까지 반복한다.</li>
<li><strong>결합(Combine)</strong>:   conquer과정에서 값의 위치가 바뀌므로 따로 결합은 하지 않는다.</li>
</ol>
<h3 id="간단한-구현-1">간단한 구현</h3>
<pre><code class="language-java">import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] arr = {3, 5, 2, 7, 1, 4, 6};
        int[] tmp = new int[arr.length];
        mergeSort(arr, tmp, 0, arr.length - 1);
        System.out.println(&quot;합병 정렬: &quot; + Arrays.toString(arr));
}

    public static void mergeSort(int[] arr, int[] tmp, int left, int right) {
    if (left &gt;= right) return;
    int mid = (left + right) &gt;&gt;&gt; 1;
    mergeSort(arr, tmp, left, mid);
    mergeSort(arr, tmp, mid + 1, right);
    merge(arr, tmp, left, mid, right);
}

    private static void merge(int[] arr, int[] tmp, int left, int mid, int right) {
    int i = left, j = mid + 1, k = left;

    // 두 포인터로 병합 (안정성: 왼쪽 &lt;= 오른쪽이면 왼쪽 먼저)
    while (i &lt;= mid &amp;&amp; j &lt;= right) {
        if (arr[i] &lt;= arr[j]) tmp[k++] = arr[i++];   // stable
        else                 tmp[k++] = arr[j++];
    }
    while (i &lt;= mid)  tmp[k++] = arr[i++];
    while (j &lt;= right) tmp[k++] = arr[j++];

    System.arraycopy(tmp, left, arr, left, right - left + 1);
}</code></pre>
<p>}</p>
<h3 id="퀵-정렬quick-sort-성능-요약">퀵 정렬(Quick Sort) 성능 요약</h3>
<ul>
<li><p><strong>핵심:</strong> 피봇 선택 품질이 성능을 좌우함. 극단(최소/최대) 피봇이면 한쪽으로 치우친 분할 발생.</p>
</li>
<li><p><strong>최악(Worst) 경우:</strong></p>
<p>  피봇이 매번 최소(또는 최대) → 비교 횟수 합이 <code>n-1 + n-2 + … + 1 = n(n-1)/2</code> → <strong>O(n²)</strong></p>
</li>
<li><p><strong>최선(Best) 경우:</strong></p>
<p>  매 단계에서 반씩 균등 분할 → 각 층 비교 <strong>O(n)</strong> × 층수 <strong>log₂n</strong> → <strong>O(n log n)</strong></p>
</li>
<li><p><strong>평균(Average) 경우:</strong></p>
<p>  피봇을 무작위로 고른다고 가정하면, 기대 성능은 최선과 동일하게 <strong>O(n log n)</strong></p>
</li>
<li><p><strong>근거 요약:</strong></p>
<ul>
<li>층별 비용은 항상 입력 크기에 비례(<strong>O(n)</strong>).</li>
<li>균등 분할이면 층수는 <strong>log₂n</strong>, 편향 분할이면 층수가 <strong>n</strong>에 가까워져 <strong>O(n²)</strong>로 악화.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="추가-퀵-정렬-피봇-선정--성능-향상-요약">추가: 퀵 정렬: 피봇 선정 &amp; 성능 향상 요약</h2>
<h3 id="피봇-선정-방법">피봇 선정 방법</h3>
<ul>
<li><strong>랜덤 피봇:</strong> 무작위로 하나 선택 → 평균적으로 균형 잡힌 분할 기대.</li>
<li><strong>Median-of-Three:</strong> 왼쪽/중간/오른쪽 3개 중 <strong>중앙값</strong>을 피봇으로 선택 → 이미 정렬되었거나 역정렬된 입력에서 편향 완화.<ul>
<li>예: <code>[31, 1, 26]</code> → 중앙값 <strong>26</strong>을 피봇.</li>
</ul>
</li>
<li><strong>Tukey’s Ninther(“세 개의 세 중 중앙값”)</strong>: 배열을 3구간으로 잡고, 각 구간에서 (왼/중/오) 3개 중 중앙값을 구한 뒤, <strong>그 3개 중앙값의 중앙값</strong>을 피봇으로 사용 → 극단적 편향 가능성을 더 낮춤.</li>
</ul>
<h3 id="성능-향상하이브리드">성능 향상(하이브리드)</h3>
<ul>
<li><strong>작은 구간은 삽입 정렬로 전환:</strong> 재귀 오버헤드가 커지는 작은 부분배열(예: <strong>25~50</strong> 이하)에서는 더 이상 분할하지 않고 삽입 정렬 적용.<ul>
<li>근거 부족/환경 의존: 임계값은 <strong>플랫폼·캐시·컴파일러</strong> 등에 따라 달라 실험적으로 정하는 것이 일반적입니다 <em>(확실하지 않음: 고정 최적값은 없음)</em>.</li>
</ul>
</li>
</ul>
<h3 id="복잡도-재정리">복잡도 재정리</h3>
<ul>
<li><strong>최악:</strong> 피봇이 매번 최소/최대 → <strong>O(n²)</strong></li>
<li><strong>평균/최선:</strong> 균등 분할 가정(또는 무작위 피봇 기대치) → <strong>O(n log n)</strong></li>
</ul>
<p><strong>핵심 메시지:</strong> 좋은 피봇(중앙값 근처)을 선택하고, 작은 구간에서 삽입 정렬로 스위칭하면, 실제 실행 시간(상수항)이 유의하게 줄어든다.</p>
<hr>
<h2 id="3-이진-탐색binary-search-알고리즘-동작-원리">3. [이진 탐색<strong>(Binary search)</strong> 알고리즘 동작 원리]</h2>
<ul>
<li>정렬된 데이터를 효과적으로 탐색할 수 있는 방법이다. (정렬된 데이터만 사용 가능; 참고로 정렬되지 않은 데이터 탐색은 파라메트릭 서치로 가능)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/2dabc0c8-2eb1-46dc-833a-58d3117a253b/image.png" alt=""></p>
<ol>
<li><strong>분할(Divide)</strong>:  배열의 가운데 원소를 기준으로 왼쪽, 오른쪽 부분배열로 분할한다. 탐색키와 가운데 원소가 같으면 분할을 종료한다.</li>
<li><strong>정복(Conquer)</strong>:  탐색키가 가운데 원소보다 작으면 왼쪽 부분배열을 대상으로 이진 탐색을 순환 호출하고, 크면 오른쪽 부분배열을 대상으로 이진 탐색을 호출한다.</li>
<li><strong>결합(Combine)</strong>:   탐색 결과가 직접 반환되므로 결합이 불필요하다.</li>
</ol>
<aside>
💡

<p><strong>[파라메트릭 서치 (이분탐색)] 란 ?</strong></p>
<p><strong>최적화 문제를 결정 문제로 바꾸어 해결하는 기법</strong>이다. 결정 문제란, &#39;예&#39; 혹은 &#39;아니오&#39;로 답하는 문제를 말한다. &#39;주어진 범위에서 원하는 조건을 만족하는 가장 알맞은 값을 찾는 문제&#39;에 주로 파라메트릭 서치를 사용한다.</p>
</aside>

<hr>
<h3 id="간단한-구현-2">간단한 구현</h3>
<ol>
<li>반복문을 이용한 방법</li>
</ol>
<pre><code class="language-cpp">int binarySearch (int arr[], int low, int high, int key) {
  while (low &lt;= high) {
    int mid = low + (high-low) / 2;

    if (arr[mid] == key) // 종료 조건1 검색 성공.
      return mid;
    else if (arr[mid] &gt; key)
      high = mid - 1;
    else
      low = mid + 1;
  }
  return -1 ; // 종료 조건2 (low &gt; high) 검색 실패.
}</code></pre>
<ol>
<li>재귀 함수를 이용한 방법</li>
</ol>
<pre><code class="language-cpp">int binarySearch (int arr[], int low, int high, int key) {

  if (low &gt; high) // 종료 조건2 검색 실패.
    return -1;

  int mid = low + (high-low)/2;

  if (arr[mid] == key) // 종료 조건1 검색 성공.
    return mid;
  else if (arr[mid] &gt; key)
    return binarySearch(arr, low, mid-1, key);
  else
    return binarySearch(arr, mid+1, high, key);
}</code></pre>
<hr>
<h2 id="추가-삽입-정렬---insertion-sort">추가: [삽입 정렬 - <strong>Insertion Sort]</strong></h2>
<ul>
<li>자료 배열의 모든 요소를 앞에서부터 차례대로 이미 정렬된 배열 부분과 비교하여 자신의 위치를 찾아 삽입함으로써 정렬을 완성하는 알고리즘.</li>
<li>정렬되지 않은 목록(리스트, 배열)을 정렬된 부분과 정렬되지 않은 부분으로 나누며 정렬되지 않은 부분의 원소를 하나씩 선택하여 정렬된 부분의 적절한 위치에 삽입하는 방식으로 동작.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/ee4b70ff-1bca-4879-b6fa-a0374abed972/image.png" alt=""></p>
<h3 id="간단한-구현-3">간단한 구현</h3>
<pre><code class="language-java">package io.github.ones1kk.study.sorting;

public class InsertionSort {

    public static void insertionSort(int[] arr, int size) {
        for (int i = 1; i &lt; size; i++) {
            int target = arr[i];

            int j = i - 1;
            while (j &gt;= 0 &amp;&amp; target &lt; arr[j]) {
                arr[j + 1] = arr[j];
                j--;
            }

            arr[j + 1] = target;
        }
    }

}</code></pre>
<hr>
<h1 id="분할정복-특징-및-장단점"><strong>[분할정복 특징 및 장단점]</strong></h1>
<ul>
<li>분할된 작은 문제는 원래 문제와 성격이 동일하다 -&gt; 입력 크기만 작아짐</li>
<li>분할된 문제는 서로 독립적이다(중복 제거 X) -&gt; 순환적 분할 및 결과 결합 가능</li>
</ul>
<p>분할정복은 Top-down방식으로 재귀 호출의 장단점과 똑같다고 보면 된다.</p>
<p><strong>분할정복 알고리즘 장점:</strong></p>
<ol>
<li>빠른 속도: 큰 문제를 작은 하위 문제로 분할하고 해결하여 전체 문제를 해결하는 데 걸리는 시간을 줄일 수 있다.</li>
<li>쉬운 병렬화: 분할정복 알고리즘은 하위 문제를 병렬로 처리하기 쉬우므로 멀티코어 시스템에서 성능을 크게 향상할 수 있다.</li>
<li>유연성: 이 알고리즘은 여러 응용 분야에서 사용될 수 있으며, 문제의 복잡도와 데이터 크기에 상관없이 적용할 수 있다.</li>
</ol>
<p><strong>분할정복 알고리즘 단점:</strong></p>
<ol>
<li>추가적인 메모리 요구: 알고리즘은 재귀적으로 호출되므로 많은 추가적인 메모리를 필요로 할 수 있다.</li>
<li>최악의 경우 시간 복잡도: 일부 문제에 대해서는 분할정복 알고리즘이 최악의 경우에도 빠른 해결책을 제공하지 못할 수 있다.</li>
<li>구현의 복잡성: 이 알고리즘은 구현이 비교적 복잡할 수 있으며, 종종 추가적인 문제를 발생시킬 수 있다.</li>
</ol>
<p>분할정복 알고리즘은 많은 문제에 대해 매우 효과적이며, 많은 응용 분야에서 사용된다. 하지만 구현이 복잡하고 추가적인 메모리 요구가 있을 수 있으므로, 적용 전에 장단점을 고려해야 한다.</p>
<table>
<thead>
<tr>
<th><strong>장점</strong></th>
<th><strong>단점</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Top-down 재귀방식으로 구현하기 때문에 코드가 직관적.</td>
<td>재귀 함수 호츨로 오버헤드가 발생할 수 있다.</td>
</tr>
<tr>
<td>문제를 나누어 해결한다는 특징상 병렬적으로 문제를 해결할 수 있다.</td>
<td>∙ 스택에 다량의 데이터가 보관되는 경우 오버플로우가 발생할 수 있다.</td>
</tr>
</tbody></table>
<p>참고:
 <a href="https://loosie.tistory.com/237">https://loosie.tistory.com/237</a></p>
<p><a href="https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html">https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html</a></p>
<hr>
<h1 id="직접-풀어보며-이해하기">[직접 풀어보며 이해하기]</h1>
<p>난이도 별로 한 문제씩 준비해봤다. 브론즈로 먼저 분할 정복 알고리즘이 어떤 느낌인 지 익히고, 실버 문제로 직접 알고리즘을 문제에 적용 시키는 연습을 한 후 골드 문제로 심화과정을 익혀보도록 하겠다.</p>
<h2 id="1-bronze--백준-13277번">1) Bronze : 백준 13277번</h2>
<p><a href="https://www.acmicpc.net/problem/13277">https://www.acmicpc.net/problem/13277</a></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/8d3dd4b5-2428-4fc3-bdf7-811391426808/image.png" alt=""></p>
<p>백준에서 분할 정복으로 분류된 유일한 브론즈 문제이다 !</p>
<p>브론즈 문제가 이 문제 뿐이라 가져오긴 했지만 굉장히 당황스럽다.</p>
<p>매우 간단한 문제이지만 …. 나는 틀리긴 했다 … </p>
<p>사실 이게 분할 정복 알고리즘이랑 무슨 연관인 지는 모르겠다 !!!</p>
<h2 id="구현">구현</h2>
<pre><code>import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        BigInteger n = new BigInteger(st.nextToken());
        BigInteger m = new BigInteger(st.nextToken());

        System.out.println(n.multiply(m));

    }
}</code></pre><h2 id="2-silver--백준-2630번">2) Silver : 백준 2630번</h2>
<p><a href="https://www.acmicpc.net/problem/2630">https://www.acmicpc.net/problem/2630</a></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/e4f82377-3027-4ffb-b021-36cea0b03267/image.png" alt=""></p>
<p>이번에 풀어볼 문제는 2630의 색종이 문제이다 !!</p>
<p>이 문제를 풀 때는 흰색과 파란색 정사각형 색종이의 개수를 알아내야 하는데, 규칙은 이렇다.</p>
<ul>
<li><p>부분 색종이는 모두 같은 색상이어야 한다.</p>
</li>
<li><p>만약 모두 같은 색상이 아닐 경우 색종이를 잘라야 한다.</p>
</li>
<li><p>색종이를 자를 때는 1/2 씩, 즉 절반씩 잘라서 정사각형을 얻어야 한다. </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/d6d25146-0c2f-4722-9665-ca0878af9c27/image.png" alt=""></p>
<p>위의 조건대로 문제를 풀자면 이러한 그림의 형태를 얻을 수 있다!!</p>
<p>코드를 구성할 때 중요한 점은</p>
<ol>
<li><p>각각의 행과 열의 시작점(초기는 (0, 0)이 기준)에서 현재 파티션에 대하여 모두 같은 색상인지 체크를 먼저 해야한다.</p>
</li>
<li><p>색상이 같다면 해당 색상의 개수를 1 증가시키고 함수를 종료한다.</p>
</li>
<li><p>색상이 같지 않다면, 4등분 하여 각 부분 문제로 쪼개어 문제를 푼다.</p>
</li>
</ol>
<p>이러한 조건들로 코드를 구성하자면 다음과 같은 코드를 얻을 수 있다.</p>
<pre><code>import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static int white = 0;
    static int blue = 0;
    static int[][] paper;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        paper = new int[N][N];
        StringTokenizer st;
        for(int i = 0; i &lt; N; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j &lt; N; j++) {
                paper[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        divied(0, 0, N);
        System.out.println(white);
        System.out.println(blue);
    }

    public static void divied(int x, int y, int n) {
        boolean flag = true;
        // 색종이 통일 되어 있는지 탐색
        for(int i = 0; i &lt; n; i++) {
            for(int j = 0; j &lt; n; j++) {
                // 다른 색 하나 있을 경우
                if(paper[x][y] != paper[x + i][y + j]) {
                    flag = false;
                    break;
                }
                if(!flag) break;
            }
        }
        // 탐색한 영역이 한가지 색으로 통일된 경우
        if (flag) {
            if(paper[x][y] == 0) {
                white++;
            }else {
                blue++;
            }
        }else {
            divied(x, y, n / 2);
            divied(x + n / 2, y, n / 2);
            divied(x, y + n / 2, n / 2);
            divied(x + n / 2, y + n / 2, n / 2);
        }
    }

}
</code></pre><h2 id="3-gold--백준-1074번">3) Gold : 백준 1074번</h2>
<p><a href="https://www.acmicpc.net/problem/1074">https://www.acmicpc.net/problem/1074</a></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/7c0ee2db-5310-413c-b910-ee712c3f20da/image.png" alt=""></p>
<p>다음은 백준 골드5의 문제이다… 내 실력으론 혼자서 골드 문제를 풀 수 없다. 골드 5인만큼 난이도가 상당해서 같이 차근차근 풀어보는 것이 좋을 것 같아서 가져왔다!!</p>
<p>문제는 간단하게, Z 모양으로 반복되는 패턴에서,</p>
<p>0행 0열부터 시작한다면, 3행 3열은 몇 번째 숫자를 나타 내는지를 구하는 문제이다.</p>
<p>이 문제를 분할정복을 이용하여 해결하여 봅시다...</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/922d6884-c1cd-4edd-beb5-a03e00662854/image.png" alt=""></p>
<h2 id="먼저-처음에-해야할-것은-분할-단계이다-">먼저, 처음에 해야할 것은 <strong>분할 단계이다 !!!</strong></h2>
<p>일단은 숫자는 신경 쓰지 말고 사각형의 크기에만 집중해 보자. 위의 그림은 반복되는 패턴이 있으므로 4 등분하여 문제를 작게 분할해보자.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/931c02f3-43b5-49fb-9939-c5218ea465a3/image.png" alt=""></p>
<p>이 중, 구하고자 하는 3행 3열은 첫 번째 사각형에 위치한다. 하지만 3행 3열에 집중하지 말고, 사각형에만 집중해 보자.</p>
<p>그리고 보면 이 사각형을 한 번 더 나눌 수 있을 것 같다 !! 아까와 같이 똑같이 반복되는 패턴 4개로 작게 분할 한다.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/635afc32-0f81-4333-ad91-2a7d74b993ec/image.png" alt=""></p>
<p>이 그림에서도 숫자가 아니라 사각형에 집중하자. 엥 !!!! 아니 이것도 뭔가 나눌 수 있을 것 같다 ?? 한 번만 더 나눠보자 !!!.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/5ea18244-baa6-4c85-8639-a01553bd7e5a/image.png" alt=""></p>
<p>이제 더 이상 나눌 수 없을 것 같다 !! 이 것이 가장 작게 나눈 가장 작은 문제가 된다. 현재 그림에는 숫자가 쓰여 있어서 헷갈릴 수 있지만,</p>
<p>가장 작은 사각형 중에 네 번째 사각형이므로 실제 우리가 구한 값은 아래의 그림.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/bc21a616-03b5-4cb6-b434-c9255da2419b/image.png" alt=""></p>
<p>즉 <strong>해결 단계</strong>에서, 3의 값을 구한 것이다.</p>
<p>가장 작은 문제를 해결했으므로, 이제는 작은 문제들을 <strong>결합</strong>시킨다.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/7d5f8e2b-6011-404e-846c-fc00cb11350f/image.png" alt=""></p>
<p>내가 구한 것은 가장 작은 문제에서 3의 값을 나타내지만, 이 작은 문제들의 결합인 위의 문제에서는 네 번째 그림을 나타낸다.</p>
<p>각 사각형의 첫 번째 값이 4 단위로 증가하기 때문에,</p>
<p>(가장 작은 사각형에서 구한 값 3) + (네 번째 사각형 4-1) x ( 단위 4) = 15의 값을 구할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/bea48ce9-4e0a-42f1-94ef-6199b9a4ecea/image.png" alt=""></p>
<p>마지막 위의 결합 부분에서는 구하고자 하는 결과가 첫 번째 사각형 이기 때문에, 그대로 15라는 답이 도출된다.</p>
<p>만약, 위 문제가 3행 3열이 아닌, 7행 3열을 구하는 문제였다면, 위 그림에서 세 번째 사각형에 위치한다.</p>
<p>각 사각형이 16 단위로 증가하기 때문에,</p>
<p>(이 전 사각형에서의 값 15) + ( 세 번째 사각형 3-1 ) x  ( 단위 16) =  47의 값이 도출된다.</p>
<h2 id="전체-코드">전체 코드</h2>
<pre><code>import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int count = 0;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int r = Integer.parseInt(st.nextToken()); //행
        int c = Integer.parseInt(st.nextToken()); //열
        int size = (int) Math.pow(2, N); //한 변의 사이즈

        find(size, r, c);
        System.out.println(count);
    }

    private static void find(int size, int r, int c) {
        if(size == 1)
            return;

        if(r &lt; size/2 &amp;&amp; c &lt; size/2) {
            find(size/2, r, c);
        }
        else if(r &lt; size/2 &amp;&amp; c &gt;= size/2) {
            count += size * size / 4;
            find(size/2, r, c - size/2);
        }
        else if(r &gt;= size/2 &amp;&amp; c &lt; size/2) {
            count += (size * size / 4) * 2;
            find(size/2, r - size/2, c);
        }
        else {
            count += (size * size / 4) * 3;
            find(size/2, r - size/2, c - size/2);
        }
    }
}</code></pre><p>출처: <a href="https://wiselog.tistory.com/133">https://wiselog.tistory.com/133</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Algorithm Study : 1weeks - Dynamic Programing]]></title>
            <link>https://velog.io/@jung_ji_in02/1%EC%A3%BC%EC%B0%A8-DP</link>
            <guid>https://velog.io/@jung_ji_in02/1%EC%A3%BC%EC%B0%A8-DP</guid>
            <pubDate>Mon, 15 Sep 2025 05:49:26 GMT</pubDate>
            <description><![CDATA[<p>문제 풀이에 들어가기에 앞서서 DP에 대해서 먼저 찾아봤다.</p>
<h1 id="동적-계획법---dynamic-programing-dp란"><strong>[동적 계획법 - Dynamic Programing] DP란?</strong></h1>
<ul>
<li>하나의 큰 문제를 작은 문제로 나누어 해결하는 기법을 의미.</li>
<li>기존의 재귀적인 문제 풀이 방법으론 이전의 값(같은 값)을 여러 번 구하는 문제점이 있음</li>
</ul>
<p>→ N이 커질 수록 기하급수적으로 비효율적인 프로그램이 됨. </p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/6676bd9e-8c2c-4839-81d1-f9fa86bc479c/image.png" alt=""></p>
<ul>
<li>그래서 이미 한 번 구한 값은 배열 등에 저장해두고 다시 필요하면 재귀를 돌릴 필요 없이 저장된 값을 불러오면 문제를 해결할 수 있다.</li>
<li>재귀 함수만을 사용하면 점화식으로 문제를 푸는 것이지만, 메모이제이션까지 이용하여 DP, 문제를 작은 문제로 분해 후 해결하는 것이다.</li>
<li>DP를 적용하는 단계에서 구현을 할 때, 보통 두 가지 방법이 있다. 각각 <strong>Top-Down</strong>방식과 <strong>Botton-Up</strong>방식이다.</li>
</ul>
<p><strong>→ Botton-Up</strong>방식은 말 그대로 초기조건을 기반으로 차곡차곡 데이터를 쌓아가 큰 문제의 결과를 도출하는 과정이다. 보통 반복문을 사용한다. 예를 들어 메모이제이션을 위한 배열 dp[]가 있었다면, dp[0] 부터 차례로 값을 채운다. <strong>Bottom-Up</strong>에 한하여 이러한 값 채우기를 본 떠 메모이제이션을  <strong>Tabulation</strong>이라 부르기도 한다.</p>
<p><strong>→ Top-Down</strong>방식은 주로 재귀함수를 사용하며 dp[n]값을 찾기 위한 재귀 함수의 호출이 dp[0](또는 초기 조건까지) 내려간 다음 결과들이 재귀 함수에 맞물리며 재활용되는 방식이다.</p>
<aside>
💡

<p>메모이제이션(memoization)이란 입력값에 대한 결괏값을 저장해둠으로써 같은 입력값에 따른 함수의 실행이 중복해서 일어나지 않도록 해주는 기법이다. <a href="https://dense.tistory.com/entry/dp"><strong>DP</strong></a>로 문제를 풀 때 자주 등장하며, 함수의 중복 호출을 방지해 효율을 높여준다. </p>
</aside>

<blockquote>
<p>추가적으로 분할정복도 DP와 함께 자주 언급된다고 한다. 두 기법은 모두 문제를 소문제로 나누어 해결한다는 공통점이 있지만, 분할정복은 하위 문제에서 중복이 일어나지 않는 방면, DP는 하위 문제에서 중복이 일어난다는 차이점이 있다.</p>
</blockquote>
<hr>
<h2 id="dp-성립-조건-체크리스트">DP 성립 조건 체크리스트</h2>
<ul>
<li><strong>최적 부분 구조</strong>: 전체 최적해가 하위 문제들의 최적해 조합으로 표현된다.</li>
<li><strong>중복 하위 문제</strong>: 동일하거나 동형인 하위 문제가 반복해서 등장한다.</li>
<li><strong>DFS , BFS 문제</strong>: DFS , BFS 로 풀 수 있지만 경우의 수가 너무 많은 문제.</li>
<li>최악의 경우의 수를 계산하는 가장 쉬운 방법은 직접 계산해보는 것.</li>
<li>특정 패턴을 찾을 때 까지 직접 계산해보는 것도 하나의 방법이다.</li>
<li><strong>중복 연산이 많은 문제</strong>: 각 위치까지 올 수 있는 최적의 값만 남겨두고 나머지 값들은 다 버린 후, 남겨진 값들 중 가장 좋은 조합을 추리는 식으로 풀이.</li>
</ul>
<hr>
<h2 id="구현-방식-두-가지">구현 방식 두 가지</h2>
<blockquote>
<p>용어 정리: Top-Down = 하향식(메모이제이션), Bottom-Up = 상향식(타뷸레이션)</p>
</blockquote>
<table>
<thead>
<tr>
<th>구분</th>
<th>Top-Down (하향식, Memoization)</th>
<th>Bottom-Up (상향식, Tabulation)</th>
</tr>
</thead>
<tbody><tr>
<td>아이디어</td>
<td>큰 문제에서 시작해 필요한 하위 문제만 재귀로 계산·캐싱</td>
<td>기저 상태부터 테이블을 순서대로 채워 올라간다</td>
</tr>
<tr>
<td>구현</td>
<td>재귀 + 캐시(배열/맵)</td>
<td>반복문 + DP 테이블</td>
</tr>
<tr>
<td>장점</td>
<td>필요한 상태만 계산해 스파스한 경우 유리, 직관적</td>
<td>호출 스택 없음, 상수 오버헤드 작음</td>
</tr>
<tr>
<td>단점</td>
<td>깊은 재귀는 스택 오버플로우 위험, 재귀 오버헤드</td>
<td>모든 상태를 채울 때 불필요 계산 가능, 전개 순서 설계 필요</td>
</tr>
<tr>
<td>메모리</td>
<td>캐시 + 호출 스택</td>
<td>테이블, 롤링 배열로 축소 가능</td>
</tr>
</tbody></table>
<hr>
<h2 id="피보나치로-보는-dp-필요성">피보나치로 보는 DP 필요성</h2>
<ul>
<li><strong>순수 재귀</strong>: <code>f(n)=f(n-1)+f(n-2)</code> 호출이 중복되어 시간 복잡도 O(2^n)까지 커진다.</li>
<li><strong>DP 적용</strong>: 한 번 계산한 값을 저장하고 재사용하면 O(n)으로 줄일 수 있다.</li>
</ul>
<hr>
<h2 id="복잡도-비교">복잡도 비교</h2>
<ul>
<li>피보나치수열은 대표적인 동적 프로그래밍의 사례.</li>
<li>피보나치수열을 보면 DP와 비슷하는데 피보나치수열과 DP의 차이점은 재사용.</li>
</ul>
<pre><code class="language-bash">public class Fibonacci {
    public static void main(String[] args) {
        int input = 6;

        System.out.println(fibo(input));
     }

     public static int fibo(int n) {
if (n &lt;= 1) return n;
else return fibo(n-2) + fibo(n-1);
     }
}

// 결과 : 8</code></pre>
<p>숫자가 작을 때는 차이는 없지만 숫자가 커지면 속도가 심각하게 저하된다. 만약 위 코드에서 input이 40이어도 시간이 걸리는 게 눈에 보이고 만약 100이라면 정말 오래 걸릴 것이다.</p>
<ul>
<li>순수 재귀: 시간 O(2^n)</li>
<li>Top-Down / Bottom-Up: 시간 O(n)</li>
<li>공간<ul>
<li>Top-Down: O(n) 캐시 + 최악 O(n) 재귀 스택</li>
<li>Bottom-Up: O(n) 테이블 → 롤링 기법으로 O(1)까지 축소 가능</li>
</ul>
</li>
</ul>
<hr>
<h2 id="메모이제이션-핵심">메모이제이션 핵심</h2>
<ul>
<li><strong>중복 계산 제거</strong>: 이미 구한 값을 캐시에 저장해 재사용한다.</li>
<li><strong>캐싱 자료구조 선택</strong>: 인덱스가 명확하면 배열, 상태가 복합이면 해시맵.</li>
<li><strong>효과</strong>: 지수 시간을 선형·다항 시간으로 대폭 감소시킨다.</li>
</ul>
<hr>
<h2 id="언제-무엇을-쓰나">언제 무엇을 쓰나</h2>
<ul>
<li><strong>Top-Down 권장</strong><ul>
<li>필요한 상태만 일부 계산하면 되는 희소 상태 공간</li>
<li>전이 로직이 복잡하고 재귀로 쓰면 의미가 잘 드러날 때</li>
</ul>
</li>
<li><strong>Bottom-Up 권장</strong><ul>
<li>전개 순서가 명확하고 모든 상태를 채우는 게 자연스러울 때</li>
<li>재귀 깊이가 커서 스택 위험을 피해야 할 때</li>
</ul>
</li>
</ul>
<hr>
<h2 id="적용-시-체크-포인트">적용 시 체크 포인트</h2>
<ul>
<li><strong>기저 사례(Base Case)</strong> 를 정확히 둔다.</li>
<li><strong>전이식(Recurrence)</strong> 이 최적 부분 구조를 보장하는지 확인한다.</li>
<li><strong>상태 정의/차원 을 최소화</strong>해 불필요한 차원을 줄인다.</li>
<li><strong>메모리 최적화</strong> 가 필요하면 <strong>롤링 배열</strong> 등으로 압축한다.</li>
<li>DP 부적합 사례<ul>
<li><strong>하위 최적해의</strong> 조합이 <strong>전체 최적해를</strong> 보장하지 않는 경우</li>
<li>하위 문제가 매번 달라 중복이 거의 없는 경우</li>
</ul>
</li>
</ul>
<hr>
<h2 id="예시-코드">예시 코드</h2>
<h3 id="top-down-하향식-메모이제이션">Top-Down (하향식, 메모이제이션)</h3>
<pre><code class="language-java">import java.util.*;

class FibTopDown {
    static long[] dp;

    static long fib(int n) {
        if (n &lt;= 1) return n;
        if (dp[n] != -1) return dp[n];
        dp[n] = fib(n - 1) + fib(n - 2);
        return dp[n];
    }

    public static void main(String[] args) {
        int n = 50;
        dp = new long[n + 1];
        Arrays.fill(dp, -1);
        System.out.println(fib(n));
    }
}</code></pre>
<h3 id="bottom-up-상향식-타뷸레이션-롤링-배열">Bottom-Up (상향식, 타뷸레이션, 롤링 배열)</h3>
<pre><code class="language-java">class FibBottomUp {
    static long fib(int n) {
        if (n &lt;= 1) return n;
        long prev2 = 0, prev1 = 1; // f(0), f(1)
        for (int i = 2; i &lt;= n; i++) {
            long cur = prev1 + prev2;
            prev2 = prev1;
            prev1 = cur;
        }
        return prev1;
    }

    public static void main(String[] args) {
        int n = 50;
        System.out.println(fib(n));
    }
}</code></pre>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li>DP 적용 조건은 <strong>최적 부분 구조 + 중복 하위 문제</strong> 이다.</li>
<li>구현은 <strong>Top-Down(메모이제이션)</strong> 과 <strong>Bottom-Up(타뷸레이션)</strong> 으로 나뉜다.</li>
<li>피보나치처럼 저장·재사용만으로 <strong>O(2^n) → O(n)</strong> 으로 개선된다.</li>
</ul>
<p>출처:
<a href="https://dense.tistory.com/entry/dp">https://dense.tistory.com/entry/dp</a>
<a href="https://pixx.tistory.com/163">https://pixx.tistory.com/163</a></p>
<p>참고:</p>
<p><a href="https://www.youtube.com/watch?v=0bqfTzpWySY&amp;t=160s">https://www.youtube.com/watch?v=0bqfTzpWySY&amp;t=160s</a></p>
<hr>
<h1 id="문제-풀이">문제 풀이</h1>
<h3 id="문제-선정">문제 선정</h3>
<ul>
<li>모든 문제를 다시 다 풀어보기엔 시간이 비효율적인 것 같아서 내가 나름대로 내 기준을 정하여 문제를 선정해봤다…..</li>
</ul>
<ol>
<li>골드 문제는 모두 선정하였다. 일단 기본적으로 골드 문제라는 건 문제 난이도가 좀 있다는 것이고 , 난이도가 있는 만큼 자세히 짚어보고 넘어가는 것이 좋다고 생각했다.</li>
<li>내가 틀렸던 문제들을 선정하였다. 이유는 약간 이기적이긴 하다 … 내가 틀렸던 문제를 다시 한 번 풀어보기 위해서 선정했다.</li>
<li>창의적인 방법으로 문제를 풀어야하는 문제들을 선정해봤다.</li>
</ol>
<hr>
<h1 id="1-1463번--1로-만들기-실버-3">1) 1463번 : <strong>1로 만들기 (실버 3)</strong></h1>
<ul>
<li>문제 :</li>
</ul>
<p>정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다.</p>
<ol>
<li>X가 3으로 나누어 떨어지면, 3으로 나눈다.</li>
<li>X가 2로 나누어 떨어지면, 2로 나눈다.</li>
<li>1을 뺀다.</li>
</ol>
<p>정수 N이 주어졌을 때, 위와 같은 연산 세 개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.</p>
<hr>
<h2 id="문제-풀이-1">문제 풀이</h2>
<h3 id="1-처음에-주목한-점">1) 처음에 주목한 점</h3>
<p>목표 값이 1이고, 그 목표값을 구하기 위한 조건들을 봤다.</p>
<p>사용할 수 있는 연산이 ÷2, ÷3 , -1 이기 때문에 연산들을 이용하여 조건문을 완성해주면 원하는 코드를 얻을 수 있다고 생각했다.</p>
<p><code>i % 2 == 0</code>, <code>i % 3 == 0</code> 같은 if문으로 가능한 연산만 후보로 삼아야 한다고 판단했다.</p>
<p>그리고 연산을 사용하는 횟수의 최솟값을 출력하라는 것이다. </p>
<p>이 부분이 헷갈렸던 부분인데 예제를 보면 10을 큰 수부터 나눠보기 시작하면 10 -&gt; 5 -&gt; 4 -&gt; 2 -&gt; 1 이렇게 4번의 연산이 필요하다. 하지만 정답은 10 -&gt; 9 -&gt; 3 -&gt; 1 로 3이 정답이다.</p>
<p>또한 내가 재출한 코드에서는 구현하진 않았는데 , 내가 참고한 자료에선 2와 3의 배수, 즉 6으로 나눠지는 경우의 수도 있다. 내가 이 부분은 따로 구현하지 않았는데 통과된 이유는 <code>÷6</code>은 결국 두 번의 허용 연산으로 이루어 지기 때문에 DP 점화식이 이미 그 경우를 자연스럽게 포함하고 있다고 한다.</p>
<p>그럼 코드로 짠다면 다음과 같은 경우의 수로 나눌 수 있겠다.</p>
<hr>
<h2 id="1-입출력-초기화">1. 입출력 초기화</h2>
<pre><code class="language-java">BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];

dp[1] = 0;</code></pre>
<ul>
<li>나는 사실 탑다운, 바텀업이라는 개념 자체를 모르고 출발해서 잘은 몰랐지만 , 내가 풀이한 방식은 바텀업 방식이라고 한다. 무의식적으로 나는 바텀업을 선호하고 있었나보다.</li>
<li>바텀업 방식을 사용하게 된다면 필요한 값이 항상 준비돼 있게되어서 계산이 편해진다.</li>
<li>먼저 전에 계산한 값을 저장해두기 위해 <code>dp</code> 배열을 선언해주고 있고, 인덱스 <code>1..n</code>을 쓰므로 크기를 <code>n+1</code>로 할당해주었다.</li>
<li><code>1</code>은 이미 1이므로 연산 0번이라 초기값을  → <code>dp[1] = 0</code> 이렇게 초기화해두었다.</li>
</ul>
<h2 id="2-점화식과-반복">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 2; i &lt;= n; i++) {
    dp[i] = dp[i - 1] + 1;                 // 기본 후보: 1을 빼는 연산
    if (i % 2 == 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1);  // 2로 나눠떨어지면 후보 갱신
    if (i % 3 == 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1);  // 3으로 나눠떨어지면 후보 갱신
}</code></pre>
<ul>
<li><code>dp[i]</code>는 “마지막 연산이 무엇이었는지”에 따라 다음 후보 중 최솟값을 결정한다.<ul>
<li><code>i → i-1</code>을 마지막에 했다면 <code>dp[i-1] + 1</code></li>
<li><code>i</code>가 2의 배수라면 <code>i → i/2</code> 가능 → <code>dp[i/2] + 1</code></li>
<li><code>i</code>가 3의 배수라면 <code>i → i/3</code> 가능 → <code>dp[i/3] + 1</code></li>
</ul>
</li>
<li>먼저 <code>dp[i-1] + 1</code>로 초기 세팅한 뒤, 조건이 맞을 때만 <code>min</code>으로 갱신해주었다.</li>
<li>이런식으로 dp[2] 부터 dp[n] 까지의 계산 결과를 저장해둔다.</li>
<li>예를 들어 dp[9]를 계산할 땐 (dp[8], dp[3]) 결과에서 dp[9]는 dp[8] 기준으로  -1 연산을 한 번 추가해준 거기 때문에 (dp[8] + 1) 해서 4가 나오지만 , dp[3] 기준에선 ÷3 연산을 한 번 추가해준 것이기 때문에 (dp[3] + 1 ) = 3 이 되어 Math.min 함수가 두 수중에 더 낮은 숫자를 골라주어 dp[9] =  3 이 되는 원리이다.</li>
</ul>
<h2 id="전체-코드">전체 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];

    dp[1] = 0;

    for (int i = 2; i &lt;= n; i++) {
        dp[i] = dp[i - 1] + 1;
        if (i % 2 == 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1);
        if (i % 3 == 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1);
    }

    System.out.println(dp[n]);
    br.close();
  }

}</code></pre>
<p>참고 : <a href="https://st-lab.tistory.com/133">https://st-lab.tistory.com/133</a></p>
<hr>
<h1 id="2-11726번-2×n-타일링-실버-3">2) 11726번: 2×n 타일링 <strong>(실버 3)</strong></h1>
<ul>
<li><p><strong>문제 요약:</strong></p>
<p>  2×n 직사각형을 1×2(세로), 2×1(가로) 타일로 빈틈없이 채우는 방법의 수를 구해라. 정답은 10007로 나눈 나머지를 출력한다.</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-2">문제 풀이</h2>
<h3 id="1-처음에-주목한-점-1">1) 처음에 주목한 점</h3>
<ul>
<li>최종 목표는 “방법의 수”이다! 이때 나는 타일 배치에 주목을 해봤다. 타일의 마지막 형태를 보면 선택지가 2개로 나온다.<ul>
<li>마지막 한 열(세로 1칸 폭)을 세로 타일(1×2) 하나로 채우거나,</li>
<li>마지막 두 열(가로 2칸 폭)을 가로 타일(2×1) 두 개로 채운다.</li>
</ul>
</li>
<li>이 관찰에서 자연스럽게 작은 문제로 쪼개지는 구조가 나온다.</li>
</ul>
<h3 id="2-점화식-도출">2) 점화식 도출</h3>
<ul>
<li><p><code>dp[i]</code> = 2×i 보드를 채우는 방법의 수</p>
</li>
<li><p>마지막 배치에 따라:</p>
<ul>
<li>마지막에 세로를 세우면 남은 모양은 2×(i−1) → <strong><code>dp[i−1]</code></strong></li>
<li>마지막에 가로 두 개를 깔면 남은 모양은 2×(i−2) → <strong><code>dp[i−2]</code></strong></li>
</ul>
</li>
<li><p>따라서,</p>
<pre><code>  dp[i] = dp[i−1] + dp[i−2]</code></pre></li>
<li><p><code>dp[1] = 1</code>  ← 세로 1×2 ( 한 가지 !! )</p>
</li>
<li><p><code>dp[2] = 2</code>  ← 세로 1×2 두 장, 혹은 가로 2×1 두 장 ( 두 가지 !! )</p>
</li>
</ul>
<hr>
<h2 id="1-입출력초기화">1. 입출력/초기화</h2>
<pre><code class="language-java">BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];

dp[1] = 1;
if (n &gt;= 2) dp[2] = 2;</code></pre>
<ul>
<li><code>dp[i]</code>는 “2×i를 채우는 방법 수” 이다.</li>
<li>먼저  <code>dp[1]=1</code>, <code>dp[2]=2</code>를 기저로 두었다.</li>
<li><code>n=1</code>인 입력도 있으므로 <code>dp[2]</code> 대입은 조건부(n≥2)로 처리해주었다.</li>
</ul>
<h2 id="2-점화식과-반복-1">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 3; i &lt;= n; i++) {
    dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
}</code></pre>
<ul>
<li>마지막 배치 기준의 두 경우(<code>i−1</code>, <code>i−2</code>)를 더해주었다 !!</li>
<li>그리고 문제에서 10007 모듈러 계산을 하라고 해서 코드를 넣었는데 아마 너무 수가 커지는 걸 방지해주는 것 같다.</li>
</ul>
<h2 id="전체-코드-1">전체 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] dp = new int[n + 1];

        dp[1] = 1;
        if (n &gt;= 2) dp[2] = 2;

        for (int i = 3; i &lt;= n; i++){
            dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
        }

        System.out.println(dp[n]);
        br.close();
    }
}
</code></pre>
<p>참고: <a href="https://www.youtube.com/watch?v=HmvD3X5pme8&amp;t=437s">https://www.youtube.com/watch?v=HmvD3X5pme8&amp;t=437s</a></p>
<hr>
<h1 id="3-11727번-2×n-타일링-2-실버-3">3) 11727번: 2×n 타일링 2 <strong>(실버 3)</strong></h1>
<ul>
<li><p>문제 요약:</p>
<p>  11726과 거의 같은데, 타일 종류에 2×2가 추가된다. 정답은 10007로 나눈 나머지.</p>
</li>
</ul>
<h2 id="문제-풀이-3">문제 풀이</h2>
<h3 id="1-처음에-주목한-점-2">1) 처음에 주목한 점</h3>
<ul>
<li><p>11726에서 나는 “마지막 배치”를 기준으로 <code>dp[i] = dp[i-1] + dp[i-2]</code>를 만들었었다.</p>
</li>
<li><p>11727에서는 마지막 두 열을 덮는 방법이 1가지가 아니라 2가지가 된다:</p>
<ul>
<li>(A) 가로 타일(2×1) 두 개를 위아래로 쌓기</li>
<li>(B) 2×2 타일 한 장</li>
</ul>
</li>
<li><p>그래서 <code>i-2</code> 부분이 두 배가 된다. 즉,</p>
<pre><code>  dp[i] = dp[i-1] + 2*dp[i-2]</code></pre></li>
</ul>
<h3 id="2-점화식-도출-1">2) 점화식 도출</h3>
<ul>
<li><code>dp[i]</code> = 2×i 보드를 채우는 방법의 수</li>
<li>마지막 배치에 따라:<ul>
<li>세로(1×2) 한 장으로 마지막 한 열을 채우면 → 남은 건 2×(i−1) → <code>dp[i−1]</code></li>
<li>마지막 두 열을 채우는 방법이 2가지:<ul>
<li>(A) 가로(2×1) 두 장 → <code>dp[i−2]</code></li>
<li>(B) 2×2 한 장 → <code>dp[i−2]</code></li>
</ul>
</li>
<li>합치면 <code>dp[i−2] + dp[i−2] = 2*dp[i−2]</code></li>
</ul>
</li>
<li>따라서 <code>dp[i] = dp[i−1] + 2*dp[i−2]</code></li>
<li>기저값<ul>
<li><code>dp[1] = 1</code></li>
<li><code>dp[2] = 3</code> (세로×2, 가로×2, 2×2 한 장 → 총 3가지)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="1-입출력초기화-1">1. 입출력/초기화</h2>
<pre><code class="language-java">BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];

dp[1] = 1;
if (n &gt;= 2) dp[2] = 3;</code></pre>
<ul>
<li>11726과 동일한 흐름이지만 <code>dp[2]</code> 값이 2 x 2 때문에 <strong>3</strong>으로 바뀐다</li>
</ul>
<h2 id="2-점화식과-반복-2">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 3; i &lt;= n; i++){
    dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 2]) % 10007; // = dp[i-1] + 2*dp[i-2]
}</code></pre>
<ul>
<li><code>dp[i - 2]</code> 에서 마지막 두 열을 덮는 방법이 (가로×2, 2×2)로 두 가지이기 때문에 2번 더해준다 !</li>
</ul>
<h2 id="전체-코드-2">전체 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());
    int[] dp = new int[n + 1];
        dp[1] = 1;
        if(n &gt;= 2) dp[2] = 3;

        for(int i = 3; i &lt;= n; i++){
            dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 2]) % 10007;
        }

        System.out.println(dp[n]);

        br.close();
     }
}</code></pre>
<hr>
<h1 id="4-9461번-파도반-수열-실버-3">4) 9461번: <strong>파도반 수열 (실버 3)</strong></h1>
<ul>
<li><p>문제 요약:</p>
<p>  여러 개의 테스트 케이스에 대해 <code>P(n)</code>을 구해 출력한다.</p>
<p>  (입력: <code>T</code>개, 각 <code>n</code>은 <code>1 ≤ n ≤ 100</code>)</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-4">문제 풀이</h2>
<h3 id="1-접근-방법">1) 접근 방법</h3>
<ul>
<li>나는 먼저 dp[1]부터 dp[5]까지의 배열을 먼저 대입해봤다. 그리고 난 다음에 이 배열안에 숨겨진 수열을 찾으려고 했다.</li>
</ul>
<h3 id="2-점화식핵심-규칙">2) 점화식(핵심 규칙)</h3>
<ul>
<li><p>파도반 수열의 앞 값들을 보면:</p>
<p>  <code>1, 1, 1, 2, 2, 3, 4, 5, 7, 9, ...</code></p>
<p>  처럼 되어있따 ! 이 배열을 보면 한가지 규칙이 있다는 걸 눈치챌 수 있다.</p>
<p>  n이 6 이상일 때 부터 n-2 번째와 n-3 번째의 수를 합 한 것이 자신의 숫자가 되는 걸 알아차릴 수 있었고 , 그걸 식으로 나타내면</p>
<pre><code class="language-markup">  P(n) = P(n-2) + P(n-3)    (n ≥ 6)</code></pre>
</li>
<li><p>기저값:</p>
<pre><code class="language-markup">  P(1)=P(2)=P(3)=1
  P(4)=P(5)=2</code></pre>
</li>
<li><p>수가 커질 수 있어서 long 사용해줘야했다 !! 나는 int 형 사용했다가 출력 형태 오류로 틀렸다.</p>
</li>
</ul>
<h3 id="3-구현-포인트">3) 구현 포인트</h3>
<ul>
<li><code>dp[1..100]</code>을 한 번 채워두고, 각 <code>n</code>에 대해 <code>dp[n]</code> 출력</li>
</ul>
<hr>
<h2 id="전체-코드-내-코드">전체 코드 (내 코드)</h2>
<pre><code class="language-java">import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        long[] dp = new long[101];

        dp[1] = dp[2] = dp[3] = 1;
        dp[4] = dp[5] = 2;

        for (int i = 6; i &lt;= 100; i++) {
            dp[i] = dp[i - 2] + dp[i - 3];
        }

        for (int t = 0; t &lt; T; t++) {
            int n = Integer.parseInt(br.readLine());
            System.out.println(dp[n]);
        }
    }
} </code></pre>
<p>참고: <a href="https://st-lab.tistory.com/127">https://st-lab.tistory.com/127</a></p>
<hr>
<h1 id="5-2011--암호코드-골드5">5) 2011 : <strong>암호코드 (골드5)</strong></h1>
<ul>
<li><p><strong>문제 요약:</strong></p>
<p>  1→A, 2→B, …, 26→Z로 매핑할 때, 주어진 숫자 문자열을 해석할 수 있는 경우의 수를 구한다.</p>
<p>  해석이 불가능하면 0을 출력하고, 결과는 1,000,000으로 나눈 나머지를 출력한다.</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-5">문제 풀이</h2>
<h3 id="1-처음에-주목한-점-3">1) 처음에 주목한 점</h3>
<ul>
<li>한 자리(1<del>9)는 그 자리만 해석할 수 있고, 두 자리(10</del>26)는 앞자리와 묶어서 해석할 수 있다.</li>
<li>그래서 어떤 위치 i를 해석할 때 두 가지 선택이 생긴다:<ul>
<li>현재 자리만 해석(한 자리) → <code>… + dp[i-1]</code></li>
<li>앞자리와 함께 해석(두 자리) → <code>… + dp[i-2]</code></li>
</ul>
</li>
<li>단, ‘0’은 단독으로 해석할 수 없고, 10/20만 유효하다.</li>
</ul>
<h3 id="2-점화식-도출-2">2) 점화식 도출</h3>
<ul>
<li><p><code>dp[i]</code> = 앞에서부터 i자리까지 해석하는 경우의 수</p>
<p>  (편하게 쓰려고 빈 문자열도 한 경우로 보고 <code>dp[0] = 1</code>로 둔다.)</p>
</li>
<li><p>한 자리 유효(현재 문자 <code>1~9</code>)면: <code>dp[i] += dp[i-1]</code></p>
</li>
<li><p>두 자리 유효(앞 두 문자 <code>10~26</code>)면: <code>dp[i] += dp[i-2]</code></p>
</li>
<li><p>각 단계에서 <code>dp[i] %= 1_000_000</code></p>
</li>
</ul>
<hr>
<h2 id="1-입출력초기화-2">1. 입출력/초기화</h2>
<pre><code class="language-java">BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
int n = s.length();

int[] dp = new int[n + 1];
dp[0] = 1;

if (s.charAt(0) == &#39;0&#39;) {
    System.out.println(0);
    return;
} else {
        dp[1] = 1;
}</code></pre>
<ul>
<li><code>dp[0]=1</code>: 빈 문자열은 1가지로 취급(점화식 편의를 위한 표준 트릭).</li>
<li>첫 글자가 ‘0’이면 시작부터 해석 불가 → 바로 0 출력.</li>
<li>그렇지 않으면 첫 글자는 <code>1~9</code>이므로 <code>dp[1]=1</code>.</li>
</ul>
<h2 id="2-점화식과-반복-3">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 2; i &lt;= n; i++) {
    int one = s.charAt(i - 1) - &#39;0&#39;;         // 한 자리
    int two = Integer.parseInt(s.substring(i - 2, i)); // 두 자리

    if (one &gt;= 1 &amp;&amp; one &lt;= 9) {
        dp[i] = (dp[i] + dp[i - 1]) % 1000000; // 현재 자리만 해석
    }
    if (two &gt;= 10 &amp;&amp; two &lt;= 26) {
        dp[i] = (dp[i] + dp[i - 2]) % 1000000; // 앞자리와 묶어 해석
    }
}
System.out.println(dp[n]);</code></pre>
<ul>
<li><p><code>one</code>이 1~9면 한 자리 해석 가능 → <code>dp[i-1]</code> 더함.</p>
</li>
<li><p><code>two</code>가 10~26이면 두 자리 해석 가능 → <code>dp[i-2]</code> 더함.</p>
</li>
<li><p>‘0’은 단독 불가이므로 <code>one==0</code>이면 첫 조건이 자동으로 스킵된다.</p>
<p>  대신 <code>two==10</code> 또는 <code>two==20</code>일 때만 두 자리로 처리되어 살아남는다.</p>
</li>
</ul>
<h2 id="전체-코드-3">전체 코드</h2>
<pre><code class="language-java">import java.io.*;

public class Main {

~~~~    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
        int n = s.length();

        int[] dp = new int[n + 1];
        dp[0] = 1;

        if (s.charAt(0) == &#39;0&#39;) {
            System.out.println(0);
            return;
        } else {
            dp[1] = 1;
        }

        for (int i = 2; i &lt;= n; i++) {
            int one = s.charAt(i - 1) - &#39;0&#39;;
            int two = Integer.parseInt(s.substring(i - 2, i));

            if (one &gt;= 1 &amp;&amp; one &lt;= 9) {
                dp[i] = (dp[i] + dp[i - 1]) % 1000000;
            }
            if (two &gt;= 10 &amp;&amp; two &lt;= 26) {
                dp[i] = (dp[i] + dp[i - 2]) % 1000000;
            }
        }

        System.out.println(dp[n]);
    }
}</code></pre>
<p>참고: <a href="https://tussle.tistory.com/1137">https://tussle.tistory.com/1137</a></p>
<hr>
<h1 id="6-2133번-타일-채우기-3×n-골드4">6) 2133번: <strong>타일 채우기</strong> (3×n) <strong>(골드4)</strong></h1>
<ul>
<li><p><strong>문제 요약:</strong></p>
<p>  3×n 보드를 2×1, 1×2 타일로 채우는 방법의 수를 구한다.</p>
<p>  n이 홀수면 애초에 채울 수 없으므로 0.</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-6">문제 풀이</h2>
<h3 id="1-처음에-주목한-점-4">1) 처음에 주목한 점</h3>
<ul>
<li>3×n을 채우려면 가로 폭이 짝수여야 한다. (홀수면 어떤 식으로도 채울 수 없음)</li>
<li>기본 블록(3×2)을 붙이는 경우 외에, 특수 무늬들이 더해져서 단순한 피보나치형이 아니라 누적 합이 들어가는 점화식이 된다.</li>
</ul>
<h3 id="2-점화식-도출-3">2) 점화식 도출</h3>
<ul>
<li><p><code>dp[i]</code> = 3×i 보드를 채우는 방법의 수 로 가정했다.</p>
</li>
<li><p>n이 <strong>홀수</strong>면 애초에 못 채운다 → <code>0</code>.</p>
</li>
<li><p>작은 케이스를 기준으로 규칙을 만든다:</p>
<ul>
<li><code>dp[0] = 1</code> : 아무것도 안 놓는 1가지</li>
<li><code>dp[2] = 3</code> : 3×2를 채우는 3가지 기본 패턴</li>
</ul>
</li>
<li><p>점화식 (i는 짝수, i ≥ 4)</p>
<ul>
<li><p>기본 3가지 패턴로 시작: <code>3 * dp[i-2]</code></p>
</li>
<li><p>그 외 특수 무늬들이 i-4, i-6, … 에서 새로 조합되어 각각 2가지씩 추가됨</p>
<p>  → <code>2 * (dp[i-4] + dp[i-6] + ... + dp[0])</code></p>
</li>
</ul>
</li>
<li><p>합치면:</p>
<pre><code>  dp[i] = 3*dp[i-2] + 2*(dp[i-4] + dp[i-6] + ... + dp[0])   (i는 짝수, i≥4)</code></pre></li>
</ul>
<h3 id="3-예시--velog-참고-">3) 예시! ( Velog 참고 )</h3>
<ul>
<li><code>N=2</code> → 3가지 → <code>DP[2]=3</code></li>
<li><code>N=4</code> → <code>3*DP[2]=9</code>에 <strong>특수 무늬 2개</strong> 추가 → <code>DP[4]=11</code></li>
<li><code>N=6</code> → <code>3*DP[4] + 2*DP[2] + 2*DP[0] = 3*11 + 2*3 + 2*1 = 41</code></li>
</ul>
<hr>
<h2 id="1-입출력초기화-3">1. 입출력/초기화</h2>
<pre><code class="language-java">int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];

if (n % 2 == 1) {
    System.out.println(0); // 홀수는 불가능
    return;
}

dp[0] = 1;
dp[2] = 3;</code></pre>
<ul>
<li>홀수 예외를 초반에 조건식으로 정리하여 필터링 해주었다.</li>
<li><code>dp[0]=1</code>은 뒤에 나올 짝수들을 매핑하기 위해서 처음에 초기화 해주었다!</li>
</ul>
<h2 id="2-점화식과-반복-4">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 4; i &lt;= n; i += 2) {
    dp[i] = dp[i - 2] * 3;           // 기본 3가지 패턴
    for (int j = i - 4; j &gt;= 0; j -= 2) {
        dp[i] += dp[j] * 2;          // 누적 특수 무늬(각 2가지)
    }
}
System.out.println(dp[n]);</code></pre>
<ul>
<li>바깥 루프는 우리가 짝수 파트만 다루면 되기 때문에 짝수 폭만 진행하게 만들었다.</li>
<li>안쪽 루프가 <code>dp[i-4], dp[i-6], …, dp[0]</code>를 모두 더해 특수 무늬를 반영한다.</li>
</ul>
<hr>
<h2 id="전체-코드-4">전체 코드</h2>
<pre><code class="language-java">import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] dp = new int[n + 1];

        if (n % 2 == 1) {
            System.out.println(0); // 홀수 n은 채울 수 없음
            return;
        }

       // 타일이 없을 경우의 수는 아무것도 채우지 않는 경우이다
        dp[0] = 1;

        // 3x1 타일을 채울 수 있는 경우의 수는 0개이다.
        dp[1] = 0;

        // 3x2 타일을 채울 수 있는 경우의 수는 3개이다.
        dp[2] = 3;

                // N은 홀수가 될 수 없고 짝수만 될 수 있기 때문에 2씩 증가를 한다.
        for (int i = 4; i &lt;= n; i += 2) {
            dp[i] = dp[i - 2] * dp[2] + 2;
            for (int j = i - 2; j &gt;= 4; j -= 2) {
                dp[i] += dp[i-j] * 2;
            }
        }

        System.out.println(dp[n]);
    }
}
</code></pre>
<p>참고: <a href="https://velog.io/@newtownboy/JAVA2133%EB%B2%88-%ED%83%80%EC%9D%BC-%EC%B1%84%EC%9A%B0%EA%B8%B0">https://velog.io/@newtownboy/JAVA2133%EB%B2%88-%ED%83%80%EC%9D%BC-%EC%B1%84%EC%9A%B0%EA%B8%B0</a></p>
<hr>
<h1 id="7-2225번-합분해-골드-5">7) 2225번: <strong>합분해 (골드 5)</strong></h1>
<ul>
<li><p><strong>문제 요약:</strong></p>
<p>  0부터 N까지의 정수 K개를 더해서 그 합이 N이 되는 경우의 수를 구하는 프로그램을 작성하시오.</p>
<p>  덧셈의 순서가 바뀐 경우는 다른 경우로 센다(1+2와 2+1은 서로 다른 경우). 또한 한 개의 수를 여러 번 쓸 수도 있다.</p>
<p>  결과는 1,000,000,000으로 나눈 나머지.</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-7">문제 풀이</h2>
<p>먼저 작은 값을 일일히 대입해보면서 감을 잡는 방법으로 진행했다. </p>
<ul>
<li><strong>N=1 일 때</strong><ul>
<li>K=1: 1가지 <code>(1)</code></li>
<li>K=2: 2가지 <code>(1+0, 0+1)</code></li>
<li>K=3: 3가지 <code>(0+1+0, 1+0+0, 0+0+1)</code></li>
</ul>
</li>
<li><strong>N=2 일 때</strong><ul>
<li>K=1: 1가지 <code>(2)</code></li>
<li>K=2: 3가지 <code>(2+0, 0+2, 1+1)</code></li>
<li>K=3: 6가지 <code>(2+0+0, 0+2+0, 0+0+2, 0+1+1, 1+0+1, 1+1+0)</code></li>
</ul>
</li>
<li><strong>N=3 일 때</strong><ul>
<li>K=1: 1가지 <code>(3)</code></li>
<li>K=2: 4가지 <code>(3+0, 0+3, 2+1, 1+2)</code></li>
<li>K=3: 10가지 <code>(3+0+0, 0+3+0, 0+0+3, 1+1+1, 2+0+1, 1+0+2, 0+1+2, 0+2+1, 1+2+0, 2+1+0)</code></li>
</ul>
</li>
</ul>
<p>이 패턴에서 바로 점화식이 나온다:</p>
<pre><code>dp[n][k] = dp[n-1][k] + dp[n][k-1]</code></pre><ul>
<li>마지막에 더한 값을 1 증가시키는 경우(<code>n-1, k</code>)와</li>
<li>항의 개수를 늘려서 같은 합을 만드는 경우(<code>n, k-1</code>)의 합.</li>
</ul>
<h3 id="2-점화식기저값-정리-내-코드-기준">2) 점화식/기저값 정리 (내 코드 기준)</h3>
<ul>
<li><p><code>dp[n][k]</code> = 합이 n이 되도록 k개 수로 만드는 방법 수</p>
</li>
<li><p>기저값</p>
<ul>
<li><code>dp[n][1] = 1</code> (하나로 n을 만드는 방법은 n 하나뿐)</li>
<li><code>dp[1][k] = k</code> (1을 k개로 만드는 방법은 k가지)</li>
<li><code>dp[n][0] = 0</code> (항이 0개면 양의 합은 만들 수 없음)</li>
</ul>
</li>
<li><p><strong>점화식</strong></p>
<pre><code>  dp[n][k] = (dp[n-1][k] + dp[n][k-1]) % MOD</code></pre></li>
</ul>
<hr>
<h2 id="1-입출력초기화-4">1. 입출력/초기화</h2>
<pre><code class="language-java">int[][] dp = new int[N + 1][K + 1];
for (int i = 0; i &lt;= N; i++) {
    dp[i][0] = 0;
    dp[i][1] = 1;
}
for (int i = 0; i &lt;= K; i++) {
    dp[1][i] = i;
}</code></pre>
<ul>
<li><code>dp[i][1]=1</code>, <code>dp[1][i]=i</code> 두 줄로 <strong>N=1, K=1</strong> 축의 기저를 고정.</li>
<li><code>dp[i][0]=0</code>으로 <strong>항 0개</strong>는 불가능하게 처리.</li>
</ul>
<h2 id="2-점화식과-반복-5">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 2; i &lt;= N; i++) {
    for (int j = 2; j &lt;= K; j++) {
        dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % MOD;
    }
}</code></pre>
<hr>
<h2 id="전체-코드-5">전체 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static int N;
    static int K;

    static int MOD = 1_000_000_000;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

        int result = countWaysToSum();
        System.out.println(result);
    }

    public static int countWaysToSum() {
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 0; i &lt;= N; i++) {
            dp[i][0] = 0;
            dp[i][1] = 1;
        }

        for (int i = 0; i &lt;= K; i++) {
            dp[1][i] = i;
        }

        for (int i = 2; i &lt;= N; i++) {
            for (int j = 2; j &lt;= K; j++) {
                dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % MOD;
            }
        }

        return dp[N][K];
    }
}
</code></pre>
<p>참고: <a href="https://superohinsung.tistory.com/293">https://superohinsung.tistory.com/293</a></p>
<hr>
<h1 id="8-9465번-스티커-실버-1">8) 9465번: <strong>스티커 (실버 1)</strong></h1>
<ul>
<li><p>문제 요약:</p>
<p>  2×N 스티커에서 인접(상하좌우)한 스티커는 동시에 뗄 수 없다. 점수가 적힌 스티커들을 골라 최대 점수를 구한다. 테스트 케이스가 여러 개.</p>
</li>
</ul>
<hr>
<h2 id="문제-풀이-8">문제 풀이</h2>
<h3 id="1-처음에-주목한-점-5">1) 처음에 주목한 점</h3>
<ul>
<li>같은 열에서 윗줄과 아랫줄은 서로 인접(세로) → 한 열에서 둘 다 선택 불가.</li>
<li>같은 줄에서 옆 칸(왼/오)은 인접(가로) → 같은 줄에서는 연속해서 붙어 있는 열을 동시에 선택 불가.</li>
<li>결국 열(column) 단위로 보면서, <code>i</code>열에서 윗줄을 고르면 직전(<code>i-1</code>)은 아랫줄만 가능, <code>i-2</code>는 양쪽 줄 모두에서 가능.</li>
</ul>
<h3 id="2-점화식-도출-4">2) 점화식 도출</h3>
<ul>
<li><p>정의</p>
<p>  <code>dp[0][i]</code> = i열의 윗 스티커를 선택했을 때 얻을 수 있는 최대 점수</p>
<p>  <code>dp[1][i]</code> = i열의 아랫 스티커를 선택했을 때 얻을 수 있는 최대 점수</p>
</li>
<li><p>초기값</p>
<pre><code>  dp[0][0] = sticker[0][0]
  dp[1][0] = sticker[1][0]
  n &gt; 1 일 때
  dp[0][1] = dp[1][0] + sticker[0][1]
  dp[1][1] = dp[0][0] + sticker[1][1]</code></pre></li>
<li><p>전개(<code>i ≥ 2</code>)</p>
<pre><code>  dp[0][i] = max(dp[1][i-1], dp[1][i-2]) + sticker[0][i]
  dp[1][i] = max(dp[0][i-1], dp[0][i-2]) + sticker[1][i]
</code></pre><ul>
<li><p><code>i</code>열 윗줄을 고르면, <code>i-1</code>에서는 아랫줄만 가능,</p>
<p>  <code>i-2</code>에서는 아랫줄 선택으로 끝난 상태와 이어 붙이는 게 안전하므로 <code>dp[1][i-2]</code>까지 비교.</p>
</li>
<li><p>아랫줄도 대칭.</p>
</li>
</ul>
</li>
<li><p>정답</p>
<p>  마지막 열에서 둘 중 큰 값:</p>
<pre><code>  answer = max(dp[0][n-1], dp[1][n-1])
</code></pre></li>
</ul>
<hr>
<h2 id="1-입출력초기화-내-코드-흐름">1. 입출력/초기화 (내 코드 흐름)</h2>
<pre><code class="language-java">int t = Integer.parseInt(br.readLine());
while (t-- &gt; 0) {
    int n = Integer.parseInt(br.readLine());
    int[][] sticker = new int[2][n];
    int[][] dp = new int[2][n];

    // 입력
    ...
    // 초기값
    dp[0][0] = sticker[0][0];
    dp[1][0] = sticker[1][0];
    if (n &gt; 1) {
        dp[0][1] = dp[1][0] + sticker[0][1];
        dp[1][1] = dp[0][0] + sticker[1][1];
    }</code></pre>
<ul>
<li><code>n=1</code>일 때는 초기값만으로 처리되고 바로 답을 낸다.</li>
</ul>
<h2 id="2-점화식과-반복-6">2. 점화식과 반복</h2>
<pre><code class="language-java">for (int i = 2; i &lt; n; i++) {
    dp[0][i] = Math.max(dp[1][i - 1], dp[1][i - 2]) + sticker[0][i];
    dp[1][i] = Math.max(dp[0][i - 1], dp[0][i - 2]) + sticker[1][i];
}
int maxScore = Math.max(dp[0][n - 1], dp[1][n - 1]);</code></pre>
<ul>
<li>열을 왼쪽→오른쪽으로 진행하면서 “교차 선택 + 두 칸 전”만 비교하면 된다.</li>
</ul>
<h2 id="전체-코드-내-코드-1">전체 코드 (내 코드)</h2>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());

        StringBuilder sb = new StringBuilder();
        while (t-- &gt; 0) {
            int n = Integer.parseInt(br.readLine());
            int[][] sticker = new int[2][n];
            int[][] dp = new int[2][n];

            // 입력
            for (int i = 0; i &lt; 2; i++) {
                StringTokenizer st = new StringTokenizer(br.readLine());
                for (int j = 0; j &lt; n; j++) {
                    sticker[i][j] = Integer.parseInt(st.nextToken());
                }
            }

            dp[0][0] = sticker[0][0];
            dp[1][0] = sticker[1][0];

            if (n &gt; 1) {
                dp[0][1] = dp[1][0] + sticker[0][1];
                dp[1][1] = dp[0][0] + sticker[1][1];
            }

            // DP 진행
            for (int i = 2; i &lt; n; i++) {
                dp[0][i] = Math.max(dp[1][i - 1], dp[1][i - 2]) + sticker[0][i];
                dp[1][i] = Math.max(dp[0][i - 1], dp[0][i - 2]) + sticker[1][i];
            }

            int maxScore = Math.max(dp[0][n - 1], dp[1][n - 1]);
            sb.append(maxScore).append(&quot;\n&quot;);
        }
        System.out.print(sb);
    }
}
</code></pre>
<p>참고: <a href="https://velog.io/@yanghl98/%EB%B0%B1%EC%A4%80-9465-%EC%8A%A4%ED%8B%B0%EC%BB%A4-JAVA">https://velog.io/@yanghl98/%EB%B0%B1%EC%A4%80-9465-%EC%8A%A4%ED%8B%B0%EC%BB%A4-JAVA</a></p>
<hr>
<h1 id="9-10844---쉬운-계단-수-실버-1">9) 10844 - <strong>쉬운 계단 수 (실버 1)</strong></h1>
<ul>
<li>문제 요약</li>
</ul>
<p>인접한 모든 자리의 차이가 1인 수를 계단 수라고 한다.</p>
<p>N이 주어질 때, 길이가 N인 계단 수가 총 몇 개 있는지 구해보자. 0으로 시작하는 수는 계단수가 아니다.</p>
<p>첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 100보다 작거나 같은 자연수이다.</p>
<p>첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.</p>
<hr>
<h2 id="문제-풀이-9">문제 풀이</h2>
<h3 id="1-처음-주목한-점">1) 처음 주목한 점</h3>
<ul>
<li><strong>첫 자릿수는 0 불가</strong> → 시작 자릿값은 1~9만 가능.</li>
<li>인접 자릿수 차이 = 1 → 자릿값 <code>d</code>의 이웃은 <code>d-1</code> 또는 <code>d+1</code>.</li>
<li>경계값 처리: <code>d=0</code>이면 이웃은 1만, <code>d=9</code>이면 8만 가능.</li>
<li>길이(<code>len</code>)를 1씩 늘리며 마지막 자릿수 기준 DP로 전이하는 구조가 자연스럽다.</li>
</ul>
<h3 id="2-점화식-도출-5">2) 점화식 도출</h3>
<ul>
<li>상태 정의: <code>dp[len][d]</code> = 길이 <code>len</code>에서 *<em>마지막 자릿수가 <code>d</code></em>인 계단 수의 개수.</li>
<li>전이 논리: 길이가 하나 늘어날 때 마지막 자릿수 <code>d</code>는 이전 길이의 <code>d-1</code> 또는 <code>d+1</code>에서만 올 수 있음(경계 <code>0↔1</code>, <code>9↔8</code>).</li>
<li>모듈러 적용: 각 합산마다 <code>MOD=1,000,000,000</code>으로 나머지 처리.</li>
</ul>
<hr>
<h2 id="1-초기값">1) 초기값</h2>
<ul>
<li><code>dp[1][1..9] = 1</code>, <code>dp[1][0] = 0</code> (선행 0 금지)</li>
</ul>
<h2 id="2-점화식">2) 점화식</h2>
<ul>
<li><code>d = 0</code> → <code>dp[len][0] = dp[len-1][1]</code></li>
<li><code>d = 9</code> → <code>dp[len][9] = dp[len-1][8]</code></li>
<li><code>1 ≤ d ≤ 8</code> → <code>dp[len][d] = dp[len-1][d-1] + dp[len-1][d+1]</code></li>
<li>각 항은 매번 <code>% MOD</code> 처리</li>
</ul>
<h1 id="전체-코드-6">전체 코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static final long MOD = 1_000_000_000L;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        long[] prev = new long[10];
        for (int d = 1; d &lt;= 9; d++) prev[d] = 1;  // dp[1][1..9] = 1

        for (int len = 2; len &lt;= N; len++) {
            long[] cur = new long[10];
            cur[0] = prev[1] % MOD;
            for (int d = 1; d &lt;= 8; d++) {
                cur[d] = (prev[d - 1] + prev[d + 1]) % MOD;
            }
            cur[9] = prev[8] % MOD;
            prev = cur;
        }

        long ans = 0;
        for (int d = 0; d &lt;= 9; d++) ans = (ans + prev[d]) % MOD;
        if (N == 1) ans = 9; // dp[1] 합은 1..9의 9개
        System.out.println(ans % MOD);
    }
}</code></pre>
<p>참고: <a href="https://st-lab.tistory.com/134">https://st-lab.tistory.com/134</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조 정복하기 3]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0-3</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0-3</guid>
            <pubDate>Sat, 21 Jun 2025 17:51:49 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-hashing-해싱">📌 Hashing (해싱)</h1>
<h2 id="✅-개념-요약">✅ 개념 요약</h2>
<ul>
<li>Hashing은 데이터를 저장하고 조회하는 데 사용되는 <strong>고속 탐색 기법</strong>으로, <strong>배열 기반</strong> 자료구조와 **해시 함수(hash function)**를 이용하여 구현됨.</li>
<li>일반적으로 해시는 **키(key)**를 기반으로 한 값을 특정한 위치에 빠르게 저장하거나 검색함.</li>
<li><strong>정렬이 필요 없는 탐색 방식</strong>이며, 시간 복잡도는 평균적으로 **O(1)**에 가까움.</li>
</ul>
<hr>
<h2 id="🔐-해시-함수-hash-function">🔐 해시 함수 (Hash Function)</h2>
<ul>
<li>입력값(key)을 고정된 크기의 배열 인덱스로 매핑하는 함수</li>
<li><strong>동일한 입력값은 항상 동일한 해시값을 반환</strong>해야 함</li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li>결정성: 동일한 입력에 대해 항상 동일한 해시값 반환</li>
<li>해시값은 일반적으로 배열의 인덱스로 사용됨</li>
</ul>
<pre><code class="language-c">int hash(int key) {
    return key % TABLE_SIZE;
}</code></pre>
<hr>
<h2 id="⚠️-충돌collision과-처리-방법">⚠️ 충돌(Collision)과 처리 방법</h2>
<ul>
<li>서로 다른 키가 동일한 해시 인덱스를 가질 경우 발생</li>
<li>이를 해결하기 위해 다양한 <strong>충돌 해결 전략</strong>이 사용됨</li>
</ul>
<h3 id="1-체이닝-chaining">1. 체이닝 (Chaining)</h3>
<ul>
<li>각 배열 인덱스가 연결 리스트를 가지며 충돌된 키들을 리스트에 추가</li>
<li><strong>공간 낭비는 줄지만</strong>, 평균 탐색 시간은 O(n/k)로 느려질 수 있음</li>
</ul>
<h3 id="2-선형-탐사-linear-probing">2. 선형 탐사 (Linear Probing)</h3>
<ul>
<li>충돌 발생 시, 다음 인덱스로 이동하여 빈 자리를 찾는 방법</li>
<li><strong>클러스터링 문제</strong> 발생 가능</li>
</ul>
<pre><code class="language-c">int hash(int key) {
    int index = key % TABLE_SIZE;
    while (table[index] != EMPTY) {
        index = (index + 1) % TABLE_SIZE;
    }
    return index;
}</code></pre>
<h3 id="3-이차-탐사-quadratic-probing">3. 이차 탐사 (Quadratic Probing)</h3>
<ul>
<li>선형이 아닌 제곱 간격으로 이동: <code>index + i^2</code></li>
<li>클러스터링을 완화할 수 있음</li>
</ul>
<h3 id="4-더블-해싱-double-hashing">4. 더블 해싱 (Double Hashing)</h3>
<ul>
<li>두 개의 해시 함수를 사용하여 충돌 시 보조 해시 함수로 재계산</li>
<li>가장 효과적인 오픈 어드레싱 방식 중 하나</li>
</ul>
<hr>
<h2 id="🔁-리사이징-resizing">🔁 리사이징 (Resizing)</h2>
<ul>
<li>해시 테이블의 크기를 동적으로 늘리는 방법</li>
<li>보통 **로드 팩터(load factor)**가 일정 비율 이상이 되면 리사이징 수행</li>
<li>리사이징 시 모든 원소를 <strong>새로운 해시 테이블로 재해싱(rehash)</strong> 해야 함</li>
</ul>
<hr>
<h2 id="⏱-시간-복잡도">⏱ 시간 복잡도</h2>
<table>
<thead>
<tr>
<th>연산</th>
<th>평균</th>
<th>최악</th>
</tr>
</thead>
<tbody><tr>
<td>검색(Search)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>삽입(Insert)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>삭제(Delete)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
</tbody></table>
<ul>
<li>충돌이 없다면 O(1), 충돌이 많아질수록 O(n)에 가까워짐</li>
</ul>
<hr>
<h2 id="🟩-장점">🟩 장점</h2>
<ul>
<li>매우 빠른 검색/삽입/삭제 연산 가능 (평균 O(1))</li>
<li>정렬이 필요 없는 환경에서 효율적</li>
</ul>
<h2 id="🟥-단점">🟥 단점</h2>
<ul>
<li>메모리 사용량 증가 가능</li>
<li>해시 함수 설계가 성능에 결정적</li>
<li>충돌 처리 알고리즘이 필요</li>
</ul>
<hr>
<h2 id="✅-활용-예시">✅ 활용 예시</h2>
<ul>
<li>데이터베이스 인덱싱</li>
<li>캐시 시스템 (예: LRU Cache)</li>
<li>중복 검사 (중복 문자 탐지 등)</li>
<li>사전(Dictionary), 집합(Set) 구현</li>
</ul>
<hr>
<h2 id="✅-핵심-요약">✅ 핵심 요약</h2>
<blockquote>
<p>Hashing은 **규칙성 있는 키 변환 함수(hash function)**와 <strong>충돌 대응 전략</strong>을 통해 데이터를 빠르고 효율적으로 다루는 자료구조이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조: 후위 표기식 계산기 예시]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%9B%84%EC%9C%84-%ED%91%9C%EA%B8%B0%EC%8B%9D-%EA%B3%84%EC%82%B0%EA%B8%B0-%EC%98%88%EC%8B%9C</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%9B%84%EC%9C%84-%ED%91%9C%EA%B8%B0%EC%8B%9D-%EA%B3%84%EC%82%B0%EA%B8%B0-%EC%98%88%EC%8B%9C</guid>
            <pubDate>Sat, 21 Jun 2025 17:09:42 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-스택-기본-구현-예제-stack-operations-in-c">📌 스택 기본 구현 예제 (Stack Operations in C)</h1>
<h2 id="✅-전체-기능-요약">✅ 전체 기능 요약</h2>
<p>이 코드는 배열 기반 스택 구조를 C 언어로 구현한 예제입니다. 주요 기능은 다음과 같습니다:</p>
<ol>
<li><strong>스택 초기화, 삽입(push), 제거(pop), 확인(peek)</strong></li>
<li><strong>스택 전체 출력(print)</strong></li>
<li><strong>오버플로우, 언더플로우 검사</strong></li>
<li><strong>예제 실행: 문자 &#39;c&#39;, &#39;a&#39;, &#39;t&#39;, &#39;s&#39;를 삽입하고 출력 후 peek 수행</strong></li>
</ol>
<hr>
<h2 id="🧾-전체-코드">🧾 전체 코드</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

#define N 100

typedef struct StackType {
    int top;
    char stack[N];
} StackType;

void init(StackType* S) {
    S-&gt;top = -1;
}

int isFull(StackType* S) {
    return S-&gt;top == N - 1;
}

int isEmpty(StackType* S) {
    return S-&gt;top == -1;
}

void push(StackType* S, char c) {
    if (isFull(S))
        printf(&quot;Overflow!\n&quot;);
    else {
        S-&gt;top++;
        S-&gt;stack[S-&gt;top] = c;
    }
}

char peek(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top];
}

char pop(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top--];
}

void print(StackType* S) {
    for (int i = 0; i &lt;= S-&gt;top; i++) {
        printf(&quot;%c&quot;, S-&gt;stack[i]);
    }
    printf(&quot;\n&quot;);
}

// -------------------------------------------------------------

int main() {
    StackType S;
    init(&amp;S);

    push(&amp;S, &#39;c&#39;);
    push(&amp;S, &#39;a&#39;);
    push(&amp;S, &#39;t&#39;);
    push(&amp;S, &#39;s&#39;);

    print(&amp;S);  // 출력: cats

    getchar();  // 엔터 대기 (디버깅용)

    printf(&quot;After pop : %c\n&quot;, peek(&amp;S));  // top에 있는 &#39;s&#39; 출력

    return 0;
}</code></pre>
<hr>
<h2 id="✅-출력-예시">✅ 출력 예시</h2>
<pre><code>cats
After pop : s</code></pre><hr>
<h2 id="✅-요약">✅ 요약</h2>
<ul>
<li>이 코드는 스택 연산의 기본을 연습할 수 있는 좋은 예제입니다.</li>
<li>문자열을 문자 단위로 스택에 넣고 꺼내보는 테스트에 적합합니다.</li>
<li>응용 확장: 정수 스택, 동적 할당, 구조체 저장 등</li>
</ul>
<p>필요 시, 다양한 타입(Generic) 또는 동적 메모리 할당 기반으로 리팩터링할 수 있습니다.</p>
<hr>
<h1 id="📌-괄호-검사-프로그램-bracket-matching-using-stack">📌 괄호 검사 프로그램 (Bracket Matching using Stack)</h1>
<h2 id="✅-전체-기능-요약-1">✅ 전체 기능 요약</h2>
<ol>
<li><p><strong>스택 자료구조 정의 및 연산</strong></p>
<ul>
<li>구조체 <code>StackType</code>을 정의하고, <code>push</code>, <code>pop</code>, <code>peek</code>, <code>isEmpty</code>, <code>isFull</code>, <code>print</code> 등의 기본 스택 연산을 구현합니다.</li>
</ul>
</li>
<li><p><strong>괄호 검사 기능 (<code>check</code>)</strong></p>
<ul>
<li>중괄호 <code>{}</code>, 대괄호 <code>[]</code>, 소괄호 <code>()</code>의 짝이 맞는지 검사합니다.</li>
<li>여는 괄호는 스택에 push하고, 닫는 괄호가 나올 때 스택에서 pop하여 짝이 맞는지 비교합니다.</li>
</ul>
</li>
<li><p><strong>실행 흐름</strong></p>
<pre><code class="language-c">char expr[N];
scanf(&quot;%s&quot;, expr);
if (check(expr))
    printf(&quot;Success!\n&quot;);
else
    printf(&quot;Fail\n&quot;);</code></pre>
<ul>
<li>입력된 문자열 내 괄호의 짝이 올바른지 판단하고 그 결과를 출력합니다.</li>
</ul>
</li>
<li><p><strong>예시 입력/출력</strong></p>
<ul>
<li>입력: <code>({[]})</code> → 출력: <code>Success!</code></li>
<li>입력: <code>({[})</code> → 출력: <code>Fail</code></li>
</ul>
</li>
</ol>
<hr>
<h2 id="🧾-전체-코드-1">🧾 전체 코드</h2>
<pre><code class="language-c">#define _CRT_SECURE_NO_WARNINGS
#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;
#include&lt;string.h&gt;

#define N 100

typedef struct StackType
{
    int top;
    char stack[N];
} StackType;

void init(StackType *S) {
    S-&gt;top = -1;
}

int isFull(StackType* S) {
    return S-&gt;top == N - 1;
}

int isEmpty(StackType* S) {
    return S-&gt;top == -1;
}

void push(StackType* S, char c) {
    if (isFull(S))
        printf(&quot;Overflow!\n&quot;);
    else {
        S-&gt;top++;
        S-&gt;stack[S-&gt;top] = c;
    }
}

char peek(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top];
}

char pop(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top--];
}

void print(StackType* S) {
    for (int i = 0; i &lt;= S-&gt;top; i++) {
        printf(&quot;%c&quot;, S-&gt;stack[i]);
    }
    printf(&quot;\n&quot;);
}

int check(char expr[]) {
    StackType S;
    init(&amp;S);

    char c, t;
    int n = strlen(expr);

    for (int i = 0; i &lt; n; i++) {
        c = expr[i];
        if (c == &#39;(&#39; || c == &#39;{&#39; || c == &#39;[&#39;)
            push(&amp;S, c);
        else if (c == &#39;)&#39; || c == &#39;}&#39; || c == &#39;]&#39;) {
            if (isEmpty(&amp;S))
                return 0;
            t = pop(&amp;S);
            if ((t == &#39;(&#39; &amp;&amp; c != &#39;)&#39;) ||
                (t == &#39;{&#39; &amp;&amp; c != &#39;}&#39;) ||
                (t == &#39;[&#39; &amp;&amp; c != &#39;]&#39;))
                return 0;
        }
    }
    return isEmpty(&amp;S);
}

int main() {
    char expr[N];
    scanf(&quot;%s&quot;, expr);

    if (check(expr))
        printf(&quot;Sucess!\n&quot;);
    else
        printf(&quot;Fail\n&quot;);

    /* 디버그용 코드 (사용 안함)
    StackType S;
    init(&amp;S);
    push(&amp;S, &#39;c&#39;);
    push(&amp;S, &#39;a&#39;);
    push(&amp;S, &#39;t&#39;);
    push(&amp;S, &#39;s&#39;);
    print(&amp;S);
    getchar();
    printf(&quot;After pop : %c\n&quot;, peek(&amp;S));
    */

    return 0;
}</code></pre>
<hr>
<h2 id="✅-요약-1">✅ 요약</h2>
<ul>
<li>스택을 사용하여 괄호 짝을 검사하는 전형적인 문제 해결 코드입니다.</li>
<li>실전에서 중첩 구조 검증, HTML/XML 태그 처리, 코드 정적 분석 등에 활용될 수 있습니다.</li>
</ul>
<hr>
<h1 id="후위-표기식-계산기-c-언어-기반">후위 표기식 계산기 (C 언어 기반)</h1>
<h2 id="📌-개요">📌 개요</h2>
<p>이 코드는 <strong>C 언어로 구현된 후위 표기식 계산기</strong>로, 스택 구조를 이용해 수식을 처리합니다. 한 자리 숫자로 구성된 후위 표기식(예: <code>23+5*</code>)을 입력받아 최종 계산 결과를 출력합니다.</p>
<hr>
<h2 id="🔧-사용된-자료구조">🔧 사용된 자료구조</h2>
<h3 id="stacktype-구조체">StackType 구조체</h3>
<pre><code class="language-c">typedef struct StackType {
    int top;
    char stack[N];
} StackType;</code></pre>
<ul>
<li>스택 자료구조를 배열로 구현 (<code>char</code>형 사용)</li>
<li><code>top</code>은 현재 스택의 가장 위 인덱스를 의미함</li>
</ul>
<hr>
<h2 id="🧱-스택-관련-함수">🧱 스택 관련 함수</h2>
<h3 id="초기화">초기화</h3>
<pre><code class="language-c">void init(StackType* S) {
    S-&gt;top = -1;
}</code></pre>
<h3 id="상태-확인">상태 확인</h3>
<pre><code class="language-c">int isFull(StackType* S) {
    return S-&gt;top == N - 1;
}

int isEmpty(StackType* S) {
    return S-&gt;top == -1;
}</code></pre>
<h3 id="삽입-및-제거">삽입 및 제거</h3>
<pre><code class="language-c">void push(StackType* S, char c) {
    if (isFull(S)) printf(&quot;Overflow!\n&quot;);
    else S-&gt;stack[++S-&gt;top] = c;
}

char pop(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top--];
}

char peek(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top];
}</code></pre>
<h3 id="디버그용-출력">디버그용 출력</h3>
<pre><code class="language-c">void print(StackType* S) {
    for (int i = 0; i &lt;= S-&gt;top; i++) {
        printf(&quot;%c&quot;, S-&gt;stack[i]);
    }
    printf(&quot;\n&quot;);
}</code></pre>
<hr>
<h2 id="✅-후위-표기식-계산-함수">✅ 후위 표기식 계산 함수</h2>
<h3 id="함수-정의">함수 정의</h3>
<pre><code class="language-c">int evaluate(char postfix[]) {
    StackType S;
    init(&amp;S);

    int op1, op2, value;
    char c;
    int n = strlen(postfix);

    for (int i = 0; i &lt; n; i++) {
        c = postfix[i];
        if (c != &#39;+&#39; &amp;&amp; c != &#39;-&#39; &amp;&amp; c != &#39;*&#39; &amp;&amp; c != &#39;/&#39;) {
            value = c - &#39;0&#39;;  // 문자 숫자를 정수로 변환
            push(&amp;S, value);
        } else {
            op2 = pop(&amp;S);
            op1 = pop(&amp;S);
            switch (c) {
                case &#39;+&#39;: push(&amp;S, op1 + op2); break;
                case &#39;-&#39;: push(&amp;S, op1 - op2); break;
                case &#39;*&#39;: push(&amp;S, op1 * op2); break;
                case &#39;/&#39;: push(&amp;S, op1 / op2); break;
            }
        }
    }
    return pop(&amp;S);
}</code></pre>
<h3 id="예시-입력출력">예시 입력/출력</h3>
<pre><code>입력: 23+5*   (== (2+3)*5)
출력: 25</code></pre><hr>
<h2 id="🧪-main-함수와-실행-흐름">🧪 main 함수와 실행 흐름</h2>
<pre><code class="language-c">int main() {
    char postfix[N];
    scanf(&quot;%s&quot;, postfix);

    printf(&quot;%d\n&quot;, evaluate(postfix));
    return 0;
}</code></pre>
<ul>
<li>사용자로부터 후위 수식 문자열 입력을 받아 계산 결과 출력</li>
</ul>
<hr>
<h2 id="⚠️-제한-사항">⚠️ 제한 사항</h2>
<ul>
<li>숫자는 <strong>한 자리 숫자만 입력 가능</strong></li>
<li>예: <code>123+*</code> → 해석 불가능 (문자 하나씩만 인식함)</li>
<li>음수, 공백 구분, 여러 자리 숫자, 실수 등은 처리 불가능</li>
</ul>
<hr>
<h2 id="📂-전체-코드">📂 전체 코드</h2>
<pre><code class="language-c">#define _CRT_SECURE_NO_WARNINGS
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

#define N 100

typedef struct StackType {
    int top;
    char stack[N];
} StackType;

void init(StackType* S) {
    S-&gt;top = -1;
}

int isFull(StackType* S) {
    return S-&gt;top == N - 1;
}

int isEmpty(StackType* S) {
    return S-&gt;top == -1;
}

void push(StackType* S, char c) {
    if (isFull(S))
        printf(&quot;Overflow!\n&quot;);
    else {
        S-&gt;top++;
        S-&gt;stack[S-&gt;top] = c;
    }
}

char peek(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top];
}

char pop(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top--];
}

void print(StackType* S) {
    for (int i = 0; i &lt;= S-&gt;top; i++) {
        printf(&quot;%c&quot;, S-&gt;stack[i]);
    }
    printf(&quot;\n&quot;);
}

int evaluate(char postfix[]) {
    StackType S;
    init(&amp;S);

    int op1, op2, value;
    char c;
    int n = strlen(postfix);

    for (int i = 0; i &lt; n; i++) {
        c = postfix[i];
        if (c != &#39;+&#39; &amp;&amp; c != &#39;-&#39; &amp;&amp; c != &#39;*&#39; &amp;&amp; c != &#39;/&#39;) {
            value = c - &#39;0&#39;;
            push(&amp;S, value);
        } else {
            op2 = pop(&amp;S);
            op1 = pop(&amp;S);
            switch (c) {
                case &#39;+&#39;: push(&amp;S, op1 + op2); break;
                case &#39;-&#39;: push(&amp;S, op1 - op2); break;
                case &#39;*&#39;: push(&amp;S, op1 * op2); break;
                case &#39;/&#39;: push(&amp;S, op1 / op2); break;
            }
        }
    }
    return pop(&amp;S);
}

int main() {
    char postfix[N];
    scanf(&quot;%s&quot;, postfix);
    printf(&quot;%d\n&quot;, evaluate(postfix));
    return 0;
}</code></pre>
<hr>
<h2 id="✅-마무리">✅ 마무리</h2>
<p>이 코드는 후위 수식을 계산하는 스택 응용 예제로서, 컴파일러, 계산기, 수식 변환 알고리즘 등을 학습할 때 매우 유용한 구조입니다. 필요한 경우 여러 자리 숫자, 공백 구분 등을 처리하는 버전으로 확장 가능합니다.</p>
<hr>
<h1 id="📌-후위-표기식-계산기-postfix-expression-evaluator">📌 후위 표기식 계산기 (Postfix Expression Evaluator)</h1>
<h2 id="✅-전체-기능-요약-2">✅ 전체 기능 요약</h2>
<ol>
<li><p><strong>스택 자료구조 정의 및 연산</strong></p>
<ul>
<li><code>StackType</code> 구조체를 정의하고 <code>push</code>, <code>pop</code>, <code>peek</code>, <code>isEmpty</code>, <code>isFull</code> 등 기본 스택 연산을 구현함</li>
</ul>
</li>
<li><p><strong>후위 표기식 계산 (evaluate)</strong></p>
<ul>
<li>문자열로 주어진 후위 표기식을 입력받아 스택을 이용해 계산함</li>
<li>예: <code>23+5*</code> → <code>(2 + 3) * 5</code> → <code>25</code></li>
</ul>
</li>
<li><p><strong>실행 흐름</strong></p>
<pre><code class="language-c">char postfix[N];
scanf(&quot;%s&quot;, postfix);
printf(&quot;%d\n&quot;, evaluate(postfix));</code></pre>
<ul>
<li>문자열 형태로 수식을 입력받아 <code>evaluate()</code> 함수로 계산 후 결과 출력</li>
</ul>
</li>
<li><p><strong>제약 사항</strong></p>
<ul>
<li>입력은 <strong>한 자리 정수</strong>만 가능하며, 공백 없음</li>
<li>두 자리 이상의 숫자, 실수, 음수는 처리하지 않음</li>
</ul>
</li>
</ol>
<hr>
<h2 id="🧾-전체-코드-2">🧾 전체 코드</h2>
<pre><code class="language-c">#define _CRT_SECURE_NO_WARNINGS
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

#define N 100

typedef struct StackType {
    int top;
    char stack[N];
} StackType;

void init(StackType* S) {
    S-&gt;top = -1;
}

int isFull(StackType* S) {
    return S-&gt;top == N - 1;
}

int isEmpty(StackType* S) {
    return S-&gt;top == -1;
}

void push(StackType* S, char c) {
    if (isFull(S))
        printf(&quot;Overflow!\n&quot;);
    else {
        S-&gt;top++;
        S-&gt;stack[S-&gt;top] = c;
    }
}

char peek(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top];
}

char pop(StackType* S) {
    if (isEmpty(S)) {
        printf(&quot;Empty!\n&quot;);
        return -1;
    }
    return S-&gt;stack[S-&gt;top--];
}

void print(StackType* S) {
    for (int i = 0; i &lt;= S-&gt;top; i++) {
        printf(&quot;%c&quot;, S-&gt;stack[i]);
    }
    printf(&quot;\n&quot;);
}

int evaluate(char postfix[]) {
    StackType S;
    init(&amp;S);

    int op1, op2, value;
    char c;
    int n = strlen(postfix);

    for (int i = 0; i &lt; n; i++) {
        c = postfix[i];
        if (c != &#39;+&#39; &amp;&amp; c != &#39;-&#39; &amp;&amp; c != &#39;*&#39; &amp;&amp; c != &#39;/&#39;) {
            value = c - &#39;0&#39;;
            push(&amp;S, value);
        } else {
            op2 = pop(&amp;S);
            op1 = pop(&amp;S);
            switch (c) {
                case &#39;+&#39;: push(&amp;S, op1 + op2); break;
                case &#39;-&#39;: push(&amp;S, op1 - op2); break;
                case &#39;*&#39;: push(&amp;S, op1 * op2); break;
                case &#39;/&#39;: push(&amp;S, op1 / op2); break;
            }
        }
    }
    return pop(&amp;S);
}

int main() {
    char postfix[N];
    scanf(&quot;%s&quot;, postfix);
    printf(&quot;%d\n&quot;, evaluate(postfix));
    return 0;
}</code></pre>
<hr>
<h2 id="✅-예시-입력-및-출력">✅ 예시 입력 및 출력</h2>
<p>입력:</p>
<pre><code>23+5*</code></pre><p>출력:</p>
<pre><code>25</code></pre><p>해석:</p>
<ul>
<li><code>2 + 3 = 5</code></li>
<li><code>5 * 5 = 25</code></li>
</ul>
<hr>
<h2 id="✅-확장-방향-아이디어">✅ 확장 방향 (아이디어)</h2>
<ul>
<li>한 자리 숫자 → 여러 자리 숫자 처리 (토큰 기반 파싱 필요)</li>
<li>공백 구분 허용 (<code>&quot;12 3 +&quot;</code>) → <code>strtok</code> 또는 <code>sscanf</code> 활용</li>
<li>음수, 실수 지원 → <code>float</code>, <code>double</code> + 예외 처리 추가</li>
<li>연산자 추가 (<code>%</code>, <code>^</code>, etc)</li>
</ul>
<hr>
<p>후위 표기식 계산기는 스택 자료구조의 대표적인 응용 사례로, 컴파일러, 계산기, 알고리즘 수업 등에서 자주 등장합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조 정복하기 2]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0-2</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0-2</guid>
            <pubDate>Sat, 21 Jun 2025 17:00:28 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-삽입-정렬-insertion-sort">📌 삽입 정렬 (Insertion Sort)</h1>
<h2 id="1-개념">1. 개념</h2>
<p>삽입 정렬은 배열의 왼쪽부터 차례대로 <strong>하나씩 원소를 꺼내</strong>, 그것을 앞에 있는 <strong>정렬된 부분에 적절한 위치에 삽입</strong>하면서 정렬을 완성하는 방식이다.<br>→ 마치 카드를 손에 들고 정렬하는 것과 비슷하다.</p>
<hr>
<h2 id="2-정렬-과정-동작-방식">2. 정렬 과정 (동작 방식)</h2>
<ol>
<li>두 번째 원소부터 시작한다.</li>
<li>현재 원소(<code>key</code>)를 정렬된 부분(왼쪽)에 있는 원소들과 비교한다.</li>
<li><code>key</code>보다 큰 원소들은 오른쪽으로 한 칸씩 이동시킨다.</li>
<li><code>key</code>를 빈자리에 삽입한다.</li>
</ol>
<hr>
<h2 id="3-예시">3. 예시</h2>
<pre><code class="language-c">int arr[] = {5, 2, 4, 6, 1, 3};</code></pre>
<table>
<thead>
<tr>
<th>단계</th>
<th>key</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>초기</td>
<td>-</td>
<td><code>5 2 4 6 1 3</code></td>
</tr>
<tr>
<td>1단계</td>
<td>2</td>
<td><code>2 5 4 6 1 3</code></td>
</tr>
<tr>
<td>2단계</td>
<td>4</td>
<td><code>2 4 5 6 1 3</code></td>
</tr>
<tr>
<td>3단계</td>
<td>6</td>
<td><code>2 4 5 6 1 3</code></td>
</tr>
<tr>
<td>4단계</td>
<td>1</td>
<td><code>1 2 4 5 6 3</code></td>
</tr>
<tr>
<td>5단계</td>
<td>3</td>
<td><code>1 2 3 4 5 6</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="코드-c-언어">코드 (C 언어)</h2>
<pre><code class="language-c">void insertionSort(int arr[], int n){
    int i, j, key;
    for(i = 1; i &lt; n; i++){
        key = arr[i];
        j = i - 1;
        while (j &gt;= 0 &amp;&amp; arr[j] &gt; key){
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;
    }
}</code></pre>
<hr>
<h2 id="4-시간-복잡도">4. 시간 복잡도</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>최선 (이미 정렬됨)</td>
<td>O(n)</td>
<td>비교만 하고 이동 없음</td>
</tr>
<tr>
<td>평균</td>
<td>O(n²)</td>
<td>일반적인 경우</td>
</tr>
<tr>
<td>최악 (역순 정렬)</td>
<td>O(n²)</td>
<td>매번 모든 값 이동 필요</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-특징-요약">5. 특징 요약</h2>
<ul>
<li><strong>정렬 방식</strong>: 내부 정렬, 제자리 정렬 (In-place)</li>
<li><strong>안정성</strong>: 안정 정렬 (Stable Sort)</li>
<li><strong>장점</strong>: 구현이 간단, 거의 정렬된 배열에서 효율적</li>
<li><strong>단점</strong>: 대규모 데이터에 비효율적 (O(n²))</li>
</ul>
<hr>
<h2 id="6-예시-코드">6. 예시 코드</h2>
<pre><code>#include &lt;stdio.h&gt;

void insertionSort(int arr[], int n){
    int i, j, key;
    for(i = 1; i &lt; n; i++){
        key = arr[i];
        j = i - 1;
        while (j &gt;= 0 &amp;&amp; arr[j] &gt; key){
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;
    }
}

int main()
{
    int arr[] = {5, 2, 4, 6, 1, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    insertionSort(arr, n);

    printf(&quot;정렬 결과: &quot;);
    for(int i = 0; i &lt; n; i++)
        printf(&quot;%d &quot;, arr[i]);

    return 0;
}</code></pre><h2 id="✅-정리">✅ 정리</h2>
<p>삽입 정렬은 간단한 알고리즘이지만, 배열이 거의 정렬되어 있는 경우 빠르게 동작하며, 데이터 개수가 적을 때 유용하다.</p>
<hr>
<h1 id="📌-selection-sort-선택-정렬">📌 Selection Sort (선택 정렬)</h1>
<h2 id="✅-개념">✅ 개념</h2>
<ul>
<li>Selection Sort는 정렬되지 않은 데이터 중에서 가장 작은(또는 큰) 값을 선택하여 앞쪽의 자리와 교환하는 방식으로 동작하는 정렬 알고리즘이다.</li>
<li>이름 그대로 &quot;선택&quot;에 기반하여, 각 반복(iteration)에서 가장 적합한 값을 골라 위치를 확정한다.</li>
</ul>
<hr>
<h2 id="🔁-알고리즘-동작-흐름">🔁 알고리즘 동작 흐름</h2>
<ol>
<li>배열의 첫 번째 원소부터 시작해서 오른쪽으로 비교해나가며 가장 작은 값(min)을 찾음.</li>
<li>찾은 가장 작은 값을 현재 인덱스(i)의 값과 교환(swap).</li>
<li>이후 i를 증가시키며 남은 배열 구간에서 반복 수행.</li>
<li>전체 배열의 길이가 n일 때, 총 n-1번 outer 루프가 돌면 정렬이 완료됨.</li>
</ol>
<hr>
<h2 id="🧠-단계별-설명-오름차순-예시">🧠 단계별 설명 (오름차순 예시)</h2>
<p>배열: <code>[5, 2, 4, 6, 1, 3]</code></p>
<ol>
<li>i = 0 → 최소값 1 → index 4 → swap(0,4) → <code>[1, 2, 4, 6, 5, 3]</code></li>
<li>i = 1 → 최소값 2 → 그대로 → <code>[1, 2, 4, 6, 5, 3]</code></li>
<li>i = 2 → 최소값 3 → index 5 → swap(2,5) → <code>[1, 2, 3, 6, 5, 4]</code></li>
<li>i = 3 → 최소값 4 → index 5 → swap(3,5) → <code>[1, 2, 3, 4, 5, 6]</code></li>
<li>i = 4 → 최소값 5 → 그대로 → 완료</li>
</ol>
<hr>
<h2 id="🔍-루프-범위">🔍 루프 범위</h2>
<ul>
<li>Outer Loop (i): <code>0 ~ n-2</code> → 기준 요소 선택</li>
<li>Inner Loop (j): <code>i+1 ~ n-1</code> → 최소값 탐색</li>
</ul>
<hr>
<h2 id="⏱-시간-복잡도">⏱ 시간 복잡도</h2>
<table>
<thead>
<tr>
<th>경우</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>최악</td>
<td>O(n²)</td>
<td>모든 쌍 비교 필요</td>
</tr>
<tr>
<td>평균</td>
<td>O(n²)</td>
<td>비교 횟수 고정</td>
</tr>
<tr>
<td>최선</td>
<td>O(n²)</td>
<td>이미 정렬되어 있어도 비교함</td>
</tr>
</tbody></table>
<ul>
<li>비교 횟수: 항상 n(n-1)/2</li>
<li>교환 횟수: 최대 n-1</li>
</ul>
<hr>
<h2 id="⚙️-공간-복잡도">⚙️ 공간 복잡도</h2>
<ul>
<li><strong>O(1)</strong>: 제자리 정렬 (In-place)</li>
</ul>
<hr>
<h2 id="🟩-장점">🟩 장점</h2>
<ul>
<li>구현이 매우 단순하고 직관적</li>
<li>교환 횟수가 적음 → Bubble Sort보다 효율적일 수 있음</li>
<li>입력 데이터 상태에 관계없이 일정한 성능</li>
</ul>
<hr>
<h2 id="🟥-단점">🟥 단점</h2>
<ul>
<li>비교 횟수가 많아 성능이 좋지 않음</li>
<li>대규모 데이터에는 부적합</li>
<li><strong>불안정 정렬</strong>: 동일한 값의 상대적 순서가 바뀔 수 있음</li>
</ul>
<hr>
<h2 id="🧷-요약-정리표">🧷 요약 정리표</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>정렬 방식</td>
<td>선택 정렬 (Selection-Based)</td>
</tr>
<tr>
<td>안정성</td>
<td>❌ 불안정 정렬</td>
</tr>
<tr>
<td>시간 복잡도</td>
<td>O(n²) (최선, 평균, 최악 모두 동일)</td>
</tr>
<tr>
<td>공간 복잡도</td>
<td>O(1) (추가 메모리 불필요)</td>
</tr>
<tr>
<td>실제 활용</td>
<td>주로 학습용, 데이터가 매우 작을 때</td>
</tr>
</tbody></table>
<hr>
<h2 id="💻-전체-코드-예제">💻 전체 코드 예제</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 두 값을 교환하는 함수
void swap(int* a, int* b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 선택 정렬 함수
void selectionSort(int arr[], int n){
    int i, j, min;
    for(i = 0; i &lt; n - 1; i++){
        min = i;
        for(j = i + 1; j &lt; n; j++){
            if(arr[j] &lt; arr[min])
                min = j;
        }
        swap(&amp;arr[min], &amp;arr[i]);
    }
}

int main()
{
    int arr[] = {5, 2, 4, 6, 1, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    selectionSort(arr, n);

    printf(&quot;정렬 결과: &quot;);
    for(int i = 0; i &lt; n; i++)
        printf(&quot;%d &quot;, arr[i]);

    return 0;
}</code></pre>
<hr>
<h2 id="💡-관련-개념-비교-요약">💡 관련 개념 비교 (요약)</h2>
<table>
<thead>
<tr>
<th>정렬 알고리즘</th>
<th>시간 복잡도</th>
<th>안정성</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>선택 정렬</td>
<td>O(n²)</td>
<td>❌</td>
<td>선택 후 교환, 교환 적음</td>
</tr>
<tr>
<td>버블 정렬</td>
<td>O(n²)</td>
<td>✅</td>
<td>인접 비교, 교환 많음</td>
</tr>
<tr>
<td>삽입 정렬</td>
<td>O(n²)/O(n)</td>
<td>✅</td>
<td>거의 정렬된 경우 매우 빠름</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조 정복하기 1]]></title>
            <link>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jung_ji_in02/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 19 Jun 2025 17:16:30 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-스택-stack">📚 스택 (Stack)</h2>
<p><strong>스택(Stack)</strong>은 데이터를 효율적으로 관리할 수 있도록 돕는 <strong>자료 구조</strong>. 데이터를 넣고 빼는 방식에 독특한 특징이 있다.</p>
<h3 id="특징-filo-first-in-last-out-또는-lifo-last-in-first-out">특징: FILO (First In Last Out) 또는 LIFO (Last In First Out)</h3>
<p>스택은 <strong>가장 먼저 들어온 데이터가 가장 마지막에 나가고</strong>, <strong>가장 마지막에 들어온 데이터가 가장 먼저 나가는</strong> 선형 구조. FILO와 LIFO는 스택의 동작 방식을 설명하는 같은 의미의 용어.</p>
<h4 id="💡-예시">💡 예시</h4>
<ul>
<li><strong>Ctrl + Z (되돌리기)</strong>: 가장 최근에 작업한 내용부터 순서대로 취소.</li>
<li><strong>햄버거 쌓기</strong></li>
</ul>
<h3 id="스택-구현-방법">스택 구현 방법</h3>
<p>스택은 주로 두 가지 방식으로 구현.</p>
<ol>
<li><strong>정적 1차원 배열 (Static Array)</strong><ul>
<li><strong>장점</strong>: 구현이 간단.</li>
<li><strong>단점</strong>: 스택의 최대 크기를 미리 정해야 한다.</li>
</ul>
</li>
<li><strong>동적 연결 리스트 (Dynamic Linked List)</strong><ul>
<li><strong>장점</strong>: 스택의 크기가 유동적으로 변할 수 있어 미리 크기를 알 필요가 없다.</li>
<li><strong>단점</strong>: 배열보다 구현이 복잡하고, 메모리 사용량이 상대적으로 많을 수 있다.</li>
</ul>
</li>
</ol>
<h3 id="스택의-주요-연산">스택의 주요 연산</h3>
<p>스택에서 데이터를 다루기 위한 핵심 연산들이 있다.</p>
<ol>
<li><strong>PUSH</strong>: 스택에 새로운 데이터를 <strong>추가</strong>하는 연산. 데이터는 항상 스택의 가장 위에 쌓인다.</li>
<li><strong>POP</strong>: 스택의 가장 위에 있는 데이터를 <strong>제거</strong>하는 연산. 데이터를 꺼냄과 동시에 스택에서 사라진다.</li>
<li><strong>Top / Peek</strong>: 스택의 가장 위에 있는 데이터를 <strong>확인</strong>하는 연산. 데이터는 스택에 그대로 남아있다.</li>
</ol>
<hr>
<h2 id="⚡-퀵-정렬-quick-sort">⚡ 퀵 정렬 (Quick Sort)</h2>
<p><strong>퀵 정렬(Quick Sort)</strong>은 <strong>분할 정복 알고리즘</strong>을 기반으로 하는 효율적인 <strong>정렬 알고리즘</strong>. 특정 기준 값인 <strong>&#39;피벗(Pivot)&#39;</strong>을 중심으로 배열을 나눈 후, 각 부분을 재귀적으로 정렬하는 방식으로 동작.</p>
<h3 id="퀵-정렬의-핵심-원리">퀵 정렬의 핵심 원리</h3>
<ul>
<li><strong>피벗 기준 분할</strong>: 배열을 피벗을 기준으로 두 개의 하위 배열로 분할. 하나는 피벗보다 작은 값들, 다른 하나는 피벗보다 큰 값들로 이루어진다.</li>
<li><strong>피벗 위치 확정</strong>: 분할 과정에서 피벗은 최종 정렬된 위치를 찾아가게 된다.</li>
<li><strong>성능과 피벗</strong>: 피벗을 어떻게 선택하느냐에 따라 퀵 정렬의 성능이 크게 달라질 수 있다.</li>
</ul>
<h3 id="퀵-정렬-c-코드-예시">퀵 정렬 C 코드 예시</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 두 원소의 값을 교환하는 함수
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 배열을 분할하고 피벗의 최종 위치를 반환하는 함수
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 배열의 마지막 원소를 피벗으로 선택
    int i = (low - 1); // 작은 원소들의 경계를 추적하는 인덱스

    // 배열을 순회하며 피벗보다 작거나 같은 원소들을 왼쪽으로 이동
    for (int j = low; j &lt;= high - 1; j++) {
        if (arr[j] &lt;= pivot) {
            i++;
            swap(&amp;arr[i], &amp;arr[j]);
        }
    }
    // 피벗을 올바른 위치로 이동
    swap(&amp;arr[i + 1], &amp;arr[high]);
    return (i + 1); // 피벗의 최종 위치 반환
}

// 퀵 정렬을 수행하는 재귀 함수
void quickSort(int arr[], int low, int high) {
    if (low &lt; high) {
        // 배열을 피벗을 기준으로 분할하고 피벗의 위치를 얻음
        int pi = partition(arr, low, high);

        // 피벗을 기준으로 좌측과 우측 하위 배열을 재귀적으로 정렬
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

// 메인 함수
int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf(&quot;정렬 전 배열: &quot;);
    for (int i = 0; i &lt; n; i++) {
        printf(&quot;%d &quot;, arr[i]);
    }
    printf(&quot;\n&quot;);

    quickSort(arr, 0, n - 1); // 퀵 정렬 수행

    printf(&quot;정렬 후 배열: &quot;);
    for (int i = 0; i &lt; n; i++) {
        printf(&quot;%d &quot;, arr[i]);
    }
    printf(&quot;\n&quot;);

    return 0;
}
</code></pre>
<h3 id="퀵-정렬-요약">퀵 정렬 요약</h3>
<ol>
<li>분할 정복: 퀵 정렬은 배열을 분할하고 각 부분을 정복(정렬)하는 방식으로 동작.</li>
<li>피벗 기준 재귀: 피벗을 정한 뒤, 피벗보다 작은 원소들은 왼쪽, 큰 원소들은 오른쪽으로 배치하고, 이 과정을 각 하위 배열에 대해 재귀적으로 반복.</li>
<li>다양한 피벗 선정: 피벗을 선택하는 방식(예: 첫 번째 원소, 중간 원소, 무작위 원소)에 따라 퀵 정렬의 성능에 영향을 미친다.</li>
<li>시간 복잡도:</li>
</ol>
<ul>
<li>최악의 경우 (Worst Case): $O(N^2)$ (배열이 이미 정렬되어 있거나 역순으로 정렬되어 있고, 피벗 선택이 항상 가장 작거나 큰 원소가 될 때)</li>
<li>평균의 경우 (Average Case): $O(N \log N)$</li>
<li>최선의 경우 (Best Case): $O(N \log N)$</li>
</ul>
<hr>
<h2 id="병합-정렬merge-sort">병합 정렬(Merge Sort)</h2>
<ul>
<li>단순하지 않는 정렬 시리즈 중 제일 단순한 정렬</li>
<li>분할 정복 알고리즘</li>
<li>모든 숫자를 다 나눈 다음에 병합하는 방식으로 정렬을 진행</li>
</ul>
<h3 id="알고리즘-설명">알고리즘 설명</h3>
<h4 id="1-배열을-분할">1. 배열을 분할</h4>
<p>배열을 중간 지점에서 두 개로 나눕니다.</p>
<h4 id="2-재귀적으로-정렬">2. 재귀적으로 정렬</h4>
<p>각각의 하위 배열에 대해 병합 정렬을 재귀적으로 적용합니다.</p>
<h4 id="3-병합">3. 병합</h4>
<p>두 정렬된 배열을 병합하여 하나의 정렬된 배열을 만듭니다.</p>
<hr>
<h3 id="코드-예시">코드 예시</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 병합 함수
void merge(int arr[], int l, int m, int r) {
    int n1 = m - l + 1;  // 왼쪽 배열의 크기
    int n2 = r - m;      // 오른쪽 배열의 크기

    int L[n1], R[n2];

    // 왼쪽 배열과 오른쪽 배열에 데이터 복사
    for (int i = 0; i &lt; n1; i++)
        L[i] = arr[l + i];
    for (int j = 0; j &lt; n2; j++)
        R[j] = arr[m + 1 + j];

    // 두 배열을 병합하여 arr[l..r]에 저장
    int i = 0, j = 0, k = l;
    while (i &lt; n1 &amp;&amp; j &lt; n2) {
        if (L[i] &lt;= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }

    // L[] 배열에 남은 요소 복사
    while (i &lt; n1) {
        arr[k] = L[i];
        i++;
        k++;
    }

    // R[] 배열에 남은 요소 복사
    while (j &lt; n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

// 병합 정렬 함수
void mergeSort(int arr[], int l, int r) {
    if (l &lt; r) {
        // 중간 인덱스를 계산
        int m = l + (r - l) / 2;

        // 왼쪽과 오른쪽 배열을 재귀적으로 정렬
        mergeSort(arr, l, m);
        mergeSort(arr, m + 1, r);

        // 두 배열을 병합
        merge(arr, l, m, r);
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int arr_size = sizeof(arr) / sizeof(arr[0]);

    // 주어진 배열 출력
    printf(&quot;주어진 배열: &quot;);
    for (int i = 0; i &lt; arr_size; i++)
        printf(&quot;%d &quot;, arr[i]);
    printf(&quot;\n&quot;);

    // 병합 정렬 함수 호출
    mergeSort(arr, 0, arr_size - 1);

    // 정렬된 배열 출력
    printf(&quot;정렬된 배열: &quot;);
    for (int i = 0; i &lt; arr_size; i++)
        printf(&quot;%d &quot;, arr[i]);
    printf(&quot;\n&quot;);

    return 0;
}</code></pre>
<h3 id="코드-설명">코드 설명</h3>
<h4 id="mergesort-함수">mergeSort 함수</h4>
<ul>
<li>배열을 재귀적으로 두 개로 나누고, 각 부분을 정렬한 후 merge 함수로 병합합니다.</li>
<li>l과 r은 배열의 시작과 끝 인덱스를 나타냅니다.</li>
</ul>
<h4 id="merge-함수">merge 함수</h4>
<ul>
<li>두 개의 정렬된 배열을 하나의 배열로 병합하는 함수입니다.</li>
<li>L 배열은 왼쪽 부분 배열, R 배열은 오른쪽 부분 배열입니다.</li>
<li>두 배열을 하나씩 비교하며 더 작은 값을 결과 배열에 삽입합니다.</li>
</ul>
<h4 id="시간-복잡도">시간 복잡도</h4>
<ul>
<li>시간 복잡도: O(n log n)</li>
<li>배열을 계속 나누기 때문에 log n 단계가 필요하고, 각 단계에서 배열의 모든 요소를 비교하므로 O(n)이 걸립니다.</li>
<li>병합 정렬의 특징
안정적인 정렬: 값이 같은 요소들의 순서를 변경하지 않습니다.</li>
<li>분할 정복 알고리즘: 배열을 반복적으로 절반씩 나누어 정렬하고 병합합니다.</li>
<li>최악의 경우에도 O(n log n): 최악의 경우에도 O(n log n) 시간 복잡도를 보장합니다.&#39;</li>
</ul>
<hr>
<h3 id="정리">정리</h3>
<p>이 알고리즘은 특히 큰 데이터 집합을 정렬할 때 유용하며, O(n log n)의 시간 복잡도를 가지므로 효율적입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[8강]]></title>
            <link>https://velog.io/@jung_ji_in02/8%EA%B0%95</link>
            <guid>https://velog.io/@jung_ji_in02/8%EA%B0%95</guid>
            <pubDate>Tue, 10 Jun 2025 03:15:15 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-section-01-넘파이-소개">✅ Section 01. 넘파이 소개</h2>
<blockquote>
<p>넘파이는 과학 계산을 위한 파이썬 핵심 라이브러리로, 고성능 다차원 배열 객체 및 다양한 수학 함수 제공</p>
</blockquote>
<h3 id="넘파이-설치-및-버전-확인">넘파이 설치 및 버전 확인</h3>
<ul>
<li><code>pip</code>을 이용해 넘파이를 설치하고, <code>import</code>로 불러와 버전을 확인합니다.</li>
</ul>
<pre><code class="language-python"># 주피터 노트북 사용자
!pip install numpy</code></pre>
<pre><code class="language-python">import numpy as np
print(np.__version__)</code></pre>
<pre><code>1.26.4</code></pre><hr>
<h2 id="✅-2-넘파이-배열">✅ 2. 넘파이 배열</h2>
<h3 id="배열-생성-및-모양-확인">배열 생성 및 모양 확인</h3>
<ul>
<li><code>np.arange()</code>로 배열을 만들고 <code>reshape()</code>로 형태를 바꿉니다.</li>
</ul>
<pre><code class="language-python">d = np.arange(12).reshape(3, 4)
print(d, d.shape)</code></pre>
<pre><code>[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]] (3, 4)</code></pre><h3 id="배열의-데이터-타입-확인">배열의 데이터 타입 확인</h3>
<ul>
<li><code>dtype</code> 속성으로 배열 내 데이터 형을 확인합니다.</li>
</ul>
<pre><code class="language-python">print(d.dtype)</code></pre>
<pre><code>int64</code></pre><h3 id="배열의-차원-수-확인">배열의 차원 수 확인</h3>
<ul>
<li><code>ndim</code>은 배열의 차원 수를 반환합니다.</li>
</ul>
<pre><code class="language-python">print(d.ndim)</code></pre>
<pre><code>2</code></pre><h3 id="전치-transpose">전치 (transpose)</h3>
<ul>
<li><code>T</code> 속성은 행과 열을 바꿉니다.</li>
</ul>
<pre><code class="language-python">print(d.T)</code></pre>
<pre><code>[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]</code></pre><h3 id="요소-개수-확인">요소 개수 확인</h3>
<ul>
<li>전체 요소 개수는 <code>size</code>로 확인합니다.</li>
</ul>
<pre><code class="language-python">print(d.size)</code></pre>
<pre><code>12</code></pre><h3 id="메모리-바이트-크기-확인">메모리 바이트 크기 확인</h3>
<ul>
<li>배열이 차지하는 메모리 바이트 수를 확인합니다.</li>
</ul>
<pre><code class="language-python">print(d.nbytes)</code></pre>
<pre><code>96</code></pre><h3 id="flat으로-모든-요소-1로-변경">flat으로 모든 요소 1로 변경</h3>
<ul>
<li><code>flat</code> 속성으로 요소에 반복적으로 접근 가능합니다.</li>
</ul>
<pre><code class="language-python">d.flat = 1
print(d)</code></pre>
<pre><code>[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]</code></pre><hr>
<h2 id="✅-3-넘파이-배열-생성">✅ 3. 넘파이 배열 생성</h2>
<h3 id="리스트-→-배열-변환">리스트 → 배열 변환</h3>
<ul>
<li><code>np.array()</code>를 이용해 리스트를 넘파이 배열로 변환합니다.</li>
</ul>
<pre><code class="language-python">mynpa1 = np.array([1, 2, 3])
print(type(mynpa1))</code></pre>
<pre><code>&lt;class &#39;numpy.ndarray&#39;&gt;</code></pre><h3 id="2차원-리스트-→-배열">2차원 리스트 → 배열</h3>
<pre><code class="language-python">mylist2 = [[1, 2, 3], [4, 5, 6]]
mynpa2 = np.array(mylist2)
print(type(mynpa2))</code></pre>
<pre><code>&lt;class &#39;numpy.ndarray&#39;&gt;</code></pre><h3 id="데이터프레임-→-배열">데이터프레임 → 배열</h3>
<ul>
<li><code>pandas.DataFrame</code> 객체를 배열로 변환할 수 있습니다.</li>
</ul>
<pre><code class="language-python">import pandas as pd
mypd1 = pd.DataFrame([1, 2, 3])
mynp1 = np.array(mypd1)
print(type(mynp1))</code></pre>
<pre><code>&lt;class &#39;numpy.ndarray&#39;&gt;</code></pre><hr>
<h2 id="✅-4-n차원-넘파이-배열">✅ 4. n차원 넘파이 배열</h2>
<h3 id="1차원-배열">1차원 배열</h3>
<ul>
<li><code>np.arange()</code>는 연속된 수로 배열을 생성합니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(3)
print(a, type(a), a.ndim, a.shape)</code></pre>
<pre><code>[0 1 2] &lt;class &#39;numpy.ndarray&#39;&gt; 1 (3,)</code></pre><h3 id="다양한-arange-사용-예">다양한 arange 사용 예</h3>
<ul>
<li>인자가 많아질수록 생성 규칙이 명확해집니다.</li>
</ul>
<pre><code class="language-python">a2 = np.arange(4)
a3 = np.arange(2, 4)
a4 = np.arange(0, 5, 2)
print(a2)
print(a3)
print(a4)</code></pre>
<pre><code>[0 1 2 3]
[2 3]
[0 2 4]</code></pre><hr>
<h2 id="✅-5-reshape로-배열-구조-변경">✅ 5. reshape로 배열 구조 변경</h2>
<ul>
<li><code>reshape</code>은 배열 모양을 바꿉니다. <code>-1</code>은 자동 계산입니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(12)
print(a.reshape(3, 4))
print(a.reshape(-1, 6))
print(a.reshape(2, 2, 3))</code></pre>
<pre><code>[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
[[[ 0  1  2]
  [ 3  4  5]]

 [[ 6  7  8]
  [ 9 10 11]]]</code></pre><hr>
<h2 id="✅-6-flatten으로-1차원-배열-평탄화">✅ 6. flatten으로 1차원 배열 평탄화</h2>
<ul>
<li><code>flatten()</code>은 다차원 배열을 1차원으로 펼칩니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(12).reshape(3, 4)
print(a.flatten())</code></pre>
<pre><code>[ 0  1  2  3  4  5  6  7  8  9 10 11]</code></pre><hr>
<h2 id="✅-7-배열-방향-반전">✅ 7. 배열 방향 반전</h2>
<ul>
<li>행 또는 열 순서를 바꾸려면 슬라이싱을 활용합니다.</li>
</ul>
<pre><code class="language-python">print(a[::-1])       # 행 역순
print(a[:, ::-1])    # 열 역순
print(a[::-1, ::-1]) # 전체 역순</code></pre>
<hr>
<h2 id="✅-8-transpose-전치">✅ 8. transpose 전치</h2>
<ul>
<li><code>transpose()</code> 또는 <code>.T</code>는 행렬을 전치합니다.</li>
</ul>
<pre><code class="language-python">print(a.transpose())
print(a.T)</code></pre>
<pre><code>[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]</code></pre><hr>
<h2 id="✅-9-배열-연결">✅ 9. 배열 연결</h2>
<ul>
<li><code>vstack</code>은 수직 연결, <code>hstack</code>은 수평 연결입니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(9).reshape(3, 3)
b = a * 2
print(np.vstack((a, b)))
print(np.hstack((a, b)))</code></pre>
<pre><code>[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 0  2  4]
 [ 6  8 10]
 [12 14 16]]
[[ 0  1  2  0  2  4]
 [ 3  4  5  6  8 10]
 [ 6  7  8 12 14 16]]</code></pre><hr>
<h2 id="✅-10-배열-분할">✅ 10. 배열 분할</h2>
<ul>
<li><code>vsplit</code>, <code>hsplit</code>, <code>split</code>을 이용해 배열을 나눌 수 있습니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(12).reshape(4, 3)
print(np.vsplit(a, 4))
print(np.hsplit(a.T, 3))</code></pre>
<hr>
<h2 id="✅-11-인덱싱과-슬라이싱">✅ 11. 인덱싱과 슬라이싱</h2>
<ul>
<li>배열 내부 요소를 특정 조건으로 추출합니다.</li>
</ul>
<pre><code class="language-python">a = np.arange(6)
print(a[0], a[-1], a[2:5], a[::-1])

b = np.arange(12).reshape(3, 4)
print(b[1, 2], b[:, 1], b[::2, ::2])</code></pre>
<hr>
<h2 id="✅-12-불-인덱싱-조건-기반-선택">✅ 12. 불 인덱싱 (조건 기반 선택)</h2>
<ul>
<li>조건식을 기반으로 원하는 값만 선택합니다.</li>
</ul>
<pre><code class="language-python">a = np.array([1, 2, 3, 4, 5])
print(a[a &gt; 2])
print(a[(a &gt; 2) &amp; (a &lt; 5)])</code></pre>
<pre><code>[3 4 5]
[3 4]</code></pre><hr>
<h2 id="✅-13-정렬">✅ 13. 정렬</h2>
<ul>
<li><code>np.sort()</code>는 정렬된 배열을 반환하며, <code>argsort()</code>는 인덱스를 반환합니다.</li>
</ul>
<pre><code class="language-python">a = np.array([5, 2, 9, 1])
print(np.sort(a))
print(a.argsort())</code></pre>
<pre><code>[1 2 5 9]
[3 1 0 2]</code></pre><hr>
<h2 id="✅-14-배열-간-연산">✅ 14. 배열 간 연산</h2>
<ul>
<li>배열 간 덧셈, 곱셈, 행렬곱을 수행할 수 있습니다.</li>
</ul>
<pre><code class="language-python">a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(a + b)      # 요소별 덧셈
print(a * b)      # 요소별 곱셈
print(a.dot(b))   # 행렬 곱셈</code></pre>
<pre><code>[[ 6  8]
 [10 12]]
[[ 5 12]
 [21 32]]
[[19 22]
 [43 50]]</code></pre><hr>
<h2 id="✅-15-브로드캐스팅-차원-자동-확장">✅ 15. 브로드캐스팅 (차원 자동 확장)</h2>
<ul>
<li>다른 형태의 배열끼리도 자동으로 크기를 맞춰 연산할 수 있습니다.</li>
</ul>
<pre><code class="language-python">a = np.array([[1, 2], [3, 4]])
b = np.array([10, 20])
print(a + b)</code></pre>
<pre><code>[[11 22]
 [13 24]]</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[<빅데이터분석> NumPy, 머신러닝, 신경망 요약 정리]]></title>
            <link>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EB%B6%84-%EC%B4%9D-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EB%B6%84-%EC%B4%9D-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 09 Jun 2025 18:03:05 GMT</pubDate>
            <description><![CDATA[<h1 id="numpy-머신러닝-신경망-요약-정리">NumPy, 머신러닝, 신경망 요약 정리</h1>
<h2 id="numpy-기초">NumPy 기초</h2>
<h3 id="배열-생성과-속성">배열 생성과 속성</h3>
<pre><code class="language-python">import numpy as np
print(np.__version__)

# 배열 생성 및 속성 확인
a = np.arange(12).reshape(3,4)
print(a, a.shape)
print(a.dtype)   # 데이터 타입
print(a.ndim)    # 차원 수
print(a.T)       # 전치
print(a.size)    # 전체 원소 개수
print(a.nbytes)  # 바이트 크기

a.flat = 1       # 모든 요소를 1로 설정
print(a)</code></pre>
<h3 id="배열-변환">배열 변환</h3>
<pre><code class="language-python">mylist2 = [1,2,3,4]
mynpa2 = np.array(mylist2)</code></pre>
<h3 id="차원-이해">차원 이해</h3>
<ul>
<li>1차원: <code>[1, 2, 3]</code></li>
<li>2차원: <code>[[1, 2], [3, 4]]</code></li>
<li>3차원: <code>[[[1]]]</code></li>
<li>차원 수는 <code>ndim</code>, 모양은 <code>shape</code>로 확인</li>
</ul>
<h3 id="브로드캐스팅-조건">브로드캐스팅 조건</h3>
<ol>
<li>배열 모양이 같거나</li>
<li>한 배열의 차원이 1</li>
</ol>
<h2 id="벡터-연산">벡터 연산</h2>
<h3 id="스칼라-곱">스칼라 곱</h3>
<pre><code class="language-python">A = [0,1,2,3,4,5]
scalar_multiply = list(map(lambda x: x*2, A))</code></pre>
<h3 id="평균-계산-예시">평균 계산 예시</h3>
<pre><code class="language-python">Hong1 = [80, 90]
Jung1 = [70, 80]
a = 0.5
avg = [a * sum(i) for i in zip(Hong1, Jung1)]</code></pre>
<h3 id="numpy-활용">NumPy 활용</h3>
<pre><code class="language-python">npHong1 = np.array(Hong1)
npJung1 = np.array(Jung1)
np_S = npHong1 + npJung1
a * np_S</code></pre>
<h3 id="내적-dot-product">내적 (dot product)</h3>
<pre><code class="language-python">np.dot(avg1, W1)</code></pre>
<h3 id="벡터의-길이">벡터의 길이</h3>
<pre><code class="language-python">from numpy.linalg import norm
norm(v)</code></pre>
<h2 id="오차-지표">오차 지표</h2>
<ul>
<li>MAE: <code>np.mean(np.abs(y_pred - y_true))</code></li>
<li>MSE: <code>np.mean((y_pred - y_true)**2)</code></li>
<li>RMSE: <code>np.sqrt(MSE)</code></li>
</ul>
<h2 id="신경망-이론">신경망 이론</h2>
<h3 id="시그모이드-함수">시그모이드 함수</h3>
<pre><code class="language-python">def sigmoid(x):
    return 1 / (1 + np.exp(-x))</code></pre>
<h3 id="순전파-예시">순전파 예시</h3>
<pre><code class="language-python">out = sigmoid(np.dot(input, weight) + bias)</code></pre>
<h3 id="역전파-계산">역전파 계산</h3>
<pre><code class="language-text">new_w = old_w - lr * delta * input
new_b = old_b - lr * delta

delta2 = -(t - y2)(1 - y2)y2
delta1 = y1(1 - y1) * w2 * delta2</code></pre>
<h3 id="사이킷런-구현">사이킷런 구현</h3>
<pre><code class="language-python">from sklearn.neural_network import MLPClassifier</code></pre>
<h2 id="머신러닝">머신러닝</h2>
<h3 id="지도비지도-학습">지도/비지도 학습</h3>
<ul>
<li>지도: 라벨 있음 (분류/회귀)</li>
<li>비지도: 라벨 없음 (군집화, 차원 축소)</li>
</ul>
<h3 id="모델-비교-예시">모델 비교 예시</h3>
<table>
<thead>
<tr>
<th>Model</th>
<th>Train</th>
<th>Test</th>
</tr>
</thead>
<tbody><tr>
<td>Random Forest</td>
<td>87.8</td>
<td>69.58</td>
</tr>
<tr>
<td>Decision Tree</td>
<td>87.8</td>
<td>69.58</td>
</tr>
<tr>
<td>KNN</td>
<td>84.0</td>
<td>68.61</td>
</tr>
<tr>
<td>SVM</td>
<td>80.3</td>
<td>78.64</td>
</tr>
<tr>
<td>Logistic Regression</td>
<td>80.3</td>
<td>79.61</td>
</tr>
<tr>
<td>Perceptron</td>
<td>79.5</td>
<td>81.23</td>
</tr>
<tr>
<td>Naive Bayes</td>
<td>77.8</td>
<td>75.40</td>
</tr>
<tr>
<td>SGD</td>
<td>75.1</td>
<td>80.58</td>
</tr>
</tbody></table>
<ul>
<li>과적합 모델: Random Forest, Decision Tree</li>
<li>일반화 잘된 모델: Perceptron, SGD</li>
</ul>
<h2 id="차원-축소">차원 축소</h2>
<h3 id="pca-예시">PCA 예시</h3>
<pre><code class="language-python">from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit_transform(data)</code></pre>
<h3 id="표준화">표준화</h3>
<pre><code class="language-python">from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)</code></pre>
<h2 id="군집화-kmeans">군집화 (KMeans)</h2>
<pre><code class="language-python">from sklearn.cluster import KMeans
model = KMeans(n_clusters=3, random_state=123)
model.fit(data_scaled)
model.cluster_centers_
model.labels_
model.inertia_</code></pre>
<h2 id="교차검증">교차검증</h2>
<pre><code class="language-python">from sklearn.model_selection import KFold, LeaveOneOut, cross_val_score</code></pre>
<ul>
<li>KFold, LeaveOneOut, StratifiedKFold 제공</li>
</ul>
<h2 id="회귀-분석-선형회귀">회귀 분석 (선형회귀)</h2>
<h3 id="sse-제곱-오차-합">SSE (제곱 오차 합)</h3>
<pre><code class="language-python">sse = np.sum((y_true - y_pred) ** 2)</code></pre>
<h3 id="sst-총-제곱합">SST (총 제곱합)</h3>
<pre><code class="language-python">sst = np.sum((y_true - np.mean(y_true)) ** 2)</code></pre>
<h3 id="결정계수-r2">결정계수 R^2</h3>
<pre><code class="language-python">r2 = 1 - sse/sst</code></pre>
<h3 id="예측력-rmse">예측력: RMSE</h3>
<pre><code class="language-python">rmse = np.sqrt(np.mean((y_pred - y_true)**2))</code></pre>
<h3 id="경사하강법-회귀계수-갱신">경사하강법 회귀계수 갱신</h3>
<pre><code class="language-python">coef[0] -= lr * error     # 절편
coef[1:] -= lr * error * x # 가중치</code></pre>
<h3 id="상관계수">상관계수</h3>
<pre><code class="language-python">np.corrcoef(x, y)</code></pre>
<hr>
<h1 id="numpy-머신러닝-신경망-요약-정리-시험-대비">NumPy, 머신러닝, 신경망 요약 정리 (시험 대비)</h1>
<h2 id="📌-시험-구성-안내-총-40문항--50점">📌 시험 구성 안내 (총 40문항 / 50점)</h2>
<ul>
<li><strong>프로그래밍 실습형</strong>: 11문항 (11점)</li>
<li><strong>프로그래밍 설명형</strong>: 19문항 (19점)</li>
<li><strong>프로그래밍 기타형</strong>: 4문항 (6점)</li>
<li><strong>이론 설명형</strong>: 10문항 (14점)</li>
</ul>
<hr>
<h2 id="💻-프로그래밍-실습형-대비-11문항">💻 프로그래밍 실습형 대비 (11문항)</h2>
<h3 id="배열-생성-및-속성-확인">배열 생성 및 속성 확인</h3>
<pre><code class="language-python">import numpy as np

a = np.arange(12).reshape(3, 4)
a.dtype, a.ndim, a.shape, a.size, a.nbytes
a.T        # 전치</code></pre>
<h3 id="리스트-→-배열-변환">리스트 → 배열 변환</h3>
<pre><code class="language-python">mylist = [1, 2, 3]
np.array(mylist)</code></pre>
<h3 id="브로드캐스팅-연산">브로드캐스팅 연산</h3>
<pre><code class="language-python">a = np.array([1, 2, 3])
b = 2
a + b  # [3, 4, 5]</code></pre>
<h3 id="평균-계산">평균 계산</h3>
<pre><code class="language-python">Hong1 = [80, 90]
Jung1 = [70, 80]
avg = [0.5 * sum(i) for i in zip(Hong1, Jung1)]</code></pre>
<h3 id="스칼라-곱--내적">스칼라 곱 / 내적</h3>
<pre><code class="language-python">A = np.array([1, 2, 3])
B = np.array([4, 5, 6])
np.dot(A, B)</code></pre>
<h3 id="벡터-길이">벡터 길이</h3>
<pre><code class="language-python">from numpy.linalg import norm
norm([3, 4])  # 5.0</code></pre>
<h3 id="시그모이드-함수-1">시그모이드 함수</h3>
<pre><code class="language-python">def sigmoid(x):
    return 1 / (1 + np.exp(-x))</code></pre>
<h3 id="pca-차원-축소">PCA 차원 축소</h3>
<pre><code class="language-python">from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)</code></pre>
<h3 id="군집화">군집화</h3>
<pre><code class="language-python">from sklearn.cluster import KMeans
model = KMeans(n_clusters=3)
model.fit(X)
model.labels_</code></pre>
<h3 id="표준화-1">표준화</h3>
<pre><code class="language-python">from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)</code></pre>
<h3 id="회귀-예측값">회귀 예측값</h3>
<pre><code class="language-python">def predict(X, coef):
    return coef[0] + coef[1] * X</code></pre>
<hr>
<h2 id="📚-프로그래밍-설명형-대비-19문항">📚 프로그래밍 설명형 대비 (19문항)</h2>
<h3 id="배열-속성-의미">배열 속성 의미</h3>
<ul>
<li><code>dtype</code>: 데이터형</li>
<li><code>ndim</code>: 차원 수</li>
<li><code>shape</code>: 구조</li>
<li><code>T</code>: 전치</li>
<li><code>size</code>: 전체 원소 수</li>
<li><code>nbytes</code>: 총 메모리 크기</li>
</ul>
<h3 id="브로드캐스팅-조건-1">브로드캐스팅 조건</h3>
<ol>
<li>배열 모양이 같거나</li>
<li>한 배열의 차원이 1</li>
</ol>
<h3 id="순전파feedforward">순전파(Feedforward)</h3>
<ul>
<li>입력층 → 은닉층 → 출력층</li>
<li>각 노드: <code>w*x + b</code>, 시그모이드 활성화 적용</li>
</ul>
<h3 id="역전파-핵심-흐름">역전파 핵심 흐름</h3>
<pre><code class="language-text">출력층 delta2 = -(t - y2)(1 - y2)y2
은닉층 delta1 = y1(1 - y1) * w2 * delta2</code></pre>
<h3 id="mae--mse--rmse">MAE / MSE / RMSE</h3>
<ul>
<li>MAE: <code>mean(abs(y - pred))</code></li>
<li>MSE: <code>mean((y - pred)^2)</code></li>
<li>RMSE: <code>sqrt(MSE)</code></li>
</ul>
<h3 id="r²-결정계수">R² (결정계수)</h3>
<pre><code class="language-python">sse = np.sum((y_true - y_pred)**2)
sst = np.sum((y_true - np.mean(y_true))**2)
r2 = 1 - sse / sst</code></pre>
<h3 id="상관계수-1">상관계수</h3>
<pre><code class="language-python">np.corrcoef(x, y)</code></pre>
<h3 id="gradient-descent">Gradient Descent</h3>
<ul>
<li>비용 함수(SSE)를 최소화</li>
<li>절편 coef[0], 기울기 coef[1] 조정</li>
</ul>
<h3 id="교차검증-종류">교차검증 종류</h3>
<ul>
<li>K-Fold</li>
<li>Leave-One-Out</li>
<li>Stratified K-Fold</li>
</ul>
<h3 id="경사하강법-예시">경사하강법 예시</h3>
<pre><code class="language-python">coef[0] -= lr * error
coef[1] -= lr * error * x</code></pre>
<hr>
<h2 id="🧾-기타형-프로그래밍--분석-4문항">🧾 기타형 프로그래밍 + 분석 (4문항)</h2>
<h3 id="차원-축소-과정">차원 축소 과정</h3>
<ol>
<li>표준화</li>
<li>PCA 적용</li>
<li>시각화용 2차원 변환</li>
</ol>
<h3 id="kmeans-군집-평가">KMeans 군집 평가</h3>
<ul>
<li><code>model.labels_</code>: 각 데이터 군집 번호</li>
<li><code>model.inertia_</code>: 거리 총합 (작을수록 좋음)</li>
</ul>
<h3 id="r²-및-예측력-평가">R² 및 예측력 평가</h3>
<pre><code class="language-python">from sklearn.metrics import r2_score, mean_squared_error
r2_score(y, pred)
np.sqrt(mean_squared_error(y, pred))  # RMSE</code></pre>
<h3 id="예측-함수">예측 함수</h3>
<pre><code class="language-python">def predict(x, coef):
    return coef[0] + coef[1] * x</code></pre>
<hr>
<h2 id="📘-이론-설명형-대비-10문항">📘 이론 설명형 대비 (10문항)</h2>
<h3 id="지도-vs-비지도-학습">지도 vs 비지도 학습</h3>
<ul>
<li>지도: 정답 있음 (분류, 회귀)</li>
<li>비지도: 정답 없음 (군집화, 차원 축소)</li>
</ul>
<h3 id="군집화란">군집화란?</h3>
<ul>
<li>데이터의 유사도 기반으로 그룹핑</li>
<li>대표적 방법: KMeans (유클리디안 거리 사용)</li>
</ul>
<h3 id="회귀-분석-요소">회귀 분석 요소</h3>
<ul>
<li>SSE: 오차 제곱합</li>
<li>SST: 총 변동량</li>
<li>SSR: 설명 가능한 변동량</li>
</ul>
<h3 id="회귀계수-의미">회귀계수 의미</h3>
<ul>
<li>독립변수가 1 증가할 때 종속변수가 얼마나 변화하는지</li>
<li>회귀선: <code>y = β₀ + β₁x</code></li>
</ul>
<h3 id="상관계수-의미">상관계수 의미</h3>
<ul>
<li>-1~1 사이 값</li>
<li>0에 가까우면 무상관, 1/–1에 가까우면 강한 상관</li>
</ul>
<h3 id="시그모이드-vs-항등-함수">시그모이드 vs 항등 함수</h3>
<ul>
<li>시그모이드: 0~1 값으로 압축</li>
<li>항등함수: 출력층에서 그대로 값 전달</li>
</ul>
<h3 id="벡터와-행렬의-의미">벡터와 행렬의 의미</h3>
<ul>
<li>벡터: 방향 + 크기 가진 선형 자료구조</li>
<li>행렬: 여러 벡터 모음, 2차원</li>
</ul>
<h3 id="머신러닝-주요-유형">머신러닝 주요 유형</h3>
<ul>
<li>분류: 이산값 예측</li>
<li>회귀: 연속값 예측</li>
<li>군집화: 유사도 기반 그룹화</li>
<li>차원 축소: 정보 손실 최소화하며 변수 축소</li>
</ul>
<h3 id="데이터-표준화-필요성">데이터 표준화 필요성</h3>
<ul>
<li>변수 간 스케일이 다르면 학습에 악영향</li>
<li><code>StandardScaler</code>, <code>MinMaxScaler</code> 사용</li>
</ul>
<h3 id="학습률과-에포크">학습률과 에포크</h3>
<ul>
<li>학습률: 가중치 갱신 크기</li>
<li>에포크: 전체 학습 반복 횟수</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[<빅데이터 분석> 11. 상관분석과 회귀분석]]></title>
            <link>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-11</link>
            <guid>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-11</guid>
            <pubDate>Fri, 06 Jun 2025 13:40:56 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-section-01-상관분석-초-통계를-이용한-상관계수-도출">✅ Section 01. 상관분석 초 통계를 이용한 상관계수 도출</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">train=[[25, 100], [52, 256], [38, 152], [32, 140], [25, 150]]</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">x=[i[0] for i in train]
y=[j[1] for j in train]
x,y</code></pre>
<hr>
<h2 id="✅-기초-통계-함수-구현하기">✅ 기초 통계 함수 구현하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def mean(x):
  return sum(x) / len(x)
mean(x), mean(y)</code></pre>
<hr>
<h2 id="✅-개별-값과-평균의-차">✅ 개별 값과 평균의 차</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def d_mean(x):
  x_mean=mean(x)
  return [i - x_mean for i in x]

d_mean(x), d_mean(y)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">x1=d_mean(x)
mean(x1)</code></pre>
<hr>
<h2 id="✅-내적">✅ 내적</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def dot(x, y):
  return sum([x*y for x, y in zip(x, y)])
dot(x, y)</code></pre>
<hr>
<h2 id="✅-제곱의-합">✅ 제곱의 합</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def sum_of_squares(v):
  return dot(v, v)
sum_of_squares(x), sum_of_squares(y)</code></pre>
<hr>
<h2 id="✅-분산">✅ 분산</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def variance(x):
  n=len(x)
  d=d_mean(x)
  return sum_of_squares(d) / (n-1)
variance(x)</code></pre>
<hr>
<h2 id="✅-표준편차">✅ 표준편차</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def standard_deviation(x):
  return variance(x)**0.5

standard_deviation(x)</code></pre>
<hr>
<h2 id="✅-여기서-잠깐-math-라이브러리의-sqrt-함수로-표준편차-함수-구현하기">✅ &lt;여기서 잠깐&gt; math 라이브러리의 sqrt 함수로 표준편차 함수 구현하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import math
def standard_deviation(x):
 return math.sqrt(variance(x))
standard_deviation(x)</code></pre>
<hr>
<h2 id="✅-공분산">✅ 공분산</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def covariance(x, y):
  n=len(x)
  return dot(d_mean(x), d_mean(y)) / (n-1)

covariance(x, y)</code></pre>
<hr>
<h2 id="✅-상관계수">✅ 상관계수</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def correlation(x, y):
  stdev_x=standard_deviation(x) # 표준편차(x) 할당
  stdev_y=standard_deviation(y) # 표준편차(y) 할당
  if stdev_x &gt; 0 and stdev_y &gt; 0: # stdev_x와 stdev_y가 0을 초과하면
    return covariance(x, y) / (stdev_x * stdev_y) # 상관계수 결과 반환
  else:
    return 0

correlation(x, y)</code></pre>
<hr>
<h2 id="✅-넘파이-함수로-기초-통계-구하기">✅ 넘파이 함수로 기초 통계 구하기</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
x1=np.array(x)
x1.mean(), x1.var(), x1.std()</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">np.cov(x1,y), np.corrcoef(x1,y)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">np.cov(x1,y)[0][1], np.corrcoef(x1,y)[0][1]</code></pre>
<hr>
<h2 id="✅-section-02-회귀분석1-최소자승법을-이용한-회귀계수-도출">✅ Section 02. 회귀분석1 최소자승법을 이용한 회귀계수 도출</h2>
<h2 id="1-회귀계수-구하기">1. 회귀계수 구하기</h2>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def OLS(x,y):
  beta=covariance(x, y)/variance(x) # 공분산(x,y)/분산(x)
  alpha=mean(y)-beta*mean(x) # 평균(y)–beta*평균(x)
  return [alpha, beta]

OLS(x,y)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def OLS_fit(x,y):
  beta=(correlation(x, y)*standard_deviation(y))/standard_deviation(x)
  # beta=(상관계수(x,y)*표준편차(y))/표준편차(x)
  alpha=mean(y)-beta*mean(x)
  return [alpha, beta]

OLS_fit(x,y)</code></pre>
<hr>
<h2 id="✅-예측값-구하기">✅ 예측값 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def predict(alpha, beta,train, test):
  predictions=list() # 예측값 선언
  x=[i[0] for i in train] # 변수 x에 train 데이터의 아파트 평수 저장
  y=[j[1] for j in train] # 변수 y에 train 데이터의 전력량 저장
  alpha, beta=OLS_fit(x,y) # alpha와 beta에 OSL_fit 함수의 반환값 저장
  for i in test:
    yhat=alpha+beta*i[0]
    predictions.append(yhat) # predictions 리스트에 변수 yhat에 입력된 값 추가
  return predictions # predictions 리스트 값 반환

train=[[25,100],[52,256],[38,152],[32,140],[25,150]]
alpha,beta=OLS_fit(x,y)
pr=predict(alpha, beta, train, train) # predict 함수의 반환값을 변수 pr에 할당
print(pr) # 변수 pr에 할당된 prediction 값 출력</code></pre>
<hr>
<h2 id="✅-여기서-잠깐-matplotlib-라이브러리를-이용한-산점도-그래프-표시">✅ &lt;여기서 잠깐&gt; matplotlib 라이브러리를 이용한 산점도 그래프 표시</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># matplotlib 라이브러리 임포트, 글꼴 설정
import matplotlib.pyplot as plt
plt.rc(&#39;font&#39;, family=&#39;NanumGothic&#39;)
plt.title(&#39;아파트 평수에 따른 전기 사용량&#39;)# 그래프 제목 설정
plt.scatter(x, y, c=&#39;red&#39;) # 산점도 차트 표시. x축, y축 데이터 설정. 색상은 red로 설정
plt.plot(x,pr) # 라인 그래프 표시. x축, y축은 변수 pr에 할당된 값 사용
plt.xlabel(&#39;아파트 평형&#39;) # x축 제목
plt.ylabel(&#39;전기사용량&#39;) # y축 제목
plt.show()</code></pre>
<hr>
<h2 id="✅-sse-구하기">✅ SSE 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def SSE(alpha, beta, train, test):
  sse=0
  for i in test:
    error=(i[1]-(alpha+beta*i[0]))**2 # (실제값-예측값)의 제곱
    sse=error+sse
  return sse

SSE(alpha, beta, train, train)</code></pre>
<hr>
<h2 id="✅-sst-구하기">✅ SST 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def SST(alpha, beta, train, test):
  sst=0
  x=[i[0] for i in train]
  y=[j[1] for j in train]
  for i in test:
    sum_ds=(i[1]-mean(y))**2
    sst=sum_ds+sst
  return sst

SST(alpha, beta, train, train)</code></pre>
<hr>
<h2 id="✅-결정계수-구하기">✅ 결정계수 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def R_squared(alpha, beta, train, test):
  return 1.0-(SSE(alpha, beta, train, test)/SST(alpha, beta, train, test))

R_squared(alpha, beta, train, train)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">train=[[25, 100], [52, 256], [38, 152], [32, 140], [25, 150]]
x=[i[0] for i in train]
y=[j[1] for j in train]
import statsmodels.api as sms
_X=sms.add_constant(x)
model=sms.OLS(y, _X).fit()
print(model.summary())</code></pre>
<hr>
<h2 id="✅-예측력-구하기">✅ 예측력 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">test=[[45,183],[40,175],[55,203],[28,152],[42,198]]
test</code></pre>
<hr>
<h2 id="✅-종속변수-값-예측하기">✅ 종속변수 값 예측하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def predict(alpha, beta, train, test):
  predictions=list()
  x=[i[0] for i in train]
  y=[j[1] for j in train]
  alpha, beta=OLS_fit(x,y)
  for i in test:
    yhat=alpha+beta*i[0]
    predictions.append(yhat)
  return predictions

predict(alpha, beta, train, test)</code></pre>
<hr>
<h2 id="✅-예측-결과-평가하기">✅ 예측 결과 평가하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">actual=[j[1] for j in test]
predicted=predict(alpha, beta, train, test)
actual, predicted</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from math import sqrt
def RMSE(actual, predicted): # 변수 RMSE 선언, 인자는 actual, predicted
  sum_error=0.0 # 변수 sum_error 값을 0.0으로 초기화
  for i in range(len(actual)): # for 문으로 변수 actual에 저장된 값만큼 반복
    prediction_error=predicted[i]-actual[i] # 예측값[i]-실제값[i] 반환
    sum_error+=(prediction_error**2) # prediction_error 제곱 누적
    mean_error=sum_error/float(len(actual)) # sum_error/len(actual) 값 저장
  return sqrt(mean_error) # mean_error 제곱근 반환

RMSE(actual, predicted)</code></pre>
<hr>
<h2 id="✅-section-03-회귀분석2-경사하강법을-이용한-회귀계수-도출">✅ Section 03. 회귀분석2 경사하강법을 이용한 회귀계수 도출</h2>
<h3 id="✅-회귀계수-구하기">✅ 회귀계수 구하기</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">dataset=[[25,100],[52,256],[38,152],[32,140],[25,150],[45,183],[40,175],[55,203],[28,152],[42,198]]
train=dataset[:5]
test=dataset[5:]
print(&#39;학습 데이터(train): {}, 테스트 데이터(test): {}&#39;.format(train, test))</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">coef=[0.0 for i in range(len(train[0]))] # 회귀계수 값 초기화
coef</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def predict(row, coef):
  # yhat=coef[0]+coef[i+1]*row[i]
  yhat=coef[0] # 절편
  for i in range(len(row)-1):
    yhat+=coef[i+1]*row[i] # 회귀계수*학습 데이터
  return yhat</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def coefficients_sgd(train, l_rate, n_epoch): # 학습 데이터, 학습률, 학습 횟수
  coef=[0.0 for i in range(len(train[0]))] # 절편과 회귀계수 모두 0으로 초기화
  for epoch in range(n_epoch): # 학습 횟수만큼 반복
    sse=0 # 오차 제곱합
    for row in train: # 학습 데이터를 차례로 row에 넘겨 predict 함수 실행
      yhat=predict(row, coef) # 예측값
      error=yhat-row[-1] # 오차
      sse+=error**2 # 오차 제곱
      coef[0]=coef[0] - l_rate * error # 절편 수정
      for i in range(len(row)-1):
        coef[i+1]=coef[i+1]-l_rate*error*row[i]
      return coef, sse</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import math
def coefficients_sgd(train, l_rate, n_epoch):
  coef=[0.0 for i in range(len(train[0]))] #초기 회귀계수 값
  for epoch in range(n_epoch):
    sum_error=0
    for row in train:
      yhat=predict(row, coef)
      error=row[-1]-yhat
      sum_error+=(error**2)
      coef[0]=coef[0]+l_rate*error
      for i in range(len(row)-1):
        coef[i+1]=coef[i+1]+l_rate*error*row[i]
 # print(&#39;&gt;epoch=%d, lrate=%.5f, error=%.2f&#39; % (epoch,l_rate,sum_error))
  return coef, math.sqrt(sum_error/len(train))</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">l_rate=0.0001
n_epoch=10
coef=coefficients_sgd(train, l_rate, n_epoch)
coef</code></pre>
<hr>
<h3 id="✅-예측값-구하고-결과-평가하기">✅ 예측값 구하고 결과 평가하기</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">dataset=[[25,100],[52,256],[38,152],[32,140],[25,150],[45,183],[40,175],[55,203],[28,152],[42,198]]
train=dataset[:5]
test=dataset[5:]
actual=[j[1] for j in test]
l_rate=0.0001
n_epoch=10
alpha,beta=coefficients_sgd(train,l_rate,n_epoch)[0][0],coefficients_sgd(train, l_rate, n_epoch)[0][1]

alpha,beta</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def predicted(train, test, alpha, beta):
  predictions=list()
  x=[i[0] for i in train]
  y=[j[1] for j in train]
  for i in test:
    yhat=alpha+beta*i[0]
    predictions.append(yhat)
  return predictions

pred=predicted(train, test, alpha, beta)
pred</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from math import sqrt

def RMSE(actual, predicted):
  sum_error=0.0
  for i in range(len(actual)):
    prediction_error=predicted[i]-actual[i]
    sum_error+=(prediction_error**2)

  mean_error=sum_error/float(len(actual))
  return sqrt(mean_error)

RMSE(actual, pred)</code></pre>
<hr>
<h3 id="✅-종합-및-심화">✅ 종합 및 심화</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def sgd1(sq_error, sq_error_grad, x, y, theta_0, l_rate_0):
  data=list(zip(x, y))
  theta=theta_0
  l_rate=l_rate_0
  min_value=float(&quot;inf&quot;)
  iterations=0
  while iterations &lt; 5:
    value=sum(sq_error(xi, yi, theta) for xi, yi in data)
    if value &lt; min_value:
      min_theta, min_value=theta, value
      iterations=0
      l_rate=l_rate_0
    else:
      iterations+=1
      l_rate*=0.9
    for xi, yi in data:
      gradient_i=sq_error_grad(xi, yi, theta)
      theta=vector_subtract(theta, scalar_multiply(l_rate, gradient_i))
      # print(&quot;min_theta&quot;,min_theta)
  return min_theta</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def sq_error(xi, yi,theta):
  alpha, beta=theta
  error=yi-(beta * xi + alpha)
  sq_error=error**2
  return sq_error

def sq_error_grad(xi, yi, theta):
  alpha, beta=theta
  return [-2 *(yi-(beta * xi + alpha)),-2 *(yi-(beta * xi + alpha)) * xi]

def vector_subtract(v, w):
  return [vi - wi for vi, wi in zip(v,w)]

def scalar_multiply(c, v):
  return [c * vi for vi in v]</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">dataset=[[25,100],[52,256],[38,152],[32,140],[25,150],[45,183],[40,175],[55,203],[28,152],[42,198]]
train=dataset[:5]
test=dataset[5:]
x=[i[0] for i in train]
y=[j[1] for j in train]
l_rate_0=0.001
theta_0=[0,0]
sgd1(sq_error, sq_error_grad, x, y, theta_0, l_rate_0)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<빅데이터 분석> 10. 기본-소비 데이터 분석과 승객 생존율 예측]]></title>
            <link>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-10</link>
            <guid>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-10</guid>
            <pubDate>Fri, 06 Jun 2025 13:37:44 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-1-분석-준비">✅ 1. 분석 준비</h2>
<blockquote>
<p>넘파이 라이브러리를 설치합니다.</p>
</blockquote>
<pre><code class="language-python">!pip install pandas</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import pandas as pd
url=&#39;https://raw.githubusercontent.com/sehakflower/data/main/titanic.csv&#39;
titanic_df= pd.read_csv(url,sep=&#39;\t&#39;)

# 컬럼명을 소문자로 바꾸기
new_columns=[&#39;passengerId&#39;, &#39;survived&#39;, &#39;pclass&#39;, &#39;name&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;sibsp&#39;, &#39;parch&#39;,
&#39;ticket&#39;, &#39;fare&#39;, &#39;cabin&#39;, &#39;embarked&#39;]
titanic_df.columns=new_columns

# 필요한 컬럼만 가져오기
titanic_df1=titanic_df[[&#39;survived&#39;, &#39;pclass&#39;, &#39;name&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;sibsp&#39;, &#39;parch&#39;, &#39;fare&#39;]]

# 1. sex열을 gender로 바꾸고 여성은 0, 남성은 1로 바꾸기
tmp=[]
for each in titanic_df1[&#39;sex&#39;]:
  if each==&#39;female&#39;:
    tmp.append(0)
  else:
    tmp.append(1)

titanic_df1[&#39;gender&#39;]=tmp
titanic_df1.drop(columns=&#39;sex&#39;, inplace=True) # sex 열 삭제하기

# 2. 호칭을 숫자형으로 바꾸기
condition=lambda x: x.split(&#39;,&#39;)[1].split(&#39;.&#39;)[0].strip()
titanic_df1[&#39;title&#39;]=titanic_df1[&#39;name&#39;].map(condition) # name에서 호칭만 가져오기
Special=[&#39;Master&#39;, &#39;Don&#39;, &#39;Rev&#39;] # 3개 호칭을 title 열의 Special로 대체
for each in Special:
  titanic_df1[&#39;title&#39;]=titanic_df1[&#39;title&#39;].replace(each, &#39;Special&#39;)

titanic_df1=titanic_df1.drop(&#39;name&#39;, axis=1) # 사용하지 않는 name 열 삭제하기

# 숫자형으로 만드는 함수 만들기
def convert_title(x):
  if x==&quot;Special&quot;:
    return 1
  else:
    return 0

titanic_df1[&quot;special_title&quot;]=titanic_df1[&quot;title&quot;].apply(convert_title)
# special_title 열 만들어서 special은 1, 나머지는 0으로 바꾸기
titanic_df1.drop(&#39;title&#39;, axis=1, inplace=True) # title 열 삭제하기
# 3. sibpar 열 만들어 동반자 수 더한 값 넣기
titanic_df1[&#39;sibpar&#39;]=titanic_df1[&#39;sibsp&#39;]+titanic_df1[&#39;parch&#39;]
titanic_df1.drop([&#39;sibsp&#39;,&#39;parch&#39;], axis =1, inplace=True)
# 사용하지 않는 sibsp, parch 열 삭제하기
# 4. avgfare 열 만들어 1인당 평균 탑승 요금 넣기
titanic_df1[&#39;avgfare&#39;]=titanic_df1[&#39;fare&#39;]/titanic_df1[&#39;sibpar&#39;]
titanic_df1[&#39;n_family&#39;]=titanic_df1[&#39;sibpar&#39;]+1
# n_family 열 만들어 동반자에 1을 더하여 0으로 나누어지지 않도록 함
titanic_df1[&#39;avgfare&#39;]=titanic_df1[&#39;fare&#39;]/titanic_df1[&#39;n_family&#39;] # 1인당 평균 탑승 요금
titanic_df1=titanic_df1.drop([&#39;fare&#39;,&#39;sibpar&#39;], axis=1) # 필요 없는 열 삭제하기
# 5. 최종 분석 데이터 컬럼명만 추출하기
titanic_df1.rename(columns={&#39;gender&#39;:&#39;sex&#39;, &#39;special_title&#39;:&#39;title&#39;, &#39;avgfare&#39;:&#39;fare&#39;, &#39;n_family&#39;:&#39;num_family&#39;}, inplace=True) # 컬럼명 변경하기
titanic_df1=titanic_df1[[&#39;survived&#39;, &#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;title&#39;, &#39;fare&#39;, &#39;num_family&#39;]]
# 컬럼 순서 다시 정하기
titanic_df1=titanic_df1.dropna()
# age에는 NaN 값이 있으므로 이를 삭제한 데이터를 분석에 사용
titanic_df1</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">raw=titanic_df1
np_raw=raw.values
type(np_raw)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">train=np_raw[:100]
test=np_raw[100:]
y_train = [i[0] for i in train]
X_train = [j[1:] for j in train]
y_test = [i[0] for i in test]
X_test = [j[1:] for j in test]
len(X_train),len(y_train), len(y_test),len(X_test)</code></pre>
<hr>
<h2 id="✅-2-의사결정나무">✅ 2. 의사결정나무</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">!pip3 install -U scikit-learn</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.tree import DecisionTreeClassifier
model=DecisionTreeClassifier(criterion=&#39;entropy&#39;,max_depth=3,min_samples_leaf=5).fit(X_train, y_train)
model.fit(X_train, y_train)
DecisionTreeClassifier(criterion=&#39;entropy&#39;, max_depth=3, min_samples_leaf=5)
print(&#39;Score:{}&#39;.format(model.score(X_train, y_train)))
print(&#39;Score:{}&#39;.format(model.score(X_test, y_test)))</code></pre>
<hr>
<blockquote>
<p>넘파이 라이브러리를 설치합니다.</p>
</blockquote>
<pre><code class="language-python">!pip install graphviz</code></pre>
<hr>
<h2 id="✅-아래-코드가-실행되지-않는다면">✅ 아래 코드가 실행되지 않는다면?</h2>
<ol>
<li>다음 경로에서 파일을 다운받고 설치하면 됩니다.<br><a href="https://graphviz.org/download/">https://graphviz.org/download/</a><br>여기서는 graphviz-8.0.5 (64-bit) EXE installer [sha256] 항목을 설치하였다. </li>
</ol>
<p>2.아나콘다설치후 주피터노트북을 실행한다면 아나콘다 프롬프트쉘에서 conda install python-graphviz 입력하여 실행한다.</p>
<ol start="3">
<li>환경변수에서 path도 함께 설정해주어야 합니다.<br>(1) 윈도우 검색란에 &#39;환경&#39;이라고 검색하고 Enter를 누르면 시스템 속성 대화상자가 나타난다.<br>(2) 하단의 [환경변수] 단추를 클릭하고 상단 목록에서 [path]항목을 선택한후 [편집] 단추를 클릭한다.<br>(3) 환경변수 편집 대화상자가 나타나면 [새로 만들기] 단추를 눌러 다음의 두 개의 경로를 추가한후 확인을 클릭한다.     </li>
</ol>
<p>pc에 대한 사용자 변수창 path변수에 아래를 추가한다.</p>
<blockquote>
<p>C:\Program Files\Graphviz\bin    </p>
</blockquote>
<p>시스템 변수창 path에 아래를 추가한다.</p>
<blockquote>
<p>C:\Program Files\Graphviz\bin\dot.exe    </p>
</blockquote>
<p>(4) 저장후 주피터 노트북을 재실행한 후 코드를 실행하면 오류 없이 잘 실행되는 것을 확인할수 있다. </p>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.tree import export_graphviz
export_graphviz(
 model,
 out_file=&quot;titanic.dot&quot;,
 feature_names=[&#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;title&#39;, &#39;fare&#39;, &#39;num_family&#39;],
 class_names=[&#39;0&#39;,&#39;1&#39;],
 rounded=True,
 filled=True
)
import graphviz
with open(&quot;titanic.dot&quot;) as f:
 dot_graph=f.read()
dot=graphviz.Source(dot_graph)
dot.format=&#39;png&#39;
dot.render(filename=&#39;titanic_tree&#39;, directory=&#39;image/decision_trees&#39;, cleanup=True)
dot</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.metrics import accuracy_score
y_pred=model.predict(X_test)
print(&quot;Test Accuracy is &quot;, accuracy_score(y_test, y_pred)*100)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">feature_names=[&#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;title&#39;, &#39;fare&#39;, &#39;num_family&#39;]
Tom=[1,1,33,1,50,4]
Jane=[2,0,50,0,8,1]</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">model.predict_proba([Tom])</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">model.predict_proba([Jane])</code></pre>
<hr>
<h2 id="✅-3-로지스틱-회귀분석">✅ 3. 로지스틱 회귀분석</h2>
<h3 id="표준화하지-않은-데이터-이용하기">표준화하지 않은 데이터 이용하기</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression(random_state=13, solver=&#39;liblinear&#39;, C=10.)
log_reg.fit(X_train, y_train)
LogisticRegression(C=10.0, random_state=13, solver=&#39;liblinear&#39;)
from sklearn.metrics import accuracy_score
pred=log_reg.predict(X_train)
accuracy_score(y_train, pred)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">pred = log_reg.predict(X_test)
accuracy_score(y_test, pred)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, pred)</code></pre>
<hr>
<h2 id="✅-표준화한-데이터-이용하기">✅ 표준화한 데이터 이용하기</h2>
<blockquote>
<p>넘파이 라이브러리를 설치합니다.</p>
</blockquote>
<pre><code class="language-python">!pip install seaborn</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import seaborn as sns
sns.boxplot(data=titanic_df1[[&#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;title&#39;, &#39;fare&#39;, &#39;num_family&#39;]])</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">X=titanic_df1
from sklearn.preprocessing import MinMaxScaler, StandardScaler
MMS=MinMaxScaler()
SS=StandardScaler()
SS.fit(X)
MMS.fit(X)
X_ss=SS.transform(X)
X_mms=MMS.transform(X)
X_ss_pd=pd.DataFrame(X_ss, columns=X.columns)
X_mms_pd=pd.DataFrame(X_mms, columns=X.columns)
X_ss_pd.head()</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">X_mms_pd.head()</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">sns.boxplot(data=X_mms_pd[[&#39;pclass&#39;,&#39;sex&#39;,&#39;age&#39;,&#39;title&#39;,&#39;fare&#39;,&#39;num_family&#39;]])</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">sns.boxplot(data=X_ss_pd[[&#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;title&#39;, &#39;fare&#39;, &#39;num_family&#39;]])</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">y=raw[&#39;survived&#39;]
X=raw.drop([&#39;survived&#39;], axis=1)
X.head()</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">y.head()</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test=\
train_test_split(X, y, test_size=0.2, random_state=13)</code></pre>
<hr>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
np.unique(y_train, return_counts=True)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">X_out=X_mms_pd
X_train, X_test, y_train, y_test=\
train_test_split(X_out, y, test_size=0.2, random_state=13)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">log_reg=LogisticRegression(random_state=13, solver=&#39;liblinear&#39;, C=10.)
log_reg.fit(X_train, y_train)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">pred=log_reg.predict(X_test)
accuracy_score(y_test, pred)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">X_out=X_ss_pd
X_train, X_test, y_train, y_test=\
train_test_split(X_out, y, test_size=0.2, random_state=13)
log_reg=LogisticRegression(random_state=13, solver=&#39;liblinear&#39;, C=10.)
log_reg.fit(X_train, y_train)
pred=log_reg.predict(X_test)
accuracy_score(y_test, pred)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">log_reg.coef_</code></pre>
<hr>
<h2 id="✅-1-머신러닝-기법에-적용하기">✅ 1. 머신러닝 기법에 적용하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import pandas as pd
titanic_url=&#39;https://github.com/sehakflower/data/blob/main/titanic_1309.xlsx?raw=true&#39;
titanic=pd.read_excel(titanic_url,sheet_name=&#39;total&#39;)
train_1000=titanic.iloc[:1000]
test_309=titanic.iloc[1000:]
train_1000.drop([&#39;boat&#39;, &#39;body&#39;, &#39;home.dest&#39;], axis=1, inplace=True)
test_309.drop([&#39;boat&#39;, &#39;body&#39;, &#39;home.dest&#39;], axis=1, inplace=True)
train_df=train_1000
test_df=test_309

# 1. name 열을 5개로 구분하고 숫자형 데이터로 바꾸기
total=[train_df, test_df]
titles={&#39;Mr&#39;:1, &#39;Miss&#39;:2, &#39;Mrs&#39;:3, &#39;Master&#39;:4, &#39;Special&#39;:5} # 호칭에 따라 숫자로 표시하기
for dataset in total:
 dataset[&#39;title&#39;]=dataset.name.str.extract(&#39;([A-Za-z]+)\.&#39;, expand=False)
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].replace([&#39;Lady&#39;, &#39;Countess&#39;, &#39;Capt&#39;, &#39;Col&#39;,
&#39;Don&#39;, &#39;Dr&#39;, &#39;Major&#39;, &#39;Rev&#39;, &#39;Sir&#39;,
&#39;Jonkheer&#39;, &#39;Dona&#39;], &#39;Special&#39;)
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].replace(&#39;Mlle&#39;, &#39;Miss&#39;)
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].replace(&#39;Ms&#39;, &#39;Miss&#39;)
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].replace(&#39;Mme&#39;, &#39;Mrs&#39;)
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].map(titles) # 타이틀을 숫자로 변경
 dataset[&#39;title&#39;]=dataset[&#39;title&#39;].fillna(0) # NaN을 0으로 변경
train_df=train_df.drop([&#39;name&#39;], axis=1) # 필요 없는 name 열 삭제
test_df=test_df.drop([&#39;name&#39;], axis=1) # 필요 없는 name 열 삭제

# 2.age 열의 빈 값을 호칭별 중앙값으로 바꾸기
train_df[&#39;age&#39;].fillna(train_df.groupby(&#39;title&#39;)[&#39;age&#39;].transform(&#39;median&#39;), inplace=True)
test_df[&#39;age&#39;].fillna(test_df.groupby(&#39;title&#39;)[&#39;age&#39;].transform(&#39;median&#39;), inplace=True)
data=[train_df, test_df]
for dataset1 in data:
 dataset1[&#39;age&#39;]=dataset1[&#39;age&#39;].astype(int)
 dataset1.loc[ dataset1[&#39;age&#39;]&lt;=11, &#39;age&#39;]=0
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;11)&amp;(dataset1[&#39;age&#39;]&lt;=18), &#39;age&#39;]=1
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;18)&amp;(dataset1[&#39;age&#39;]&lt;=22), &#39;age&#39;]=2
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;22)&amp;(dataset1[&#39;age&#39;]&lt;=27), &#39;age&#39;]=3
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;27)&amp;(dataset1[&#39;age&#39;]&lt;=33), &#39;age&#39;]=4
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;33)&amp;(dataset1[&#39;age&#39;]&lt;=40), &#39;age&#39;]=5
 dataset1.loc[(dataset1[&#39;age&#39;]&gt;40)&amp;(dataset1[&#39;age&#39;]&lt;=66), &#39;age&#39;]=6
 dataset1.loc[ dataset1[&#39;age&#39;]&gt;66, &#39;age&#39;]=6
# 3. sex 열에서 &#39;male&#39;:0, &#39;female&#39;:1로 바꾸기
data=[train_df, test_df]
# 일부 전처리된 train_df, test_df를 다시 업데이트해서 더한 후 숫자로 바꾸기 위함
sex_mapping={&#39;male&#39;:0, &#39;female&#39;:1}
for dataset in data:
 dataset[&#39;sex&#39;]=dataset[&#39;sex&#39;].map(sex_mapping)
# 4. embarked 열
for dataset in data:
 dataset[&#39;embarked&#39;]=dataset[&#39;embarked&#39;].fillna(&#39;S&#39;)
embarked_mapping={&#39;S&#39;:0, &#39;C&#39;:1, &#39;Q&#39;:2}
for dataset in data:
 dataset[&#39;embarked&#39;]=dataset[&#39;embarked&#39;].map(embarked_mapping)
# 5. sibsp, parch 열
for dataset in data:
 dataset[&#39;sibpar&#39;]=dataset[&#39;sibsp&#39;]+dataset[&#39;parch&#39;]
 dataset.loc[dataset[&#39;sibpar&#39;]&gt;0, &#39;n_alone&#39;]=0
 dataset.loc[dataset[&#39;sibpar&#39;]==0, &#39;n_alone&#39;]=1
 dataset[&#39;n_alone&#39;]=dataset[&#39;n_alone&#39;].astype(int)
# 6. fare 열, 개인별 요금 나타내는 fare_person 열 추가하기
train_df[&quot;fare&quot;].fillna(train_df.groupby(&#39;pclass&#39;)[&#39;fare&#39;].transform(&#39;median&#39;),
inplace=True)
test_df[&quot;fare&quot;].fillna(test_df.groupby(&#39;pclass&#39;)[&#39;fare&#39;].transform(&#39;median&#39;),
inplace=True)

for dataset in data:
 dataset.loc[ dataset[&#39;fare&#39;]&lt;=20, &#39;fare&#39;]=1
 dataset.loc[(dataset[&#39;fare&#39;]&gt;20)&amp;(dataset[&#39;fare&#39;]&lt;=30), &#39;fare&#39;]=2
 dataset.loc[(dataset[&#39;fare&#39;]&gt;30)&amp;(dataset[&#39;fare&#39;]&lt;=50), &#39;fare&#39;]=3
 dataset.loc[(dataset[&#39;fare&#39;]&gt;50)&amp;(dataset[&#39;fare&#39;]&lt;=100), &#39;fare&#39;]=4
 dataset.loc[ dataset[&#39;fare&#39;]&gt;100, &#39;fare&#39;]=5
for dataset1 in data:
 dataset1[&#39;fare_person&#39;]=dataset1[&#39;fare&#39;]/(dataset1[&#39;sibpar&#39;]+1)
 dataset1[&#39;fare_person&#39;]=dataset1[&#39;fare_person&#39;].astype(int)
# 7. 최종 데이터
X_columns=[&#39;pclass&#39;, &#39;sex&#39;, &#39;age&#39;, &#39;embarked&#39;, &#39;title&#39;, &#39;sibpar&#39;, &#39;n_alone&#39;, &#39;fare_person&#39;]
y_column=[&#39;survived&#39;]
X_train=train_df[X_columns]
y_train=train_df[y_column]
X_test=test_df[X_columns]
y_test=test_df[y_column]</code></pre>
<hr>
<h2 id="✅-필요한-라이브러리">✅ 필요한 라이브러리</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np # 선형대수
import pandas as pd # 데이터 처리
# 머신러닝 알고리즘
from sklearn import linear_model
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC, LinearSVC
from sklearn.naive_bayes import GaussianNB
# 시각화
import seaborn as sns
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import style</code></pre>
<hr>
<h2 id="✅-의사결정나무">✅ 의사결정나무</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">decision_tree=DecisionTreeClassifier()
decision_tree.fit(X_train, y_train)
Y_pred=decision_tree.predict(X_test)
train_acc_decision_tree=round(decision_tree.score(X_train, y_train)*100, 2)
test_acc_decision_tree=round(decision_tree.score(X_test, y_test)*100, 2)
train_acc_decision_tree,test_acc_decision_tree</code></pre>
<hr>
<h2 id="✅-랜덤-포레스트">✅ 랜덤 포레스트</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">random_forest=RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, y_train)
Y_prediction=random_forest.predict(X_test)
random_forest.score(X_train, y_train)
train_acc_random_forest=round(random_forest.score(X_train, y_train)*100, 2)
test_acc_random_forest=round(random_forest.score(X_test, y_test)*100, 2)
train_acc_random_forest,test_acc_random_forest</code></pre>
<hr>
<h2 id="✅-로지스틱-회귀분석">✅ 로지스틱 회귀분석</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">log_reg=LogisticRegression()
log_reg.fit(X_train, y_train)
Y_pred=log_reg.predict(X_test)
train_acc_log=round(log_reg.score(X_train, y_train)*100, 2)
test_acc_log=round(log_reg.score(X_test, y_test)*100, 2)
train_acc_log, test_acc_log</code></pre>
<hr>
<h2 id="✅-나이브-베이지안">✅ 나이브 베이지안</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">gaussian=GaussianNB()
gaussian.fit(X_train, y_train)
Y_pred=gaussian.predict(X_test)
train_acc_gaussian=round(gaussian.score(X_train, y_train)*100, 2)
test_acc_gaussian=round(gaussian.score(X_test, y_test)*100, 2)
train_acc_gaussian,test_acc_gaussian</code></pre>
<hr>
<h2 id="✅-k-nearest-neighbor">✅ K-Nearest Neighbor</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">knn=KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
Y_pred=knn.predict(X_test)
train_acc_knn=round(knn.score(X_train, y_train)*100, 2)
test_acc_knn=round(knn.score(X_test, y_test)*100, 2)
train_acc_knn,test_acc_knn</code></pre>
<hr>
<h2 id="✅-svm-모델">✅ SVM 모델</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">svc=LinearSVC()
svc.fit(X_train, y_train)
Y_pred=svc.predict(X_test)
train_acc_svc=round(svc.score(X_train, y_train)*100, 2)
test_acc_svc=round(svc.score(X_test, y_test)*100, 2)
train_acc_svc,test_acc_svc</code></pre>
<hr>
<h2 id="✅-퍼셉트론-모델">✅ 퍼셉트론 모델</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">perceptron=Perceptron(max_iter=5)
perceptron.fit(X_train, y_train)
Y_pred=perceptron.predict(X_test)
train_acc_perceptron=round(perceptron.score(X_train, y_train)*100, 2)
test_acc_perceptron=round(perceptron.score(X_test, y_test)*100, 2)
train_acc_perceptron,test_acc_perceptron</code></pre>
<hr>
<h2 id="✅-확률적-경사하강법-방법">✅ 확률적 경사하강법 방법</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">sgd=linear_model.SGDClassifier(max_iter=5, tol=None)
sgd.fit(X_train, y_train)
Y_pred=sgd.predict(X_test)
sgd.score(X_train, y_train)
train_acc_sgd=round(sgd.score(X_train, y_train)*100, 2)
test_acc_sgd=round(sgd.score(X_test, y_test)*100, 2)
train_acc_sgd,test_acc_sgd</code></pre>
<hr>
<h2 id="✅-2-모델-비교">✅ 2. 모델 비교</h2>
<p>###모델의 정확도 비교</p>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">results = pd.DataFrame({
 &#39;Model&#39;: [&#39;Support Vector Machines&#39;, &#39;KNN&#39;, &#39;Logistic Regression&#39;, &#39;Random Forest&#39;,&#39;Naive Bayes&#39;, &#39;Perceptron&#39;, &#39;Stochastic Gradient Decent&#39;, &#39;Decision Tree&#39;],
 &#39;train_Score&#39;: [train_acc_svc, train_acc_knn, train_acc_log,train_acc_random_forest,
train_acc_gaussian, train_acc_perceptron, train_acc_sgd, train_acc_decision_tree],
 &#39;test_Score&#39;: [test_acc_svc, test_acc_knn,test_acc_log, test_acc_random_forest, test_acc_gaussian, test_acc_perceptron, test_acc_sgd, test_acc_decision_tree]})
result_df=results.sort_values(by=&#39;train_Score&#39;, ascending=False)
result_df=result_df.set_index(&#39;Model&#39;)
result_df.head(10)</code></pre>
<hr>
<h2 id="✅-랜덤-포레스트-모델의-교차검증-실습">✅ 랜덤 포레스트 모델의 교차검증 실습</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">from sklearn.model_selection import cross_val_score
rf=RandomForestClassifier(n_estimators=100)
scores=cross_val_score(rf, X_train, y_train, cv=10, scoring=&quot;accuracy&quot;)
print(&quot;Scores:&quot;, scores)
print(&quot;Mean:&quot;, scores.mean())
print(&quot;Standard Deviation:&quot;, scores.std())</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<빅데이터 분석> 9. 선형대수와 신경망 기초 ]]></title>
            <link>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-9.-%EC%84%A0%ED%98%95%EB%8C%80%EC%88%98%EC%99%80-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-9.-%EC%84%A0%ED%98%95%EB%8C%80%EC%88%98%EC%99%80-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 06 Jun 2025 13:30:36 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-section-01-벡터">✅ Section 01. 벡터</h2>
<p>##1. 스칼라와 벡터</p>
<hr>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
a=[1, 2]</code></pre>
<hr>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
a1=np.array([1, 2])
a1</code></pre>
<hr>
<h2 id="✅-벡터의-합과-차">✅ 벡터의 합과 차</h2>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python"># 벡터의 합
a=np.array([1, 2])
b=np.array([3, 4])
c=a+b
c</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python"># 벡터의 차
a=np.array([1, 2])
b=np.array([3, 4])
c=a-b
c</code></pre>
<hr>
<h2 id="✅-리스트-합과-벡터-합">✅ 리스트 합과 벡터 합</h2>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 리스트의 합
a=[1, 2]
b=[3, 4]
a+b</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python"># 벡터의 합
a1=np.array([1, 2])
b1=np.array([3, 4])
a1+b1</code></pre>
<hr>
<h2 id="✅-2-벡터의-연산">✅ 2. 벡터의 연산</h2>
<h3 id="❶-리스트-원소끼리-각각-더하기">❶ 리스트 원소끼리 각각 더하기</h3>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">Hong1=[80, 90]
Jung1=[70, 80]
Score=[]
for i in range(len(Hong1)): # len(Hong1)=2
 Score.append(Hong1[i]+Jung1[i])
print(Score)</code></pre>
<hr>
<h3 id="❷-넘파이를-이용해-더하기">❷ 넘파이를 이용해 더하기</h3>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
npHong1=np.array(Hong1)
npJung1=np.array(Jung1)
npHong1+npJung1</code></pre>
<hr>
<h2 id="✅-벡터의-차">✅ 벡터의 차</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def vector_sub(*args):# 각 성분끼리 더하기
  return [vi - wi for vi, wi in zip(*args)]
sub=vector_sub(Hong1, Jung1)
sub</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">npHong1=np.array(Hong1)
npJung1=np.array(Jung1)
npHong1-npJung1</code></pre>
<hr>
<h2 id="✅-3-벡터의-선형조합">✅ 3. 벡터의 선형조합</h2>
<h3 id="스칼라와-벡터의-곱"><strong>스칼라와 벡터의 곱</strong></h3>
<h3 id="❶-넘파이-배열-사용">❶ 넘파이 배열 사용</h3>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">x1=np.array([1, 2])
x2=np.array([3, 4])
x3=0.5*x1 + 0.5*x2
x3</code></pre>
<hr>
<h2 id="✅-❷-map-함수-사용">✅ ❷ map 함수 사용</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">A=[1, 2, 3, 4, 5]
scalar_multiply=list(map(lambda x: x*2, A))
print(scalar_multiply)</code></pre>
<hr>
<h2 id="✅-선형조합의-예">✅ 선형조합의 예</h2>
<h4 id="❶-sum-함수-zip-함수-이용">❶ sum 함수, zip 함수 이용</h4>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">Hong1=[80, 90]
Jung1=[70, 80]
a=0.5
avg=[a*sum(i) for i in zip(Hong1, Jung1)]
avg</code></pre>
<hr>
<h2 id="✅-❷-넘파이를-이용한-스칼라와-벡터의-곱">✅ ❷ 넘파이를 이용한 스칼라와 벡터의 곱</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
npHong1=np.array(Hong1)
npJung1=np.array(Jung1)
np_S= npHong1+npJung1
a=0.5
a*np_S</code></pre>
<hr>
<h2 id="✅-4-벡터의-내적">✅ 4. 벡터의 내적</h2>
<h3 id="함수를-만들어-벡터의-내적-구하기">함수를 만들어 벡터의 내적 구하기</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def dot(v, w):
 return sum(v_i * w_i for v_i, w_i in zip(v, w))
A=[4, 3]
B=[6, 0]
dot(A, B)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">x1=[1, 2, 3]
y1=[4, 5, 6]
dot(x1, y1)</code></pre>
<hr>
<h2 id="✅-넘파이로-벡터의-내적-구하기">✅ 넘파이로 벡터의 내적 구하기</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">npx1=np.array([1, 2, 3])
npy1=np.array([4, 5, 6])
sum(npx1*npy1)</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([1, 2, 3, 4, 5])
b=np.array([2, 4, 6, 8, 10])
print(a*b)
print(sum(a*b))
print(np.sum(a*b))</code></pre>
<hr>
<h2 id="✅-넘파이-브로드캐스팅으로-벡터의-내적-구하기">✅ 넘파이 브로드캐스팅으로 벡터의 내적 구하기</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">np_Hong1=np.array([80, 90, 10, 9])
np_Jung1=np.array([70, 80, 9, 8])
v=np_Hong1+np_Jung1
w=[0.5, 0.5, 0.5, 0.5]
[i * j for i, j in zip(v, w)]</code></pre>
<hr>
<h2 id="✅-❶-넘파이-브로드캐스팅-실습">✅ ❶ 넘파이 브로드캐스팅 실습</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">avg=[75.0, 85.0, 9.5, 8.5]
avg1=np.array(avg)
W1=np.array([0.4, 0.4, 1, 1])
print(avg1*W1)</code></pre>
<hr>
<h2 id="✅-❷-벡터의-내적-실습">✅ ❷ 벡터의 내적 실습</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">Final=avg1*W1
print(Final.sum())</code></pre>
<hr>
<h2 id="✅-5-벡터의-길이">✅ 5. 벡터의 길이</h2>
<h3 id="넘파이-함수로-벡터의-길이-구하기">넘파이 함수로 벡터의 길이 구하기</h3>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a1=np.array([1, 2])
np.linalg.norm(a1)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def dot(v, w):
 return sum(v_i * w_i for v_i, w_i in zip(v, w))
def sum_of_squares(v):
 return dot(v, v)
v=[1, 2]
sum_of_squares(v)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def sum_of_squares1(v, w):
 return dot(v, w)
v=[1, 2]
w=[3, 4]
sum_of_squares1(v, w)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import math
def magnitude(v):
  return math.sqrt(sum_of_squares(v))
v=[1, 2]
magnitude(v)</code></pre>
<hr>
<h2 id="✅-벡터-간의-거리유클리드-거리-구하기">✅ 벡터 간의 거리(유클리드 거리) 구하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def vector_subtract(v, w):
  return [v_i - w_i for v_i, w_i in zip(v, w)]
def squared_distance(v, w):
  return sum_of_squares(vector_subtract(v, w))
v=[1, 2]
w=[3, 4]
squared_distance(v, w)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def distance(v, w):
 return math.sqrt(squared_distance(v, w))
def distance1(v, w):
 return magnitude(vector_subtract(v, w))
v=[1, 2]
w=[3, 4]
print(distance(v, w))
print(distance1(v, w))</code></pre>
<hr>
<h2 id="✅-6-벡터를-이용한-평균-제곱-오차와-평균-제곱근-오차">✅ 6. 벡터를 이용한 평균 제곱 오차와 평균 제곱근 오차</h2>
<h3 id="평균-제곱-오차">평균 제곱 오차</h3>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">y=[1, 1, 0, 1, 0, 1, 0, 1, 0, 0] # 실제값
t=[0, 0, 1, 0, 0, 1, 0, 1, 0, 0] # 예측값
import numpy as np
def mean_squared_error(y, t):
 return 0.5 * np.sum((y-t)**2)
mean_squared_error(np.array(y), np.array(t))</code></pre>
<hr>
<h3 id="평균-제곱근-오차">평균 제곱근 오차</h3>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">c=np.array([1, 2, 3, 4, 5])
d=np.array([2, 4, 6, 8, 10])
print((sum((c-d)**2)/len(c))**(1/2))</code></pre>
<hr>
<h2 id="✅-section-02-행렬">✅ Section 02. 행렬</h2>
<h3 id="1-행렬의-표기">1. 행렬의 표기</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">Hong=[[80, 90, 20, 18], [70, 80, 18, 16]] # 홍길동 성적을 리스트로 표시
Lee=[(90, 80, 18, 20), (100, 95, 20, 18)] # 이몽룡 성적을 튜플로 표시
Kim={&#39;m&#39;: (100, 95), &#39;f&#39;:(95, 100), &#39;a&#39;:(20, 20), &#39;r&#39;:(19, 18)} # 김철수 성적을 딕셔너리로 표시
Kim</code></pre>
<hr>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
A=[[1, 2, 3],
 [4, 5, 6]]
B=[[1, 2],
 [3, 4],
 [5, 6]]
A1=np.array(A)
A1.shape
np.shape(A)</code></pre>
<hr>
<h3 id="✅-2-행렬의-연산">✅ 2. 행렬의 연산</h3>
<h4 id="행렬의-덧셈-뺄셈">행렬의 덧셈, 뺄셈</h4>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">x=np.array([10, 11, 12, 13, 14])
y=np.array([0, 1, 2, 3, 4])
x+y
x-y</code></pre>
<hr>
<h2 id="✅-행렬의-곱셈">✅ 행렬의 곱셈</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">x=np.array([[1], [2], [3]])
y=np.array([[4], [5], [6]])
x*y</code></pre>
<hr>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">x.shape, y.shape # (3, 1), (3, 1)
x.T # array([[1, 2, 3]])
x.T.shape # (1, 3)
x.T*y</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">x_T=np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
y=np.array([[4, 4, 4], [5, 5, 5], [6, 6, 6]])
x_T*y</code></pre>
<hr>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">x=np.array([[1], [2], [3]]) # 3*1
y=np.array([[4], [5], [6]]) # 3*1
print(x.T@y)
print(x@y.T)</code></pre>
<hr>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">print(np.dot(x.T, y))
print(np.dot(x, y.T))</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.array([[1, 2, 3], [4, 5, 6]])
A.shape # (2, 3)
B=np.array([[1, 2], [3, 4], [5, 6]])
B.shape # (3, 2)
A*B</code></pre>
<hr>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.array([[1, 2, 3], [4, 5, 6]])
A.shape # (2, 3)
B=np.array([[1, 2], [3, 4], [5, 6]])
B.shape # (3, 2)
print(A.T*B)
print(A*B.T)
print(A@B)</code></pre>
<hr>
<h2 id="✅-section-03-신경망-기초">✅ Section 03. 신경망 기초</h2>
<h3 id="1-시그모이드-함수">1. 시그모이드 함수</h3>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
 return 1 / (1 + np.exp(-x))
X=np.arange(-5.0, 5.0, 0.1)
Y=sigmoid(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()</code></pre>
<hr>
<h3 id="2-순전파-신경망-구현">2. 순전파 신경망 구현</h3>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">input=[0.2, 0.9] # 2개의 입력층
weight1=[[0.05,0.01], [-0.01,0.03], [0.02,-0.01]] #가중치 값
bias1=[-0.3,0.2,0.05]
weight2=[0.01,0.05,0.015] # 은닉층에서 출력층으로 가는 가중치
bias2=[-0.015] # 출력층으로 가는 바이어스</code></pre>
<hr>
<h2 id="✅-입력층에서-은닉층으로-신호-전달">✅ 입력층에서 은닉층으로 신호 전달</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 입력층의 첫 번째 노드에서 은닉층의 첫 번째 노드로 신호 전달
def dot(v, w):
  return sum(i * j for i, j in zip(v, w))
input=[0.2,0.9]
w=[0.05,0.01]
dot(input,w)+bias1[0] # 0.2*0.05 +0.9*0.01 + (-0.3)</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 입력층에서 은닉층 노드로 보내는 신호값
N3=dot(input,weight1[0])+bias1[0]
N4=dot(input,weight1[1])+bias1[1]
N5=dot(input,weight1[2])+bias1[2]
N3, N4, N5</code></pre>
<hr>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python"># 시그모이드 함수를 이용해 N3, N4, N5 노드에서 출력하는 값
import numpy as np
def sigmoid(x):
 return 1 / (1 + np.exp(-x))
outputN3=sigmoid(N3)
outputN4=sigmoid(N4)
outputN5=sigmoid(N5)
outputN3, outputN4, outputN5</code></pre>
<hr>
<h2 id="✅-은닉층에서-출력층으로-신호-전달">✅ 은닉층에서 출력층으로 신호 전달</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">Z1=[outputN3, outputN4, outputN5]
N6=dot(Z1, weight2)+bias2
outputN6=sigmoid(N6)
outputN6</code></pre>
<hr>
<h2 id="✅-넘파이를-활용한-순전파-신경망-계산">✅ 넘파이를 활용한 순전파 신경망 계산</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python"># 입력층에서 첫 번째 은닉층으로 신호 전달
import numpy as np
input=np.array([[0.2, 0.9]]) # 1*2 행렬
weight1=np.array([[0.05,0.01], [-0.01,0.03], [0.02,-0.01]]) # 3*2 행렬
bias1=np.array([-0.3,0.2,0.05])
A1=np.dot(input, weight1.T) + bias1 # weight1.T는 weight의 전치 행렬
Z1=sigmoid(A1)
Z1</code></pre>
<hr>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">weight2=np.array([[0.01, 0.05, 0.015]])
bias2=np.array([[-0.015]])
A2=np.dot(Z1, weight2.T)+bias2
Z2=sigmoid(A2)
Z2</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">def identity_function(x):
 return x
Y=identity_function(A2) # 또는 Y=A2
Y</code></pre>
<hr>
<h2 id="✅-순전파-구현-정리">✅ 순전파 구현 정리</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">def init_network():
  network={}
  network[&#39;W1&#39;]=np.array([[0.05, -0.01, 0.02],[0.01, 0.03, -0.01]])
  network[&#39;b1&#39;]=np.array([-0.3,0.2,0.05])
  network[&#39;W2&#39;]=np.array([0.01,0.05,0.015])
  network[&#39;b2&#39;]=np.array([-0.015])
  return network

def forward(network, x):
  W1, W2=network[&#39;W1&#39;], network[&#39;W2&#39;]
  b1, b2=network[&#39;b1&#39;], network[&#39;b2&#39;]
  a1=np.dot(x, W1) + b1
  z1=sigmoid(a1)
  a2=np.dot(z1, W2) + b2
  z2=sigmoid(a2) # 시그모이드 함수 사용
  # z2=identity_function(a2) # 항등 함수 사용
  return z2

network=init_network()

input=np.array([0.2, 0.9])
Z=forward(network, input)
Z</code></pre>
<hr>
<h2 id="✅-3-역전파-신경망-구현">✅ 3. 역전파 신경망 구현</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">y=Z
t=1
E=y-t
E</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python"># 출력층에서 은닉층으로의 역전파 시 가중치 업데이트
# 이전의 가중치
weight2_old=np.array([0.01,0.05,0.015])
bias2_old=np.array([-0.015])
delta=(1-y)*y*(y-t) # 업데이트 되는 가중치
learning_rate=0.01 # 학습률
learning_rate=0.01
bias2_new=bias2_old+learning_rate*delta # 바이어스
weight36=weight2_old[0]+learning_rate*delta*Z1[0][0] # weight2_new[0]
weight46=weight2_old[1]+learning_rate*delta*Z1[0][1] # weight2_new[1]
weight56=weight2_old[2]+learning_rate*delta*Z1[0][2] # weight2_new[2]
print(weight36, weight46, weight56, bias2_new)</code></pre>
<hr>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">#이전의 가중치
weight1_old=np.array([[0.05, -0.01, 0.02],[0.01, 0.03, -0.01]])
bias1_old=np.array([[-0.3, 0.2, 0.05]])
#업데이트되는 가중치
learning_rate=0.01 # 학습률
X=np.array([[0.2, 0.9]])
bias13_new=bias1_old[0][0]+learning_rate*delta # 바이어스
bias14_new=bias1_old[0][1]+learning_rate*delta # 바이어스
bias15_new=bias1_old[0][2]+learning_rate*delta # 바이어스
weight13=weight1_old[0][0]+learning_rate*delta*X[0][0] # weight1_new[0][0]
weight14=weight1_old[0][1]+learning_rate*delta*X[0][0] # weight2_new[0][1]
weight15=weight1_old[0][2]+learning_rate*delta*X[0][0] # weight3_new[0][2]
weight23=weight1_old[1][0]+learning_rate*delta*X[0][1] # weight1_new[1][0]
weight24=weight1_old[1][1]+learning_rate*delta*X[0][1] # weight2_new[1][1]
weight25=weight1_old[1][2]+learning_rate*delta*X[0][1] # weight3_new[1][2]
print(weight13,weight14,weight15,weight23,weight24,weight25)
print(bias13_new,bias14_new,bias15_new)</code></pre>
<hr>
<h2 id="✅-4-사이킷런을-이용한-신경망-구현">✅ 4. 사이킷런을 이용한 신경망 구현</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">import pandas as pd
url=&#39;https://raw.githubusercontent.com/sehakflower/data/main/TinyData.csv&#39;
data= pd.read_csv(url,sep=&#39;,&#39;) # 또는 data=pd.read_csv(&#39;TinyData.csv&#39;)
data</code></pre>
<hr>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">predictors=[&#39;Fat&#39;, &#39;Salt&#39;]
outcome=&#39;Acceptance&#39;
X=data[predictors]
y=data[outcome]
classes=sorted(y.unique())
from sklearn.neural_network import MLPClassifier
clf=MLPClassifier(hidden_layer_sizes=(3,), activation=&#39;logistic&#39;, solver=&#39;lbfgs&#39;, random_state=1)
clf.fit(X, y)
clf.predict(X)
print(&#39;바이어스 값&#39;)
print(clf.intercepts_)
print(&#39;가중치 값&#39;)
print(clf.coefs_)
print(pd.concat([data, pd.DataFrame(clf.predict_proba(X), columns=classes)], axis=1))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<빅데이터 분석> 8. 넘파이 활용]]></title>
            <link>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-8.-%EB%84%98%ED%8C%8C%EC%9D%B4-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@jung_ji_in02/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-8.-%EB%84%98%ED%8C%8C%EC%9D%B4-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Fri, 06 Jun 2025 13:12:05 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-section-01-넘파이-소개">✅ Section 01. 넘파이 소개</h2>
<blockquote>
<p>넘파이 라이브러리를 설치합니다.</p>
</blockquote>
<pre><code class="language-python"># 주피터 노트북 사용자
!pip install numpy</code></pre>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np # numpy의 별칭을 np로 설정
print(np.__version__) # 파이썬 라이브러리의 버전 확인</code></pre>
<hr>
<h2 id="✅-2-넘파이-배열">✅ 2. 넘파이 배열</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">d=np.arange(12).reshape(3,4)
print(d,d.shape)</code></pre>
<hr>
<h2 id="✅-dtype">✅ dtype</h2>
<blockquote>
<p>배열의 데이터 타입을 확인합니다.</p>
</blockquote>
<pre><code class="language-python">print(d.dtype)</code></pre>
<hr>
<h2 id="✅-ndim">✅ ndim</h2>
<blockquote>
<p>배열의 차원 수를 확인합니다.</p>
</blockquote>
<pre><code class="language-python">print(d.ndim)</code></pre>
<hr>
<h2 id="✅-t">✅ T</h2>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">print(d.T) # 전치 행렬</code></pre>
<hr>
<h2 id="✅-size">✅ size</h2>
<blockquote>
<p>배열의 전체 원소 개수를 반환합니다.</p>
</blockquote>
<pre><code class="language-python">print(d.size) # 요소의 개수</code></pre>
<hr>
<h2 id="✅-nbytes">✅ nbytes</h2>
<blockquote>
<p>배열이 차지하는 바이트 수를 반환합니다.</p>
</blockquote>
<pre><code class="language-python">print(d.nbytes) # 64bit는 8byte이므로 총 96byte(8*12)</code></pre>
<hr>
<h2 id="✅-flat">✅ flat</h2>
<blockquote>
<p>flat을 사용하여 모든 원소를 1로 설정합니다.</p>
</blockquote>
<pre><code class="language-python">d.flat=1
print(d)</code></pre>
<hr>
<h2 id="✅-3-넘파이-배열-생성">✅ 3. 넘파이 배열 생성</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">mynpa1=np.array([1, 2, 3])
print(type(mynpa1))</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">mylist2=[[1, 2, 3],
 [4, 5, 6]]
print(type(mylist2))
mynpa2=np.array(mylist2) # 변수명 직접 입력
print(type(mynpa2))</code></pre>
<hr>
<h2 id="✅-데이터프레임으로-생성">✅ 데이터프레임으로 생성</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">import pandas as pd
mypd1=pd.DataFrame([1, 2, 3])
print(type(mypd1))
mynp1=np.array(mypd1)
print(type(mynp1))</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">mylist2=[[1, 2, 3],
 [4, 5, 6]]
print(type(mylist2))
mypd2=pd.DataFrame(mylist2)
print(type(mypd2))
mynpa2=np.array(mypd2)
print(type(mynpa2))
mypd2</code></pre>
<hr>
<h2 id="✅-4-n차원-넘파이-배열">✅ 4. n차원 넘파이 배열</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
a=np.arange(3)
print(a, type(a), a.ndim, a.shape)</code></pre>
<hr>
<h2 id="✅-①-종료값만-설정">✅ ① 종료값만 설정</h2>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">a2=np.arange(4)
a2</code></pre>
<hr>
<h2 id="✅-②-시작값-종료값-설정">✅ ② 시작값, 종료값 설정</h2>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">a2=np.arange(2, 4)
print(a2, a2.shape)</code></pre>
<hr>
<h2 id="✅-③-시작값-종료값-증가값-모두-설정">✅ ③ 시작값, 종료값, 증가값 모두 설정</h2>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">a3=np.arange(0, 5, 2)
print(a3, a3.shape)</code></pre>
<hr>
<h2 id="✅-6-2차원-넘파이-배열-생성">✅ 6. 2차원 넘파이 배열 생성</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([[0, 1, 2],
 [3, 4, 5]])
print(m, m.shape)</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([np.arange(3), np.arange(3, 6)])
print(m, m.shape)</code></pre>
<hr>
<h2 id="✅-3행-2열의-배열-생성">✅ 3행 2열의 배열 생성</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([[0, 3],
 [1, 4],
 [2, 5]])
print(m, m.shape)
print(m.shape[0], m.shape[1])</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([np.arange(0, 4, 3), np.arange(1, 5, 3),
np.arange(2, 6, 3)])
print(m, m.shape)</code></pre>
<hr>
<h2 id="✅-7-3차원-넘파이-배열-생성">✅ 7. 3차원 넘파이 배열 생성</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([[[0, 1, 2],
 [3, 4, 5]],
 [[0, 1, 2],
 [3, 4, 5]]])
print(m)
print(m.shape)</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([[[0, 1],
 [2, 3]],
 [[4, 5],
 [6, 7]],
 [[8, 9],
 [10, 11]]])
print(m, m.shape)</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">m=np.array([[[0, 1, 2]],
 [[3, 4, 5]],
 [[0, 1, 2]],
 [[3, 4, 5]]])
print(m, m.shape)</code></pre>
<hr>
<h2 id="✅-여기서-잠깐-파이썬의-range-함수와-넘파이의-arange-함수">✅ &lt;여기서 잠깐&gt; 파이썬의 range 함수와 넘파이의 arange 함수</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">list=[1, 2, 3, 4, 5]
for i in list:
  print(i)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">list=[1, 2, 3, 4, 5]
for i in range(1, 6):
  print(i)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">list=[1, 2, 3, 4, 5]
for i in range(1, 6, 2):
  print(i)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">arr=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr</code></pre>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">arr=np.arange(start=1, stop=11, step=1)
arr</code></pre>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">arr=np.arange(1, 11)
arr</code></pre>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">arr=np.arange(start=1, stop=10, step=2)
arr</code></pre>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">arr=np.arange(1, 10, 2)
arr</code></pre>
<hr>
<h2 id="✅-section-02-배열-다루기">✅ Section 02. 배열 다루기</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np

nda=np.array([0, 1, 2, 3, 4, 5])
print(nda, type(nda)) # nda 변수의 데이터형 반환
print(nda.dtype) # 넘파이 배열인 nda 요소의 자료형 반환
nda # 넘파이 배열이므로 array([]) 형식으로 표시</code></pre>
<blockquote>
<p>배열의 데이터 타입을 확인합니다.</p>
</blockquote>
<pre><code class="language-python">nda1=np.array([0, 1, 2, 3.0, 4, 5])
print(nda1, nda1.dtype)</code></pre>
<blockquote>
<p>배열의 데이터 타입을 확인합니다.</p>
</blockquote>
<pre><code class="language-python">nda2=np.array([0, 1, 2, 3, 4, 5])
print(nda2, nda2.dtype)</code></pre>
<hr>
<h2 id="✅-2-배열의-형태-변경">✅ 2. 배열의 형태 변경</h2>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">m1=np.arange(6)
print(m1)
print(m1.shape)</code></pre>
<blockquote>
<p>배열의 구조를 변경합니다.</p>
</blockquote>
<pre><code class="language-python">m2=m1.reshape(6, 1)
print(m2, m2.shape) # 1행 6열을 6행 1열로 변경</code></pre>
<blockquote>
<p>배열의 구조를 변경합니다.</p>
</blockquote>
<pre><code class="language-python">m2=m1.reshape(2, 3)
print(m2, m2.shape)</code></pre>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">m1=np.arange(12)
m3=m1.reshape(2, 2, 3)
print(m3, m3.shape)</code></pre>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">m1=np.arange(6)
print(m1, m1.shape)
m2=m1.reshape(-1, 2) # 행 수는 자동, 2열 생성
print(m2, m2.shape)
m2=m1.reshape(-1, 3) # 행 수는 자동, 3열 생성
print(m2, m2.shape)
m2=m1.reshape(-1, 1) # 행 수는 자동, 1열 생성
print(m2, m2.shape)</code></pre>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">m1=np.arange(6)
m3=m1.reshape(-1, 2, 3)
print(m3, m3.shape)
m3=m1.reshape(-1, 3, 2) # 면 수 자동, 3행 2열 생성
print(m3,m3.shape)
m3=m1.reshape(2, 3, -1) # 열 수 자동, 2면 3행 생성
print(m3, m3.shape)</code></pre>
<hr>
<h2 id="✅-다차원-배열을-1차원-배열로-평탄화하기-flatten-함수">✅ 다차원 배열을 1차원 배열로 평탄화하기: flatten 함수</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.arange(12).reshape(3, 4)
a</code></pre>
<blockquote>
<p>flat을 사용하여 모든 원소를 1로 설정합니다.</p>
</blockquote>
<pre><code class="language-python">f=a.flatten()
print(f, f.shape)</code></pre>
<hr>
<h2 id="✅-배열의-방향-변경하기">✅ 배열의 방향 변경하기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a</code></pre>
<blockquote>
<p>행 또는 열의 순서를 뒤집습니다.</p>
</blockquote>
<pre><code class="language-python">print(a[::-1]) # 행 순서를 거꾸로 변경</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[:,::-1]) # 열 순서를 거꾸로 변경</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[::-1,::-1]) # 행과 열 순서를 거꾸로 변경</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">t=a.transpose()
print(a, a.shape) # a 배열은 3행 4열
print(t, t.shape) # t 배열은 a 배열을 4행 3열로 변경한 것</code></pre>
<blockquote>
<p>배열의 전치(transpose)를 구합니다.</p>
</blockquote>
<pre><code class="language-python">print(a.T) # transpose() 함수와 결과 동일</code></pre>
<hr>
<h2 id="✅-3-배열-통합과-분할">✅ 3. 배열 통합과 분할</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.arange(9).reshape(3, 3)
b=a*2
print(a)
print(b)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">r=np.vstack((a, b))
print(r, r.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">c=np.row_stack((a, b))
print(c, c.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">x=np.concatenate((a, b), axis=0)
print(x, x.shape)</code></pre>
<hr>
<h2 id="✅-열-방향으로-합치기">✅ 열 방향으로 합치기</h2>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">r=np.hstack((a, b))
print(r, r.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">c=np.column_stack((a, b))
print(c, c.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">x=np.concatenate((a, b), axis=1)
print(x, x.shape)</code></pre>
<hr>
<h2 id="✅-행-단위로-분할하기">✅ 행 단위로 분할하기</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.arange(12).reshape(4, 3)
print(a)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(np.vsplit(a, 4))</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(np.split(a, 4, axis=0))</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[0:1], a[1:2], a[2:3], a[3:])</code></pre>
<hr>
<h2 id="✅-열-단위로-분할하기">✅ 열 단위로 분할하기</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.arange(12).reshape(3, 4)
print(a)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(np.hsplit(a, 4))</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(np.split(a, 4, axis=1))</code></pre>
<hr>
<h2 id="✅-section-03-배열의-인덱싱과-슬라이싱">✅ Section 03. 배열의 인덱싱과 슬라이싱</h2>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">d=np.arange(6)
print(d, d.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 1차원 배열의 인덱싱
print(d[0], d[1], d[2], d[-1], d[-2])</code></pre>
<blockquote>
<p>행 또는 열의 순서를 뒤집습니다.</p>
</blockquote>
<pre><code class="language-python"># 1차원 배열의 슬라이싱
print(d[2:3], d[:], d[:-1])
print(d[::2], d[::-1])
d[0]=10 # 요소 값 변경
print(d)</code></pre>
<hr>
<h2 id="✅-2-2차원-배열의-인덱싱과-슬라이싱">✅ 2. 2차원 배열의 인덱싱과 슬라이싱</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">d=np.arange(12).reshape(3, 4)
print(d, d.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 2차원 배열의 인덱싱
print(d[0][0]) # 배열명[행][열] 형태
print(d[0,0]) # 배열명[행,열] 형태
print(d[2,1])</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 2차원 배열의 슬라이싱
print(d[0]) # 0행만 슬라이싱
print(d[0,:]) # 0행의 전체 열 슬라이싱
print(d[:,:-1]) # 마지막 열 제외한 전체 행과 열 슬라이싱
print(d[::2,::2]) # 2행 2열 간격으로 전체 행과 열 슬라이싱</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">d[0]=20 # 0번 행 요소를 모두 20으로 변경
print(d)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">d[::2,::3]=30 # 슬라이싱된 요소값을 한꺼번에 변경
print(d[::2,::3])
print(d)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">d[:,:]=-1 # 전체 요소값을 한꺼번에 변경
print(d)</code></pre>
<hr>
<h2 id="✅-3-3차원-배열의-인덱싱과-슬라이싱">✅ 3. 3차원 배열의 인덱싱과 슬라이싱</h2>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">d=np.arange(12).reshape(2, 2, 3)
print(d, d.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 3차원 배열의 인덱싱
print(d[0][0][0]) # 0면 0행 0열 인덱싱
print(d[0, 0, 0]) # 0면 0행 0열 인덱싱
print(d[0, 1, 2]) # 0면 1행 2열 인덱싱</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 3차원 배열의 슬라이싱
print(d[:,:-1,:-1])</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(d[:,1:,1:])</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(d[:,:,-1])</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(d[:,-1,-1])
print(d[-1,-1,-1])</code></pre>
<blockquote>
<p>arange로 정수 배열을 생성합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.arange(12)
print(a, a.shape)
print(a[0]) # 스칼라
print(a[0:1]) # 배열(ndarray)</code></pre>
<hr>
<h2 id="✅-4-불-인덱싱">✅ 4. 불 인덱싱</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python">import numpy as np
a=np.array([1, 2, 3, 4, 5, 6])
print(a)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a&gt;2)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[a&gt;2])</code></pre>
<hr>
<h2 id="✅-5-조건-필터">✅ 5. 조건 필터</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([1, 2, 3, 4, 5, 6])
result=a&gt;2
result</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a[a&gt;2]</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a[a!=3]</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[(a&gt;2) &amp; (a&lt;6)]) # 2보다 크고 6보다 작은 요소만 추출
print(a[a % 2==0]) # 2로 나눈 나머지가 0인 요소만 추출
print(a[a&lt;a.mean( )]) # 평균보다 작은 값만 추출</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">print(a[True])
print(a[False])</code></pre>
<hr>
<h2 id="✅-section-04-넘파이-배열의-정렬">✅ Section 04. 넘파이 배열의 정렬</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([2, 8, 9, 7, 3, 1, 4, 5, 6])
print(a, a.shape)
r=np.sort(a) # 원본 변경 없이 오름차순 정렬
print(r, a)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 오름차순 정렬
a.sort()
print(a)</code></pre>
<blockquote>
<p>행 또는 열의 순서를 뒤집습니다.</p>
</blockquote>
<pre><code class="language-python"># 내림차순 정렬
r=np.sort(a)[::-1]
print(r)</code></pre>
<hr>
<h2 id="✅-2-인덱스-반환-정렬-argsort-함수">✅ 2. 인덱스 반환 정렬: argsort 함수</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([2, 8, 9, 7, 3, 1, 4, 5, 6])
print(a, a.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">np.argsort(a)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">sort_index=np.argsort(a)
print(sort_index)</code></pre>
<hr>
<h2 id="✅-3-2차원-배열의-정렬">✅ 3. 2차원 배열의 정렬</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">arr2d=np.array([[5, 6, 7, 8],
 [4, 3, 2, 1],
 [10, 9, 12, 11]])
print(arr2d)
print(arr2d.shape)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 열 방향으로 같은 행의 요소 정렬
np.sort(arr2d, axis=1)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 행 방향으로 같은 열의 요소 정렬
np.sort(arr2d, axis=0)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 열 방향으로 같은 행 정렬
print(arr2d)
print(np.sort(arr2d))
np.argsort(arr2d, axis=1)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"># 행 방향으로 같은 열 정렬
print(arr2d)
print(np.sort(arr2d, axis=0))
np.argsort(arr2d, axis=0)</code></pre>
<hr>
<h2 id="✅-section-05-넘파이-배열의-연산">✅ Section 05. 넘파이 배열의 연산</h2>
<blockquote>
<p>넘파이를 np라는 이름으로 불러오고 버전을 출력합니다.</p>
</blockquote>
<pre><code class="language-python"># 같은 배열 내 요소의 합 구하기
import numpy as np
a=np.array([[1, 2, 3],
 [4, 5, 6]])
np.sum(a, axis=0), np.sum(a, axis=1)</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python">a.sum()</code></pre>
<hr>
<h2 id="✅-배열과-상수의-사칙연산">✅ 배열과 상수의 사칙연산</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([[1, 2, 3],
 [4, 5, 6]])
b=2
print(a+b)
print(a-b)
print(a*b)
print(a/b)</code></pre>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.arange(1, 13).reshape(3, 4)
print(A+10)
print(A-10)
print(A*10)
print(A/10)</code></pre>
<blockquote>
<p>arange로 생성한 배열을 reshape로 모양 변경합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.arange(1, 13).reshape(3, 4)
A-1</code></pre>
<hr>
<h2 id="✅-배열과-배열의-요소-간-연산">✅ 배열과 배열의 요소 간 연산</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([[1, 2, 3],
 [4, 5, 6]])
b=np.array([1, 2, 3]) # b=np.array([[1, 2, 3]])도 가능
print(a+b)
print(a-b)
print(a*b)
print(a/b)</code></pre>
<hr>
<h2 id="✅-배열-간의-곱셈과-행렬-곱셈의-차이">✅ 배열 간의 곱셈과 행렬 곱셈의 차이</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">a=np.array([[1, 2],
 [3, 4]])
b=np.array([[1, 2],
 [1, 2]])
print(a*b)
print(a.dot(b))</code></pre>
<hr>
<h2 id="✅-2-브로드캐스팅">✅ 2. 브로드캐스팅</h2>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.array([[1, 2],
 [3, 4]])
B=np.array([1, 2])
A+B</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.array([[1, 2, 3],
 [4, 5, 6]])
B=np.array([2])
A+B</code></pre>
<blockquote>
<p>리스트나 데이터프레임을 넘파이 배열로 변환합니다.</p>
</blockquote>
<pre><code class="language-python">A=np.array([[1, 2, 3],
 [4, 5, 6]])
B=np.array([[2],
 [4]])
A+B</code></pre>
<blockquote>
<p>넘파이 배열 관련 기능을 시연합니다.</p>
</blockquote>
<pre><code class="language-python"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[구글 소셜 로그인 해보기!]]></title>
            <link>https://velog.io/@jung_ji_in02/%EA%B5%AC%EA%B8%80-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%B4%EB%B3%B4%EA%B8%B0-1</link>
            <guid>https://velog.io/@jung_ji_in02/%EA%B5%AC%EA%B8%80-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%B4%EB%B3%B4%EA%B8%B0-1</guid>
            <pubDate>Mon, 19 May 2025 13:20:37 GMT</pubDate>
            <description><![CDATA[<p>이번주 스터디 과제는 구글 소셜 로그인 해보기이다!! </p>
<p>소셜 로그인은 완전 처음 해보는거라 먼저 구글링부터 해보았다.</p>
<blockquote>
<p>&#39;소셜 로그인&#39;이란 별도의 회원가입 절차 없이 소셜 미디어 계정을 이용해 간편하게 새로운 앱이나 웹 사이트를 이용할 수 있는 기능입니다.</p>
</blockquote>
<p><a href="https://www.catchsecu.com/archives/13964#:~:text=&#39;%EC%86%8C%EC%85%9C%20%EB%A1%9C%EA%B7%B8%EC%9D%B8&#39;%EC%9D%B4%EB%9E%80%20%EB%B3%84%EB%8F%84%EC%9D%98,%EC%9D%B4%EC%9A%A9%ED%95%A0%20%EC%88%98%20%EC%9E%88%EB%8A%94%20%EA%B8%B0%EB%8A%A5%EC%9E%85%EB%8B%88%EB%8B%A4.">캐치시큐</a></p>
<p>소셜 로그인은 OAuth 2.0이나 OpenID Connect 같은 인증 프로토콜을 통해 구글, 페이스북, 네이버 같은 제3자 인증 제공자(IDP, Identity Provider)에게 인증을 위임하는 방식이다.</p>
<p>즉, 사용자의 ID/비밀번호를 우리가 저장하거나 처리하지 않고, 외부 플랫폼이 인증을 맡게 된다.</p>
<p>그래서 이것의 장점은 </p>
<table>
<thead>
<tr>
<th>번호</th>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>비밀번호 관리 불필요</td>
<td>사용자 비밀번호를 저장하거나 암호화할 필요 없음</td>
</tr>
<tr>
<td>2</td>
<td>보안 위험 감소</td>
<td>비밀번호 유출, 해킹 등의 위험 감소</td>
</tr>
<tr>
<td>3</td>
<td>개발 간소화</td>
<td>자체 로그인 시스템을 만들지 않아도 되어 개발 리소스 절감 가능</td>
</tr>
<tr>
<td>4</td>
<td>인증 시스템 간편 구성</td>
<td>OAuth나 OpenID Connect 연동만으로 인증 기능 구현 가능</td>
</tr>
<tr>
<td>5</td>
<td>사용자 경험 향상</td>
<td>새로운 계정을 만들지 않아도 되므로 진입 장벽이 낮아짐</td>
</tr>
<tr>
<td>6</td>
<td>빠르고 편리한 로그인</td>
<td>외부 계정으로 한 번의 클릭만으로 로그인 가능</td>
</tr>
</tbody></table>
<hr>
<h3 id="주의할-점">주의할 점</h3>
<p>인증 자체는 구글 등이 해주지만, <strong>인증 이후 받은 사용자 정보를 어떻게 처리할지는 우리 책임</strong>이다.</p>
<p>즉, 로그인 후 우리 DB에 유저를 생성하거나 관리하는 로직, 세션/토큰 관리, 보안 처리 등은 여전히 개발자가 직접 신경 써야 한다!</p>
<hr>
<h3 id="1단계-기존-로그인-→-jwt-방식으로-리팩토링">1단계: 기존 로그인 → JWT 방식으로 리팩토링</h3>
<p>나는 RequestParam 형식으로 작성해서 보안이 취약하기 때문에
기존의 형식에서 JWT 토큰 형식으로 바꿔주기로 결정했다 !!!</p>
<ul>
<li>이메일 + 비밀번호 로그인 검증</li>
<li>로그인 성공 시 JWT 발급</li>
<li>이후 요청에서 JWT로 인증 처리</li>
<li>클라이언트가 <code>?email=xxx</code> 파라미터 넘기는 방식은 제거</li>
</ul>
<h3 id="2단계-구글-소셜-로그인-연동">2단계: 구글 소셜 로그인 연동</h3>
<ul>
<li><code>Spring Security + OAuth2</code> 설정 추가</li>
<li>로그인 성공 시 JWT 토큰 생성</li>
<li>토큰을 HTTP 응답 헤더 또는 리다이렉트 URL로 전달</li>
</ul>
<h3 id="현재-확인된-사항-요약">현재 확인된 사항 요약</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>로그인 방식</td>
<td>이메일 + 비밀번호, <code>POST /user/login</code></td>
</tr>
<tr>
<td>인증 방식</td>
<td>세션/쿠키 없음, 파라미터로 email 넘김</td>
</tr>
<tr>
<td>Spring Security</td>
<td>❌ 없음 → 앞으로 도입해야 함</td>
</tr>
</tbody></table>
<p>라는 문제점들이 있었다 !!! 그래서 먼저 이 부분부터 해결한 후에 소셜 로그인을 도전해야겠다고 생각했다.</p>
<hr>
<h2 id="1단계-기존-로그인-→-jwt-방식으로-리팩토링-1">1단계: 기존 로그인 → JWT 방식으로 리팩토링</h2>
<p>을 만들어 주기 위해서 일단 기존에 RequestParam을 통해 email을 받아주던 코드들을 모두 지워주고 , 로그인 시 JWT 토큰을 HttpOnly 쿠키에 저장하는 형식으로 바꿔주었다 !!</p>
<p>그 이후 인증 요청 시 쿠키에서 토큰을 읽어 인증 처리를 하게 했고 로그아웃 시 쿠키를 삭제하는 형식으로 만들었다.</p>
<p>이번주 주제가 토큰은 아니니 자세한 설명은 건너 뛰었다.</p>
<hr>
<h2 id="2단계-구글-소셜-로그인-연동-1">2단계: 구글 소셜 로그인 연동</h2>
<h3 id="1-google-oauth2-클라이언트-등록">1. Google OAuth2 클라이언트 등록</h3>
<p><strong>1-1. 프로젝트 생성</strong></p>
<ul>
<li>Google Cloud Console 접속 → 프로젝트 생성</li>
</ul>
<p><strong>1-2. OAuth 동의 화면 구성</strong></p>
<ul>
<li><p>외부(External) 사용자 선택</p>
</li>
<li><p>앱 이름, 이메일, 도메인 등록</p>
</li>
<li><p>범위: email, profile 체크</p>
</li>
</ul>
<p><strong>1-3. OAuth2 Client ID 생성</strong></p>
<ul>
<li><p>애플리케이션 유형: 웹 애플리케이션</p>
</li>
<li><p>승인된 리디렉션 URI:</p>
</li>
</ul>
<p><code>http://localhost:8080/login/oauth2/code/google</code></p>
<p><strong>1-4. 클라이언트 ID / 시크릿 복사</strong></p>
<ul>
<li>.yml 또는 .properties 파일에 추가</li>
</ul>
<p><a href="https://velog.io/@gmlstjq123/%EA%B5%AC%EA%B8%80-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84">출처-구글 소셜 로그인 설정</a></p>
<hr>
<h3 id="2-applicationyml-세팅">2. application.yml 세팅</h3>
<pre><code class="language-spring:">  security:
    oauth2:
      client:
        registration:
          google:
            client-id: [발급받은 client-id]
            client-secret: [발급받은 client-secret]
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/{registrationId}&quot;
            scope:
              - profile
              - email
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
            user-name-attribute: sub
  jwt:
    secret: [너의 JWT 비밀 키]
FRONT_ADDRESS: http://localhost:3000
LOGIN_SUCCESS_REDIRECT_PAGE: /info
LOGIN_SUCCESS_REDIRECT_PAGE_IF_INFO_ALREADY_EXISTS: /main</code></pre>
<hr>
<h3 id="3-메서드-구현">3. 메서드 구현</h3>
<p>메서드를 구현하기 전에 각 각의 메서드가 어떤 역할을 하고 왜 필요한 지에 대해서 찾아보았다.</p>
<p>메서드를 아직 만들진 않았지만, 동아리 회장님이 올려준 코드들을 참고하여 어떤 메서드가 필요한 지 판단했고, 그 메서드에 대해서 조사해봤다.</p>
<p><strong>#SecurityConfig</strong></p>
<pre><code>- Spring Security 전반 설정을 담당
- URL 별 접근 권한 설정 (`.authorizeHttpRequests`)
- OAuth2 로그인 관련 설정 (`.oauth2Login`)
- JWT 필터 추가 (`.addFilterBefore`)
- CSRF, 기본 로그인폼 비활성화</code></pre><p>먼저 이 부분은 Jwt 토큰을 활용하면서 미리 만들어놨던 메서드라 일부 수정만 해주면 됐어서 정말 편했다.</p>
<p>*<em>#JwtUtil (JwtTokenProvider) *</em></p>
<pre><code>- JWT 토큰 생성 및 검증 전담 클래스
- 주요 메서드:
  - `createToken(email, role)` : 사용자 정보를 기반으로 JWT 생성
  - `getEmail(token)` : 토큰에서 이메일 추출
  - `getRole(token)` : 토큰에서 사용자 역할 추출
  - `isTokenExpired(token)` : 토큰 만료 여부 확인
- 서명 키(`secretKey`)를 통해 토큰 서명/검증</code></pre><p>이 부분이 기존에 있던 JwtTokenProvider 메서드와 닮았어서 기존에 있던 메서드를 리펙토링 해줘야하나 , 아니면 아에 별도로 새로 만들어야하나 엄청 헷갈렸다.</p>
<p>결론은 똑같이 사용자 정보를 기반으로 토큰을 만들어주는 메서드라 기존의 메서드를 활용해서 만들었다 !</p>
<p><strong>#CustomOAuth2UserService</strong></p>
<pre><code>- Google 로그인 성공 시 사용자 정보를 받아오는 서비스
- Google OAuth2에서 제공하는 `OAuth2UserRequest`를 받아 사용자 정보 추출
- DB에 해당 유저가 없을 경우 새로 저장
- 반환된 `OAuth2User` 객체는 SecurityContext에 저장되어 인증에 사용됨</code></pre><p>이 메서드가 구글에서 사용자 정보를 받아오는 메서드이다 !</p>
<p>여기서 오류가 굉장히 많이 나서 답답해 죽는 줄 알았는데 알고 보니까 또 의존성 까먹었다</p>
<p><strong>#OAuth2SuccessHandler</strong></p>
<pre><code>- OAuth2 로그인 성공 직후 실행되는 핸들러
- 사용자 정보 (`email`, `role`) 를 추출
- `JwtUtil`을 이용해 JWT 토큰 발급
- 토큰을 응답 헤더 또는 쿠키에 담아 클라이언트로 전달
- 필요에 따라 프론트엔드로 리다이렉션 처리</code></pre><p>이 친구도 같이 더블로 오류가 나서 왜 안되나 했는데 .... 의존성을 넣어주니까 다행히 잘 됐다!!</p>
<p><strong>#JwtAuthenticationFilter</strong></p>
<pre><code>- 모든 요청에 대해 JWT 유효성 검사 수행
- `Authorization` 헤더에서 토큰 추출
- `JwtUtil`로 검증 후, 유효하면 인증 객체 생성하여 SecurityContext에 저장
- 이후 컨트롤러에서 `@AuthenticationPrincipal` 또는 `SecurityContextHolder`로 사용자 정보 접근 가능</code></pre><p>이 메서드도 jwt를 활용하면서 만들었던 메서드를 재활용할 수 있어서 좋았다!!</p>
<p>아무튼 이러한 메서드들이 존재했고 나는 이 메서드들이 서로 어떻게 상호작용하는지 전체적인 흐름이 궁굼했다!!</p>
<p>근데 자세히 알려주는 곳이 없어서 
GPT를 통해서 전체적인 흐름을 물어봤다.</p>
<pre><code class="language-plaintext">1️⃣ 사용자가 /oauth2/authorization/google 로 접속
    ↓ (Spring Security 내장 처리)
2️⃣ 로그인 성공 시 → OAuth2SuccessHandler.onAuthenticationSuccess()
    ↓
3️⃣ 사용자 정보(email, role) 추출
    ↓
4️⃣ JwtTokenProvider.createToken(email, role) → JWT 발급
    ↓
5️⃣ JWT를 &quot;token&quot;이라는 이름의 쿠키로 생성 → 응답에 추가
    ↓
6️⃣ 클라이언트는 이후 모든 요청에서 이 쿠키를 자동 포함
    ↓
7️⃣ JwtAuthenticationFilter에서 요청마다 token 쿠키 확인
    ↓
8️⃣ JwtTokenProvider.validateToken() → 유효성 검사
    ↓
9️⃣ SecurityContext에 인증 객체 설정 → 로그인된 상태 유지</code></pre>
<p>이것을 또 간략하게 설명해줬는데 ...</p>
<ol>
<li>[로그인 요청]  → /oauth2/authorization/google</li>
<li>[로그인 성공]  → OAuth2SuccessHandler → JWT 발급</li>
<li>[응답 전송]    → token 쿠키 포함 + 리다이렉트</li>
<li>[이후 요청들] → JwtAuthenticationFilter → 인증 유지</li>
</ol>
<p>이런식으로 흘러간다는 것을 알았다!!</p>
<hr>
<h3 id="결과-">결과 ....</h3>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/dad6daac-8319-4de5-9fe9-ea930ddf0c23/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/1d5c8791-d3a3-4c40-9ddd-0c276e8fb117/image.png" alt=""></p>
<h4 id="안댐">안댐</h4>
<p>왜 안될까?? 
<img src="https://velog.velcdn.com/images/jung_ji_in02/post/4804f49d-7b32-4792-9e94-15524b2c5eaa/image.png" alt=""></p>
<p>먼저 디버깅 오류를 분석해봤더니 </p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/d0cc1f5c-f8a4-4c1c-b272-4b7747790d1b/image.png" alt=""></p>
<p>아 !! 내가 test할 url을 넣지 않았구나 하며 급하게 저 코드를 넣어서 다시 돌려봤다!
<img src="https://velog.velcdn.com/images/jung_ji_in02/post/e06d7962-3372-491a-9fcf-661f78241f2e/image.png" alt=""></p>
<p>?</p>
<p>하하 근데 사실 저 url은 필요 없었다 !!</p>
<p>에초에 나는 프론트 서버를 돌리고 있지 않는데 어떻게 프론트 서버에서 테스트를 해보겠나 .....</p>
<p>바로 url을</p>
<p>board/list로 바로 게시글 목록으로 넘어가게 바꿔주었다 !
<img src="https://velog.velcdn.com/images/jung_ji_in02/post/7c20cf10-1ab6-46d4-95f4-facb753a4c94/image.png" alt=""></p>
<p>그러니 잘 넘어갔다 !!</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/833ee015-af6a-45f0-be5b-f8b874a16796/image.png" alt=""></p>
<p>유저 메인 페이지에서도 로그인이 유지되어있는 걸 확인할 수 있었다!</p>
<hr>
<h3 id="산-넘어-산">산 넘어 산</h3>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/eb94a9b3-f51e-436f-a4ea-cde23c47c4df/image.png" alt=""></p>
<p>이젠 또 다른 오류가 날 괴롭혔다 !!</p>
<p>이젠 로그아웃을 하고 index 페이지로 넘어가는데 .... 어라라 왜 로그인 페이지가 없지 ??</p>
<p>#
#
내가 관리자 계정을 생성하면서 인덱스 페이지에 로그인이 되어있으면 로그인이 안 뜨게 html을 설정해놨었다.</p>
<pre><code>@GetMapping(&quot;/user/logout&quot;)
    public String logout(HttpServletResponse response) {

        Cookie cookie = new Cookie(&quot;token&quot;, null);
        cookie.setHttpOnly(true);
        cookie.setPath(&quot;/&quot;);
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        return &quot;redirect:/user/login&quot;;
    }
</code></pre><p>하지만 내가 작성한 코드를 보면 로그아웃을 하면 쿠키를 즉시 만료 시키고 다시 로그인 페이지로 리다이렉트 해줬지만, 어떤 이유에서 인지 토큰은 지워졌는데 계속 로그인이 되어있는 상태였다.</p>
<h1 id=""></h1>
<p>원인을 찾아보니 Security에서 계속 인증 완료인 상태라 로그아웃을 해서 토큰을 없애도 계속 로그인 상태가 유지되고 있었다 !!</p>
<p>그래서 </p>
<pre><code>SecurityContextHolder.clearContext();</code></pre><p>이 코드를 추가 해주어 Security 인증을 초기화 시켜주어 제대로 로그아웃을 시켜주었다 !!</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/e2fbb8c2-6e77-4217-882f-396be8cb68ae/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/4fd590b2-b801-4ac9-a202-b05a9c082a70/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/27831619-0575-4fdb-bbca-f754b193877b/image.png" alt=""></p>
<p>이젠 그럴싸 하게 잘 돌아간다 ... </p>
<hr>
<h3 id="느낀-점-">느낀 점 !!!</h3>
<p>첫 번째 주는 사실 아이디어톤 준비 때문에 기존 RequestParam 형식에서 Jwt 토큰 형식으로 바꾸는 것 까지 밖에 제대로 하지 못했다. 그래도 동아리 회장님이 올려주신 코드들을 보면서 대충 어떤 구조로 구현해야하고 어떤 메서드를 만들어야 하는지 알 수 있어서 좋았다!! </p>
<p>그리고 JWT 토큰으로 바꾸면서 계속 전에 스터디했을 때 희원님이 설명해주신 기억이 계속 나면서 그래도 어떤 방식으로 JWT가 흘러가는 지는 이해가 잘 됐다. 이게 스터디의 기능일까??</p>
<p>전에 해놨던 방식이 html에서도 직접적으로 email을 받고 있던 부분이 많아서 거의 모든 코드들을 고쳐줘야했어서 엄청 복잡하고 힘들었다. 그래도 모르는 부분은 GPT나 우리 스터디분들 Velog를 참고하면서 잘 리팩토링 해나갔다.</p>
<p>소셜 로그인을 처음 들어봐서 사실 엄청 걱정했다 ... 하지만 jwt 토큰을 이용하면 정말 금방 할 수 있었다 !! 하지만 나의 jwt 토큰 생성기나, Security 코드가 완전하지 않아서 많은 오류들과 싸웠지만 정말 잘 구현되어있는 코드였다면 잘 넘어갈 수 있을 것 같았다! </p>
<p>그래도 완성된 결과를 보니 정말 뿌듯하고 멋있었다... 별거 아니지만 내가 이런거 까지 할 수 있다니 하면서 혼자 뿌듯해했다 ...</p>
<p>같은 동아리 분 중에 카카오톡으로도 구글과 똑같이 API 생성해서 소셜 로그인을 할 수 있다고 들어서 다음에는 카카오톡으로도 로그인할 수 있게 만들어보고 싶다!!</p>
<p>그리고 아직은 local에서만 돌릴 수 있어서 다음엔 배포까지 할 수 있으면 좋겠다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS를 활용한 EC2 - Spring 배포 해보기 2]]></title>
            <link>https://velog.io/@jung_ji_in02/AWS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-EC2-Spring-%EB%B0%B0%ED%8F%AC-%ED%95%B4%EB%B3%B4%EA%B8%B0-2</link>
            <guid>https://velog.io/@jung_ji_in02/AWS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-EC2-Spring-%EB%B0%B0%ED%8F%AC-%ED%95%B4%EB%B3%B4%EA%B8%B0-2</guid>
            <pubDate>Sun, 18 May 2025 04:54:31 GMT</pubDate>
            <description><![CDATA[<p>오늘은 저번에 실패했던 aws를 통한 ec2 배포 해보기를 마무리 해보려한다.</p>
<hr>
<pre><code class="language-."> /\\ / ___&#39;_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | &#39;_ | &#39;_| | &#39;_ \/ _ | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  &#39;  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.3)


2025-05-12T14:57:20.916Z  WARN 1 --- [board] [           main] o.m.jdbc.message.server.ErrorPacket      : Error: 1045-28000: Access denied for user &#39;root&#39;@&#39;172.19.0.2&#39; (using password: YES)
2025-05-12T14:57:21.920Z  WARN 1 --- [board] [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1045, SQLState: 28000
2025-05-12T14:57:21.921Z ERROR 1 --- [board] [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : (conn=15) Access denied for user &#39;root&#39;@&#39;172.19.0.2&#39; (using password: YES)
2025-05-12T14:57:21.922Z  WARN 1 --- [board] [           main] o.h.e.j.e.i.JdbcEnvironmentInitiator     : HHH000342: Could not obtain connection to query metadata

org.hibernate.exception.GenericJDBCException: unable to obtain isolated JDBC connection [(conn=15) Access denied for user &#39;root&#39;@&#39;172.19.0.2&#39; (using password: YES)] [n/a]
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:63) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:108) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]    
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:94) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]     
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:116) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:320) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:129) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:81) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]
        at org.hibernate.boot.model.relational.Database.&lt;init&gt;(Database.java:45) ~[hibernate-core-6.6.8.Final.jar!/:6.6.8.Final]</code></pre>
<p>저번에 해결하지 못했던 오류를 다시 가져와봤다.</p>
<p>사실 그 다음에도 이 문제가 계속 해결이 되지않았다. </p>
<p>위의 오류를 간단하게 해석하자면 , Spring 앱이 MariaDB에 접속을 시도했지만 아이디와 비밀번호가 맞지 않아 거부 당하고 있다는 것이였다.</p>
<pre><code>docker-compose down  // 서버 정보 및 리소스 삭제

docker-compose up -d --build // 도커 파일에 있는 정보를 리소스</code></pre><p>를 하여 서버를 다시 띄어준 후에</p>
<pre><code>docker logs -f spring </code></pre><p>를 통해서 다시 spring에서 나오는 로그를 확인해줬지만 결국 문제는 해결되지 않았다.</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/a2a6475a-d640-4a2f-bc99-81be572bcb90/image.png" alt=""></p>
<p>그러다가 동아리 회장님께 도움을 청하던 날, 이미지는 만들어져있는데 왜 DockerDesktop에 이미지가 안 올라가 있었다.</p>
<p>이 부분이 뭔가 이상해서 잘 찾아보던 도중에 ...</p>
<p><code>&quot;너 혹시 이거 코끼리 안 눌렀니 ? &quot;</code></p>
<p>네???? ....... </p>
<p>나는 여태까지 코끼리를 돌려야하는 것도 몰랐다. </p>
<p>내가 dockerfile의 정보를 바꾸고 코끼리를 돌려줘야지 바꾼 정보가 저장이 되는거였다 ...</p>
<p>나는 당연하게도 한 번도 코끼리를 돌린 적이 없었다.</p>
<p>회장님께 3대 정도 맞고 바로 코끼리를 돌린 후에 </p>
<p>서버를 다시 돌려봤는데 !!!!</p>
<p>안됐어요...</p>
<p>뭐가 문제지 하고 처음부터 다시 시작하면서 도커 파일을 빌드하던 도중에 뭔가 이상한 점을 발견했다.</p>
<pre><code>docker build -t jiinjung/study .</code></pre><p>뭔가 이상한 점이 있었다.</p>
<p><code>&quot;어 너 왜 이름이 이러냐 근데&quot;</code></p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/f8110a9b-3781-4e08-a7d2-3bd27469accc/image.png" alt=""></p>
<p>뭔가가 지금 달랐다.</p>
<p>어?</p>
<p>왜 나 도커 허브 계정 이름이 jungjiin이지 ... </p>
<p>그렇다 그런 것이였다. 존재하지 않는 이미지에 계속 push하고 있었던 것!!</p>
<p>간단히 말하자면 나는 지금까지 없는 파일에 계속 이미지를 올리고 , 그 파일을 통해서 서버에 대한 정보를 계속 올렸던 것이였다 ....</p>
<p>그러니 잘못된 정보가 들어간 파일은 계속 남아있고 , 나는 존재하지도 않던 파일의 정보를 계속 고치고 있던 것이였다 ....</p>
<p>이름을 바꿔주고 다시 시작해봤더니 ....</p>
<p><img src="https://velog.velcdn.com/images/jung_ji_in02/post/a6f43727-2ffd-47d8-949f-006f629e39e0/image.png" alt=""></p>
<p>짜잔 !!</p>
<p>드디어 성공했다...</p>
<hr>
<p>사실 이건 나의 혼자 힘으로 해결하지 못 했다. 사실 더 많은 문제들이 있었고, 생각보다 더 오래 걸렸는데 그때 동아리 회장님과 서로 현타 오면서 풀었던 문제라 어떤 오류들이 있었고, 어떤 오류들을 해결했는 지 일일히 저장하진 못했었다!! 그러나 내가 가장 실수였다고 느낀건 초반에 아무 생각 없이 잘못된 정보를 입력했던게 문제였다. 
한 번 단추를 잘못 끼웠더니 그 뒤에 잘 끼웠던 단추들은 한 순간에 의미가 없어졌다.  그 순간에 많은 회의감이 들었던 적도 있었다. </p>
<p>이 문제를 3일정도 계속 붙잡고 있었더니 내가 꼭 풀고 싶다는 의지도 생겼지만 결국 다른 사람의 힘을 빌려서 문제를 해결하게 되었다. 뭔가 내가 진짜 열심히 고치려고 해봐도 안되는 문제가 있다는게 너무 나한텐 큰 벽을 만났던 것 같았다. 그래서 사실 나한테 많이 실망했다.</p>
<p>하지만 동아리 회장님이 이러면서 성장하고, 이렇게 고생해봐야 너의 기억에 제대로 박혀 다음에 같은 문제가 발생했을 때 무리 없이 잘 해결해 나갈 수 있을거라며 많은 위로를 해줬다. 그게 허탈감을 느끼던 나한텐 많은 힘과 도움이 됐던 것 같다. </p>
<p>나는 아직은 많이 부족하다. 내가 노력해도 안되는 부분이 있다.
그래도 괜찮다 나는 아직 배우고있고 아직 성장할 수 있다. </p>
]]></description>
        </item>
    </channel>
</rss>