computer_science/algorithm

[알고리즘] 04. 선택구조

렁치 2026. 8. 1. 13:00

순차구조만으로는 "조건에 따라 다르게" 행동할 수 없다. 합격/불합격을 가르거나, 짝수/홀수를 판별하려면 갈림길이 필요하다. 이 갈림길이 선택구조다. 수업에서 Raptor로 푼 예제들을 단순 분기 → 중첩 분기 → 다중 분기 순으로 정리했다.


1. 선택구조란?

선택구조의 세 요소
  • 참·거짓을 가르는 조건식
  • 조건이 참(Yes)일 때 실행할 명령 블록
  • 조건이 거짓(No)일 때 실행할 명령 블록

 

Raptor에서는 마름모에 조건식을 쓰고, 참이면 왼쪽(Yes), 거짓이면 오른쪽(No) 경로로 분기한다. 짝수/홀수 판별을 순서도로 그리면 이렇다.

 

 

조건식에 쓰는 연산자는 두 종류다.

종류 연산자
관계 = !=(/=) < <= > >= 3 < 4 → Yes
논리 and or xor not (3<4) and (10<20) → Yes

 

여러 조건을 조합할 땐 관계 연산자가 먼저 평가되고 논리 연산자가 나중에 평가된다(우선순위: 함수 → 괄호 → 거듭제곱 → 곱·나눗셈 → 덧·뺄셈 → 관계 → not → and → xor → or). 헷갈리면 괄호로 묶어 의도를 분명히 한다.


2. 중첩 선택 (예제 4-7: 이차방정식 판별)

조건 안에 또 조건을 두면 셋 이상으로 갈래를 나눌 수 있다. 이차방정식 ax²+bx+c=0의 판별식 d=b²-4ac 부호로 근의 종류를 가르는 예제가 중첩 선택의 전형이다.

GET a, b, c
d ← b^2 - 4 * a * c
IF d > 0
    out ← "Different real roots"     // 서로 다른 두 실근
ELSE
    IF d < 0
        out ← "Imaginary roots"      // 허근
    ELSE
        out ← "Multiple root"        // 중근 (d = 0)
PUT out

 

d>0이 아니면(거짓 경로) 그 안에서 다시 d<0인지 따져 허근/중근을 가른다. "큰 갈래를 먼저 나누고, 그 안에서 더 잘게 나눈다"는 게 중첩 선택의 사고법이었다. 직접 그려보니 마름모 안에 마름모가 들어가는 트리 모양이 됐다.


3. 다중 분기 사다리 (예제 4-8: 학점 판정)

같은 변수를 여러 구간으로 나눌 땐 IF ~ ELSE IF 사다리를 쓴다. 중간·기말 평균으로 A~F 학점을 매기는 예제다.

 

예제 4-8: 학점 판정
GET x, y
avg ← (x + y) / 2
IF avg >= 90       grade ← "A"
ELSE IF avg >= 80  grade ← "B"
ELSE IF avg >= 70  grade ← "C"
ELSE IF avg >= 60  grade ← "D"
ELSE               grade ← "F"
PUT grade           // 98, 96 → 평균 97 → A

 

여기서 순서가 핵심이다. avg>=70을 맨 위에 두면 97점도 거기서 먼저 걸려 C가 되어버린다. 큰(좁은) 조건부터 위에 두고, 한 번 걸리면 아래는 검사하지 않는다는 점을 직접 점수를 바꿔가며 확인했다.


4. 다중 분기 + 복합 조건 (예제 4-10: 급여 계산)

등급별 시급에 초과근무 가산까지 붙는 예제다. 다중 분기(등급)와 단일 분기(초과근무)가 한 문제에 섞여 있다.

GET grade, hours
IF grade = 1       cost ← 12000
ELSE IF grade = 2  cost ← 8000
ELSE               cost ← 6000
IF hours > 50                          // 50시간 초과분은 1.5배
    overtime ← hours - 50
    pay ← cost * 50 + cost * overtime * 1.5
ELSE
    pay ← cost * hours
PUT "Pay="+pay      // 2등급 60시간 → 520000

 

"등급으로 시급을 정하고, 근무시간으로 가산을 정한다"는 두 단계의 분기가 차례로 이어진다. 조건을 한 번에 다 처리하려 하기보다 단계로 쪼개니 흐름이 깔끔했다.


5. 직접 풀어볼 유제

  • 유제 4-2: 입력 x가 1·2·3이면 각각 인사말, 그 외엔 "Try Again" (다중 분기)
  • 유제 4-3: 구입 수량별 할인율(10~19개 20%, … 100개↑ 50%) 적용해 금액 계산 (구간 분기)
  • 유제 4-7: 정수가 양수/음수/0이면 PLUS/MINUS/ZERO (중첩 선택)
  • 유제 4-8: 구입액 구간별 할인(20만 미만 0% … 80만↑ 30%)으로 지불액 계산
  • 유제 4-9: 네 수 중 최솟값 찾기 (세 수 비교를 한 단계 확장)
  • 유제 4-10: 한우 등급·무게로 가격 계산 + 총액 구간별 추가 할인 (등급 분기 + 금액 구간 분기)

특히 유제 4-3·4-8은 "구간을 나누는 사다리"라, 학점 예제(4-8)의 순서 규칙을 그대로 적용하면 풀린다.


6. C 코드로 풀이 흐름 정리

선택구조는 조건을 한 줄로 압축하기보다, 먼저 계산할 값을 만들고 그 값을 기준으로 분기하면 디버깅이 쉽다.

 

예제 4-7: 이차방정식 판별

Raptor로 그리면 판별식 계산 뒤 마름모가 두 번 나온다. 먼저 d > 0을 판단하고, 거짓일 때만 다시 d < 0을 판단한다.

int iA;
int iB;
int iC;
int iDiscriminant;

printf("a b c 입력: ");
scanf("%d %d %d", &iA, &iB, &iC);

iDiscriminant = iB * iB - 4 * iA * iC;

if (iDiscriminant > 0)
{
    printf("서로 다른 두 실근\n");
}
else if (iDiscriminant < 0)
{
    printf("허근\n");
}
else
{
    printf("중근\n");
}

 

입력 a,b,c 판별식 출력
1, -3, 2 1 서로 다른 두 실근
1, 2, 1 0 중근
1, 1, 1 -3 허근

 

유제: 구입 수량별 할인

구간 분기는 마름모가 사다리처럼 이어진다. 한 조건에서 Yes가 나오면 할인율을 정하고, No일 때만 다음 조건으로 내려간다.

int iCount;
int iUnitPrice;
int iTotalPrice;
int iDiscountRate;
int iPayPrice;

printf("수량과 단가 입력: ");
scanf("%d %d", &iCount, &iUnitPrice);

iTotalPrice = iCount * iUnitPrice;
iDiscountRate = 0;

if (iCount >= 100)
{
    iDiscountRate = 50;
}
else if (iCount >= 50)
{
    iDiscountRate = 30;
}
else if (iCount >= 10)
{
    iDiscountRate = 20;
}

iPayPrice = iTotalPrice - iTotalPrice * iDiscountRate / 100;

printf("할인율: %d%%\n", iDiscountRate);
printf("지불액: %d\n", iPayPrice);

 

구간 분기는 큰 조건부터 내려오면 겹침을 줄이기 쉽다. 100개 이상, 50개 이상, 10개 이상 순서로 검사하면 한 입력이 정확히 한 구간에 걸린다.


오늘 느낀 점

  • 선택구조의 핵심은 문법이 아니라 "조건을 어떤 순서로 세우느냐"였다. 학점 예제에서 순서를 뒤집어 97점이 C로 나오는 걸 직접 보고 확실히 박혔다.
  • 이차방정식 판별처럼 "큰 갈래를 먼저, 그 안에서 잘게" 나누는 중첩 선택이 트리 모양으로 그려지는 게 흥미로웠다. 분기를 단계로 쪼개니 복잡한 문제도 풀렸다.
  • 급여 예제처럼 다중 분기와 단일 분기가 섞여도, 한 번에 처리하려 하지 말고 단계로 끊으니 흐름이 깔끔해졌다.

한 걸음 더

  • IF~ELSE IF 사다리에서 조건 순서가 결과를 가른다. 구간을 나눌 땐 "넓은 범위부터 좁혀 내려갈지, 좁은 범위부터 넓혀 올라갈지" 한 방향으로 일관되게 배치해야 빠짐·겹침이 없다. 모든 입력이 정확히 하나의 가지로 가는지 점검하는 습관이 버그를 줄인다.
  • 패스워드 비교(유제 4-5)처럼 "1234"(문자열)와 1234(숫자)를 구분해야 하는 경우가 있다. 앞자리 0이나 비교 방식 때문에 자료형을 잘못 잡으면 같은 값인데도 다르다고 판정될 수 있다. "무엇을 어떤 타입으로 비교하는가"를 늘 의식하자.
  • 중첩 선택이 깊어지면 읽기 어려워진다. 그래서 실제 코드에서는 깊은 중첩 대신, 조건을 논리 연산자로 묶거나(a and b) 이른 반환(early return)으로 평평하게 펴는 기법을 쓴다. Raptor의 트리형 분기를 코드로 옮길 때 한 번쯤 고민해볼 지점이다.