오늘도 더 나은 코드를 작성하였습니까?

Algorithm(알고리즘) 기본 수학(조합, 순열, 소수 등) 정리 feat. kotlin 본문

Algorithm/개념

Algorithm(알고리즘) 기본 수학(조합, 순열, 소수 등) 정리 feat. kotlin

hik14 2023. 8. 16. 18:50

1.  조합(★★★★★)

정의 - 서로 다른 n개의 원소가 모인 집합에서 r개로 이루어진 집합을 뽑는 경우의 수

 

컴퓨터의 데이터를 다룰때 기본적으로 자료구조(배열, 리스트...등) 데이터를 담아서 조합을 이용한다.

즉, 그래서 index를 선택함으로 구현한다. 

완전탐색 문제에서 자주 사용된다.

* n C r =  n C n-r 

 

구현

r이 3이하인 경우 반복문으로 구현.

fun main() {

    val arr = Array(4){ it }
    
    // n = arr.size r = 3 일때 
    for (i in 0 until arr.size){
        for (j in i+1 until arr.size){
            for (k in j+1 until arr.size){
                println("i:$i j:$j k:$k")
            }
        }
    }
}

r이 4이상인 경우 재귀로 구현.

val combinationList = mutableListOf('a', 'b', 'c', 'd')
var combinationTotalCnt = 0

// 조합의 결과 뽑은 index를 담을 배열.
val resultList: MutableList<Int> = mutableListOf()
fun combination(n: Int, r: Int, start: Int = 0) {

    if (resultList.size == r) {
        /*
            logic

        * */
        combinationTotalCnt += 1
        println(resultList.joinToString(" "))
        return
    }

    for (i in start until n) {
        // 뽑고 -> 다음 idx 부터 뽑도록 start param i+1로 증가 
        resultList.add(i)
        combination(n, r, start = i + 1)
        // 원복.
        resultList.removeLast()
    }
}

fun main() {
    combination(combinationList.size, 3)
    println("combinationTotalCnt: ${combinationTotalCnt}")
}

 

2.  순열(★★★★☆)

정의 -  서로 다른 n 개 중 r 개를 골라 순서를 고려해 나열한 경우의 수.

 

완전탐색문제에서 자주 사용되지만, 상대적으로 조합보다 덜 사용된다.

조합과 달리 순서가 변경되면 다른 경우의 수로 취급을 하여 뽑아야되기 때문에, "뽑고 다음것을 뽑고 원복"  하는 조합 코드와는 다르다.

조합은 다음 재귀호출에서 이전에 뽑은 idx에 +1 부터 반복을 시작하여 중복을 선택하지 않으면서, 순서도 고려하지 않고 뽑을 수 있었지만, 순열은 순서를 고려해야되기 때문에 항상 매 재귀마다 idx = 0 부터 뽑되, 중복 선택을 막기 위한 체크를 위한 배열이 필요하다.

var isSelect = BooleanArray(permutationArr.size) { false }

구현

val permutationArr = arrayOf(
    'a', 'b', 'c', 'd', 'e'
)

var isSelect = BooleanArray(permutationArr.size) { false }

// result 항상 index 담아둔다.
val permutationResult = mutableListOf<Int>()
var permutationTotalCnt = 0

fun permutation(n: Int, r: Int) {

    // 카운트 숫자를 매개변수로 주어 뽑으려는 수까지 cnt를 세게되면 메서드 종료
    if (permutationResult.size == r) {
        /*
            logic
         */
        permutationTotalCnt += 1
        println(permutationResult.joinToString(" "))
        return
    }

    //매번 재귀 마다 반복문 시작이 0부터 시작함.

    for (i in 0 until n) {
        // 중복 선택을 방지한다.
        if (isSelect[i]) continue

        permutationResult.add(i)
        isSelect[i] = true

        permutation(n = n, r = r)

        permutationResult.removeLast()
        isSelect[i] = false
    }
}

fun main() {
    permutation(n= permutationArr.size, r= 4)
    println("PermutationTotalCnt: $permutationTotalCnt")
}

 

3.  소수(★★★☆☆)

정의 - 1과 자기 자신만으로 나누어 떨어지는 1보다 큰 양의 정수

 

자주 출제되지는 않지만, 출제되어 효율성을 묻는 문제라면, 특히, 에라스토테네스의 체를 사용해야된다면, 효율성을 통과하지 못할 가능성이 높다. 코드가 간단하기에 외우도록 하자.

/*
 *  정의에 충실한 단순 구현 
 *  가장 단순하고 비효율적인 방법
 *  시간 복잡도 O(N)
 */
 
fun isPrimeV1(n: Int): Boolean {
    if (n == 1) return false
    if (n == 2) return true

    for (i in 2 until n) {
        if (n % i == 0) return false
    }
    return true
}

효율성 개선

import kotlin.math.sqrt

/*
 *  곱의 절반인 sqrt(n.toDouble()).toInt()까지만 반복.
 *  시간 복잡도 O(logN)
 */
 
fun isPrimeV2(n: Int): Boolean {
    if (n == 1) return false
    if (n == 2) return true

    val end = sqrt(n.toDouble()).toInt()

    for (i in 2..end) {
        if (n % i == 0) return false
    }
    return true
}

 

에라토스 테네스의 체

 

/*
 * 특정 정수 n까지 모든 정수에 대한 소수를 미리 판별해 놓고,
 * 여러번 오는 쿼리에 대해서 빠르게 처리한다.
 * O(1)
 *
 */
 
val primes = mutableListOf<Int>()
fun eratos(n: Int) {

    /* idx = 해당 수를 의미하고
    * arr[idx] = 1 이면 소수
    * arr[idx] = 0 이면 소수가 아님을 뜻한다.
    * 초기화 - 모든 값을 1로 초기화 한다.
    * 0, 1은 소수가 아니다.
    */
    val arr = IntArray(n + 1) { 1 }
    arr[0] = 1
    arr[1] = 0

    // 2 .. n 까지 반복
    for (i in 2..n) {
        // 이미 0이면 건너뜀.
        if (arr[i] == 0) continue
        // 자신을 제외한 배수를 arr[idx] = 0 로 만들어 준다.
        for (j in i*2 ..n step i){
           arr[j] = 0
        }
    }

    // 소수로 판명된 것들은 리스트에 담음.
    for (i in 2..n) {
        if (arr[i] != 0) {
            primes.add(i)
        }
    }
}

fun main() {
    eratos(10000)
    println(primes.joinToString(" "))
}

 

4.  최대 공약수, 최소 공배수(★★☆☆☆)

정의

최소 공배수  -  두개의 정수 a, b의  공통 배수 중에서 최소인 것

최대 공약수 -   두개의 정수 a, b의  공통 약수 중에서 최대인 것

 

/*
    gcd - 최대 공약수

    두 수 a, b 있고 a > b다면, val r = a / b
    a와 b의 최대 공약수는 b와 r의 공약수와 같다.

    lcm - 최소 공배수
    
    (a * b) / 최대 공약수
 */

fun gcd(num1: Int, num2: Int): Int {

    var (a, b) =
        if (num1 > num2) {
            num1 to num2
        } else {
            num2 to num1
        }

    while (b != 0) {
        val r = a % b
        a = b
        b = r
    }
    return a
}

fun lcm(a: Int, b: Int): Int {

    return a * b / gcd(a, b)
}

fun main() {
    println(gcd(200, 160))
    println(lcm(200,160))
}