본문 바로가기
☁️ Algorithm

[알고리즘] 조합(Combination) (Java)

by hyeon-z 2023. 3. 21.

 

조합이란?

 

n개의 숫자 중에서 r개의 수를 순서 없이 뽑는 경우를 말한다.

 

예를 들어 3개의 숫자(1, 2, 3)에서 2개의 수를 순서 없이 뽑는 경우는 이와 같다.

[1, 2]
[1, 3]
[2, 3]

 

조합의 기본 원리

 

조합은 기호로 nCr로 표기한다.

또한 해당 식으로 표현이 가능하다.

nCr = n-1Cr-1 + n-1Cr

즉 조합은 하나의 원소를 선택한 경우 + 하나의 원소를 선택하지 않을 경우, 이 둘의 합으로 나타낸다.

 

예를 들어 (1,2,3)의 수에서 2개의 수를 선택하는 조합을 생각해 보자.

  • 1을 선택하는 경우 (하나의 원소를 선택한 경우)
  • 1을 선택하지 않는 경우 (하나의 원소를 선택하지 않을 경우)

 

1을 선택하는 경우 

- (1, 2), (1, 3)

1을 선택했으므로 (2, 3) 두 개의 수 중에서 1개의 수를 뽑아야 하므로 2C1, 즉 n-1Cr-1이 된다.

 

1을 선택하지 않는 경우

- (2, 3)

1을 선택하지 않았으므로 (2, 3) 두 개의 수 중에서 2개의 수를 뽑아야 하므로 2C2, 즉 n-1Cr이 된다.

 

해당 식을 바탕으로 결국 더 이상 쪼개지지 않을 때까지 즉, nC0 = 1이 될 때까지 구한다면 nCr을 구할 수 있게 된다!!

 

조합의 경우의 수 구하기

 

nCr에서 n과 r이 같은 경우, 해당 숫자를 모두 뽑는 경우만 존재하므로 경우의 수는 1가지이다.

nCr에서 r이 0인 경우, 숫자를 더 이상 뽑지 않는 선택지만 존재하므로 경우의 수는 1가지이다.

그 외의 경우에는 위에서 다뤘던 nCr = n-1Cr-1 + n-1Cr의 식을 통해요 재귀로 호출한다.

 

public static void main(String[] args) {
    System.out.println(combination(3, 2)); // 3개 중에서 2개를 뽑는 조합의 경우의 수
}

public static int combination(int n, int r) {
    if (n == r || r == 0)
        return 1;
    else
        return combination(n - 1, r - 1) + combination(n - 1, r);
}

 

조합 구하기

 

조합을 구하는 방법은 배열의 처음부터 마지막까지 돌며

 

-  현재 인덱스를 선택하는 경우

-  현재 인덱스를 선택하지 않는 경우

 

이렇게 두 가지의 경우를 완전 탐색해야 한다.

 

해당 조합을 구하는 구현 방법은 총 2가지가 존재한다.

1. 백 트래킹

2. 재귀

 

백트래킹을 이용한 구현

먼저 구현을 위해 필요한 요소들을 살펴보자.

  • arr: 조합을 뽑아낼 배열
  • n: 배열의 길이
  • r: 뽑아야 하는 수
  • visited: 해당 수가 뽑혔는지 확인하기 위한 배열
public class Comb1 {
    static int[] arr;
    static boolean[] visited;
    static int n, r;

    public static void main(String[] args) {
        arr = new int[]{1, 2, 3};
        n = arr.length;
        r = 2;
        visited = new boolean[n];

        combination(0, r);
    }
 }

 

이제 combination 함수를 살펴보자.

public static void combination(int start, int r) {
    if (r == 0) {
        print();
    } else {
        for (int i = start; i < n; i++) {
            visited[i] = true;
            combination(i + 1, r - 1);
            visited[i] = false;
        }
    }
}

 

start와 r, 2가지 매개변수가 있다.

  • start: 배열의 시작 index
  • r: 뽑을 숫자의 개수

예를 들어, 3C2를 구한다고 가정해 보자.

arr = {1, 2, 3};
r = 2;

 

동작 과정

 

1. 처음 start와 r은 (0, 2)으로 시작한다.

2. 1을 방문처리한 후 start를 +1, r을 -1 한다.

3. 2를 방문처리한 후 start를 +1, r을 -1 한다.

4. r이 0과 같아져서 해당 조합을 출력한다. => (1, 2)

5. 2의 방문처리를 해제한다.

6. 3을 방문처리한 후 start를 +1, r을 -1 한다.

7. r이 0과 같아져서 해당 조합을 출력한다. => (1, 3)

8. 3의 방문처리를 해제한다.

9. 1의 방문처리를 해제한다.

10. 2를 방문처리한 후 start를 +1, r을 -1 한다.

11. 3을 방문처리한 후 start를 +1, r을 -1 한다.

12. r이 0과 같아져서 해당 조합을 출력한다. => (2, 3) 

13. 3의 방문처리를 해제한다.

14. 2의 방문처리를 해제한다.

 

해당 과정을 통해 (1,2), (1,3), (2,3)을 출력하게 된다.

 

그림을 통해 살펴보면 조금 더 쉽게 이해할 수 있다.

 

전체 코드

 

public class Comb1 {
    static int[] arr;
    static boolean[] visited;
    static int n, r;

    public static void main(String[] args) {
        arr = new int[]{1, 2, 3};
        n = arr.length;
        r = 2;
        visited = new boolean[n];

        combination(0, r);
    }

    public static void combination(int start, int r) {
        if (r == 0) {
            print();
        } else {
            for (int i = start; i < n; i++) {
                visited[i] = true;
                combination(i + 1, r - 1);
                visited[i] = false;
            }
        }
    }

    public static void print() {
        for (int i = 0; i < arr.length; i++) {
            if (visited[i]) {
                System.out.print(arr[i] + " ");
            }
        }
        System.out.println();
    }
}

 

r을 depth로 바꾸어 구현이 가능하다.

이때 depth는 뽑은 숫자의 개수이다.

public class Comb1 {
    static int[] arr;
    static boolean[] visited;
    static int n, r;

    public static void main(String[] args) {
        arr = new int[]{1, 2, 3};
        n = arr.length;
        r = 2;
        visited = new boolean[n];

        combination(0, 0);
    }

    public static void combination(int start, int depth) {
        if (depth == r) {
            print();
        } else {
            for (int i = start; i < n; i++) {
                visited[i] = true;
                combination(i + 1, depth + 1);
                visited[i] = false;
            }
        }
    }

    public static void print() {
        for (int i = 0; i < arr.length; i++) {
            if (visited[i]) {
                System.out.print(arr[i] + " ");
            }
        }
        System.out.println();
    }
}

 

재귀를 이용한 구현

백트래킹 방법과 달리 이번 구현에서는 depth로 이전에 탐색한 값인지 판단한다.

 

public static void combination(int depth, int r) {
    if (r == 0) {
        print();
        return;
    }
    if (depth != n) {
        visited[depth] = true;
        combination(depth + 1, r - 1);
        visited[depth] = false;
        combination(depth + 1, r);
    }
}

 

depth와 r, 2가지 매개변수가 있다.

  • depth: 현재 index
  • r: 뽑을 숫자의 개수

depth가 index의 범위를 벗어나지 않는 동안 재귀함수를 수행한다.

  • 해당 숫자를 뽑은 경우: combination(depth + 1, r - 1)
  • 해당 숫자를 뽑지 못한 경우: combination(depth + 1, r)

두 경우 모두 해당 숫자를 확인했으므로 depth를 1 증가시킨다.

 

r이 0이 되는 경우는 모든 숫자가 뽑힌 경우이므로 해당 조합을 출력한 뒤 재귀를 종료한다.

또한 depth가 n이 되면 모든 인덱스를 모두 확인했으므로 재귀를 종료한다.

 

그림을 살펴보면 조금 더 쉽게 이해할 수 있다.

 

전체 코드

 

public class Comb2 {
    static int[] arr;
    static boolean[] visited;
    static int n, r;

    public static void main(String[] args) {
        arr = new int[]{1, 2, 3};
        n = arr.length;
        r = 2;
        visited = new boolean[n];

        combination(0, r);
    }

    public static void combination(int depth, int r) {
        if (r == 0) {
            print();
            return;
        }
        if (depth != n) {
            visited[depth] = true;
            combination(depth + 1, r - 1);
            visited[depth] = false;
            combination(depth + 1, r);
        }
    }


    public static void print() {
        for (int i = 0; i < arr.length; i++) {
            if (visited[i]) {
                System.out.print(arr[i] + " ");
            }
        }
        System.out.println();
    }
}

 

 

 

Reference

https://minhamina.tistory.com/38

 

'☁️ Algorithm' 카테고리의 다른 글

트리(Tree) (Java)  (0) 2023.05.03
이진 탐색(Binary Search) (Java)  (0) 2023.05.03
[알고리즘] BFS (Java)  (0) 2023.03.15
[알고리즘] DFS (Java)  (0) 2023.03.14
[알고리즘] Graph  (0) 2023.03.10

댓글