<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yoo-seunghyeon.log</title>
        <link>https://velog.io/</link>
        <description>커피를 넣으면 코드가 나옵니다.</description>
        <lastBuildDate>Tue, 26 Aug 2025 15:11:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yoo-seunghyeon.log</title>
            <url>https://velog.velcdn.com/images/yoo-seunghyeon/profile/d8c57c64-a9a7-4c7b-b85e-538fbc1a8c90/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yoo-seunghyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yoo-seunghyeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Neuralangelo (with. WSL)]]></title>
            <link>https://velog.io/@yoo-seunghyeon/Neuralangelo-WSL</link>
            <guid>https://velog.io/@yoo-seunghyeon/Neuralangelo-WSL</guid>
            <pubDate>Tue, 26 Aug 2025 15:11:07 GMT</pubDate>
            <description><![CDATA[<h1 id="neuralangelo">Neuralangelo</h1>
<p>Nividia에서 개발한 3D 모델링 AI</p>
<blockquote>
<p><strong>참고</strong></p>
<p>공식 사이트 : <a href="https://research.nvidia.com/labs/dir/neuralangelo/">https://research.nvidia.com/labs/dir/neuralangelo/</a>
Baseline : <a href="https://colab.research.google.com/drive/1JD9CpAPteg_JsxneB1B-0XX-pZpybuEj">https://colab.research.google.com/drive/1JD9CpAPteg_JsxneB1B-0XX-pZpybuEj</a></p>
</blockquote>
<h1 id="환경-세팅">환경 세팅</h1>
<ol>
<li>WSL Ubuntu 22.04 : 파이썬 버전과 CUDA 버전, cmake 버전을 모두 만족하는 ubuntu 버전</li>
</ol>
<pre><code class="language-bash">python3 --version
&gt; 3.10

sudo apt update

sudo apt upgrade

sudo apt install build-essential ffmpeg git python3-pip cmake python3-venv

python3 -m venv venv
source venv/bin/activate

git clone https://github.com/nvlabs/neuralangelo
cd ./neuralangelo
git submodule update --init --recursive

cd ..

sudo apt-get install \
    ninja-build \
    build-essential \
    libboost-program-options-dev \
    libboost-filesystem-dev \
    libboost-graph-dev \
    libboost-system-dev \
    libeigen3-dev \
    libflann-dev \
    libfreeimage-dev \
    libmetis-dev \
    libgoogle-glog-dev \
    libgtest-dev \
    libsqlite3-dev \
    libglew-dev \
    qtbase5-dev \
    libqt5opengl5-dev \
    libcgal-dev \
    libceres-dev

sudo apt-get install xvfb

pip install gdown
pip show gdown
echo &#39;export PATH=$PATH:/home/crysh/.local/bin&#39; &gt;&gt; ~/.bashrc
source ~/.bashrc


gdown 1Ob2pSUFp46lZNAEK7y7ZSnpwvdIMsILb
sudo tar -C /usr -zxf colmap-3.8.tar.gz
pip install \
    addict \
    k3d \
    opencv-python-headless \
    pillow \
    plotly \
    pyyaml \
    trimesh


cd ./neuralangelo

gdown 1Ee6ucKlmmS2ZsB1uD09XHhf3p_pYv7nt

cd ./third_party/colmap

mkdir build
cd build

sudo apt-get update
sudo apt-get install -y libceres-dev libboost-all-dev libfreeimage-dev libflann-dev libsqlite3-dev libgl1-mesa-dev libglu1-mesa-dev libglew-dev libmetis-dev qtbase5-dev qt5-qmake

cmake .. -DCUDA_ENABLED=ON -DCMAKE_CUDA_ARCHITECTURES=&quot;70;72;75;80;86&quot; -GNinja

ninja 

sudo sudo ninja install

wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin

sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600

wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda-repo-wsl-ubuntu-11-8-local_11.8.0-1_amd64.deb

sudo dpkg -i cuda-repo-wsl-ubuntu-11-8-local_11.8.0-1_amd64.deb

sudo cp /var/cuda-repo-wsl-ubuntu-11-8-local/cuda-*-keyring.gpg /usr/share/keyrings/

sudo apt-get update

sudo apt-get -y install cuda

echo &#39;export PATH=/usr/local/cuda-11.8/bin:$PATH&#39; &gt;&gt; ~/.bashrc
echo &#39;export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH&#39; &gt;&gt; ~/.bashrc

sudo ln -sfn /usr/local/cuda-11.8 /usr/local/cuda

source ~/.bashrc

which nvcc
nvcc --version

cd ~
git clone https://github.com/NVIDIA/libglvnd
cd ./libglvnd

sudo apt-get install libxext-dev libx11-dev x11proto-gl-dev
sudo apt-get install autoconf automake libtool
sudo apt-get install libffi-dev
sudo ./autogen.sh
sudo ./configure
sudo make -j4
sudo make install
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/wsl/lib
export LDFLAGS=&quot;-L/usr/lib/wsl/lib&quot;

---
pip uninstall -y tinycudann

sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build

cd ~
git clone https://github.com/NVlabs/tiny-cuda-nn.git

cd ~/tiny-cuda-nn/
git submodule update --init --recursive
cd bindings/torch

export TCNN_CUDA_ARCHITECTURES=75


pip install .
</code></pre>
<p>이후 필요한 코드만 사용하면 됩니다.</p>
<h1 id="code">code</h1>
<p>예제에서 설치부분을 제외한 실행 부분만을 사용했습니다.
순차적으로 ipynb 파일에서 쉘 단위로 실행하면 됩니다.
(1)</p>
<pre><code class="language-python"># Take a look at the video.
from IPython.display import HTML
from base64 import b64encode
mp4 = open(&quot;lego.mp4&quot;, &quot;rb&quot;).read()
data_url = &quot;data:video/mp4;base64,&quot; + b64encode(mp4).decode()
HTML(f&quot;&quot;&quot;&lt;video src=&quot;{data_url}&quot; width=400 controls&gt;&lt;/video&gt;&quot;&quot;&quot;)</code></pre>
<p>(2)</p>
<pre><code class="language-python">SEQUENCE = &quot;lego&quot;
PATH_TO_VIDEO = &quot;lego.mp4&quot;
DOWNSAMPLE_RATE = 2
SCENE_TYPE = &quot;object&quot;  # {outdoor,indoor,object}
# Run the script.
colmap_path = f&quot;datasets/{SEQUENCE}_ds{DOWNSAMPLE_RATE}&quot;
!rm -rf {colmap_path}
!bash projects/neuralangelo/scripts/preprocess.sh {SEQUENCE} {PATH_TO_VIDEO} {DOWNSAMPLE_RATE} {SCENE_TYPE}
# Check whether we have 100 images registered.
import os
num_images = len(os.listdir(f&quot;{colmap_path}/images&quot;))
print(&quot;----------------------------------------&quot;)
print(f&quot;Number of registered images: {num_images}&quot;)</code></pre>
<p>(3)</p>
<pre><code class="language-python">%cd ./neuralangelo
# Import Python libraries.
import numpy as np
import torch
import k3d
import json
import plotly.graph_objs as go
from collections import OrderedDict
# Import imaginaire modules.
from projects.nerf.utils import camera, visualize
from third_party.colmap.scripts.python.read_write_model import read_model
# Read the COLMAP data.
cameras, images, points_3D = read_model(path=f&quot;{colmap_path}/sparse&quot;, ext=&quot;.bin&quot;)
# Convert camera poses.
images = OrderedDict(sorted(images.items()))
qvecs = torch.from_numpy(np.stack([image.qvec for image in images.values()]))
tvecs = torch.from_numpy(np.stack([image.tvec for image in images.values()]))
Rs = camera.quaternion.q_to_R(qvecs)
poses = torch.cat([Rs, tvecs[..., None]], dim=-1)  # [N,3,4]
print(f&quot;# images: {len(poses)}&quot;)
# Get the sparse 3D points and the colors.
xyzs = torch.from_numpy(np.stack([point.xyz for point in points_3D.values()]))
rgbs = np.stack([point.rgb for point in points_3D.values()])
rgbs_int32 = (rgbs[:, 0] * 2**16 + rgbs[:, 1] * 2**8 + rgbs[:, 2]).astype(np.uint32)
print(f&quot;# points: {len(xyzs)}&quot;)</code></pre>
<p>(4)</p>
<pre><code class="language-python">json_fname = f&quot;{colmap_path}/transforms.json&quot;
with open(json_fname) as file:
    meta = json.load(file)
center = meta[&quot;sphere_center&quot;]
radius = meta[&quot;sphere_radius&quot;]
# ------------------------------------------------------------------------------------
# These variables can be adjusted to make the bounding sphere fit the region of interest.
# The adjusted values can then be set in the config as data.readjust.center and data.readjust.scale
readjust_x = 0.  # @param {type:&quot;number&quot;}
readjust_y = 0.  # @param {type:&quot;number&quot;}
readjust_z = 0.  # @param {type:&quot;number&quot;}
readjust_scale = 1.  # @param {type:&quot;number&quot;}
readjust_center = np.array([readjust_x, readjust_y, readjust_z])
# ------------------------------------------------------------------------------------
center += readjust_center
radius *= readjust_scale
# Make some points to hallucinate a bounding sphere.
sphere_points = np.random.randn(100000, 3)
sphere_points = sphere_points / np.linalg.norm(sphere_points, axis=-1, keepdims=True)
sphere_points = sphere_points * radius + center</code></pre>
<p>(5)</p>
<pre><code class="language-python">vis_depth = 0.2
# Visualize with Plotly.
x, y, z = *xyzs.T,
colors = rgbs / 255.0
sphere_x, sphere_y, sphere_z = *sphere_points.T,
sphere_colors = [&quot;#4488ff&quot;] * len(sphere_points)
traces_poses = visualize.plotly_visualize_pose(poses, vis_depth=vis_depth, xyz_length=0.02, center_size=0.01, xyz_width=0.005, mesh_opacity=0.05)
trace_points = go.Scatter3d(x=x, y=y, z=z, mode=&quot;markers&quot;, marker=dict(size=1, color=colors, opacity=1), hoverinfo=&quot;skip&quot;)
trace_sphere = go.Scatter3d(x=sphere_x, y=sphere_y, z=sphere_z, mode=&quot;markers&quot;, marker=dict(size=0.5, color=sphere_colors, opacity=0.7), hoverinfo=&quot;skip&quot;)
traces_all = traces_poses + [trace_points, trace_sphere]
layout = go.Layout(scene=dict(xaxis=dict(showspikes=False, backgroundcolor=&quot;rgba(0,0,0,0)&quot;, gridcolor=&quot;rgba(0,0,0,0.1)&quot;),
                              yaxis=dict(showspikes=False, backgroundcolor=&quot;rgba(0,0,0,0)&quot;, gridcolor=&quot;rgba(0,0,0,0.1)&quot;),
                              zaxis=dict(showspikes=False, backgroundcolor=&quot;rgba(0,0,0,0)&quot;, gridcolor=&quot;rgba(0,0,0,0.1)&quot;),
                              xaxis_title=&quot;X&quot;, yaxis_title=&quot;Y&quot;, zaxis_title=&quot;Z&quot;, dragmode=&quot;orbit&quot;,
                              aspectratio=dict(x=1, y=1, z=1), aspectmode=&quot;data&quot;), height=800)
fig = go.Figure(data=traces_all, layout=layout)
fig.show()</code></pre>
<p>(6)</p>
<pre><code class="language-python">GROUP = &quot;test_exp&quot;
NAME = &quot;lego&quot;
!torchrun --nproc_per_node=1 train.py \
    --logdir=logs/{GROUP}/{NAME} \
    --show_pbar \
    --config=projects/neuralangelo/configs/custom/lego.yaml \
    --data.readjust.scale=0.5 \
    --max_iter=40000 \
    --validation_iter=99999999 \
    --model.object.sdf.encoding.coarse2fine.step=200 \
    --model.object.sdf.encoding.hashgrid.dict_size=19 \
    --optim.sched.warm_up_end=200 \
    --optim.sched.two_steps=[12000,16000]</code></pre>
<p>(7)</p>
<pre><code class="language-python">mesh_fname = f&quot;logs/{GROUP}/{NAME}/mesh.ply&quot;
!torchrun --nproc_per_node=1 projects/neuralangelo/scripts/extract_mesh.py \
    --config=logs/{GROUP}/{NAME}/config.yaml \
    --checkpoint=logs/{GROUP}/{NAME}/epoch_00068_iteration_000020000_checkpoint.pt \
    --output_file={mesh_fname} \
    --resolution=300 --block_res=128 \
    --textured</code></pre>
<p>(8)</p>
<pre><code class="language-python">import numpy as np
import trimesh
# Load the mesh.
mesh = trimesh.load(mesh_fname)
print(f&quot;# vertices: {len(mesh.vertices)}&quot;)
print(f&quot;# faces: {len(mesh.faces)}&quot;)
# Create a Trimesh scene and visualize the mesh.
scene = trimesh.Scene()
scene.add_geometry(mesh)
scene.show()</code></pre>
<h3 id="결과물">결과물</h3>
<p>(원본)
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/b5d55846-2bae-4e94-8aa0-e8b3c0b25c04/image.gif" alt=""></p>
<p>위의 영상을 랜더링한 결과는 아래와 같습니다.</p>
<p>(결과물)
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/42fa0a95-abd1-40ba-b8b5-b35b07f2a56e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[무선 통신 프로토콜 목록]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%EB%AC%B4%EC%84%A0-%ED%86%B5%EC%8B%A0-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EB%AA%A9%EB%A1%9D</link>
            <guid>https://velog.io/@yoo-seunghyeon/%EB%AC%B4%EC%84%A0-%ED%86%B5%EC%8B%A0-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EB%AA%A9%EB%A1%9D</guid>
            <pubDate>Tue, 26 Aug 2025 01:15:26 GMT</pubDate>
            <description><![CDATA[<h4 id="단거리-무선-통신-short-range-wireless"><strong>단거리 무선 통신 (Short-Range Wireless)</strong></h4>
<ul>
<li><p><strong>[[Wi-Fi (Wireless Fidelity)]]:</strong> 가장 널리 사용되는 무선 근거리 통신망(WLAN) 기술입니다. IEEE 802.11 표준을 기반으로 하며, Wi-Fi 4 (802.11n), Wi-Fi 5 (802.11ac), <strong>Wi-Fi 6/6E</strong> (802.11ax), <strong>Wi-Fi 7</strong> (802.11be) 등 다양한 버전이 있습니다.</p>
</li>
<li><p><strong>[[블루투스 (Bluetooth)]]:</strong> 스마트폰, 이어폰, 키보드 등 휴대용 장치 간의 저전력, 단거리 데이터 교환에 사용됩니다. 클래식 블루투스와 저전력 블루투스(BLE)로 나뉩니다.</p>
</li>
<li><p><strong>[[Zigbee]]:</strong> 저전력, 저속도, 저비용을 특징으로 하는 프로토콜입니다. 스마트 홈의 조명, 센서, 스위치 등 사물 인터넷(IoT) 기기 제어에 널리 쓰입니다.</p>
</li>
<li><p><strong>[[Z-Wave]]:</strong> Zigbee와 유사하게 스마트 홈 및 IoT 기기에 사용되는 저전력 무선 기술입니다.</p>
</li>
<li><p><strong>[[NFC (Near Field Communication)]]:</strong> 매우 짧은 거리(약 10cm 이내)에서 작동하며, 교통카드, 모바일 결제, 기기 간 간편한 페어링에 사용됩니다.</p>
</li>
<li><p>[[<strong>UWB (Ultra-Wideband)]]:</strong> 매우 넓은 주파수 대역을 사용하여 정밀한 거리 측정과 위치 추적이 가능한 기술입니다. 스마트폰을 이용한 디지털 키나 물품 추적에 활용됩니다.</p>
</li>
</ul>
<hr>
<h4 id="장거리-무선-통신-long-range-wireless"><strong>장거리 무선 통신 (Long-Range Wireless)</strong></h4>
<p>광역 통신망(WAN) 환경에서 사용되며, 더 넓은 범위를 지원합니다.</p>
<ul>
<li><p><strong>[[LoRaWAN (Long Range Wide Area Network)]]:</strong> 저전력으로 수 킬로미터까지 통신이 가능하여, 스마트 시티, 스마트 농업 등 넓은 지역의 IoT 센서 데이터 수집에 적합합니다.</p>
</li>
<li><p><strong>[[Sigfox]]:</strong> LoRaWAN과 유사한 저전력 광역 통신망(LPWAN) 기술로, 간단한 메시지를 보내는 데 특화되어 있습니다.</p>
</li>
<li><p><strong>[[LTE-M (LTE for Machine-Type Communication)]]:</strong> 기존 LTE 망을 활용하여 IoT 기기 통신을 지원하는 기술입니다. LoRa나 Sigfox보다 빠른 속도를 제공합니다.</p>
</li>
<li><p><strong>[[NB-IoT (Narrowband-IoT)]]:</strong> LTE-M과 함께 대표적인 셀룰러 IoT 기술로, 더 좁은 대역폭을 사용하여 커버리지를 넓히고 배터리 소모를 줄인 것이 특징입니다.</p>
</li>
</ul>
<hr>
<h4 id="셀룰러-통신-cellular-communication"><strong>셀룰러 통신 (Cellular Communication)</strong></h4>
<p>이동통신 기지국을 통해 음성 및 데이터를 주고받는 기술입니다.</p>
<ul>
<li><p><strong>[[3G (WCDMA, EV-DO)]]:</strong> 초기 데이터 통신을 가능하게 한 3세대 이동통신 기술입니다.</p>
</li>
<li><p><strong>[[4G (LTE, LTE-Advanced)]]:</strong> 고속 데이터 통신을 대중화시킨 4세대 이동통신 기술입니다.</p>
</li>
<li><p><strong>[[5G (NR - New Radio)]]:</strong> 초고속, 초저지연, 초연결을 특징으로 하는 5세대 이동통신 기술로, 자율주행, 원격의료, 실감형 미디어 등에 활용됩니다.</p>
</li>
</ul>
<hr>
<h4 id="미디어-스트리밍-프로토콜"><strong>미디어 스트리밍 프로토콜</strong></h4>
<p>언급하신 프로토콜들은 실시간 음성/영상 데이터를 전송하는 데 특화되어 있으며, 유선 및 무선 환경 모두에서 사용됩니다.</p>
<ul>
<li><p><strong>[[WebRTC (Web Real-Time Communication)]]:</strong> 웹 브라우저 간에 별도의 플러그인 없이 실시간으로 음성, 영상, 데이터를 주고받을 수 있게 하는 개방형 기술입니다. 화상 회의, 온라인 교육 등에 널리 쓰입니다.</p>
</li>
<li><p><strong>[[RTMP (Real-Time Messaging Protocol)]]:</strong> 실시간 비디오 및 오디오 스트리밍을 위한 프로토콜입니다. 주로 인터넷 방송 플랫폼으로 영상을 송출할 때 사용됩니다.</p>
</li>
<li><p><strong>[[RTSP (Real-Time Streaming Protocol)]]:</strong> 미디어 서버를 원격으로 제어하기 위한 프로토콜로, IP 카메라나 영상 감시 시스템에서 많이 사용됩니다.</p>
</li>
<li><p><strong>[[HLS (HTTP Live Streaming)]]:</strong> Apple에서 개발한 프로토콜로, HTTP를 기반으로 하여 안정적인 스트리밍이 가능합니다. 대부분의 동영상 플랫폼에서 사용됩니다.</p>
</li>
<li><p><strong>[[MPEG-DASH (Dynamic Adaptive Streaming over HTTP)]]:</strong> HLS와 유사한 HTTP 기반의 적응형 스트리밍 기술로, 국제 표준이라는 장점이 있습니다.</p>
</li>
<li><p><strong>[[Secure Reliable Transport (SRT)]]:</strong> SRT 프로토콜은 스트리밍 기술 제공업체인 Haivision이 개발한 오픈 소스 표준입니다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebRTC]]></title>
            <link>https://velog.io/@yoo-seunghyeon/WebRTC</link>
            <guid>https://velog.io/@yoo-seunghyeon/WebRTC</guid>
            <pubDate>Tue, 26 Aug 2025 01:14:41 GMT</pubDate>
            <description><![CDATA[<h2 id="배경지식">배경지식</h2>
<p><strong>RFC(Request for Comments)</strong>
직역하면 비평을 기다리는 문서. IETF는 RFC로 발표된 일부 제안을 인터넷 표준으로 채택함. 모든 내용이 표준인 것은 아님.</p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Request_for_Comments">https://en.wikipedia.org/wiki/Request_for_Comments</a></p>
</blockquote>
<p><strong>IETF(Internet Engineering Task Force)</strong>
인터넷 표준 기구. 프로토콜을 구성하는 기술 표준을 담당함.</p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force">https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force</a></p>
</blockquote>
<p><strong>인터넷 표준(Internet Standard, STD)</strong> 
인터넷에 적용되는 기술이나 방법론을 표준으로 제정한 규격. </p>
<blockquote>
<p>참고 : <a href="https://en.wikipedia.org/wiki/Internet_Standard">https://en.wikipedia.org/wiki/Internet_Standard</a>
공식문서 : <a href="https://www.rfc-editor.org/standards">https://www.rfc-editor.org/standards</a></p>
</blockquote>
<p><strong>NAT (Network Address Translation)</strong>
여러 대의 컴퓨터가 IP 주소를 공유하도록 하는 기술 : <a href="https://developer.mozilla.org/ko/docs/Glossary/NAT">https://developer.mozilla.org/ko/docs/Glossary/NAT</a>
공인 IP 부족을 해결하는 기술 : <a href="https://en.wikipedia.org/wiki/Network_address_translation">https://en.wikipedia.org/wiki/Network_address_translation</a></p>
<blockquote>
<p>but. 얘 때문에 P2P 연결이 까다로워짐.</p>
</blockquote>
<hr>
<h2 id="ice-interactive-connectivity-establishment">ICE (Interactive Connectivity Establishment)</h2>
<blockquote>
<p><a href="https://datatracker.ietf.org/doc/html/rfc8445">https://datatracker.ietf.org/doc/html/rfc8445</a></p>
</blockquote>
<p>P2P 네트워킹에서 두 컴퓨터가 가능한 한 직접적으로 서로 통신할 수 있는 방법을 찾기 위해 컴퓨터 네트워킹에 사용되는 기술</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/2df60565-a269-4dae-b777-c7d805fa1256/image.png" alt=""></p>
<p><strong><em>공식문서 기반 Gemini 설명</em></strong></p>
<h2 id="ice란-무엇인가-초간단-비유">ICE란 무엇인가? (초간단 비유)</h2>
<p>일단 개념부터 잡고 가자. ICE는 <strong>Interactive Connectivity Establishment</strong>의 약자야. 우리말로 하면 &#39;상호 연결성 확립&#39; 정도 되겠네.</p>
<p>쉽게 비유를 들어볼게.</p>
<p>네가 아파트 단지(사설망, Private Network)에 사는 친구(Peer)에게 전화를 걸고 싶다고 해보자.</p>
<ol>
<li><p><strong>직접 전화 (Host Candidate):</strong> 친구의 집 전화번호(내부 IP)를 알면 바로 걸 수 있겠지. 하지만 너도 다른 아파트 단지에 산다면, 같은 동네가 아닌 이상 그 번호는 소용이 없어.</p>
</li>
<li><p><strong>경비실 통해 연결 (Server-Reflexive Candidate):</strong> 친구 집 전화번호를 모르니, 친구네 아파트 경비실(STUN 서버)에 전화를 걸어서 &quot;XX동 OOO호 사는 친구 좀 연결해주세요&quot;라고 하는 거야. 그럼 경비실이 친구 집으로 연결해주면서, 외부에서 전화할 수 있는 대표 번호(공인 IP 주소)를 알려주지.</p>
</li>
<li><p><strong>교환원 통해 연결 (Relayed Candidate):</strong> 경비실을 통해서도 연결이 안 되는 아주 보안이 철저한 아파트(대칭형 NAT 등)도 있어. 이럴 땐 외부 통신을 중계해주는 교환원(TURN 서버)을 쓰는 거야. 너는 교환원에게 전화하고, 교환원이 다시 친구에게 전화를 걸어서 서로 통화하게 해주는 거지. 이건 확실하지만, 교환원을 거치니 조금 느리고 비용(서버 트래픽)이 발생해.</p>
</li>
</ol>
<p><strong>ICE는 바로 이 모든 방법을 다 시도해서, 너와 친구가 통신할 수 있는 가장 빠르고 효율적인 경로를 찾아내는 프로토콜이야.</strong> 핵심은 &#39;될 때까지 다 해본다&#39;는 아주 적극적인 방식이라는 거지.</p>
<hr>
<h2 id="ice의-작동-원리-4단계">ICE의 작동 원리 (4단계)</h2>
<p>ICE는 크게 4단계로 동작해. RFC 8445 문서의 핵심 내용이기도 하지.</p>
<h3 id="1단계-후보-수집-candidate-gathering">1단계: 후보 수집 (Candidate Gathering)</h3>
<p>각 클라이언트(Peer)는 자신이 통신에 사용할 수 있는 모든 주소, 즉 <strong>후보(Candidate)</strong>들을 수집해. 위 비유에서 말한 방법들이 다 동원돼.</p>
<ul>
<li><p><strong>Host Candidate:</strong> 자신의 네트워크 카드에 할당된 IP 주소 (내부 IP). 같은 네트워크에 있다면 가장 빠른 경로야.</p>
</li>
<li><p><strong>Server-Reflexive Candidate:</strong> STUN 서버에 요청을 보내서 알아낸 자신의 공인 IP 주소. 대부분의 가정집이나 사무실 환경(NAT 환경)에서 필요해.</p>
</li>
<li><p><strong>Relayed Candidate:</strong> TURN 서버를 통해 할당받은 중계 주소. 가장 강력한 최후의 보루야. 어떤 네트워크 환경에서도 통신을 가능하게 해주지.</p>
</li>
</ul>
<p>이렇게 수집한 후보 리스트를 만들어. 각 후보는 <strong>우선순위(priority)</strong>를 가지는데, 보통 Host &gt; Server-Reflexive &gt; Relayed 순으로 높아. 직접 연결하는 게 가장 좋으니까.</p>
<h3 id="2단계-후보-교환-candidate-exchange">2단계: 후보 교환 (Candidate Exchange)</h3>
<p>이제 각자 수집한 후보 리스트를 상대방에게 알려줘야 해. 이건 ICE 프로토콜 자체의 역할은 아니고, SIP나 WebSocket 같은 <strong>신호(Signaling) 프로토콜</strong>을 통해 이뤄져. 보통 SDP(Session Description Protocol)라는 형식에 담아서 교환하지.</p>
<blockquote>
<p>나(Peer A): &quot;안녕? 나랑 통화하려면 여기로 연락해봐. 내 후보들은 A, B, C야.&quot;</p>
<p>너(Peer B): &quot;오케이! 내 후보들은 X, Y, Z야. 너도 참고해.&quot;</p>
</blockquote>
<p>이제 양쪽 모두 자신과 상대방의 모든 후보 주소 목록을 갖게 됐어.</p>
<h3 id="3단계-연결성-검사-connectivity-checks">3단계: 연결성 검사 (Connectivity Checks)</h3>
<p>이 단계가 ICE의 핵심이야. 서로의 후보들을 조합해서 <strong>후보 쌍(Candidate Pair)</strong>을 만들어. 예를 들면 (A, X), (A, Y), (B, X), (B, Y) ... 이런 식으로 모든 조합을 만드는 거지.</p>
<p>그리고 우선순위가 높은 후보 쌍부터 차례대로 <strong>STUN 프로토콜</strong>을 이용해서 연결을 시도해봐.</p>
<blockquote>
<p>나(A) -&gt; 너(X) 에게: &quot;STUN 요청 보낸다! 들리니?&quot;</p>
<p>너(X) -&gt; 나(A) 에게: &quot;어, 잘 받았어! STUN 응답 보낸다!&quot;</p>
</blockquote>
<p>이 요청과 응답이 성공적으로 오가면, 해당 후보 쌍은 <strong>유효(Valid)</strong>하다고 판단하고 &#39;유효 리스트(Valid List)&#39;에 추가해. 이 과정에서 새로운 후보가 발견되기도 하는데, 이걸 <strong>Peer-Reflexive Candidate</strong>라고 해. 내가 보낸 요청을 받은 상대방이 &quot;어? 네가 나한테 보낸 주소는 내가 아는 네 공인 주소랑 다른데? 실제로는 이 주소로 왔어&quot; 라고 알려주는 거지. 이것도 아주 중요한 통신 경로가 될 수 있어.</p>
<h3 id="4단계-후보-쌍-지명-및-ice-종료-nominating-pairs--concluding">4단계: 후보 쌍 지명 및 ICE 종료 (Nominating Pairs &amp; Concluding)</h3>
<p>연결성 검사를 통해 유효한 경로들을 여러 개 찾았을 수 있잖아? 그럼 이제 둘 중 하나의 <strong>제어 에이전트(Controlling Agent)</strong>가 최종적으로 사용할 경로를 하나 <strong>지명(Nominate)</strong> 해야 해.</p>
<blockquote>
<p><strong>나(Controlling):</strong> &quot;여러 경로를 테스트해보니 (B, Y) 경로가 제일 좋은 것 같아. 앞으로 이걸로 통신하자!&quot;</p>
</blockquote>
<p>제어 에이전트는 STUN 요청에 <strong><code>USE-CANDIDATE</code></strong> 라는 특별한 플래그를 붙여서 보내. 이 요청을 받은 상대방(Controlled Agent)도 동의하면, 이 후보 쌍이 <strong>선택된 쌍(Selected Pair)</strong>이 되고, 앞으로 모든 데이터는 이 경로를 통해 오가게 돼.</p>
<p>ICE 프로세스가 성공적으로 끝나면, 다른 후보들은 정리하고 선택된 경로로만 통신을 유지하는 거야.</p>
<hr>
<h2 id="왜-이런-기능이-필요할까-의문점-파헤치기">왜 이런 기능이 필요할까? (의문점 파헤치기)</h2>
<p>RFC 문서를 읽다 보면 &#39;이건 왜 이렇게 복잡하게 만들었지?&#39; 싶은 부분이 있을 거야. 몇 가지 중요한 것들을 짚어줄게.</p>
<h3 id="❓-왜-후보마다-복잡한-우선순위priority를-계산할까">❓ 왜 후보마다 복잡한 우선순위(Priority)를 계산할까?</h3>
<ul>
<li><p><strong>이유:</strong> <strong>효율성 때문이야.</strong> 수십 개의 후보 쌍을 무작위로 테스트하면 가장 좋은 경로를 찾기까지 너무 오래 걸려. 그래서 RFC 5.1.2.1에 나오는 공식 <code>priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 - component ID)</code> 을 사용해.</p>
</li>
<li><p><strong>상황:</strong> 이 공식을 통해 양쪽 피어가 거의 동일한 순서로 테스트를 진행하게 돼. <strong>Host(직접) &gt; Peer-Reflexive &gt; Server-Reflexive(NAT 통과) &gt; Relayed(중계)</strong> 순으로 우선순위를 부여해서, 가장 빠르고 비용이 적게 드는 경로를 먼저 시도하게 만드는 거지. 이렇게 함으로써 통화 연결 시간을 단축시킬 수 있어.</p>
</li>
</ul>
<h3 id="❓-왜-주기적으로-keepalive-메시지를-보낼까">❓ 왜 주기적으로 <code>Keepalive</code> 메시지를 보낼까?</h3>
<ul>
<li><p><strong>이유:</strong> <strong>NAT 장비가 길을 잊어버리기 때문이야.</strong> NAT는 일정 시간 동안 트래픽이 없으면 만들어뒀던 &#39;내부 IP &lt;-&gt; 공인 IP&#39; 매핑 정보를 삭제해버려. 이걸 <strong>NAT 바인딩 타임아웃</strong>이라고 해.</p>
</li>
<li><p><strong>상황:</strong> 통화 중에 잠시 말을 안 하면(Silence Suppression) 데이터 전송이 멈추겠지? 그때 NAT가 연결을 끊어버리면 상대방 목소리가 다시 들리지 않게 돼. 이걸 막기 위해 RFC 11장에 따라 보통 15초마다 작은 STUN 패킷(Binding Indication)을 보내서 &quot;나 아직 이 경로 쓰고 있어!&quot;라고 NAT에게 알려주는 거야.</p>
</li>
</ul>
<h3 id="❓-lite-구현과-full-구현은-왜-나뉘어-있나">❓ <code>Lite</code> 구현과 <code>Full</code> 구현은 왜 나뉘어 있나?</h3>
<ul>
<li><p><strong>이유:</strong> <strong>모든 장치가 복잡한 ICE 로직을 다 구현할 필요는 없기 때문이야.</strong></p>
</li>
<li><p><strong>상황:</strong> 예를 들어, 항상 공인 IP를 사용하는 서버(미디어 서버 등)가 있다고 해보자. 이 서버는 NAT 뒤에 있는 클라이언트처럼 복잡하게 자기 주소를 찾을 필요가 없어. 이런 서버는 <strong><code>Lite</code></strong> 버전만 구현해서, 들어오는 연결성 검사에 응답만 잘 해주면 돼. 반면, 일반 사용자 PC나 스마트폰은 언제 어디서든 접속해야 하니 모든 기능을 갖춘 <strong><code>Full</code></strong> 버전을 구현해야 하는 거지. 이렇게 역할을 나눠서 구현의 부담을 줄여주는 거야.</p>
</li>
</ul>
<h3 id="❓-ice2-옵션은-무엇이고-왜-필요한가">❓ <code>ice2</code> 옵션은 무엇이고 왜 필요한가?</h3>
<ul>
<li><p><strong>이유:</strong> <strong>프로토콜 버전 관리 및 하위 호환성 때문이야.</strong></p>
</li>
<li><p><strong>상황:</strong> 현재 표준인 RFC 8445는 이전 버전인 RFC 5245에서 일부 기능을 개선하고 변경했어. 가장 큰 차이는 &#39;공격적 지명(Aggressive Nomination)&#39;이라는 절차가 사라진 거야. <code>ice2</code> 옵션은 후보 교환 시 &quot;나는 새로운 RFC 8445 규격을 따르는 에이전트야&quot;라고 상대방에게 알려주는 신호야. 이 신호를 받은 구버전 에이전트는 호환되지 않는 기능을 사용하지 않게 되어 통신 오류를 막을 수 있어.</p>
</li>
</ul>
<h3 id="❓-stun-사용자-이름username은-왜-그렇게-중요할까-보안-그-이상">❓ STUN 사용자 이름(Username)은 왜 그렇게 중요할까? (보안 그 이상)</h3>
<ul>
<li><p><strong>이유:</strong> 이건 단순히 인증을 넘어, <strong>잘못된 상대와 연결되는 것을 막기 위한 핵심 장치야.</strong></p>
</li>
<li><p><strong>상황:</strong> (RFC 부록 B.4 참조) A, B 두 회사가 있다고 치자. 두 회사 모두 내부망에서 <code>192.168.1.10</code> 이라는 IP를 쓰는 직원이 있을 수 있어. A사의 &#39;철수&#39;가 B사의 &#39;영희&#39;에게 연결을 시도하는데, 신호가 잘못 전달되어 같은 A사에 있는 &#39;민수&#39;의 후보 정보가 전달됐다고 생각해봐. 철수는 영희의 주소라고 생각하고 <code>192.168.1.10</code>으로 연결성 검사를 보냈는데, 그게 민수에게 도착할 수 있어. 이때, 후보 교환 시 주고받은 <strong>무작위 <code>username</code></strong> 값이 다르면 민수의 ICE 클라이언트는 &quot;어? 이거 내 거 아닌데?&quot; 하고 요청을 거부해. 만약 이 장치가 없다면, 철수는 영희가 아닌 민수와 연결될 뻔한 거지!</p>
</li>
</ul>
<hr>
<p>ICE는 네트워크 환경의 불확실성을 극복하기 위해 만들어진 아주 똑똑하고 실용적인 프로토콜이야. 처음에는 용어도 많고 절차도 복잡해 보이지만, 각 단계와 기능이 왜 필요한지 이해하고 나면 전체 그림이 보일 거야. 궁금한 거 있으면 언제든지 다시 물어보라고.</p>
<h2 id="webrtc">WebRTC</h2>
<blockquote>
<p>추가 참고 공식문서
<a href="https://www.rfc-editor.org/rfc/rfc7742.html">https://www.rfc-editor.org/rfc/rfc7742.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[YDLidar SDK 라이브러리 사용하기]]></title>
            <link>https://velog.io/@yoo-seunghyeon/YDLidar-SDK-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoo-seunghyeon/YDLidar-SDK-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 26 Aug 2025 01:06:26 GMT</pubDate>
            <description><![CDATA[<h1 id="ydlidar">YDLidar</h1>
<blockquote>
<p>공식 홈페이지 : <a href="https://www.ydlidar.com/">https://www.ydlidar.com/</a>
공식 github : <a href="https://github.com/YDLIDAR/YDLidar-SDK">https://github.com/YDLIDAR/YDLidar-SDK</a></p>
</blockquote>
<p>사용한 LiDAR 버전은 X4-Pro</p>
<h3 id="윈도우에서는-build가-너무-힘들어서-wsl을-사용">윈도우에서는 build가 너무 힘들어서 WSL을 사용</h3>
<h2 id="usb-포트-연결">USB 포트 연결</h2>
<ol>
<li>WSL 20.04 설치 및 등록</li>
<li>VSCode에서 연결</li>
<li>Powershell 관리자 모드로 열기</li>
<li>win-usbipd 설치^[<a href="https://github.com/dorssel/usbipd-win%5D">https://github.com/dorssel/usbipd-win]</a><pre><code>winget install usbipd</code></pre></li>
<li>설치가 완료되면 Powershell 관리자 모드로 새로 창 열고 기존 창은 닫기</li>
<li>usb 포트 확인하기<pre><code>usbipd list</code></pre></li>
<li>연결할 포트 번호 확인해서 공유로 변경<pre><code>usbipd bind --busid 3-2</code></pre></li>
<li>공유가 가능한 포트 wsl에 연결<pre><code>usbipd attach --wsl --busid 3-2</code></pre></li>
<li>다시 WSL로 돌아가서 확인해보기<pre><code>lsusb</code></pre></li>
</ol>
<h2 id="cmake-세팅">Cmake 세팅</h2>
<pre><code>sudo apt update &amp;&amp; sudo apt upgrade -y
sudo apt install git cmake g++ build--essential swig</code></pre><h2 id="python-세팅">Python 세팅</h2>
<pre><code>sudo apt install python3-pip</code></pre><h2 id="ydlidar-설치">YDLidar 설치</h2>
<pre><code>cd ~
git clone https://github.com/YDLIDAR/YDLidar-SDK.git

cd ~/YDLidar-SDK
mkdir build

cd build
cmake ..
make
sudo make install

cd ~/YDLidar-SDK
sudo python3 setup.py install</code></pre><h2 id="추가-정보">추가 정보</h2>
<p>YDLiDAR의 SDK API for Developers를 확인하면 파라미터 테이블이 있다.
하지만 여기에는 X4-Pro의 파라미터가 없어 사용할 수 없었다.</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/c1318bec-79c9-485c-beda-add11d123aff/image.png" alt=""></p>
<p>이를 해결하기 위해 깃허브를 탐색했고, 그 결과 YDLiDAR ROS2에서 힌트를 얻을 수 있었다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/fa508451-6d11-44a4-9213-07e8b6e249e6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/308785f5-aee4-41eb-9a43-03f14fe99b19/image.png" alt=""></p>
<p>X4와의 차이점은 <code>isSingleChannel</code>과 <code>support_motor_dtr</code> 였고 이를</p>
<pre><code class="language-python">dir(ydlidar)</code></pre>
<p>를 통해 유사한 함수를 찾았고, 해당 파라미터 옵션을 세팅하니 동작하여 X4-Pro를 사용할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Social Login]]></title>
            <link>https://velog.io/@yoo-seunghyeon/Social-Login</link>
            <guid>https://velog.io/@yoo-seunghyeon/Social-Login</guid>
            <pubDate>Mon, 25 Aug 2025 23:50:49 GMT</pubDate>
            <description><![CDATA[<p>소셜 로그인이란 OAuth2.0기반으로 사용자의 정보를 이용하여 다른 웹 사이트나 애플리케이션에 간편하게 로그인할 수 있도록 하는 기능</p>
<h2 id="oauth20이란">OAuth2.0이란?</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/b39f7b23-dc74-4848-af59-f6726e5f3869/image.png" alt=""></p>
<blockquote>
<p>참고 : <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1">https://datatracker.ietf.org/doc/html/rfc6749#section-1.1</a></p>
</blockquote>
<h2 id="사진으로-보는-프로토콜-요약">사진으로 보는 프로토콜 요약</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/e343e35e-c588-4209-ab24-ab59a822babb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/40a66be4-e1b8-43db-b970-2710844c3059/image.png" alt=""></p>
<h2 id="개요">개요</h2>
<p>OAuth는 인가 계층(authorization layer)을 도입하고 클라이언트의 역할과 리소스 소유자의 역할을 분리함으로써 이러한 문제들을 해결합니다. </p>
<ol>
<li>OAuth에서 클라이언트는 리소스 소유자에 의해 제어되고 리소스 서버에 의해 호스팅되는 리소스에 대한 접근을 요청하며, 리소스 소유자의 자격 증명과는 다른 별개의 자격 증명 집합을 발급받습니다.</li>
<li>리소스 소유자의 자격 증명을 사용하여 보호된 리소스에 접근하는 대신, 클라이언트는 액세스 토큰—특정 범위, 수명 및 기타 접근 속성을 나타내는 문자열—을 획득합니다. </li>
<li>액세스 토큰은 리소스 소유자의 승인을 받아 인가 서버에 의해 제3자 클라이언트에게 발급됩니다. </li>
<li>클라이언트는 이 액세스 토큰을 사용하여 리소스 서버가 호스팅하는 보호된 리소스에 접근합니다.</li>
</ol>
<p>예를 들어, 최종 사용자(리소스 소유자)는 인쇄 서비스(클라이언트)에 자신의 사용자 이름과 비밀번호를 공유하지 않고도 사진 공유 서비스(리소스 서버)에 저장된 자신의 보호된 사진에 대한 접근 권한을 부여할 수 있습니다. 대신, 그녀는 사진 공유 서비스가 신뢰하는 서버(인가 서버)에 직접 인증하고, 이 서버는 인쇄 서비스에 위임 관련 특정 자격 증명(액세스 토큰)을 발급합니다.</p>
<p>이 명세는 HTTP([RFC2616])와 함께 사용하도록 설계되었습니다. HTTP 이외의 프로토콜을 통한 OAuth의 사용은 이 문서의 범위를 벗어납니다.</p>
<p>정보 문서로 발표된 OAuth 1.0 프로토콜([RFC5849])은 소규모의 임시 커뮤니티 노력의 결과물이었습니다. 이 표준 트랙 명세는 OAuth 1.0의 배포 경험뿐만 아니라, 더 넓은 IETF 커뮤니티로부터 수집된 추가적인 사용 사례 및 확장성 요구사항을 기반으로 합니다. OAuth 2.0 프로토콜은 OAuth 1.0과 하위 호환되지 않습니다. 두 버전은 네트워크 상에서 공존할 수 있으며, 구현체는 둘 다 지원하도록 선택할 수 있습니다. 그러나 이 명세의 의도는 새로운 구현이 이 문서에 명시된 대로 OAuth 2.0을 지원하고, OAuth 1.0은 기존 배포를 지원하기 위해서만 사용되도록 하는 것입니다. OAuth 2.0 프로토콜은 OAuth 1.0 프로토콜과 구현 세부 사항을 거의 공유하지 않습니다. OAuth 1.0에 익숙한 구현자는 이 문서의 구조와 세부 사항에 대한 어떠한 가정도 없이 접근해야 합니다.</p>
<h2 id="나의-비유">나의 비유</h2>
<p>사용자가 가게에서 어떤 물건을 구매하고자 한다. 사용자의 돈은 모두 은행에 있다.</p>
<p>이 상황에서 금액을 지불하기 위해 사용자가 자신의 “계좌번호”와 “비밀번호”를 달려주는 것은 최악이다.</p>
<p>가게 사장이 물건 값 이상의 비용을 출금할 수 있기 때문이다. 이를 방지하기 위해 사용자는 수표를 써주기로 한다.</p>
<ol>
<li>수표에 “누가” 돈을 “얼마나” 출금할 수 있게 허가하는지 본인이 돈을 저축해둔 &quot;은행”에서 수표를 작성해준다.</li>
<li>이를 가게 사장에게 제공하면 가게 사장은 해당 은행에 수표를 들고 가서 제출하고, 은행에서는 금고 접근 허가증을 발급해준다.</li>
<li>허가증을 가지고 가게 사장이 금고에 가면 금고 보안 요원이 허가증에 적힌 금액만큼 돈을 꺼내서 가게 사장에게 준다. 이를 받은 가게 사장은 사용자에게 물건을 제공한다.</li>
</ol>
<h2 id="gemini로-이해하는-예제-및-설명">Gemini로 이해하는 예제 및 설명</h2>
<h2 id="👨💻-초보-개발자도-이해하는-oauth-20-구글-소셜-로그인-파헤치기">👨‍💻 초보 개발자도 이해하는 OAuth 2.0: 구글 소셜 로그인 파헤치기</h2>
<p>안녕하세요! 이제 막 개발의 세계에 발을 들인 여러분을 위해, &#39;인증&#39;과 &#39;인가&#39;라는 복잡해 보이는 산을 함께 넘어보려 합니다. 그중에서도 현대 웹 서비스의 필수 기능인 <strong>OAuth 2.0</strong>에 대해 알아보겠습니다. RFC 6749 문서는 그 표준 명세지만, 처음 보면 외계어 같을 수 있죠. 제가 실제 &#39;구글 소셜 로그인&#39;이 어떻게 동작하는지 보여드리면서 쉽고 재미있게 풀어드리겠습니다.</p>
<h3 id="🤔-oauth-20-대체-왜-필요한가요">🤔 OAuth 2.0, 대체 왜 필요한가요?</h3>
<p>옛날 옛적, &#39;찍스타그램&#39;이라는 사진 편집 앱이 있다고 상상해봅시다. 이 앱은 여러분의 구글 포토에 있는 사진을 가져와서 편집하고 싶어 합니다. 어떻게 해야 할까요?</p>
<ul>
<li><strong>나쁜 방법 👎:</strong> &#39;찍스타그램&#39; 앱에 여러분의 <strong>구글 아이디와 비밀번호</strong>를 직접 알려준다.</li>
<li><strong>문제점:</strong><ol>
<li><strong>비밀번호 유출 위험:</strong> &#39;찍스타그램&#39;이 해킹당하면 내 구글 계정 정보가 통째로 넘어갑니다.</li>
<li><strong>과도한 권한:</strong> &#39;찍스타그램&#39;은 사진만 필요한데, 내 이메일, 주소록, 구글 드라이브 파일까지 모두 접근할 수 있게 됩니다.</li>
<li><strong>접근 차단 불가:</strong> &#39;찍스타그램&#39;의 접근만 막고 싶어도, 구글 비밀번호를 바꾸는 수밖에 없습니다. 그럼 다른 모든 서비스에서도 비밀번호를 바꿔야 하죠.</li>
</ol>
</li>
</ul>
<p>이런 끔찍한 문제를 해결하기 위해 등장한 것이 바로 <strong>OAuth (Open Authorization)</strong> 입니다. OAuth는 &quot;비밀번호를 직접 주지 않고, 특정 권한만 제한적으로 부여하는 똑똑한 위임 방식&quot;이라고 할 수 있습니다. 마치 호텔 룸키처럼, 레스토랑이나 수영장은 이용할 수 있지만 다른 손님의 방은 열 수 없는 &#39;제한된 키&#39;를 발급해주는 것과 같습니다.</p>
<h3 id="등장인물-roles-oauth-20의-4가지-역할">등장인물 (Roles): OAuth 2.0의 4가지 역할</h3>
<p>OAuth 2.0에는 4명의 주요 등장인물이 있습니다. 구글 로그인 예시와 함께 살펴보죠.</p>
<ol>
<li><strong>Resource Owner (자원 소유자):</strong><ul>
<li><strong>설명:</strong> 보호된 자원(개인정보, 사진, 이메일 등)의 주인입니다.</li>
<li><strong>구글 로그인 예시:</strong> 바로 <strong>&#39;나&#39; 자신</strong>, 즉 서비스를 이용하려는 사용자입니다.</li>
</ul>
</li>
<li><strong>Client (클라이언트):</strong><ul>
<li><strong>설명:</strong> 자원 소유자를 대신해 특정 자원에 접근하려고 요청하는 애플리케이션입니다.</li>
<li><strong>구글 로그인 예시:</strong> 우리가 로그인하려는 웹사이트나 앱 (예: <code>awesome-service.com</code>)</li>
</ul>
</li>
<li><strong>Authorization Server (권한 서버):</strong><ul>
<li><strong>설명:</strong> 자원 소유자를 인증하고, 클라이언트에게 <strong>액세스 토큰(Access Token)</strong>을 발급해주는 서버입니다. &quot;너는 이 권한을 가져도 좋아&quot;라고 허락해주는 주체죠.</li>
<li><strong>구글 로그인 예시:</strong> <strong>Google의 인증/권한 서버</strong> (<code>accounts.google.com</code>)</li>
</ul>
</li>
<li><strong>Resource Server (자원 서버):</strong><ul>
<li><strong>설명:</strong> 보호된 자원을 실제로 가지고 있는 서버입니다. 액세스 토큰을 확인하고 요청에 응답합니다.</li>
<li><strong>구글 로그인 예시:</strong> <strong>Google의 API 서버</strong> (예: 사용자의 프로필 정보를 제공하는 <code>googleapis.com</code>)</li>
</ul>
</li>
</ol>
<blockquote>
<p>💡 Tip: 많은 경우, 권한 서버와 자원 서버는 같은 회사(Google)에 의해 운영되지만, 역할은 명확히 구분됩니다.</p>
</blockquote>
<hr>
<h3 id="🔑-핵심-아이템-토큰token이란">🔑 핵심 아이템: 토큰(Token)이란?</h3>
<p>OAuth 2.0의 핵심은 &#39;토큰&#39;이라는 특별한 열쇠를 주고받는 것입니다.</p>
<ul>
<li><strong>Access Token (액세스 토큰):</strong><ul>
<li>자원 서버에 접근할 수 있는 <strong>임시 출입증</strong>입니다.</li>
<li>수명이 짧고(예: 1시간), &quot;프로필 정보 읽기&quot;와 같이 제한된 권한만 담고 있습니다.</li>
<li>클라이언트(앱)는 이 토큰을 가지고 자원 서버(Google API)에 가서 &quot;이 출입증 보여줄 테니, OOO님의 프로필 정보 좀 주세요!&quot;라고 요청합니다.</li>
</ul>
</li>
<li><strong>Refresh Token (리프레시 토큰):</strong><ul>
<li>액세스 토큰의 유효기간이 만료되었을 때, <strong>새로운 액세스 토큰을 발급받기 위한 티켓</strong>입니다.</li>
<li>수명이 길며(예: 6개월), 사용자가 다시 로그인하는 번거로움 없이 서비스를 계속 이용하게 해줍니다.</li>
<li>액세스 토큰이 만료되면, 클라이언트는 이 리프레시 토큰을 권한 서버(Google)에 보내 &quot;티켓 여기 있으니, 새 출입증(액세스 토큰)으로 바꿔주세요&quot;라고 요청합니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="🚀-구글-소셜-로그인-과정-authorization-code-grant-방식">🚀 구글 소셜 로그인 과정 (Authorization Code Grant 방식)</h3>
<p>이제 모든 조각을 맞춰봅시다. OAuth 2.0의 가장 표준적이고 안전한 방식인 <strong>&#39;Authorization Code Grant&#39;</strong> 흐름을 통해 구글 소셜 로그인이 어떻게 이루어지는지 단계별로 살펴보겠습니다. (RFC 6749, Section 4.1)</p>
<h3 id="1단계-사용자의-로그인-요청"><strong>1단계: 사용자의 로그인 요청</strong></h3>
<blockquote>
<p><strong>나 (Resource Owner)</strong>는 awesome-service.com(Client)에서 &quot;Google로 로그인하기&quot; 버튼을 클릭합니다.</p>
</blockquote>
<p>이 간단한 클릭이 모든 여정의 시작입니다!</p>
<h3 id="2단계-권한-요청을-위한-google로의-이동"><strong>2단계: 권한 요청을 위한 Google로의 이동</strong></h3>
<blockquote>
<p>awesome-service.com(Client)은 나를 Google 권한 서버로 보냅니다. 이때 그냥 보내는 게 아니라, 특별한 정보들을 담아서 보냅니다.</p>
</blockquote>
<p>실제로는 브라우저가 아래와 같은 URL로 리디렉션됩니다.</p>
<p><code>https://accounts.google.com/o/oauth2/v2/auth?
 response_type=code&amp;
 client_id=YOUR_CLIENT_ID.apps.googleusercontent.com&amp;
 scope=openid%20profile%20email&amp;
 redirect_uri=https://awesome-service.com/auth/google/callback&amp;
 state=a_random_string_to_prevent_csrf</code></p>
<p>각 파라미터의 의미는 다음과 같습니다.</p>
<ul>
<li><code>response_type=code</code>: &quot;나중에 <strong>인증 코드(Authorization Code)</strong>를 발급해주세요&quot; 라는 의미입니다. 이게 바로 &#39;Authorization Code Grant&#39; 방식의 핵심입니다.</li>
<li><code>client_id</code>: <code>awesome-service.com</code>이 구글에 미리 등록하고 발급받은 <strong>고유 식별자</strong>입니다. &quot;제가 바로 그 앱이에요!&quot;라고 신분을 밝히는 것이죠. (RFC, Section 2.2)</li>
<li><code>scope</code>: 앱이 <strong>요청하는 권한의 범위</strong>입니다. 여기서는 &#39;openid, profile, email&#39; 정보에 접근하겠다고 요청하고 있습니다. (RFC, Section 3.3)</li>
<li><code>redirect_uri</code>: 구글이 인증 절차를 마친 후, 사용자를 <strong>다시 돌려보낼 주소</strong>입니다. (<code>awesome-service.com</code>의 특정 경로) 보안을 위해 이 주소는 구글에 미리 등록되어 있어야 합니다. (RFC, Section 3.1.2)</li>
<li><code>state</code>: CSRF(Cross-Site Request Forgery) 공격을 방지하기 위한 <strong>임의의 문자열</strong>입니다. 나중에 구글이 이 값을 그대로 돌려주면, <code>awesome-service.com</code>은 자기가 처음에 보낸 값이 맞는지 확인합니다. (RFC, Section 10.12)</li>
</ul>
<h3 id="3단계-사용자의-동의"><strong>3단계: 사용자의 동의</strong></h3>
<blockquote>
<p>Google 권한 서버는 나에게 로그인 창을 보여줍니다. 로그인을 하면, &quot;awesome-service.com이(가) 내 이름, 이메일 주소, 프로필 사진 등에 액세스하려고 합니다. 동의하시겠습니까?&quot; 라는 동의 화면을 보여줍니다.</p>
</blockquote>
<p>여기서 내가 &quot;동의&quot; 버튼을 누르면, 자원에 대한 접근을 허락하는 것입니다.</p>
<h3 id="4단계-google의-인증-코드-발급"><strong>4단계: Google의 &#39;인증 코드&#39; 발급</strong></h3>
<blockquote>
<p>내가 동의하면, Google 권한 서버는 2단계에서 지정된 redirect_uri로 나를 다시 돌려보냅니다. 이때 주소 뒤에 <strong>아주 짧은 시간만 유효한 임시 인증 코드(code)</strong>를 붙여줍니다.</p>
</blockquote>
<p>브라우저는 다음 주소로 리디렉션됩니다.</p>
<p><a href="https://awesome-service.com/auth/google/callback?code=A_TEMPORARY_AUTHORIZATION_CODE&amp;state=a_random_string_to_prevent_csrf">https://awesome-service.com/auth/google/callback?code=A_TEMPORARY_AUTHORIZATION_CODE&amp;state=a_random_string_to_prevent_csrf</a></p>
<p><strong>중요:</strong> 아직 액세스 토큰이 아닙니다! 이건 그저 액세스 토큰으로 교환할 수 있는 <strong>&#39;교환권&#39;</strong>일 뿐입니다. 이 교환권은 브라우저(사용자)를 통해 전달되므로 비교적 덜 안전한 채널을 거칩니다.</p>
<h3 id="5단계-인증-코드로-액세스-토큰-요청"><strong>5단계: &#39;인증 코드&#39;로 &#39;액세스 토큰&#39; 요청</strong></h3>
<blockquote>
<p>awesome-service.com의 백엔드 서버는 브라우저를 통해 전달받은 <strong>인증 코드</strong>와 자신의 Client Secret(구글에 등록할 때 받은 비밀키)을 가지고, Google 권한 서버의 <strong>토큰 엔드포인트(Token Endpoint)</strong>에 직접 요청을 보냅니다.</p>
</blockquote>
<p>이 과정은 사용자의 브라우저를 거치지 않고, <strong>서버 대 서버</strong>로 안전하게 통신합니다. (RFC, Section 4.1.3)</p>
<p>Bash</p>
<h1 id="awesome-servicecom-서버가-google-토큰-서버에-보내는-요청-예시-curl"><a href="http://awesome-service.com/"><code>awesome-service.com</code></a> 서버가 Google 토큰 서버에 보내는 요청 예시 (curl)</h1>
<pre><code class="language-bash">curl --location --request POST &#39;https://oauth2.googleapis.com/token&#39; \
--header &#39;Content-Type: application/x-www-form-urlencoded&#39; \
--data-urlencode &#39;code=A_TEMPORARY_AUTHORIZATION_CODE&#39; \
--data-urlencode &#39;client_id=YOUR_CLIENT_ID.apps.googleusercontent.com&#39; \
--data-urlencode &#39;client_secret=YOUR_CLIENT_SECRET&#39; \
--data-urlencode &#39;redirect_uri=https://awesome-service.com/auth/google/callback&#39; \
--data-urlencode &#39;grant_type=authorization_code&#39;</code></pre>
<ul>
<li><code>grant_type=authorization_code</code>: &quot;제가 받아온 인증 코드를 드릴 테니, 액세스 토큰으로 바꿔주세요&quot; 라는 의미입니다.</li>
</ul>
<h3 id="6단계-google의-액세스-토큰-및-리프레시-토큰-발급"><strong>6단계: Google의 &#39;액세스 토큰&#39; 및 &#39;리프레시 토큰&#39; 발급</strong></h3>
<blockquote>
<p>Google 권한 서버는 인증 코드, client_id, client_secret 등이 모두 유효한지 확인하고, 마침내 <strong>액세스 토큰</strong>과 <strong>리프레시 토큰</strong>을 awesome-service.com 서버에게 발급해줍니다.</p>
</blockquote>
<p>성공 시 <code>awesome-service.com</code> 서버가 받는 응답(JSON)입니다. (RFC, Section 5.1)</p>
<pre><code class="language-json">{
  &quot;access_token&quot;: &quot;A_REAL_ACCESS_TOKEN_STRING&quot;,
  &quot;expires_in&quot;: 3599,
  &quot;refresh_token&quot;: &quot;A_LONG_LIVED_REFRESH_TOKEN&quot;,
  &quot;scope&quot;: &quot;openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&quot;,
  &quot;token_type&quot;: &quot;Bearer&quot;
}</code></pre>
<p>이제 <code>awesome-service.com</code>은 드디어 진짜 열쇠(액세스 토큰)를 손에 넣었습니다! 이 토큰들은 서버의 데이터베이스에 안전하게 저장됩니다.</p>
<h3 id="7단계-액세스-토큰으로-사용자-정보-요청"><strong>7단계: &#39;액세스 토큰&#39;으로 사용자 정보 요청</strong></h3>
<blockquote>
<p>awesome-service.com 서버는 발급받은 <strong>액세스 토큰</strong>을 이용해 <strong>Google 자원 서버(API 서버)</strong>에 내 프로필 정보를 요청합니다.</p>
</blockquote>
<h1 id="awesome-servicecom-서버가-google-api-서버에-보내는-요청-예시"><a href="http://awesome-service.com/"><code>awesome-service.com</code></a> 서버가 Google API 서버에 보내는 요청 예시</h1>
<pre><code class="language-bash">curl --location --request GET &#39;https://www.googleapis.com/oauth2/v2/userinfo&#39; \
--header &#39;Authorization: Bearer A_REAL_ACCESS_TOKEN_STRING&#39;`</code></pre>
<ul>
<li><code>Authorization: Bearer ...</code>: HTTP 요청 헤더에 &quot;이 <code>Bearer</code>(소지자) 토큰을 가지고 있으니 권한을 확인해주세요&quot; 라는 의미로 액세스 토큰을 담아 보냅니다. (RFC, Section 7)</li>
</ul>
<h3 id="8단계-자원-제공-및-로그인-완료"><strong>8단계: 자원 제공 및 로그인 완료</strong></h3>
<blockquote>
<p>Google 자원 서버는 액세스 토큰이 유효한지 확인하고, awesome-service.com 서버에게 내 프로필 정보(이름, 이메일 등)를 보내줍니다.</p>
</blockquote>
<p>이제 <code>awesome-service.com</code>은 내 정보를 받았으므로, 이 정보를 바탕으로 회원가입을 시키거나 로그인을 완료하고 서비스를 제공할 수 있게 됩니다.</p>
<hr>
<h3 id="✨-요약-및-결론">✨ 요약 및 결론</h3>
<p>복잡해 보이지만 핵심은 간단합니다.</p>
<ol>
<li><strong>&quot;비밀번호를 직접 주지 않는다.&quot;</strong></li>
<li>사용자는 <strong>Google에 직접 로그인</strong>해서 권한을 <strong>&#39;동의&#39;</strong>만 한다.</li>
<li>앱은 <strong>&#39;인증 코드(교환권)&#39;</strong>를 임시로 받아온다.</li>
<li>앱의 <strong>&#39;서버&#39;</strong>가 안전하게 <strong>&#39;액세스 토큰(진짜 열쇠)&#39;</strong>으로 교환한다.</li>
<li>앱은 이 <strong>액세스 토큰</strong>으로 허락된 정보(예: 프로필)에만 접근한다.</li>
</ol>
<p>이것이 바로 OAuth 2.0이 우리의 소중한 정보를 안전하게 지키면서도 편리한 소셜 로그인을 가능하게 하는 원리입니다. 이 흐름을 잘 이해하신다면, 앞으로 API 문서를 보거나 인증 관련 기능을 구현할 때 훨씬 더 자신감을 가질 수 있을 겁니다. 화이팅입니다! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[힙과 백트래킹]]></title>
            <link>https://velog.io/@yoo-seunghyeon/heap%EA%B3%BC-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</link>
            <guid>https://velog.io/@yoo-seunghyeon/heap%EA%B3%BC-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</guid>
            <pubDate>Sat, 02 Aug 2025 10:16:25 GMT</pubDate>
            <description><![CDATA[<h1 id="힙heap">힙(heap)</h1>
<ul>
<li>완전 이진 트리에 있는 노드 중에서 키 값이 &quot;가장 큰 노드&quot;(max heap)나 키 값이 &quot;가장 작은 노드&quot;(min heap)를 찾기 위해서 만든 자료구조</li>
</ul>
<h2 id="개요">개요</h2>
<h3 id="최대-힙max-heap">최대 힙(max heap)</h3>
<ul>
<li>키 값이 가장 큰 노드를 찾기 위한 완전 이진 트리</li>
<li>부모 노드의 키 값 &gt; 자식 노드의 키 값</li>
<li>루트 노드: 키 값이 가장 큰 노드</li>
</ul>
<h3 id="최소-힙min-heap">최소 힙(min heap)</h3>
<ul>
<li>최대 힙의 반대 개념</li>
</ul>
<h3 id="예시"><em>예시</em></h3>
<p>&lt;힙 구조 O&gt;
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5ac94f89-52b9-48f2-b98e-b9093b2bb803/image.png" alt=""></p>
<p>&lt;힙 구조 X&gt;
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/055bd29c-994e-460a-9286-a4069f59ed93/image.png" alt=""></p>
<hr>
<h2 id="힙heap-연산">힙(heap) 연산</h2>
<h3 id="1-삽입">1. 삽입</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/8a29c62e-f45b-4e3f-aae9-94eed331e5d7/image.png" alt=""><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/6132121b-9c0f-414b-b415-7efba9238f47/image.png" alt=""></p>
<h3 id="2-삭제">2. 삭제</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/113a25ac-f35b-4c06-8980-6fe687db3dff/image.png" alt=""></p>
<hr>
<h2 id="구현">구현</h2>
<pre><code class="language-python">class MaxHeap:
    def __init__(self):
        self.heap = []

    def heappush(self, item):
        self.heap.append(item)
        self._siftup(len(self.heap) - 1)

    def _siftup(self, idx):
        parent = (idx - 1) // 2
        while idx &gt; 0 and self.heap[idx] &gt; self.heap[parent]:
            self.heap[idx], self.heap[parent] = self.heap[parent], self.heap[idx]
            idx = parent
            parent = (idx - 1) // 2

    def heappop(self):
        if len(self.heap) == 0
            raise IndexError(&quot;힙이 비었습니다.&quot;)
        if len(self.heap) == 1:
            return self.heap.pop()
        root = self.heap[0]
        self.heap[0] = self.heap.pop()
        self._siftdown(0)
        return root

    def heapify(self, array):
        self.heap = array[:]
        n = len(array)
        for i in range(n // 2 - 1, -1, -1):
            self._siftdown(i)

    def _siftdown(self, idx):
        n = len(self.heap)
        largest = idx
        left = 2 * idx + 1
        right = 2 * idx + 2

        if left &lt; n and self.heap[left] &gt; self.heap[largest]:
            largest = left
        if right &lt; n and self.heap[right] &gt; self.heap[largest]:
            largest = right
        if largest != idx:
            self.heap[idx], self.heap[largest] = self.heap[largest], self.heap[idx]
            self._siftdown(largest)</code></pre>
<h3 id="heapq-모듈">heapq 모듈</h3>
<ul>
<li>최소 힙을 구현한 라이브러리</li>
<li>힙 함수 활용<ul>
<li>heapq.heappush(heap, item) : item을 heap에 추가</li>
<li>heapq.heappop(heap) : heap에서 가장 왼쪽 원소를 pop</li>
<li>Heapq.heapify(x) : 리스트 x를 heap으로 변환 ( O(N) )</li>
</ul>
</li>
</ul>
<blockquote>
<p>그러면 최대 힙은 어떻게? -&gt; 값들을 모두 음수로 바꾸면 최소 힙으로 최대 힙처럼 사용 가능. 결과의 부호만 잘 처리하면 됨</p>
</blockquote>
<h2 id="우선순위-큐-priority-queue">우선순위 큐 (Priority Queue)</h2>
<ul>
<li>우선순위를 가진 항목들을 저장하는 큐</li>
<li>FIFO 순서가 아니라 우선순위가 높은 순서대로 먼저 나가게 된다.</li>
<li>우선순위 큐를 구현하는 가장 효율적인 방법은 힙을 사용하는 것이다.</li>
<li>노드 하나 추가/삭제의 시간 복잡도가 O(logN) 이며, 최대값/최소값을 O(1)에 구할 수 있다.</li>
</ul>
<h3 id="예시-1">예시</h3>
<pre><code class="language-python">import heapq

priority_queue = []

heapq.heappush(priority_queue, (3, &quot;3순위 작업&quot;))
heapq.heappush(priority_queue, (1, &quot;1순위 작업&quot;))
heapq.heappush(priority_queue, (2, &quot;2순위 작업&quot;))

print(priority_queue)
# [(1, &quot;1순위 작업&quot;), (3, &quot;3순위 작업&quot;), (2, &quot;2순위 작업&quot;)]

while priority_queue:
    task = heapq.heappop(priority_queue):
    print(task)

# (1, &quot;1순위 작업&quot;)
# (2, &quot;2순위 작업&quot;)
# (3, &quot;3순위 작업&quot;)</code></pre>
<h1 id="백트래킹">백트래킹</h1>
<h2 id="개요-1">개요</h2>
<h3 id="백트래킹-기법">백트래킹 기법</h3>
<ul>
<li>어떤 노드의 유망성(promising)을 점검하여 유망하지 않다면 그 부모의 노드로 돌아가고(backtracking) 다음 자식 노드를 살핀다.</li>
<li>유망하다. = 해답이 될 가능성이 있다.</li>
<li>가지치기(pruning) = 유망하지 않다면 해당 노드를 가지 않는다. 나무(Tree)의 쓸모없는 가지를 치는 것.</li>
</ul>
<h3 id="백트래킹-절차">백트래킹 절차</h3>
<ol>
<li>상태 공간 트리의 깊이 우선 탐색(DFS)를 실시한다.</li>
<li>각 노드가 유망한지 점검한다.</li>
<li>만일 해당 노드가 유망하지 않으면, 그 노드의 부모 노드로 돌아가서 다른 노드 검색을 계속 진행한다.</li>
</ol>
<h3 id="백트래킹과-dfs-차이점">백트래킹과 DFS 차이점</h3>
<ul>
<li>DFS는 완전 탐색이다. 백트래킹은 가지치기를 통해 필요한 요소만 탐색한다. -&gt; DFS를 pruning으로 최적화 하면 그게 백트래킹이다.</li>
</ul>
<h3 id="일반적인-코드-형태">일반적인 코드 형태</h3>
<pre><code class="language-python">def backtracking(node v):
    if promising(v):
        if there is a solution at v:
            find the solution
    else:
        for child of v:
            backtracking(child)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[cuda 연결하기]]></title>
            <link>https://velog.io/@yoo-seunghyeon/cuda-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoo-seunghyeon/cuda-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 06 Jun 2025 07:29:30 GMT</pubDate>
            <description><![CDATA[<ol start="0">
<li>NVIDIA GPU가 있어야 사용 가능
윈도우에서 내 GPU 확인하기<pre><code>nvidia-smi</code></pre><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5141e1ef-cbcd-4029-bb8c-2fa40618b9ab/image.png" alt=""></li>
</ol>
<ol start="2">
<li><p>아키텍쳐 확인
<a href="https://en.wikipedia.org/wiki/CUDA#GPUs_supported">https://en.wikipedia.org/wiki/CUDA#GPUs_supported</a>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/6cc1af96-ef0c-40a3-ae7d-ea4b5cd67a80/image.png" alt=""></p>
</li>
<li><p>아키텍쳐에 맞는 CUDA 버전 확인
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/46b472d1-643a-4364-842c-22fc62e3a63a/image.png" alt=""></p>
</li>
</ol>
<p>본인은 11.1~12.5까지 가능</p>
<ol start="4">
<li>CUDA 버전 및 파이썬 버전에 맞는 Pytorch 버전 확인
<a href="https://github.com/pytorch/pytorch/blob/main/RELEASE.md#release-compatibility-matrix">https://github.com/pytorch/pytorch/blob/main/RELEASE.md#release-compatibility-matrix</a>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/2068a2dd-c48d-4af8-918c-3f8cd8d8aec7/image.png" alt=""></li>
</ol>
<p>결론 : CUDA 12.1 | CUDNN 9.1.0.70 | Pytorch 2.4 | Python 3.12</p>
<ol start="5">
<li><p>CUDA 설치
<a href="https://developer.nvidia.com/cuda-toolkit-archive">https://developer.nvidia.com/cuda-toolkit-archive</a></p>
</li>
<li><p>CUDA 설치 확인 명령어</p>
<pre><code>nvcc --version</code></pre><p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/c9732ae9-5f3c-46d4-8064-7c808f1a5dea/image.png" alt=""></p>
</li>
</ol>
<ol start="7">
<li><p>설치할 때 입력한 경로와 실제 설치된 경로가 다르다는 것을 헤매다 블로그를 통해 알게됨. exe로 설치해서 그런듯</p>
<pre><code>C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.1</code></pre></li>
<li><p>CUDNN도 설치
<a href="https://developer.nvidia.com/rdp/cudnn-archive">https://developer.nvidia.com/rdp/cudnn-archive</a></p>
</li>
<li><p>설치된 zip파일의 압축을 해제하고, 안에 있는 bin, include, lib, LICENSE를 전부 복사해서 CUDA에 덮어쓰기</p>
</li>
<li><p>버전에 맞는 Pytorch 설치
<a href="https://pytorch.org/get-started/locally/">https://pytorch.org/get-started/locally/</a>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/54586ebc-5b5d-4453-9895-a026f1198db1/image.png" alt="">
밑에 나오는 명령어 가상환경에서 실행</p>
</li>
</ol>
<ol start="11">
<li>Pytorch 버전 확인<pre><code>import torch
print(torch.__version__)</code></pre><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/b879d69b-2067-42a4-9491-f20f47eb0e05/image.png" alt=""></li>
</ol>
<ol start="12">
<li>GPU 연결 확인<pre><code>USE_CUDA = torch.cuda.is_available()
device = torch.device(&#39;cuda:0&#39; if USE_CUDA else &#39;cpu&#39;)
</code></pre></li>
</ol>
<p>print(&#39;CUDA 사용 가능 여부 :&#39;, USE_CUDA)
print(&#39;현재 사용 device :&#39;, device)
print(&#39;CUDA Index :&#39;, torch.cuda.current_device())
print(&#39;GPU 이름 :&#39;, torch.cuda.get_device_name())
print(&#39;GPU 개수 :&#39;, torch.cuda.device_count())</p>
<pre><code>![](https://velog.velcdn.com/images/yoo-seunghyeon/post/3f79868b-3f6b-4ea0-9285-2c01430aa7c0/image.png)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Github 꾸미기]]></title>
            <link>https://velog.io/@yoo-seunghyeon/Github-%EA%BE%B8%EB%AF%B8%EA%B8%B0</link>
            <guid>https://velog.io/@yoo-seunghyeon/Github-%EA%BE%B8%EB%AF%B8%EA%B8%B0</guid>
            <pubDate>Fri, 06 Jun 2025 07:24:05 GMT</pubDate>
            <description><![CDATA[<p>두 사이트를 활용해 아이콘을 만들면 Github Profile을 꾸미기 유용하다.</p>
<blockquote>
<p>아이콘 만드는 사이트
<a href="https://shields.io/badges">https://shields.io/badges</a></p>
</blockquote>
<blockquote>
<p>아이콘 정보를 알 수 있는 사이트
<a href="https://simpleicons.org/">https://simpleicons.org/</a></p>
</blockquote>
<blockquote>
<p>shields.io 사용법
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/6504cf5e-1b1f-486e-8fa2-c8ff2d9b58c5/image.png" alt=""></p>
</blockquote>
<hr>
<p>추가로 도움 되는 아이콘 사이트</p>
<blockquote>
<p>Draw.io 또는 PPT에 사용하기 좋은 아이콘 사이트
<a href="https://techicons.dev/">https://techicons.dev/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 간단 CLI]]></title>
            <link>https://velog.io/@yoo-seunghyeon/AWS-%EA%B0%84%EB%8B%A8-CLI</link>
            <guid>https://velog.io/@yoo-seunghyeon/AWS-%EA%B0%84%EB%8B%A8-CLI</guid>
            <pubDate>Fri, 06 Jun 2025 07:21:19 GMT</pubDate>
            <description><![CDATA[<p>AWS CLI 버전 확인</p>
<pre><code>aws --version</code></pre><p>AWS Configure 확인</p>
<pre><code>aws sts get-caller-identity
</code></pre><p>AWS Configure 등록</p>
<pre><code>aws configure</code></pre><p>이후 access key 와 secret key 입력하고 나머지 2개의 입력창은 그냥 enter 누르면 됨.</p>
<p>AWS Configure 삭제
명령어가 따로 있는건 아닌듯. 그냥 home 디렉토리에 있는 .aws라는 디렉토리를 지우면 됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RDS - MySQL (Free Tier) 세팅]]></title>
            <link>https://velog.io/@yoo-seunghyeon/RDS-MySQL-Free-Tier-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@yoo-seunghyeon/RDS-MySQL-Free-Tier-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Fri, 06 Jun 2025 07:20:12 GMT</pubDate>
            <description><![CDATA[<p>우선 VPC 세팅이 필요.
VPC 하나 만들고 그 VPC에 두 개 이상의 AZ에 Subnet이 존재하게 세팅.
RDS 검색
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/7b8fca5a-0133-42bd-8e6a-3d5f8147c358/image.png" alt="">
데이터베이스 선택
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/7efb72dd-eb61-49eb-8e28-87ca6cb6bec3/image.png" alt="">
데이터베이스 생성 선택</p>
<pre><code>
# 데이터베이스 생성 방식 선택 : 표준 생성
# 엔진 옵션 : MySQL -&gt; 확장지원, 다중 AZ, 최적화된 쓰기를 ... 모두 비활성화
# 템플릿 : 프리티어
# 가용성 및 내구성 : 템플릿을 프리티어로 하면 모두 비활성화 됨.
# 설정
- DB 인스턴스 식별자 : 소문자로 원하는 이름 지정
- 자격 증명 설정
-&gt; 마스터 사용자 이름 : admin | root 쓰는게 편해보임.
-&gt; 자격 증명 관리 : 자체 관리
-&gt; 암호 자동 생성 : 비활성화
-&gt; 마스터 암호 : [알아서 입력]
-&gt; 마스터 암호 확인 : [알아서 입력]
# 인스턴스 구성
- 모두 비활성화, 프리티어 템플릿 사용하면 자동으로 다 비활성화 되어 있음.
- 인스턴스만 &quot;db.t3.micro&quot; 쓰는게 속편함.
# 스토리지
- 스토리지 유형 : 안전하게 gp2 사용하는게 속편함.
- 할당된 스토리지 : 20~6144GiB 인데 20이 제일 편함.
- 고급 설정 : 프리티어 템플릿이면 자동으로 비활성화
- 스토리지 자동 조정
-&gt; 스토리지 자동 조정 활성화 : 비활성화로 바꿀 필요 있음
# 연결
- 컴퓨팅 리소스 : 원하는거 선택. 본인은 EC2 컴퓨팅 리소스에 연결 안함 선택
- 네트워크 유형 : IPv4
- Virtual Private Cloud(VPC) : 자신의 VPC 선택
- DB 서브넷 그룹 : 생성하기로 생성
- 퍼블릭 액세스 : 테스트 용도로 쓸거라 허용했음.
- VPC 보안 그룹(방화벽) : DB용 보안 그룹 생성해둔거 사용
- 가용 영역 : 앞서 만들어둔 2개의 subnet 중 하나 선택
- RDS 프록시 : 비활성화
- 인증 기관 : 기본값
- 추가구성
-&gt; 포트 : 3306이 기본값. 그대로 사용
# 태그
- 새 태그 추가 : Name 태그를 통해 이름 설정
# 데이터베이스 싱증
- 데이터베이스 인증 옵션 : 암호 인증 선택
# 모니터링
- 아직은 비활성화
# 추가 구성
- 데이터 베이스 옵션
-&gt; 초기 데이터베이스 이름 : 입력해서 생성함
-&gt; DB 파라미터 그룹 : 그대로
-&gt; 옵션 그룹 : 그대로
- 백업 : 비활성화
- 암호화 : 비활성화
- 로그 내보내기 : 전부 비활성화
- IAM 역할 : 비활성화 (CloudWatch 쓸거면 세팅해야한다고 함.)
- 유지 관리
-&gt; 마이너 버전 자동 업그레이드 사용 : 비활성화
- 유지 관리 기간 : 기본 설정 없음
- 삭제 방지 : 비활성화</code></pre><h1 id="estimated-monthly-costs">Estimated Monthly costs</h1>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5b6bef10-ceba-4860-9f6c-34745969fee6/image.png" alt=""></p>
<p>위와 같이 세팅하면 이렇게 나온다.</p>
<p>주의할점은 VPC에서 서로 다른 AZ에 Subnet을 각각 하나씩 2개 이상은 만들어 둬야지 RDS가 만들어진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[K-최근접 이웃 알고리즘]]></title>
            <link>https://velog.io/@yoo-seunghyeon/K-NN</link>
            <guid>https://velog.io/@yoo-seunghyeon/K-NN</guid>
            <pubDate>Fri, 06 Jun 2025 07:11:47 GMT</pubDate>
            <description><![CDATA[<h1 id="k-nearest-neighbors-knn-or-k-nn">K-Nearest Neighbors (KNN or K-NN)</h1>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/845b5e32-5e55-49ba-b6b1-cb98e8305ec7/image.png" alt=""></p>
<h2 id="what">What?</h2>
<ul>
<li>머신러닝의 <strong>지도학습</strong> 알고리즘 중 하나.</li>
<li>새로운 데이터 포인트에서 가장 가까운 k개의 이웃을 찾고, 가장 빈도가 높은 클래스를 예측값으로 사용하는 알고리즘.</li>
</ul>
<h2 id="when">When?</h2>
<ul>
<li>데이터를 분류하거나 회귀할 때.</li>
</ul>
<h2 id="장단점">장단점</h2>
<blockquote>
<h3 id="장점">장점</h3>
</blockquote>
<ol>
<li>이해하기 쉽다.</li>
<li>적은 조정으로 좋은 성능을 발휘한다.</li>
</ol>
<blockquote>
<h3 id="단점">단점</h3>
</blockquote>
<ol>
<li>훈련 세트가 매우 크면 예측이 느려짐.</li>
<li>많은 특성을 가진 데이터셋에는 잘 동작하지 않음.</li>
<li>특성 값 대부분이 0인 데이터셋과는 특히 잘 동작하지 않음.</li>
</ol>
<h2 id="how">How?</h2>
<pre><code class="language-python"># 필요한 라이브러리 import
from sklearn.datasets import load_iris                     # 데이터
from sklearn.model_selection import train_test_split     # 데이터 분할
from sklearn.neighbors import KNeighborsClassifier         # K-최근접 알고리즘 모듈

# 데이터 가져오기
iris_datasets = load_iris()

# 학습용, 테스트용 으로 데이터 분할 (75%, 25%)
X_train, X_test, y_train, y_test = train_test_split(iris_dataset[&#39;data&#39;], iris_dataset[&#39;target&#39;], random_state=0)

# K-최근접 이웃 알고리즘 적용. 학습용 데이터 사용
knn = KNeighborsClassifier(n_neighbors=1)                # k=1 적용
knn.fit(X_train, y_train)

# 정확도 측정. 테스트용 데이터 사용
print(&quot;테스트 세트의 정확도 : {.2f}&quot;.format(knn.score(X_test,y_test)))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[지도학습]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%EC%A7%80%EB%8F%84%ED%95%99%EC%8A%B5</link>
            <guid>https://velog.io/@yoo-seunghyeon/%EC%A7%80%EB%8F%84%ED%95%99%EC%8A%B5</guid>
            <pubDate>Fri, 06 Jun 2025 07:11:11 GMT</pubDate>
            <description><![CDATA[<h1 id="지도학습">지도학습</h1>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/a39d9c9f-7593-4d91-a4a2-ab2a177d2828/image.png" alt=""></p>
<h2 id="what">What?</h2>
<ul>
<li>입력과 출력 샘플 데이터가 있고, 주어진 입력으로부터 출력을 예측하는 머신러닝 방법.</li>
</ul>
<h2 id="종류">종류</h2>
<blockquote>
<h3 id="1-분류">[1] 분류</h3>
<p><strong>What?</strong>
미리 정의된, 가능성 있는 여러 클래스 레이블 중 하나를 예측하는 것.</p>
</blockquote>
<p><strong>종류</strong></p>
<ol>
<li>이진 분류 : 딱 두 개의 클래스로 분류 (예/아니오)</li>
<li>다중 분류 : 셋 이상의 클래스로 분류</li>
</ol>
<blockquote>
<h3 id="2-회귀">[2] 회귀</h3>
<p><strong>What?</strong>
연속적인 숫자를 예측하는 것.</p>
</blockquote>
<h2 id="핵심-키워드">핵심 키워드</h2>
<blockquote>
<h3 id="1-일반화">[1] 일반화</h3>
<p>모델이 처음 보는 데이터에 대해 정확하게 예측할 수 있는 것
( = 훈련 데이터, 테스트 데이터 모두 잘 예측하는 것)</p>
</blockquote>
<blockquote>
<h3 id="2-과대적합">[2] 과대적합</h3>
<p>모델이 훈련 데이터에 너무 가깝게 맞춰져서 일반화가 안되는 것
( = 훈련 데이터는 잘 예측하나 테스트 데이터는 잘 예측하지 못하는 것)</p>
</blockquote>
<blockquote>
<h3 id="3-과소적합">[3] 과소적합</h3>
<p>모델이 너무 간단해서 데이터의 면면과 다양성을 잡아내지 못하는 것
( = 훈련 데이터, 테스트 데이터 모두 잘 예측하지 못하는 상태)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[S3 & Cloudfront를 활용한 정적 배포]]></title>
            <link>https://velog.io/@yoo-seunghyeon/S3-Cloudfront%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@yoo-seunghyeon/S3-Cloudfront%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Fri, 06 Jun 2025 07:10:13 GMT</pubDate>
            <description><![CDATA[<ol>
<li>사용할 도메인을 Rout53에서 구매 또는 등록</li>
<li>S3를 연결할 도메인 이름으로 생성</li>
<li>S3를 퍼블릭으로 열고 정책 생성기 실행</li>
<li>GetObjects 하나만 선택</li>
<li>생성된 정책 제일 뒤에 /* 붙이기</li>
<li>버지니아 북부 리전의 ACM에서 인증서 발급</li>
<li>발급하고 Rout53에 레코드 등록</li>
<li>Cloudfront 생성</li>
<li>Cloudfront 생성할 때 CNAME과 SSL 설정 필수</li>
<li>Rout53에서 레코드 생성</li>
<li>A레코드 선택 및 별칭 선택</li>
<li>cloudfront 리소스 별칭으로 찾아 선택 및 생성</li>
</ol>
<blockquote>
<p>AWS 강의실 - 인증서 없이 HTTP 웹사이트를 HTTPS로 바꾸는 2가지 방법
<a href="https://youtu.be/WS2n8mkrFaY?si=-abefK4zcoraHcge">https://youtu.be/WS2n8mkrFaY?si=-abefK4zcoraHcge</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[BFS]]></title>
            <link>https://velog.io/@yoo-seunghyeon/BFS</link>
            <guid>https://velog.io/@yoo-seunghyeon/BFS</guid>
            <pubDate>Tue, 15 Apr 2025 01:15:43 GMT</pubDate>
            <description><![CDATA[<h1 id="bfs-breadth-first-search">BFS (Breadth First Search)</h1>
<p>루트 노드의 자식 노드들을 먼저 모두 차례로 방문한 후, 방문했던 자식 노드들을 기준으로 하여 다시 해당 노드의 자식 노드들을 차례로 방문하는 방식</p>
<p>인접한 노드들에 대해 탐색을 한 후, 차례로 다시 너비우선탐색을 진행해야 하므로, 큐가 적합함</p>
<p><em>간단한 트리에서의 BFS</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/860f1484-5eba-4609-aeb6-315ed3484acc/image.png" alt=""></p>
<pre><code class="language-python">from collections import deque

def bfs_tree(tree, root_node):
    queue = deque([root_node])
    result = []

    while queue:
        node = queue.popleft()
        result.append(node)

        if node not in tree: continue

        for child in tree[node]:
            queue.append(child)

    return result

tree = {
    &#39;A&#39;: [&#39;B&#39;, &#39;C&#39;, &#39;D&#39;],
    &#39;B&#39;: [&#39;E&#39;, &#39;F&#39;],
    &#39;D&#39;: [&#39;G&#39;, &#39;H&#39;, &#39;I&#39;]
}

root_node = &#39;A&#39;

result = bfs_tree(tree, root_node)

print(&#39; &#39;.join(result))</code></pre>
<h1 id="bfs---그래프">BFS - 그래프</h1>
<p>Tree BFS와 전체적으로 동일하나 방문 여부를 확인해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/434aa3f9-d758-4e18-b242-7dd36975f057/image.png" alt=""></p>
<p><em>그래프 BFS 코드</em></p>
<pre><code class="language-python">from collections import deque

def bfs(start):
    nodes = list(graph.keys())
    visited = [False] * len(nodes)
    queue = deque([start])
    result = []

    start_index = nodes.index(start)
    visited[start_index] = True

    while queue:
        vertex = queue.popleft()
        result.append(vertex)

        for neighbor in graph[vertex]:
            neighbor_index = nodes.index(neighbor)
            if not visited[neighbor_index]:
                queue.append(neighbor)
                visited[neighbor_index] = True

    return result

graph = {
    &#39;A&#39;: [&#39;B&#39;, &#39;C&#39;],
    &#39;B&#39;: [&#39;A&#39;, &#39;D&#39;, &#39;E&#39;],
    &#39;C&#39;: [&#39;A&#39;],
    &#39;D&#39;: [&#39;B&#39;, &#39;F&#39;],
    &#39;E&#39;: [&#39;B&#39;, &#39;F&#39;],
    &#39;F&#39;: [&#39;D&#39;, &#39;E&#39;, &#39;G&#39;],
    &#39;G&#39;: [&#39;F&#39;],
}
nodes = list(graph.keys())
visited = [False] * len(nodes)
result = []
for node in nodes:
    if not visited[nodes.index(node)]:
        result.extend(bfs(node))
print(&quot;그래프 탐색 경로:&quot;, result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[DFS]]></title>
            <link>https://velog.io/@yoo-seunghyeon/DFS</link>
            <guid>https://velog.io/@yoo-seunghyeon/DFS</guid>
            <pubDate>Tue, 15 Apr 2025 00:30:55 GMT</pubDate>
            <description><![CDATA[<h1 id="비선형-자료구조">비선형 자료구조</h1>
<p>비선형구조(= 트리, 그래프)의 각 노드(정점)를 중복되지 않게 &quot;전부&quot; 방문하는 것을 의미함.</p>
<p>선형구조와 다르게 선후 연결 관계를 알 수 없다.
=&gt; 특별한 방법이 필요하다</p>
<p>두 가지 방법</p>
<ol>
<li>깊이 우선 탐색 (Depth First Search, DFS)</li>
<li>너비 우선 탐색 (Breadth First Search, BFS)</li>
</ol>
<h1 id="dfs-depth-first-search">DFS (Depth First Search)</h1>
<ul>
<li><p>루트 노드에서 출발하여 한 방향으로 갈 수 있는 곳까지 <strong>&quot;깊이 탐색&quot;</strong>을 하고, 더 이상 갈 곳이 없게 되면, 가장 마지막에 만났던 갈림길 간선이 있는 노드로 되돌아와서 다른 방향의 노드로 탐색을 계속 반복하여 결국 모든 노드를 방문하는 순회 방법</p>
</li>
<li><p>가장 마지막에 만났던 갈림길의 노드로 되돌아 가서 다시 깊이 우석 탐색을 반복 =&gt; 재귀 or 스택 으로 구현 가능</p>
</li>
</ul>
<p><em>간단한 트리 DFS 코드</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5a0b425b-a974-4ce6-8c67-2ddcd69b1cd8/image.png" alt=""></p>
<pre><code class="language-python">tree = {&#39;A&#39;: [&#39;B&#39;, &#39;C&#39;, &#39;D&#39;],
        &#39;B&#39;: [&#39;E&#39;, &#39;F&#39;],
        &#39;D&#39;: [&#39;G&#39;, &#39;H&#39;, &#39;I&#39;]}

def dfs(tree, node):
    print(node) # 방문한 노드 출력

    if node not in tree: # 자식 노드가 없으면 종료
        return

    for child in tree[node]: # 자식 노드들을 순서대로 다시 탐색
        dfs(tree, child)

dfs(tree, &#39;A&#39;)</code></pre>
<h1 id="dfs---그래프">DFS - 그래프</h1>
<p>시작 정점에서 출발하여 한 방향으로 갈 수 있는 곳까지 &quot;깊이 탐색&quot;을 진행하고, 더 이상 갈 곳이 없다면 가장 마지막 갈림길 간선이 있는 정점에서 다시 &quot;깊이 탐색&quot;을 진행하는 흐름은 같음.</p>
<p>하지만 자식 노드라는 관계가 없기 때문에 <strong>&quot;방문여부&quot;</strong>를 체크한다.</p>
<p><em>간단한 그래프 DFS 코드</em></p>
<pre><code class="language-python">N = 5  # 정점의 수
# 인접 행렬로 그래프 표현
adj_matrix = [
    [0, 1, 1, 0, 0]
    [1, 0, 0, 1, 1],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 0, 1],
    [0, 1, 1, 1, 0]
]
visited = [False] * N

def dfs(current, adj_matix, visited):
    visited[current] = True

    for i in range(len(adj_matrix)):
        # 현재 정점과 연결되어 있고, 아직 방문하지 않은 정점이라면
        if adj_matrix[current][i] and not visited[i]:
            dfs(i, adj_matrix, visited)

# 연결되어 있지 않은 정점을 위해 모든 정점에 대해 dfs 실행
for i in range(N):
    if visited[i]:
        continue

    dfs(0, adj_matrix, visited)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[싸밥(=밥프)]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%EC%8B%B8%EB%B0%A5</link>
            <guid>https://velog.io/@yoo-seunghyeon/%EC%8B%B8%EB%B0%A5</guid>
            <pubDate>Fri, 11 Apr 2025 16:48:49 GMT</pubDate>
            <description><![CDATA[<h1 id="version-010">Version 0.1.0</h1>
<p>로그 데이터를 수정하고, DB의 구성과 Front에 자신의 평가 지표가 보이는 등 두 번째 배포를 진행했습니다.</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/f043a2c3-351b-4596-b08c-0c5965549dd3/image.png" alt=""></p>
<p>이러한 형태로 진행이 되었습니다.</p>
<p>추가로 분석페이지를 설계하여 제작중에 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/35658b64-5828-4bc9-9457-205bae260a8d/image.png" alt=""></p>
<p>하지만 이번 배포의 가장 중요한 사항은 바로 서버에 모니터링 시스템을 도입한 것이라고 생각합니다.</p>
<h2 id="서버-모니터링">서버 모니터링</h2>
<p>서버를 운영하면서 사용자가 몰릴때 서버가 어느정도 부하가 가해지는지 궁금했고 이를 위해 <strong><em>NetData</em></strong>를 도입했습니다.</p>
<p> NetData는 특별한 설치 없이 모니터링 시스템을 웹 브라우저를 통해 볼 수 있는 오픈소스 프로그램입니다. </p>
<pre><code class="language-bash">bash &lt;(curl -SsL https://my-netdata.io/kickstart.sh)</code></pre>
<pre><code class="language-bash">sudo ufw allow 19999/tcp</code></pre>
<p>그리고 외부에서도 모니터링이 가능하게 nginx도 수정했습니다. </p>
<pre><code class="language-nginx">location /netdata/ {
    proxy_pass http://localhost:19999/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_pass_request_headers on;
    proxy_set_header Connection &quot;keep-alive&quot;;
}</code></pre>
<p>이를 세팅하고 모니터링을 진행한 결과 <strong>큰 문제</strong>를 발견했습니다.
<strong>아무 접속자가 없을 때 CPU 사용률이 66% 이상</strong>인 것을 확인했습니다. 이를 이상하게 생각하여 조사한 결과 uvicorn을 실행할 때 사용한 <strong>--reload 옵션</strong>이 문제임을 알았습니다. </p>
<p>그래서 이를 해결하면서 추가로 서버를 종료했다가 실행해도 바로 동작하도록 세팅을 진행했습니다.
이를 적용하여 fastapi를 백그라운드에서 실행하고 cpu 사용률을 측정한 결과는 다음과 같았습니다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/513711dc-2cd7-4753-b7f5-872f83390120/image.png" alt=""></p>
<blockquote>
<p><del>앞서 아무런 사용자가 없어도 66</del>80%까지 오가던 cpu 사용량이 10% 미만으로 확 줄어버렸습니다.(2025.04.11)~~
다시 확인한 결과 그대로 60% 대를 유지하고 있었습니다.
그 원인을 파악중에 있습니다. (2025.04.12)</p>
</blockquote>
<p>사용한 스크립트는 다음과 같습니다.</p>
<pre><code class="language-bash">sudo nano /etc/systemd/system/fastapi.service</code></pre>
<p>해당 경로에 파일을 생성하고, 다음과 같이 스크립트를 작성했습니다.</p>
<pre><code class="language-script">[Unit]
Description=FastAPI Server (Uvicorn)
After=network.target

[Service]
User=ssabab
WorkingDirectory=/home/ssabab/바탕화면/projects/ssabab/back-end
ExecStart=/home/ssabab/바탕화면/projects/ssabab/back-end/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --proxy-headers --forwarded-allow-ips=&quot;*&quot;

Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target</code></pre>
<p>이후 스크립트를 등록하기 위해 아래의 명령어를 실행하면 모든 적용이 끝납니다.</p>
<pre><code class="language-bash">sudo systemctl daemon-reload
sudo systemctl enable fastapi
sudo systemctl start fastapi</code></pre>
<p>덕분에 저희는 지속적인 모니터링이 가능했고, 이를 통해 수집되는 log 또한 추후 활용할 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[밥프]]></title>
            <link>https://velog.io/@yoo-seunghyeon/babp</link>
            <guid>https://velog.io/@yoo-seunghyeon/babp</guid>
            <pubDate>Thu, 03 Apr 2025 05:13:41 GMT</pubDate>
            <description><![CDATA[<h1 id="ssafy-사람들과-진행한-첫-프로젝트---babp">SSAFY 사람들과 진행한 첫 프로젝트 - Babp</h1>
<blockquote>
<p>URL : <a href="https://ssabab.com">https://ssabab.com</a>
GitHub : <a href="https://github.com/haeri-ne">https://github.com/haeri-ne</a></p>
</blockquote>
<h2 id="소개">소개</h2>
<p>싸피 대전 캠퍼스의 식단에 대한 선호도를 조사하는 앱</p>
<h2 id="구성">구성</h2>
<p><strong>App</strong> : <strong>Vue</strong>(Front) + <strong>FastAPI</strong>(Back) + <strong>Sqlite</strong>(DB)
<strong>Infra</strong> : <strong>Vercel</strong>(Front) + <strong>AWS EC2</strong>(Back, DB) + <strong>S3</strong>(DB Backup)</p>
<h2 id="팀원">팀원</h2>
<p>김*리(Front), 황*혁(Back), 유승현(Infra)</p>
<h2 id="sds-요약">SDS (요약)</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/e0a3f725-b205-4e61-bcbf-1273ea6dd3da/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/33b536bf-c125-4a11-bb38-beed19296eda/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/61f8dbeb-8716-4c72-99a3-fd21290c3122/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/2022cd02-9742-4228-bc8b-74e02baf7d42/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/323e4d9e-3b8e-4127-bb43-0e8251cdea50/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5fb84369-7184-4916-997e-cba7ce847b20/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/c6750106-509b-4918-a46c-3539afd39aab/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/3e83a55a-3d46-4f3d-b042-babab3b1173c/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/960736b4-c0bc-4df5-8ff3-8cf86ba9b207/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/a2e20803-1385-484a-8969-6bc55505186b/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/179b8c6f-69cf-4c07-b6ce-6ecd6bff6feb/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/ab5df517-ac82-4220-b7a3-131f52e48484/image.png" alt=""></p>
<h2 id="문제-발생">문제 발생</h2>
<ol>
<li>Front <code>.env</code>파일의 중요성
배포할 때 모든 url을 전부 서버 도메인으로 바꾸면서 굉장히 후회했습니다. 이를 계기로 <code>.env</code>를 도입했고, Vite의 경우 환경변수 앞에 꼭 <code>VITE_</code> 가 붙어야 한다는 사실을 알았습니다.</li>
<li>Nginx와 SSL
기존의 계획은 EC2가 아니라, 제 개인 PC를 24시간 돌릴 예정이었습니다. 하지만 Nginx 세팅이 너무 힘들어서 2일간 시도하고, 일단 EC2로 배포를 하자고 결정했습니다.</li>
<li>코딩 전 설계 부족
SDS를 작성하면서 설계를 충분히 했다 생각했으나, 프로그램을 만들고 보니 생각보다 구조가 효율적이지 못했습니다.
예를 들어 AdminView.vue 의 경우 local에서는 잘 돌아갔으나 배포를 해서는 해당 url을 따로 사용하는 것이 되지 않았습니다. 이를 늦게 알고 admin 페이지를 따로 만들었습니다.</li>
<li>로그의 어려움
처음으로 로그를 도입했는데, 한 사람이 평가 하나만 해도 로그가 약 20개씩 쌓였습니다. 또한 이걸 브라우저에 저장하고 있다가 제출하기 버튼을 누르거나 페이지를 옮길 때 제출되게 했더니 제출하기 버튼을 눌렀을 때 제출이 너무 느린 문제가 있었습니다. 이런 설계 미스가 너무 아쉬워웠습니다.</li>
</ol>
<h2 id="피드백">피드백</h2>
<p>감사하게도 많은 분들이 피드백을 주셨습니다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/42a82859-4cb5-4fc0-8dac-8a1e1413c917/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/25622178-0254-449f-b863-5fa36cb35f5f/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/aa8dc59a-7064-41c2-ad19-e43a00f6a7c1/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/79ec315e-f94b-428e-9745-1e45da97031c/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/1ccd4734-da3b-458b-87a8-d78bac731bbc/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/d20fe9cb-3ffc-43ac-95fd-fb6b2852af36/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/79999413-c183-473d-b8ae-03e4bdffc09b/image.png" alt="">
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/d9fa7c42-f6d2-44df-8759-71e46219ab42/image.png" alt=""></p>
<h2 id="성과">성과</h2>
<p>2025.04.03 11시 쯤 배포를 했고 14시까지 수집된 데이터가 굉장히 만족스러웠습니다.</p>
<blockquote>
<p>back_logs 7443개
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/ea28e641-bd17-4378-b182-47d3e0620cca/image.png" alt="">
front_logs 6414개
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/d4581934-9f8d-41db-8775-098dfe49da4e/image.png" alt="">
score 216개
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/d62eb5ea-3e9d-4071-816e-39123256f55a/image.png" alt=""></p>
</blockquote>
<h2 id="앞으로-진행할-과정">앞으로 진행할 과정</h2>
<ol>
<li>피드백 반영</li>
<li>로그 전송 수정</li>
<li>서버 추가 구매 또는 개인 pc로 서버 전환</li>
<li>DADE를 도입하여 메뉴 추천 기능</li>
<li>한 줄 맛평가 기능 추가</li>
<li>평가 요약 기능 추가</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리와 그래프]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%ED%8A%B8%EB%A6%AC%EC%99%80-%EA%B7%B8%EB%9E%98%ED%94%84</link>
            <guid>https://velog.io/@yoo-seunghyeon/%ED%8A%B8%EB%A6%AC%EC%99%80-%EA%B7%B8%EB%9E%98%ED%94%84</guid>
            <pubDate>Wed, 19 Mar 2025 15:16:16 GMT</pubDate>
            <description><![CDATA[<h1 id="트리tree">트리(Tree)</h1>
<ul>
<li>비선형 구조</li>
<li>원소들 간에 1:N 관계를 가지는 자료구조</li>
<li>원소들 간에 <strong>&quot;계층관계&quot;</strong>를 가지는 계층형 자료구조</li>
<li>상위 원소에서 하위 원소로 내려가면서 확장되는 트리(나무)모양의 구조</li>
</ul>
<h2 id="트리의-정의">트리의 정의</h2>
<ul>
<li>한 개 이상의 노드로 이루어진 유한 집합</li>
<li>노드 중 최상위 노드를 <strong>루트</strong>(root)라 한다.</li>
<li>나머지 노드들은 n(&gt;= 0)개의 분리 집합 T1, T2, ..., Tn으로 분리될 수 있다.</li>
<li>분리 집합 각각은 하나의 <strong>트리</strong>가 되며(재귀적 정의) 루트의 <strong>부 트리</strong>(subtree)라 한다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/10f5afa6-507f-44b7-bfa8-794cfa4efdce/image.png" alt=""></li>
</ul>
<h2 id="트리-용어">트리 용어</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/116426b8-9155-4825-898a-0ffe0f3ce109/image.png" alt=""></p>
<ol>
<li><p>노드 (node)</p>
<ul>
<li>티리의 원소를 의미함</li>
<li>A, B, C, ...</li>
</ul>
</li>
<li><p>간선 (edge)</p>
<ul>
<li>노드와 노드를 연결하는 선</li>
<li>부모 노드와 자식 노드를 연결</li>
</ul>
</li>
<li><p>루트 노드 (root node)</p>
<ul>
<li>트리의 시작 노드인 최상위 노드</li>
<li>트리 T의 루트 노드 = A</li>
</ul>
</li>
<li><p>형제 노드 (sibling node)</p>
<ul>
<li>같은 부모 노드의 자식 노드들</li>
<li>B, C, D는 형제 노드</li>
</ul>
</li>
<li><p>조상 노드 (ancestor node)</p>
<ul>
<li>간선을 따라 루트 노드까지 이르는 경로에 있는 모든 노드들<ul>
<li>K의 조상 노드: F, B, A</li>
</ul>
</li>
</ul>
</li>
<li><p>서브 트리 (subtree)</p>
<ul>
<li>부모 노드와 연결된 간선을 끊었을 때 생성되는 트리</li>
</ul>
</li>
<li><p>자손 노드 (descendant node)</p>
<ul>
<li>서브 트리에 있는 하위 레벨의 노드들</li>
<li>B의 자손 노드: E, F, K</li>
</ul>
</li>
<li><p>차수 (degree)</p>
<ul>
<li>노드의 차수<ul>
<li>노드에 연결된 자식 노드 수</li>
<li>B의 차수 = 2, C의 차수 = 1</li>
</ul>
</li>
<li>트리의 차수<ul>
<li>트리에 있는 노드의 차수 중에서 가장 큰 값</li>
<li>트리 T의 차수 = 3</li>
</ul>
</li>
<li>단말 노드 (leaf node)<ul>
<li>차수가 0인 노드. 즉, 자식 노드가 없는 노드
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/06e4c0d9-f62f-4102-a655-8d0693514aa4/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="9">
<li><p>레벨</p>
<ul>
<li>루트에서 노드까지의 거리</li>
<li>로트 노드의 레벨은 0, 자식 노드의 레벨은 부모 레벨 + 1</li>
</ul>
</li>
<li><p>높이</p>
<ul>
<li>루트 노드엠서 가장 먼 리프 노트까지의 간선 수</li>
<li>트리의 높이는 최대 레벨 (트리 T의 높이 = 3)</li>
</ul>
</li>
</ol>
<hr>
<h1 id="이진-트리binary-tree">이진 트리(Binary Tree)</h1>
<ul>
<li>차수가 2인 트리</li>
<li>각 노드가 자식 노드를 최대 2개 까지 가질 수 있는 트리 (왼쪽, 오른쪽 자식 노드)</li>
</ul>
<p>ex)
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/c48f7f4c-880d-48ed-8b48-7e345697a8dd/image.png" alt=""></p>
<h2 id="이진-트리-특성">이진 트리 특성</h2>
<ul>
<li>레벨 i에서의 노드의 최대 개수는 2^i개</li>
<li>높이가 h인 이진 트리가 가질 수 있는 노드의 최소 개수는 (h+1)개가 되며, 최대 개수는 (2^{h+1}-1)개가 된다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/168b8d50-aa75-44a0-86ac-63bbc6fdc02a/image.png" alt=""></li>
</ul>
<h2 id="포화-이진-트리perfect-binary-tree">포화 이진 트리(Perfect Binary Tree)</h2>
<ul>
<li>모든 레벨에 노드가 포화 상태로 차 있는 이진 트리</li>
<li>높이가 h일 때, 최대 노드 개수인 (2^{h+1}-1)의 노드를 가진 이진 트리<ul>
<li>높이가 3일 때 2^{3+1}-1 = 15개의 노드</li>
</ul>
</li>
<li>루트를 1번으로 하여 2^{h+1}-1까지 정해진 위치에 대한 노드 번호를 가짐
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/3947677b-c760-43a2-b170-407f005325bf/image.png" alt="">
이렇게 꽉 찬 이진 트리를 의미함</li>
</ul>
<h2 id="완전-이진-트리complete-binary-tree">완전 이진 트리(Complete Binary Tree)</h2>
<ul>
<li>높이가 h이고 노드 수가 n일 때 (단, h+1 &lt;= n &lt; 2^{h+1}-1), 포화 이진 트리의 노드 번호 1번부터 n번까지 빈 자리가 없는 이진 트리
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/eed144a7-c074-4ba4-a030-a75a4c46275f/image.png" alt="">
이렇게 왼쪽부터 오른쪽으로 하나씩 채워져 나가는 트리</li>
</ul>
<h2 id="편향-이진-트리skewed-binary-tree">편향 이진 트리(Skewed Binary Tree)</h2>
<ul>
<li>높이 h에 대한 최소 개수의 노드를 가지면서 한쪽 방향의 자식 노드 만을 가진 이진 트리<ul>
<li>왼쪽 편향 이진 트리
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5760fe3e-bf4d-4137-813f-4b3a965843b8/image.png" alt=""></li>
<li>오른쪽 편향 이진 트리
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/7f2f450a-c68d-4296-a496-bd69c20a0403/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="리스트를-이용한-이진-트리">리스트를 이용한 이진 트리</h2>
<h4 id="파이썬에서는-리스트를-이용하여-이진-트리를-표현할-수-있다">파이썬에서는 리스트를 이용하여 이진 트리를 표현할 수 있다.</h4>
<p>우선 이진 트리를 숫자로 표현하면 다음과 같다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/1300f55a-1bad-4ede-862b-6079d70280f1/image.png" alt=""></p>
<p>여기서 규칙을 찾을 수 있다.
바로 왼쪽 자식 노드는 (부모 노드 x 2), 오른쪽 자식 노드는 (부모 노드 x 2 + 1) 이다.
이를 활용하여 노드를 표현하면 다음과 같다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/1c5870b1-7d5f-49f7-834b-f21a21e96d4e/image.png" alt=""></p>
<p>이렇게 인덱스를 활용하여 노드를 표현하고 이를 활용해 해당 노드의 부모 노드와 자식 노드들을 찾을 수 있다!</p>
<p>ex) 이를 활용한 이진 트리 구현</p>
<pre><code class="language-python">class BinaryTree:
    def __init__(self):
        self.tree = [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;, &#39;E&#39;, &#39;F&#39;, &#39;G&#39;, &#39;H&#39;, &#39;I&#39;, &#39;J&#39;, &#39;K&#39;, &#39;L&#39;]

    def insert(self, value):
        self.tree.append(value)

    def get_node(self, index):
        if index &lt; len(self.tree):
            return self.tree[index]
        return None

    def set_node(self, index, value):
        while len(self,tree) &lt; index:
            self.tree.append(None)
        self.tree[index] = value

    def get_left_child(self, index):
        left_index = 2 * index
        if left_index &lt; len(self.tree):
            return self.tree[left_index]
        return None

    def get_right_child(self, index):
        right_index = 2 * index + 1
        if right_index &lt; len(self.tree):
            return self.tree[right_index]
        return None

    def get_parent(self, index):
        if index = 0:
            return None
        parent_index = index // 2
        return self.tree[parent_index]</code></pre>
<p>리스트를 이용한 이진 트리 표현의 단점</p>
<ul>
<li>편향 이진 트리의 경우에 사용하지 않는 리스트 원소에 대한 메모리 낭비 발생</li>
<li>트리 중간에 새로운 노드를 삽입하거나 기존의 노드를 삭제할 경우 리스트의 크기 변경이 어려워 비효율적</li>
</ul>
<h2 id="연결-리스트를-이용한-이진-트리-표현">연결 리스트를 이용한 이진 트리 표현</h2>
<ul>
<li>리스트를 이용한 이진 트리의 표현의 단점을 보완하기 위해 연결 리스트를 이용하여 트리를 표현할 수 있다.</li>
<li>이진 트리의 모든 노드는 최대 2개의 자식 노드를 가지므로 일정한 구조의 이중 연결 리스트 노드를 사용하여 구현
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/8889ed17-e479-4c69-8735-8856b401b5a0/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/796550e1-2d1b-43cf-8dc2-666b6dd97641/image.png" alt=""></p>
<hr>
<h2 id="이진-트리-순회traversal">이진 트리 순회(traversal)</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/acf45d6a-f316-4f1b-8650-84249384342e/image.png" alt=""></p>
<ul>
<li>트리의 노드들을 체계적으로 방문하는 것.</li>
<li>3가지 기본적인 순회 방법<ol>
<li>전위 순회(Preorder Traversal) : VLR 순서로 순회</li>
<li>중위 순회(Inorder Traversal) : LVR 순서로 순회</li>
<li>후위 순회(Postorder Traversal) : LRV 순서로 순회</li>
</ol>
</li>
</ul>
<p>해당 구현들은 재귀의 순서만 바꾸면 쉽게 구현할 수 있다.</p>
<p>ex) 전위 순회 구현</p>
<pre><code class="language-python">class TreeNode:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.val = key

def preorder_traversal(root):
    if root:
        print(root.val)
        preorder_traversal(root.left)
        preorder_traversal(root.right)</code></pre>
<p>ex) 중위 순회 구현</p>
<pre><code class="language-python">class TreeNode:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.val = key

def inorder_traversal(root):
    if root:
        inorder_traversal(root.left)
        print(root.val)
        inorder_traversal(root.right)</code></pre>
<p>ex) 후위 순회 구현</p>
<pre><code class="language-python">class TreeNode:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.val = key

def postorder_traversal(root):
    if root:
        postorder_traversal(root.left)
        postorder_traversal(root.right)
        print(root.val)</code></pre>
<h2 id="수식-트리expression-tree">수식 트리(Expression Tree)</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/975ad566-8dbf-4214-a5cd-239e968e0c2e/image.png" alt=""></p>
<ul>
<li>수식을 표현하는 이진 트리</li>
<li>수식 이진 트리(Expression binary Tree)라고 부르기도 함</li>
<li>연산자는 루트 노드이거나 가지 노드</li>
<li>피연산자는 모두 잎 노드</li>
</ul>
<p>동일한 트리에 대해 세가지 순회 방법에 따른 계산 방법</p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/52a68b71-0a66-46ec-aa6d-cd56bdad4f31/image.png" alt=""></p>
<ul>
<li>전위 순회 : + * * / A B C D E</li>
<li>중위 순회 : A / B * C * D + E</li>
<li>후위 순회 : A B / C * D * E +</li>
</ul>
<hr>
<h1 id="그래프">그래프</h1>
<h2 id="그래프-유형과-표현">그래프 유형과 표현</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/6d84df5d-7246-4658-962f-d990d992568b/image.png" alt=""></p>
<ul>
<li><p>아이템(사물 또는 추상적 개념)들과 이들 사이의 연결 관계를 표현한다.</p>
</li>
<li><p>정점(Vertex): 그래프의 구성요소로 하나의 연결점</p>
</li>
<li><p>간선(Edge): 두 정점을 연결하는 선</p>
</li>
<li><p>차수(Degree): 정점에 연결된 간선의 수</p>
</li>
<li><p>그래프 = 정점(Vertex)들의 집합과 이들을 연결하는 간선(Edge)들의 집합으로 구성된 자료 구조</p>
<ul>
<li>V: 정점의 개수, E: 그래프에 포함된 간선의 개수</li>
<li>V 개의 정점을 가지는 그래프는 최대 V * (V - 1) / 2 간선이 개수</li>
</ul>
</li>
<li><p>선형 자료구조나 트리 자료구조로 표현하기 어려운 M:N 관계를 가지는 원소들을 표현하기에 용이하다.</p>
</li>
</ul>
<h2 id="그래프-유형">그래프 유형</h2>
<p><em>무방향 그래프(Undirected Graph)</em></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/f6747cdb-b747-4b3a-8605-9c95cadc21a0/image.png" alt=""></p>
<p><em>방향 그래프(Directed Graph)</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/8027eccc-2afd-4d40-b9d2-c21a330102d0/image.png" alt=""></p>
<p><em>가중치 그래프(Weighted Graph)</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/134be772-2b19-4739-95ce-682f69dc5c8f/image.png" alt=""></p>
<p><em>사이클 없는 방향 그래프(DAG, Directed Acyclic Graph)</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/12cd9885-8e0f-4b91-b455-a35227a62b99/image.png" alt=""></p>
<p><em>트리(Tree)</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/9bb8a571-1748-46ae-ac60-346176006bfa/image.png" alt=""></p>
<p><em>완전 그래프</em>
정점들에 대해 가능한 모든 간선들을 가진 그래프
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/53846173-d962-4d63-8f14-53f65ee0ff15/image.png" alt=""></p>
<p><em>부분 그래프</em>
원래 그래프에서 일부의 정점이나 간선을 제외한 그래프</p>
<h2 id="인접-정점">인접 정점</h2>
<p>인접(Adjacency)</p>
<ul>
<li>두 개의 정점에 간선이 존재 (연결됨)하면 서로 인접해 있다고 한다.</li>
<li>완전 그래프에 속한 임의의 두 정점들은 서로 인접해 있다.</li>
<li>두 정점 1과 2는 서로 인접해 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/0c7641c7-390e-4368-a314-98617f003b11/image.png" alt=""></p>
<h2 id="그래프-경로">그래프 경로</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/9afbd5cb-5909-43e4-a7db-75551e972c21/image.png" alt="">
경로 : 어떤 정점 A에서 시작하여 다른 정점 B로 끝나는 순회로 두 정점 사이를 잇는 간선들을 순서대로 나열할 것.</p>
<ul>
<li><p>같은 정점을 거치지 않은 간선들의 sequence</p>
</li>
<li><p>어떤 정점에서 다른 정점으로 가는 경로는 여러가지일 수 있다.</p>
</li>
<li><p>0 - 6의 경로 예시</p>
<ul>
<li>정점들: 0 - 2 - 4 - 6</li>
<li>간선들: (0, 2), (2, 4), (4, 6)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/53633e04-2762-4a8a-9e43-da2d244ba4af/image.png" alt="">
사이클 : 경로의 시작 정점과 끝 정점이 같음 = 시작한 정점에서 끝나는 경로</p>
<ul>
<li>1 - 3 - 5 - 1 </li>
</ul>
<h2 id="그래프를-표현하는-세가지-방법">그래프를 표현하는 세가지 방법</h2>
<ol>
<li>인접 행렬(Adjacent Matrix): V * V 크기의 2차원 배열을 이요해서 간선 정보를 저장</li>
<li>인접 리스트(Adjacent List): 각 정점마다 다른 정점으로 나가는 간선의 정보를 저장</li>
<li>간선 리스트(Edge List): 간선의 정보를 객체로 표현하여 리스트에 저장</li>
</ol>
<h3 id="1-인접-행렬">1. 인접 행렬</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/01e65df0-d72c-440b-bcf5-f3023ad38a45/image.png" alt=""></p>
<p>이렇게 무향 그래프, 유향 그래프인지에 따라 행렬의 형태가 달라진다.
무향 = 대칭 행렬
유향 = 비대칭 행렬</p>
<blockquote>
</blockquote>
<p>장점</p>
<ul>
<li>두 정점 사이의 간선이 있는지 확인하는 연산이 O(1)로 빠름</li>
<li>구현이 단순</li>
<li><strong>정적 그래프</strong>에 적합.(정적 그래프: 정점과 간선의 수가 변하지 않음)</li>
</ul>
<blockquote>
<p>단점</p>
</blockquote>
<ul>
<li>많은 메모리를 차지함 = O(V^2)</li>
<li>간선의 수를 확인하거나 인접한 정점을 나열하는 연산이 느림</li>
</ul>
<blockquote>
<p>사용하기 좋은 상황</p>
</blockquote>
<ul>
<li>Dense Graph(밀집 그래프)에 적합</li>
<li>두 정점 사이에 간선이 있는지 빠르게 확인해야 하는 경우</li>
</ul>
<h3 id="2-인접-리스트">2. 인접 리스트</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/6fe1e639-d247-4fb7-8168-09fa5b76c39e/image.png" alt=""></p>
<p>이렇게 해당 정점과 연결된 정점을 표시. 무향이면 그 표시가 2배가 됨.</p>
<blockquote>
<p>장점</p>
</blockquote>
<ul>
<li>필요한 공간만 사용하므로 공간 복잡도 O(V + E)</li>
<li>인접한 정점을 나열하는 연산이 빠름</li>
</ul>
<blockquote>
<p>단점</p>
</blockquote>
<ul>
<li>두 정점 사이에 간선이 있는지 확인하는 연산이 느림 = O(V)</li>
<li>Linked List로 구현이 복잡</li>
</ul>
<blockquote>
<p>사용하기 좋은 상황</p>
</blockquote>
<ul>
<li>Sparse Graph(희소 그래프)에 적합</li>
<li>그래프가 동적으로 변하는 경우 (정점과 간선이 자주 추가/삭제 되는 경우)</li>
<li>인접한 정점을 자주 순회해야 하는 경우</li>
</ul>
<h3 id="3-간선-리스트">3. 간선 리스트</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/0aa48c7d-bceb-409c-b493-44d67de9e4da/image.png" alt=""></p>
<p>두 정점에 대한 간선 그 자체를 객체로 표현하여 리스트로 저장</p>
<blockquote>
<p>장점</p>
</blockquote>
<ul>
<li>필요한 간선만 저장하므로 공간 복잡도 O(E)</li>
<li>간선을 직접 다루는 연산에 효율적</li>
</ul>
<blockquote>
<p>단점</p>
</blockquote>
<ul>
<li>두 정점 사이에 간선이 있는 지 확인하는 연산이 느림 = O(E)</li>
<li>특정 정점에 인접한 정점을 찾는 연산이 느림 = O(E)</li>
</ul>
<blockquote>
<p>사용하기 좋은 상황</p>
</blockquote>
<ul>
<li>간선 중심 연산을 자주 수행해야 하는 경우. ex) MST</li>
</ul>
<hr>
<h1 id="bst">BST</h1>
<h2 id="bst-binary-search-tree">BST (Binary Search Tree)</h2>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/a4ccef2c-dbc0-43b9-9f4a-6f0538b7014b/image.png" alt=""></p>
<p><strong>정의</strong></p>
<ul>
<li>데이터의 저장, 검색, 삽입, 삭제를 효율적으로 처리하기 위한 자료구조</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li>각 노드가 최대 2개의 자식을 가짐</li>
<li>데이터를 정렬된 형태로 저장하여 탐색/삽입/삭제를 효율적으로 수행</li>
</ul>
<p><strong>구조</strong></p>
<ul>
<li><p>이진 트리의 특성을 가지지만 추가로 다음과 같은 속성을 가짐(순서 속성)</p>
<ul>
<li>왼쪽 자식 노드의 키 값이 부모 노드의 키 값보다 작다.</li>
<li>오른쪽 자식 노드의 키 값이 부모 노드의 키 값보다 크다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>장점</p>
</blockquote>
<ul>
<li>배열이나 Linked List와 달라, 삽입/삭제 후에도 데이터가 정렬된 상태를 유지</li>
<li>데이터가 균형있게 분포되어 있을 때 평균적으로 탐색/삽입/삭제 연산의 시간 복잡도가 O(logN)</li>
<li>동적으로 크기를 조절할 수 있어, 크기가 고정된 배열에 비해 유연성이 높음</li>
</ul>
<blockquote>
<p>단점</p>
</blockquote>
<ul>
<li>트리가 한쪽으로 치우치면 (즉, 균형이 맞지 않으면) 최악의 경우, 시간 복잡도가 O(n)이 될 수 있음</li>
<li>각 노드의 두 개의 자식 포인터를 저장해야 하므로, 큰 데이터 집합의 경우 메모리 오버헤드가 발생할 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/f8e45012-13e6-4470-aeea-9b8c1d76b4c2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/7fa0040b-4fc5-4b9b-90fd-ef309169ff4e/image.png" alt=""></p>
<h2 id="bst-속성">BST 속성</h2>
<p><strong>높이</strong></p>
<ul>
<li>특정 노드에서 가장 깊은 리프 노드까지의 경로에 있는 간선의 개수</li>
<li>트리의 높이는 루트 노드의 높이와 동일</li>
<li>리프 노드의 높이는 0</li>
<li>균형잡힌 트리의 높이는 O(ogN), 불균형한 트리의 높이는 O(N)이다.</li>
</ul>
<p><strong>깊이</strong></p>
<ul>
<li>루트 노드에서 해당 노드까지의 경로에 있는 간선의 수</li>
<li>루트 노드의 높이는 0</li>
</ul>
<h2 id="bst-주요-연산">BST 주요 연산</h2>
<h3 id="탐색">탐색</h3>
<p>루트 노드부터 탐색을 시작하며, 현재 노드값과 찾고자 하는 키를 비교, 그 차에 따라 왼쪽 또는 오른쪽으로 이동. 
이를 반복하다 현재 노드값과 찾고자 하는 값이 같으면 탐색 종료</p>
<pre><code class="language-python">class Node:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None

class BST:
    def __init__(self):
        self.root = None

    def search(self, key):
        return self._search(self.root, key)

    def _search(self, node, key):
        if node is None or node.key == key:
            return node
        if key &lt; node.key:
            return self._search(node.left,key)
        return self._search(node.right, key)</code></pre>
<h3 id="삽입">삽입</h3>
<p>새로운 노드를 삽입해도 BST의 특징인 &quot;자식노드는 최대 2개&quot;를 유지하면서 삽입할 위치를 찾기위해 루트부터 적절한 위치까지 내려감.
트리의 순서 속성을 유지하기 위해서 새로운 노드는 리프 노드로 삽입</p>
<pre><code class="language-python">class BST:
    def insert(self, key):
        if self.root is None:
            self.root = Node(key)
        else:
            self._insert(self.root, key)

    def _insert(self, node, key):
        if key &lt; node.key:
            if node.left is None:
                node.left = Node(key)
            else:
                self._insert(node.left, key)

        else:
            if node.right is None:
                node.right = Node(key)
            else:
                self._insert(node.right, key)</code></pre>
<h3 id="삭제">삭제</h3>
<p>삭제하려는 노드의 위치와 자식 노드의 유무에 따라 세 가지 경우로 나눠서 처리</p>
<ol>
<li><p>삭제할 노드가 <strong>리프 노드</strong>인 경우 -&gt; 단순히 제거하면 됨
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/248d85e8-816e-4b3f-aa71-5a1e95f0af3b/image.png" alt=""></p>
</li>
<li><p>삭제할 노드가 <strong>한 개의 자식 노드</strong>를 가질 경우 -&gt; 삭제할 노드의 자식노드를 부모 노드에 연결
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/1ab91858-b860-43cb-92b2-f23a857e78fc/image.png" alt=""></p>
</li>
<li><p>삭제할 노드가 <strong>두 개의 자식 노드</strong>를 가질 경우 -&gt; 중위 후속자 또는 중위 전임자 찾기</p>
</li>
</ol>
<ul>
<li>중위 후속자 : 삭제할 노드의 오른쪽 서브 트리에서 가장 작은 값 (일반적으로 사용)</li>
<li>중위 전임자 : 삭제할 노드의 왼쪽 서브 틀에서 가장 큰 값</li>
<li>BST 구조를 유지하기 위해서 삭제할 노드와 가장 가까운 값을 찾는 것.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/02777279-7c6c-48ff-af25-46dea187c8fa/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5467898e-e163-4963-a29f-d30e22782023/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5e5919f2-a426-4d30-b2e4-4807a0fdccf7/image.png" alt=""></p>
<pre><code class="language-python">class BST:
    def delete(self, key):
        self.root = self._delete(self.root, key)

    def _delete(self, node, key):
        if node is None:
            return node

        if key &lt; node.key:
            node.left = self._delete(node.left, key)
        elif key &gt; node.key:
            node.right = self._delete(node.right, key)
        else:
            if node.left is None:
                return node.right
            elif node.right is None:
                return node.left

            temp = self._minValueNode(node.right)
            node.key = temp.key
            node.right = self._delete(node.right, temp.key)

        return node

    def _minValueNode(self, node):
        current = node
        while current.left is not None:
            current = current.left
        return current</code></pre>
<h2 id="bst의-균형과-불균형">BST의 균형과 불균형</h2>
<ul>
<li><p>BST의 구조는 삽입되는 데이터의 순서에 따라 결정되기 때문에 특정 패턴으로 삽입되는 경우 불균형이 발생</p>
</li>
<li><p>불균형한 BST의 문제점</p>
<ul>
<li>검색/삽입/삭제 연산의 시간복잡도가 O(N)이 됨. (균형 잡힌 BST의 경우 O(logN))</li>
<li>트리의 높이가 증가하게 되면 많은 메로리 공간이 필요</li>
<li>트리의 높이가 증가하게 되면서 깊있는 재귀호출로 인해서 스택 오버플로우가 발생할 수 있음</li>
</ul>
</li>
<li><p>불균형 문제를 해결할 수 있는 방법은?</p>
<ul>
<li>자가 균형 트리(Self-Balancing Tree): AVL Tree, Red-Black Tree</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스택과 큐]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</link>
            <guid>https://velog.io/@yoo-seunghyeon/%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</guid>
            <pubDate>Thu, 13 Mar 2025 14:08:07 GMT</pubDate>
            <description><![CDATA[<h1 id="스택">스택</h1>
<ul>
<li><p>물건을 쌓아 올리듯 자료를 쌓아 올린 형태의 자료구조</p>
</li>
<li><p>스택에 저장된 자료는 &quot;선형 구조&quot;를 갖는다.</p>
<ul>
<li>선형 구조 : 데이터 요소들 사이에 순서가 존재</li>
<li>비선형 구조 : 데이터 요소가 순차적으로 나열되지 않음</li>
</ul>
</li>
<li><p>스택에서 자료를 삽입하거나 스택에서 자료를 꺼낼 수 있다.</p>
</li>
<li><p>LIFO (Last In First Out)
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/3663ff3e-2bc7-42ad-9f2b-969c904700d8/image.png" alt=""></p>
</li>
</ul>
<h3 id="스택의-주요-연산">스택의 주요 연산</h3>
<ul>
<li>Push : 삽입</li>
<li>Pop : 삭제</li>
<li>IsEmpty : 공백 확인</li>
<li>Peek : 최상단 자료 확인</li>
</ul>
<pre><code class="language-python">class Stack:
    def __init__(self, capacity = 10):
        self.capacity = capacity
        self.items = [None] * capacity
        self.top = -1

    def is_empty(self):
        return self.top == -1

    def is_full(self):
        return self.top == self.capacity - 1

    def get_size(self):
        return self.top + 1

    def peek(self):
        if self.is_empty():
            raise IndexError(&quot;Stack is empty&quot;)
        return self.items[self.top]

    def push(self, item):
        if self.is_full():
            raise IndexError(&quot;Stack is full&quot;)
        self.top += 1
        self.items[self.top] = item

    def pop(self):
        if self.is_empty():
            raise IndexError(&quot;Stack is empty&quot;)
        itme = self.items[self.top]
        self.items[self.top] = None
        self.top -= 1
        return item
</code></pre>
<p>파이썬에서는 list의 append()와 pop(), len()을 활용하여 쉽게 스택을 이용할 수 있다.</p>
<pre><code class="language-python">stack = []

# push
stack.append(&#39;a&#39;)
stack.append(&#39;b&#39;)
stack.append(&#39;c&#39;) # stack = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]

# pop
stack.pop() # c 출력, stack = [&#39;a&#39;, &#39;b&#39;]

# peek
stack[len(stack)] # b 출력, stack = [&#39;a&#39;, &#39;b&#39;]</code></pre>
<hr>
<h1 id="큐">큐</h1>
<ul>
<li>스택과 마찬가지로 삽입과 삭제의 위치가 제한적인 자료구조</li>
<li>FIFO (First In First Out)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/854411af-6eb1-45aa-82dc-cfbb41206634/image.png" alt=""></p>
<h3 id="큐의-주요-연산">큐의 주요 연산</h3>
<ul>
<li>EnQueue : 삽입</li>
<li>Dequeue : 삭제</li>
<li>IsEmpty : 공백인지 확인</li>
<li>Peek : 가장 앞쪽의 원소 확인</li>
</ul>
<pre><code class="language-python">class Queue:
    def __init__(self, capacity = 10):
        self.capacity = capacity
        self.items = [None] * capacity
        self.front = -1
        self.reer = -1

    def is_empty(self):
        return self.front == self.rear

       def is_full(self):
        return self.rear == self.capacity - 1

    def peek(self):
        if self.is_empty():
            raise IndexError(&quot;Queue is empty&quot;)
        return self.items[self.front + 1]

    def get_size(self):
        return self.rear - self.front

    def enqueue(self, item):
        if self.is_full():
            raise IndexError(&quot;Queue is full&quot;)
        self.rear += 1
        self.items[self.rear] = item

    def dequeue(self):
        if self.is_empty():
            raise IndexError(&quot;Queue is empty&quot;)
        self.front += 1
        item = self.items[self.front]
        self.items[self.front] = None
        return item</code></pre>
<p>여기서 문제점이 있음
<strong>잘못된 포화 상태 인식</strong>
삽입과 삭제를 반복하다 보면 리스트의 앞부분이 None임에도 불구하고 포화상태로 인식할 수 있다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/4f24d5c0-b6e3-4355-b956-d401552662e5/image.png" alt=""></p>
<p>이를 위해 항상 앞쪽을 채우도록 하려면 한번의 삭제마다 n번의 이동이 필요하다. -&gt; 매우 비효율적</p>
<p>이를 해결하기 위해 <strong>원형 큐</strong>를 사용할 수 있다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/5b92e25c-f1aa-4b33-925c-cd71dfdfba70/image.png" alt=""></p>
<pre><code class="language-python">class CircleQueue:
    def __init__(self, capacity = 10):
        self.capacity = capacity + 1
        self.items = [None] * self.capacity
        self.front = 0
        self.rear = 0

    def is_empty(self):
        return self.front == self.rear

    def is_full(self):
        return (self.rear + 1) % self.capacity == self.front

    def peek(self):
        if self.is_empty():
            raise IndexError(&quot;Queue is empty&quot;)
        return self.itmes[(self.front + 1) % self.capacity]

    def get_size(self):
        return (self.rear - self.front + self.capacity) % self.capacity

    def enqueue(self, item):
        if self.is_full():
            raise IndexError(&quot;Queue is full&quot;)
        self.rear = (self.rear + 1) % capacity
        self.items[self.rear] = item

    def dequeue(self):
        if self.is_empty():
            raise IndexError(&quot;Queue is empty&quot;)
        self.front = (self.front + 1) % self.capacity
        item = self.items[self.front]
        self.items[self.front] = None
        return item</code></pre>
<p>파이썬에서 deque라는 라이브러리로 쉽게 사용가능하다.</p>
<hr>
<h1 id="연결-리스트">연결 리스트</h1>
<p>기존의 리스트에서 중간에 특정 값을 삭제하면 뒤의 모든 값을 앞으로 이동시켜야 하는 문제가 있다. 이렇게 하면 한 번의 삭제에 n번의 연산이 필요한 겂이다. 이를 해결하기 위해 <strong>연결 리스트</strong>를 사용할 수 있다.</p>
<ul>
<li>자료의 논리적 순서와 메모리 상의 물리적 순서가 일치하지 않음</li>
<li>링크로 원소에 접근</li>
<li>자료구조의 크기를 동적으로 조정 가능 = 메모리의 효율적인 사용이 가능</li>
</ul>
<h3 id="연결-리스트의-기본-구조">연결 리스트의 기본 구조</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/743ac99f-e650-42ff-a438-eafa88eb1b11/image.png" alt=""></p>
<ul>
<li><p>노드</p>
<ul>
<li><p>연결 리스트에서 하나의 원소를 표현하는 구성 요소</p>
</li>
<li><p>구성요소</p>
<ul>
<li>데이터 필드 : 원소의 값 저장</li>
<li>링크 필드 : 다음 노드의 참조 값을 저장</li>
</ul>
</li>
</ul>
</li>
<li><p>헤드</p>
<ul>
<li>연결 리스트의 첫 노드에 대한 참조 값</li>
</ul>
</li>
</ul>
<hr>
<h1 id="단순-연결-리스트singly-linked-list">단순 연결 리스트(Singly Linked List)</h1>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/7006586b-1222-42e4-aec7-52b6eace4dd6/image.png" alt=""></p>
<ul>
<li>노드가 하나의 링크 필드에 의해 다음 노드와 연결되는 구조</li>
<li>헤드가 가장 앞의 노드를 가리키고, 링크 필드가 연속적으로 다음 노드를 가리킨다.</li>
<li>링크 필드가 Null인 노드가 가장 마지막 노드이다.</li>
</ul>
<pre><code class="language-python">class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class SinglyLinkedList:
    def __init__(self):
        self.head = None

    def is_empty(self):
        return self.head is None

    def insert(self, data, position):
        new_node = Node(data)
        if position == 0:
            new_node.next = self.head
            self.head = new_node
        else:
            current = self.head
            for _ in range(position - 1):
                if current is None:
                    print(&quot;범위를 벗어난 삽입입니다.&quot;)
                    return
                current = current.next
            new_node.next = current.next
            current.next = new_node

    def append(self, data):
        new_node = Node(data)
        if self.is_empty():
            self.head = new_node
        else:
            current = self.head
            while current.next:
                current = current.next
            current.next = new_node

    def delete(self, position):
        if self.is_empty():
            print(&quot;싱글 링크드 리스트가 비었습니다.&quot;)
            return

        if position == 0:
            deleted_data = self.head.data
            self.head = self.head.next
        else:
            current = self.head
            for _ in range(position - 1):
                if current is None or current.next is None:
                    print(&quot;범위를 벗어났습니다.&quot;)
                    return
                current = current.next
            deleted_node = current.next
            deleted_data = deleted_node.data
            current.next = current.next.next
        return deleted_data

    def search(self, data):
        current = self.head
        position = 0
        while current:
            if current.data == data:
                return position
            current = current.next
            position += 1
        return -1

    def __str__(self):
        result = []
        current = self.head
        while current:
            result.append(current.data)
            current = current.next
        return str(result)</code></pre>
<blockquote>
</blockquote>
<p>장점</p>
<ul>
<li>필요한 만큼만 메모리를 사용<blockquote>
</blockquote>
단점</li>
<li>특정 요소에 접근하려면 순차적으로 탐색 (O(n))</li>
<li>역방향 탐색 불가</li>
</ul>
<hr>
<h1 id="이중-연결-리스트doubly-linked-list">이중 연결 리스트(Doubly Linked List)</h1>
<p>역방향 탐색이 가능하도록 링크 필드를 두 개, 데이터 필드 한 개로 구성
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/3b48c601-07e6-46e6-af23-697d6d334cdd/image.png" alt=""></p>
<h3 id="삽입-연산">삽입 연산</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/3ddfd5ff-5b50-4042-889f-ab2b40cd1bf4/image.png" alt=""></p>
<h3 id="삭제-연산">삭제 연산</h3>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/507b694b-79c9-4f2c-9466-ba4832923b37/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 기본]]></title>
            <link>https://velog.io/@yoo-seunghyeon/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@yoo-seunghyeon/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Wed, 12 Mar 2025 07:09:07 GMT</pubDate>
            <description><![CDATA[<h4 id="알고리즘--문제를-해결하기-위한-절차나-방법">알고리즘 = 문제를 해결하기 위한 절차나 방법</h4>
<h3 id="무엇이-좋은-알고리즘-인가">무엇이 좋은 알고리즘 인가?</h3>
<ol>
<li>정확성</li>
<li>효율성</li>
<li>확장성</li>
<li>단순성</li>
</ol>
<p>가장 중요한건 <strong>&quot;정확성&quot;</strong></p>
<hr>
<p>알고리즘을 평가하는 지표 = <strong>복잡도</strong></p>
<ol>
<li>시간 복잡도 (= 수행 시간)</li>
<li>공간 복잡도 (= 메모리 사용량)</li>
</ol>
<hr>
<h1 id="시간-복잡도">시간 복잡도</h1>
<p>시간 복잡도는 세 가지 경우가 있다.</p>
<ol>
<li>최선의 경우 (Best Case)</li>
<li>평균적인 경우 (Average Case)S</li>
<li>최악의 경우 (Worst Case)
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/4ff304bc-a17d-42a1-a345-84555c5f818a/image.png" alt=""></li>
</ol>
<p>우리가 중요하게 생각하는건 <strong>&quot;최악의 경우&quot;</strong></p>
<hr>
<p>복잡도는 점근적 표기를 사용한다.
입력 크기 n에 대한 함수로 표기한다.
빅-오(O) 표기법은 함수 중 가장 큰 영향력을 주는 n에 대한 항만을 표시
<em>만약 O(3n + 2) 라면 O(n)으로 표기</em>
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/2e862c4a-bf71-477c-9db6-982b14c03dcc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/75e5a930-7c0f-4725-8635-0159069f7da9/image.png" alt=""></p>
<p>처음 알고리즘을 배울때는 <strong>정확도</strong>를 <strong>최우선</strong>으로 고려하고 그 후 <strong>빅-오</strong>를 통해 <strong>효율</strong>을 생각하자. 확장성과 단순성이 이후에 고려해도 늦지 않다.</p>
<hr>
<h1 id="완전-검색">완전 검색</h1>
<ul>
<li><p>정확도를 최우선으로 고려하는 알고리즘</p>
</li>
<li><p>문제의 해법으로 생각할 수 있는 모든 경우의 수를 나열해보고 확인하는 기법</p>
</li>
<li><p>Brute-foce 혹은 generate-and-test 기법이라고도 불린다.</p>
<ul>
<li>&quot;Just-do-it&quot;</li>
<li>force의 의미는 사람(지능)보다는 컴퓨터의 force를 의미한다.</li>
</ul>
</li>
<li><p>대부분의 문제에 적용 가능</p>
</li>
<li><p>모든 알고리즘의 시작!!! 매우 중요</p>
</li>
</ul>
<p><em>완전 검색은 모든 경우의 수를 생성하고 테스트하기 때문에 수행 속도는 느리지만, 해답을 찾아내지 못할 확률이 낮다.</em></p>
<blockquote>
</blockquote>
<p>검정 등에서 문제를 풀 때, <strong>우선 완전 검색으로 접근하여 해답을 도출한 후, 성능 개선을 위해 다른 알고리즘을 사용하고 해답을 확인</strong>하는 것이 바람직하다.</p>
<hr>
<h1 id="재귀-함수">재귀 함수</h1>
<ul>
<li>자기 자신을 호출하는 함수</li>
<li>함수 호출은 메모리 구조에서 스택을 사용한다.</li>
<li>무분별한 재귀 함수는 성능저하를 발생시킨다.</li>
</ul>
<h3 id="대표적인-예시-팩토리얼">대표적인 예시 (팩토리얼)</h3>
<p>n! = 1 x 2 x 3 x ... x (n-1) x n 이다.
이를 재귀 함수로 표현하면 다음과 같다.</p>
<pre><code class="language-python">def fact(n):
    if n &lt;= 1:
        return 1
    else:
        return n * fact(n-1)</code></pre>
<p>이를 그림을 통해 살펴보면 다음과 같다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/641d707b-1c7a-4c96-b0fc-61b237260c03/image.png" alt=""></p>
<p>fact(4)에서 fact(3), fact(3)에서 fact(2) 이렇게 자기 자신보다 값이 1 작은 함수를 계속해서 호출하다가 fact(1)이 되면 &#39;return 1&#39;이 되면서 다시 fact(2), fact(3)을 계산하고 최종적으로 시작했던 fact(4)를 return하면서 결과가 반환된다.</p>
<h3 id="추가-예시-피보나치-수열">추가 예시 (피보나치 수열)</h3>
<p>이전의 두 수 합을 다음 항으로 하는 수열을 피보나치 수열이라고 한다.
이를 재귀함수로 구현하면 다음과 같다.</p>
<pre><code class="language-python">def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)</code></pre>
<p>가장 시작이 되는 fibonacci(0)과 fibonacci(1)만 return 값을 정의해주면 이후의 값은 재귀적으로 만들어지게 됩니다.</p>
<p>이처럼 재귀함수는 반환이 시작되는 부분을 <strong>기본 부분</strong> 이라고 하고, 재귀를 쌓는 부분을 <strong>유도 부분</strong> 이라고 한다.</p>
<p>이를 잘 구분하고 정의하는 것이 재귀함수를 잘 사용하는 첫 걸음이다.</p>
<p>피보나치 수열의 기본 부분과 유도 부분은 다음과 같다.</p>
<pre><code class="language-python">def fibonacci(n):
    if n == 0: # 기본 부분
        return 0
    elif n == 1: # 기본 부분
        return 1
    else: # 유도 부분
        return fibonacci(n - 1) + fibonacci(n - 2)</code></pre>
<hr>
<h1 id="순열">순열</h1>
<p>서로 다른 n개에서 r개를 뽑아 <strong>순서대로</strong> 나열하는 것. -&gt; 순서가 중요. (표현법 : <strong>nPr</strong>)</p>
<p>다수의 알고리즘 문제들은 순서화된 요소들의 집합에서 최선의 방법을 찾는 것과 관련이 있다.
<img src="https://velog.velcdn.com/images/yoo-seunghyeon/post/ee664ab9-4d09-4255-af8b-5ea416e9f457/image.png" alt=""></p>
<p>이를 반복문을 통해 생성한다면 다음과 같다.
ex) {1, 2, 3}의 모든 순열</p>
<pre><code class="language-python">for i in range(1, 4):
    for j in range(1, 4):
        if j != i:
            for k in range(1, 4):
                if k != i and k != j:
                    print(i, j, k)</code></pre>
<p>이는 n이 커지면 반복문을 그만큼 중첩해야하고, 이를 하나의 함수로 표현하기도 어렵다. 그래서 보통 <strong>재귀 호출</strong>을 통해 순열을 생성한다.</p>
<p>ex) 재귀 호출을 활용한 {1, 2, 3}의 모든 순열</p>
<pre><code class="language-python">def perm(selected, remain):
    if not remain:
        print(selected)
    else:
        for i in range(len(remain)):
            select_i = remain[i]
            remain_list = remain[:i] + remain[i + 1:]
            perm(selected + [select_i], remain_list)

perm([], [1, 2, 3])</code></pre>
<p>재귀 함수가 동작하는 것을 순서대로 따라가면 다음과 같다.</p>
<blockquote>
</blockquote>
<p>perm([], [1, 2, 3]) 
-&gt; perm([1], [2, 3]) -&gt; perm([1, 2], [3]) -&gt; perm([1, 2, 3], [])에서 print(1, 2, 3) 실행 
-----&gt; perm([1, 3], [2]) -&gt; perm([1, 3, 2], [])에서 print(1, 3, 2) 실행 
-&gt; perm([2], [1, 3]) -&gt; perm([2, 1], [3]) -&gt; perm([2, 1, 3], [])에서 print(2, 1, 3) 
-----&gt; perm([2, 3], [1]) -&gt; perm([2, 3, 1], [])에서 print(2, 3, 1) 
-&gt; perm([3], [1, 2]) -&gt; perm([3, 1], [2]) -&gt; perm([3, 1, 2], [])에서 print(3, 1, 2) 
-----&gt; perm([3, 2], [1]) -&gt; perm([3, 2, 1], [])에서 print(3, 2, 1)</p>
<p>이런식으로 진행이 된다.</p>
<hr>
<h1 id="조합">조합</h1>
<p>서로 다른 n개의 원소 중 r개를 순서없이 골라낸 것. (표현법 : <strong>nCr</strong>)</p>
<p>ex) {1, 2, 3, 4}중 3개를 뽑는 조합을 반복문으로 표현하면?</p>
<pre><code class="language-python">for i in range(1, 5):
    for j in range(i + 1, 5):
        for k in range(j+1, 5):
            print(i, j, k)</code></pre>
<p>순열과 동일하게 r이 증가하면 그만큼 for문이 반복된다. 그래서 보통 재귀 함수를 통해 조합을 생성한다.</p>
<p>ex) 동일한 조합을 재귀 함수로 표현하면?</p>
<pre><code class="language-python">def comb(arr, r):
    result = []
    if r == 1:
        return [[i] for i in arr]

    for i in range(len(arr)):
        elem = arr[i]
        for rest in comb(arr[i + 1:], r - 1):
            result.append([elem] + rest)
    return result

print(comb([1, 2, 3, 4], 3))</code></pre>
<p>조합의 재귀 함수가 동작하는 것을 순서대로 따라가면 다음과 같다.</p>
<blockquote>
</blockquote>
<p>comb([1, 2, 3, 4], 3) -&gt; result = [], elem = 1, rest = comb([2, 3, 4], 2)
comb([2, 3, 4], 2) -&gt; result = [], elem = 2, rest = comb([3, 4], 1)
comb([3, 4], 1) -&gt; return [[3], [4]]
comb([2, 3, 4], 2) -&gt; result = [[2, 3], [2, 4]], elem = 3, rest = comb([4], 1)
comb([4], 1) -&gt; return [[4]]
comb([2, 3, 4], 2) -&gt; result = [[2, 3], [2, 4], [3, 4]]
comb([1, 2, 3, 4], 3) -&gt; result = [[2, 3], [2, 4], [3, 4]], elem = 2, rest = comb([3, 4], 2) -&gt; result = [[1, 2, 3], [1, 2, 4], [1, 3, 4]]
comb([3, 4], 2) -&gt; result = [], elem = 3, rest = comb([4], 1)
comb([4], 1) -&gt; return [[4]]
comb([3, 4], 2) -&gt; result = [[3, 4]]
comb([1, 2, 3, 4], 3) -&gt; result = [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]</p>
<hr>
<h1 id="중복-순열--중복-조합">중복 순열 &amp; 중복 조합</h1>
<pre><code class="language-python">def comb(arr, r):
    result = []
    if n == 1:
        return [[i] for i in arr]

    for i in range(len(arr)):
        elem = arr[i]

        for rest in comb(arr[i + 1:], r - 1): # 조합
        # for rest in comb(arr[:i] + arr[i + 1:] r - 1): # 순열
        # for rest in comb(arr, r - 1): # 중복 순열
        # for rest in comb(arr[i:], r - 1): # 중복 조합
            result.append([elem] + rest)

    return result</code></pre>
<hr>
<h1 id="부분-집합power-set">부분 집합(Power Set)</h1>
<p>ex) {1, 2, 3}집합의 모든 부분 집합
<em>반목문으로 구현</em></p>
<pre><code class="language-python">selected = [0] * 3
for i in range(2):
    selected[0] = i
    for j in range(2):
        selected[1] = j
        for m in range(2):
            selected[2] = ,
            subset = []
            for n in range(3):
                if selected[n] = 1:
                    subset.append(n + 1)
            print(subset)</code></pre>
<p><em>재귀 함수로 구현</em></p>
<pre><code class="language-python">def generate_subset(depth, included):
    if depth == len(input_list):
        cnt_subset = [input_list[i] for i in range(len(input_list)) if included[i]]
        subsets.append(cnt_subset)
        return

    included[depth] = False
    generate_subset(depth + 1, included)

    included[depth] = True
    generate_subset(depth + 1, included)

input_list = [1, 2, 3]
subsets = []
init_included = [False] * len(input_list)
generate_subset(0, init_included)
print(subsets)</code></pre>
<p><em>Binary Counting</em> 이진법을 활용한 방법</p>
<pre><code class="language-python">arr = [1, 2, 3]
n = len(arr)
subset_cnt = 2 ** n

subsets = []
for i in range(subset_cnt):
    subset = []
    for j in range(n):
        if i &amp; (1 &lt;&lt; j):
            subset.append(arr[j])
    subsets.append(subset)

print(subsets)</code></pre>
]]></description>
        </item>
    </channel>
</rss>