본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 4. · 19 Views
파이썬 자료 구조 완벽 가이드
파이썬에서 데이터를 효율적으로 다루기 위한 핵심 자료 구조들을 알아봅니다. 리스트부터 스택, 큐, 트리까지 실무에서 바로 활용할 수 있는 내용을 담았습니다.
목차
1. 리스트와 튜플
신입 개발자 김개발 씨가 첫 번째 과제를 받았습니다. 사용자 데이터를 저장하고 관리하는 기능을 구현하라는 것이었습니다.
김개발 씨는 고민에 빠졌습니다. 데이터를 어디에 어떻게 담아야 할까요?
리스트는 여러 데이터를 순서대로 담는 가변 컨테이너입니다. 마치 서랍장처럼 원하는 위치에 물건을 넣고 뺄 수 있습니다.
반면 튜플은 한 번 담으면 변경할 수 없는 불변 컨테이너입니다. 이 둘을 적절히 활용하면 데이터의 특성에 맞는 안전한 코드를 작성할 수 있습니다.
다음 코드를 살펴봅시다.
# 리스트: 순서가 있고 변경 가능한 자료형
fruits = ["사과", "바나나", "오렌지"]
fruits.append("포도") # 맨 뒤에 추가
fruits.insert(1, "딸기") # 특정 위치에 삽입
fruits.remove("바나나") # 특정 값 삭제
# 튜플: 순서가 있지만 변경 불가능한 자료형
coordinates = (37.5665, 126.9780) # 서울 좌표
# coordinates[0] = 35.0 # 오류 발생! 튜플은 수정 불가
# 리스트 컴프리헨션으로 간결하게 데이터 처리
numbers = [1, 2, 3, 4, 5]
squares = [n ** 2 for n in numbers] # [1, 4, 9, 16, 25]
김개발 씨는 입사 첫 주에 간단한 과제를 받았습니다. 회원 목록을 저장하고 새 회원을 추가하거나 탈퇴한 회원을 삭제하는 기능이었습니다.
어떤 자료형을 써야 할지 고민하던 중 선배 박시니어 씨가 다가왔습니다. "데이터가 자주 바뀌어야 하나요, 아니면 한 번 정해지면 그대로인가요?" 박시니어 씨의 질문에 김개발 씨는 잠시 생각했습니다.
회원은 계속 늘어나기도 하고 줄어들기도 하니 당연히 자주 바뀌는 데이터였습니다. 그렇다면 리스트가 정답입니다.
리스트는 마치 출석부와 같습니다. 새 학생이 전학 오면 이름을 추가하고, 전학 가면 이름을 지웁니다.
순서도 유지되어 있어서 몇 번째 학생인지도 알 수 있습니다. 반면 튜플은 주민등록번호처럼 절대 바뀌면 안 되는 데이터를 담을 때 사용합니다.
위도와 경도 좌표, 날짜 정보, 설정값처럼 변경되면 곤란한 데이터가 여기에 해당합니다. 리스트에는 다양한 기능이 있습니다.
append 메서드는 맨 뒤에 데이터를 추가합니다. insert는 원하는 위치에 끼워 넣습니다.
remove는 특정 값을 찾아 삭제하고, pop은 마지막 요소를 꺼내옵니다. 코드의 6번째 줄을 보면 리스트 컴프리헨션이 등장합니다.
이것은 반복문을 한 줄로 압축한 파이썬만의 문법입니다. 숫자 리스트의 각 요소를 제곱하는 작업을 단 한 줄로 해결했습니다.
실무에서 리스트는 정말 자주 사용됩니다. 데이터베이스에서 조회한 결과를 담거나, API 응답 데이터를 처리하거나, 임시로 데이터를 모아둘 때 모두 리스트가 제격입니다.
튜플은 함수에서 여러 값을 반환할 때도 유용합니다. 예를 들어 사용자의 이름과 나이를 동시에 반환하고 싶다면 return (name, age) 형태로 튜플을 사용할 수 있습니다.
주의할 점도 있습니다. 리스트 안에 리스트를 넣는 다차원 리스트를 복사할 때는 얕은 복사와 깊은 복사의 차이를 알아야 합니다.
단순히 list2 = list1로 복사하면 같은 메모리를 참조하게 되어 예상치 못한 버그가 발생할 수 있습니다. 김개발 씨는 박시니어 씨의 설명을 듣고 회원 목록에는 리스트를, 회원 등급 코드처럼 고정된 값에는 튜플을 사용하기로 했습니다.
적재적소에 맞는 자료형을 선택하는 것이 좋은 코드의 시작입니다.
실전 팁
💡 - 변경이 필요하면 리스트, 불변이어야 하면 튜플을 선택하세요
- 리스트 컴프리헨션을 활용하면 코드가 훨씬 간결해집니다
- 다차원 리스트 복사 시 copy.deepcopy()를 사용하세요
2. 딕셔너리와 세트
김개발 씨가 두 번째 과제를 받았습니다. 이번에는 상품 코드로 상품 정보를 빠르게 찾는 기능이었습니다.
리스트에 담아서 처음부터 끝까지 찾아보려니 상품이 10만 개라면 너무 느릴 것 같았습니다.
딕셔너리는 키와 값의 쌍으로 데이터를 저장하는 자료형입니다. 마치 사전에서 단어를 찾으면 뜻이 나오듯이, 키를 주면 해당 값을 즉시 찾아줍니다.
세트는 중복을 허용하지 않는 집합 자료형으로, 고유한 값들만 모아둘 때 사용합니다.
다음 코드를 살펴봅시다.
# 딕셔너리: 키로 값을 빠르게 조회
product_info = {
"A001": {"name": "노트북", "price": 1500000},
"A002": {"name": "마우스", "price": 35000},
"A003": {"name": "키보드", "price": 89000}
}
laptop = product_info["A001"] # O(1) 시간복잡도로 즉시 조회
product_info["A004"] = {"name": "모니터", "price": 450000} # 새 항목 추가
# 세트: 중복 없는 고유 값 집합
visitors_today = {"user1", "user2", "user3", "user1"} # 자동 중복 제거
visitors_yesterday = {"user2", "user4", "user5"}
new_visitors = visitors_today - visitors_yesterday # 차집합: 오늘 첫 방문자
박시니어 씨가 김개발 씨에게 물었습니다. "상품 코드 A001의 정보를 찾으려면 리스트에서 어떻게 해야 할까요?" 김개발 씨는 대답했습니다.
"처음부터 하나씩 확인해서 A001인지 비교해야 합니다." 바로 여기서 문제가 생깁니다. 상품이 10만 개라면 최악의 경우 10만 번을 비교해야 합니다.
이것을 O(n) 시간복잡도라고 합니다. 데이터가 늘어날수록 찾는 시간도 비례해서 늘어나는 것입니다.
딕셔너리는 이 문제를 해결합니다. 내부적으로 해시 테이블이라는 구조를 사용해서, 키를 주면 값의 위치를 바로 계산합니다.
마치 도서관에서 책 번호만 알면 정확한 서가 위치를 바로 알 수 있는 것과 같습니다. 코드의 7번째 줄을 보면 **product_info["A001"]**로 노트북 정보를 즉시 가져옵니다.
상품이 100만 개여도 찾는 시간은 동일합니다. 이것이 바로 O(1) 상수 시간복잡도의 위력입니다.
딕셔너리에 새 데이터를 추가하는 것도 간단합니다. 존재하지 않는 키에 값을 할당하면 자동으로 추가됩니다.
기존 키에 할당하면 값이 갱신됩니다. 세트는 또 다른 유용한 자료형입니다.
가장 큰 특징은 중복을 허용하지 않는다는 것입니다. 같은 값을 여러 번 넣어도 하나만 남습니다.
실무에서 세트는 중복 제거에 자주 사용됩니다. 예를 들어 오늘 접속한 사용자 ID 목록에서 중복을 제거하려면 세트로 변환하면 됩니다.
리스트의 중복 제거도 **list(set(my_list))**로 간단히 해결됩니다. 세트의 또 다른 강점은 집합 연산입니다.
두 세트의 교집합, 합집합, 차집합을 구할 수 있습니다. 코드에서 visitors_today - visitors_yesterday는 오늘 방문자 중 어제는 방문하지 않았던 신규 방문자를 찾아냅니다.
주의할 점이 있습니다. 딕셔너리의 키와 세트의 요소는 반드시 해시 가능한 값이어야 합니다.
리스트처럼 변경 가능한 자료형은 키나 세트 요소로 사용할 수 없습니다. 대신 튜플은 가능합니다.
김개발 씨는 상품 조회 기능에 딕셔너리를 사용하기로 했습니다. 박시니어 씨가 덧붙였습니다.
"자료구조를 잘 선택하면 성능이 천 배, 만 배 차이 날 수 있어요." 올바른 도구를 선택하는 것이 절반의 성공입니다.
실전 팁
💡 - 빠른 조회가 필요하면 딕셔너리를 사용하세요
- 중복 제거에는 세트가 가장 간편합니다
- 딕셔너리의 get() 메서드를 사용하면 키가 없을 때 기본값을 반환할 수 있습니다
3. 시리즈와 데이터프레임
김개발 씨가 데이터 분석 프로젝트에 투입되었습니다. 엑셀 파일에 담긴 수만 건의 판매 데이터를 분석해야 했습니다.
리스트와 딕셔너리만으로는 감당이 안 될 것 같았습니다. 박시니어 씨가 미소를 지으며 말했습니다.
"Pandas를 배울 때가 됐네요."
Pandas는 파이썬 데이터 분석의 핵심 라이브러리입니다. 시리즈는 인덱스가 있는 1차원 배열이고, 데이터프레임은 행과 열이 있는 2차원 테이블 구조입니다.
마치 엑셀 스프레드시트를 파이썬으로 다루는 것과 같습니다.
다음 코드를 살펴봅시다.
import pandas as pd
# 시리즈: 인덱스가 있는 1차원 배열
sales = pd.Series([150, 200, 180, 220],
index=["1월", "2월", "3월", "4월"])
print(sales["2월"]) # 200 출력
# 데이터프레임: 행과 열이 있는 2차원 테이블
df = pd.DataFrame({
"상품명": ["노트북", "마우스", "키보드"],
"가격": [1500000, 35000, 89000],
"판매량": [50, 200, 150]
})
df["매출"] = df["가격"] * df["판매량"] # 새 열 추가
top_sales = df[df["매출"] > 5000000] # 조건 필터링
데이터 분석 분야에서 일하려면 Pandas는 필수입니다. 김개발 씨도 이제 Pandas의 세계로 들어가게 되었습니다.
엑셀로 하던 작업을 코드로 자동화할 수 있다니, 기대가 되었습니다. 시리즈는 Pandas의 기본 단위입니다.
일반 리스트와 비슷하지만 각 요소에 이름표(인덱스)를 붙일 수 있습니다. 코드에서 **sales["2월"]**로 2월 판매량을 바로 조회하는 것을 보세요.
숫자 인덱스 대신 의미 있는 이름으로 데이터에 접근할 수 있습니다. 데이터프레임은 여러 시리즈가 모여 만든 표입니다.
행과 열로 구성된 2차원 테이블이라고 생각하면 됩니다. 엑셀 시트 하나가 데이터프레임 하나에 대응됩니다.
코드의 10번째 줄부터 데이터프레임을 만드는 과정을 보세요. 딕셔너리를 넘기면 키가 열 이름이 되고, 값 리스트가 해당 열의 데이터가 됩니다.
직관적이고 간편합니다. 데이터프레임의 강력함은 벡터 연산에 있습니다.
15번째 줄에서 **df["가격"] * df["판매량"]**으로 모든 행의 매출을 한 번에 계산합니다. 반복문 없이 전체 데이터를 처리하는 것입니다.
조건 필터링도 간편합니다. 16번째 줄에서 매출이 500만 원 이상인 상품만 골라냅니다.
SQL의 WHERE 절과 비슷한 기능을 한 줄로 수행합니다. 실무에서 데이터프레임은 다양하게 활용됩니다.
CSV 파일 읽기, 데이터 정제, 그룹별 집계, 피벗 테이블 생성 등 엑셀에서 하던 거의 모든 작업을 더 빠르고 자동화된 방식으로 처리할 수 있습니다. 주의할 점도 있습니다.
데이터프레임은 메모리를 많이 사용합니다. 수백만 행의 데이터를 다룰 때는 데이터 타입을 최적화하거나, 청크 단위로 읽는 방법을 고려해야 합니다.
박시니어 씨가 조언했습니다. "Pandas를 잘 다루면 엑셀로 하루 종일 할 작업을 몇 분 만에 끝낼 수 있어요." 김개발 씨는 데이터프레임으로 판매 데이터를 분석하기 시작했습니다.
생산성이 눈에 띄게 올라가는 것을 느낄 수 있었습니다.
실전 팁
💡 - CSV 파일은 pd.read_csv()로 바로 데이터프레임으로 읽을 수 있습니다
- describe() 메서드로 데이터의 통계 요약을 빠르게 확인하세요
- 대용량 데이터는 dtypes를 확인하고 메모리 효율적인 타입으로 변환하세요
4. 행렬 연산
김개발 씨가 머신러닝 프로젝트에 참여하게 되었습니다. 수천 개의 이미지 데이터를 처리해야 했는데, 반복문으로 픽셀 하나하나를 계산하니 너무 느렸습니다.
박시니어 씨가 말했습니다. "NumPy를 쓰면 100배는 빨라질 거예요."
NumPy는 파이썬의 수치 연산 라이브러리입니다. 다차원 배열인 ndarray를 제공하며, C로 구현되어 순수 파이썬보다 수십 배 빠릅니다.
행렬 연산, 선형대수, 통계 계산 등 과학 계산의 기초가 되는 라이브러리입니다.
다음 코드를 살펴봅시다.
import numpy as np
# 2차원 배열(행렬) 생성
matrix_a = np.array([[1, 2, 3],
[4, 5, 6]])
matrix_b = np.array([[7, 8],
[9, 10],
[11, 12]])
# 행렬 곱셈 (2x3 @ 3x2 = 2x2)
result = np.dot(matrix_a, matrix_b) # 또는 matrix_a @ matrix_b
# 요소별 연산 (브로드캐스팅)
normalized = matrix_a / matrix_a.max() # 모든 요소를 최댓값으로 나눔
# 배열 형태 변환과 통계
flattened = matrix_a.flatten() # 1차원으로 펼치기
print(f"평균: {matrix_a.mean()}, 합계: {matrix_a.sum()}")
머신러닝과 데이터 과학에서 행렬 연산은 빠질 수 없습니다. 이미지는 픽셀의 행렬이고, 신경망의 가중치도 행렬입니다.
이런 대량의 숫자를 효율적으로 다루려면 NumPy가 필수입니다. NumPy의 핵심은 ndarray(N-dimensional array)입니다.
일반 파이썬 리스트와 달리 모든 요소가 같은 타입이어야 하고, 연속된 메모리 공간에 저장됩니다. 이 덕분에 C 언어 수준의 성능을 낼 수 있습니다.
코드의 4번째 줄에서 2x3 행렬을 만들고, 6번째 줄에서 3x2 행렬을 만듭니다. 11번째 줄의 np.dot 함수로 행렬 곱셈을 수행합니다.
행렬 곱셈에서 (2x3) @ (3x2)는 (2x2) 결과를 만들어냅니다. 브로드캐스팅은 NumPy의 강력한 기능입니다.
14번째 줄에서 행렬 전체를 최댓값으로 나누는데, 반복문 없이 한 줄로 해결합니다. NumPy가 자동으로 스칼라 값을 배열 크기에 맞게 확장해서 연산합니다.
순수 파이썬으로 같은 연산을 하면 이중 반복문이 필요합니다. 100만 개의 요소가 있다면 100만 번의 반복이 필요하죠.
NumPy는 내부적으로 최적화된 C 코드를 사용해서 이 작업을 수십 배 빠르게 처리합니다. 실무에서 NumPy는 다양한 곳에 쓰입니다.
이미지 처리에서 픽셀 값을 정규화하거나, 딥러닝에서 텐서 연산을 수행하거나, 과학 시뮬레이션에서 대량의 수치 계산을 할 때 모두 NumPy가 기반이 됩니다. 형태 변환도 자주 사용됩니다.
17번째 줄의 flatten은 다차원 배열을 1차원으로 펼칩니다. 반대로 reshape는 원하는 형태로 배열을 재구성합니다.
머신러닝 모델에 데이터를 입력할 때 형태를 맞추는 데 필수적인 기능입니다. 통계 함수도 기본 제공됩니다.
평균, 합계, 표준편차, 최댓값, 최솟값 등을 한 줄로 계산할 수 있습니다. 축(axis)을 지정하면 행별 또는 열별 통계도 구할 수 있습니다.
김개발 씨는 이미지 전처리 코드를 NumPy로 다시 작성했습니다. 30초 걸리던 작업이 0.3초로 줄었습니다.
박시니어 씨가 말했습니다. "이게 바로 벡터화 연산의 힘이에요." 올바른 도구를 사용하면 코드가 간결해지고 성능도 극적으로 향상됩니다.
실전 팁
💡 - 반복문 대신 벡터화 연산을 사용하면 성능이 수십 배 향상됩니다
- reshape(-1)은 자동으로 크기를 계산해줍니다
- axis=0은 행 방향, axis=1은 열 방향 연산입니다
5. 벡터 자료형
김개발 씨가 추천 시스템 개발에 참여하게 되었습니다. 사용자와 상품을 벡터로 표현해서 유사도를 계산해야 했습니다.
수학 시간에 배운 벡터가 프로그래밍에서 이렇게 중요하게 쓰일 줄은 몰랐습니다.
벡터는 크기와 방향을 가진 수학적 객체입니다. 프로그래밍에서는 숫자들의 순서 있는 나열로 표현됩니다.
NumPy의 1차원 배열이 바로 벡터이며, 덧셈, 스칼라곱, 내적 등 다양한 연산을 수행할 수 있습니다. 머신러닝에서 데이터 포인트는 모두 벡터로 표현됩니다.
다음 코드를 살펴봅시다.
import numpy as np
# 벡터 생성과 기본 연산
user_preference = np.array([0.8, 0.5, 0.2, 0.9]) # 사용자 선호도 벡터
product_features = np.array([0.7, 0.6, 0.3, 0.8]) # 상품 특성 벡터
# 벡터 연산
vector_sum = user_preference + product_features # 요소별 덧셈
scaled = user_preference * 2 # 스칼라 곱
# 내적(dot product): 유사도 계산에 활용
similarity = np.dot(user_preference, product_features) # 1.55
# 벡터 정규화 (크기를 1로 만듦)
norm = np.linalg.norm(user_preference) # 벡터의 크기
normalized = user_preference / norm # 단위 벡터
추천 시스템의 핵심은 유사도 계산입니다. 이 사용자가 좋아할 만한 상품은 무엇일까요?
이 질문에 답하려면 사용자와 상품을 비교 가능한 형태로 표현해야 합니다. 그것이 바로 벡터입니다.
김개발 씨의 프로젝트에서는 사용자의 선호도를 4개의 숫자로 표현했습니다. 액션 영화 선호도, 로맨스 선호도, 공포 선호도, SF 선호도처럼 각 차원이 하나의 특성을 나타냅니다.
상품(영화)도 같은 방식으로 표현합니다. 코드의 4번째 줄에서 사용자 선호도 벡터를 만듭니다.
**[0.8, 0.5, 0.2, 0.9]**는 액션과 SF를 좋아하고 공포는 별로인 사용자를 나타냅니다. 5번째 줄의 상품 벡터도 비슷한 구조입니다.
벡터 연산은 요소별로 수행됩니다. 두 벡터를 더하면 같은 위치의 요소끼리 더해집니다.
스칼라를 곱하면 모든 요소에 그 값이 곱해집니다. 직관적이고 간단합니다.
가장 중요한 연산은 **내적(dot product)**입니다. 12번째 줄에서 두 벡터의 내적을 계산합니다.
내적 값이 클수록 두 벡터가 비슷한 방향을 가리킵니다. 즉, 사용자 취향과 상품 특성이 비슷하다는 뜻입니다.
코사인 유사도는 내적을 정규화한 것입니다. 벡터의 크기에 영향받지 않고 순수하게 방향만 비교합니다.
15번째 줄에서 벡터의 크기(norm)를 구하고, 16번째 줄에서 단위 벡터로 정규화합니다. 실제 추천 시스템에서는 사용자와 모든 상품 벡터의 유사도를 계산해서 가장 높은 것을 추천합니다.
행렬 연산을 활용하면 수백만 개의 상품 유사도를 빠르게 계산할 수 있습니다. 워드 임베딩도 벡터의 활용 사례입니다.
단어를 수백 차원의 벡터로 표현하면, 비슷한 의미의 단어들이 가까운 위치에 모입니다. "왕 - 남자 + 여자 = 여왕" 같은 벡터 연산이 가능해집니다.
김개발 씨는 벡터 유사도를 기반으로 추천 알고리즘을 구현했습니다. 박시니어 씨가 말했습니다.
"벡터는 데이터 과학의 언어예요. 모든 것을 벡터로 표현할 수 있으면 비교하고 분석할 수 있습니다." 세상의 데이터를 숫자의 나열로 바꾸는 것, 그것이 벡터의 힘입니다.
실전 팁
💡 - 코사인 유사도는 scipy.spatial.distance.cosine으로 간편하게 계산할 수 있습니다
- 고차원 벡터에서는 유클리드 거리보다 코사인 유사도가 더 유용합니다
- 벡터 정규화는 비교 전에 항상 수행하는 것이 좋습니다
6. 스택 구현
김개발 씨가 텍스트 에디터의 실행 취소(Undo) 기능을 구현하라는 과제를 받았습니다. 최근에 한 작업부터 거꾸로 취소해야 하는데, 어떤 자료구조를 써야 할까요?
박시니어 씨가 힌트를 주었습니다. "접시를 쌓아 올린다고 생각해봐요."
스택은 후입선출(LIFO, Last In First Out) 구조의 자료형입니다. 마지막에 넣은 데이터가 가장 먼저 나옵니다.
접시를 쌓으면 맨 위의 접시부터 꺼내는 것과 같습니다. 실행 취소, 괄호 검사, 함수 호출 스택 등 다양한 곳에서 활용됩니다.
다음 코드를 살펴봅시다.
class Stack:
def __init__(self):
self.items = [] # 내부 저장소로 리스트 사용
def push(self, item):
"""스택에 항목 추가 (맨 위에 쌓기)"""
self.items.append(item)
def pop(self):
"""스택에서 항목 제거 및 반환 (맨 위에서 꺼내기)"""
if not self.is_empty():
return self.items.pop()
raise IndexError("스택이 비어있습니다")
def peek(self):
"""맨 위 항목 확인 (제거하지 않음)"""
if not self.is_empty():
return self.items[-1]
return None
def is_empty(self):
return len(self.items) == 0
실행 취소 기능을 생각해봅시다. 문서에서 "Hello"를 입력하고, "World"를 입력하고, 글자를 굵게 만들었습니다.
실행 취소를 누르면 무엇이 먼저 취소될까요? 당연히 가장 최근 작업인 "굵게 만들기"가 취소됩니다.
이것이 바로 후입선출(LIFO) 원리입니다. 마지막에 들어온 것이 가장 먼저 나갑니다.
스택은 이 원리를 구현한 자료구조입니다. 스택에는 두 가지 핵심 연산이 있습니다.
push는 데이터를 맨 위에 쌓는 것이고, pop은 맨 위에서 데이터를 꺼내는 것입니다. 접시를 쌓고 꺼내는 것과 똑같습니다.
코드를 살펴봅시다. 3번째 줄에서 내부 저장소로 리스트를 사용합니다.
파이썬 리스트의 append와 pop 메서드가 이미 스택 연산과 일치하기 때문에 구현이 간단합니다. push 메서드는 리스트 끝에 항목을 추가합니다.
pop 메서드는 리스트 끝에서 항목을 제거하며 반환합니다. 두 연산 모두 O(1) 시간복잡도로 매우 빠릅니다.
peek 메서드는 맨 위 항목을 확인하되 제거하지는 않습니다. 스택이 비어있는지 확인할 때나, 다음에 꺼낼 데이터를 미리 볼 때 유용합니다.
스택은 실행 취소 외에도 많은 곳에 쓰입니다. 프로그래밍 언어에서 괄호가 올바르게 짝지어졌는지 검사할 때 스택을 사용합니다.
여는 괄호는 push하고, 닫는 괄호를 만나면 pop해서 짝이 맞는지 확인합니다. 웹 브라우저의 뒤로 가기 버튼도 스택입니다.
새 페이지로 이동할 때마다 현재 페이지를 스택에 push하고, 뒤로 가기를 누르면 pop해서 이전 페이지로 돌아갑니다. 프로그램의 함수 호출도 스택으로 관리됩니다.
함수 A가 함수 B를 호출하면 B가 스택에 쌓이고, B가 끝나면 pop되어 A로 돌아갑니다. 재귀 함수가 너무 깊어지면 "스택 오버플로우" 오류가 발생하는 이유가 바로 이것입니다.
김개발 씨는 스택을 활용해 실행 취소 기능을 구현했습니다. 사용자의 모든 작업을 스택에 push하고, Ctrl+Z를 누르면 pop해서 취소합니다.
단순하지만 강력한 자료구조입니다.
실전 팁
💡 - 파이썬에서는 리스트를 스택으로 바로 사용할 수 있습니다 (append/pop)
- collections.deque가 양방향 스택으로 더 효율적입니다
- 재귀를 반복문으로 바꿀 때 명시적 스택을 사용하면 됩니다
7. 큐와 트리 구현
김개발 씨가 새로운 과제를 받았습니다. 첫째는 프린터 대기열 시스템, 둘째는 조직도 검색 기능이었습니다.
스택만으로는 해결할 수 없는 문제들이었습니다. 박시니어 씨가 말했습니다.
"이번에는 큐와 트리를 배워볼까요?"
큐는 선입선출(FIFO, First In First Out) 구조입니다. 먼저 들어온 데이터가 먼저 나갑니다.
은행 대기줄처럼 순서대로 처리됩니다. 트리는 계층적 구조를 표현하는 자료구조입니다.
폴더 구조, 조직도, HTML DOM처럼 부모-자식 관계가 있는 데이터에 적합합니다.
다음 코드를 살펴봅시다.
from collections import deque
# 큐 구현: 선입선출(FIFO) 구조
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
self.items.append(item) # 뒤에 추가
def dequeue(self):
if self.items:
return self.items.popleft() # 앞에서 제거
raise IndexError("큐가 비어있습니다")
# 트리 노드 구현
class TreeNode:
def __init__(self, value):
self.value = value
self.children = [] # 자식 노드 리스트
def add_child(self, child_node):
self.children.append(child_node)
# 조직도 예시
ceo = TreeNode("CEO")
cto = TreeNode("CTO")
cfo = TreeNode("CFO")
ceo.add_child(cto)
ceo.add_child(cfo)
프린터 대기열을 생각해봅시다. 먼저 인쇄 요청한 문서가 먼저 출력되어야 합니다.
나중에 요청한 사람이 먼저 출력되면 불공평하겠죠. 이것이 선입선출(FIFO) 원리이며, 큐가 이를 구현합니다.
스택과 큐의 차이점을 명확히 알아두세요. 스택은 후입선출(LIFO)로 마지막 것이 먼저 나오고, 큐는 선입선출(FIFO)로 처음 것이 먼저 나옵니다.
스택은 접시 쌓기, 큐는 줄 서기로 기억하면 됩니다. 코드의 8-13번째 줄을 봅시다.
enqueue는 데이터를 큐 뒤에 추가합니다. dequeue는 큐 앞에서 데이터를 꺼냅니다.
여기서 deque(덱)을 사용하는 이유가 있습니다. 리스트의 **pop(0)**은 O(n)이지만 deque의 **popleft()**는 O(1)입니다.
큐는 다양한 곳에서 활용됩니다. 메시지 큐, 작업 스케줄러, BFS(너비 우선 탐색) 알고리즘 등이 대표적입니다.
서버가 요청을 처리하는 방식도 기본적으로 큐 구조입니다. 이제 트리를 살펴봅시다.
17번째 줄부터 트리 노드를 정의합니다. 각 노드는 값(value)과 자식 노드 목록(children)을 가집니다.
루트 노드에서 시작해 아래로 뻗어나가는 구조입니다. 27-30번째 줄에서 간단한 조직도를 만듭니다.
CEO 아래에 CTO와 CFO가 있습니다. 이것이 트리의 부모-자식 관계입니다.
CEO가 부모이고 CTO, CFO가 자식입니다. 트리 구조는 우리 주변 어디에나 있습니다.
컴퓨터의 폴더 구조, 웹 페이지의 HTML DOM, 데이터베이스의 인덱스(B-트리), 결정 트리 등이 모두 트리입니다. 이진 트리는 자식이 최대 2개인 특수한 트리입니다.
이진 탐색 트리는 왼쪽 자식이 부모보다 작고 오른쪽 자식이 부모보다 큰 규칙을 따릅니다. 이 규칙 덕분에 데이터를 O(log n) 시간에 탐색할 수 있습니다.
트리 순회 방법도 중요합니다. 전위 순회는 부모-왼쪽-오른쪽, 중위 순회는 왼쪽-부모-오른쪽, 후위 순회는 왼쪽-오른쪽-부모 순서로 방문합니다.
용도에 따라 적절한 순회 방법을 선택합니다. 김개발 씨는 큐로 프린터 대기열을, 트리로 조직도 검색을 구현했습니다.
박시니어 씨가 말했습니다. "자료구조는 문제의 본질을 파악하면 자연스럽게 선택됩니다.
순서대로 처리하면 큐, 계층 구조면 트리예요." 문제를 보는 눈이 자료구조 선택의 핵심입니다.
실전 팁
💡 - 큐는 collections.deque를 사용하면 O(1) 성능을 보장합니다
- 트리 탐색에는 재귀가 자연스럽지만, 스택/큐로 반복문 구현도 가능합니다
- 이진 탐색 트리가 편향되면 성능이 O(n)으로 떨어지므로 균형 트리를 고려하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.