<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Codejuggler.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 25 Mar 2025 15:30:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Codejuggler.log</title>
            <url>https://velog.velcdn.com/images/codejugller-9999/profile/267f9655-b323-4b71-ad70-c9555b79dcc7/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Codejuggler.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/codejugller-9999" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[백준] 4195.친구 네트워크]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-4195.%EC%B9%9C%EA%B5%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-4195.%EC%B9%9C%EA%B5%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Tue, 25 Mar 2025 15:30:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.acmicpc.net/problem/4195">문제 링크</a> </p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>노드끼리 연결되어있는지를 확인하는 문제로 일반적으로 Union-find를 생각해볼 수 있다. 일반적인 Union-find문제와 다르다고 생각한 점은 크게 2가지가 있었다.</p>
<ul>
<li>일반 배열이 아닌 Map을 사용해서 부모의 정보를 저장(Map&lt;String, String&gt;)</li>
<li>연결된 노드의 개수를 확인하는 것</li>
</ul>
<h2 id="map을-사용한-정점-노드-확인실패">Map을 사용한 정점 노드 확인(실패)</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static Map&lt;String, String&gt; parent;
    static Map&lt;String, Integer&gt; nameCnt;
    static int cnt;

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

        StringTokenizer st;

        int T = Integer.parseInt(br.readLine());

        StringBuilder sb = new StringBuilder();
        while (T-- &gt; 0) {
            int F = Integer.parseInt(br.readLine());
            parent = new HashMap&lt;&gt;();
            nameCnt = new HashMap&lt;&gt;();
            for (int i = 0; i &lt; F; i++) {
                st = new StringTokenizer(br.readLine());
                String name1 = st.nextToken();
                String name2 = st.nextToken();

                if(!parent.containsKey(name1)){
                    parent.put(name1, name1);
                }
                if(!parent.containsKey(name2)){
                    parent.put(name2, name2);
                }

                union(name1, name2);
                sb.append(cnt).append(&quot;\n&quot;);
            }
        }
        System.out.println(sb);
        br.close();
    }
    static String find(String name){
        if(parent.get(name).equals(name)){
            return name;
        }
        return parent.put(name,find(parent.get(name)));
    }

    static void union(String name1, String name2){
        name1 = find(name1);
        name2 = find(name2);

        if(!name1.equals(name2)){
            if(!nameCnt.containsKey(name1) &amp;&amp; !nameCnt.containsKey(name2)){
                parent.put(name2, name1);
                nameCnt.put(name1, 2);
                cnt = 2;
            }else if(nameCnt.containsKey(name1) &amp;&amp; !nameCnt.containsKey(name2)){
                parent.put(name2, name1);
                cnt = nameCnt.get(name1) + 1;
                nameCnt.put(name1, cnt);
            }else if(!nameCnt.containsKey(name1) &amp;&amp; nameCnt.containsKey(name2)){
                parent.put(name1, name2);
                cnt = nameCnt.get(name2) + 1;
                nameCnt.put(name2, cnt);
            }else{
                parent.put(name2, name1);
                cnt = nameCnt.get(name1) + nameCnt.get(name2);
                nameCnt.put(name1, cnt);
            }
        }
    }
}
</code></pre>
<h3 id="map을-사용한-부모-정보-저장">Map을 사용한 부모 정보 저장</h3>
<pre><code class="language-java">for (int i = 0; i &lt; F; i++) {
    st = new StringTokenizer(br.readLine());
    String name1 = st.nextToken();
    String name2 = st.nextToken();

    if(!parent.containsKey(name1)){
        parent.put(name1, name1);
    }
    if(!parent.containsKey(name2)){
        parent.put(name2, name2);
    }
    ...
}</code></pre>
<ul>
<li>처음 등장한 친구의 이름이라면 자기 자신을 부모로 설정하여 <code>Map&lt;String, String&gt; parent</code> 저장한다.</li>
</ul>
<h3 id="find">find</h3>
<pre><code class="language-java">static String find(String name){
    if(parent.get(name).equals(name)){
        return name;
    }
    return parent.put(name,find(parent.get(name)));
}</code></pre>
<ul>
<li>재귀 호출을 통해 정점인 노드를 찾기 위한 함수로, return시에 값을 변경해주면서 경로를 압축해준다.</li>
</ul>
<h3 id="union">union</h3>
<pre><code class="language-java">static void union(String name1, String name2){
    name1 = find(name1);
    name2 = find(name2);

    if(!name1.equals(name2)){
        if(!nameCnt.containsKey(name1) &amp;&amp; !nameCnt.containsKey(name2)){
            parent.put(name2, name1);
            nameCnt.put(name1, 2);
            cnt = 2;
        }else if(nameCnt.containsKey(name1) &amp;&amp; !nameCnt.containsKey(name2)){
            parent.put(name2, name1);
            cnt = nameCnt.get(name1) + 1;
            nameCnt.put(name1, cnt);
        }else if(!nameCnt.containsKey(name1) &amp;&amp; nameCnt.containsKey(name2)){
            parent.put(name1, name2);
            cnt = nameCnt.get(name2) + 1;
            nameCnt.put(name2, cnt);
        }else{
            parent.put(name2, name1);
            cnt = nameCnt.get(name1) + nameCnt.get(name2);
            nameCnt.put(name1, cnt);
        }
    }
}</code></pre>
<ul>
<li><code>Map&lt;String, Integer&gt; nameCnt</code> 는 해당 정점이 몇개의 노드와 연결이 되어있는지 확인하는 것으로, 코드가 많지만 이유는 간단하다. 순서대로 다음과 같다.<ul>
<li>두 이름이 모두 처음 등장했을 때 → 왼쪽을 기준으로 부모 값을 설정</li>
<li>두 이름 중 하나가 현재 누군가로부터 정점일 때 →  해당 노드를 기준으로 부모를 설정</li>
<li>둘다 정점인 노드일때 → 왼쪽을 기준으로 두 그룹을 합쳐준다.</li>
</ul>
</li>
</ul>
<h3 id="결론">결론</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/28a12f4f-4f41-44c4-ab09-c043d0c72401/image.png" alt=""></p>
<ul>
<li>경로를 압축하는 <code>find</code> 함수에서 <code>put</code> 메소드의 오버헤드라고 판단하여 이를 단순히 부모 값을 리턴하는 코드를 작성했지만 이번에는 메모리 초과가 아닌 틀렸다는 결과를 볼 수 있었다.</li>
</ul>
<h2 id="정답-코드">정답 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static Map&lt;String, Integer&gt; cnt;
    static Map&lt;String, String&gt; parent;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int T = Integer.parseInt(br.readLine());

        for (int i = 0; i &lt; T; i++) {
            int num = Integer.parseInt(br.readLine());
            parent = new HashMap&lt;&gt;();
            cnt = new HashMap&lt;&gt;();
            for (int j = 0; j &lt; num; j++) {
                String[] name = br.readLine().split(&quot; &quot;);

                for (int z = 0; z &lt; 2; z++) {
                    if (!parent.containsKey(name[z])) {
                        parent.put(name[z], name[z]);
                        cnt.put(name[z], 1);
                    }
                }
                union(name[0], name[1]);

                bw.write((cnt.get(find(name[1]))) + &quot;\n&quot;);
            }
        }

        bw.flush();
        bw.close();
        br.close();

    }

    static String find(String x){
        if (!x.equals(parent.get(x))) {
            parent.replace(x, find(parent.get(x))); 
        }
        return parent.get(x);
    }
    static void union(String x, String y){
        x = find(x);
        y = find(y);

        if(!x.equals(y)){
            parent.replace(y, x);
            cnt.replace(x, cnt.get(x) + cnt.get(y));
        }
    }
}
</code></pre>
<h3 id="find-1">find</h3>
<pre><code class="language-java">static String find(String x){
    if (!x.equals(parent.get(x))) {
        parent.replace(x, find(parent.get(x))); 
    }
    return parent.get(x);
}</code></pre>
<p>크게 2가지를 변경했다.</p>
<ul>
<li><code>자기 자신이 정점이 아닐 때</code> 로 조건문을 수정해서 <code>parent.get(x)</code> 로 리턴하도록 수정</li>
<li><code>put</code>이 아닌 <code>replace</code> 를 사용</li>
</ul>
<h3 id="⭐-put-vs-replace">⭐ put vs replace</h3>
<p>이미 존재하는 key의 value값을 업데이트 할때,  <code>replace</code>를 사용하는 것이 당연할 수 있겠지만 왜 그런건지 내부 동작을 보면서 살펴봤다. </p>
<h4 id="put">put</h4>
<pre><code class="language-java">public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    if (table == null || table.length == 0) resize();

    int i = (n - 1) &amp; hash;

    if (table[i] != null) {
        ...
    }

    table[i] = newNode(hash, key, value, null);

    if (++size &gt; threshold) resize();

    return oldValue;
}

...

final Node&lt;K, V&gt;[] resize() {
      Node&lt;K, V&gt;[] oldTab = this.table;
      int oldCap = oldTab == null ? 0 : oldTab.length;
      int oldThr = this.threshold;
      int newThr = 0;
      int newCap;
        ...
}</code></pre>
<ul>
<li>resize를 통한 배열 크기 확인<ul>
<li>크기가 threshold를 넘었을 때, resize를 호출해서 배열의 크기를 늘리면서 모든 노드를 재배치 하는 과정이 발생</li>
</ul>
</li>
<li>중복 키라도 값비교 없이 항상 덮어 씌우기 때문에, 연산 비용이 증가 된다.</li>
</ul>
<h4 id="replace">replace</h4>
<pre><code class="language-java">public V replace(K key, V value) {
    Node e;
    if ((e = this.getNode(key)) != null) {
        V oldValue = e.value;
        e.value = value;
        this.afterNodeAccess(e);
        return oldValue;
    } else {
        return null;
    }
}</code></pre>
<ul>
<li>반면에, <code>replace</code> 는 탐색을 수행하고 새로운 값으로 변경하기 때문에 메모리 관점에서 훨씬 유리하게 작용하는 것이다.</li>
</ul>
<h3 id="union-1">union</h3>
<pre><code class="language-java">static void union(String x, String y){
    x = find(x);
    y = find(y);

    if(!x.equals(y)){
        parent.replace(y, x);
        cnt.replace(x, cnt.get(x) + cnt.get(y));
    }
}</code></pre>
<ul>
<li>정점을 따로 구분하지 않고 <code>Map&lt;String, Integer&gt; cnt</code> 에 x 값을 기준으로 cnt를 더해서 저장한다.</li>
<li>여기서 x 값은 인풋값을 기준으로 왼쪽에 있는 노드를 무조건 부모로 사용한 것인데, 이럴 경우 모두 연결되었을 때 한명의 부모 값을 가지지 못하는 상황이 발생할 수 있다.</li>
</ul>
<pre><code class="language-java">1
2
Fred Barney
Betty Fred

{Barney=Fred, Betty=Betty, Fred=Betty}</code></pre>
<ul>
<li><strong>하지만 결국 연결된 노드들의 개수만 파악하면 되기 때문에 이전처럼 관계를 복잡하게 코드로 구현할 필요가 없었다.</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] Socket == File]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-Socket</link>
            <guid>https://velog.io/@codejugller-9999/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-Socket</guid>
            <pubDate>Sun, 05 Jan 2025 07:44:42 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가기-전">들어가기 전</h1>
<p>소켓(Socket), 웹소켓(WebSocket)은 정확히 어떤 것이고, 어떤 차이를 가지고 있는가?</p>
<p>결론부터 말하면 다음과 같다.</p>
<ul>
<li><strong>소켓은 파일</strong>이며, OS 커널에 구현되어 있는 추상화된 인터페이스</li>
<li>웹소켓은 소켓을 기반으로하는 양방향 통신 프로토콜</li>
</ul>
<p>차이점을 얘기하기에는 본질적으로 다른 개념이라는 것을 알게 되었고, 이 소켓(Socket)에 대해 더 자세히 알아보고자 한다.</p>
<h1 id="소켓socket이란">소켓(Socket)이란?</h1>
<h2 id="소켓은-파일이다-파일의-입출력io-관점에서-소켓-이해하기">소켓은 파일이다: 파일의 입출력(I/O) 관점에서 소켓 이해하기</h2>
<p>위에서 언급한 것과 같이 소켓은 파일이며, OS 커널에 구현되어 있는 추상화된 인터페이스이다.</p>
<h4 id="os-커널">OS 커널?</h4>
<p>운영체제의 핵심 부분으로, <strong>하드웨어와 소프트웨어 사이에서 중재 역할</strong>을 하는 프로그램으로 <strong>복잡한 하드웨어 동작을 간단한 함수 호출</strong>로 사용할 수 있도록 제공되는 <strong>표준화된 인터페이스</strong></p>
<p>즉, 응용 프로그램을 수행하는데 필요한 여러가지 서비스(중에 소켓이 존재), 컴퓨터의 자원(CPU, 메모리)를 관리하는 역할이다.</p>
<p> <strong>유저 모드 (User Mode)</strong></p>
<ul>
<li>애플리케이션이 동작하는 모드로, 커널에 직접 접근하지 않고, 커널이 제공하는 시스템 콜(system call)을 통해 작업 요청.</li>
<li>소켓 API(추상화된 인터페이스)를 사용해 네트워크 통신 요청을 전달</li>
</ul>
<blockquote>
<p>시스템 콜이란?</p>
<ul>
<li>유저 모드와 커널 모드 간의 인터페이스</li>
<li>유저 모드에서 커널에 서비스를 요청하는 방법이로, 파일 입출력, 메모리 관리 등 작업에 사용</li>
</ul>
</blockquote>
<p> <strong>커널 모드 (Kernel Mode)</strong></p>
<ul>
<li>운영 체제의 핵심적인 기능(TCP/IP 스택 처리, 파일 시스템 접근 등)을 수행.</li>
<li>TCP 프로토콜, IP 프로토콜의 동작을 직접 제어하고, 데이터를 처리함.</li>
<li>네트워크 계층에서 캡슐화:<ol>
<li>TCP 헤더 추가 → 데이터 세그먼트 생성.</li>
<li>IP 헤더 추가 → 패킷 생성.</li>
</ol>
</li>
</ul>
<blockquote>
<p><strong>추가 : OS 커널</strong></p>
<ul>
<li>추상화란 복잡한 시스템의 내부 구현을 감추고, 간단하고 이해하기 쉬운 인터페이스만 제공하는 것을 의미한다. </li>
<li><code>socket()</code> - 소켓 생성, <code>connect()</code> - 소켓(cli)연결, 3-way handshake 과정 수행 </li>
<li>위 함수와 같이 에플리케이션에서는 단순한 함수 호출하면, <strong>시스템 콜</strong>을 통해 커널 모드에서 작업이 처리된다.</li>
</ul>
</blockquote>
<h3 id="프로세스-파일에-대한-주체">프로세스: 파일에 대한 주체</h3>
<p>여기서 프로세스는 응용 프로그램 실행파일, 예를 들어 클라이언트-서버 모델에서 서버의 실행파일이 될 수 있는데 이러한 <strong>프로세스가 소켓(파일)에 대한 주체</strong>가 된다.</p>
<p>프로세스는 파일처럼 소켓을 열고(<code>open</code>) 닫으며(<code>close</code>), TCP/IP 프로토콜을 사용하는 TCP 소켓일 경우</p>
<ul>
<li><code>write</code> -&gt; <code>send</code> : 데이터를 네트워크로 보냄 (서버 자원을 네트워크를 통해 보냄)</li>
<li><code>read</code> -&gt; <code>receive</code> : 네트워크에서 데이터를 읽음(네트워크를 통해 들어온 데이터를 읽음)</li>
</ul>
<blockquote>
<p><strong>요약</strong></p>
<ul>
<li>소켓(Socket)은 애플리케이션의 <strong>프로세스가 네트워크를 사용하기 위한 통로</strong>이다.       </li>
<li>파일로써, <strong>데이터 입출력(I/O)과정을 통해 역할을 수행</strong>한다.</li>
</ul>
</blockquote>
<h4 id="q-모든-클라이언트가-80번-포트를-통해-연결되는가">Q: 모든 클라이언트가 80번 포트를 통해 연결되는가?</h4>
<ul>
<li>NO<ul>
<li>80번 포트는 <strong>초기 연결 시에만 사용</strong></li>
<li>클라이언트가 서버와 연결된 이후, 서버는 클라이언트와의 통신을 위해 <strong>새로운 동적 포트(49152~65535)</strong>를 할당</li>
<li>따라서 80번 포트는 <strong>&quot;들어오는 요청의 진입점&quot;</strong> 역할  </li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] DNS 서비스]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-DNS-%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@codejugller-9999/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-DNS-%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Sat, 28 Dec 2024 01:34:44 GMT</pubDate>
            <description><![CDATA[<h1 id="www">WWW</h1>
<blockquote>
<p><strong>WWW란?</strong></p>
<ul>
<li>World Wide Web은 인터넷에 연결된 사용자들이 서로의 정보를 공유할 수 있는 공간으로, 정확히 말해 인터넷 상의 인기 있는 하나의 서비스이다.</li>
<li>웹 서버는 HTTP 프로토콜을 사용하며 클라이언트(웹 브라우저)는 TCP 포트 번호 80번을 이용해 서버와 연결을 시도한다.</li>
</ul>
</blockquote>
<p><strong>Port</strong></p>
<ul>
<li>0 - 1023 : well -known port<ul>
<li>특정 서비스, 프로토콜이 표준화된 포트 번호</li>
<li>ex) 80 - HTTP, 443 - HTTPS</li>
</ul>
</li>
<li>1024 - 49151 : registered port<ul>
<li>특정 애플리케이션이나 서비스가 사용하기 위해 <em>IANA(Internet Assigned Numbers Authority)</em> 에 의해 등록된 포트를 말한다.</li>
<li>ex ) 8080 - 웹 프록시 서버, 3306 - MySQL 데이터베이스</li>
</ul>
</li>
<li>49152 - 65535 : dynamic port<ul>
<li>클라이언트가 서버와 통신을 위해 임시로 할당하는 포트를 말한다.</li>
</ul>
</li>
</ul>
<h2 id="클라이언트---서버-모델">클라이언트 - 서버 모델</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/69d49479-6c9d-4f4d-be63-24f0e29292ef/image.png" alt=""></p>
<h3 id="1-사용자가-웹-브라우저에서-서버-이름을-입력한다-get">1. 사용자가 웹 브라우저에서 서버 이름을 입력한다. <strong>(GET)</strong></h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/88e6676b-63f9-4a29-a464-d7eb6609c8eb/image.png" alt=""></p>
<h3 id="2-dns-서버를-통해-해당-도메인에-등록된-ip-주소를-응답받는다">2. DNS 서버를 통해 해당 도메인에 등록된 IP 주소를 응답받는다.</h3>
<p>이 과정에서는 UDP 통신을 사용한다.</p>
<h3 id="3-서버는-들어온-요청에-대한-정적-파일을-클라이언트에게-응답한다-post">3. 서버는 들어온 요청에 대한 정적 파일을 클라이언트에게 응답한다. <strong>(POST)</strong></h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/f96c75e5-c23b-481d-947f-de1aa07c1858/image.png" alt=""></p>
<ul>
<li>Remote Address를 보면 <code>www.korean.go.kr</code>의 IP 주소가 <code>124.137.201.220</code>인 것을 알 수 있다. 또한, port 번호를 <code>443</code>을 사용하기 때문에 HTTPS 프로토콜이라는 것을 알 수 있다.</li>
</ul>
<p><img src ="https://velog.velcdn.com/images/codejugller-9999/post/f9928b36-193a-4f21-a2e0-f3c49a7a0c1f/image.png"
     width = "400"></p>
<ul>
<li>Response Headers에 <code>Content-Type</code> 을 보면 <code>text/html</code> 것을 확인할 수 있다.</li>
</ul>
<h3 id="실제로-응답된-html">실제로 응답된 html</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/5068d5d3-13ef-40b8-9903-2c2aaf445e63/image.png" alt=""></p>
<hr>
<h2 id="http-프로토콜">HTTP 프로토콜</h2>
<blockquote>
<p>HTTP란?</p>
<ul>
<li>HyperText Transfer Protocol으로 분산 하이퍼미디어 환경에서 빠르고 간편하게 데이터를 전송하는 프로토콜.</li>
<li>서버는 80번 포트에서 대기하고, 클라이언트는 TCP를 사용해 연결을 설정</li>
</ul>
</blockquote>
<h3 id="state-vs-stateless">State vs Stateless</h3>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>상태(State)</strong></th>
<th><strong>비상태(Stateless)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>상태 유지</strong></td>
<td>이전 상태를 저장하고 다음 요청에 반영</td>
<td>각 요청이 독립적이며 상태를 저장하지 않음</td>
</tr>
<tr>
<td><strong>메모리 사용</strong></td>
<td>세션, 상태 정보 저장을 위해 리소스 필요</td>
<td>상태를 저장하지 않아 리소스 소모가 적음</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td>확장하기 어려움</td>
<td>확장성이 뛰어남</td>
</tr>
<tr>
<td><strong>예시</strong></td>
<td>로그인 세션, 장바구니</td>
<td>HTTP, REST API</td>
</tr>
</tbody></table>
<h3 id="q-상태가-있는-시스템이-확장이-어려운-이유">Q. 상태가 있는 시스템이 확장이 어려운 이유?</h3>
<ul>
<li><strong>서버간 세션 동기화</strong><ul>
<li>A에서 저장된 세션 정보를 B서버에서도 사용할 수 있어야한다.</li>
<li>세션 데이터를 동기화하려면 네트워크 비용, 지연이 발생하며 서버 수가 늘어날 수록 동기화가 어려워지고 성능 저하가 발생한다.</li>
</ul>
</li>
</ul>
<h3 id="q-jwt-기반-인증-방식은-state일까-stateless일까">Q. JWT 기반 인증 방식은 State일까 Stateless일까?</h3>
<ul>
<li>Stateless이다. 표를 보면 다음과 같이 구분할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>Stateful</strong></th>
<th><strong>Stateless</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장 위치</strong></td>
<td>서버 세션 저장소</td>
<td>클라이언트 쿠키에 저장 (JWT)</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td>확장성 낮음 (세션 동기화 필요)</td>
<td>확장성 높음</td>
</tr>
<tr>
<td><strong>상태 유지</strong></td>
<td>서버에서 사용자 상태 유지</td>
<td>서버에서 사용자 상태 유지하지 않음</td>
</tr>
<tr>
<td><strong>속도 및 성능</strong></td>
<td>세션 조회로 인한 오버헤드</td>
<td>토큰 검증만으로 빠르게 처리</td>
</tr>
<tr>
<td><strong>보안</strong></td>
<td>세션 ID 유출 방지 필요</td>
<td>토큰 유출 시 보안 위험 (만료 설정 필수)</td>
</tr>
<tr>
<td>- 즉, 서버 세션 저장소에 상태를 저장하는 state와 달리 JWT는 클라이언트에서 토큰 등으로 사용자 정보를 관리하는 stateless 방식이다. 그러므로 서버 세션 저장소를 동기화해야하는 state와 달리 JWT 인증 방식은 확장성이 높다. 대신, 토큰의 유출 시 보안 문제를 고려해 만료 설정 및 리프래시 토큰을 통해 보안을 고려해야한다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="요청-메세지와-응답-메세지">요청 메세지와 응답 메세지</h3>
<table>
<thead>
<tr>
<th><strong>명령</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td>클라이언트가 서버에 URL이 가리키는 웹 문서의 내용을 전송하도록 요청한다. 문서 내용은 서버가 회신하는 응답 메시지의 바디에 포함된다.</td>
</tr>
<tr>
<td><strong>HEAD</strong></td>
<td>문서 내용보다는 특정 문서의 정보를 원할 때 사용한다.</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td>클라이언트가 서버에 정보를 전송할 수 있도록 해준다. 보통 게시판, 방명록처럼 사용자가 입력한 정보를 서버에게 전달하는 용도로 사용한다.</td>
</tr>
<tr>
<td><strong>PUT</strong></td>
<td>클라이언트가 서버에 문서를 전달하려고 사용한다. 문서 내용은 바디에 포함된다.</td>
</tr>
<tr>
<td>추가로, DELETE 등의 요청 명령도 있다.</td>
<td></td>
</tr>
</tbody></table>
<blockquote>
<p>멱등성이란?</p>
<ul>
<li>동일한 작업을 여러 번 수행해도 그 결과가 처음 한번 수행 했을 때와 동일한 상태를 유지하는 성질</li>
<li><strong>GET, PUT, DELETE, HEAD</strong></li>
<li>반면에 POST는 멱등성을 가지지 않는다.</li>
</ul>
</blockquote>
<h3 id="get-요청-후-응답-예시">GET 요청 후 응답 예시</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/80ce8262-10df-4a63-9b18-fc089bdf7c6e/image.png" alt=""></p>
<hr>
<h1 id="dns">DNS</h1>
<h2 id="ip-주소-클래스">IP 주소 클래스</h2>
<p><strong>네트워크 주소</strong>는 장치들을 그룹화하고, <strong>호스트 주소</strong>는 해당 그룹 내에서 개별 장치를 식별한다.</p>
<table>
<thead>
<tr>
<th><strong>클래스</strong></th>
<th><strong>IP 주소 범위</strong></th>
<th><strong>네트워크 주소</strong></th>
<th><strong>호스트 주소</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>A</strong></td>
<td>0 ~ 127</td>
<td>www</td>
<td>xxx.yyy.zzz</td>
</tr>
<tr>
<td><strong>B</strong></td>
<td>128 ~ 191</td>
<td><a href="http://www.xxx/">www.xxx</a></td>
<td>yyy.zzz</td>
</tr>
<tr>
<td><strong>C</strong></td>
<td>192 ~ 223</td>
<td><a href="http://www.xxx.yyy">www.xxx.yyy</a></td>
<td>zzz</td>
</tr>
<tr>
<td>- A클래스가 가장 많은 호스트를 가질 수 있고 주로 대규모 네트워크(글로벌 기업 등)에 사용된다.</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>- C클래스는 가장 적은 호스트를 가질 수 있고 주로 소규모 네트워크(가정용 네트워크, 소규모 회사)에서 사용된다.</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="q-유니캐스팅-vs-멀티캐스팅">Q. 유니캐스팅 vs 멀티캐스팅</h3>
<p><strong>유니캐스팅</strong>은 1대1 통신 방식으로 단일 수신자를 대상으로 데이터 전송하기 때문에 특정 IP 주소를 기반으로 특정 host에게만 데이터를 전달한다.</p>
<p><strong>멀티캐스팅</strong>은 1대다 통신 방식으로 특정 그룹에 속한 수신자에게 동시에 데이터를 전송한다. 그래서 전부 네트워크 주소로 이루어진 D클래스가 해당 통신방식을 지원한다.</p>
<p><strong>추가</strong>
A,B,C 클래스는 단일 호스트 통신에 최적화 되어있는 경우로, 네트워크와 호스트를 명확하게 구분하기 때문에 멀티캐스팅을 지원하기에는 부적합하다.</p>
<h2 id="dns가-필요한-이유">DNS가 필요한 이유?</h2>
<blockquote>
<p>IP주소의 문자 표현</p>
<ul>
<li>IPv4에서 32bit의 주소를 8비트 씩 10진수로 바꿔 표현한다. 하지만 이러한 주소 표현도 <strong>사용자</strong> 입장에서 특정 호스트 서버에 접근하는데 어려움이 있기 때문에 문자로 된 호스트 이름을 추가로 정의했다.</li>
</ul>
</blockquote>
<blockquote>
<p>초기 DNS 관리 방법</p>
<ul>
<li>문자로된 호스트 이름을 추가로 정의했기 때문에 특정 IP와 매칭시킨 정보가 필요.</li>
<li>컴퓨터 시스템이 전체 호스트의 명칭 정보를 관리했지만 호스트가 증가하면서 모든 호스트의 정보를 유지하기가 불가능해졌다.</li>
<li>이로 인해, <strong>DNS 서비스</strong>가 등장했다.</li>
</ul>
</blockquote>
<h2 id="dns-서비스의-동작과정">DNS 서비스의 동작과정</h2>
<blockquote>
<p>DNS 서비스란?</p>
<ul>
<li>도메인 이름에서 IP주소를 얻을 수 있는 <strong>분산 데이터베이스 시스템</strong>.</li>
<li>해석기는 UDP를 이용해 자신이 위치한 지역의 DNS 네임 서버에 변환을 요청하여 IP주소를 얻을 수 있다.</li>
</ul>
</blockquote>
<h3 id="dns-서비스의-계층-구조---네임스페이스">DNS 서비스의 계층 구조 - 네임스페이스</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/c9726f94-91fe-40a3-beee-8d1015a9f11c/image.png" alt=""></p>
<ul>
<li>도메인 이름을 체계적으로 관리하고 구분하는 계층적 구조를 말한다.</li>
<li>최상위에 이름이 없는 Root가 존재하고, Root 바로 밑에 위치한 호스트인 <strong>TLD(Top Level Domain)</strong> 이 있다.<ul>
<li>보통, <strong>TLD</strong>는 com, net 처럼 기구의 이름이나 kr, us와 같이 일반 도메인과 별도로 국가 코드를 정의한다.</li>
</ul>
</li>
</ul>
<h3 id="spring-boot에서-엿보는-dns">Spring boot에서 엿보는 DNS</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/41ab2a0a-0b63-47b1-a322-8be6dc308b8a/image.png" alt=""></p>
<p>Spring boot 프로젝트를 생성 시, 볼 수 있는 인터페이스인데 이와 같이 Group, Package name으로 네임스페이스와 같은 계층 구조로 프로젝트를 생성하는 것을 알 수 있다.</p>
<h3 id="동작과정">동작과정</h3>
<p>동작과정을 설명하기 전에 네임 서버와 DNS 서비스가 분산된 데이터베이스 시스템이라는 것을 알고 있어야한다.
<img src="https://velog.velcdn.com/images/codejugller-9999/post/1c556c4e-bca5-4c6a-8b4c-26fa9a084ae8/image.png" alt=""></p>
<h3 id="네임-서버와-존">네임 서버와 존</h3>
<p>위 사진과 같이 네임 서버 즉, 실제 도메인 이름과 IP 주소 간의 매핑 정보를 제공하는 곳은 존에 따라 분류된다.</p>
<h3 id="네임-서버의-종류">네임 서버의 종류</h3>
<ol>
<li>루트 네임 서버<ul>
<li>도메인 네임스페이스의 최상위 레벨을 관리한다.</li>
<li>ex) <code>.com</code>, <code>.org</code>, <code>.net</code>과 같은 <strong>TLD에 대한 정보를 제공한다.</strong></li>
</ul>
</li>
<li>TLD 네임 서버<ul>
<li>각 최상위 도메인(TLD)에 대한 정보를 관리한다.</li>
<li>ex) <code>.com</code>에 속한 모든 도메인의 정보를 관리한다.</li>
</ul>
</li>
<li>권한 네임 서버<ul>
<li>특정 도메인에 대한 최종 정보를 제공한다.</li>
<li>ex) <code>example.com</code>의 IP 주소에 대한 정확한 매핑 정보를 제공한다.</li>
</ul>
</li>
<li>캐시 네임 서버<ul>
<li>자주 요청된 DNS 정보를 <strong>캐싱</strong>하여 저장하고, 빠르게 응답한다.</li>
<li>ex) 사용자가 이전에 방문한 사이트의 DNS 정보를 임시로 저장한다.</li>
</ul>
</li>
</ol>
<h3 id="동작과정-1-비재귀적-처리">동작과정 1. (비)재귀적 처리</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/86af62a0-fe49-4e2d-9c00-108ade789d88/image.png" alt=""></p>
<ol>
<li>해석기에서 가장 가까운 네임 서버(로컬 네임 서버)와 접촉해 IP 주소를 요청하고 다른 네임서버에 요청을 재귀적으로 수행하면서 IP주소를 응답받는다.<ul>
<li><strong>해석기</strong> : 도메인 이름과 호스트 주소의 변환 정보를 원하는 네트워크 응용 프로그램</li>
</ul>
</li>
<li>로컬 네임 서버가 보관하고 있는 캐시 정보를 회신함으로써 재귀적인 호출 없이 IP주소를 응답받을 수 있다.</li>
</ol>
<ul>
<li>만약에 내가 <code>www.naver.com</code> 의 대한 IP주소를 원한다면 com을 관리하는 네임서버(존으로 분류)에 요청을 하고 해당 존은 naver를 관리하는 네임 서버에 요청을 반복하면서 최종적으로 <code>www.naver.com</code>의 IP주소를 응답 받을 수 있는 것이다.</li>
</ul>
<h3 id="동작과정-2-반복적-처리">동작과정 2. 반복적 처리</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/cbccdfb5-69d6-4329-94de-6dbebc607549/image.png" alt=""></p>
<p>재귀적인 처리 방법과는 다르게 로컬 네임 서버가 반복적으로 직접 여러 네임서버에 접촉해서 부족한 정보를 모두 채워나가 IP주소를 응답하는 방식이다.</p>
<h3 id="q-서버가-도메인-주소로-ip주소를-얻는-과정에-대해서-설명하시오">Q. 서버가 도메인 주소로 IP주소를 얻는 과정에 대해서 설명하시오.</h3>
<p>DNS 서비스를 이용해 IP 주소를 얻을 수 있다. 클라이언트가 웹 브라우저에서 도메인 주소로 요청을 보내면 DNS 서버에 해석기가 요청을 보내 재귀적 처리 혹은 반복적 처리를 통해 IP 주소를 응답 받을 수 있다.</p>
<p>추가
일반적으로 재귀적 처리는 웹사이트 접근 시 사용된다.</p>
<ul>
<li>클라이언트가 단순히 요청만 하기 때문에 간편하다</li>
<li>DNS 해석기가 주간 결과를 캐시해서 재사용이 가능하다.</li>
</ul>
<p>반면에 반복적 처리는 네트워크 디버깅, DNS 테스트에서 사용된다.</p>
<ul>
<li>각 네임 서버가 자신의 역할만 수행하기 때문에 특정 서버에 과도한 부하가 걸리지 않는다.</li>
</ul>
<p>Q. 그럼 재귀적 호출은 각 네임 서버가 자신의 역할만 수행하는게 아닌건가??
=&gt; 재귀적 호출은 최종 IP주소를 찾을 때까지 모든 과정을 처리하는데, 자신이 요청받은 도메인의 IP주소를 모를 경우 자신 영역 외의 요청도 처리하며, 답을 찾아 클라이언트에게 전달하기 때문이다.</p>
<blockquote>
<p><strong>출처: 캡쳐된 사진을 출처는 &quot;쉽게 배우는 데이터 통신과 컴퓨터 네트워크 - 박기현&quot;에서 인용한 자료입니다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 자바에서 객체 참조는 어떻게 되는가]]></title>
            <link>https://velog.io/@codejugller-9999/Java-%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4-%EC%B0%B8%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%90%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@codejugller-9999/Java-%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4-%EC%B0%B8%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%90%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Thu, 12 Dec 2024 07:11:51 GMT</pubDate>
            <description><![CDATA[<h2 id="상황-발생">상황 발생</h2>
<blockquote>
<p>코딩 테스트 문제를 풀다가 다음과 같은 코드를 작성했는데 내가 예상하지 못하는 결과가 나왔다.</p>
</blockquote>
<h3 id="코드">코드</h3>
<pre><code class="language-java">...

List&lt;Set&lt;Integer&gt;&gt; pCard = new ArrayList&lt;&gt;();  
for (int i = 0; i &lt; 3; i++) {  
    pCard.add(new HashSet&lt;&gt;());  
}

... // 대충 pCard.get(num).add 하는 코드


while(true){
    ...

    Set&lt;Integer&gt; temp = pCard.get(i);

    for (int j = 0; i + j * 3 &lt; N; j++) {  
        if (temp.contains(card[i + j * 3])) {  
            temp.remove(card[i + j * 3])
        }  
    }
    ...
}
</code></pre>
<p>코드를 보면 <code>Set&lt;Integer&gt;</code> 를 List로 여러개 만들고 확인되는 값이 있으면 지워서 중복으로 되는 경우를 제거할 수 있도록 했다. </p>
<p>하지만,  반복문을 돌면서 원래 pCard의 Set이 remove가 되어 제대로 결과를 출력하지 못하게 되는데..</p>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-java">Set&lt;Integer&gt; init = new HashSet&lt;&gt;();  
init.add(1);  
init.add(2);  
init.add(3);  

System.out.println(&quot;init = &quot; + init);  

Set&lt;Integer&gt; test = init;  
test.remove(3);  

System.out.println(&quot;init = &quot; + init);  


출력 결과
init = [1, 2, 3]
init = [1, 2]
</code></pre>
<p>결과를 보면 분명 test의 있는 3을 remove했는데 init에도 결과가 반영된 것을 볼 수 있다. 그렇다면 왜 그런걸까? </p>
<h2 id="객체-참조">객체 참조</h2>
<p>문제에 대한 답은 객체 참조에서 알 수 있다.
객체 참조에 대해서 설명하기 전에 용어에 대해서 짚고 넘어가자</p>
<h3 id="객체object">객체(Object)</h3>
<p>Heap 메모리에 저장되는 실제 데이터를 말한다. 예를 들어, <code>new String(&quot;Hello&quot;)</code> 와 같은 명령어는 Heap 메모리에 &quot;Hello&quot;라는 문자열 객체를 생성한다. </p>
<h3 id="참조reference">참조(Reference)</h3>
<p>객체가 Heap 메모리에 저장된 위치를 가리키는 변수를 말한다. 참조는 객체의 메모리 주소를 저장하지만, Java에서는 직접 주소를 다룰 수 없고 참조 변수를 통해서만 접근이 가능하다. </p>
<p><code>String greeting = new String(&quot;Hello&quot;);</code></p>
<p>즉, 다음과 같은 코드에서 greeting은 위치(주소), &quot;Hello&quot;는 Heap 메모리에 있는 데이터인 것이다.</p>
<h2 id="이유는">이유는?</h2>
<pre><code class="language-java">Set&lt;Integer&gt; init = new HashSet&lt;&gt;();  
init.add(1);  
init.add(2);  
init.add(3);  

System.out.println(&quot;init: &quot; + init);  
System.out.println(&quot;init hash: &quot; + System.identityHashCode(init));  

Set&lt;Integer&gt; test = init;  
System.out.println(&quot;test hash before modification: &quot; + System.identityHashCode(test));  

test.remove(3);  

System.out.println(&quot;init after modification: &quot; + init);  
System.out.println(&quot;test hash after modification: &quot; + System.identityHashCode(test));


출력 결과
init: [1, 2, 3]
init hash: 288665596
test hash before modification: 288665596
init after modification: [1, 2]
test hash after modification: 288665596</code></pre>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/12d11411-461d-480e-8f8d-e9ecf271679a/image.png" alt=""></p>
<p>객체가 참조하는 주소값을 출력해보면 다음과 같다. 288665596으로 test는 init과 같이 같은 주소를 참조하고 있는 것을 확인할 수 있다. </p>
<p>그래서 <strong>test를 수정하더라도 실제로 init이 가리키는 heap 메모리에 영역의 데이터를 수정하기 때문에 결과가 반영되는 것이다.</strong></p>
<blockquote>
<p><strong>identityHashCode?</strong>
객체 고유한 해시 코드를 반환하는 메서드로, 객체의 메모리 참조 기준으로 생성되는 hashCode이다.</p>
</blockquote>
<h3 id="new로-새로운-객체를-생성하고-할당하면">new로 새로운 객체를 생성하고 할당하면?</h3>
<pre><code class="language-java">...

Set&lt;Integer&gt; test = new HashSet&lt;&gt;();  
test = init;

...</code></pre>
<p>새로운 객체를 만들고 기존 데이터를 할당하면 어떻게 될까?</p>
<p>결론부터 말하면 <code>Variable &#39;test&#39; initializer &#39;new HashSet&lt;&gt;()&#39; is redundant</code> 다음과 같은 메세지를 출력한다. 대충 변수 test 초기화가 중복된다는 의미인데 자세한 이유는 다음과 같다. </p>
<ol>
<li><code>Set&lt;Integer&gt; test = new HashSet&lt;&gt;();</code><ul>
<li>test 라는 변수를 new HashSet&lt;&gt;()으로 초기화한다.</li>
<li>이 시점에서, test는 빈 HashSet을 가리킨다.
<img src="https://velog.velcdn.com/images/codejugller-9999/post/80a6eb28-7d8f-46f6-8b4b-9f00a44e5771/image.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="2">
<li><code>test = init;</code><ul>
<li>이후 test에 init을 할당한다.</li>
<li>결국, test가 가리키는 객체는 init으로 변경된다.
<img src="https://velog.velcdn.com/images/codejugller-9999/post/378b8613-79bc-48ae-a29a-0c30792f2956/image.png" alt=""></li>
</ul>
</li>
</ol>
<h2 id="결론">결론</h2>
<p>대학 1학년, 객체 지향 프로그래밍 수업에서 C++ 로 지겹도록 배웠던 개념인데 자바를 계속 사용하면서 기본적인 것을 잊고 있었던 것 같다.😢</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 17144 - 미세먼지 안녕!]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-17144-%EB%AF%B8%EC%84%B8%EB%A8%BC%EC%A7%80-%EC%95%88%EB%85%95</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-17144-%EB%AF%B8%EC%84%B8%EB%A8%BC%EC%A7%80-%EC%95%88%EB%85%95</guid>
            <pubDate>Sun, 20 Oct 2024 07:25:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.acmicpc.net/problem/17144">문제 링크</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/777720f7-82c2-4298-b553-3f6d291bf9de/image.png" alt=""></p>
<h2 id="문제-설명">문제 설명</h2>
<p>구현 문제로, 미세먼지를 확산시키는 과정은 일반적으로 그래프 탐색에서 사용하는 방법으로 코드를 구현하면 된다. 다만, 자신이 확산시키는 것과 인접한 칸에서 확산되어서 오는 값을 합할 때, 현재 누적값으로 확산하는 것이 아닌 원래 가지고 있던 값을 확신시켜야 한다. 이어서, 바람의 방향대로 한칸씩 값을 밀어야 하는데 바람이 처음 나오는 칸이 0이 되기 때문에 역순으로 반복해서 구현했다. </p>
<h2 id="문제-풀이정답">문제 풀이(정답)</h2>
<pre><code class="language-java">import java.util.*;  
import java.io.*;  

public class Main {  
    static int[][][] map;  
    static int[][] cleaner = new int[2][2];  

    static Queue&lt;Node&gt; dust = new LinkedList&lt;&gt;();  

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

        StringTokenizer st = new StringTokenizer(br.readLine());  

        int r = Integer.parseInt(st.nextToken());  
        int c = Integer.parseInt(st.nextToken());  
        int t = Integer.parseInt(st.nextToken());  

        map = new int[r][c][2];  

        int temp = 0;  
        for (int i = 0; i &lt; r; i++) {  
            st = new StringTokenizer(br.readLine());  
            for (int j = 0; j &lt; c; j++) {  
                map[i][j][0] = Integer.parseInt(st.nextToken());  
                if (map[i][j][0] == -1) {  
                    // 공기 청정기의 위치  
                    cleaner[temp][0] = i;  
                    cleaner[temp][1] = j;  
                    temp++;  
                } else if (map[i][j][0] != 0) {  
                    dust.add(new Node(i, j));  
                }  
            }  
        }  
        // 공기 청정기 위아래 확인  
        if (cleaner[0][1] &lt; cleaner[1][1]) {  
            temp = cleaner[0][1];  
            cleaner[0][1] = cleaner[1][1];  
            cleaner[1][1] = temp;  
        }  
        // n초 후 새로운 Q를 넣기전 먼지가 확산된 상태 list  

        int[] dr = {-1, 1, 0, 0};  
        int[] dc = {0, 0, -1, 1};  

        int result = 0;  

        for (int i = 0; i &lt; t; i++) {  
            Set&lt;Node&gt; check = new HashSet&lt;&gt;();  
            while (!dust.isEmpty()) {  
                Node current = dust.poll();  
                // 값이 업데이트(먼지) 되야하는 list                int divideValue = map[current.getR()][current.getC()][0] / 5;  
                int cnt = 0;  
                for (int j = 0; j &lt; 4; j++) {  
                    int nr = current.getR() + dr[j];  
                    int nc = current.getC() + dc[j];  

                    if (nr &gt;= 0 &amp;&amp; nr &lt; r &amp;&amp; nc &gt;= 0 &amp;&amp; nc &lt; c &amp;&amp; map[nr][nc][0] != -1) {  
                        map[nr][nc][1] += divideValue;  
                        check.add(new Node(nr, nc));  
                        cnt++;  
                    }  
                }  
                map[current.getR()][current.getC()][0] -= (divideValue * cnt);  
                if (map[current.getR()][current.getC()][0] &gt; 0) {  
                    check.add(new Node(current.getR(), current.getC()));  
                }  
            }  

            for (Node node : check) {  
                int nr = node.getR();  
                int nc = node.getC();  
                map[nr][nc][0] += map[nr][nc][1];  
                map[nr][nc][1] = 0;  
            }  


            // 공기청정기 작동 (상단 반시계방향)  
            int upper = cleaner[0][0];  
            for (int j = upper - 1; j &gt; 0; j--) {  
                map[j][0][0] = map[j - 1][0][0];  
            }  
            for (int j = 0; j &lt; c - 1; j++) {  
                map[0][j][0] = map[0][j + 1][0];  
            }  
            for (int j = 0; j &lt; upper; j++) {  
                map[j][c - 1][0] = map[j + 1][c - 1][0];  
            }  
            for (int j = c - 1; j &gt; 1; j--) {  
                map[upper][j][0] = map[upper][j - 1][0];  
            }  
            map[upper][1][0] = 0;  // 공기청정기에서 나오는 바람  

            // 공기청정기 작동 (하단 시계방향)  
            int lower = cleaner[1][0];  
            for (int j = lower + 1; j &lt; r - 1; j++) {  
                map[j][0][0] = map[j + 1][0][0];  
            }  
            for (int j = 0; j &lt; c - 1; j++) {  
                map[r - 1][j][0] = map[r - 1][j + 1][0];  
            }  
            for (int j = r - 1; j &gt; lower; j--) {  
                map[j][c - 1][0] = map[j - 1][c - 1][0];  
            }  
            for (int j = c - 1; j &gt; 1; j--) {  
                map[lower][j][0] = map[lower][j - 1][0];  
            }  
            map[lower][1][0] = 0;  // 공기청정기에서 나오는 바람  

            for (int j = 0; j &lt; r; j++) {  
                for (int k = 0; k &lt; c; k++) {  
                    if(map[j][k][0] &gt; 0){  
                        if(i == t-1){  
                            result += map[j][k][0];  
                        }else{  
                            dust.add(new Node(j, k));  
                        }  
                    }  
                }  
            }  
        }  

        System.out.println(result);  
        br.close();  
    }  


    static class Node {  
        int r;  
        int c;  

        public Node(int r, int c) {  
            this.r = r;  
            this.c = c;  
        }  

        int getR() {  
            return this.r;  
        }  

        int getC() {  
            return this.c;  
        }  

    }  

}</code></pre>
<h3 id="미세먼지-확산">미세먼지 확산</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/aa5b395c-7ab5-4e06-aaf3-45f558b5b83e/image.png" alt=""></p>
<pre><code class="language-java">...

map = new int[r][c][2];  

...

Set&lt;Node&gt; check = new HashSet&lt;&gt;();  
while (!dust.isEmpty()) {  
    Node current = dust.poll();  
    // 값이 업데이트(먼지) 되야하는 list                
    int divideValue = map[current.getR()][current.getC()][0] / 5;  
    int cnt = 0;  
    for (int j = 0; j &lt; 4; j++) {  
        int nr = current.getR() + dr[j];  
        int nc = current.getC() + dc[j];  

        if (nr &gt;= 0 &amp;&amp; nr &lt; r &amp;&amp; nc &gt;= 0 &amp;&amp; nc &lt; c &amp;&amp; map[nr][nc][0] != -1) {  
            map[nr][nc][1] += divideValue;  
            check.add(new Node(nr, nc));  
            cnt++;  
        }  
    }  
    map[current.getR()][current.getC()][0] -= (divideValue * cnt);  
    if (map[current.getR()][current.getC()][0] &gt; 0) {  
        check.add(new Node(current.getR(), current.getC()));  
    }  
}  

for (Node node : check) {  
    int nr = node.getR();  
    int nc = node.getC();  
    map[nr][nc][0] += map[nr][nc][1];  
    map[nr][nc][1] = 0;  
}  </code></pre>
<ul>
<li>입력받은 row, col의 크기와 한 차원을 추가해 3차원 배열로 선언했다. 이유는 다음과 같다.<ul>
<li><code>map[r][c][0]</code> 는 현재 미세먼지 값, 즉 해당 칸이 확산할 때 사용할 미세먼지의 값이다.</li>
<li><code>map[r][c][1]</code> 는 다른 인접한 칸으로 인해 확산된 값을 누적해서 더한 값이다.</li>
</ul>
</li>
<li>이전에 입력시, dust에 Node 객체로 미세먼지가 있는 위치를 add 해두었기 때문에 q가 빌때까지 반복한다.</li>
<li>4방향으로 미세먼지 확산 연산을 해준 이후, 미세먼지 값이 있는 칸을 <strong>hashSet</strong>에 넣어 <ul>
<li><code>map[r][c][1]</code> 확산을 통해 얻은 값과  <code>map[r][c][0]</code> 확산하고 남은 값을 더한다.</li>
</ul>
</li>
</ul>
<h3 id="공기청정기-동작">공기청정기 동작</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/04fc81dd-80d5-4c47-8ada-495562f968a2/image.png" alt=""></p>
<pre><code class="language-java">// 공기청정기 작동 (상단 반시계방향)  
int upper = cleaner[0][0];  
for (int j = upper - 1; j &gt; 0; j--) {  
    map[j][0][0] = map[j - 1][0][0];  
}  
for (int j = 0; j &lt; c - 1; j++) {  
    map[0][j][0] = map[0][j + 1][0];  
}  
for (int j = 0; j &lt; upper; j++) {  
    map[j][c - 1][0] = map[j + 1][c - 1][0];  
}  
for (int j = c - 1; j &gt; 1; j--) {  
    map[upper][j][0] = map[upper][j - 1][0];  
}  
map[upper][1][0] = 0;  // 공기청정기에서 나오는 바람  

// 공기청정기 작동 (하단 시계방향)  
int lower = cleaner[1][0];  
for (int j = lower + 1; j &lt; r - 1; j++) {  
    map[j][0][0] = map[j + 1][0][0];  
}  
for (int j = 0; j &lt; c - 1; j++) {  
    map[r - 1][j][0] = map[r - 1][j + 1][0];  
}  
for (int j = r - 1; j &gt; lower; j--) {  
    map[j][c - 1][0] = map[j - 1][c - 1][0];  
}  
for (int j = c - 1; j &gt; 1; j--) {  
    map[lower][j][0] = map[lower][j - 1][0];  
}  
map[lower][1][0] = 0;  // 공기청정기에서 나오는 바람</code></pre>
<ul>
<li>공기청정기가 나온 방향은 0이 되기 때문에 공기청정기로 들어오는 쪽부터 연산을 해서 한칸씩 값을 밀어서 미세먼지 값을 업데이트 한다.</li>
</ul>
<h3 id="결과-출력-및-다음-초-연산-준비">결과 출력 및 다음 초 연산 준비</h3>
<pre><code class="language-java">for (int j = 0; j &lt; r; j++) {  
    for (int k = 0; k &lt; c; k++) {  
        if(map[j][k][0] &gt; 0){  
            if(i == t-1){  
                result += map[j][k][0];  
            }else{  
                dust.add(new Node(j, k));  
            }  
        }  
    }  
}   

System.out.println(result);  </code></pre>
<ul>
<li>반복해서 현재 result에 값을 누적해서 저장한다. 현재 실행중인 로직이 마지막 연산일 때는 해당 연산을, 아닐 경우에는 다음 초에서 확인할 미세먼지의 위치를 node객체로 dust(queue)에 추가한다. </li>
</ul>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/5b9e899d-9a9d-4104-9ce1-42848a75539f/image.png" alt=""></p>
<p>정답은 맞았지만 다른 사람들의 제출 결과를 보니 메모리와 시간이 비교적 높은 것을 알 수 있었다. 그래서 다른 코드들을 참고해서 좀 더 개선된 코드를 만들어봤다. </p>
<h2 id="코드-개선">코드 개선</h2>
<pre><code class="language-java">import java.util.*;
import java.io.*;

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

        int r = Integer.parseInt(st.nextToken());
        int c = Integer.parseInt(st.nextToken());
        int t = Integer.parseInt(st.nextToken());

        int[][] map = new int[r][c];
        int[][] cleaner = new int[2][2];

        int temp = 0;
        for (int i = 0; i &lt; r; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; c; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
                if (map[i][j] == -1) {
                    cleaner[temp][0] = i;
                    cleaner[temp][1] = j;
                    temp++;
                }
            }
        }

        int[] dr = {-1, 1, 0, 0};
        int[] dc = {0, 0, -1, 1};


        while (t-- &gt; 0) {
            int[][] check = new int[r][c];

            // 확산 상태 채크
            for (int i = 0; i &lt; r; i++) {
                for (int j = 0; j &lt; c; j++) {
                    if (map[i][j] &gt; 0) {
                        check[i][j] = map[i][j];
                    }
                }
            }
            // 확산
            for (int i = 0; i &lt; r; i++) {
                for (int j = 0; j &lt; c; j++) {
                    if (check[i][j] &gt; 0) {
                        for (int k = 0; k &lt; 4; k++) {
                            int nr = i + dr[k];
                            int nc = j + dc[k];
                            if (nr &gt;= 0 &amp;&amp; nr &lt; r &amp;&amp; nc &gt;= 0 &amp;&amp; nc &lt; c &amp;&amp; map[nr][nc] != -1) {
                                map[nr][nc] = map[nr][nc] + check[i][j] / 5;
                                map[i][j] = map[i][j] - (check[i][j] / 5);
                            }
                        }
                    }
                }
            }
            int upper = cleaner[0][0]; // ex 2
            for (int i = upper - 1; i &gt; 0; i--) {
                map[i][0] = map[i - 1][0];
            }
            for (int i = 0; i &lt; c - 1; i++) {
                map[0][i] = map[0][i + 1];
            }
            for (int i = 0; i&lt; upper; i++){
                map[i][c-1] = map[i+1][c-1];
            }
            for(int i = c-1; i&gt;1; i--){
                map[upper][i] = map[upper][i-1];
            }
            map[upper][1] =0;

            int lower = cleaner[1][0];
            for (int j = lower + 1; j &lt; r - 1; j++) {
                map[j][0] = map[j + 1][0];
            }
            for (int j = 0; j &lt; c - 1; j++) {
                map[r - 1][j] = map[r - 1][j + 1];
            }
            for (int j = r - 1; j &gt; lower; j--) {
                map[j][c - 1] = map[j - 1][c - 1];
            }
            for (int j = c - 1; j &gt; 1; j--) {
                map[lower][j] = map[lower][j - 1];
            }
            map[lower][1] = 0;
        }
        int result = 0;

        for(int i=0; i&lt; r; i++){
            for(int j=0; j&lt; c; j++){
                if(map[i][j] != -1){
                    result += map[i][j];
                }
            }
        }
        System.out.println(result);

        br.close();
    }
}

</code></pre>
<ul>
<li>대부분의 연산 과정은 동일하지만 일단 Queue를 사용하지 않고 매번 반복문을 통해 미세먼지가 있는 부분을 찾아서 확산하도록 코드를 변경했다. </li>
</ul>
<pre><code class="language-java">if (nr &gt;= 0 &amp;&amp; nr &lt; r &amp;&amp; nc &gt;= 0 &amp;&amp; nc &lt; c &amp;&amp; map[nr][nc] != -1) {  
    map[nr][nc] = map[nr][nc] + check[i][j] / 5;  
    map[i][j] = map[i][j] - (check[i][j] / 5);  
}</code></pre>
<ul>
<li>추가로 3차원 배열을 사용하지 않고 매초마다 현재 미세먼지 값을 측정하는 2차원 배열을 만들어, 해당 배열을 사용해 미세먼지가 있는 위치 및 확산에 사용할 미세먼지의 값을 계산하는데 이용해 간결하게 코드를 수정했다. </li>
</ul>
<h2 id="결론">결론</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/28acb323-37ef-4a5e-be2e-ca3eaf94e988/image.png" alt=""></p>
<blockquote>
<p> <strong>공기청정기 작동시키는 동작에서 생각보다 오래 시간이 걸렸다. 코드를 개선해보며 결과적으로, 가장 먼저 작성했더 코드에 비해 
메모리는 약 1/7배 , 시간은 2000ms 넘게 감소시켰다. 맞은 것도 물론 중요하지만, 좀 더 효율적인 코드를 작성하는 습관을 익혀야겠다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 7785. 회사에 있는 사람]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-7785.-%ED%9A%8C%EC%82%AC%EC%97%90-%EC%9E%88%EB%8A%94-%EC%82%AC%EB%9E%8C</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-7785.-%ED%9A%8C%EC%82%AC%EC%97%90-%EC%9E%88%EB%8A%94-%EC%82%AC%EB%9E%8C</guid>
            <pubDate>Thu, 17 Oct 2024 05:39:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.acmicpc.net/problem/7785">문제 링크</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/0e7e4ea0-3ec1-4cf3-be59-19671bdc27c1/image.png" alt=""></p>
<h2 id="문제-설명">문제 설명</h2>
<p>해시를 사용하는 간단한 문제로 풀기 전 고민했던 부분은 3가지가 있었다. </p>
<ol>
<li>문자열을 분리해서 이름과 출입 여부에 관한 문자열로 나눠야한다.</li>
<li>Set을 통해 동일한 이름의 직원이 중복해서 들어가지 않도록하며, 출입 여부에 따라 삽입, 삭제 해야한다.</li>
<li>출력시, 알파벳 기준 내림차순으로 정렬해야한다. </li>
</ol>
<h2 id="treeset-사용한-풀이-정답">TreeSet 사용한 풀이 (정답)</h2>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.util.*;  
import java.io.*;  

public class Main {  

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

        Set&lt;String&gt; employees = new TreeSet&lt;&gt;(Collections.reverseOrder());  

        int n = Integer.parseInt(br.readLine());  

        for (int i = 0; i &lt; n; i++) {  
            String temp = br.readLine();  
            String[] employ = temp.split(&quot; &quot;);  

            if(employ[1].equals(&quot;enter&quot;)){  
                employees.add(employ[0]);  
            }else{  
                employees.remove(employ[0]);  
            }  
        }  

        for (String employee : employees) {  
            System.out.println(employee);  
        }  

    }  

}</code></pre>
<h3 id="treeset">TreeSet</h3>
<pre><code class="language-java">Set&lt;String&gt; employees = new TreeSet&lt;&gt;(Collections.reverseOrder());  </code></pre>
<ul>
<li>TreeSet은 기본적으로 오름차순으로 순서를 보장해서 저장하기 때문에 <code>Collections.reverseOrder()</code>를 사용하여 내림차순으로 저장되도록 한다. </li>
</ul>
<h3 id="문자열-분리">문자열 분리</h3>
<pre><code class="language-java">String[] employ = temp.split(&quot; &quot;);  

    if(employ[1].equals(&quot;enter&quot;)){  
        employees.add(employ[0]);  
    }else{  
        employees.remove(employ[0]);  
    }  </code></pre>
<ul>
<li>&quot;Baha enter&quot;, &quot;Baha leave&quot; 등 공백을 기준으로 이름과 출입 여부에 관한 문자열이 나뉜다. <ul>
<li>공백을 기준으로 문자열을 분리해 배열에 저장해서 출입 여부에 따라 add, remove를 결정한다.</li>
</ul>
</li>
</ul>
<h3 id="출력">출력</h3>
<pre><code class="language-java">// 방법 1
for (String employee : employees) {  
    System.out.println(employee);  
} 

// 방법 2
Iterator&lt;String&gt; iter = employees.iterator();  

while(iter.hasNext()){  
    System.out.println(iter.next());  
}</code></pre>
<ul>
<li><strong>방법 1</strong><ul>
<li>이미 순서를 보장한 TreeSet을 사용했기 때문에 요소를 처음부터 하나씩 출력한다.</li>
</ul>
</li>
<li><strong>방법 2</strong><ul>
<li><code>Iterator</code> 타입으로 변환 후에 <code>hasNext()</code> 즉, 다음 요소가 있을 때까지 반복하며 출력한다. </li>
</ul>
</li>
</ul>
<h2 id="결론">결론</h2>
<blockquote>
<p><strong>이전 문제에서 Set의 중요성에 대해서 좀 알 수 있었는데 TreeSet과 정렬 순서를 바꾸는 등, 이와 같은 것을 숙지해두고 실전 상황에서 바로 적용시킬 수 있도록 해야할 것 같다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] Lv.3 단어 변환]]></title>
            <link>https://velog.io/@codejugller-9999/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.3-%EB%8B%A8%EC%96%B4-%EB%B3%80%ED%99%98</link>
            <guid>https://velog.io/@codejugller-9999/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.3-%EB%8B%A8%EC%96%B4-%EB%B3%80%ED%99%98</guid>
            <pubDate>Thu, 17 Oct 2024 02:40:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43163">문제 링크</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>시작 단어를 주어진 값을 활용해서 target 단어로 변환하여 단어를 바꾼 횟수를 계산하는 문제이다. 추가로, 한 번에 한 개의 알파벳만 바꿀 수 있다는 조건이 있다. 해당 문제와 조건을 보고 다음과 같은 주요 로직을 생각해봤다.</p>
<ul>
<li>현재 단어와 바꿀 단어가 1개만 차이나는지 확인해야 한다.</li>
<li>주어진 단어 중에 target 단어가 없으면 굳이 동작을 수행 할 필요가 없다.</li>
<li>깊이, 너비 중 어떤 탐색을 통해 해야하는가?</li>
</ul>
<h2 id="1-dfs와-백트래킹을-사용한-접근--실패-">1. DFS와 백트래킹을 사용한 접근 ( 실패 )</h2>
<p>처음에 가장 먼저 떠오른건 DFS와 백트래킹을 사용하여 되는 경우를 찾아보는 것이다. <del>(이전에 풀었던 문제가 백트래킹을 사용했 던 것이라 그런 것 같다.)</del>. </p>
<h3 id="코드">코드</h3>
<pre><code class="language-java">class Solution {
    boolean[] visited;
    int answer;
    public int solution(String begin, String target, String[] words) {
        visited = new boolean[words.length];
        answer = 0;
        dfs(begin, target, words, 0);
        return answer;
    }
    void dfs(String word, String target, String[] words, int len){

        if(word.equals(target)){
            answer = len;
            return;
        }

        if(len == words.length){
            return;
        }

        for(int i=0; i &lt; words.length; i++){
            if(!visited[i]){
                visited[i] = true;

                int cnt = 0;
                for(int j=0; j&lt;word.length(); j++){
                    if(word.charAt(j) != words[i].charAt(j)){
                        cnt++;
                    }
                    if(cnt &gt; 1){
                        break;
                    }                    
                }
                if(cnt &lt;= 1){
                    dfs(words[i], target, words, len+1);
                }
            }
            visited[i] = false;
        }
    }
}</code></pre>
<ul>
<li>방문 정보를 확인하며 깊이 우선 탐색을 통해 되는 경우를 확인한다. </li>
<li>현재 단어와 비교할 단어의 길이만큼 차이를 비교해서 2개 이상 차이날 경우는 탐색하지 않는다.</li>
</ul>
<h3 id="결과">결과</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/6d53dceb-4f5b-420b-9e18-3446b64f9b0b/image.png" alt=""></p>
<p><strong>3번째 테스트에서 시간 초과가 발생했다.</strong>
문제 원인으로는 2가지 정도가 있을 것이라고 추측했다. </p>
<ol>
<li>단어를 비교하는 로직의 시간 복잡성</li>
<li>깊이 우선 탐색 시, 최악의 경우 연산량이 많아진다. <ul>
<li>단어의 개수를 50(최대)개라고 했을 때, 한번 최대 깊이로 갈 때,50 * 49 * 48 * ... * 41 정도의 연산이 필요한데 마지막에 답을 찾을 경우, 너무 많은 연산이 이뤄진다.</li>
</ul>
</li>
</ol>
<p><del>3. 주어진 단어 중에 target 단어가 없을 때 확인</del></p>
<ul>
<li><del>해당 문제는 없어도 크게 상관 없을 것 같아 고려하지 않았다.</del></li>
</ul>
<p>일단, 깊이 우선 탐색이 가장 문제 될 것 같아. Queue를 사용해 너비 우선 탐색으로 풀어봤다.</p>
<h2 id="2-bfs를-사용한-접근-방식-성공">2. BFS를 사용한 접근 방식 (성공)</h2>
<h3 id="코드-1">코드</h3>
<pre><code class="language-java">import java.util.*;

class Solution {
    class Node{
        String word;
        int len;

        public Node(String word, int len){
            this.word = word;
            this.len = len;
        }
        String getWord(){
            return this.word;
        }
        int getLen(){
            return this.len;
        }
    }
    int answer;
    public int solution(String begin, String target, String[] words) {
        answer = 0;

        Queue&lt;Node&gt; q = new LinkedList&lt;&gt;();

        q.add(new Node(begin, 0));

        while(!q.isEmpty()){
            Node current = q.poll();

            if(current.getLen() &gt; words.length){
                break;
            }
            if(current.getWord().equals(target)){
                answer = current.getLen();
                break;
            }
            for(int i=0; i&lt;words.length; i++){
                boolean check = checkWord(current.getWord(), words[i]);
                if(check){
                    q.add(new Node(words[i], current.getLen()+1));
                }
            }
        }
        return answer;
    }
    boolean checkWord(String a, String b){
        int cnt = 0;
        for(int i=0; i&lt;a.length(); i++){
            if(a.charAt(i) != b.charAt(i)){
                cnt++;
            }
            if(cnt&gt;1){
                break;
            }
        }
        if(cnt &lt; 2){
            return true;
        }else{
            return false;
        }
    }

}</code></pre>
<h3 id="a-class-node">a. Class Node</h3>
<pre><code class="language-java">class Node{
    String word;
    int len;

    public Node(String word, int len){
        this.word = word;
        this.len = len;
    }
    String getWord(){
        return this.word;
    }
    int getLen(){
        return this.len;
    }
}</code></pre>
<ul>
<li>가장 먼저 단어와 현재 깊이를 가질 수 있는 Node 클래스를 선언했다. 여기서 Len이 현재 깊이를 의미하며 최대 깊이는 words의 length일 것이다.</li>
</ul>
<h3 id="b-solution">b. Solution</h3>
<pre><code class="language-java">    public int solution(String begin, String target, String[] words) {
        answer = 0;

        Queue&lt;Node&gt; q = new LinkedList&lt;&gt;();

        q.add(new Node(begin, 0));

        while(!q.isEmpty()){
            Node current = q.poll();

            if(current.getLen() &gt; words.length){
                break;
            }
            if(current.getWord().equals(target)){
                answer = current.getLen();
                break;
            }
            for(int i=0; i&lt;words.length; i++){
                boolean check = checkWord(current.getWord(), words[i]);
                if(check){
                    q.add(new Node(words[i], current.getLen()+1));
                }
            }
        }
        return answer;
    }</code></pre>
<ul>
<li>첫번째 단어와, 0(len)을 Queue에 넣고 반복을 시작</li>
<li>현재 노드가 최대 깊이(words.length)보다 클 경우, 반복문을 종료<ul>
<li>방문 처리를 따로 하지 않기도 했고, words.length보다 더 많이 탐색할 수 없기 때문이다.</li>
</ul>
</li>
<li>현재 노드의 단어가 target단어와 같다면 answer에 len을 할당하고 종료 (정답을 추출하는 부분)</li>
<li>현재 단어와 바꿀 단어를 비교하는 로직은 그대로 사용했으나 함수로 만들어서 사용했다. <ul>
<li>한 글자만 다르다면 Queue에 삽입하고 , len을 1 증가시킨다.</li>
</ul>
</li>
</ul>
<h3 id="결과-1">결과</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/4d251c09-a6a5-45b5-a7eb-28e3ecbc9c86/image.png" alt="">
이전에 통과 못했던 테스트 3를 통과해서 정답을 맞췄다. 하지만 여전히 테스트 3의 실행 시간이 다른 테스트에 비해 매우 긴 것을 볼 수 있고 완벽히 최적의 해를 찾은 건 아닌 것 같았다. </p>
<h2 id="review">Review</h2>
<p>친구와 푼 문제를 리뷰화는 과정에서 이전 정답에서 완벽히 해결하지 못했던 부분의 문제를 알 수 있었다. 단어끼리 틀린 개수를 확인하는 로직이 문제였는데, 기존 내가 작성한 정답을 생각해보면 단어의 길이가 10일 때, 대충 다음과 같은 연산 수를 가지게 된다. (단어 개수는 50개)
depth 1 일 때, 10 * 50 
depth 2 일 때, 10 * 50 * 50 
...
최악의 경우를 생각하면 대략 계산만 해도 마지막 depth 일 때, 10 * 10 * 50 ^ 50  정도 되는 것 같다.. ^^; <del>(정확하지 않을 수 있다.)</del></p>
<h3 id="코드-2">코드</h3>
<pre><code class="language-java">import java.util.*;

public class Solution {
    public int solution(String begin, String target, String[] words) {
        Set&lt;String&gt; wordSet = new HashSet&lt;&gt;(Arrays.asList(words));
        Set&lt;String&gt; visited = new HashSet&lt;&gt;();
        int answer = 0;

        int wordSize = words[0].length();

        // 큐에 시작 단어와 변환 횟수를 담음
        Queue&lt;Pair&gt; queue = new LinkedList&lt;&gt;();
        queue.offer(new Pair(begin, 0));
        visited.add(begin);

        while (!queue.isEmpty()) {
            Pair current = queue.poll();
            String word = current.word;
            int count = current.count;

            // 목표 단어에 도달한 경우
            if (word.equals(target)) {
                return count;
            }

            // 단어의 각 문자를 하나씩 바꿔가며 탐색
            for (int i = 0; i &lt; wordSize; i++) {
                for (char j = &#39;a&#39;; j &lt;= &#39;z&#39;; j++) {
                    char[] tempArr = word.toCharArray();
                    tempArr[i] = j;
                    String temp = new String(tempArr);

                    // 바꾼 단어가 words 목록에 있고, 아직 방문하지 않은 경우
                    if (wordSet.contains(temp) &amp;&amp; !visited.contains(temp)) {
                        queue.offer(new Pair(temp, count + 1));
                        visited.add(temp);
                    }
                }
            }
        }

        return answer; // target에 도달하지 못한 경우
    }

    // 단어와 변환 횟수를 저장할 클래스
    class Pair {
        String word;
        int count;

        Pair(String word, int count) {
            this.word = word;
            this.count = count;
        }
    }
}</code></pre>
<p>변경된 주요 코드를 보면 다음과 같다. </p>
<pre><code class="language-java">Set&lt;String&gt; wordSet = new HashSet&lt;&gt;(Arrays.asList(words));
Set&lt;String&gt; visited = new HashSet&lt;&gt;();

...

for (int i = 0; i &lt; wordSize; i++) {
    for (char j = &#39;a&#39;; j &lt;= &#39;z&#39;; j++) {
        char[] tempArr = word.toCharArray();
        tempArr[i] = j;
        String temp = new String(tempArr);

        // 바꾼 단어가 words 목록에 있고, 아직 방문하지 않은 경우
        if (wordSet.contains(temp) &amp;&amp; !visited.contains(temp)) {
            queue.offer(new Pair(temp, count + 1));
            visited.add(temp);
        }
    }
}
</code></pre>
<ul>
<li>방문 단어를 다시 탐색하지 않도록 하기 위한 <code>HashSet</code> visited와 words를 <code>HashSet</code>에 넣어 비교할 단어와 확인 할 수 있다.<ul>
<li>HashSet의 contain은 시간 복잡도가 O(1)이기 때문에 반복해서 찾을 때의 O(n)보다 빠르게 찾을 수 있다. </li>
</ul>
</li>
<li>이후, 선택된 단어를 Queue에 넣고 visited를 설정해 너비 우선 탐색 과정을 수행할 수 있다. </li>
</ul>
<h3 id="결론">결론</h3>
<blockquote>
<p><strong>이전까지 Array 형태로 반복해서 적절한 값을 찾는 방법으로 해왔는데, Set을 적절하게 사용하면 실행 시간을 많이 단축할 수 있을 것 같다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Arrays.sort(), compareTo에 대해서 알아보기]]></title>
            <link>https://velog.io/@codejugller-9999/Java-Arrays.sort-compareTo%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@codejugller-9999/Java-Arrays.sort-compareTo%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 11 Oct 2024 11:03:42 GMT</pubDate>
            <description><![CDATA[<h2 id="정리를-하게된-계기">정리를 하게된 계기</h2>
<p>프로그래머스에서 알고리즘 문제를 풀던 도중 이해하기 어려운 코드 한줄을 발견했다.
<code>Arrays.sort(arr, (o1, o2) -&gt; (o2 + o1).compareTo(o1 + o2));</code></p>
<p>다익스트라 알고리즘을 공부하면서 PriorityQueue를 사용할 때 compareTo를 Override해서 사용한 적이 있는데 명확하게 이해하지 못한 것 같아 짚고 넘어가려 한다. </p>
<h2 id="arrayssort란">Arrays.sort()란?</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/8bab3a43-b3b6-4799-af65-a6bf6447a8d2/image.png" alt=""></p>
<p>공식 문서를 보면 여러 타입의 배열이 모두 정렬될 수 있도록 정의된 것을 볼 수 있다.</p>
<p>동작과정을 살펴보면 다음과 같다. </p>
<pre><code class="language-java">// Arrays class
public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
}

...
// DualPivotQuicksort class

static void sort(int[] a, int parallelism, int low, int high) {
    int size = high - low;
    if (parallelism &gt; 1 &amp;&amp; size &gt; 4096) {
       int depth = getDepth(parallelism, size &gt;&gt; 12);
       int[] b = depth == 0 ? null : new int[size];
       (new Sorter((CountedCompleter)null, a, b, low, size, low, depth)).invoke();
    } else {
        sort((Sorter)null, (int[])a, 0, low, high);
    }
}

static void sort(Sorter sorter, int[] a, int bits, int low, int high) {
        while(true) {
            int end = high - 1;
            int size = high - low;
            if (size &lt; 65 + bits &amp;&amp; (bits &amp; 1) &gt; 0) {
                mixedInsertionSort(a, low, high - 3 * (size &gt;&gt; 5 &lt;&lt; 3), high);
                return;
            }

            if (size &lt; 44) {
                insertionSort(a, low, high);
                return;
            }

...(생략)</code></pre>
<p>내부적으로 두개의 pivot을 사용하는 Quicksort로 동작하며 시간 복잡도는 <strong>O(n log n)</strong>으로 좋은 성능을 가지기 때문에 알고리즘을 풀때 사용해도 큰 문제가 없을 것 같다.</p>
<p>매개 변수에는 3가지 정도가 있다. </p>
<ul>
<li><strong>fromIndex</strong>(시작 인덱스)</li>
<li><strong>toIndex</strong>(마지막 인덱스)</li>
<li><strong>Comparator&lt;? super T&gt; c</strong></li>
</ul>
<h3 id="interface-comparator-t">Interface Comparator&lt; T &gt;</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/be92370e-a41e-4638-90e7-45c15044641f/image.png" alt=""></p>
<p>주목해야할 매개변수로 인터페이스인 Comparator은 람다 표현식 등을 사용한 비교기를 정렬 방법으로 전달할 수 있다고 명시되어있다. </p>
<hr>
<h2 id="example">Example</h2>
<h3 id="기본-형태오름차순">기본 형태(오름차순)</h3>
<pre><code class="language-java">public static void main(String[] args) {
    String[] words = {&quot;banana&quot;, &quot;apple&quot;, &quot;orange&quot;, &quot;kiwi&quot;};

    Arrays.sort(words);

    System.out.println(&quot;문자열 정렬: &quot; + Arrays.toString(words));

    Integer[] numbers = {5, 1, 8, 3, 7};

    Arrays.sort(numbers);

    System.out.println(&quot;숫자 정렬: &quot; + Arrays.toString(numbers));
}

// 실행 결과
문자열 정렬: [apple, banana, kiwi, orange]
숫자 정렬: [1, 3, 5, 7, 8]</code></pre>
<p>기본적인 형태로 default는 오름차순으로 정렬한다. sort함수에 정렬할 배열만 넣어 사용할 수 있다. 문자열을 정렬할 때는 알파벳 순서(ASCII code 비교)로 정렬하게 되고 같은 알파벳일 경우 그 다음 문자열을 비교해서 정렬하게 된다. </p>
<pre><code class="language-java">String[] words = {&quot;aanana&quot;, &quot;apple&quot;, &quot;orange&quot;, &quot;kiwi&quot;};

Arrays.sort(words);

System.out.println(&quot;문자열 정렬: &quot; + Arrays.toString(words));

//실행 결과
문자열 정렬: [aanana, apple, kiwi, orange]</code></pre>
<h3 id="내림차순-정렬comparator">내림차순 정렬(Comparator)</h3>
<pre><code class="language-java">public static void main(String[] args) {
    String[] words = {&quot;banana&quot;, &quot;apple&quot;, &quot;orange&quot;, &quot;kiwi&quot;};

    Arrays.sort(words, Comparator.reverseOrder());

    System.out.println(&quot;문자열 정렬(내림차순): &quot; + Arrays.toString(words));

    Integer[] numbers = {5, 1, 8, 3, 7};

    Arrays.sort(numbers, Comparator.reverseOrder());

    System.out.println(&quot;숫자 정렬(내림차순): &quot; + Arrays.toString(numbers));
}

// 실행 결과
문자열 정렬(내림차순): [orange, kiwi, banana, apple]
숫자 정렬(내림차순): [8, 7, 5, 3, 1]</code></pre>
<p>Comparator.reverseOrder()을 추가해서 내림차순으로 정렬할 수 있다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    Integer[] numbers = {5, 1, 8, 3, 7};

    Arrays.sort(numbers, (a, b) -&gt; b - a);

    System.out.println(&quot;내림차순 정렬: &quot; + Arrays.toString(numbers));
}</code></pre>
<p>람다 표현식으로 a,b를 비교하여 내림차순으로 정렬하는 방법이다.</p>
<ul>
<li>b - a가 양수면, b가 a보다 크기 때문에 b가 앞에 와야한다.</li>
<li>b - a가 음수면, a가 b보다 크기 때문에 a가 그대로 유지된다. </li>
</ul>
<h3 id="comparator-응용">Comparator 응용</h3>
<p>이전 내림차순 정렬을 위해 사용한 람다식 표현으로 더 복잡한 정렬기준을 만들어서 정렬을 할 수 있다. </p>
<ul>
<li>2차원 배열에서 첫번째 요소로만 정렬하고 싶을 때</li>
<li>이전 값과의 연산을 통해 정렬 기준을 정할 때</li>
</ul>
<p>상황에 더 따라 다양하게 사용할 수 있는데 실제로 필요했던 예시만 언급했다. </p>
<h4 id="1-2차원-배열의-정렬-조건">1. 2차원 배열의 정렬 조건</h4>
<pre><code class="language-java">public static void main(String[] args) {
    Integer[][] workers = {{1, 280}, {3, 120}, {2, 150}, {5, 400}, {4, 330}};

    Arrays.sort(workers, (a, b) -&gt; b[1].compareTo(a[1]));

    System.out.println(&quot;급여(내림차순) 정렬: &quot; + Arrays.deepToString(workers));

// 실행결과
급여(내림차순) 정렬: [[5, 400], [4, 330], [1, 280], [2, 150], [3, 120]]
}</code></pre>
<p>ID, 급여 정보가 있는 2차원 배열이 있다고 생각해보자. 만약 급여가 높은 순서대로 정렬하고 싶다면 다음과 같이 코드를 작성할 수 있다. </p>
<p><code>(a, b) -&gt; b[1].compareTo(a[1])</code> 는 람다 표현식으로, 정렬 기준이 된다. </p>
<ul>
<li><p>a, b배열의 두개의 요소</p>
<ul>
<li>example</li>
<li>a는 <code>{1 , 280}</code> , b는 <code>{3 , 120}</code></li>
</ul>
</li>
<li><p><code>b[1]</code>, <code>a[1]</code>로 배열의 두번째(급여)를 사용하여 비교</p>
</li>
<li><p>동작과정</p>
<ul>
<li><code>b[1]</code>가 <code>a[1]</code>보다 크면 양수가 반환되어 b가 a앞에 위치한다. </li>
<li><code>b[1]</code>가 <code>a[1]</code>보다 작으면 음수가 반환되어 순서가 유지된다.</li>
<li><code>b[1]</code>가 <code>a[1]</code>보다 같으면 순서를 바꾸지 않는다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="2-이전-값과의-연산을-통한-정렬기준">2. 이전 값과의 연산을 통한 정렬기준</h4>
<p>프로그래머스 <a href="https://school.programmers.co.kr/learn/courses/30/lessons/42746">가장 큰 수</a> 를 풀 때 필요한 솔루션으로 문제 상황은 다음과 같다. </p>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/830b3f75-0a63-4ae4-9ba0-c6c8afc4a88c/image.png" alt=""></p>
<pre><code class="language-java">public class Solution {
    public String solution(int[] numbers) {
        String[] arr = new String[numbers.length];

        for (int i = 0; i &lt; arr.length; i++) {
            arr[i] = String.valueOf(numbers[i]);
        }

        Arrays.sort(arr, (a, b) -&gt; (b + a).compareTo(a + b));

          ...
    }
}</code></pre>
<p>주어진 입력 값을 String 배열로 변환 후에  <code>Arrays.sort(arr, (o1, o2) -&gt; (o2 + o1).compareTo(o1 + o2));</code> 로 정렬을 수행했다. </p>
<p><strong>동작 과정</strong>
<code>a = 30</code> , <code>b = 34</code> 라고 할 때, &quot;3430&quot;이 &quot;3034&quot;보다 크기 때문에 양수가 반환되어 <code>b</code>가 <code>a</code>보다 앞에 위치하게 된다. 이런식으로 내림차순으로 정렬해서 가장 큰 수의 문자열을 만들 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 12891. DNA 비밀번호]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-12891.-DNA-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-12891.-DNA-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8</guid>
            <pubDate>Wed, 09 Oct 2024 01:29:42 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/12891">문제 링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/74517ff3-4db2-4d23-b611-d7654038dc7c/image.png" alt=""></p>
<p>주어진 문자열에서 특정 문자가 최소 n개 이상 들어있는지 확인하는 문제로 처음에는 한칸씩 밀면서 문자열을 확인하는 방식으로 접근했다. </p>
<pre><code class="language-java">for(int i=0; i &lt; S - P + 1; i++){
    String checkString = dna.subString(i, i + P);
     ...
}</code></pre>
<p>이와 같이 문자열을 subString으로 추출하고 특정 로직을 수행하는 방식으로 해당 문자열이 비밀번호로 사용할 문자가 적합한지 판단했다. 반복문을 사용해서 확인하면 시간초과가 발생할 것을 우려해 
<code>checkString.length() - checkString.replace(&quot;A&quot;, &quot; &quot;).length()</code>와 같이 추출한 전체 길이에서 확인하려는 문자를 공백으로 바꾼뒤 빼서 확인했지만 시간초과가 발생하는 것을 알 수 있었는데 이유는 다음과 같다.</p>
<pre><code class="language-java">public String replace(CharSequence target, CharSequence replacement) {  
    String trgtStr = target.toString();  
    String replStr = replacement.toString();  
    int thisLen = this.length();  
    int trgtLen = trgtStr.length();  
    int replLen = replStr.length();  
    if (trgtLen &gt; 0) {  
        if (trgtLen == 1 &amp;&amp; replLen == 1) {  
            return this.replace(trgtStr.charAt(0), replStr.charAt(0));  
        } else {  
            boolean thisIsLatin1 = this.isLatin1();  
            boolean trgtIsLatin1 = trgtStr.isLatin1();  
            boolean replIsLatin1 = replStr.isLatin1();  
            String ret = thisIsLatin1 &amp;&amp; trgtIsLatin1 &amp;&amp; replIsLatin1 ? StringLatin1.replace(this.value, thisLen, trgtStr.value, trgtLen, replStr.value, replLen) : StringUTF16.replace(this.value, thisLen, thisIsLatin1, trgtStr.value, trgtLen, trgtIsLatin1, replStr.value, replLen, replIsLatin1);  
            return ret != null ? ret : this;  
        }    } else {  
        int resultLen;  
        try {  
            resultLen = Math.addExact(thisLen, Math.multiplyExact(Math.addExact(thisLen, 1), replLen));  
        } catch (ArithmeticException var12) {  
            throw new OutOfMemoryError(&quot;Required length exceeds implementation limit&quot;);  
        }  
        StringBuilder sb = new StringBuilder(resultLen);  
        sb.append(replStr);  

        for(int i = 0; i &lt; thisLen; ++i) {  
            sb.append(this.charAt(i)).append(replStr);  
        }  
        return sb.toString();  
    }}</code></pre>
<p>해당 코드는 replace 내장 함수로 동작 과정을 보면 마지막에 반복문을 사용해 대체한 String을 다시 할당해서 반환하는 것을 볼 수 있다. 그렇기 때문에 코드상에서 반복문을 사용하는 것과 크게 다를 것이 없다. </p>
<pre><code class="language-java">for(int i = 0; i &lt; thisLen; ++i) {  
            sb.append(this.charAt(i)).append(replStr);  
}  
return sb.toString(); </code></pre>
<h2 id="정답-코드">정답 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;  
import java.io.InputStreamReader;  
import java.io.IOException;  

public class Main {  


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

        String[] firstLine = br.readLine().split(&quot; &quot;);  
        int S = Integer.parseInt(firstLine[0]);  
        int P = Integer.parseInt(firstLine[1]);  

        String dna = br.readLine();  

        int result = 0;  
        String[] thirdLine = br.readLine().split(&quot; &quot;);  
        int[] minCount = new int[4];  
        for (int i = 0; i &lt; 4; i++) {  
            minCount[i] = Integer.parseInt(thirdLine[i]);  
        }  

        result = answer(dna, minCount, S, P);  
        System.out.println(result);  

        br.close();  
    }  

    static int answer(String dna, int[] minCount, int S, int P) {  
        int result = 0;  
        int[] checkCount = new int[4];  
        boolean check;  
        char before = &#39; &#39;;  
        for (int i = 0; i &lt; S - P + 1; i++) {  
            if (i == 0) {  
                String checkString = dna.substring(i, i + P);  
                checkCount = initCheck(checkString);  
                before = checkString.charAt(i);  
            } else {  
                char after = dna.charAt(i + P - 1);  
                checkCount = updateCheck(before, after, checkCount);  
                before = dna.charAt(i);  
            }  
            check = checkString(minCount, checkCount);  
            if (check) {  
                result++;  
            }  
        }  
        return result;  
    }  

    static int[] initCheck(String checkString) {  
        int[] result = new int[4];  
        result[0] = checkString.length() - checkString.replace(&quot;A&quot;, &quot;&quot;).length();  
        result[1] = checkString.length() - checkString.replace(&quot;C&quot;, &quot;&quot;).length();  
        result[2] = checkString.length() - checkString.replace(&quot;G&quot;, &quot;&quot;).length();  
        result[3] = checkString.length() - checkString.replace(&quot;T&quot;, &quot;&quot;).length();  
        return result;  
    }  

    static int[] updateCheck(char before, char after, int[] checkCount) {  
        if (before == &#39;A&#39;) {  
            checkCount[0]--;  
        } else if (before == &#39;C&#39;) {  
            checkCount[1]--;  
        } else if (before == &#39;G&#39;) {  
            checkCount[2]--;  
        } else {  
            checkCount[3]--;  
        }  

        if (after == &#39;A&#39;) {  
            checkCount[0]++;  
        } else if (after == &#39;C&#39;) {  
            checkCount[1]++;  
        } else if (after == &#39;G&#39;) {  
            checkCount[2]++;  
        } else {  
            checkCount[3]++;  
        }  
        return checkCount;  
    }  

    static boolean checkString(int[] minCount, int[] checkCount) {  
        if (checkCount[0] &lt; minCount[0]) {  
            return false;  
        }  
        if (checkCount[1] &lt; minCount[1]) {  
            return false;  
        }  
        if (checkCount[2] &lt; minCount[2]) {  
            return false;  
        }  
        if (checkCount[3] &lt; minCount[3]) {  
            return false;  
        }  
        return true;  
    }  
}</code></pre>
<p>메소드를 분리해서 코드가 길지만 동작 과정은 매우 단순하다. </p>
<h4 id="1-문자열-추출-및-checkcount">1. 문자열 추출 및 checkCount</h4>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/26c9f521-a319-4705-8ea7-8417b5a2f24b/image.png" alt=""></p>
<p>가장 처음에는 비밀번호로 사용할 만큼 길이의 문자열을 처음 인덱스부터 추출해서 checkCount를 만들어준다. 여기서 checkCount는 현재 문자열에 포함된 단어를 check하는 배열로 해당 배열로 최소 포함 개수 배열과 비교하며 비밀번호로 적절한지 판단한다.</p>
<h4 id="2-추가--삭제되는-문자를-checkcount에-반영">2. 추가 , 삭제되는 문자를 checkCount에 반영</h4>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/50385b39-eb8a-4416-ac89-17f1708f73c5/image.png" alt=""></p>
<p>이후 새로 확인하는 문자가 &quot;AT&quot;라고 할 때 G를 before, T를 After로 설정해 before에 해당하는 count를 1 감소 , after 해당하는 count를 1 증가하여 checkCount를 업데이트 한다. </p>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/277bdbe0-f471-4b6b-9936-b77d37abb562/image.png" alt=""></p>
<p>해당 과정을 반복하며 다음과 같은 checkString을 수행해주며 비밀번호로 적절한 문자열을 판단한다. </p>
<pre><code class="language-java">static boolean checkString(int[] minCount, int[] checkCount) {  
    if (checkCount[0] &lt; minCount[0]) {  
        return false;  
    }  
    if (checkCount[1] &lt; minCount[1]) {  
        return false;  
    }  
    if (checkCount[2] &lt; minCount[2]) {  
        return false;  
    }  
    if (checkCount[3] &lt; minCount[3]) {  
        return false;  
    }  
    return true;  
}</code></pre>
<h2 id="정리">정리</h2>
<p>일반적으로 1억번의 연산이 1초에 진행된다고 가정하고 시간초과가 발생하지 않게 코드를 구현하는 방법에 대해서 좀 더 고민해야겠다.🙃</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11659. 구간 합 구하기 4]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-11659.-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-4</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-11659.-%EA%B5%AC%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0-4</guid>
            <pubDate>Tue, 08 Oct 2024 12:35:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/11659">문제 링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/de225b8a-4210-49d0-9d1a-38e6f568f86f/image.png" alt=""></p>
<p>누적합으로 푸는 아주 간단한 실버 문제이다. 하지만 &#39;당연히 되겠지?&#39; 라는 생각으로 반복문을 두번 돌려서 풀다가 <strong>시간초과</strong>가 발생하는 것을 발견하고 기억하고자 글을 작성했다.😭</p>
<h2 id="정답-코드">정답 코드</h2>
<p>풀이는 굉장히 간단하다. i ~ j 까지 합을 구하는 것이기 때문에 n번째까지 더한 값을 담아두는 sum을 선언하고 <code>sum[j] - sum[i-1]</code>을 해서 결과를 출력하면된다.</p>
<pre><code class="language-java">import java.io.*;  
import java.util.*;  

public class Main {  

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

        String[] firstLine = br.readLine().split(&quot; &quot;);  
        int N = Integer.parseInt(firstLine[0]);  
        int M = Integer.parseInt(firstLine[1]);  

        int[] numbers = new int[N + 1];  // 1-based index 사용  
        String[] secondLine = br.readLine().split(&quot; &quot;);  
        for (int i = 1; i &lt;= N; i++) {  
            numbers[i] = Integer.parseInt(secondLine[i - 1]);  
        }  
        int[] sum = new int[N + 1];  
        for (int i = 1; i &lt;= N; i++) {  
            sum[i] = numbers[i] + sum[i - 1];  
        }  
        for (int i = 0; i &lt; M; i++) {  
            StringTokenizer st = new StringTokenizer(br.readLine());  
            int start = Integer.parseInt(st.nextToken());  
            int end = Integer.parseInt(st.nextToken());  
            System.out.println(sum[end] - sum[start - 1]);  
        }  
        br.close();  
    }  

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2056. 작업]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-2056.-%EC%9E%91%EC%97%85</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-2056.-%EC%9E%91%EC%97%85</guid>
            <pubDate>Thu, 26 Sep 2024 13:25:46 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2056">문제링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/46219058-464b-4e87-bc95-db8cfa6c827f/image.png" alt=""></p>
<p>위상 정렬 문제로 진입차수가 없는(선행 작업이 존재하지 않은) 노드들을 먼저 실행시키고 작업이 완료되면 하위 노드의 차수를 감소시켜 진입차수가 0이 되는 노드의 작업을 시작하는 방식으로 접근했다. 시간 제한이 2초로 나름? 여유가 있어 1시간 단위로 반복문을 실행해 작업중인 노드의 time 값을 감소시켜 0이 되면 위 작업을 실행하도록 코드를 작성했다. </p>
<h2 id="정답-코드라고-생각했던-것">정답 코드(라고 생각했던 것)</h2>
<pre><code class="language-java">import java.io.*;  
import java.util.*;  

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

        StringTokenizer st;  

        int[] degree = new int[n + 1];  
        int[] time = new int[n + 1];  

        List&lt;List&lt;Integer&gt;&gt; graph = new ArrayList&lt;&gt;();  
        for (int i = 0; i &lt;= n; i++) {  
            graph.add(new ArrayList&lt;&gt;());  
        }  
        for (int i = 1; i &lt;= n; i++) {  
            st = new StringTokenizer(br.readLine());  

            time[i] = Integer.parseInt(st.nextToken());  
            int cnt = Integer.parseInt(st.nextToken());  
            degree[i] = cnt;  
            for (int j = 0; j &lt; cnt; j++) {  
                int parent = Integer.parseInt(st.nextToken());  
                graph.get(parent).add(i);  
            }  

        }  

        Queue&lt;Integer&gt; q = new LinkedList&lt;&gt;();  
        for (int i = 1; i &lt;= n; i++) {  
            if (degree[i] == 0) {  
                q.add(i);  
            }  
        }  

        int cnt = 0;  
        while (!q.isEmpty()) {  
            for (int i = 0; i &lt; q.size(); i++) {  
                int t = q.poll();  
                time[t]--;  
                if (time[t] == 0) {  
                    List&lt;Integer&gt; nodes = graph.get(t);  
                    for(Integer node: nodes){  
                        degree[node]--;  
                        if(degree[node] == 0){  
                            q.add(node);  
                        }  
                    }  
                } else {  
                    q.add(t);  
                }  
            }  
            cnt++;  
        }  
        System.out.println(cnt);  

    }  
}</code></pre>
<h3 id="주요-알고리즘">주요 알고리즘</h3>
<pre><code class="language-java">       int cnt = 0;  
        while (!q.isEmpty()) {  
            for (int i = 0; i &lt; q.size(); i++) {  
                int t = q.poll();  
                time[t]--;  
                if (time[t] == 0) {  
                    List&lt;Integer&gt; nodes = graph.get(t);  
                    for(Integer node: nodes){  
                        degree[node]--;  
                        if(degree[node] == 0){  
                            q.add(node);  
                        }  
                    }  
                } else {  
                    q.add(t);  
                }  
            }  
            cnt++;  
        }  </code></pre>
<p>queue에 담겨있는(진행중인 작업)을 가져와 time값을 감소시키고 time이 0이 아닐 때는 다시 queue에 넣어 작업을 계속 진행하도록 했다. 최저 시간을 출력하는 문제이지만 어짜피 하위 노드의 작업은 모든 상위 노드가 끝났을 때 진행되기 때문에 별다른 비교연산없이 이상적으로 동시에 작업이 진행된다고 생각했다...결과를 확인하기 전까지</p>
<p><img src=https://velog.velcdn.com/images/codejugller-9999/post/f7f3648f-b65d-40c6-9cfe-4a5db4a5dda6/image.png 
     align = center width= 400></p>
<h2 id="정답-코드">정답 코드</h2>
<pre><code class="language-java">import java.io.*;  
import java.util.*;  

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

        StringTokenizer st;  

        int[] degree = new int[n + 1];  
        int[] time = new int[n + 1];  
        int[] cTime = new int[n + 1];  
        List&lt;List&lt;Integer&gt;&gt; graph = new ArrayList&lt;&gt;();  
        for (int i = 0; i &lt;= n; i++) {  
            graph.add(new ArrayList&lt;&gt;());  
        }  
        for (int i = 1; i &lt;= n; i++) {  
            st = new StringTokenizer(br.readLine());  
            time[i] = Integer.parseInt(st.nextToken());  
            int cnt = Integer.parseInt(st.nextToken());  
            degree[i] = cnt;  
            for (int j = 0; j &lt; cnt; j++) {  
                int parent = Integer.parseInt(st.nextToken());  
                graph.get(parent).add(i);  
            }  

        }  

        Queue&lt;Integer&gt; q = new LinkedList&lt;&gt;();  
        for (int i = 1; i &lt;= n; i++) {  
            if (degree[i] == 0) {  
                q.add(i);  
                cTime[i] = time[i];  
            }  
        }  

        while (!q.isEmpty()) {  
            int current = q.poll();  

            for (Integer next : graph.get(current)) {  
                degree[next]--;  
                cTime[next] = Math.max(cTime[next], cTime[current] + time[next]);  
                if(degree[next] == 0){  
                    q.add(next);  
                }  
            }  
        }  
        int result = 0;  
        for (int i = 1; i &lt;= n; i++) {  
            result = Math.max(result, cTime[i]);  
        }  
        System.out.println(result);  
    }  
}</code></pre>
<pre><code class="language-java">        for (int i = 1; i &lt;= n; i++) {  
            if (degree[i] == 0) {  
                q.add(i);  
                cTime[i] = time[i];  
            }  
        }  

        while (!q.isEmpty()) {  
            int current = q.poll();  

            for (Integer next : graph.get(current)) {  
                degree[next]--;  
                cTime[next] = Math.max(cTime[next], cTime[current] + time[next]);  
                if(degree[next] == 0){  
                    q.add(next);  
                }  
            }  
        }  </code></pre>
<p>cTime이라는 완료시간을 저장하는 배열을 선언하고 정점인 노드 즉, 가장 먼저 작업을 시작하는 노드의 값은 해당 작업이 걸리는 시간을 할당한다. 이후 다음과 같은 연산을 통해 해당 노드가 작업이 완료된 시간을 정한다. </p>
<hr>
<p><img src=https://velog.velcdn.com/images/codejugller-9999/post/37fae662-1507-488c-90a5-3e2a6eeaf5a5/image.png
     align = center width= 600></p>
<p>다음과 같은 상황이 있다고 가정하자. (실제로 cTime[3]은 값이 없지만 편의상 해당 작업의 시간이라고 생각한다.) </p>
<hr>
<p><img src=https://velog.velcdn.com/images/codejugller-9999/post/cf4e6bee-ae37-4b38-a3aa-9b5fa35b09e8/image.png
     align = center width= 600>
1,2 작업이 동시에 시작되며 15시간이 걸리는 1작업이 먼저 끝나고 연산을 통해 cTime[3]은 20이 된다. </p>
<hr>
<p><img src=https://velog.velcdn.com/images/codejugller-9999/post/2cefb940-5087-4767-bbb8-a960325113e4/image.png
     align = center width= 600></p>
<p>이후 2작업이 5시간 뒤에 끝나게 되고 이전의 값인 20과 새롭게 계산한 25중에서 큰 값인 25로 설정해서 상위 작업이 전부 끝나고 완료된 시간을 기록하게 된다. </p>
<h2 id="정리">정리</h2>
<p>사실 아직 첫번째 만든 코드가 틀린 이유는 잘 모르겠다.<del>(물론 깔끔한 코드라고 생각은 안함 ㅎ)</del> 상위 작업이 끝난 후에 작업이 끝나도록 코드를 작성했기 때문에 예제와 질문 게시판에 있는 반례 몇개를 해봤지만 제대로 정답이 출력되었기 때문이다. 시간초과라고 뜨면 납득이 되겠지만 채점이 틀렸다고 한건 분명 반례가 있다는건데... (있으면 댓글 부탁드림다.)
어쨌든 위상정렬에 대해서 좀 더 확실한 이해가 되었던 문제였다고 생각한다. 🙃</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1932. 정수 삼각형]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1932.-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1932.-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</guid>
            <pubDate>Mon, 05 Aug 2024 12:00:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.acmicpc.net/problem/1932">문제 링크</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/e7bf863e-6a6c-49a0-b930-878548dc07b4/image.png" alt=""></p>
<p>일반적인 DP 문제로 합이 최대가 되는 경로의 수의 합을 출력하는 문제이다. 어떤 경로인지까지는 파악할 필요가 없고 마지막 줄에서 합이 가장 큰 값을 출력하면된다. 맨 윗줄부터 더한 값을 계속 누적해서 마지막 줄에서 가장 큰 값을 출력하는 방식으로 접근했고 가장자리가 아닌 경우는 가로 윗 줄의 두가지 값을 더한 것 중 최대값으로 해야하기 때문에 이에 대한 코드를 조건문으로 구현했다. </p>
<h2 id="정답-코드">정답 코드</h2>
<pre><code class="language-python"># 정수 삼각형  
n = int(input())  

# 정수 삼각형의 값을 2차원 배열로 형태로 입력받음
t = []  
for i in range(n):  
    t.append(list(map(int, input().split())))  

# 입력받은 n의 크키에 맞는 0의 값을 가지는 배열을 선언
dp = [[0] * i for i in range(1, n + 1)] 

for i in range(n):  
    # 가로 첫번째 줄은 값을 그대로 입력
    if i == 0:  
        dp[0][0] = t[0][0]  
        continue  
    # 가로 두번째 줄은 첫번째 줄의 값과의 합으로 입력
    if i == 1:  
        dp[1][0] = dp[0][0] + t[i][0]  
        dp[1][1] = dp[0][0] + t[i][1]  
        continue  

    for j in range(i + 1):  
        # 왼쪽 가장자리인 경우
        if j == 0:  
            dp[i][j] = dp[i - 1][j] + t[i][j]  
        # 오른쪽 가장자리인 경우
        elif j == i:  
            dp[i][j] = dp[i - 1][j - 1] + t[i][j]  
        # 가장자리가 아닌 경우
        # 두가지 값이 생기는데 그 중 최대값으로 설정
        else:  
            dp[i][j] = max(dp[i - 1][j - 1] + t[i][j], dp[i - 1][j] + t[i][j])  

# 마지막 줄에서 가장 큰 값을 출력
print(max(dp[n - 1]))</code></pre>
<h3 id="값-누적-연산">값 누적 연산</h3>
<pre><code class="language-python">for j in range(i + 1):  
        # 왼쪽 가장자리인 경우
        if j == 0:  
            dp[i][j] = dp[i - 1][j] + t[i][j]  
        # 오른쪽 가장자리인 경우
        elif j == i:  
            dp[i][j] = dp[i - 1][j - 1] + t[i][j]  
        # 가장자리가 아닌 경우
        # 두가지 값이 생기는데 그 중 최대값으로 설정
        else:  
            dp[i][j] = max(dp[i - 1][j - 1] + t[i][j], dp[i - 1][j] + t[i][j])  </code></pre>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/f4f29215-fe4d-41bb-bc51-e458607fc133/image.png
 width = 400></p>
<p>가장 자리가 아닌 경우 더한 값에서 최대 값으로 설정해야 하는데 그림에서 3개의 값이 있는 줄을 연산하고 있을 때 1은 10과 15중 더 큰 15와 더한 값인 16으로 값을 누적시켜야한다. 
<img src =https://velog.velcdn.com/images/codejugller-9999/post/6bd8d072-6650-4729-8df0-77d88b9d8831/image.png
 width = 400></p>
<h2 id="추가">추가</h2>
<p>정답은 맞췄지만 이 글을 작성하면서 코드를 다시 봤는데 수정하고 싶은 사항이 너무 많아서 코드를 다시 작성해서 제출해봤다. 수정사항은 다음과 같다.</p>
<h3 id="dp-배열의-필요성">dp 배열의 필요성</h3>
<p>굳이 하나의 배열을 더 선언하지 않고 입력받은 <code>t</code>에 값을 수정하면서 누적하면 될 것 같다. 불필요한 배열때문에 다시 봤을 때 코드가 쓸데없이 복잡하다는 생각이 들었다. <code>t</code>를 그대로 사용하면 반복문의 횟수도 줄어들 것 같다.</p>
<h3 id="누적-값-연산-코드">누적 값 연산 코드</h3>
<p>이전 코드에서는 가장자리가 아닐 경우 값을 더한 후에 <code>max</code>함수에서 최대 값을 추출했는데 굳이 그럴 필요 없이 두개 중 최대값을 뽑아서 더해서 저장하면 될 것 같다. </p>
<h2 id="수정된-코드">수정된 코드</h2>
<pre><code class="language-python">n = int(input())  

t = []  
for i in range(n):  
    t.append(list(map(int, input().split())))  

for i in range(1, n):  
    if i == 1:  
        t[1][0] = t[0][0] + t[i][0]  
        t[1][1] = t[0][0] + t[i][1]  
        continue  
    for j in range(i + 1):  
        if j == 0:  
            t[i][j] = t[i - 1][j] + t[i][j]  
        elif j == i:  
            t[i][j] = t[i - 1][j - 1] + t[i][j]  
        else:  
            t[i][j] = max(t[i - 1][j - 1], t[i - 1][j]) + t[i][j]  

print(max(t[n - 1]))</code></pre>
<p>크게 개선된 느낌은 아니지만 코드 상에서 반복되는 부분을 줄이고 굳이 필요하지 않은 배열 선언을 없앤걸로 만족!🙃</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다이나믹 프로그래밍]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%8B%A4%EC%9D%B4%EB%82%98%EB%AF%B9-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@codejugller-9999/%EB%8B%A4%EC%9D%B4%EB%82%98%EB%AF%B9-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Mon, 05 Aug 2024 03:08:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 &#39;이것이 코딩테스트다 with 파이썬&#39; (나동빈 지음) 책 내용을 정리한 것입니다.</p>
</blockquote>
<h2 id="다이나믹-프로그래밍이란">다이나믹 프로그래밍이란?</h2>
<blockquote>
<p><strong>DP</strong>, <strong>동적 계획법</strong>이라고도 불리는 이것은 메모리 공간을 더 사용하여 연산 속도를 비약적으로 증가시킬 수 있는 방법이다. </p>
</blockquote>
<h3 id="다이나믹-프로그래밍의-조건">다이나믹 프로그래밍의 조건</h3>
<p>다이나믹 프로그래밍을 사용하기위해 다음과 같은 조건이 필요하다. </p>
<ol>
<li>큰 문제를 작은 문제로 나눌 수 있다. </li>
<li>작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다. </li>
</ol>
<p>피보나치 수열을 예시로 비교를 해보면 다음과 같다.</p>
<h3 id="예시--피보나치-수열">예시 : 피보나치 수열</h3>
<h4 id="dp를-사용하지-않은-구현-예시">DP를 사용하지 않은 구현 예시</h4>
<pre><code class="language-python">def fibo(x):
    if x == 1 or x == 2:
        return 1
    return fibo(x - 1) + fibo(x - 2)

print(fibo(4))</code></pre>
<p>일반적인 재귀호출 방법으로 피보나치 수열을 구현한 것이다. 하지만 이와 같이 구현할 경우 몇가지 문제가 발생한다. </p>
<p><strong>시간 복잡도</strong></p>
<ul>
<li>f(n) 함수에서 n이 커질 수록 수행 시간이 기하급수적으로 늘어난다.<ul>
<li>O(2^n)</li>
<li>N = 30이면, 약 10억 가량의 연산을 수행한다.</li>
</ul>
</li>
</ul>
<p><img src= https://velog.velcdn.com/images/codejugller-9999/post/6a42dc93-4693-4fd1-82d6-5688d280ac77/image.png
     width = 500></p>
<ul>
<li>f(6)을 예시로 그림으로 표현하면 f(3)이 3번 호출되는 것을 볼 수 있다. </li>
<li>f(n)에서 n이 커질수록 중복 호출되는 경우가 증가하고 f(100)일 경우에는 답을 도출하기 힘들 정도로 많은 시간이 소요된다.</li>
</ul>
<h4 id="dp-사용한-구현-예시">DP 사용한 구현 예시</h4>
<blockquote>
<p><strong>[Top-Down VS Bottom-UP]</strong></p>
</blockquote>
<ul>
<li><strong>Top-Down</strong> : 재귀 함수를 이용해 큰 문제를 해결하기 위해 작은 문제를 호출하는 방식</li>
<li><strong>Bottom-UP</strong> : 반복문을 이용해 작은 문제부터 차근차근 답을 도출하는 방법</li>
</ul>
<p><strong>1. Top-Down</strong></p>
<pre><code class="language-python">d = [0] * 100

def fibo(x):
    if x == 1 or x == 2:
        return 1
    if d[x] != 0
        return d[x]
    d[x] = fibo(x - 1) + fibo(x - 2)
    return d[x]

print(fibo(99))</code></pre>
<p>한번 푼 문제는 저장해 놨다가 나중에 동일한 문제를 풀어야 할때(여기서 문제는 호출을 의미)이미 저장한 값을 반환하여 반복적으로 호출되는 상황을 막을 수 있다. </p>
<p><img src= https://velog.velcdn.com/images/codejugller-9999/post/b6840b60-78be-4a66-9336-9811f91d4429/image.png
     width = 500></p>
<ul>
<li>점선으로 표시한 부분은 호출이 되지 않은 경우로 메모이제이션을 통해 시간복잡도를 줄일 수 있다.<ul>
<li>O(N)의 시간복잡도를 가진다. 왜냐하면 한번 구한 결과는 다시 구해지지 않기 때문이다.</li>
</ul>
</li>
</ul>
<p><strong>2. Bottom-UP</strong></p>
<pre><code class="language-python">d = [0] * 100

d[1] = 1
d[2] = 2
n = 99

for i in range(3, n+1):
    d[i] = d[i - 1] + d[i - 2]

print(d[n])</code></pre>
<blockquote>
<p><strong>[추가]</strong></p>
</blockquote>
<ul>
<li>때에 따라 dict 자료형을 이용하여 메모이제이션을 할 수 있다. </li>
<li><code>sys.setrecursionlimit()</code>을 사용해 재귀함수 호출 제한을 완화할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 완벽 가이드] 5. 웹 서버]]></title>
            <link>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-5.-%EC%9B%B9-%EC%84%9C%EB%B2%84</link>
            <guid>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-5.-%EC%9B%B9-%EC%84%9C%EB%B2%84</guid>
            <pubDate>Sun, 04 Aug 2024 00:41:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 &#39;HTTP 완벽 가이드&#39; 책을 읽고 정리한 내용입니다.</p>
</blockquote>
<blockquote>
<p><strong>[이번 장에서]</strong></p>
<ul>
<li>여러 종류의 소프트웨어 및 하드웨어 웹 서버에 대해 조사한다.</li>
<li>어떻게 웹 서버가 HTTP 트랜잭션을 처리하는지 단계별로 설명한다.</li>
</ul>
</blockquote>
<h2 id="다채로운-웹-서버">다채로운 웹 서버</h2>
<blockquote>
<p><strong>[웹 서버의 역할]</strong></p>
<ul>
<li>웹 서버는 HTTP 요청을 처리하고 응답을 제공한다.</li>
<li>웹 서버는 자신이 제공하는 리소스를 관리하고 웹 서버를 <strong>설정, 통제, 확장</strong>하기 위한 관리 기능을 제공한다.</li>
</ul>
</blockquote>
<h4 id="아파치-톰캣">아파치 톰캣?</h4>
<ul>
<li>아파치(Apache) <ul>
<li>웹 서버 프로그램 - 오픈 소스 소프트웨어</li>
</ul>
</li>
<li>톰캣 WAS(web application server)<ul>
<li>JSP와 Servlet을 구동하기 위한 서블릿 컨테이너 역할 수행</li>
<li>아파치 서버와는 다르게 DB연결, 다른 응용프로그램과 상호 작용 등 동적인 기능들을 사용할 수 있다.   </li>
</ul>
</li>
<li>아파치 톰캣이라고 불리는 이유가 뭐지?</li>
</ul>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/7bec4915-513f-46a1-ad33-9b96f2028c45/image.png 
         width =500 align = center></p>
<ul>
<li>기본적으로 위처럼 아파치와 톰캣의 기능은 나뉘어져 있지만, 톰캣 안에 있는 컨테이너를 통해 일부 아파치의 기능을 발휘하기 때문에 보통 아파치 톰캣으로 합쳐서 부르곤 한다.</li>
<li><a href="https://velog.io/@kdhyo/Apache-Tomcat-%EB%91%98%EC%9D%B4-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4%EC%A7%80">참고 블로그</a></li>
</ul>
<p><strong>임베디드 웹 서버</strong> </p>
<ul>
<li>일반 소비자용 제품에 내장될 목적으로 만들어진 작은 웹 서버(프린터, 가전제품)</li>
<li>일반 소바자용 기기를 웹 브라우저 인터페이스로 관리할 수 있게 해준다.</li>
</ul>
<h2 id="웹-서버가-하는-일">웹 서버가 하는 일</h2>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/25ca0f0e-4256-4f89-9794-d04123bb8c1a/image.png 
     width =400 align = center></p>
<ol>
<li><strong>커넥션을 맺는다</strong> : 클라이언트의 접속 허용 및 차단</li>
<li><strong>요청을 받는다</strong> : HTTP 요청 메세지를 읽기</li>
<li><strong>요청을 처리한다</strong> : 요청 메세지 해석 및 처리</li>
<li><strong>리소스에 접근한다</strong> : 메세지에서 지정한 리소스에 접근</li>
<li><strong>응답을 만든다</strong> : HTTP 응답 메세지 생성</li>
<li><strong>응답을 보낸다</strong> : 클라이언트에게 응답 반환</li>
<li><strong>트랜잭션을 로그로 남긴다</strong> : 로그파일에 트랜잭션 완료에 대한 기록</li>
</ol>
<h3 id="1-클라이언트-커넥션-수락">1. 클라이언트 커넥션 수락</h3>
<blockquote>
<p>웹 서버가 클라이언트의 커넥션을 수락하는 단계</p>
</blockquote>
<p>지속적 커넥션이 있다면 해당 커넥션으로 요청이 가능하고 없다면 새로운 커넥션을 열어야 한다.</p>
<h4 id="새-커넥션-다루기">새 커넥션 다루기</h4>
<ol>
<li>클라이언트의 TCP 커넥션 요청</li>
<li>웹 서버의 TCP 커넥션 수락 및 클라이언트 IP 확인</li>
<li>웹 서버의 데이터 수신 대기 </li>
</ol>
<ul>
<li>웹 서버는 어떤 커넥션이든 마음대로 거절하거나 즉시 닫을 수 있다.<ul>
<li>클라이언트 IP 주소 혹은 호스트 명이 인가되지 않은 경우</li>
</ul>
</li>
</ul>
<h4 id="웹-서버의-클라이언트-식별">웹 서버의 클라이언트 식별</h4>
<blockquote>
<p><strong>역방향 DNS</strong> </p>
<ul>
<li>클라이언트의 IP에 해당하는 도메인이 있는지 검사하는 과정으로 호스트 명을 반환하여 확인한다.</li>
<li>IP -&gt; 도메인 (정방향으로는 도메인 -&gt; IP 이다.)</li>
</ul>
</blockquote>
<ul>
<li>웹 서버는 클라이언트 호스트 명을 구체적인 접근 제어, 로깅을 위해 사용할 수 있다. </li>
<li>호스트 명 룩업은 시간이 많이 걸려 트랜잭션을 느력지게 할 수 있다.</li>
</ul>
<blockquote>
<p><strong>IETF ident 프로토콜</strong></p>
<ul>
<li>서버에게 어떤 사용자 이름이 HTTP 커넥션을 초기화 했는지 찾아낼 수 있게 해준다. </li>
</ul>
</blockquote>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/61c9a953-61d7-4b4e-8fe1-ba459a871a29/image.png 
     width =500 align = center></p>
<p>한마디로, TCP 커넥션 이후  ident 커넥션을 열어 웹서버가 클라이언트의 정보를 요청하는 과정을 통해 클라이언트 정보를 얻는다.</p>
<p>ident는 조직 내부에서는 잘 사용 가능하지만, 공용 네트워크에서는 여러가지 이유로 잘 동작하지 않는다.</p>
<ul>
<li>ident는 HTTP 트랜잭션을 유의미하게 지연시킨다.</li>
<li>방화벽이 ident 트래픽이 들어오는 것을 막는 경우가 많다.</li>
</ul>
<h3 id="2-요청-메세지-수신">2. 요청 메세지 수신</h3>
<blockquote>
<p>커넥션 데이터를 읽고 파싱하여 요청 메세지를 구성하는 단계</p>
</blockquote>
<p><strong>웹 서버의 요청 메세지 파싱</strong></p>
<ul>
<li>요청줄을 파싱하여 요청 메서드, URL, 버전 번호(프로토콜) 정보를 읽는다.</li>
<li>CRLF를 기준으로 각 헤더와 본문의 내용을 읽는다.</li>
</ul>
<p>요청 메세지 파싱 과정에서 입력 데이터를 불규칙적으로 받기 때문에 커넥션이 무효화 될 수 있다.</p>
<ul>
<li>웹 서버는 파싱해서 이해 가능한 수준을 확보할때까지 메세지 일부를 메모리에 저장해둘 필요가 있다.</li>
</ul>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/30a291d7-556e-47de-9831-5e473d7c4277/image.png 
         width =500></p>
<p>위 사진과 같이 요청 메세지에서 정보를 내부 자료 구조에 따라 저장한다. </p>
<ul>
<li>이는 요청 메세지를 쉽게 다루기 위한 웹 서버의 기능이다.</li>
</ul>
<h4 id="커넥션-io-처리-아키텍처">커넥션 I/O 처리 아키텍처</h4>
<blockquote>
<p>클라이언트로부터 동시에 들어오는 수많은 커넥션 요청을 처리하기 위해 4가지 아키텍처가 있다.</p>
</blockquote>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/5485fdd6-61c2-46b4-b2ac-5f102dbc4efd/image.png 
     width =500></p>
<p><strong>1. 단일 스레드 웹서버</strong></p>
<ul>
<li>한번에 하나의 요청을 처리하며 트랜잭션 완료후, 다음 커넥션을 처리한다.</li>
<li>처리 과정 중 다른 커넥션은 무시된다. </li>
</ul>
<p><strong>2.멀티 프로세스와 멀티 스레드 웹 서버</strong></p>
<ul>
<li>여러개의 프로세스 혹은 스레드가 여러 요청을 동시에 처리한다.</li>
<li>프로세스/스레드는 미리 만들어 질 수 있고 매 커넥션마다 한개를 할당한다. <ul>
<li>너무 많이 생성될 경우 리소스를  많이 소비하기 때문에 최대 개수 제한을 건다.</li>
</ul>
</li>
<li><em>3. 다중 I/O 서버*</em></li>
<li><strong>많은 커넥션을 처리하기 위해 대부분의 웹 서버가 해당 아키텍처를 채택</strong></li>
<li>모든 커넥션의 활동을 감시해 상태가 바뀔때(데이터 사용가능 및 에러 발생) 작은 양의 처리를 수행한다.</li>
<li>처리 완료 후, 상태 변경을 위해 &#39;열린 커넥션 목록&#39;으로 돌아가 다시 차례를 기다린다. </li>
</ul>
<p><strong>4. 다중 멀티스레드 웹 서버</strong></p>
<ul>
<li><p>멀티 스레딩과 다중화를 결합한 경우로 여러 개의 CPU의 이점을 살릴 수 있다. </p>
</li>
<li><p>여러 개의 스레드가 각 열려있는 커넥션을 감시하고 조금씩 작업을 수행한다. </p>
<h3 id="3-요청-처리">3. 요청 처리</h3>
<blockquote>
<p>웹 서버가 받은 요청을 메서드, 리소스, 헤더, 본문을 얻어 처리하는 단계</p>
</blockquote>
</li>
<li><p>요청 메서드에 따라 엔터티 본문이 있을 것을 요구한다.(POST, OPTIONS 등은 허용하되 요구하진 않음)</p>
<h3 id="4-리소스의-매핑과-접근">4. 리소스의 매핑과 접근</h3>
<blockquote>
<p>HTML 페이지, JPEG 와 같은 <strong>정적 콘텐츠</strong> 및 서버 위에서 동작하는 생성 애플리케이션을 통해 만들어진 <strong>동적 콘텐츠</strong>를 제공</p>
</blockquote>
</li>
</ul>
<h4 id="docroot">Docroot</h4>
<blockquote>
<p>요청 URL을 웹 서버의 파일 시스템 안에 있는 파일 이름으로 사용하는 것으로 웹 콘텐츠를 위해 예약해둔 특별한 폴더를 <strong>문서 루트</strong> 혹은 <strong>docroot</strong>라고 한다.</p>
</blockquote>
<p>요청 URL : /specials/saw-blade.gif
docroot : /usr/local/httpd/files</p>
<p>웹 서버의 반환 : /usr/local/httpd/files/specials/saw-blade.gif</p>
<ul>
<li>서버는 상대적인 url이 docroot를 벗어나 docroot 이외 부분이 노출되는 일이 생기지 않도록 주의해야한다.</li>
</ul>
<p><strong>가상 호스팅된 docroot</strong></p>
<blockquote>
<p>분리된 docroot를 주는 방법으로 하나의 웹 서버 위에 두 개의 사이트가 완전히 분리된 콘텐츠를 갖고 호스팅 되도록 할 수 있다.</p>
</blockquote>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/951c7482-23ca-4ab8-8ea2-199764da625f/image.png 
     width =500></p>
<p>요청에 따라 다른 docroot를 통해 다른 파일을 반환할 수 있다.
요청 A : /docs/joe/index.html
요청 B : /docs/marry/index.html</p>
<p><strong>사용자 홈 디렉터리 docroot</strong></p>
<blockquote>
<p>사용자들이 하나의 웹 서버에서 개인 웹 사이트를 만들 수 있도록 해주는 것</p>
</blockquote>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/0e20578d-1744-404b-bb46-ca25df66523e/image.png 
     width =500></p>
<ul>
<li>**/~(사용자 이름)/index.html&quot;</li>
<li>개인 docroot는 주로 사용자의 홈 디렉터리 안에 있는 public_html로 불리는 디렉터리이다.(설정에 따라 다를 수 있다.)</li>
</ul>
<blockquote>
<p><strong>[파일이 아닌 디렉터리를 가리키는 경로에 대한 요청]</strong>
일반적으로 서버는 다음과 같은 행동을 취한다.</p>
<ol>
<li>에러 반환</li>
<li>디렉터리 대신 특별한 &#39;색인 파일&#39; 반환</li>
<li>디텍터리 탐색 후 그 내용을 담은 HTML 페이지 반환</li>
</ol>
<ul>
<li>아파치에선 DirectoryIndex 설정을 통해 기본 디렉터리 파일로 사용될 파일 이름 집합을 설정할 수 있다.<ul>
<li>이에 따라 나열된 파일 중 하나를 반환하게 되는 것</li>
</ul>
</li>
<li>하지만, 2,3번과 같이 할 경우, 일반적으로 발견할 수 없는 파일이 드러날 수 있기 때문에 해당 기능을 끌 수 있다.</li>
</ul>
</blockquote>
<blockquote>
<p><strong>[동적 콘텐츠 리소스 매핑]</strong>
웹 서버는 URL을 요청에 맞게 콘텐츠를 생성하는 프로그램에 매핑할 수 있다. </p>
<ul>
<li>아파치는 서버가 실행 가능한 경로명을 포함한 URL로 요청을 받으면, 그 경로에 대응하는 디렉터리에서 프로그램을 찾아 실행하려 시도한다.</li>
<li>특정 확장자의 파일만 실행하도록 설정이 가능하다.</li>
</ul>
</blockquote>
<p>많은 웹 서버가 서버사이드 인클루드도 지원하고 이는 &#39;#include&#39; 지시문을 사용하여 하나 이상의 파일 내용을 웹 서버의 웹 페이지에 포함 시키는데 유용하다. (<a href="https://ko.wikipedia.org/wiki/Server_Side_Includes">참고</a>)
서버는 콘텐츠에 변수 이름, 내장된 스크립트가 될 수 있는 특별한 패턴이 있는지 검사 받고 이는 변수 값, 실행 가능한 스크립트의 출력 값으로 치환된다. 이는 동적 콘텐츠를 만드는 쉬운 방법이다.
(코드 상에서 변수 이름과 같이 선언된 것을 값으로 치환하여 클라이언트에게 제공한다는 의미인 것 같다.)
(동적 콘텐츠란 DB 상태에 따라 다르게 출력이 되는 콘텐츠 같은 것을 의미)</p>
<h3 id="5-응답-만들기">5. 응답 만들기</h3>
<blockquote>
<p>서버가 리소스 식별 후, 요청 메서드로 서술되는 동작을 수행한 뒤 응답 메세지를 반환하는 단계</p>
<ul>
<li>응답 메세지의 구성요소</li>
</ul>
<ol>
<li>응답 상태 코드</li>
<li>응답 헤더</li>
<li>응답 본문(있다면)</li>
</ol>
</blockquote>
<p><strong>응답 본문</strong>이 있다면 다음과 같은 내용을 포함한다. </p>
<ul>
<li>응답 본문의 MIME 타입을 서술하는 Content-Type 헤더</li>
<li>응답 본문의 길이를 서술하는 Content-Length 헤더</li>
<li>실제 응답 본문의 내용<h4 id="mime-타입-결정">MIME 타입 결정</h4>
MIME 타입과 리소스를 연결하는 여러 가지 방법은 다음과 같다.
(MIME는 미디어 타입으로 문서, 파일, 바이트 집합의 특징을 나타내는 것이다.)</li>
</ul>
<p><strong>mime.types</strong></p>
<ul>
<li>확장자별 MIME 타입이 담겨 있는 파일을 탐색하여 리소스의 타입을 계산<img src =https://velog.velcdn.com/images/codejugller-9999/post/648bb930-79f2-4af3-93a7-75fb5ff36191/image.png width =500>


</li>
</ul>
<p><strong>매직 타이핑(Magic typing)</strong></p>
<ul>
<li>아파치 웹 서버는 파일의 내용을 검사해 알려진 패턴에 대한 테이블에 해당하는 패턴이 있는지 찾아 결정할 수 있다.</li>
<li>느리지만 파일이 표준 확장자 없이 이름이 지어진 경우에는 특히 편리하다.</li>
</ul>
<p><strong>유형 명시(Explicit typing)</strong></p>
<ul>
<li>특정 파일, 디렉터리 안의 파일들이 확장자나 내용에 상관없이 어떤 MIME 타입을 갖도록 웹 서버를 설정할 수 있다.</li>
</ul>
<p><strong>유형 협상(Type negotiation)</strong></p>
<ul>
<li>한 리소스가 여러 종류의 문서 형식에 속하도록 설정하는 것으로 서버가 사용자와 협상 과정을 통해 사용하기 가장 좋은 형식을 판별한 것인지 여부도 설정이 가능하다. </li>
<li>웹 서버는 특정 파일이 특정 MIME 타입을 갖게끔 설정할 수도 있다. </li>
</ul>
<h4 id="리다이렉션">리다이렉션</h4>
<p>요청을 수행하기 위해 종종 다른 브라우저로 가도록 리다이렉트 할 수 있다. </p>
<ul>
<li>응답 코드 : 3XX</li>
</ul>
<p>다음과 같은 상황에서 유용하다.</p>
<ul>
<li>영구적으로 리소스가 옮겨진 경우</li>
<li>임시로 리소스가 옮겨진 경우</li>
<li>URL 증강 - 문맥 종보를 포함시키기 위해 재 작성된 URL로 리다이렉트</li>
<li>부하 균형 - 과부화된 서버에 대한 요청을 다른 서버로 리다이렉트<del>(좀 더 자세히 공부해야 할 부분인 것 같다.)</del></li>
<li>디렉터리 이름 정규화 <ul>
<li>클라이언트가 URL 요청 시 빗금(&#39;/&#39;)을 빠뜨렸다면, 대부분의 웹 서버는 상대경로가 정상적으로 동작할 수 있도록 빗금(&#39;/&#39;)을 추가한 URL로 리다이렉트한다.<h3 id="6-응답-보내기">6. 응답 보내기</h3>
<blockquote>
<p>서버가 생성한 응답을 클라이언트로 보내는 단계</p>
<ul>
<li>서버는 여러 커넥션을 맺고 있기 때문에 커넥션 상태를 추적해야한다.<ul>
<li>지속 커넥션이라면 특별히 주의해서 다뤄야한다.(Content-Length 헤더를 바르게 계산해야한다.)</li>
<li>비지속 커넥션이라면 모든 메세지 전송 후 커넥션을 닫아야한다.</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="7-로깅">7. 로깅</h3>
<blockquote>
<p>트랜잭션이 완료되었을 때 트랜잭션이 어떻게 수행되었는지에 대한 로그를 로그파일에 기록하는 단계
<del>(자세한 내용은 21장에서 다룬다.)</del></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘 ]]></title>
            <link>https://velog.io/@codejugller-9999/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@codejugller-9999/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Tue, 30 Jul 2024 09:15:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 &#39;이것이 코딩테스트다 with 파이썬&#39; (나동빈 지음) 책 내용을 정리한 것입니다.</p>
</blockquote>
<h2 id="정렬-알고리즘-개요">정렬 알고리즘 개요</h2>
<blockquote>
<p><strong>정렬(Sorting)</strong>이란 테이터를 특정한 기준에 따라서 순서대로 나열하는 것을 말한다.</p>
</blockquote>
<h2 id="1-선택-정렬">1. 선택 정렬</h2>
<blockquote>
<p>가장 작은 데이터를 선택해서 스왑(swap)을 통해 맨 앞부터 정렬하는 방법</p>
</blockquote>
<p><strong>1. 다음과 같은 배열이 있다고 가정하자</strong></p>
<table>
<thead>
<tr>
<th align="center">7</th>
<th align="center">5</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
</table>
<p>*<em>2. 초기 단계에서는 전체 데이터 중 가장 작은 걸 선택에서 맨 앞에 &#39;7&#39;과 바꾼다. *</em></p>
<table>
<thead>
<tr>
<th align="center">7</th>
<th align="center">5</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">O</td>
<td align="center"></td>
<td align="center"></td>
<td align="center">O</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>3. 가장 작은 &#39;0&#39;은 올바른 위치에 정렬되었기 때문에 이를 제외한 숫자 중 가장 작은 것을 선택해 5와 바꾼다.</strong></p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">5</th>
<th align="center">9</th>
<th align="center">7</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">✅</td>
<td align="center">O</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">O</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>4. 위 과정을 반복하면 최종적으로 배열이 정렬된다.</strong></p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">1</th>
<th align="center">2</th>
<th align="center">3</th>
<th align="center">4</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">7</th>
<th align="center">8</th>
<th align="center">9</th>
</tr>
</thead>
</table>
<h3 id="코드">코드</h3>
<pre><code class="language-python">def selection_sort():
    for i in range(len(array)):
        min_index = i
        for j in range(i + 1, len(array)):
            if array[min_index] &gt; array[j]:
                min_index = j
        array[i], array[min_index] = array[min_index], array[i]

    return array</code></pre>
<h3 id="시간-복잡도">시간 복잡도</h3>
<p><strong>선택 정렬</strong>의 시간 복잡도를 구해보면 다음과 같다.</p>
<ul>
<li>기본적으로 N-1 만큼 가장 작은 수를 찾아서 맨 앞으로 보내야한다.</li>
<li>위 코드로 구현했을 때 연산 횟수<ul>
<li>N + (N - 1) + (N - 2) + ••• + 2 ≈ N(N+1)/2</li>
</ul>
</li>
<li><strong>빅오 표기법</strong>으로 간단히 <strong>O(N²)</strong> 로 표기할 수 있다.</li>
</ul>
<h2 id="2-삽입-정렬">2. 삽입 정렬</h2>
<blockquote>
<p>데이터를 하나씩 확인하여 적절한 위치에 삽입하는 방법</p>
</blockquote>
<ul>
<li>직관적으로 이해하기 쉽고 선택 정렬에 비해 구현은 어렵지만 실행 시간 측면에서 효율적이다.</li>
<li>필요할 때만 위치를 바꾸기 때문에 <strong>데이터가 거의 정렬되어 있을 때</strong> 훨씬 효율적이다.</li>
</ul>
<blockquote>
<p>⬆️는 들어갈 수 있는 위치를 의미한다.</p>
</blockquote>
<p><strong>1. 다음과 같은 배열이 있다고 가정하자</strong></p>
<table>
<thead>
<tr>
<th align="center">7</th>
<th align="center">5</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
</table>
<p><strong>2. 두번째부터 시작해서 &#39;5&#39;는 &#39;7&#39;보다 작기 때문에 &#39;7&#39; 앞인 첫번째 위치에 삽입한다.</strong></p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">7</th>
<th align="center"></th>
<th align="center">5</th>
<th align="center"></th>
<th align="center">9</th>
<th align="center"></th>
<th align="center">0</th>
<th align="center"></th>
<th align="center">3</th>
<th align="center"></th>
<th align="center">1</th>
<th align="center"></th>
<th align="center">6</th>
<th align="center"></th>
<th align="center">2</th>
<th align="center"></th>
<th align="center">4</th>
<th align="center"></th>
<th align="center">8</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center">O</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>3. &#39;7&#39;은 올바른 위치에 있기 때문에 그대로 둔다. (다음 과정인 &#39;9&#39; 또한 변화가 없다.)</strong></p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">5</th>
<th align="center"></th>
<th align="center">7</th>
<th align="center"></th>
<th align="center">9</th>
<th align="center"></th>
<th align="center">0</th>
<th align="center"></th>
<th align="center">3</th>
<th align="center"></th>
<th align="center">1</th>
<th align="center"></th>
<th align="center">6</th>
<th align="center"></th>
<th align="center">2</th>
<th align="center"></th>
<th align="center">4</th>
<th align="center"></th>
<th align="center">8</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center">O</td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>4. &#39;0&#39;은 &#39;5&#39;, &#39;7&#39;, &#39;9&#39; 중에 가장 작기 때문에 &#39;5&#39;앞인 첫번째 위치에 삽입된다.</strong></p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">5</th>
<th align="center"></th>
<th align="center">7</th>
<th align="center"></th>
<th align="center">9</th>
<th align="center"></th>
<th align="center">0</th>
<th align="center"></th>
<th align="center">3</th>
<th align="center"></th>
<th align="center">1</th>
<th align="center"></th>
<th align="center">6</th>
<th align="center"></th>
<th align="center">2</th>
<th align="center"></th>
<th align="center">4</th>
<th align="center"></th>
<th align="center">8</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center">O</td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>5. &#39;3&#39;은 &#39;0&#39;, &#39;5&#39;, &#39;7&#39;, &#39;9&#39; 중에 &#39;0&#39;보다 크고 &#39;5&#39;보다 작기 때문에 두번째 위치에 삽입된다.</strong></p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">0</th>
<th align="center"></th>
<th align="center">5</th>
<th align="center"></th>
<th align="center">7</th>
<th align="center"></th>
<th align="center">9</th>
<th align="center"></th>
<th align="center">3</th>
<th align="center"></th>
<th align="center">1</th>
<th align="center"></th>
<th align="center">6</th>
<th align="center"></th>
<th align="center">2</th>
<th align="center"></th>
<th align="center">4</th>
<th align="center"></th>
<th align="center">8</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center">⬆️</td>
<td align="center">O</td>
<td align="center">⬆️</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>6. 위 과정을 반복하면 최종적으로 배열이 정렬된다.</strong></p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">1</th>
<th align="center">2</th>
<th align="center">3</th>
<th align="center">4</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">7</th>
<th align="center">8</th>
<th align="center">9</th>
</tr>
</thead>
</table>
<h3 id="코드-1">코드</h3>
<pre><code class="language-python">def insertion_sort():
    for i in range(1, len(array)):
        for j in range(i, 0, -1):
            if array[j] &lt; array[j - 1]:
                array[j], array[j - 1] = array[j - 1], array[j]
            else:
                break

    return array</code></pre>
<h3 id="시간-복잡도-1">시간 복잡도</h3>
<p><strong>삽입 정렬</strong>의 시간복잡도는 <strong>O(N²)</strong>이다.(선택 정렬과 마찬가지로 반복문이 두번 수행된다.)
하지만 데이터가 거의 정렬되어 있는 상태라면 매우 빠르게 동작하고 최선의 경우 <strong>O(N)</strong>의 시간 복잡도를 가진다.</p>
<h2 id="3-퀵-정렬">3. 퀵 정렬</h2>
<blockquote>
<p><strong>퀵 정렬</strong>은 가장 많이 사용되는 알고리즘 중 하나이다. </p>
</blockquote>
<ul>
<li>기분 데이터를 설정하고 그 기준보다 큰 데이터와 작은 데이터의 위치를 바꾼다.</li>
</ul>
<p>동작과정을 통해 설명하면 다음과 같다.</p>
<p><strong>1. 다음과 같은 배열이 있다고 가정하자</strong></p>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">7</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
</table>
<p><strong>2. &#39;5&#39;을 p(pivot)로 설정하고 왼쪽에서부터 p보다 큰 데이터를 선택하고 오른쪽에서부터 p보다 작은 데이터를 선택한다. 이후 선택된 &#39;7&#39;, &#39;4&#39;의 위치를 바꾼다.</strong></p>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">7</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">4</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">p</td>
<td align="center">l</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">r</td>
<td align="center"></td>
</tr>
</tbody></table>
<ul>
<li>위치 변환</li>
</ul>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">4</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">7</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">p</td>
<td align="center">l</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">r</td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>3. 다시 왼쪽(이때 바뀐 위치가 시작 기준이 된다.)에서부터 p보다 큰 데이터를 선택하고 오른쪽에서부터 p보다 작은 데이터를 선택한다. 이후 선택된 &#39;9&#39;, &#39;2&#39;의 위치를 바꾼다.</strong></p>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">4</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">7</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">p</td>
<td align="center"></td>
<td align="center">l</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">r</td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<ul>
<li>위치 변환</li>
</ul>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">4</th>
<th align="center">2</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">9</th>
<th align="center">7</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">p</td>
<td align="center"></td>
<td align="center">l</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">r</td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>4. 다시 과정을 반복하면 다음과 같이 r과 l의 위치가 엇갈린 상황이 발생한다. 그럼 p와 r의 위치를 바꿔준다. 이는 p가 전체 배열에서 있어야 할 위치가 되며 더 이상 위치의 변화를 주지 않는다.</strong></p>
<table>
<thead>
<tr>
<th align="center">5</th>
<th align="center">4</th>
<th align="center">2</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">9</th>
<th align="center">7</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center">p</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">r</td>
<td align="center">l</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<ul>
<li>위치 변환</li>
</ul>
<table>
<thead>
<tr>
<th align="center">1</th>
<th align="center">4</th>
<th align="center">2</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">9</th>
<th align="center">7</th>
<th align="center">8</th>
</tr>
</thead>
<tbody><tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">✅</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p>*<em>5. 과정 4에서 &#39;5&#39;를 기준으로 왼쪽과 오른쪽 부분으로 나누어 위와 같은 과정을 반복해서 수행한다. *</em></p>
<h3 id="코드-2">코드</h3>
<h4 id="version-1">version 1</h4>
<pre><code class="language-python">def quick_sort(array, start, end):
    if start &gt;= end:
        return
    pivot = start
    left = start + 1
    right = end

    while left &lt;= right:
        while left &lt;= end and array[left] &lt;= array[pivot]:
            left += 1
        while right &gt; start and array[right] &gt;= array[pivot]:
            right -= 1
        if left &gt; right:
            array[right], array[pivot] = array[pivot], array[right]
        else:
            array[left], array[right] = array[right], array[left]

    quick_sort(array, start, right - 1)
    quick_sort(array, right + 1, end)</code></pre>
<p>위  코드는 퀵 정렬을 직관적으로 이해하기 쉽게 작성한 코드이다. </p>
<h4 id="version-2">version 2</h4>
<pre><code class="language-python">def quick_sort(array):
    if len(array) &lt;= 1:
        return array

    pivot = array[0]
    tail = array[1:]

    left_side = [x for x in tail if x &lt;= pivot]
    right_side = [x for x in tail if x &gt; pivot]

    return quick_sort(left_side) + [pivot] + quick_sort(right_side)</code></pre>
<p>위 코드는 파이썬의 장점을 살려 리스트 컴프리헨션을 사용해 코드를 짧게 작성한 코드이다.</p>
<h3 id="리스트-컴프리헨션이란">리스트 컴프리헨션이란?</h3>
<blockquote>
<p>직관적으로 리스트를 생성하는 방법으로 조건문을 사용하여 조건에 만족하는 값으로만 리스트를 생성할 수 있다.</p>
</blockquote>
<p>코드에서 사용한 것을 보면 다음과 같다. 
<strong>1. 배열을 pivot과 나머지를 나눠서 선언한다.</strong></p>
<pre><code class="language-python">    pivot = array[0]
    tail = array[1:]</code></pre>
<p><strong>2. tail(나머지)에서 리스트 컴프리헨션을 사용해 left와 right을 나눈다.</strong></p>
<pre><code class="language-python">    left_side = [x for x in tail if x &lt;= pivot]
    right_side = [x for x in tail if x &gt; pivot]</code></pre>
<p>디버그로 실행하여 결과를 확인하면 다음과 같다.
<img src="https://velog.velcdn.com/images/codejugller-9999/post/f6cd0ed4-50d8-49ed-ac83-a5110db75d51/image.png" alt=""></p>
<p>pivot &#39;7&#39;를 기준으로 작은 배열의 값을 left, 큰 배열의 값은 right로 리스트가 생성된 것을 볼 수 있다. </p>
<p><strong>3. 이후 나눈 부분 가운데 pivot을 넣고 나머지 정렬과정을 수행한다.</strong></p>
<pre><code class="language-python">    return quick_sort(left_side) + [pivot] + quick_sort(right_side)
</code></pre>
<h3 id="시간-복잡도-2">시간 복잡도</h3>
<ul>
<li><strong>퀵 정렬</strong>의 평균적인 시간복잡도는 <strong>O(NlogN)</strong>이다.</li>
<li>이전 정렬에 비해 매우 빠른 편인데 이와 같은 시간복잡도를 가지는 이유는 다음과 같다.<ul>
<li>N이 8이라면 분할은 총 3번 이루어진다. </li>
</ul>
<ol>
<li>8 -&gt; 4, 4</li>
<li>4 -&gt; 2, 2</li>
<li>2 -&gt; 1, 1</li>
</ol>
<ul>
<li>여기서 log의 밑은 2이며 8은 2의 3제곱이기 때문에 최종적으로 <strong>O(NlogN)</strong>의 시간 복잡도를 가지게 된다. </li>
</ul>
</li>
<li>추가적으로 N이 1000일 때 logN은 약 10 정도되기 때문에 N에 비해서 logN은 매우 작은 수임을 알 수 있다. </li>
</ul>
<h2 id="4-계수-정렬">4. 계수 정렬</h2>
<blockquote>
<p><strong>계수 정렬</strong>은 특정 조건이 부합할때만 사용할 수 있는 방법이다.</p>
</blockquote>
<ul>
<li>데이터의 크기 범위가 제한더되어 정수 형태로 표현할 수 있을 때만 사용가능</li>
<li>일반적으로 데이터의 크기차이가 1,000,000을 넘지 않을 때 효과적</li>
</ul>
<p>동작과정을 통해 설명하면 다음과 같다. </p>
<p><strong>1. 다음과 같은 배열이 있다고 가정하자</strong></p>
<table>
<thead>
<tr>
<th align="center">7</th>
<th align="center">5</th>
<th align="center">9</th>
<th align="center">0</th>
<th align="center">3</th>
<th align="center">1</th>
<th align="center">6</th>
<th align="center">2</th>
<th align="center">9</th>
<th align="center">1</th>
<th align="center">4</th>
<th align="center">8</th>
<th align="center">0</th>
<th align="center">5</th>
<th align="center">2</th>
</tr>
</thead>
</table>
<p><strong>2. 데이터의 범주 크기에 맞는 배열을 선언한다.</strong></p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">1</th>
<th align="center">2</th>
<th align="center">3</th>
<th align="center">4</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">7</th>
<th align="center">8</th>
<th align="center">9</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
</tbody></table>
<p><strong>3. 데이터를 하나씩 확인해 몇번 등장하는지 카운트한다.</strong></p>
<p>** 7 ** 5 9 0 3 1 6 2 9 1 4 8 0 5 2 </p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">1</th>
<th align="center">2</th>
<th align="center">3</th>
<th align="center">4</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">7</th>
<th align="center">8</th>
<th align="center">9</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center"><strong>1</strong></td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
</tbody></table>
<p>.. (과정 반복) ..</p>
<table>
<thead>
<tr>
<th align="center">0</th>
<th align="center">1</th>
<th align="center">2</th>
<th align="center">3</th>
<th align="center">4</th>
<th align="center">5</th>
<th align="center">6</th>
<th align="center">7</th>
<th align="center">8</th>
<th align="center">9</th>
</tr>
</thead>
<tbody><tr>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">2</td>
</tr>
</tbody></table>
<p><strong>4. 횟수를 카운트한 배열의 출력을 반복한다.</strong></p>
<p><strong>0 0 1 1 2 2 3 4 5 5 6 7 8 9 9</strong></p>
<h3 id="코드-3">코드</h3>
<pre><code class="language-python">array = [7, 5, 9, 0, 3, 1, 6, 2, 9, 1, 4, 8, 0, 5, 2]

count = [0] * (max(array) + 1)

for i in range(len(array)):
    count[array[i]] += 1

for i in range(len(count)):
    for j in range(count[i]):
        print(i, end = &#39; &#39;)</code></pre>
<h3 id="시간-복잡도-3">시간 복잡도</h3>
<p> 모든 데이터가 양의 정수인 상황에서 데이터 개수 N, 최대값의 크기를 K라 할 때, 계수 정렬의 시간 복잡도는 <strong>O(N+K)</strong>로 매우 빠른편이다.
하지만, 공간 복잡도 또한 <strong>O(N+K)</strong>이기 때문에 만약, 데이터가 &#39;0&#39;, &#39;999,999&#39; 두개만 존재할 때도 1,000,000개의 리스트를 선언해야하는 비효율적인 상황이 발생할 수 있다. </p>
<blockquote>
<p><strong>[파이썬의 정렬 라이브러리]</strong>
<code>sort(), sorted()</code>등의 함수는 최악의 경우에도 시간 복잡도 <strong>O(NlogN)</strong>을 보장한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1012. 유기농 배추]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1012.-%EC%9C%A0%EA%B8%B0%EB%86%8D-%EB%B0%B0%EC%B6%94</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1012.-%EC%9C%A0%EA%B8%B0%EB%86%8D-%EB%B0%B0%EC%B6%94</guid>
            <pubDate>Sun, 28 Jul 2024 04:19:10 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1012">문제 링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/b5851d1e-9168-4280-98fa-daf3f21c4252/image.png" alt=""></p>
<p>일반적인 dfs문제로 1인 노드를 탐색해서 연결된 영역의 개수를 카운트하는 문제이다. 일반적으로 이와 같은 문제는 예제로 전체 맵에 대한 입력으로 하는 반면 해당 문제는 1인 노드의 좌표 값을 입력으로 받는다. 그래서 입력받은 맵의 크기만큼 모든 값이 0인 2차원 배열을 선언한 후 해당 입력 받은 좌표 값을 1로 바꿔준 후에 로직을 수행하도록 접근했다.</p>
<h2 id="문제-상황">문제 상황</h2>
<p>다음과 같이 코드를 작성했는데 예제 입력에 대한 출력이 제대로 되지 않는 것을 확인했다. 디버그 모드로 실행해서 코드에서 문제가 되는 부분을 찾았는데 다음 부분에서 문제가 발생했다.</p>
<h3 id="2차원-배열-선언">2차원 배열 선언</h3>
<p>코드에서 모든 값인 0인 2차원 배열 선언을 한 후에 배열에서 입력받은 좌표 값을 1로 바꿔주었다.</p>
<pre><code class="language-python">graph = [[0] * M] * N  

for j in range(K):  
    x, y = map(int, input().split())  
    graph[y][x] = 1  </code></pre>
<p>해당 로직을 수행 후에 graph를 출력해봤는데,, 다음과 같이 출력이 되는 것을 확인했다.</p>
<pre><code class="language-plain">[
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], 
    [1, 1, 1, 1, 1, 0, 0, 1, 1, 1]
]</code></pre>
<p><code>graph[0][0] = 1</code> 로 바꿔서 확인해보니 문제를 파악할 수 있었다. 나는 첫번째 배열의 첫번째 요소의 값만 바꿧지만 모든 배열의 첫번째 요소가 1로 바뀐 것을 볼 수 있다. </p>
<pre><code class="language-plain">[
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]</code></pre>
<h4 id="얕은-복사">얕은 복사</h4>
<p>결론은 위와 같이 2차원 배열을 선언할 경우 list의 얕은 복사가 발생한다. 즉 <code>[0] * M]</code> 길이의 list가 얕은 복사로 단순히 N개 생기는 것. 얕은 복사는 객체를 참조하기 때문에 하나의 값만 바뀌어도 나머지가 전부 바뀌게 된다.<del>(for문 쓰는 것을 피하려다 아주 기본적인 부분에서 놓쳤다.. )</del></p>
<h3 id="코드">코드</h3>
<pre><code class="language-python"># 유기농 배추  
T = int(input())  

dx = [-1, 1, 0, 0]  
dy = [0, 0, -1, 1]  

for i in range(T):  
    M, N, K = map(int, input().split())  

    graph = [[0] * M] * N  

    for j in range(K):  
        x, y = map(int, input().split())  
        graph[y][x] = 1  


    def dfs(x, y):  
        graph[x][y] = 0  

        for k in range(4):  
            nx = dx[k] + x  
            ny = dy[k] + y  

            if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; M and graph[nx][ny] == 1:  
                dfs(nx, ny)  


    result = 0  
    for y in range(N):  
        for m in range(M):  
            if graph[y][m] == 1:  
                dfs(y, m)  
                result += 1  

    print(result)</code></pre>
<h2 id="해결-방법">해결 방법</h2>
<p>2차원 배열을 다음과 같이 선언해서 얕은 복사가 일어나지 않게 했고 추가적으로 <code>[런타임 에러 (RecursionError)]</code>가 발생하는 것을 확인해서 코드를 추가해줬다.</p>
<pre><code class="language-python">import sys  
sys.setrecursionlimit(10**6)

...

# graph = [[0] * M] * N  
graph = [[0 for col in range(M)] for row in range(N)]  

for j in range(K):  
    x, y = map(int, input().split())  
    graph[y][x] = 1  

</code></pre>
<h3 id="코드-1">코드</h3>
<pre><code class="language-python"># 유기농 배추
import sys
sys.setrecursionlimit(10**6)
T = int(input())

dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

for i in range(T):
    M, N, K = map(int, input().split())

    graph = [[0 for col in range(M)] for row in range(N)]

    for j in range(K):
        x, y = map(int, input().split())
        graph[y][x] = 1


    def dfs(x, y):
        graph[x][y] = 0

        for k in range(4):
            nx = dx[k] + x
            ny = dy[k] + y

            if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; M and graph[nx][ny] == 1:
                dfs(nx, ny)


    result = 0
    for y in range(N):
        for m in range(M):
            if graph[y][m] == 1:
                dfs(y, m)
                result += 1

    print(result)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 4963. 섬의 개수]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-4963.-%EC%84%AC%EC%9D%98.-%EA%B0%9C%EC%88%98</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-4963.-%EC%84%AC%EC%9D%98.-%EA%B0%9C%EC%88%98</guid>
            <pubDate>Fri, 26 Jul 2024 03:47:43 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/4963">문제 링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/79ff0e2e-4c86-4d18-bd57-9b79112b9f21/image.png" alt=""></p>
<p>전체 맵에서 연결된 섬의 개수를 카운트하는 문제로 그래프 탐색으로 문제를 해결할 수 있다. 전체 노드를 탐색하면서 연결된 노드를 재귀방식으로 계속 탐색하고 최종적으로 더 이상 연결된 섬이 없을 때 True 값을 반환하여 개수를 카운트하는 dfs방식으로 접근해봤다.</p>
<h2 id="문제-상황">문제 상황</h2>
<p>다음과 같이 코드를 작성했는데 <code>[런타임 에러 (RecursionError)]</code> 발생했다. 8방향으로 섬(1인 값)인지 확인하기 때문에 재귀 호출이 너무 많은 것 같다고 판단.</p>
<p>인터넷에서 찾아본 결과 파이썬 자체에서 재귀호출할 수 있는 max값이 정해져있어  <code>sys.setrecursionlimit(10 ** 6)</code> 코드를 추가해 해당 값을 임의로 설정했다.</p>
<p>하지만, 이번에는 틀렸다고 결과가 나왔다.</p>
<h3 id="코드">코드</h3>
<pre><code class="language-python"># 섬의 개수  
import sys  

read = sys.stdin.readline  
sys.setrecursionlimit(10 ** 6)  


def dfs(x, y):  
    if x &lt; 0 or x &gt;= h or y &lt; 0 or y &gt;= w:  
        return False   
    if graph[x][y] == 1:  
        graph[x][y] = 0  
        dfs(x - 1, y - 1)  
        dfs(x, y - 1)  
        dfs(x + 1, y - 1)  
        dfs(x - 1, y)  
        dfs(x + 1, y)  
        dfs(x - 1, y + 1)  
        dfs(x, y + 1)  
        dfs(x + 1, y + 1)  
        return True  
    return False  

while True:  
    w, h = map(int, read().split())  

    if w == 0 and h == 0:  
        break  
    graph = []  
    for i in range(h):  
        graph.append(list(map(int, read().split())))  

    result = 0  

    for i in range(w):  
        for j in range(h):  
            if dfs(i, j):  
                result += 1  

    print(result)</code></pre>
<h2 id="해결-방법">해결 방법</h2>
<p>결론은,,, 재귀 호출 자체를 하지 않도록 코드를 짜야한다는 것이였다.
위에서 정의한 dfs 메서드를 보면 일단 dfs함수를 호출하고 해당 위치의 값이 전체 범위를 벗어났는지 혹은 섬(1)이 아닌지를 판단하여 False를 반환하는데 이 과정 자체를 조건문으로 필터링하여 호출되는 경우 자체를 줄여야한다.</p>
<pre><code class="language-python">def dfs(x, y):  
    if x &lt; 0 or x &gt;= h or y &lt; 0 or y &gt;= w:  
        return False   
    if graph[x][y] == 1:  
        graph[x][y] = 0  
        dfs(x - 1, y - 1)  
        dfs(x, y - 1)  
        dfs(x + 1, y - 1)  
        dfs(x - 1, y)  
        dfs(x + 1, y)  
        dfs(x - 1, y + 1)  
        dfs(x, y + 1)  
        dfs(x + 1, y + 1)  
        return True  
    return False  </code></pre>
<h3 id="코드-1">코드</h3>
<pre><code class="language-python"># 섬의 개수  
import sys  

read = sys.stdin.readline  
sys.setrecursionlimit(10 ** 6)  

dx = [-1, 0, 1, -1, 1, -1, 0, 1]  
dy = [-1, -1, -1, 0, 0, 1, 1, 1]  

def dfs(x, y):  
    graph[x][y] = 0  
    for i in range(8):  
        nx = x + dx[i]  
        ny = y + dy[i]  
        if 0 &lt;= nx &lt; h and 0 &lt;= ny &lt; w and graph[nx][ny] == 1:  
            dfs(nx, ny)  


while True:  
    w, h = map(int, read().split())  

    if w == 0 and h == 0:  
        break  
    graph = []  
    for i in range(h):  
        graph.append(list(map(int, read().split())))  

    result = 0  

    for i in range(h):  
        for j in range(w):  
            if graph[i][j] == 1:  
                dfs(i, j)  
                result += 1  

    print(result)</code></pre>
<h4 id="dfs">dfs</h4>
<p>이와 같이 외부에서 조건문으로 필터링하여 재귀호출 자체가 수행되지 않게 하도록함</p>
<pre><code class="language-python">if graph[i][j] == 1:  
        dfs(i, j)  
        result += 1 

...

def dfs(x, y):  
    graph[x][y] = 0  
    for i in range(8):  
        nx = x + dx[i]  
        ny = y + dy[i]  
        if 0 &lt;= nx &lt; h and 0 &lt;= ny &lt; w and graph[nx][ny] == 1:  
            dfs(nx, ny)  
</code></pre>
<h4 id="결론-및-피드백">결론 및 피드백</h4>
<ul>
<li>일반적으로 재귀호출을 통해 구현하는 dfs보다 queue를 사용하여 구현하는 bfs를 먼저 생각해보자<ul>
<li>왜냐하면, 상대적으로 더 빠르게 코드가 동작한다.</li>
</ul>
</li>
<li>문제와 별개로 x, y 값이 2차우너 배열에서 문제에서 w,h 값 중 어떤 것인지 종종 헷갈리는 상황을 발견했다. 로직이 맞더라도 반복문을 잘못 작성하면 문제가 생길 수 있기 때문에 유의해야 할 것 같다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1260. DFS와 BFS]]></title>
            <link>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1260.-DFS%EC%99%80-BFS</link>
            <guid>https://velog.io/@codejugller-9999/%EB%B0%B1%EC%A4%80-1260.-DFS%EC%99%80-BFS</guid>
            <pubDate>Thu, 25 Jul 2024 07:05:40 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1260">문제 링크</a></p>
<h2 id="문제-설명">문제 설명</h2>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/0f198b6e-b573-4e1f-af25-bfb50c801a8a/image.png" alt=""></p>
<p>그래프에 대한 노드, 간선 정보가 주어지고 DFS, BFS로 그래프 탐색 결과를 출력하는 문제이다. 일반적으로 DFS는 재귀, BFS는 큐 방식으로 구현한다. 이전에 책으로 공부하면서 간단한 구현을 하는 방법에 대해서 알고 있어서 크게 어렵지 않을거라고 생각했는데 푸는데 생각보다 어려움이 있었다.(정답율이 생각보다 낮은 이유가 있었다..)</p>
<p>답을 도출하기 전에 이전에 책으로 공부하면서 구현했는 DFS, BFS 코드를 먼저 보자
<strong>DFS, BFS 구현 예제</strong></p>
<pre><code class="language-python"># dfs  
def dfs(graph, v, visited):  
    visited[v] = True  
    print(v, end=&#39; &#39;)  

    for i in graph[v]:  
        if not visited[i]:  
            dfs(graph, i, visited)  


graph = [  
    [],    
    [2, 3, 8],  
    [1, 7],  
    [1, 4, 5],  
    [3, 5],  
    [3, 4],  
    [7],  
    [2, 6, 8],  
    [1, 7]  
]  

visited = [False] * 9  

dfs(graph, 1, visited)  

</code></pre>
<pre><code class="language-python"># bfs  
from collections import deque  
def bfs(graph, start, visited):  
    queue = deque([start])  

    visited[start] = True  

    while queue:  
        v = queue.popleft()  

        print(v, end=&#39; &#39;)  

        for i in graph[v]:  
            if not visited[i]:  
                queue.append(i)  
                visited[i] = True  

graph = [  
    [],    
    [2, 3, 8],  
    [1, 7],  
    [1, 4, 5],  
    [3, 5],  
    [3, 4],  
    [7],  
    [2, 6, 8],  
    [1, 7]  
]  

visited = [False] * 9  

bfs(graph, 1, visited)</code></pre>
<p>해당 예제는 &#39;이것이 코딩 테스트다 with python&#39; 책에서 나온 구현방법이며 가장 기본적인 형태를 가지고 있다. 
문제와 다른 점은 그래프에 대한 정보가 어떻게 주어지는지이다.</p>
<pre><code class="language-python"># 예제
graph = [  
    [],    
    [2, 3, 8],  
    [1, 7],  
    [1, 4, 5],  
    [3, 5],  
    [3, 4],  
    [7],  
    [2, 6, 8],  
    [1, 7]  
]

# 문제
graph = [
    [1, 2],
    [1, 3],
    [1, 4],
    [2, 4],
    [3, 4]
]
</code></pre>
<ul>
<li>예제는 <code>graph[i][:]</code> 로 i번째 노드가 몇번 노드와 연결되어있는지 정보가 나열되어있다.</li>
<li>문제는 2차원 배열로 하나의 요소가 연결된 어떤 노드끼리 연결되어 있는지 정보가 나열되어있다.</li>
</ul>
<h2 id="문제-상황">문제 상황</h2>
<p>그래서 리스트 컴프리헨션으로 <code>list = [i[1] for i in graph if i[0] == v]</code> 해당 노드가 연결되어 있는 노드의 정보를 추출해서 비슷한 방식으로 풀려고 했으나 당연히 되지 않았다.
(여기서 v는 시작 노드이며 i[1]을 추출해 해당 노드가 어떤 노드와 연결되어있는지 추출해 배열로 만들었다.)</p>
<ul>
<li>왜냐하면 각 요소의 i[0]가 아닌 i[1]인 경우는 고려하지 않기 때문이다.</li>
<li>예를 들어 [1, 4], [2, 1] 처럼 있다고 가정하자 위와 사실상 &#39;1&#39;노드는 &#39;2&#39;, &#39;4&#39;와 연결되어있는데 위와 같이 코드를 작성하면 &#39;2&#39; 노드에 방문을 하지 못한다. </li>
</ul>
<h2 id="해결-방법">해결 방법</h2>
<p>입력 받은 노드의 연결 정보에서 각 요소의 위치를 바꿔 <code>graph</code>에 추가해준다. 코드로 보면 다음과 같다.</p>
<pre><code class="language-python"># 기존 정보
graph = [
    [1, 2],
    [1, 3],
    [1, 4],
    [2, 4],
    [3, 4]
]
# 위치 변경 
change_location = [
    [2, 1],
    [3, 1],
    [4, 1],
    [4, 2],
    [4, 3]
]
# 최종 결과
result = [
    [1, 2],
    [1, 3],
    [1, 4],
    [2, 4],
    [3, 4],
    [2, 1],
    [3, 1],
    [4, 1],
    [4, 2],
    [4, 3]
]</code></pre>
<p>이처럼 위치를 바꾼정보를 탐색할 정보 리스트에 합쳐주면 해결할 수 있다. 어짜피 재방문한 경우 코드 상에서 <code>dfs</code> 메서드를 호출하지 않도록 구현하기 때문에 제대로 동작하는걸 확인할 수 있었다.</p>
<h3 id="정답-코드">정답 코드</h3>
<pre><code class="language-python"># DFS와 BFSfrom collections import deque  

n, m, v = map(int, input().split())  

graph = []  
for i in range(m):  
    graph.append(list(map(int, input().split())))  

visited_dfs = [False] * (n + 1)  
visited_bfs = [False] * (n + 1)  

list_1 = [i[0] for i in graph]  
list_2 = [i[1] for i in graph]  

for i in range(m):  
    graph.append([list_2[i], list_1[i]])  

def dfs(v):  
    visited_dfs[v] = True  
    print(v, end=&#39; &#39;)  

    list = [i[1] for i in graph if i[0] == v]  
    list.sort()  

    for i in list:  
        if not visited_dfs[i]:  
            dfs(i)  


def bfs(v):  
    queue = deque([v])  

    visited_bfs[v] = True  

    while queue:  
        v = queue.popleft()  

        print(v, end=&#39; &#39;)  

        list = [i[1] for i in graph if i[0] == v]  
        list.sort()  

        for i in list:  
            if not visited_bfs[i]:  
                queue.append(i)  
                visited_bfs[i] = True  

dfs(v)  
print()  
bfs(v)</code></pre>
<h4 id="입력-값의-위치변환-후-병합">입력 값의 위치변환 후 병합</h4>
<pre><code class="language-python">list_1 = [i[0] for i in graph]  
list_2 = [i[1] for i in graph]  

for i in range(m):  
    graph.append([list_2[i], list_1[i]])  </code></pre>
<h4 id="dfs">dfs</h4>
<pre><code class="language-python">def dfs(v):  
    visited_dfs[v] = True  
    print(v, end=&#39; &#39;)  

    list = [i[1] for i in graph if i[0] == v]  
    list.sort()  

    for i in list:  
        if not visited_dfs[i]:  
            dfs(i)  
</code></pre>
<ul>
<li>시작 위치에서의 값으로 연결된 노드의 정보를 리스트로 추출하고 <code>sort</code>함수를 통해 정렬한다.(왜냐하면 문제에서 작은 수부터 방문하라고 했기 때문이다.)</li>
<li>그리고 이전에 알던 구현 방법으로 재귀호출을 통해 그래프 탐색을 한다.</li>
</ul>
<h4 id="bfs">bfs</h4>
<pre><code class="language-python">def bfs(v):  
    queue = deque([v])  

    visited_bfs[v] = True  

    while queue:  
        v = queue.popleft()  

        print(v, end=&#39; &#39;)  

        list = [i[1] for i in graph if i[0] == v]  
        list.sort()  

        for i in list:  
            if not visited_bfs[i]:  
                queue.append(i)  
                visited_bfs[i] = True  </code></pre>
<ul>
<li>dfs와 마찬가지로 시작 위치에서 연결된 노드를 추출하고 queue를 사용하여 bfs를 구현</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 완벽 가이드] 4. 커넥션 관리]]></title>
            <link>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-4.-%EC%BB%A4%EB%84%A5%EC%85%98-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-4.-%EC%BB%A4%EB%84%A5%EC%85%98-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 25 Jul 2024 06:13:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 &#39;HTTP 완벽 가이드&#39; 책을 읽고 정리한 내용입니다.</p>
</blockquote>
<ul>
<li>HTTP는 어떻게 TCP 커넥션을 사용하는가</li>
<li>TCP 커넥션의 지연, 병목, 막힘</li>
<li>병렬 커넥션, keep-alive 커넥션, 커넥션 파이프라인을 활용한 HTTP의 최적화</li>
<li>커넥션 관리를 위해 따라야 할 규칙들 <h1 id="41-tcp-커넥션">4.1 TCP 커넥션</h1>
</li>
</ul>
<blockquote>
<p><strong>TCP/IP</strong>
TCP(Transmission Control Protocol) - 전송 제어 프로토콜로 한 기기에서 다른 기기로 데이터 전송을 담당
IP(Internet Protocol) - 인터넷 프로토콜로 데이터의 조각(fragment)을 대상 IP주소로 보내는 역할</p>
</blockquote>
<h3 id="tcp">TCP</h3>
<ul>
<li>커넥션을 맺을 수 있다. </li>
<li>케넥션이 맺어지면 클라이언트 &lt;-&gt; 서버 컴퓨터 간에 데이터 손실없이 안전하게 전달된다.<h3 id="udp">UDP</h3>
</li>
<li>User Datagram Protocol</li>
<li>커넥션을 맺을 수 없다. </li>
<li>TCP에 비해 안전성은 떨어지지만 더 빠르고 간단하다. (스트리밍, 게임과 같은 곳에서 사용)</li>
</ul>
<h3 id="url입력시-동작-과정">URL입력시 동작 과정</h3>
<ol>
<li>브라우저가 (특정 URL)의 호스트 명을 추출한다. </li>
<li>브라우저가 호스트 명에 대한 IP주소를 찾는다. </li>
<li>브라우저가 포트 번호(80)를 얻는다. </li>
<li>브라우저는 IP주소의 80번 포트로 <strong>TCP 커넥션</strong> 을 생성한다. </li>
<li>이후 클라이언트 &lt;-&gt; 서버의 요청 응답 </li>
<li>브라우저가 커넥션을 끊는다. <h2 id="411-신뢰-할-수-있는-데이터-전송-통로인-tcp">4.1.1 신뢰 할 수 있는 데이터 전송 통로인 TCP</h2>
</li>
</ol>
<ul>
<li>TCP 커넥션은 인터넷을 안정적으로 연결해준다. (신뢰할 만한 통신 방식 제공)</li>
<li>바이트들이 순서에 맞게 정확히 전달된다. (데이터의 손실, 손상 등이 없기 때문에)</li>
</ul>
<h2 id="412-tcp-스트림은-세그먼트로-나뉘어-ip-패킷을-통해-전송된다">4.1.2 TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다.</h2>
<h3 id="https-hypertext-transfer-protocol-secure">HTTPS (Hypertext Transfer Protocol Secure)</h3>
<ul>
<li>기존 http protocol 의 보안 기능을 더한 것</li>
<li>TLS or SSL 이라고 불리는 암호화 계층이 추가 </li>
</ul>
<h3 id="http-메세지-전송">HTTP 메세지 전송</h3>
<ul>
<li>TCP 커넥션을 통해 데이터의 내용을 순서대로 전송</li>
<li>세그먼트라는 단위로 데이터 스트림을 나눈 후에  IP 패킷에 담아 전송</li>
<li>IP 패킷<ul>
<li>IP 패킷 헤더 (20 ~ 60byte) - 20byte 초과 시 option 필드가 존재한다. (in IPv4)</li>
<li>TCP 세그먼트 헤더 (20byte)</li>
<li>TCP 데이터 조각(0 ~ 그 이상의 byte)</li>
</ul>
</li>
</ul>
<h2 id="413-tcp-커넥션-유지하기">4.1.3 TCP 커넥션 유지하기</h2>
<ul>
<li>컴퓨터 여러 개의 TCP 커넥션을 가지고 있다. <ul>
<li>포트 번호를 통해서 여러 커넥션을 유지</li>
</ul>
</li>
<li>TCP 커넥션 식별<ul>
<li><strong>&lt; src ip addr, src port, des ip addr, des port&gt;</strong></li>
<li>이 값들이 모두 같은 경우는 존재할 수 없다.</li>
</ul>
</li>
</ul>
<h2 id="414-tcp-소켓-프로그래밍">4.1.4 TCP 소켓 프로그래밍</h2>
<ul>
<li>운영체제는 TCP 커넥션의 생성과 관련된 여러 기능 제공<h4 id="클라이언트와-서버가-tcp-소켓-인터페이스를-사용하여-상호작용">클라이언트와 서버가 TCP 소켓 인터페이스를 사용하여 상호작용</h4>
</li>
<li><strong>S</strong>  소켓 생성 후 80포트로 소켓을 묶는다. 이후 커넥션 허가 및 커넥션을 기다린다(클라이언트로부터)</li>
<li><strong>C</strong>  IP addr, port를 얻고 소켓을 생성 후 서버의 IP:port 로 연결 </li>
<li><strong>S &lt;-&gt; C</strong> 연결 이후 요청과 응답 과정을 수행</li>
<li>더 이상의 요청이 없을 때, 커넥션을 종료 </li>
</ul>
<h1 id="42-tcp의-성능에-대한-고려">4.2 TCP의 성능에 대한 고려</h1>
<ul>
<li>HTTP는 TCP의 상위 계층이기 때문에 HTTP 트랜잭션의 성능은 TCP 성능에 영향을 받는다<h2 id="421-http-트랜잭션-지연">4.2.1 HTTP 트랜잭션 지연</h2>
</li>
<li><strong>트랜잭션을 처리하시는 시간 &lt; TCP 커넥션 설정 시간, 요청 전송 및 응답 시간</strong><ul>
<li>대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생<pre><code>1. URL 호스트 명을 IP주소로 변환하는 과정 (초기 방문시)
2. 클라이언트의 TCP 커넥션 요청 ( 보통 1~2초지만 요청이 많으면 소요 시간 크게 증가)
3. 서버가 요청 메세지를 읽고 처리하는 시간
4. 요청 처리 후  HTTP 응답을 보내는 시간</code></pre></li>
<li>이러한 TCP 네트워크 지연은 다음과 같은 요인이 있다.<ul>
<li>HW 성능</li>
<li>네트워크와 서버의 전송 속도</li>
<li>요청과 응답 메세지의 크기</li>
<li>클라이언트와 서버 간의 거리<h2 id="422-성능-관련-중요-요소--가장-일반적인-tcp-관련-지연들-">4.2.2 성능 관련 중요 요소 ( 가장 일반적인 TCP 관련 지연들 )</h2>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="423-tcp-커넥션-핸드셰이크-지연">4.2.3 TCP 커넥션 핸드셰이크 지연</h3>
<h5 id="tcp-커넥션-핸드셰이크">TCP 커넥션 핸드셰이크</h5>
<ul>
<li>TCP 커넥션을 맺기 위해 연속적으로 IP패킷을 교환하는 과정 </li>
<li>하지만, 작은 크기의 데이터 전송에 이러한 커넥션 연결과정은 HTTP 성능을 저하시킬 수 있다. </li>
<li><strong>핸드셰이크 과정</strong><ol>
<li>클라이언트가 <strong>SYN</strong> 라는 플래그를 가지는 TCP 패킷(40~60byte)을 서버에게 보낸다.  </li>
<li>서버는 몇가지 커넥션 매개변수 산출 후, 요청이 받아졌다는 <strong>SYN + ACK</strong> 플래그를 가진 TCP 패킷을 보낸다.</li>
<li>마지막으로, 클라이언트는 커넥션이 잘 맺어졌음을 알리는 응답신호를 보낸다.( 이때 같이 데이터를 보낼 수 있다.)</li>
</ol>
</li>
<li>크기가 작은 HTTP 트랜잭션은 50%이상의 시간을 위와 같은 TCP 커넥션을 구성하는데 사용된다.   </li>
</ul>
<h3 id="424-확인응답-지연">4.2.4 확인응답 지연</h3>
<ul>
<li>인터넷 자체가 패킷 전송을 완벽히 보장하지 않는다. <ul>
<li>혼잡 상태거나 TTL이 0이 되는 패킷들은 라우터에서 폐기한다. <h4 id="성공적인-데이터-전송을-보장하기-위한-tcp의-자체적-확인-체계">성공적인 데이터 전송을 보장하기 위한 TCP의 자체적 확인 체계</h4>
</li>
</ul>
</li>
<li>각 TCP 세그먼트는 순번, 데이터 무결성 체크섬을 가진다. <ul>
<li>전송 성공 시(각 세그먼트를 온전히 받음), src 쪽에 확인 응답 패킷 전송</li>
<li>전송 실패 시(확인 응답 패킷 X), 다시 재전송</li>
</ul>
</li>
<li><strong>확인 응답</strong></li>
<li>편승 - 크기가 작기 때문에 같은 방향(즉, 수신자가 송신자에게 보낼 때)으로 송출되는 데이터에 같이 실어서 보낸다<ul>
<li>편승되는 경우의 수를 늘리기 위해 <strong>확인응답 지연 알고리즘</strong>을 구현<ol>
<li>송출할 확인응답을 특정 시간동안 버퍼에 저장</li>
<li>편승시키기 위한 데이터 패킷 탐색</li>
<li>일정 시간 내 찾지 못하면 별도의 패킷으로 전송</li>
</ol>
</li>
<li>실제로 편승시킬 수 있는 경우가 적기 때문에 지연 알고리즘으로 인한 지연이 자주 발생</li>
<li>요청과 응답 두가지 형식으로만 이루어져 있기 때문에 <h3 id="425-tcp-느린-시작slow-start">4.2.5 TCP 느린 시작(slow start)</h3>
<h4 id="인터넷의-혼잡-제어-방법">인터넷의 혼잡 제어 방법</h4>
</li>
</ul>
</li>
<li>TCP 커넥션은 만들어진 지 얼마나 지났는지에 따라 달라질 수 있다. </li>
<li>시간이 지나면서 자체적으로 <strong>튜닝</strong> 된다.<ul>
<li>속도 제한<ul>
<li>처음에는 커넥션의 최대 속도 제한, 이후 성공적으로 전송 시 속도 제한을 높여나간다. </li>
</ul>
</li>
<li>패킷 수 제한 <ul>
<li>패킷이 성공적으로 전달 시, 추가로 2개의 패킷을 더 전송할 수 있는 권한이 생긴다. </li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>[정리]</strong>
처음부터 많은 데이터, 빠른 속도로 보낼 시 혼잡이 발생할 수 있기 때문에 
천천히, 작은 데이터를 보내보면서 속도와 크기를 늘려나가는 방식의 혼잡 제어</p>
</blockquote>
<h3 id="426-네이글-알고리즘과-tcp_nodelay">4.2.6 네이글 알고리즘과 TCP_NODELAY</h3>
<ul>
<li><p>애플리케이션이 어떤 크기의 데이터든지 TCP 스택으로 전송할 수 있도록 인터페이스를 제공</p>
<ul>
<li>TCP 세그먼트는 40byte 상당의 플래그와 헤더를 포함</li>
<li>작은 데이터 패킷을 많이 보내면 위와 같은 헤더 또한 같은 수로 증가하기 때문에 네트워크 성능 저하<h4 id="많은-양의-tcp-데이터를-합치는-알고리즘-">많은 양의 TCP 데이터를 합치는 알고리즘 <strong>??</strong></h4>
</li>
</ul>
</li>
<li><p>네이글 알고리즘은 세그먼트가 최대 크기 (1500byte 정도)가 되지 않으면 전송 X</p>
</li>
<li><p>다른 모든 패킷이 확인응답 받았을 경우, 최대 크기보다 작은 패킷 전송 허락</p>
</li>
<li><p>다른 패킷들이 아직 전송 중, 데이터는 버퍼에 저장된다. </p>
<ul>
<li>전송되고 나서 확인응답을 기다리던 패킷이 확인응답 혹은 충분한 패킷이 쌓였을 때 버퍼에 저장된 데이터가 전송된다. </li>
</ul>
<p><strong>추가</strong></p>
</li>
</ul>
<ol>
<li>TCP가 데이터를 전송할 때, 첫 번째 패킷이 네트워크를 통해 전송되고 그에 대한 확인 응답(ACK)을 기다립니다.</li>
<li>첫 번째 패킷에 대한 ACK가 도착하기 전에는, 작은 크기의 메시지들을 큐에 저장하고, 이 메시지들을 누적시켜 하나의 큰 패킷으로 만듭니다.</li>
<li>첫 번째 패킷에 대한 ACK가 도착하면, 큐에 누적된 메시지들을 하나의 큰 패킷으로 전송합니다.<h4 id="문제점">문제점</h4>
</li>
</ol>
<ul>
<li>크기가 작은 HTTP 메세지는 추가적인 데이터를 기다리며 지연</li>
<li>확인응답 지연과 함께 쓰일 경우 형편없이 동작<ul>
<li>확인응답이 도착할때까지 데이터 전송을 멈추고 있는 반면, 확인응답 지연 알고리즘은 확인응답을 100~200ms 지연시킨다. </li>
</ul>
</li>
<li>그래서 성능 향상을 위해 <strong>TCP_NODELAY</strong>를 설정해 네이글 알고리즘을 비활성화 하기도 한다. <ul>
<li>이럴 경우 작은 크기의 패킷이 많이 생기는 것을 방지하기 위해 큰 크기의 데이터 덩어리를 만들어야함.</li>
</ul>
</li>
</ul>
<h3 id="427-time_wait의-누적과-포트-고갈">4.2.7 TIME_WAIT의 누적과 포트 고갈</h3>
<ul>
<li>TCP 커넥션을 끊으면 종단에서는 커넥션의 IP 주소와 포트 번호를 메모리의 작은 제어영역에 기록<ul>
<li>이는 새로운 커넥션이 일정시간동안 생성되지 않기 위한 목적</li>
<li>세그먼트 최대 생명주기의 두배(약 2분)가 유지된다.</li>
</ul>
</li>
<li>서버의 성능에 따라 포트 고갈이 일어날 수도 있다. <ul>
<li>트랜잭션을 빠르게 처리하면 일어날 가능성이 높다. </li>
</ul>
</li>
<li>커넥션을 너무 많이 맺거나 대기 상태로 있는 제어 블록이 많아지는 상황을 주의해야한다. <h1 id="43-http-커넥션-관리">4.3 HTTP 커넥션 관리</h1>
</li>
<li>커넥션을 생성하고 최적화하는 HTTP 기술<h2 id="431-흔히-잘못-이해하는-connection-헤더-">4.3.1 흔히 잘못 이해하는 Connection 헤더 <strong>??</strong></h2>
</li>
<li>HTTP는 클라이언트와 서버 사이 중개 서버(프락시, 캐시)가 놓으는 것을 허락한다.</li>
<li>HTTP 메세지는 이 중개 서버를 거치면서 전달된다.</li>
<li>Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지고 있는다.</li>
<li>세 종류의 토큰이 전달 될 수 있다.<ul>
<li>HTTP 헤더 필드 명은, 이 커넥션에만 해당되는 헤더들을 나열</li>
<li>임시적인 토큰 값은, 커넥션에 대한 비표준 옵션을 의미</li>
<li>close 값은, 커넥션이 작업이 완료되면 종료되어야함을 의미 </li>
</ul>
</li>
<li>커넥션 토큰이 HTTP 헤더 필드 명을 가지고 있으면, 현재 커넥션만을 위한 정보이므로 다음 커넥션에 전달되면 안된다. </li>
</ul>
<h2 id="432-순차적인-트랜잭션-처리에-의한-지연">4.3.2 순차적인 트랜잭션 처리에 의한 지연</h2>
<ul>
<li>3개의 이미지가 있는 웹페이지. 이 페이지를 보여주기 위해서<ul>
<li>4개의 HTTP 트랜잭션을 만든다.<ul>
<li>커넥션 발생 지연 + 느린 시작 지연 발생</li>
</ul>
</li>
<li>여러 개의 이미지 동시 로드<ul>
<li>모든 객체를 내려받기 전까지 텅 빈 화면을 보여줘야 한다. <h4 id="http-커넥션의-성능을-향싱-시킬-수-있는-기술">HTTP 커넥션의 성능을 향싱 시킬 수 있는 기술</h4>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>병렬 커넥션</strong><ul>
<li>여러 개의 TCP 커넥션을 통한 동시 HTTP 요청</li>
</ul>
</li>
<li><strong>지속 커넥션</strong><ul>
<li>커넥션을 맺고 끊는 데서 발생하는 지연 제거를 위한 케녁션 재활용</li>
</ul>
</li>
<li><strong>파이프라인 커넥션</strong><ul>
<li>공유 TCP 커넥션을 통한 병렬 HTTP dycjd</li>
</ul>
</li>
<li><strong>다중 커넥션</strong><ul>
<li>요청과 응답들에 대한 중재( 실험적인 기술 )</li>
</ul>
</li>
</ul>
<h1 id="44-병렬-커넥션">4.4 병렬 커넥션</h1>
<ul>
<li>HTTP는 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다. <h2 id="441-병렬-커넥션은-페이지를-더-빠르게-내려받는다">4.4.1 병렬 커넥션은 페이지를 더 빠르게 내려받는다</h2>
</li>
<li>단일 커넥션의 대역폭 제한, 커넥션이 동작하지 않고 있는 시간을 활용하면 =&gt; 객체가 여러 개 있는 웹페이지를 더 빠르게 내려받을 수 있다. </li>
<li>즉, 단일 커넥션의 대역폭을 쪼개서 사용하는 것 -&gt; <strong>한번의 핸드셰이크만 필요한가?</strong> No<h2 id="442-병렬-커넥션이-항상-더-빠르지는-않다">4.4.2 병렬 커넥션이 항상 더 빠르지는 않다</h2>
</li>
<li>일반적으로 더 빠르지만 항상 그렇지는 않다.<ul>
<li><strong>대역폭이 좁을 때</strong> 여러개의 객체를 병렬로 내려받는 경우, 제한된 대역폭 내에서 전송받는 것은 느리기 때문에 성능상에 장점은 거의 없다. </li>
</ul>
</li>
<li>다수의 커넥션은 메모리를 많이 소모, 자체적인 성능 문제 발생<ul>
<li>실제로는 4개 정도의 벙렬 커넥션만을 허용<h2 id="443-병렬-커넥션은-더-빠르게-느껴질-수-있다">4.4.3 병렬 커넥션은 더 빠르게 &#39;느껴질 수&#39; 있다.</h2>
</li>
</ul>
</li>
<li>항상 빠르게 로드하지는 않는다. </li>
<li>하지만 여러 개의 객체가 동시에 보여지면서 더 빠르게 느낄 수 있다.<h1 id="45-지속-커넥션">4.5 지속 커넥션</h1>
</li>
<li>요청에 대한 처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션</li>
<li>커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지한다. </li>
<li>TCP의 느린시작으로 인한 지연을 피함으로써 더 빠르게 데이터 전송이 가능<h2 id="451-지속-커넥션-vs-병렬-커넥션">4.5.1 지속 커넥션 vs 병렬 커넥션</h2>
<h4 id="병렬-커넥션">병렬 커넥션</h4>
<h5 id="장점">장점</h5>
</li>
<li>여러 객체가 있는 페이지를 더 빠르게 전송한다. <h5 id="단점">단점</h5>
</li>
<li>각 트랜잭션마다 새로운 커넥션을 맺고 끊기 때문에 시간, 대역폭이 소요</li>
<li>각 새로운 커넥션은 TCP 느린 시작으로 인한 지연 발생</li>
<li>실제로 연결 가능한 병렬 커넥션 수의 제한 <h4 id="지속-커넥션">지속 커넥션</h4>
<h5 id="장점-1">장점</h5>
</li>
<li>커넥션을 맺기 위한 사전 작업, 지연을 줄여준다.</li>
<li>튜닝된 커넥션을 유지하며 커넥션의 수를 줄여준다. <h5 id="단점-1">단점</h5>
</li>
<li>지속 커넥션을 잘못 관리할 경우, 계속 연결된 상태로 수많은 커넥션이 쌓인다.<ul>
<li>이는 로컬의 리소스, 원격의 클라이언트와 서버의 리소스에 불필요한 소모를 발생한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>[결론]</strong>
지속 커넥션은 병렬 커넥션과 함께 사용될 때 가장 효과적이다!</p>
</blockquote>
<h5 id="http10---keep-alive-커넥션">HTTP/1.0+ :  keep-alive 커넥션</h5>
<h5 id="http11--지속-커넥션">HTTP/1.1 : 지속 커넥션</h5>
<h2 id="452-http10의-keep-alive-커넥션">4.5.2 HTTP/1.0+의 Keep-Alive 커넥션</h2>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/e8b2fbd3-d887-4358-80ab-233dcd96d4bf/image.png width=500
     align = center></p>
<h4 id="새로운-커넥션을-맺고-끊는-데-필요한-작업이-없어서-시간이-단축"><strong>새로운 커넥션을 맺고 끊는 데 필요한 작업이 없어서 시간이 단축</strong></h4>
<h2 id="453-keep-alive-동작">4.5.3 Keep-Alive 동작</h2>
<ol>
<li>클라이언트는 요청에 <strong>Connection:Keep-Alive</strong> 헤더를 포함 시킨다. </li>
<li>요청을 받은 서버는 두가지 경우가 존재<ul>
<li>Keep-Alive 지원<ul>
<li>응답 메세지에 같은 헤더를 포함 시켜 응답</li>
</ul>
</li>
<li>Keep-Alive 미지원<ul>
<li>헤더를 포함시키지 않고 응답 -&gt; 응답 전송 후 서버 커넥션을 끊을 것이라 추정<h2 id="454-keep-alive-옵션">4.5.4 Keep-Alive 옵션</h2>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<ul>
<li><strong>언제든지 현재의 Keep-Alive 커넥션을 끊을 수 있</strong>으며 처리되는 트랜잭션의 수를 제한할 수 있다. <ul>
<li>Keep-Alive의 동작은 Keep-Alive 헤더의 쉼표로 구분된 옵션들로 제어<ul>
<li><strong>timeout</strong><ul>
<li>Keep-Alive 응답 헤더를 통해 보낸다.</li>
<li>커넥션이 얼마나 유지될 것인지를 의미( 실제로 이대로 동작한다는 보장은 없다.)</li>
</ul>
</li>
<li><strong>max</strong><ul>
<li>Keep-Alive 응답 헤더를 통해 보낸다.</li>
<li>몇 개의 HTTP 트랜잭션을 처리할 때까지 유지 될 것인지를 의미( 실제로 이대로 동작한다는 보장은 없다.)</li>
</ul>
</li>
</ul>
</li>
<li>Keep-Alive 헤더 사용은 선택 사항이지만 Conncetion: Keep-Alive 헤더가 있을 때만 사용할 수 있다.</li>
</ul>
</li>
</ul>
<pre><code>Connection: Keep-Alive
Keep-Alive: max:5, timeout:120</code></pre><p>=&gt; 5개의 추가 트랜잭션이 처리될 동안 or 2분동안 커넥션 유지하라는 응답 헤더</p>
<h2 id="455-keep-alive-커넥션-제한과-규칙">4.5.5 Keep-Alive 커넥션 제한과 규칙</h2>
<ol>
<li>keep-alive는 HTTP/1.0에서 기본으로 사용되지 않는다. 클라이언트는 <strong>Connection: Keep-Alive</strong> 요청 헤더를 보내야 한다.</li>
<li>커넥션을 유지하려면 모든 메세지에 <strong>Connection: Keep-Alive</strong> 헤더를 포함해야한다. 미 포함시 서버가 커넥션을 끊을 것이다. </li>
<li>클라이언트는 서버의 <strong>Connection: Keep-Alive</strong> 응답 헤더가 없는 것을 보고 커넥션을 끊을 것을 알 수있다. </li>
<li>커넥션이 끊어지기 전 엔터티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다. <ul>
<li>엔터티 본문이 정확한 Content-Length값과 Type, 청크 전송 인코딩으로 인코드 되어야 함을 뜻함</li>
<li>잘못된 Length값은 트랜잭션이 끝나는 시점에 기존 메세지의 끝과 새로운 시작을 정확히 알 수 없다.</li>
</ul>
</li>
<li>keep-alive 커넥션은 <strong>Connection: Keep-Alive</strong> 헤더를 인식하지 못하는 서버와 맺어지면 안된다. 현실적으로는 쉽지 않다. </li>
<li>기술적으로 HTTP/1.0을 따르는 기기로부터 받는 모든 Connection 헤더 필드는 무시해야한다. <ul>
<li>오래된 프락시 서버로부터 실수로 전달될 수 있고 오래된 프락시에 행이 걸릴 수 있는 위험이 있다.</li>
<li>하지만 이 규칙을 지키지 않기도 한다. </li>
</ul>
</li>
<li>클라이언트는 응답 전체를 모두 받기 전 커넥션이 끊어졌을 경우, 요청이 반복될 경우 생기는 문제가 있기 때문에 요청을 다시 보낼 수 있게 준비되어있어야한다. <h2 id="456-keep-alive와-멍청한dumb-프락시">4.5.6 Keep-Alive와 멍청한(dumb) 프락시</h2>
<img src =https://velog.velcdn.com/images/codejugller-9999/post/e0a923e2-51e8-40da-8895-a4cd4020f6a9/image.png width=500
  align = center></li>
</ol>
<ul>
<li>멍청한 프락시는 HTTP의 Connection 헤더를 단순히 확장 헤더로만 취급. 즉, 이해하지 못한다.</li>
<li>첫 요청과 응답을 통해 클라이언트와 서버는 프락시 서버와 같이 커넥션을 유지하는 것으로 알고 있다.</li>
<li>클라이언트는 keep-alive 커넥션으로 계속 요청을 보낸다.  </li>
<li>서버는 적어도 max, timeout과 같은 매개변수의 값만큼 커넥션을 유지한다.</li>
<li><strong>하지만 멍청한 프락시는 커넥션이 끊이지기만을 기다린다.</strong> </li>
</ul>
<h5 id="프락시와-홉별-헤더">프락시와 홉별 헤더</h5>
<ul>
<li>이런 잘못된 통신을 피하기 위해 프락시는 Connection 헤더를 전달하면 안된다. </li>
<li>또한, Proxy-Connection 등과 같은 홉별 헤더들 역시 전달하거나 캐시하면 안된다. <h2 id="457-proxy-connection-살펴보기">4.5.7 Proxy-Connection 살펴보기</h2>
</li>
<li>Proxy-Connection 헤더를 사용해 클라이언트의 요청이 중개서버를 통해 이어지는 경우 모든 헤더를 무조건 전달하는 문제를 해결</li>
<li>멍청한 프락시는 홉별 헤더를 무조건 전달하기 때문에 문제를 일으킨다. </li>
</ul>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/d4fb5774-1724-43c1-a929-3f988ea298bc/image.png 
     width=500 align = center></p>
<h5 id="멍청한-프락시">멍청한 프락시</h5>
<ul>
<li>클라이언트는 Proxy-Connection을 보내고 프락시 서버는 그대로 전달한다.</li>
<li>서버는 이를 무시하고 응답을 보낸다. </li>
<li>프락시와 클라이언트, 서버 사이에 커넥션이 유지되지 않는다. <h5 id="영리한-프락시">영리한 프락시</h5>
</li>
<li>클라이언트는 Proxy-Connection을 보내고 프락시 서버는 제대로된 Connection 헤더를 보낸다.</li>
<li>서버는 일반적으로 커넥션을 유지하기 위해 응답을 보낸다. </li>
<li>프락시와 클라이언트, 서버 사이에 커넥션이 유지된다. </li>
</ul>
<h5 id="중개-서버에-멍청한-프락시와-영리한-프락시가-같이-있으면-문제가-발생한다">중개 서버에 멍청한 프락시와 영리한 프락시가 같이 있으면 문제가 발생한다.</h5>
<ul>
<li>게다가 문제를 발생시키는 프락시들은 방화벽, 캐시 서버, 리버스 프락시 서버 가속기와 같이 네트워크상에서 &#39;보이지 않는&#39; 경우가 많다. </li>
</ul>
<h2 id="458-http11의-지속-커넥션">4.5.8 HTTP/1.1의 지속 커넥션</h2>
<ul>
<li>HTTP/1.1에서는 keep-alive를 지원하지 않는 대신 더 개선된 지속 커넥션을 지원한다.</li>
<li>HTTP/1.1에서는 별도 설정을 하지 않는 한, <strong>모든 커넥션을 지속 커넥션</strong>으로 취급한다.</li>
<li>HTTP/1.1 애플리케이션은 트랜잭션이 끝난 후 Connection:close 헤더를 명시해야 커넥션을 끊을 수 있다.<ul>
<li>하지만 Connection:close 를 보내지 않는 것이 서버가 커넥션을 영원히 유지하겠다는 뜻은 아니다.</li>
<li>클라이언트와 서버는 마친가지로 언제든지 커넥션을 끊을 수 있다. <ul>
<li>이게 Connection 헤더의 파라미터 값대로 동작을 보장하지 못하는 이유</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="459-지속-커넥션의-제한과-규칙">4.5.9 지속 커넥션의 제한과 규칙</h2>
<ol>
<li>클라이언트가 요청에 Connection:close 헤더를 포함했다면, 더 이상 그 커넥션으로 요청을 보낼 수 없다.</li>
<li>클라이언트가 추가 요청이 없을 경우 마지막에 Connection:close 헤더를 보내야한다.</li>
<li>커넥션에 있는 모든 메세지는 자신의 Length정보가 정확해야 커넥션을 지속 가능<ul>
<li>엔터티 본문은 정확한 Content-Length값을 가진다</li>
<li>청크전송 인코딩으로 인코드 되어 있어야한다.</li>
</ul>
</li>
<li>HTTP/1.1 프락시는 클라이언트, 서버 각각에 별도의 지속 커넥션을 맺고 관리</li>
<li>HTTP/1.1 프락시 서버는 클라이언트의 지원 범위를 알고 있지 않다면 지속 커넥션을 맺으면 안된다. <ul>
<li>오래된 프락시의 Connection 헤더 전달 문제 </li>
<li>현실적으로 지키기 쉽지 않다.<ol start="6">
<li>서버는 메세지 전송 중 커넥션을 끊지 않고 끊기 전 적어도 한개의 요청에 대해 응답은 하지만 HTTP/1.1 기기는 Connection 헤더 값과 상관 없이 언제든지 끊을 수 있다.</li>
<li>HTTP/1.1 애플리케이션은 중간에 끊어지는 커넥션을 복구할 수 있어야만 한다. </li>
<li>클라이언트는 전체 응답전 커넥션이 끊어지면 요청을 반복해서 보내도 문제 없는 경우 보낼 준비가 되어있어야한다.</li>
<li>하나의 사용자 클라이언트는 서버 과부화를 방지하기 위해, 넉넉하게 2개의 지속 커넥션을 유지해야 한다.</li>
</ol>
</li>
</ul>
</li>
</ol>
<h1 id="46-파이프라인-커넥션">4.6 파이프라인 커넥션</h1>
<h5 id="파이프라인의-제약-사항">파이프라인의 제약 사항</h5>
<ol>
<li>HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안된다.</li>
<li>HTTP 응답은 요청 순서와 같게 와야 한다. HTTP 메세지는 순번이 매겨져 있지 않아서 응답이 순서 없이 오면 순서에 맞게 정렬시킬 방법이 없다.</li>
<li>HTTP 클라이언트는 커넥션이 언제 끊어지더라도, 완료되지 않은 요청이 파이프라인에 있으면 언제든 다시 요청 보낼 준비가 되어 있어야 한다. <ul>
<li>10개 요청후 5개의 응답만 받고 커넥션이 끊김</li>
<li>남은 5개의 실패한 요청을 커넥션을 다시 맺고 요청을 보낼 수 있어야한다.</li>
</ul>
</li>
<li>HTTP 클라이언트는 POST 요청같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안된다. <ul>
<li>에러 발생시, 요청 중 어떤 것들이 서버에서 처리되었는지 클라이언트가 알 방법이 없다.</li>
<li>POST와 같은 비멱등(연산이 한번 일어날 때 마다 결과가 변할 수 있는) 요청을 재차 보내면 문제가 생길 수도 있다.</li>
</ul>
</li>
</ol>
<p><img src =https://velog.velcdn.com/images/codejugller-9999/post/906f279d-13ab-47c2-8e66-cc4ed19af46f/image.png width=500
     align = center></p>
<h1 id="47-커넥션-끊기에-대한-미스터리">4.7 커넥션 끊기에 대한 미스터리</h1>
<ul>
<li>커넥션 관리(언제 끊어야 하는지)에는 명확한 기준이 없다. </li>
</ul>
<h2 id="471-마음대로-커넥션-끊기">4.7.1 &#39;마음대로&#39; 커넥션 끊기</h2>
<ul>
<li>어떠한 HTTP 클라이언트, 서버, 혹은 프락시든 언제든 TCP 커넥션을 끊을 수 있다.<ul>
<li>보통은 메세지를 다 보낸 다음 끊는다</li>
</ul>
</li>
</ul>
<h2 id="472-content-length와-truncation">4.7.2 Content-Length와 Truncation</h2>
<ul>
<li>각 HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다. </li>
</ul>
<h2 id="473-커넥션-끊기의-허용-재시도-멱등성">4.7.3 커넥션 끊기의 허용, 재시도, 멱등성</h2>
<ul>
<li>커넥션은 심지어 에러가 없더라도 언제든 끊을 수 있다. </li>
<li>이에 HTTP 애플리케이션은 예상치 못하게 커넥션이 끊어졌을 때에 적절히 대응할 수 있는 준비가 되어있어야한다.<ul>
<li>트랜잭션 수행중 커넥션이 끊기면, 클라이언트는 다시 커넥션을 맺고 한번 더 전송 시도 </li>
<li>클라이언트는 요청을 큐에 쌓아 놓을 수 있다.</li>
<li>반면, 서버는 아직 처리되지 않고 스케줄이 조정되어야하는 요청을 남겨둔 채로 커넥션을 끊어 버릴 수 있다.</li>
</ul>
</li>
<li>클라이언트는 커넥션이 끊기면 서버에서 얼마만큼 요청이 처리되었는지 알 수 없다.<ul>
<li>이는 GET요청은 상관 없지만 POST같은 비멱등 경우 문제가된다.</li>
<li>멱등<ul>
<li>GET, HEAD, PUT, DELETE, TRACE</li>
</ul>
</li>
<li>비멱등<ul>
<li>POST</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>** [멱등, 비멱등]**
<strong>여러번 요청해도 서버에서 상태가 변하는 유무에 따라 나뉘게 된다. **
**PUT</strong> -&gt; 여러번 실행해도 리소스의 상태가 마지막 PUT 요청의 페이로드를 반영한 상태로 유지된다. 
<strong>DELETE</strong> -&gt; 여러번 실행해도 서버에서 리소스의 상태가 &#39;삭제&#39;라는 최종 상태에는 변함이 없다.
<strong>POST</strong> -&gt; 예를 들어 첫번째 요청으로 사용자가 생성된 후 또 요청을 보내면 중복되는 새로운 사용자가 생성될 수 있기 때문에 서버의 상태가 두번의 요청 후에 다르게 되기 때문에 비멱등이다. </p>
</blockquote>
<h2 id="474-우아한-커넥션-끊기">4.7.4 우아한 커넥션 끊기</h2>
<ul>
<li>TCP 커넥션은 양방향이다. <h5 id="전체-끊기와-절반-끊기">전체 끊기와 절반 끊기</h5>
</li>
<li>전체 끊기 <ul>
<li>애플리케이션은 TCP 입력 채널과 출력 채널 중 한 개만 끊거나 둘 다 끊을 수 있다. </li>
<li>방법 : <strong>close()</strong> 호출</li>
</ul>
</li>
<li>절반 끊기<ul>
<li>입력, 출력 중 하나를 개별적으로 끊을 수 있다. </li>
<li>방법: <strong>shutdown()</strong> 호출</li>
</ul>
</li>
</ul>
<h5 id="tcp-끊기와-리셋-에러">TCP 끊기와 리셋 에러</h5>
<ul>
<li>단순한 HTTP 애플리케이션은 전체 끊기만 사용할 수 있다. <ul>
<li>애플리케이션이 각기 다른 HTTP 클라이언트, 서버, 프락시와 통신할 때</li>
<li>그들과 파이프라인 지속 커넥션을 사용할 때</li>
<li>예상치 못한 쓰기 에러를 예방하기 위해 <strong>절반 끊기</strong>를 사용해야함</li>
</ul>
</li>
<li>보통은 <strong>출력 채널</strong>을 끊는 것이 안전<ul>
<li>반대편 기기는 더이상 입력이 없음으로 내가 출력 커넥션을 끊었다는 것을 알게 됨</li>
</ul>
</li>
<li>클라이언트는 데이터를 더 보내지 않는 것에 확신하지 않는 이상, <strong>입력 채널</strong>을 끊는 것은 위험<ul>
<li>끊긴 상태에서 데이터 전송시 , 서버의 운영체제는 TCP &#39;connection reset by peer&#39; 메세지를 보냄</li>
<li>이는 심각한 에러로 취급, 버터에 저장된 + 아직 읽히지 않은 데이터 전부 삭제</li>
<li>Example<ul>
<li><strong>C</strong> - 10개의 요청을 파이프라인 지속 커넥션을 통해 전송</li>
<li><strong>S</strong> - 10개의 응답을 버퍼에 저장 ( 아직 애플리케이션이 읽지 않음)</li>
<li>이미 충분히 오래 유지되었다고 판단 -&gt; 커넥션 종료</li>
<li><strong>C</strong> - 11번째 요청 전송</li>
<li><strong>S</strong> - &#39;connection reset by peer&#39; 메세지 응답 및 , 버퍼에 저장된 응답 데이터 전부 삭제</li>
</ul>
</li>
</ul>
</li>
</ul>
<h5 id="우아하게-커넥션-끊기">우아하게 커넥션 끊기</h5>
<ul>
<li>애플리케이션의 우아한 커넥션 끊기<ul>
<li>자신의 출력 채널 끊는다.</li>
<li>다른 쪽의 출력 채널이 끊기는 것을 기다린다. ( 자신의 입력채널에 더 이상 데어티가 안들어옴)</li>
</ul>
</li>
<li>하지만 상대방이 절반 끊기를 구현했다는 보장 X</li>
<li>절반 끊기를 했는지 검사해준다는 보장 X</li>
<li>따라서 자신의 출력 채널에 절반 끊기 후, 입력 채널의 대해 상태를 주기적으로 검사<ul>
<li>만약 입력 채널이 timeout 시간 내에 끊어지지 않는다면 리소스 보호를 위해 커넥션을 강제로 종료</li>
</ul>
</li>
</ul>
<h1 id="spring에서의-동작과정">Spring에서의 동작과정</h1>
<ul>
<li>Spring Framework에서 요청과 응답을 담당하는 서블릿 컨테이너는 WAS의 일부</li>
<li>HTTP 요청을 받아 처리하고 응답을 반환하는 역할을 한다.<ul>
<li>TCP 커넥션 관리는 이 과정에서 중요한 부분 중 하나</li>
</ul>
</li>
<li>동작 과정<ol>
<li><strong>TCP 커넥션 개설</strong>: 클라이언트가 -&gt; 서버 연결 요청. 서블릿 컨테이너 내부의 웹 서버는 TCP 소켓을 열고, 클라이언트와 서버 간의 TCP 커넥션 개설(3-way handshake과정을 통해 연결 설정)</li>
<li><strong>요청 처리</strong>: TCP 커넥션을 통해 전송된 HTTP 요청은 서블릿 컨테이너에 도달합니다. 서블릿 컨테이너는 해당 요청을 분석하여 적적한 서블릿에 전달. (서블릿은 요청을 처리 후 응답 생성)<ul>
<li><strong>Handler Mapping</strong> </li>
</ul>
</li>
</ol>
<ul>
<li>들어오는 요청을 해당하는 핸들러(컨트롤러)를 찾아 매핑한다. (Spring MVC)<pre><code>  1. **요청 식별**: 클라이언트로부터 들어온 요청은 먼저 DispatcherServlet에 의해 받아진다. DispatcherServlet은 Spring MVC의 프론트 컨트롤러 역할.
  2. **핸들러 매핑 조회**: DispatcherServlet은 등록된 핸들러 매핑 전력을 조회, 요청 URL에 해당하는 핸들러(컨트롤러 메소드) 를 찾는다.
  3. **핸들러 실행**: 매핑된 핸들러가 결정되면, DispatcherServlet은 해당 핸들러 실행. (이 과정에서 핸들러에 필요한 다양한 파라미터가 바인딩, 비지니스 로직이 수행)
  4. **뷰 리졸빙 및 응답 반환**: 핸들러의 실행 결과를 바탕으로, DispatcherServlet은 적절한 뷰를 찾아 클라이언트에게 응답을 반환. (혹은 JSON 타입의 데이터 반환)</code></pre></li>
<li><strong>@RequestMapping</strong> </li>
</ul>
<ol start="3">
<li><strong>응답 반환</strong>: 처리된 응답은 다시 HTTP 응답으로 클라이언트에게 전송</li>
<li><strong>커넥션 유지 및 종료</strong> : HTTP/1.1 스펙에 따라, 일정 시간동안 커넥션 유지 될 수 있다. </li>
<li><strong>커넥션 풀링</strong>: 성능 최적화를 위해 서블릿 컨테이너는 종종 TCP 커넥션 풀을 사용. 미리 생성된 TCP 커넥션의 집합으로, 요청이 들어올 때마다 새로운 커넥션을 개설하는 대신 풀에 있는 커넥션을 재사용</li>
<li><strong>비동기 처리</strong>: 최신의 서블릿 컨테이너는 비동기 처리 지원, 하나의 요청 처리가 완료될 때까지 TCP 커넥션을 유지하면서도 그 동안 다른 요청을 처리할 수 있다. </li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 완벽 가이드] 3. HTTP 메시지]]></title>
            <link>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-3.-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80</link>
            <guid>https://velog.io/@codejugller-9999/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-3.-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80</guid>
            <pubDate>Wed, 24 Jul 2024 01:04:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 &#39;HTTP 완벽 가이드&#39; 책을 읽고 정리한 내용입니다.</p>
</blockquote>
<blockquote>
<p><strong>HTTP</strong>가 인터넷의 배달원이라면, <strong>HTTP 메시지</strong>는 무언가를 담아 보내는 소포와 같다.</p>
</blockquote>
<h3 id="메시지의-흐름">메시지의 흐름</h3>
<blockquote>
<p>메시지는 클라이언트, 서버, 프락시 사이를 흐르며 메시지의 방향을 의미하는 용어는 다음과 같다.</p>
<ul>
<li>인바운드</li>
<li>아웃바운드</li>
<li>업스트림</li>
<li>다운스트림</li>
</ul>
</blockquote>
<h4 id="1-메시지는-원-서버-방향을-인바운드로-하여-송신된다">1. 메시지는 원 서버 방향을 인바운드로 하여 송신된다.</h4>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/c322a3f5-4af3-473d-a41b-703829c26b96/image.png" alt=""></p>
<ul>
<li>인바운드 : 메시지가 원 서버로 향하는 것</li>
<li>아웃바운드 : 모든 처리가 끝난 뒤에 메시지가 클라이언트로 돌아오는 것<h4 id="2-다운스트림으로-흐르는-메시지">2. 다운스트림으로 흐르는 메시지</h4>
<blockquote>
<p>HTTP 메시지는 강물과 같이 흐른다. 요청, 응답 메시지에 관계없이 <strong>모든 메시지는 다운스트림으로 흐른다.</strong></p>
</blockquote>
</li>
</ul>
<p><img src=https://velog.velcdn.com/images/codejugller-9999/post/67c14633-33c9-4dd8-bbf9-8cdf2a15f4f6/image.png
     width = 500 align = center></p>
<h3 id="메시지의-각-부분">메시지의 각 부분</h3>
<p><img src="https://velog.velcdn.com/images/codejugller-9999/post/eca18f68-5b17-4c36-9806-aa51420d4017/image.png" alt=""></p>
<blockquote>
<p>메시지는 <strong>시작줄, 헤더 블록, 본문</strong> 이렇게 세 부분으로 이루어진다. </p>
</blockquote>
<ul>
<li>시작줄과 헤더는 줄 단위로 분리된 아스키 문자열</li>
<li>각 줄은 캐리지 리턴(ASCII 13), 개행 문자(ASCII 10)로 구성된 두 글자의 줄바꿈 문자열로 끝난다. <ul>
<li>이 줄바꿈 문자열은 <strong>CRLF</strong>라고 쓴다 <code>\r\n</code></li>
</ul>
</li>
</ul>
<h4 id="문법">문법</h4>
<blockquote>
<p>모든 HTTP 메시지는 <strong>&quot;요청 메시지&quot;</strong> , <strong>&quot;응답 메시지&quot;</strong> 으로 분류된다.</p>
</blockquote>
<h4 id="요청-메시지-형식">요청 메시지 형식</h4>
<pre><code>&lt;메서드&gt; &lt;요청 URL&gt; &lt;버전&gt;
&lt;헤더&gt;

&lt;엔터티 본문&gt;</code></pre><h4 id="응답-메시지-형식">응답 메시지 형식</h4>
<pre><code>&lt;버전&gt; &lt;상태코드&gt; &lt;사유 구절&gt;
&lt;헤더&gt;

&lt;엔터티 본문&gt;</code></pre><h4 id="시작줄">시작줄</h4>
<blockquote>
<p>모든 HTTP 메시지는 시작줄로 시작하며 이것이 어떤 메시지인지 서술한다.</p>
<ul>
<li>요청 메시지 : 무엇을 해야하는지 </li>
<li>응답 메시지 : 무슨일이 일어났는지</li>
</ul>
</blockquote>
<h4 id="요청줄">요청줄</h4>
<p> 해당 부분에서 서버에서 어떤 동작이 일어나야 하는지 설명해준다.</p>
<ul>
<li>메서드</li>
<li>동작에 대한 대상을 지칭하는 요청 URL</li>
<li>HTTP 버전<h4 id="응답줄">응답줄</h4>
해당 부분에서 수행 결과에 대한 정보를 클라이언트에게 돌려준다.<ul>
<li>수행 결과에 대한 상태 정보</li>
<li>결과 데이터</li>
<li>HTTP 버전</li>
<li>상태 코드 및 사유 구절(수행상태에 대해 설명해주는 텍스트)</li>
</ul>
</li>
</ul>
<h4 id="시작줄의-요소">시작줄의 요소</h4>
<p><strong>메서드</strong> : 서버에게 무엇을 해야하는지 말해주는 역할</p>
<ul>
<li>&#39;GET/special/saw-blade.gif HTTP/1.0&#39;</li>
<li>여기서 &#39;GET&#39;이 메서드이다.
<img src=https://velog.velcdn.com/images/codejugller-9999/post/ba159b10-6c9c-4ecb-8773-1c1f5fdb72f4/image.png
   width = 500 align = left></li>
</ul>
<hr>
<ul>
<li>&#39;GET&#39;, &#39;HEAD&#39; 메서드와 같은 경우, 서버에 어떤 작용(DB 서버의 예로 데이터 등록, 수정, 삭제 등)도 없는 안전한 메서드라고 불린다.</li>
</ul>
<p><strong>상태 코드</strong> : 클라이언트에게 무엇이 일어났는지 말해주는 역할</p>
<ul>
<li>&#39;HTTP/1.0 200 OK&#39;</li>
<li>여기서 &#39;200이 상태 코드이다.
<img src="https://velog.velcdn.com/images/codejugller-9999/post/ac50497d-1722-4355-b035-56ebee204a7c/image.png" alt=""></li>
</ul>
<p><del>(상태 코드에 대한 자세한 설명은 생략)</del></p>
<p><strong>사유 구절</strong> : 상태 코드에 대한 글로 된 설명, 응답 시작줄의 마지막 구성요소</p>
<ul>
<li>&#39;HTTP/1.0 200 OK&#39;</li>
<li>여기서 &#39;OK&#39;가 사유 구절이다. </li>
</ul>
<p><strong>버전 번호</strong> : &#39;HTTP/x.y&#39;형태로 요청, 응답 메세지 양쪽 모두에 기술된다.</p>
<ul>
<li>자신의 따르는 프로토콜의 버전을 상대방에게 말해주기 위한 수단</li>
<li>&#39;HTTP/1.1&#39;의 버전을 가지고 있다면 해당 버전까지 이해할 수 있음을 의미한다.</li>
</ul>
<h4 id="헤더-블록">헤더 블록</h4>
<blockquote>
<p>헤더와 메서드는 클라이언트와 서버가 무엇을 하는지 결정하기 위해 함께 사용된다.
헤더는 크게 다섯가지로 분류된다. </p>
</blockquote>
<h4 id="1-일반-헤더general-headers">1. 일반 헤더(General Headers)</h4>
<ul>
<li>기본적인 정보 제공</li>
<li>예를 들어 요청 메시지, 응답 메시지 작성 시, 요청, 응답의 생성 일시 등</li>
<li><em>일반 정보 헤더*</em>
<img src="https://velog.velcdn.com/images/codejugller-9999/post/2568ed57-d246-4d51-bb03-3fc6fcf548be/image.png" alt=""></li>
</ul>
<p><strong>일반 캐시 헤더</strong>
HTTP/1.0에 도입된 것으로 매번 원 서버로부터 객체를 가져오는 대신, 로컬 복사본으로 캐시할 수 있는 헤더이다.
<del>(캐시에 대한 내용은 7장에서 자세히 설명)</del></p>
<h4 id="2-요청-헤더request-headers">2. 요청 헤더(Request Headers)</h4>
<ul>
<li>요청 메시지에서만 의미를 가지는 헤더<ul>
<li>누가, 무엇이 요청을 보냈는지에 대한 정보</li>
<li>클라이언트의 선호, 능력에 대한 정보</li>
</ul>
</li>
</ul>
<p><strong>요청 정보 헤더</strong>
<img src=https://velog.velcdn.com/images/codejugller-9999/post/900b15c1-388c-4c4e-80b2-b73644ef0b8d/image.png
     width = 500 align = left></p>
<hr>
<p><strong>요청 헤더의 종류</strong>
요청 헤더에는 기능에 따라 4가지로 분류된다.</p>
<ol>
<li>Accept 관련 헤더 : 클라이언트가 무엇을 원하고 할 수 있는지에 대한 정보를 알려줄 수 있다. 이는 사용할 수 없는 것을 전송하는데 시간과 대역폭을 낭비하지 않기 때문에 서버, 클라이언트 양쪽 모두에게 유익하다.</li>
<li>조건부 요청 헤더 : 이 요청 헤더를 사용하여 클라이언트는 서버에게 요청에 응답하기 전 자신이 설정한 조건이 참인지 확인하게 하는 제약을 포함시킬 수 있다.( 예를 들어 클라이언트 문서 사본 요청 시, 자신이 가지고 있는 사본과 다를 경우에만 전송을 해라)</li>
<li>요청 보안 헤더 : 자체적으로 요청을 위한 간단한 인증요구/응답 체계를 가지고 있다. 이는 인증을 통해 트랜잭션을 더 안전하게 만들 수 있다.</li>
<li>프락시 요청 헤더 : 프락시 서버의 기능을 돕기 위한 헤더</li>
</ol>
<h4 id="3-응답-헤더response-headers">3. 응답 헤더(Response Headers)</h4>
<ul>
<li>클라이언트에게 부가 정보를 제공</li>
<li>누가 응답을 보내는지, 응답자의 능력 등을 설명하여 클라이언트가 더 나은 요청을 할 수 있도록 도와준다.</li>
</ul>
<p><strong>응답 정보 헤더</strong>
<img src=https://velog.velcdn.com/images/codejugller-9999/post/9f0675a7-202b-4e69-9e2f-5762c45e6e4b/image.png
     width = 500 align = left>     </p>
<hr>
<h4 id="4-엔터티-헤더entity-headers">4. 엔터티 헤더(Entity Headers)</h4>
<ul>
<li>요청, 응답 양쪽 모두 포함할 수 있기 때문에, 양 타입의 메시지에 모두 나타날 수 있다.</li>
<li>엔터티와 내용물에 대한, 개체의 타입 및 리소스를 요청할 수 있는 유효한 메서드 등 광범위한 정보 제공</li>
</ul>
<p><strong>엔터티 정보 헤더</strong>
<img src=https://velog.velcdn.com/images/codejugller-9999/post/ad01c573-0f99-4e14-9a74-42cce667de4d/image.png
     width = 500 align = left></p>
<hr>
<p><strong>1. 콘텐츠 헤더</strong> : 엔터티의 콘텐츠에 대한 구체적인 정보를 제공한다. (종류, 크기 등)</p>
<ul>
<li>자주 보는 &#39;Content-Type&#39;,&#39;Content-Length&#39;가 여기 해당된다.</li>
</ul>
<p><strong>2. 엔터티 캐싱 헤더</strong> : 리소스에 대해 키시된 사본이 아직 유효한지, 유효만료 시점이 언제인지 등 </p>
<h4 id="5-확장-헤더extension-headers">5. 확장 헤더(Extension Headers)</h4>
<p>확장 헤더는 애플리케이션 개발자들에 의해 만들어졌지만 아직 승인된 HTTP 명세에는 추가되지 않는 비표준 헤더이다.</p>
<h4 id="본문">본문</h4>
<blockquote>
<p>엔터티 본문이라고도 불리는 해당 부분은 HTTP 메세지의 화물이라고 할 수 있다. </p>
</blockquote>
<p>HTTP가 수송하도록 설계된 것들로 HTTP 메시지는 이미지, 비디오, HTML 문서, 소프트웨어 애플리케이션, 신용카드 트랜잭션, 전자우편 등 여러 종류의 디지털 데이터를 실어 나를 수 있다. </p>
]]></description>
        </item>
    </channel>
</rss>