해시 특강 일단 시작

류한선·2025년 7월 2일

실기연습-2

목록 보기
69/95

좋습니다. 이제 정보처리기사 실기가 3주도 남지 않았기 때문에, 해시(Hash)에 대한 개념부터 실전 문제 트랩까지 단단히 정리해보는 해시 집중 특강을 아래와 같이 진행하겠습니다.


🔥 정보처리기사 실기 대비: 해시(Hash) 집중 특강


✅ 1단계: 해시 개념이해

1. 해시란?

  • Hash(해시): 어떤 데이터를 **고정된 크기의 값(=해시값)**으로 바꿔주는 변환 기법
  • 예) "apple"238102
  • 이 값은 인덱싱, 탐색, 비교 등에 매우 빠르게 사용됨

2. 해시 함수(Hash Function)

  • 데이터를 해시값으로 바꿔주는 함수

  • 대표적 해시 함수 특징:

    • 빠르다
    • 동일한 입력 → 동일한 해시값
    • 서로 다른 입력 → 가능한 다른 해시값

3. 해시 테이블(Hash Table)

  • (Key, Value) 쌍을 저장하는 자료구조
  • key를 해시함수로 해시값으로 바꿔서 배열의 인덱스처럼 사용

예:

hash("abc") % 10 = 3 → 테이블의 인덱스 3번 칸에 저장

✅ 2단계: 해시 자료구조와 구현 개념

1. 파이썬에서의 해시

  • dict, set은 내부적으로 해시 테이블을 사용함
  • dict[key] = value 할 때 key를 해시함수로 바꿔서 배열처럼 저장

2. 충돌(Collision) 문제

  • 서로 다른 key가 같은 해시값을 만들 수도 있음

  • 해결 방법:

    • 체이닝(Chaining): 같은 인덱스에 연결 리스트로 여러 개 저장
    • 오픈 어드레싱: 빈 칸을 찾아 옆으로 저장

✅ 3단계: 실전에서 자주 나오는 해시 응용 예시

💡 예1. 중복 검사

nums = [1, 2, 3, 2]
seen = set()
for num in nums:
    if num in seen:
        print("중복 발견!")
    seen.add(num)

💡 예2. 빈도수 세기

from collections import defaultdict

freq = defaultdict(int)
for word in ["apple", "banana", "apple"]:
    freq[word] += 1

💡 예3. anagram 판별 (문자 빈도수 해시 비교)

from collections import Counter
s1 = "listen"
s2 = "silent"
print(Counter(s1) == Counter(s2))  # True

✅ 4단계: 정보처리기사 실기 스타일 문제

❗문제 1.

다음 Python 코드에서 출력 결과는?

data = ["a", "b", "a", "c", "b", "d"]
unique = set(data)
print(len(unique))
  1. 4
  2. 6
  3. 3
  4. 2

👉 정답: 1번 (4)
set은 중복을 제거: {"a", "b", "c", "d"}


❗문제 2.

다음 중 해시 자료구조의 충돌(Collision) 처리 방식이 아닌 것은?

  1. 체이닝
  2. 이진 탐색 트리
  3. 오픈 어드레싱
  4. 더블 해싱

👉 정답: 2번
→ 이진 탐색 트리는 별개의 자료구조임 (자바에서는 Java 8 이후 충돌이 많으면 Tree로 바꾸긴 함, 하지만 일반적인 해시 충돌 해결법은 아님)


✅ 5단계: 자바에서의 해시 트랩

❗다음 코드를 보자

Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 2);
System.out.println(map.get("apple"));
  • "apple"이라는 문자열은 내부적으로 **hashCode()**에 의해 해시값으로 바뀌어 저장됨
  • 자바에서 해시 키를 정확히 쓰려면 반드시 equals()와 hashCode()를 함께 오버라이드해야 함

✅ 6단계: 실전 트랩 정리

트랩설명
dict.get(key)key 없을 경우 None 리턴
set.remove(x) vs set.discard(x)remove는 없으면 에러, discard는 없으면 조용히 패스
defaultdict(int)초기값이 없을 때 자동 0
hash()는 값이 같으면 같은 해시단, 충돌도 일어날 수 있음
해시 키는 변경 불가능한 타입만 가능파이썬: list는 안 됨, tuple은 가능

🎯 마무리 요약

  • 해시는 탐색을 **O(1)**로 빠르게 해주는 강력한 도구
  • 실기에서는 중복 제거, 빈도수 세기, 매핑 등에 많이 쓰임
  • 해시 충돌 처리 방식(체이닝, 오픈 어드레싱)은 자주 출제됨
  • 파이썬에서는 dict, set / 자바에서는 HashMap, HashSet 기반

좋아, 그럼 지금부터 정보처리기사 실기 스타일의 해시(Hash) 응용 문제 10문제를 줄게.
모두 실제 시험에 출제될 수 있는 트랩 포인트가 포함되어 있고,
각 문제마다 정답 + 상세 해설 + 실전 트랩 요점을 제공할게.


🧠 [해시 응용 실전 문제 10선] — 정보처리기사 실기 대비


🔹 문제 1. 중복 검사

다음 코드의 출력값은?

data = ["a", "b", "a", "c", "b", "d"]
unique = set(data)
print(len(unique))
  1. 4
  2. 6
  3. 3
  4. 2

✅ 정답: 1번

  • set()은 중복을 자동으로 제거한다.
  • 고유한 값: "a", "b", "c", "d" → 총 4개

🔍 트랩: set은 순서도 없고, 중복도 제거되므로 list처럼 생각하면 오답 가능


🔹 문제 2. 해시 키 타입

다음 중 해시의 키로 적절하지 않은 것은?

  1. ("a", 1)
  2. frozenset([1, 2])
  3. ["b", 2]
  4. "apple"

✅ 정답: 3번

  • 키로 사용될 값은 변경 불가능한(immutable) 타입이어야 함
  • list는 mutable → 키로 쓸 수 없음

🔍 트랩: tuple, frozenset은 키로 가능하지만 list, dict는 불가


🔹 문제 3. dict.get() 활용

data = {"a": 1, "b": 2}
print(data.get("c", 0))

출력값은?

  1. KeyError
  2. None
  3. 0
  4. "c"

✅ 정답: 3번

  • .get("key", default)는 key가 없을 경우 default값 반환
  • "c"가 없기 때문에 0 반환

🔍 트랩: dict["c"]는 오류 발생하지만, .get()은 안전하게 default 반환


🔹 문제 4. 해시 충돌 방지법

해시 충돌을 방지하거나 줄이기 위한 방법으로 적절하지 않은 것은?

  1. 해시 테이블 크기를 소수로 설정한다
  2. 이중 해싱(double hashing)을 사용한다
  3. 모든 데이터를 같은 인덱스에 저장한다
  4. 체이닝(chaining) 방식으로 연결리스트를 활용한다

✅ 정답: 3번

🔍 트랩: 같은 인덱스에 저장하면 충돌을 늘리는 방식이다 (해결이 아니라 악화)


🔹 문제 5. 파이썬 dict 기본값 트랩

from collections import defaultdict

d = defaultdict(list)
d["a"].append(1)
print(d["b"])

출력 결과는?

  1. KeyError
  2. []
  3. [0]
  4. None

✅ 정답: 2번

  • defaultdict(list)는 새 키에 접근하면 자동으로 [] 생성

🔍 트랩: 일반 dict였다면 KeyError 발생, 하지만 defaultdict는 기본값 생성됨


🔹 문제 6. 자바에서 해시 충돌 처리

다음 중 자바에서 HashMap의 해시 충돌 처리에 대한 설명으로 옳지 않은 것은?

  1. Java 8 이전에는 연결 리스트로 충돌 처리
  2. Java 8 이후에는 일정 수 이상 충돌 시 Tree로 전환
  3. hashCode()와 equals()는 무조건 같이 오버라이딩해야 한다
  4. 충돌이 발생해도 값은 절대 덮어쓰여지지 않는다

✅ 정답: 4번

  • 동일한 키가 들어오면 덮어씀 → put() 시 기존 값 교체됨

🔍 트랩: 같은 해시값이 아닌 같은 key면 덮어씌워짐


🔹 문제 7. set 메서드 비교

s = set([1, 2, 3])
s.discard(4)
print(s)
  1. {1, 2, 3}
  2. {1, 2, 3, 4}
  3. 에러 발생
  4. None

✅ 정답: 1번

  • .discard(x)는 값이 없으면 그냥 넘어감
  • .remove(x)는 값이 없으면 에러

🔍 트랩: discard와 remove의 차이 반드시 숙지


🔹 문제 8. 해시 탐색 시간 복잡도

일반적으로 해시 테이블에서 탐색/삽입의 시간 복잡도는?

  1. O(log N)
  2. O(N)
  3. O(1)
  4. O(N^2)

✅ 정답: 3번

  • 해시는 평균적으로 **O(1)**의 속도로 탐색 및 삽입 가능
  • 단, 충돌이 심할 경우 O(N)까지 늘어날 수 있음

🔹 문제 9. 해시 응용 문제

arr = ["dog", "god", "cat", "tac", "act"]
groups = {}

for word in arr:
    key = "".join(sorted(word))
    groups.setdefault(key, []).append(word)

print(len(groups))
  1. 2
  2. 3
  3. 5
  4. 1

✅ 정답: 2번

  • 정렬하면 "dog", "god" → "dgo" / "cat", "tac", "act" → "act"
  • 그룹: "dgo": [dog, god], "act": [cat, tac, act]

🔍 트랩: sorted()join()을 통해 아나그램 그룹핑 → 많이 나오는 실기 유형!


🔹 문제 10. 해시에서의 동등 비교

자바에서 HashMap의 키로 사용되는 객체에 대해 올바른 설명은?

  1. hashCode만 오버라이드하면 된다
  2. equals만 오버라이드하면 된다
  3. 둘 다 오버라이드해야 정확한 비교가 가능하다
  4. 아무것도 오버라이드하지 않아도 무조건 key 비교가 된다

✅ 정답: 3번

🔍 트랩: hashCode() → 같은 버킷 찾음
→ equals() → 같은 key인지 판단


✅ 마무리 요약

핵심 개념기억할 점
set중복 제거, 순서 없음
dict.get()안전한 접근 (default 설정 가능)
defaultdict자동 기본값 생성
키 타입immutable만 가능 (tuple, str, frozenset 등)
remove vs discardremove는 에러, discard는 무시
Java의 HashMaphashCode + equals 반드시 오버라이딩
충돌 처리체이닝, 오픈 어드레싱, Java8 이후 Tree

정확한 지적이야!
지금까지 제공한 문제들은 파이썬 기반 예제 중심이었어.
이유는 다음과 같아:


✅ 왜 해시 예제가 파이썬이었는가?

  1. 파이썬이 해시 응용을 간결하게 보여주기에 매우 적합

    • dict, set, defaultdict 등이 내장되어 있고, 문법이 직관적이야
    • 실기 시험에서 논리 구조나 자료 흐름 파악할 때 빠르게 학습 가능
  2. 정보처리기사 실기 출제 유형 자체가 특정 언어보다 개념 위주

    • 어떤 언어든 해시 테이블의 개념, 충돌 처리, 탐색/삽입 성능, 키/값 구조를 정확히 이해하는 게 핵심이야
  3. 처음 개념을 잡을 때는 문법보다 흐름이 중요

    • 그래서 먼저 파이썬으로 본 거야

하지만! 이제 너가 자바와 C언어를 같이 공부 중이라는 걸 알고 있으니까,
Java 버전의 HashMap, HashSet, 그리고 실전 트랩 중심으로 설명해줄게.


🔷 [자바 기반 해시 특강] – HashMap, HashSet 완전정복


✅ 자바의 해시 구조 핵심 요약

자료구조설명
HashMap<K, V>키-값 쌍 저장
HashSet<E>값만 저장 (중복 불가)
LinkedHashMap입력 순서 유지
TreeMap키 정렬 순서 유지
IdentityHashMap== 비교 기준 (equals 무시)

🔸 Java HashMap 기본 예제

import java.util.*;

public class HashExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 3);
        map.put("banana", 2);
        map.put("apple", 5);  // 기존 값 덮어쓰기

        System.out.println(map.get("apple")); // 출력: 5
    }
}

🔸 Java 해시 트랩 3대장

  1. 동일한 키를 넣으면 값이 덮어짐

    map.put("key", 1);
    map.put("key", 2); // → key는 중복, 값은 2로 갱신됨
  2. 커스텀 객체를 키로 쓸 때는 hashCode()equals()를 모두 오버라이딩

    class Person {
        String name;
        public Person(String name) { this.name = name; }
    
        public boolean equals(Object o) { ... }
        public int hashCode() { ... }
    }
  3. HashSet도 내부적으로 HashMap을 사용

    Set<String> set = new HashSet<>();
    set.add("apple");
    set.add("apple"); // 중복 저장 안 됨

🔸 Java8 이상: HashMap 내부 구조 변화

  • 충돌이 많아진다면? → Java 8부터는 연결 리스트 → Tree 구조로 자동 전환

    • 조건: 같은 해시값을 가진 노드가 8개 이상 + 배열 크기 64 이상

🔸 Java 실기 스타일 문제 예시

❓ 문제

다음 코드 실행 결과는?

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("a", 3);
System.out.println(map.get("a"));
  1. 1
  2. 2
  3. 3
  4. null

✅ 정답: 3번

"a"라는 키는 덮어써짐 → 마지막 값 3 저장됨


🔸 커스텀 객체 키 실습 트랩

class Fruit {
    String name;
    public Fruit(String name) {
        this.name = name;
    }
}

public class Test {
    public static void main(String[] args) {
        Map<Fruit, Integer> map = new HashMap<>();
        map.put(new Fruit("apple"), 1);
        System.out.println(map.get(new Fruit("apple"))); // null?
    }
}

✅ 출력: null
→ 이유: equals()hashCode()를 오버라이딩하지 않았기 때문


✅ 결론: 지금부터 자바 중심 문제로 바꿔줄까?

너가 원한다면, 지금부터는 해시 문제를 자바로 낼게.


예를 들어:

  • HashMapequals/hashCode 트랩
  • HashSet 중복 제거 원리
  • LinkedHashMap 순서 유지
  • 해시 충돌 → Tree 전환 조건

매우 중요한 질문이야!
자바에서 equals()와 hashCode()를 왜 오버라이딩해야 하는지는, 해시 구조(HashMap, HashSet)를 정확히 이해하기 위한 핵심 중의 핵심이야.


✅ equals()와 hashCode() — 왜 오버라이딩해야 할까?

자바에서 HashMap, HashSet 같은 해시 기반 자료구조는 내부적으로 다음 두 가지를 사용해서 key를 저장하고 찾는다:

  1. hashCode()버킷 위치(배열 인덱스) 를 정함
  2. equals()진짜 같은 객체인지를 비교함

🔧 비유로 먼저 이해하자

생각해봐:

  • 너희 집 주소: 123-45번지 (hashCode)
  • 그 집 문을 열고 들어가서 확인: 주인이 너 맞는지 얼굴 대조 (equals)

💡 즉:

hashCode()어디에 저장할지를 찾고,
equals()진짜 같은 key인지 확인하는 거야.


🔬 해시 구조 내부 동작 흐름 (HashMap 기준)

  1. map.put(key, value) 를 하면:

    • key의 hashCode()로 **버킷(배열 인덱스)**를 결정하고
    • 그 자리에 같은 key가 이미 있는지 equals()로 비교
  2. map.get(key) 할 때도 똑같이:

    • key의 hashCode()로 버킷 찾고
    • 거기에 있는 key랑 equals() 비교해서 값 꺼냄

❗️문제는 여기서 발생함

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
  • p1과 p2는 다른 객체니까 기본적으로 equals()는 false고,
  • hashCode()도 다르게 나옴 (Object 기본 동작: 주소 기반)

그래서 HashMap은 이렇게 오작동함:

Map<Person, String> map = new HashMap<>();
map.put(p1, "개발자");
System.out.println(map.get(p2)); // ❌ null 나옴

🔨 해결책: equals()와 hashCode()를 override 해라

class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person other = (Person) o;
        return this.name.equals(other.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode(); // 문자열의 해시 사용
    }
}

이제 다음은 정상 작동함:

Person p1 = new Person("Alice");
Person p2 = new Person("Alice");

Map<Person, String> map = new HashMap<>();
map.put(p1, "개발자");
System.out.println(map.get(p2)); // ✅ "개발자"

✅ 요약 정리

함수역할왜 필요?
hashCode()버킷 위치 계산빠른 위치 탐색을 위해
equals()값 비교같은 객체인지 판단 위해
둘 다 재정의필수!HashMap, HashSet에서 key로 쓰려면 둘 다 override 해야 함

🎯 기억해야 할 규칙

"같은 객체"면
equals()true
hashCode()는 같아야 한다

반대로,

hashCode()가 같다고 해서 equals()도 무조건 true는 아님
→ 이건 충돌 상황일 수 있음


🔍 연습 문제로 확인해볼래?

원한다면 아래 중 하나 골라줘:

  • ✅ “equals와 hashCode 오버라이딩 문제 줘”
  • ✅ “HashMap에 넣고 get 안되는 코드 보여줘”
  • ✅ “Set에 객체 넣었는데 중복 저장된 이유 알려줘”

이 중 골라줘도 되고, 직접 원하는 유형 말해줘도 돼.
지금이 진짜 자바 해시 구조의 본질을 잡을 황금 타이밍이야.

0개의 댓글