컴퓨터공학 전공 핵심정리 하편: 알고리즘 코딩 & SQL 주관식 실전 대비

컴퓨터공학 전공 핵심정리 하편: 알고리즘 코딩 & SQL 주관식 실전 대비

주관식 3문항은 코드 출력 결과 작성 또는 직접 코드/SQL 작성 형태로 출제된다. 레퍼런스 활용이 가능하지만, 시간이 제한되므로 핵심 패턴을 완벽히 숙지해야 한다.


Part 1. 알고리즘 코딩 — 언어별 핵심 문법과 출력 트레이싱

1. C/C++ 핵심 패턴

1.1 포인터와 배열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* ★ 포인터 기초 — 출력 결과를 정확히 추적하라 */
#include <stdio.h>

int main() {
    int a = 10, b = 20;
    int *p = &a;
    int **pp = &p;

    printf("%d\n", *p);     // 10 (p가 가리키는 값)
    printf("%d\n", **pp);   // 10 (pp→p→a)

    *p = 30;
    printf("%d\n", a);      // 30 (p를 통해 a 변경)

    p = &b;
    printf("%d\n", **pp);   // 20 (pp→p→b, p가 b를 가리킴)

    return 0;
}
1
2
3
4
★ 포인터 핵심 정리:
  int *p = &a;   →  p는 a의 주소, *p는 a의 값
  int **pp = &p; →  pp는 p의 주소, *pp는 p(=a의 주소), **pp는 a의 값
  p++            →  포인터가 int 크기(4바이트)만큼 이동
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* ★ 배열과 포인터 관계 */
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("%d\n", *p);       // 10
    printf("%d\n", *(p+2));   // 30
    printf("%d\n", p[3]);     // 40 (p[i] == *(p+i))

    p += 2;
    printf("%d\n", *p);       // 30
    printf("%d\n", p[-1]);    // 20 (p[-1] == *(p-1))

    // 2차원 배열
    int mat[2][3] = {{1,2,3},{4,5,6}};
    printf("%d\n", mat[1][2]);         // 6
    printf("%d\n", *(*(mat+1)+2));     // 6 (동일)
    printf("%d\n", *(*mat+4));         // 5 (행 무시, 연속 메모리)

    return 0;
}

1.2 문자열 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* ★ 문자열 함수 — 빈출 */
#include <stdio.h>
#include <string.h>

int main() {
    char s1[] = "Hello";
    char s2[] = "World";
    char s3[20];

    printf("%lu\n", strlen(s1));        // 5 (널문자 미포함)
    printf("%lu\n", sizeof(s1));        // 6 (널문자 포함)

    strcpy(s3, s1);
    printf("%s\n", s3);                 // Hello

    strcat(s3, s2);
    printf("%s\n", s3);                 // HelloWorld

    printf("%d\n", strcmp("abc","abd")); // 음수 ('c' < 'd')
    printf("%d\n", strcmp("abd","abc")); // 양수
    printf("%d\n", strcmp("abc","abc")); // 0

    // 문자열 역순
    char str[] = "ABCDE";
    int len = strlen(str);
    for (int i = 0; i < len / 2; i++) {
        char tmp = str[i];
        str[i] = str[len-1-i];
        str[len-1-i] = tmp;
    }
    printf("%s\n", str);               // EDCBA

    return 0;
}

1.3 구조체와 공용체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* ★ 구조체 */
#include <stdio.h>

struct Student {
    char name[20];
    int score;
    float avg;
};

int main() {
    struct Student s = {"Kim", 85, 3.5};
    struct Student *p = &s;

    printf("%s %d\n", s.name, s.score);     // Kim 85
    printf("%s %.1f\n", p->name, p->avg);   // Kim 3.5

    return 0;
}

/* ★ 공용체 — 메모리 공유 (sizeof = 가장 큰 멤버) */
union Data {
    int i;
    float f;
    char c;
};
// sizeof(union Data) = sizeof(float) = 4 (가장 큰 멤버)
// 마지막에 저장한 값만 유효!

1.4 재귀와 출력 트레이싱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* ★★★ 재귀 출력 추적 — 가장 빈출 */
#include <stdio.h>

void func(int n) {
    if (n <= 0) return;
    printf("%d ", n);
    func(n - 1);
    printf("%d ", n);
}

int main() {
    func(3);
    // 출력: 3 2 1 1 2 3
    return 0;
}
1
2
3
4
5
6
7
호출 추적:
func(3): print 3 → func(2) → print 3
  func(2): print 2 → func(1) → print 2
    func(1): print 1 → func(0) → print 1
      func(0): return

실행 순서: 3 → 2 → 1 → (return) → 1 → 2 → 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* ★ 피보나치 재귀 */
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}
// fib(5) = 5, fib(6) = 8, fib(7) = 13

/* ★ 팩토리얼 재귀 */
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n-1);
}
// factorial(5) = 120

/* ★ 하노이탑 */
void hanoi(int n, char from, char to, char via) {
    if (n == 1) {
        printf("%c -> %c\n", from, to);
        return;
    }
    hanoi(n-1, from, via, to);
    printf("%c -> %c\n", from, to);
    hanoi(n-1, via, to, from);
}
// n개 원반의 이동 횟수 = 2^n - 1

1.5 비트 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* ★ 비트 연산 — 출력 문제 빈출 */
#include <stdio.h>

int main() {
    int a = 12;  // 1100
    int b = 10;  // 1010

    printf("%d\n", a & b);   // 8  (1000) AND
    printf("%d\n", a | b);   // 14 (1110) OR
    printf("%d\n", a ^ b);   // 6  (0110) XOR
    printf("%d\n", ~a);      // -13 (보수)

    printf("%d\n", a << 2);  // 48 (110000) 좌측시프트 = ×4
    printf("%d\n", a >> 1);  // 6  (110) 우측시프트 = ÷2

    // ★ XOR 활용: 두 변수 값 교환 (임시변수 없이)
    a = a ^ b;   // a=0110
    b = a ^ b;   // b=1100 (=원래 a)
    a = a ^ b;   // a=1010 (=원래 b)

    return 0;
}

2. Java 핵심 패턴

2.1 클래스와 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* ★ 상속과 다형성 — 출력 추적 */
class Animal {
    String name = "Animal";
    void sound() { System.out.println("..."); }
}

class Dog extends Animal {
    String name = "Dog";
    @Override
    void sound() { System.out.println("Woof"); }
    void fetch() { System.out.println("Fetch!"); }
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();  // 업캐스팅

        System.out.println(a.name);  // "Animal" ★ 필드는 참조타입 기준!
        a.sound();                    // "Woof"   ★ 메서드는 실제객체 기준!
        // a.fetch();                 // 컴파일 에러! Animal에 fetch 없음

        Dog d = (Dog) a;              // 다운캐스팅
        System.out.println(d.name);   // "Dog"
        d.fetch();                    // "Fetch!"
    }
}
1
2
3
4
★ 핵심: Java 다형성
  - 필드(변수): 컴파일 타임(참조 타입) 기준 → Animal.name
  - 메서드: 런타임(실제 객체) 기준 → Dog.sound() ← 동적 바인딩
  - 이것이 시험에서 가장 자주 출제되는 Java 문제!

2.2 추상 클래스와 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* ★ 추상 클래스 */
abstract class Shape {
    abstract double area();  // 추상 메서드 (구현 없음)

    void display() {         // 일반 메서드 (구현 있음)
        System.out.println("Area: " + area());
    }
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }

    @Override
    double area() { return Math.PI * r * r; }
}

/* ★ 인터페이스 */
interface Flyable {
    void fly();  // 암묵적 public abstract
    default void land() {  // Java 8+ 디폴트 메서드
        System.out.println("Landing");
    }
}

interface Swimmable {
    void swim();
}

// 다중 구현 가능 (다중 상속 X, 다중 구현 O)
class Duck extends Animal implements Flyable, Swimmable {
    @Override public void fly() { System.out.println("Fly"); }
    @Override public void swim() { System.out.println("Swim"); }
}

2.3 컬렉션과 자주 나오는 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* ★ ArrayList, HashMap — 코드 작성 문제 */
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // ArrayList
        List<Integer> list = new ArrayList<>(Arrays.asList(5,3,1,4,2));
        Collections.sort(list);
        System.out.println(list);  // [1, 2, 3, 4, 5]

        // HashMap — 빈도수 세기 패턴
        String str = "banana";
        Map<Character, Integer> map = new HashMap<>();
        for (char c : str.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        System.out.println(map);  // {a=3, b=1, n=2}

        // Stack
        Stack<Integer> stack = new Stack<>();
        stack.push(1); stack.push(2); stack.push(3);
        System.out.println(stack.pop());   // 3 (LIFO)
        System.out.println(stack.peek());  // 2 (제거 안 함)

        // Queue
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1); queue.offer(2); queue.offer(3);
        System.out.println(queue.poll());  // 1 (FIFO)
    }
}

2.4 예외 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* ★ try-catch-finally 실행 순서 */
public class Main {
    static int test() {
        try {
            System.out.println("try");
            int result = 10 / 0;  // ArithmeticException
            return 1;
        } catch (ArithmeticException e) {
            System.out.println("catch");
            return 2;
        } finally {
            System.out.println("finally");  // 항상 실행!
            // return 3;  // ★ finally에 return 있으면 catch의 return 무시!
        }
    }

    public static void main(String[] args) {
        System.out.println(test());
    }
}
// 출력:
// try
// catch
// finally
// 2

// ★ finally에 return 3; 주석 해제 시 → 출력: 3

3. Python 핵심 패턴

3.1 리스트와 슬라이싱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ★ 리스트 슬라이싱 — 인덱스 정확히!
arr = [10, 20, 30, 40, 50]

print(arr[1:4])    # [20, 30, 40]  (인덱스 1~3)
print(arr[:3])     # [10, 20, 30]  (처음~2)
print(arr[2:])     # [30, 40, 50]  (인덱스 2~끝)
print(arr[-2:])    # [40, 50]      (뒤에서 2개)
print(arr[::2])    # [10, 30, 50]  (2칸씩)
print(arr[::-1])   # [50, 40, 30, 20, 10]  (역순)

# ★ 리스트 컴프리헨션
squares = [x**2 for x in range(1, 6)]
# [1, 4, 9, 16, 25]

evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]

matrix = [[i*3+j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]

3.2 딕셔너리와 집합

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ★ 딕셔너리 활용
d = {'a': 1, 'b': 2, 'c': 3}

print(d.get('x', 0))       # 0 (없으면 기본값)
print(list(d.keys()))       # ['a', 'b', 'c']
print(list(d.values()))     # [1, 2, 3]
print(list(d.items()))      # [('a',1), ('b',2), ('c',3)]

# 빈도수 세기
from collections import Counter
text = "banana"
cnt = Counter(text)
print(cnt)                  # Counter({'a': 3, 'n': 2, 'b': 1})
print(cnt.most_common(2))   # [('a', 3), ('n', 2)]

# ★ 집합 연산
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}
print(A | B)    # {1,2,3,4,5,6}  합집합
print(A & B)    # {3,4}          교집합
print(A - B)    # {1,2}          차집합
print(A ^ B)    # {1,2,5,6}      대칭차집합

3.3 함수와 람다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ★ 가변 인자
def add(*args):
    return sum(args)

print(add(1, 2, 3))     # 6
print(add(1, 2, 3, 4))  # 10

# ★ 키워드 인자
def info(**kwargs):
    for k, v in kwargs.items():
        print(f"{k}: {v}")

info(name="Kim", age=25)
# name: Kim
# age: 25

# ★ 람다와 고차 함수
nums = [3, 1, 4, 1, 5, 9]
print(sorted(nums))                      # [1, 1, 3, 4, 5, 9]
print(sorted(nums, reverse=True))        # [9, 5, 4, 3, 1, 1]

pairs = [(1,'b'), (2,'a'), (3,'c')]
print(sorted(pairs, key=lambda x: x[1])) # [(2,'a'), (1,'b'), (3,'c')]

print(list(map(lambda x: x**2, [1,2,3])))    # [1, 4, 9]
print(list(filter(lambda x: x>2, [1,2,3,4])))# [3, 4]

3.4 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ★ Python 상속과 오버라이딩
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return f"{self.name}: Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: Meow!"

# 다형성
animals = [Dog("Rex"), Cat("Kitty"), Dog("Buddy")]
for a in animals:
    print(a.speak())
# Rex: Woof!
# Kitty: Meow!
# Buddy: Woof!

# ★ 특수 메서드 (매직 메서드)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f"({self.x}, {self.y})"

p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2)  # (4, 6)

4. JavaScript 핵심 패턴

4.1 변수 스코프와 호이스팅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* ★ var vs let vs const */
console.log(a);  // undefined (var 호이스팅: 선언만 끌어올림)
// console.log(b);  // ReferenceError (let은 TDZ)
var a = 1;
let b = 2;
const c = 3;
// c = 4;  // TypeError (const 재할당 불가)

/* ★ 클로저 — 빈출 */
function outer() {
    let count = 0;
    return function() {
        return ++count;
    };
}
const counter = outer();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3
// count는 outer 함수가 끝나도 사라지지 않음 → 클로저

/* ★ var와 클로저 함정 */
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 100);
}
// 출력: 3 3 3 (var는 함수 스코프 → 루프 종료 후 i=3)

for (let j = 0; j < 3; j++) {
    setTimeout(function() {
        console.log(j);
    }, 100);
}
// 출력: 0 1 2 (let은 블록 스코프 → 각 반복마다 새 j)

4.2 this와 화살표 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* ★ this 바인딩 */
const obj = {
    name: "Kim",
    greet: function() {
        console.log(this.name);  // "Kim" (obj가 호출)
    },
    greetArrow: () => {
        console.log(this.name);  // undefined (화살표 함수는 상위 this)
    }
};

obj.greet();       // "Kim"
obj.greetArrow();  // undefined

/* ★ 배열 고차 함수 */
const nums = [1, 2, 3, 4, 5];

const doubled = nums.map(x => x * 2);        // [2,4,6,8,10]
const evens = nums.filter(x => x % 2 === 0); // [2,4]
const sum = nums.reduce((acc, x) => acc + x, 0);  // 15
const found = nums.find(x => x > 3);         // 4
const hasEven = nums.some(x => x % 2 === 0); // true
const allPos = nums.every(x => x > 0);       // true

4.3 프로미스와 async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* ★ Promise 체이닝 */
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000)
    .then(() => { console.log("1초"); return delay(1000); })
    .then(() => { console.log("2초"); })
    .catch(err => console.error(err));

/* ★ async/await */
async function process() {
    try {
        await delay(1000);
        console.log("완료");
        return "result";
    } catch (err) {
        console.error(err);
    }
}

Part 2. 주요 알고리즘 구현

5. 정렬 알고리즘

5.1 버블 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
# ★ 버블 정렬 — O(n²), 안정 정렬
def bubble_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        for j in range(n - 1 - i):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
    return arr

# [5,3,1,4,2] → 과정 추적:
# i=0: [3,1,4,2,5]  ← 5가 맨 뒤로
# i=1: [1,3,2,4,5]  ← 4 확정
# i=2: [1,2,3,4,5]  ← 3 확정

5.2 선택 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ★ 선택 정렬 — O(n²), 불안정 정렬
def selection_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

# [5,3,1,4,2] → 과정:
# i=0: min=1 → [1,3,5,4,2]
# i=1: min=2 → [1,2,5,4,3]
# i=2: min=3 → [1,2,3,4,5]

5.3 삽입 정렬

1
2
3
4
5
6
7
8
9
10
# ★ 삽입 정렬 — O(n²), 안정, 거의 정렬된 데이터에 효율적
def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

5.4 퀵 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ★ 퀵 정렬 — 평균 O(n log n), 최악 O(n²), 불안정
def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + middle + quick_sort(right)

# ★ 제자리(in-place) 퀵 정렬 — C 스타일 출제 빈출
def quick_sort_inplace(arr, low, high):
    if low < high:
        pivot = arr[high]
        i = low - 1
        for j in range(low, high):
            if arr[j] <= pivot:
                i += 1
                arr[i], arr[j] = arr[j], arr[i]
        arr[i+1], arr[high] = arr[high], arr[i+1]
        pi = i + 1

        quick_sort_inplace(arr, low, pi - 1)
        quick_sort_inplace(arr, pi + 1, high)

5.5 병합 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ★ 병합 정렬 — O(n log n), 안정, 추가 메모리 O(n)
def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])

    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
★ 정렬 알고리즘 비교표
┌──────────┬──────────┬──────────┬──────────┬────────┬────────┐
│ 알고리즘 │ 최선     │ 평균     │ 최악     │ 공간   │ 안정   │
├──────────┼──────────┼──────────┼──────────┼────────┼────────┤
│ 버블     │ O(n)     │ O(n²)    │ O(n²)    │ O(1)   │ O      │
│ 선택     │ O(n²)    │ O(n²)    │ O(n²)    │ O(1)   │ X      │
│ 삽입     │ O(n)     │ O(n²)    │ O(n²)    │ O(1)   │ O      │
│ 퀵       │ O(nlogn) │ O(nlogn) │ O(n²)    │ O(logn)│ X      │
│ 병합     │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(n)   │ O      │
│ 힙       │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(1)   │ X      │
│ 기수     │ O(d·n)   │ O(d·n)   │ O(d·n)   │ O(n)   │ O      │
│ 계수     │ O(n+k)   │ O(n+k)   │ O(n+k)   │ O(k)   │ O      │
└──────────┴──────────┴──────────┴──────────┴────────┴────────┘
★ 퀵정렬 최악: 이미 정렬된 데이터 + 첫/마지막 원소를 피봇으로
★ 안정 정렬: 동일 키의 상대적 순서 유지 (버블, 삽입, 병합, 기수, 계수)

6. 탐색과 그래프

6.1 이진 탐색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ★ 이진 탐색 — O(log n), 정렬된 배열 필수
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# 예) arr=[1,3,5,7,9,11], target=7
# left=0, right=5, mid=2 → arr[2]=5<7 → left=3
# left=3, right=5, mid=4 → arr[4]=9>7 → right=3
# left=3, right=3, mid=3 → arr[3]=7 → return 3

# ★ lower_bound: target 이상인 첫 위치
def lower_bound(arr, target):
    left, right = 0, len(arr)
    while left < right:
        mid = (left + right) // 2
        if arr[mid] < target:
            left = mid + 1
        else:
            right = mid
    return left

6.2 BFS / DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ★ BFS — 큐 사용, 최단 경로
from collections import deque

def bfs(graph, start):
    visited = set([start])
    queue = deque([start])
    result = []

    while queue:
        node = queue.popleft()
        result.append(node)
        for neighbor in sorted(graph[node]):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return result

# ★ DFS — 스택/재귀 사용
def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    result = [node]
    for neighbor in sorted(graph[node]):
        if neighbor not in visited:
            result.extend(dfs(graph, neighbor, visited))
    return result

# 예제 그래프
graph = {
    1: [2, 3],
    2: [1, 4, 5],
    3: [1, 6],
    4: [2],
    5: [2, 6],
    6: [3, 5]
}

print(bfs(graph, 1))  # [1, 2, 3, 4, 5, 6]
print(dfs(graph, 1))  # [1, 2, 4, 5, 6, 3]

6.3 다이나믹 프로그래밍 (DP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ★ DP 핵심 패턴 — 피보나치 (탑다운 vs 바텀업)

# 탑다운 (메모이제이션)
def fib_top_down(n, memo={}):
    if n <= 1:
        return n
    if n not in memo:
        memo[n] = fib_top_down(n-1) + fib_top_down(n-2)
    return memo[n]

# 바텀업 (타뷸레이션)
def fib_bottom_up(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

# ★ 0-1 배낭 문제
def knapsack(W, weights, values, n):
    dp = [[0] * (W + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for w in range(1, W + 1):
            if weights[i-1] <= w:
                dp[i][w] = max(
                    dp[i-1][w],
                    dp[i-1][w - weights[i-1]] + values[i-1]
                )
            else:
                dp[i][w] = dp[i-1][w]
    return dp[n][W]

# ★ 최장 공통 부분수열 (LCS)
def lcs(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[m][n]

# 예) lcs("ABCBDAB", "BDCAB") = 4 ("BCAB")

6.4 그리디 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ★ 활동 선택 문제 (Activity Selection)
def activity_selection(activities):
    # activities = [(시작, 종료), ...]
    activities.sort(key=lambda x: x[1])  # 종료 시간순 정렬
    result = [activities[0]]
    for i in range(1, len(activities)):
        if activities[i][0] >= result[-1][1]:
            result.append(activities[i])
    return result

# ★ 거스름돈 문제
def coin_change_greedy(amount, coins=[500, 100, 50, 10]):
    count = 0
    for coin in coins:
        count += amount // coin
        amount %= coin
    return count

# coin_change_greedy(1260) → 500×2 + 100×2 + 50×1 + 10×1 = 6개

Part 3. SQL 주관식 실전

7. SQL 기본 — SELECT 심화

7.1 GROUP BY와 HAVING

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- ★ 부서별 평균 급여가 300만원 이상인 부서
SELECT dept_id, AVG(salary) AS avg_sal, COUNT(*) AS cnt
FROM employees
GROUP BY dept_id
HAVING AVG(salary) >= 3000000
ORDER BY avg_sal DESC;

-- ★ WHERE vs HAVING
-- WHERE: 그룹화 전 필터 (개별 행)
-- HAVING: 그룹화 후 필터 (집계 결과)

-- 예) 입사년도 2020년 이후 직원 중, 부서별 인원이 3명 이상인 부서
SELECT dept_id, COUNT(*) AS cnt
FROM employees
WHERE hire_date >= '2020-01-01'    -- 개별 행 필터
GROUP BY dept_id
HAVING COUNT(*) >= 3               -- 그룹 필터
ORDER BY cnt DESC;

7.2 JOIN 총정리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- ★ INNER JOIN: 양쪽 모두 매칭되는 행만
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;

-- ★ LEFT JOIN: 왼쪽 테이블 전체 + 오른쪽 매칭 (없으면 NULL)
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;

-- ★ RIGHT JOIN: 오른쪽 테이블 전체 + 왼쪽 매칭
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;

-- ★ FULL OUTER JOIN (MySQL은 UNION으로 구현)
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
UNION
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;

-- ★ SELF JOIN: 같은 테이블끼리 조인
-- 예) 직원과 그 직원의 관리자 이름
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;

-- ★ CROSS JOIN: 카티션 곱 (M × N 행)
SELECT e.name, d.dept_name
FROM employees e
CROSS JOIN departments d;

7.3 서브쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- ★ 스칼라 서브쿼리 (SELECT 절)
SELECT name, salary,
       (SELECT AVG(salary) FROM employees) AS avg_all
FROM employees;

-- ★ 인라인 뷰 (FROM 절)
SELECT dept_id, avg_sal
FROM (
    SELECT dept_id, AVG(salary) AS avg_sal
    FROM employees
    GROUP BY dept_id
) AS dept_avg
WHERE avg_sal > 3000000;

-- ★ 상관 서브쿼리 (WHERE 절) — 행마다 실행
-- 자기 부서 평균보다 급여가 높은 직원
SELECT e.name, e.salary, e.dept_id
FROM employees e
WHERE e.salary > (
    SELECT AVG(e2.salary)
    FROM employees e2
    WHERE e2.dept_id = e.dept_id
);

-- ★ EXISTS / NOT EXISTS
-- 주문 이력이 있는 고객만
SELECT c.name
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

-- ★ IN / NOT IN
-- 서울 지점 직원들
SELECT name FROM employees
WHERE dept_id IN (
    SELECT dept_id FROM departments WHERE location = '서울'
);

8. SQL 고급 — 윈도우 함수

8.1 순위 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- ★ ROW_NUMBER, RANK, DENSE_RANK 차이
SELECT name, salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
    RANK()       OVER (ORDER BY salary DESC) AS rnk,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rnk
FROM employees;

-- 결과 예시:
-- name    salary  row_num  rnk  dense_rnk
-- Kim     5000    1        1    1
-- Lee     5000    2        1    1         ← 동점
-- Park    4000    3        3    2         ← RANK는 3, DENSE_RANK는 2
-- Choi    3000    4        4    3

-- ★ 부서별 급여 1등
SELECT *
FROM (
    SELECT name, dept_id, salary,
           ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
    FROM employees
) ranked
WHERE rn = 1;

8.2 집계 윈도우 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- ★ 누적합
SELECT name, salary,
    SUM(salary) OVER (ORDER BY hire_date) AS cumulative_sum
FROM employees;

-- ★ 이동 평균 (최근 3명)
SELECT name, salary,
    AVG(salary) OVER (
        ORDER BY hire_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg
FROM employees;

-- ★ 부서별 합계 대비 비율
SELECT name, dept_id, salary,
    salary * 100.0 / SUM(salary) OVER (PARTITION BY dept_id) AS pct
FROM employees;

8.3 LAG / LEAD

1
2
3
4
5
6
7
8
9
10
-- ★ 전월 대비 매출 증감
SELECT month, sales,
    LAG(sales, 1) OVER (ORDER BY month) AS prev_sales,
    sales - LAG(sales, 1) OVER (ORDER BY month) AS diff
FROM monthly_sales;

-- ★ 다음 행과 비교
SELECT name, salary,
    LEAD(salary, 1) OVER (ORDER BY salary DESC) AS next_lower
FROM employees;

9. SQL 실전 — DML과 DDL

9.1 데이터 조작 (DML)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- ★ INSERT
INSERT INTO employees (name, dept_id, salary, hire_date)
VALUES ('Park', 10, 3500000, '2026-04-01');

-- 다중 행 삽입
INSERT INTO employees (name, dept_id, salary)
VALUES ('A', 10, 3000000),
       ('B', 20, 3500000),
       ('C', 10, 4000000);

-- ★ UPDATE (WHERE 필수!)
UPDATE employees
SET salary = salary * 1.1
WHERE dept_id = 10;

-- ★ DELETE (WHERE 필수!)
DELETE FROM employees
WHERE hire_date < '2020-01-01';

-- ★ MERGE (Oracle) / INSERT ... ON DUPLICATE KEY UPDATE (MySQL)
-- Oracle MERGE:
MERGE INTO target t
USING source s ON (t.id = s.id)
WHEN MATCHED THEN
    UPDATE SET t.val = s.val
WHEN NOT MATCHED THEN
    INSERT (id, val) VALUES (s.id, s.val);

-- MySQL:
INSERT INTO employees (emp_id, name, salary)
VALUES (1, 'Kim', 5000000)
ON DUPLICATE KEY UPDATE salary = 5000000;

9.2 테이블 정의 (DDL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- ★ CREATE TABLE
CREATE TABLE employees (
    emp_id    INT          PRIMARY KEY AUTO_INCREMENT,  -- MySQL
    -- emp_id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, -- Oracle
    name      VARCHAR(50)  NOT NULL,
    dept_id   INT,
    salary    DECIMAL(12,2) DEFAULT 0,
    hire_date DATE         DEFAULT CURRENT_DATE,
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
        ON DELETE SET NULL
        ON UPDATE CASCADE
);

-- ★ ALTER TABLE
ALTER TABLE employees ADD COLUMN email VARCHAR(100);
ALTER TABLE employees MODIFY COLUMN name VARCHAR(100) NOT NULL;
ALTER TABLE employees DROP COLUMN email;
ALTER TABLE employees ADD CONSTRAINT uk_name UNIQUE (name);

-- ★ 인덱스
CREATE INDEX idx_dept ON employees(dept_id);
CREATE UNIQUE INDEX idx_email ON employees(email);
DROP INDEX idx_dept ON employees;

-- ★ 뷰
CREATE VIEW emp_summary AS
SELECT dept_id, COUNT(*) AS cnt, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id;

SELECT * FROM emp_summary WHERE avg_sal > 3000000;

9.3 트랜잭션과 권한

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- ★ 트랜잭션 제어 (TCL)
START TRANSACTION;  -- BEGIN (Oracle)
UPDATE accounts SET balance = balance - 10000 WHERE id = 1;
UPDATE accounts SET balance = balance + 10000 WHERE id = 2;
COMMIT;
-- ROLLBACK;  -- 오류 시 되돌리기

-- ★ SAVEPOINT
START TRANSACTION;
INSERT INTO orders VALUES (1, 'A', 1000);
SAVEPOINT sp1;
INSERT INTO orders VALUES (2, 'B', 2000);
ROLLBACK TO sp1;   -- 두 번째 INSERT만 취소
COMMIT;             -- 첫 번째 INSERT만 반영

-- ★ 권한 (DCL)
GRANT SELECT, INSERT ON employees TO user1;
GRANT ALL PRIVILEGES ON *.* TO admin WITH GRANT OPTION;
REVOKE INSERT ON employees FROM user1;

10. 실전 모의 문제

문제 1: C 코드 출력 (포인터 + 반복)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main() {
    int arr[] = {2, 4, 6, 8, 10};
    int *p = arr + 1;
    int sum = 0;

    for (int i = 0; i < 3; i++) {
        sum += *(p + i);
    }
    printf("%d\n", sum);

    p = arr;
    while (*p < 8) {
        *p += 1;
        p++;
    }
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
풀이:
1) p = arr+1 → p는 arr[1]=4를 가리킴
   *(p+0)=4, *(p+1)=6, *(p+2)=8 → sum=18
   출력: 18

2) p = arr, *p=2<8 → *p=3, p++
   *p=4<8 → *p=5, p++
   *p=6<8 → *p=7, p++
   *p=8, 8<8 거짓 → 종료
   arr = {3, 5, 7, 8, 10}
   출력: 3 5 7 8 10

문제 2: Java 코드 출력 (상속 + 생성자)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Parent {
    int x = 10;
    Parent() { System.out.println("Parent: " + x); }
    Parent(int x) { this.x = x; System.out.println("Parent(" + x + ")"); }
}

class Child extends Parent {
    int y = 20;
    Child() {
        super(30);
        System.out.println("Child: " + x + ", " + y);
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        System.out.println(c.x + ", " + c.y);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
풀이:
1) new Child() → Child 생성자 호출
2) super(30) → Parent(int x) 호출 → this.x = 30
   출력: Parent(30)
3) Child 생성자 본문 실행, y=20
   출력: Child: 30, 20
4) main에서 출력
   출력: 30, 20

전체 출력:
Parent(30)
Child: 30, 20
30, 20

문제 3: SQL 작성 문제

1
2
3
4
5
6
테이블 구조:
employees(emp_id, name, dept_id, salary, hire_date)
departments(dept_id, dept_name, location)

[문제] 각 부서에서 급여가 가장 높은 직원의 이름, 부서명, 급여를
       급여 내림차순으로 조회하시오. 단, 직원이 없는 부서는 제외.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 풀이 1: 서브쿼리 + JOIN
SELECT e.name, d.dept_name, e.salary
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary = (
    SELECT MAX(e2.salary)
    FROM employees e2
    WHERE e2.dept_id = e.dept_id
)
ORDER BY e.salary DESC;

-- 풀이 2: 윈도우 함수 (더 간결)
SELECT name, dept_name, salary
FROM (
    SELECT e.name, d.dept_name, e.salary,
           ROW_NUMBER() OVER (PARTITION BY e.dept_id ORDER BY e.salary DESC) AS rn
    FROM employees e
    INNER JOIN departments d ON e.dept_id = d.dept_id
) ranked
WHERE rn = 1
ORDER BY salary DESC;

문제 4: Python 코드 출력

1
2
3
4
5
6
7
8
9
def mystery(n):
    if n <= 1:
        return str(n)
    if n % 2 == 0:
        return mystery(n // 2) + "0"
    else:
        return mystery(n // 2) + "1"

print(mystery(13))
1
2
3
4
5
6
7
8
9
10
풀이: 이 함수는 10진수를 2진수로 변환한다.
mystery(13): 13%2==1 → mystery(6) + "1"
  mystery(6): 6%2==0 → mystery(3) + "0"
    mystery(3): 3%2==1 → mystery(1) + "1"
      mystery(1): return "1"
    → "1" + "1" = "11"
  → "11" + "0" = "110"
→ "110" + "1" = "1101"

출력: 1101 (13의 이진수)

문제 5: SQL 실전 — 복합 쿼리

1
2
3
4
5
6
[문제] 2025년 주문 금액 상위 3개 고객의 이름, 총주문금액, 주문건수를 조회하시오.
      동점 시 모두 포함하시오.

테이블:
customers(customer_id, name, email)
orders(order_id, customer_id, amount, order_date)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- RANK 사용 (동점 포함)
SELECT name, total_amount, order_count
FROM (
    SELECT c.name,
           SUM(o.amount) AS total_amount,
           COUNT(*) AS order_count,
           RANK() OVER (ORDER BY SUM(o.amount) DESC) AS rnk
    FROM customers c
    INNER JOIN orders o ON c.customer_id = o.customer_id
    WHERE o.order_date >= '2025-01-01'
      AND o.order_date < '2026-01-01'
    GROUP BY c.customer_id, c.name
) ranked
WHERE rnk <= 3
ORDER BY total_amount DESC;

-- ★ 주의: ROW_NUMBER를 쓰면 동점이 잘리므로 RANK 사용!

최종 체크리스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌──────────────────────────────────────────────────────────────────┐
│ ★★★ 코딩 주관식 핵심 체크리스트 ★★★                              │
│                                                                  │
│ [코드 출력 문제]                                                  │
│ □ 포인터 역참조 (*p, **pp, *(p+i)) 정확히 추적                  │
│ □ 재귀 호출 스택 그려가며 출력 순서 확인                         │
│ □ Java 상속: 필드=참조타입, 메서드=실제객체                      │
│ □ JavaScript var vs let 스코프 차이                              │
│ □ 비트 연산 결과 2진수로 계산                                    │
│ □ 생성자 호출 순서: 부모 → 자식                                  │
│                                                                  │
│ [코드 작성 문제]                                                  │
│ □ 정렬: 버블/선택/삽입/퀵 직접 구현 가능                        │
│ □ 탐색: 이진탐색 경계 조건 (left<=right vs left<right)          │
│ □ BFS/DFS: 인접리스트/행렬 모두 구현 가능                       │
│ □ DP: 점화식 세우기 → 탑다운/바텀업 선택                        │
│ □ 그리디: 정렬 기준 결정 → 최적 선택 반복                       │
│                                                                  │
│ [SQL 작성 문제]                                                   │
│ □ JOIN 유형 정확히 선택 (INNER/LEFT/RIGHT)                       │
│ □ GROUP BY + HAVING 조합                                         │
│ □ 서브쿼리: 상관/비상관 구분                                     │
│ □ 윈도우 함수: ROW_NUMBER vs RANK vs DENSE_RANK                 │
│ □ NULL 처리: IS NULL, COALESCE, NVL                              │
│ □ 날짜 범위: >= AND < (BETWEEN 주의)                             │
│                                                                  │
│ [MySQL vs Oracle 차이]                                            │
│ ┌──────────────────┬──────────────────┬────────────────────────┐ │
│ │ 기능             │ MySQL            │ Oracle                 │ │
│ ├──────────────────┼──────────────────┼────────────────────────┤ │
│ │ 자동증가         │ AUTO_INCREMENT   │ SEQUENCE / IDENTITY    │ │
│ │ 문자열 연결      │ CONCAT()         │ || 연산자              │ │
│ │ NULL 대체        │ IFNULL()         │ NVL()                  │ │
│ │ 현재 날짜        │ NOW() / CURDATE()│ SYSDATE                │ │
│ │ 행 제한          │ LIMIT n          │ ROWNUM / FETCH FIRST   │ │
│ │ 조건문           │ IF() / CASE      │ DECODE() / CASE        │ │
│ │ 문자열 자르기    │ SUBSTRING()      │ SUBSTR()               │ │
│ │ FULL OUTER JOIN  │ UNION으로 구현   │ FULL OUTER JOIN 지원   │ │
│ └──────────────────┴──────────────────┴────────────────────────┘ │
│                                                                  │
│ ★ 시험장에서 레퍼런스 활용 가능하므로 문법 외우기보다            │
│   "어떤 함수가 있는지"와 "접근법"을 아는 것이 핵심!             │
└──────────────────────────────────────────────────────────────────┘

이전 글: 컴퓨터공학 전공 핵심정리 상편: 운영체제, 자료구조, 데이터베이스중편: 소프트웨어공학, 데이터통신, 네트워크에서 객관식 35문항을 대비한다.