연속부분합 찾기

  • sliding window 방식을 이용한 연속부분합
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const slidingWindow = (arr) => {
    let N = arr.length;
    let answer = Number.MIN_SAFE_INTEGER;
    /* 부분합을 구할 배열의 길이를 늘려가며 해답을 찾기
    최소값은 1이고 최대값은 배열의 길이만큼 */
    for (let len = 1; len <= N; len++) {
    let sum = 0
    /* scanf로 값을 불러오는 C++ 코드와는 달리
    배열을 받아 처리하는 함수로 작성하였기 때문에
    i = 0부터 시작해서 i+1값이 length값보다 커지면
    앞부분을 잘라내도록 변경 */
    for (let i = 0; i < N; i++) {
    // i값이 len 보다 길어지면 현재 벗어난 i-len 인덱스 부분의 값을 빼줌
    if (i+1 > len) sum -= arr[i-len]
    // 슬라이딩하여 추가된 값을 더해줌
    sum += arr[i]
    /* i+1 >= len, 즉, 해당 부분 배열의 길이가 같고
    부분합이 기존의 answer보다 크다면 갱신 */
    if (i+1 >= len && answer < sum) answer = sum;
    }
    }
    return answer
    }
  • prefix sum 방식으로 찾기
    → prefix를 사용하기 위해서는 0번째 원소(누산용 초기값 0)가 필요하다
    → 처음에 배열을 그대로 썼을 때 arr의 N번째값이 없어서 preSum의 마지막 값으로 NaN값이 출력됨
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const prefixSum = arr => {
    let answer = Number.MIN_SAFE_INTEGER;
    arr = [0, ...arr];
    let preSum = Array.from({length: arr.length + 1}, () => 0)
    // prefix를 사용하기 위해서는 0번째 원소(누산용 초기값 0)가 필요하다
    for (let i = 1; i <= N; ++i) {
    preSum[i] = preSum[i-1] + arr[i]
    }
    // 처음에 배열을 그대로 썼을 때 arr의 N번째값이 없어서 preSum의 마지막 값으로 NaN값이 출력됨
    for (let length = 1; length <= N; length++) {
    for (let i = length; i <= N; i++) {
    if (preSum[i] - preSum[i-length] > answer)
    answer = preSum[i] - preSum[i - length]
    }
    }
    return answer;
    }
  • dynamic programming 방식으로 찾기
    • D[i]를 i번째 수를 마지막으로 하는 연속 부분합의 최대값으로 정의
    • 연속 부분합이므로 A[i]를 새로운 부분합의 시작으로 하지 않는 한 D[i]값은 D[i-1]을 이용할 수 밖에 없다
    • D[i-1] <= 0이라면 D[i-1]값을 고려할 필요가 없다
    • D[i] = max(A[i], D[i-1] + A[i])
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      const dynamicSum = arr => {
      let answer = Number.MIN_SAFE_INTEGER;
      let N = arr.length
      arr = [0, ...arr] // 마찬가지로 0번째에 누산용 0값을 입력
      D = Array.from({length: N}, () => 0)
      for (let i = 1; i <= N; i++) {
      // D[i-1] 값이 음수라면 더해도 최대값이 될 수 없다
      D[i - 1] > 0 ? D[i] = D[i - 1] + arr[i] : D[i] = arr[i]

      if (D[i] > answer) answer = D[i]
      }
      return answer
      }
  • 메모이제이션으로 배열을 사용하지 않고 해결하기(Kadane’s Algorithm)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const dynamicSum = arr => {
    let answer = Number.MIN_SAFE_INTEGER;
    let N = arr.length
    arr = [0, ...arr]
    let sum = 0;
    for (let i = 1; i <= N; i++) {
    // D[i-1] 값이 음수라면 더해도 최대값이 될 수 없다
    sum > 0 ? sum += arr[i] : sum = arr[i]
    if (sum > answer) answer = sum
    }
    return answer
    }
  • 배우면서 느꼈던 것
    → 상황에 따라 배열의 인덱스 값을 고려하여 코딩해야 한다는 점을 기억하자
    → 0을 맨 앞에 추가했다면 1번째 원소부터 고려해야 한다

회전 초밥 문제

  • 손님이 k개의 접시를 연속해서 먹을 경우 할인된 가격으로 제공
  • 위 행사에 참여하면 초밥을 선택할 수 있는 쿠폰을 발행
  • 회전 초밥 음식점의 벨트 상태 N, 메뉴에 있는 초밥의 가짓수 d, 연속해서 먹는 접시의 개수 k, 쿠폰 번호 c 가 주어짐
  • 이 때, 손님이 먹을 수 있는 최대 가짓수를 구하는 문제
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
// 접시의 수(N), 초밥 가짓수(d), 연속해서 먹는 접시 수(k), 쿠폰번호(c)
// 초밥이 순서대로 담긴 배열(list)
const sushi = (N, d, k, c, list) => {
let chk = Array.from({length: d+1}, () => 0)
chk[c]++; // 쿠폰으로 먹을 수 있는 초밥을 미리 있다고 가정
let cnt = 1;

// 첫 k개의 접시는 먹은 것으로 간주하고 시작함
for (let i = 0; i < k; i++)
if (chk[list[i]]++ === 0)
cnt++

let answer = cnt;

for (let i = k; i < N + k; i++) {
// list의 i-k 번째의 값에 -1 했을 때 0이라면 이미 먹은 접시이므로 빼준다
if(--chk[list[i - k]] === 0) cnt--;
// 회전하는 list이기 때문에 초과하는 값은 다시 돌아와야 하므로 i를 N으로 나눠준 나머지값으로 계산
// chk 배열 내 list[i%N] 번째 값이 0이라면 +1로 더해주고 카운트 값도 +1함
if(chk[list[i%N]]++ === 0) cnt++
// 이 때 카운트 값이 현재의 최대값보다 크다면 갱신한다
if(cnt > answer) answer = cnt;
}
return answer;
}

Comment and share

이진검색

  • 정렬된 리스트에서 특정한 값의 위치를 찾는 알고리즘
  • 전화번호부 반으로 찢어 찾기 (from CS50)
  • True/False 결과로 최적해를 탐색하는 방법
  1. 탐색 배열의 가운데 인덱스를 구함
  2. 만약 가운데 값이 해당 값과 같다면 찾은 것
  3. 주어지는 배열은 정렬되어 있으므로 가운데 값이 해당 값보다 작다면 우측 반을 검색범위로 지정 (mid+1, high)
  4. 반대의 경우라면 (low, mid+1)로 검색범위를 지정
  5. 위 과정을 반복하여 값을 찾음 (단, low > high가 된다면 해당 값이 배열에 없는 경우)

while문을 사용한 이진검색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const binarySearch = (
arr = arr.sort((a,b) => a - b), low = 0, high = arr.length, target
) => {
while(low <= high) {
// 가운데 인덱스 구하기
let mid = Math.floor((low + high)/2);

// 가운데 값이 해당값과 같다면 찾은 것
if (arr[mid] === target) return mid;

// 해당 값보다 작다면
if (arr[mid] < target) low = mid + 1

// 해당 값보다 크다면
if (arr[mid] > target) high = mid - 1
}
// 반복하는 동안 low > high가 된다면 해당 값이 배열에 없으므로 -1을 리턴
return -1
}

재귀를 사용한 이진검색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const binarySearchRecursive = (
arr = arr.sort((a,b) => a - b), low = 0, high = arr.length, target
) => {
// 찾지 못한 경우
if (low > high) return -1

// mid값 구하기
let mid = Math.floor((low + high)/2);

// 가운데 값이 해당 값이라면 return
if (arr[mid] === target) return mid

// 해당 값 보다 작거나 크다면 함수를 재귀 호출
if (arr[mid] < target) return binarySearchRecursive(arr, mid+1, high, target)
return binarySearchRecursive(arr, low, mid-1, target)
}

True/False 결과로 최적해를 탐색하는 방법

  • 구슬의 개수 N과 그룹의 수 M이 주어질 때 임의의 그룹 내 요소들의 최대값이 최소가 되게 하는 문제
    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    const check = mid => {
    let group = 1, sum = 0;
    for (let i = 0; i < N; ++i) {
    // 첫번째 구슬부터 더하여 mid값보다 커지면 그룹화하고 새 그룹으로 넘어감
    if (sum + arr[i] > mid) {
    // mid가 10이라 가정하고 1, 2번째 값의 합이 8이 되고
    // arr[2]값이 4라면 sum + arr[2] = 12가 되어 8보다 커지므로
    // sum값을 arr[2]값으로 초기화하고 그룹 수를 늘린다
    sum = arr[i];
    group++;
    } else {
    // 만약 합이 최대값을 넘기지 않는다면 더해주어도 괜찮음
    sum += arr[i]
    }
    }
    return group <= M
    }

    let low = 0, high = 0;
    let N = 8, M = 3
    let arr = [5, 4, 2, 6, 9, 3, 8, 7]
    const input = () => {
    console.log(`${N}개의 구슬을 ${M}개의 그룹으로 나누어야 합니다`)
    for (let i = 0; i < N; ++i){
    // console.log(arr[i])
    // 각 구슬 중에 최소값을 구함
    if (low < arr[i]) low = arr[i];
    // 구슬의 총합을 최대값으로 구함
    high += arr[i];
    }
    }

    const output = () => {
    console.log(answer);
    let count = 0, sum = 0, limit = M; // limit: 그룹 수 제한
    for (let i = 0; i <= N; ++i) {
    // 구한 해답이 arr[i]번째까지의 합보다 크거나
    // 한 그룹당 하나 이상의 구슬이 있어야 하므로 N - M < i 보다 작아야 한다
    if (sum + arr[i] > answer || N - i < limit) {
    console.log(`그룹${i+1}:${count}`) // 그룹 수를 출력
    count = sum = 0; // 초기화
    limit--; // 남은 그룹 수 줄이기
    }
    sum += arr[i];
    count++;
    }
    }

    const process = () => {
    while (low <= high) {
    let mid = Math.floor((low + high) / 2);
    if (check(mid)) {
    answer = mid;
    high = mid - 1;
    }
    // group <= M 가 false를 리턴 할 때 (그룹 수가 M을 넘어갈 때)
    else low = mid + 1
    }
    }

    const solution = () => {
    input();
    process();
    output();
    }

    solution();
  • 배우면서 이해가기 어려웠던 점
    • mid값은 index값이 아닌가? 왜 mid를 최대값으로 설정하여 check를 할까?
      → mid값은 최소값 + 최대값의 중간값
      → check 함수는 group <= M의 boolean 값을 리턴하므로 이 함수가 True값을 리턴할 때만 구슬의 합을 계산하면 된다

책 복사하기

  • 주어진 대본들의 페이지(m[])를 k명의 서기공이 한 파트 이상 복사할 때 가장 많은 페이지를 맡은 서기공의 페이지를 구하는 문제
  • 답이 두 개 이상 있다면 첫 번째 서기공이 할 일이 가장 작아야 하며 두 번째 서기공이 할 일이 작아야 한다
  • 입력값이 100,000으로 커서 순차 탐색보다 이진 탐색을 사용해야 한다
    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
    48
    49
    50
    51
    52
    53
    54
    let arr = [100, 200, 300, 400, 500, 600, 700, 800, 900];
    let M = arr.length, K = 3;
    let low = 0, high = 0;

    const input = () => {
    console.log(`${M}개로 나누어진 대본을 ${K}명의 서기공이 나누어 일을 해야 합니다`);
    for (let i = 0; i < M; ++i) {
    // 숫자 구슬과 유사함
    if (low < arr[i]) low = arr[i];
    high += arr[i];
    }
    }

    const check = mid => {
    let group = 1, sum = 0;
    for (let i = 0; i < M; ++i) {
    if (sum + arr[i] > mid) {
    sum = arr[i];
    group++;
    } else sum += arr[i];
    }
    return group <= K
    }

    const process = () => {
    while (low <= high) {
    let mid = Math.floor((low + high)/2);
    if (check(mid)) {
    answer = mid;
    high = mid - 1
    } else low = mid + 1
    }
    }

    const output = (index, sum, people) => {
    if (index < 0) return;
    if (sum + arr[index] > low || index < people) {
    output(index - 1, arr[index], people - 1);
    console.log(`${arr[index]} / `)
    } else {
    output(index -1, sum + arr[index], people);
    console.log(`${arr[index]} `)
    }
    }

    const solution = () => {
    input();
    process();
    output(M - 1, 0, K - 1);
    // 배열의 마지막 번째(M-1)값부터 탐색해 들어가면 재귀를 통해 첫번째 index부터 실행된다
    // 사람이 K명일때는 "/"가 K-1개 들어가야 하므로 K-1번만 수행한다
    }

    solution();
  • 숫자 구슬 문제를 이해하고 나면 생각보다 이해하기 쉬웠음
  • 재귀적으로 호출될 때 마지막부터 탐색한다면 LIFO 콜스택에 의해 가장 먼저 출력되는 것은 0번째 index일 것이다 (탈출구문은 0보다 작을 때로 작성)

Comment and share

자바스크립트 알고리즘 문제풀이(01 ~ 22번)

  1. 세 수 중 최소값
    1
    2
    3
    4
    5
    6
    7
    const minimum_number = (a, b, c) => {
    let answer;
    if(a < b) answer = a;
    else answer = b;
    if (c < answer) answer = c;
    return answer
    }
  2. 삼각형 판별하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const isTriangle = (a, b, c) => {
    // 제일 긴 변을 제외한 두 변의 길이의 합이 제일 긴 변 보다 커야함
    let answer = "YES", max;
    let sum = a + b + c;
    if(a > b) max = a;
    else max = b;
    if (c > max) max = c;

    if ((sum-max) <= max) return answer = "NO"
    else return answer
    }
  3. 연필 수 구하기
    1
    2
    3
    const pencil_number = n => {
    return Math.ceil(n/12)
    }
  4. 1부터 N까지의 합
    1
    2
    3
    4
    5
    6
    7
    const sum = (n) => {
    let answer = 0;
    for (let i = 1; i <= n; i++){
    answer += i;
    }
    return answer
    }
  5. 최소값 구하기
    1
    2
    3
    const minVal = arr => {
    return Math.min(...arr);
    }
  6. 수 더하기: filter함수와 reduce함수를 사용하여 구하기
    1
    2
    3
    const oddSum = arr => {
    return arr.filter(n => n%2 != 0).reduce((acc, cur) => acc+cur, 0)
    }
  7. 10부제
    1
    2
    3
    const carSelect = (day, arr) => {
    return arr.filter(carNum => carNum%10 === day).length
    }
  8. 일곱난쟁이
    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
    // 단순 정렬
    const sortHeight = arr => {
    let height = [...arr];
    for (let i = 0; i < arr.length; i++) {
    for (let j = i+1; j < arr.length; j++){
    let temp = 0;
    if (height[i] > height[j]) {
    temp = height[j]
    height[j] = height[i]
    height[i] = temp
    }
    }
    }
    return height.slice(0,7);
    }

    // 합병정렬 하기
    const sortHeight = arr => {
    // 1. 탈출구문을 만들기 (arr을 더이상 쪼갤 수 없을 때 탈출)
    if(arr.length === 1) return arr;

    // 2. 배열을 둘 로 쪼개기 Divide
    let mid = Math.floor(arr.length/2);
    let leftside = arr. splice(0, mid);

    // 3. 각각 합병하기 Conqure
    return merge(sortHeight(leftside), sortHeight(arr)).slice(0,7)
    // 문제는 키가 작은 일곱명을 선출하는 것
    }

    function merge(left, right) {
    // 정리용 빈 배열을 만들기
    let result = [];
    // 각 배열에서 원소를 꺼낼 것이기 때문에 두 배열이 전부 빈 배열이 되면 끝나도록 while문으로 반복
    while (left.length !== 0 && right.length !== 0) {
    // 두 배열의 첫번째 인자를 비교하여 오른쪽이 더 크거나 같다면 작은 수인 왼쪽 배열의 수를 꺼내 정리용 배열에 푸쉬
    left[0] <= right[0] ? result.push(left.shift()) : result.push(right.shift());
    }
    // spreading operator로 result 배열과 나머지 배열 값들을 담아 리턴
    return [...result, ...left, ...right];
    }


    // ES2015 문법을 사용하여 정렬하여 잘라내기
    const sortHeight2 = arr => {
    return arr.sort((a,b) => a-b).slice(0,7)
    }
  9. A를 #으로 변경하기 (문자열 다루기)
    1
    2
    3
    4
    5
    const changeChar = str => {
    return str.split('')
    .map(char => (char === "A") ? char = "#" : char )
    .join("")
    }
  10. 문자 찾기
    1
    2
    3
    const searchChar = (str, char) => {
    return str.split("").filter(each => each === char).length;
    }
  11. 대문자 찾기
    1
    2
    3
    4
    const searchCaptial = str => {
    return str.split("")
    .filter(each => each === each.toUpperCase()).length
    }
  12. 대문자로 통일하기
    1
    2
    3
    const changeCapital = str => {
    return str.split("").map(el => el.toUpperCase()).join("")
    }
  13. 가장 긴 문자열 찾기
    1
    2
    3
    const  searchLongestStr = strArr => {
    return strArr.sort((a,b) => b.length - a.length)[0]
    }
  14. 가운데 문자열 출력하기
    1
    2
    3
    4
    5
    6
    const middleChar = str => {
    let len = str.length;
    return (len%2 === 0)
    ? str.split("").slice((len/2)-1, Math.floor(len/2)+1).join("")
    : str.split("").slice((len/2), Math.floor(len/2)+1)[0]
    }
  15. 중복된 문자 제거: Set으로 중복을 제거하고 spread operator를 사용
    1
    2
    3
    4
    const deduplication = str => {
    let split = str.split('');
    return [...new Set(split)].join('')
    }
  16. 배열 내 중복된 단어 제거
    1
    2
    3
    const deduplicationArr = arr => {
    return [...new Set(arr)]
    }
  17. 큰 수 출력하기
    1
    2
    3
    const largerThen = (n, arr) => {
    return arr.filter(el => el > n)
    }
  18. 보이는 학생
    1
    2
    3
    4
    5
    6
    7
    8
    const visibleStudent = arr => {
    let standard = arr[0];
    let count = 1; // 첫번째 사람은 무조건 보이므로 1로 시작
    for(let student of arr) {
    if (student > standard) count++
    }
    return count;
    }
  19. 가위 바위 보
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const rsp = (n, A, B) => {
    let winner = [];
    for (let i = 0; i < n; i++) {
    if (A[i] === 1 && B[i] === 3) {
    winner.push("A")
    } else if (A[i] === 2 && B[i] === 1) {
    winner.push("A")
    } else if (A[i] === 3 && B[i] === 2) {
    winner.push("A")
    } else {
    winner.push("B")
    }
    }
    return winner;
    }
  20. 점수 계산
    1
    2
    3
    4
    5
    6
    7
    const scoring = (ox, score) => {
    let sum = 0;
    for (let i = 0; i < score.length; i++){
    ox[i] === 1 ? sum += score[i] : sum
    }
    return sum;
    }
  21. 등 수 구하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const lank = (n, scores) => {
    let lank = Array.from({length:n}, () => 1)
    for(let i = 0; i< n; i++){
    for(let j = 0; j < n; j++){
    if (scores[j] > scores[i]) lank[i]++;
    }
    }
    return lank;
    }

    // 처음에는 내림차순으로 정렬해서 index값을 구하고자 했는데 같은 값이 들어오면 같은 등수로 처리해야 한다는 것을 간과함
  22. 격자판 내 가장 큰 합 구하기
    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
    let gridArr = [
    [10, 13, 10, 12, 15],
    [12, 39, 30, 23, 11],
    [11, 25, 50, 53, 15],
    [19, 27, 29, 37, 27],
    [19, 13, 30, 13, 10]
    ]

    const maxSumInGrid = gridArr => {
    let rowSum = gridArr.map(row => row.reduce((acc, cur) => acc+cur, 0))
    let culSum = [];
    for (let i = 0; i < gridArr.length; i++) {
    let sum = 0;
    for (let j = 0; j < gridArr.length; j++){
    sum += gridArr[j][i];
    }
    culSum.push(sum);
    }
    let downSum = upSum = 0
    for (let i = 0; i < gridArr.length; i++){
    downSum += gridArr[i][i];
    upSum += gridArr[i][gridArr.length-i-1]
    }
    let crossSum = [downSum, upSum]

    return Math.max(...rowSum, ...culSum, ...crossSum)
    }

    기초 문제 풀며 느낀 점

  • 이차원 배열을 다룰 때 map과 reduce를 어떻게 하면 잘 사용할 수 있을지 공부하기
  • if/else문을 사용하여 분기점을 여러개 만드는 걸 꺼리고 있는데 brute-force방식으로도 문제를 풀어보기

Comment and share

해밀턴 경로 문제

  • G = (V, E)가 연결된 무방향 그래프일 때 한 정점에서 출발하여 그래프의 모든 정점을 한 번씩만 방문하고 출발점으로 되돌아오는 경로
  • 모든 간선을 한 번씩 방문하는 오일러 경로와 비슷

해밀턴 경로를 백트랙킹 방식으로 해결하기

  • 상태공간트리의 구축
    → 트리의 레벨 0에서 출발 정점을 선정(경로의 0번째 정점)
    → 트리의 레벨 1에서 출발 정점을 제외한 각 정점을 출발 정점 다음에 올 첫째 정점으로 고려함
    → 트리의 레벨 2에서는 앞에서와 같은 정점들을 두번째 정점으로 고려함
    → 이후 레벨도 같은 방식으로 고려함
    → 마지막으로 수준 n - 1에서 정점들을 n - 1째 정점으로 고려함
    → 마지막 정점에서 출발 정점으로 돌아오는 경로가 있는지 고려함
1
2
3
4
5
6
7
8
9
def hamiltonian (i , W, vindex):
n = len(W) - 1
if (promising(i, W, vindex)):
if (i == (n - 1)):
print(vindex[0:n])
else:
for j in range(2, n + 1):
vindex[i + 1] = j
hamiltonian(i + 1, W, vindex)
  • 인접 행렬 W[i][j]: i번째 정점과 j번째 정점간에 간선이 있으면 True, 없으면 False
  • promising을 위한 유망 함수의 조건
    1. n-1번째 정점은 출발 정점과 반드시 인접해있어야 한다 (마지막엔 돌아와야 하므로)
    2. 경로상의 i번째 정점은 그 경로에서 i-1번째 정점과 반드시 인접해야 한다 (다음 노드로 고려하기 위해)
    3. i번째 정점은 그 앞에 오는 i-1개의 정점들 중 하나가 될 수 없다 (한번 방문한 노드는 한 번만 방문해야 하므로 되돌아 갈 수 없다)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def promising (i, W, vindex):
flag = True
# n-1번째 정점은 출발 정점과 반드시 인접해있어야 한다
if ((i == (n - 1)) and not W[vindex[n-1]][vindex[0]]):
flag = False
# 경로상의 i번째 정점은 그 경로에서 i-1번째 정점과 반드시 인접해야 한다
elif ((i > 0) and not W[vindex[i-1]][vindex[i]]):
flag = False
# i번째 정점은 그 앞에 오는 i-1개의 정점들 중 하나가 될 수 없다
else:
j = 1
while (j < i and flag):
if (vindex[i] == vindex[j]):
flag = False
j += 1
return flag

외판원 문제 (The Traveling Salesperson Problem, TSP)

  • 외판원은 출발 도시에서 모든 도시를 방문하고 다시 되돌아와야 한다
  • 출장 시간을 최소로 줄이기 위한 방문 계획을 구하는 문제
  • 가중치가 있는 방향 그래프라고 가정했을 때, 완전 그래프라면 (n-1)! 시간 복잡도를 가지므로 log(n)보다 나쁘다

Dynamic programming으로 외판원 문제를 해결하기

  • W: 주어진 그래프 G = (V, E)의 인접 행렬
  • V: 모든 도시의 집합
  • A: V의 부분집합
  • D[vi][A]: A에 속한 도시를 각각 한 번씩만 거쳐서 vi에서 v1로 가는 최단 경로의 길이 // 모든 경로의 집합 중에 최적해
  • 재귀적 관계식을 찾기
    • D[vi][A] = min(W[i][j] + D[vj][A - {vj}]), A ≠ ∅
    • D[vi][∅] = W[i][1], A = ∅
  • 비트를 이용한 부분 집합의 표현과 연산
    • V - {v1} = {v2, v3, v4}
    • A = { v4, v2 } = { 1, 0, 1 } = 5 와 같이 통과하는 정점을 1로, 통과하지 않는 정점을 0으로 표현
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # vi가 A에 속해있을 때
      def isIn(i, A):
      if((A & (1 << (i - 2)) != 0):
      return True
      else:
      return False
      # A - {vj} : 차집합을 구하는 비트 연산
      def diff (A, j):
      t = 1 << (j - 2)
      return (A & (~t))
      # A의 원소가 k개인가?
      def count (A, n):
      count = 0
      for i in range(n):
      if((A & (1 << i)) != 0):
      count += 1
      return count
  • *파이썬으로 TSP 문제를 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
    def travel (W):
    n = len(W) - 1
    size = 2 ** (n -1)
    D = [[0] * size for _ in range(n + 1)]
    P = [[0] * size for _ in range(n + 1)]
    for i in range(1, n + 1):
    D[i][0] = W[i][1]
    for k in range(1, n - 1):
    for A in range(1, size):
    if (count(A, n) == k):
    for i in range(2, n + 1):
    if(not isIn(i, A)):
    D[i][A], P[i][A] = minimum(W, D, i, A)
    A = size - 1
    D[1][A], P[1][A] = minimum(W, D, 1, A)
    return D, P

    def minimum(W, D, i, A):
    minValue = INF
    minJ = 1
    n = len(W) - 1
    for j in range(2, n + 1):
    if (isIn(j, A)):
    m = W[i][j] + D[j][diff(A, j)]
    if (minValue > m):
    minValue = m
    minJ = j
    return minValue, minJ

    외판원 문제를 분기 한정법으로 해결하기

  • 상태공간트리의 최적우선탐색
    → 각 노드에서 한계값을 구하고 특정 노드에서 얻을 수 있는 경로 길이의 하한을 구해야 함
    → 경로 길이의 하한이 현재의 최소 경로 길이보다 작은 경우 유망함
  • 하한값의 정의
    → 각 여행 경로는 정점을 정확히 한 번씩만 거쳐야 한다
    → 정점을 떠나는 간선 가중치 최소값들의 합
  • 분기한정 가지치기 최고우선탐색
    한계값리프 노드가 아닌 노드에서만 계산하고 여행 경로의 길이리프 노드에서 계산한다

파이썬으로 TSP를 Branch-and-Bound 방식의 BFS로 해결하기

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class SSTNode:

def __init__ (self, level):
self.level = level
self.path = []
self.bound = 0

def contains(self, x):
for i in range(len(self.path)):
if (self.path[i] == x):
return True
return False

def travel2 (W):
global minlength, opttour
n = len(W) - 1
PQ = PriorityQueue()
v = SSTNode(0)
v.path = [1]
v.bound = bound(v, W)
minlength = INF
PQ.put((v.bound, v))
while (not PQ.empty()):
v = PQ.get()[1]
if (v.bound < minlength):
for i in range(2, n + 1):
if (v.contains(i)):
continue
u = SSTNode(v.level + 1)
u.path = v.path[:]
u.path.append(i)
if (u.level == n - 2):
for k in range(2, n + 1):
if (not u.contains(k)):
u.path.append(k)
u.path.append(1)
if (length(u.path, W) < minlength):
minlength = length(u.path, W)
opttour = u.path[:]
else:
u.bound = bound(u, W)
if (u.bound < minlength):
PQ.put((u.bound, u))

def bound(v, W):
n = len(W) - 1
total = length(v.path, W)
for i in range(1, n + 1):
if hasOutgoing(i, v.path):
continue
min = INF
for j in range(1, n + 1):
if i == j: # 셀프 노드
continue
if hasIncoming(j, v.path):
continue
if j == 1 and i == v.path[len(v.path) - 1]:
continue
if min > W[i][j]:
min = W[i][j]
total += min
return total

def length(path, W):
total = 0
prev = 1
for i in range(len(path)):
if i != 0:
prev = path[i - 1]
total += W[prev][path[i]]
prev = path[i]
return total

def hasOutgoing(v, path):
for i in range(0, len(path) - 1):
if path[i] == v:
return True
return False

def hasIncoming(v, path):
for i in range(1, len(path)):
if path[i] == v:
return True
return False

Comment and share

분기한정법과 0-1 배낭 문제

  • 깊이우선탐색(DFS)와 너비우선탐색(BFS)
  • 분기한정법(Branch-and-Bound) : BFS 기반 상태공간트리로 문제를 해결
  • 항상 한계값을 고려하므로 최적화 문제를 해결하기 좋음
  • 최적우선탐색(Best-First-Search w/ Branch-and-Bound)

BFS로 배낭문제를 해결하기

1
2
3
4
5
6
7
8
9
10
11
12
13
# 슈도 코드
def breadth_first_search(tree):
queue_of_node Q
node u, v
initialize(Q)
v = root of tree
visit v
enqueue(Q, v)
while(!empty(Q)):
dequeue(Q, v)
for (each child u of v):
visit u
enqueue(Q, u)
  • 분기한정법으로 0-1 배낭 문제를 해결하려면?
    • 일반적으로 BFS가 DFS보다 나쁨
    • 유망 함수 외에 추가적인 용도로 한계값(bound)를 사용
    • 주어진 어떤 노드의 모든 자식 노드를 방문하고
      → 유망하면서 확장하지 않은 노드들을 모두 살펴보고
      → 한계값이 가장 좋은 노드를 우선적으로 확장
    • 지금까지 찾은 최적해보다 한계값이 더 좋을 때 유망함

파이썬에서 우선순위 큐 쓰는 법 예시

1
2
3
4
5
6
7
8
from queue import PriorityQueue

PQ = PriorityQueue()
data = [4, 1, 3, 2]
for i in range(len(data)):
PQ.put(data[i])
while(not PQ.empty()):
print(PQ.get())

파이썬으로 분기한정법 구현하기

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from queue import PriorityQueue


class SSTNode:

def __init__(self, level, profit, weight):
self.level = level
self.profit = profit
self.weight = weight
self.bound = 0

def print(self):
print(self.level, self.profit, self.weight, self.bound)


def bound(u, p, w):
n = len(p) - 1
if u.weight >= W:
return 0
else:
result = u.profit
j = u.level + 1
totweight = u.weight
while j <= n and totweight + w[j] <= W:
totweight += w[j]
result += p[j]
j += 1
k = j
if k <= n:
result += (W - totweight) * p[k] / w[k]
return result


def knapsack4(p, w, W):
PQ = PriorityQueue()
v = SSTNode(0, 0, 0)
maxprofit = 0
v.bound = bound(v, p, w)
PQ.put((-v.bound, v))
while not PQ.empty():
v = PQ.get()[1]
print("POP:", end="")
v.print()
if v.bound > maxprofit:
level = v.level + 1
weight = v.weight + w[level]
profit = v.profit + p[level]
u = SSTNode(level, profit, weight)
if u.weight <= W and u.profit > maxprofit:
maxprofit = u.profit
u.bound = bound(u, p, w)
print("\tPUT:", end="")
u.print()
if u.bound > maxprofit:
PQ.put((-u.bound, u))
print("\t\tYES")
u = SSTNode(level, v.profit, v.weight)
u.bound = bound(u, p, w)
print("\tPUT:", end="")
u.print()
if u.bound > maxprofit:
PQ.put((-u.bound, u))
print("\t\tYES")
return maxprofit

Comment and share

배낭 문제와 탐욕 알고리즘

  • 배낭 문제 (Knapsack Problem)

    • 도둑이 30kg까지 담을 수 있는 배낭을 메고 곡식 창고에 침투함
    • 창고 입구에는 보관 중인 곡식 전체 수량1kg당 가격이 적혀 있음
    • 이익이 최대가 되도록 배낭을 채우는 문제
  • Greedy approach로 배낭 문제를 해결해보기

    • 가장 값어치가 높은 아이템을 먼저 채우는 것
    • 1kg당 기준 가격을 내림차순으로 정렬
    • 배낭의 무게를 초과하지 않을 때 까지 비싼 순으로 채우기

파이썬으로 배낭 문제 구현하기(Greedy)

1
2
3
4
5
6
7
8
9
10
11
12
# W: 배낭 무게, w: 각 아이템의 무게 p:각 아이템의 값어치
def knapsack1 (W, w, p):
n = len(w) - 1
K = [0] * (n + 1)
weight = 0
for i in range(1, n + 1):
weight += w[i]
K[i] = w[i]
if (weight > W):
K[i] -= (weight - W)
break
return K
  • Greedy approach는 최적해를 보장하는가?

    • 아이템이 분할 가능하여 30kg를 딱 맞춰서 채울 수 있다면 가능
    • 분할이 불가능한 0-1 배낭 문제는 최적해를 보장하지 않음
    • 배낭의 용량을 넘지 않으면서 가치가 최대가 되는 S의 부분집합 A를 찾기
    • 동적계획법 or 백트랙킹 or 분기한정법으로 해결
  • Dynamic programming으로 0-1 배낭문제를 해결해보기

    • P[i][w]: 총 무게가 w를 초과할 수 없다는 제약조건 하에서 처음 i개 아이템에서만 선택할 때 얻는 최적의 이익
    • P[n][w]: n개의 아이템으로 얻을 수 있는 최대 이익
    • P[i][w] = max(P[i-1][w], pi + P[i-1][w-wi]) (단, wi ≤ w)
      or P[i-1][w] (단, wi > w)
    • max(P[i-1][w], pi + P[i-1][w-wi])
    • P[i-1][w]: i번째의 아이템까지 무게 합(wi)이 총 무게보다 크다면 해당 i번째의 아이템을 제외한 무게 합이 된다
    • P[n][W]를 계산할 때엔 P[n-1][W], P[n-1][W-Wn]만 필요하고 P[i][w]를 계산할 때엔 P[i-1][w], P[i-1][w-wi]만 필요함

파이썬으로 배낭 문제 구현하기(DP)

1
2
3
4
5
6
7
8
9
def knapsack2(i, W, w, p):
if (i <= 0 or W <= 0):
return 0
if (w[i] > W):
return knapsack2(i - 1, W, w, p)
else:
left = knapsack2(i - 1, W, w, p)
right = knapsack2(i - 1, W - w[i], w, p)
return max(left, p[i] + right)
  • backtracking로 0-1 배낭문제를 해결해보기
    • 부분집합의 합 문제와 동일하게 상태공간트리를 구성하여 최적해를 찾아 최적해의 전체 이익을 계산
1
2
3
4
5
6
7
def checknode (node):
node u
if (v값이 최적값보다 낫다면):
best = v
if (promising(v)):
for (v의 자식을 돌며):
노드를 체크()
  • promising과 가지치기
    • 배낭에 더이상 담을 공간이 없다면 유망하지 않음(weight >= W)
    • 현재까지 찾은 최적해의 이익이 현재 노드에서 앞으로 얻을 수 있는 최대 이익보다 크다면 유망하지 않음
      → 앞으로 얻을 수 있는 최대 이익 <= 현재까지 찾은 이익값

파이썬으로 배낭 문제 구현하기(backtracking)

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
48
49
50
51
52
def knapsack3 (i, profit, weight):
global maxprofit, numbest, bestset
if (weight <= W and profit > maxprofit):
maxprofit = profit
numbest = i
bestset = include[:]
if (promising(i, profit, weight)):
include[i + 1] = True
knapsack3(i + 1, profit + p[i+1], weight + w[i+1])
include[i + 1] = False
knapsack3(i + 1, profit, weight)

def promising(i, profit, weight):
if (weight > W):
return False
else:
j = i + 1
bound = profit
totweight = weight
while (j <= n and totweight + w[j] <= W):
totweight += w[j]
bound += p[j]
j += 1
k = j
if (k def knapsack3 (i, profit, weight):
global maxprofit, numbest, bestset
if weight <= W and profit > maxprofit:
maxprofit = profit
numbest = i
bestset = include[:]
if promising(i, profit, weight):
include[i + 1] = True
knapsack3(i + 1, profit + p[i+1], weight + w[i+1])
include[i + 1] = False
knapsack3(i + 1, profit, weight)


def promising(i, profit, weight):
if weight > W:
return False
else:
j = i + 1
bound = profit
totweight = weight
while j <= n and totweight + w[j] <= W:
totweight += w[j]
bound += p[j]
j += 1
k = j
if k <= n:
bound += (W - totweight) * p[k] / w[k]
return bound > maxprofit

Comment and share

부분집합의 합 구하기

  • Sum-of-Subset Problem

    • 원소가 n개인 양의 정수 집합 w와 양의 정수 W가 주어졌을 때 합이 W가 되는 w의 부분집합을 모두 찾기
  • backtracking approach로 해결하기

    • 상태공간트리를 만들어 풀기 (리프 노드가 모든 부분집합이 됨)
    • 가지치기 전략을 사용하기
      → 검색하기 전에 정수 원소를 오름차순으로 정렬해 놓으면 노드의 유망여부를 탐색 가능하게 됨
      → i번째 레벨에서 wi+1은 남아있는 정수 원소 중에서 가장 작은 값
      → 남아있는 원소 합이 W보다 작다면 하위 노드는 더이상 방문 할 필요가 없다 (유망하지 않기 때문에)
      → weight를 레벨 i까지의 정수 합이라 하고 weight가 W와 같지 않다면 weight + wi+1 > W 이라면 그 노드는 유망하지 않음
      → total이 정수 원소의 합일 때 weight를 더했을 때 W와 같아지지 않으므로 weight + total < W 이라면 이 노드도 유망하지 않다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# i = level, subset의 weight, 남은 원소의 합 total
def sum_of_subsets (i, weight, total):
n = len(w) - 1
if (promising(i, weight, total)):
if (weight == W):
print(include[1: n+1])
else:
# left subtree
include[i + 1] = True
sum_of_subsets (i+1, weight + w[i+1], total - w[i+1])
# right subtree
include[i + 1] = False
sum_of_subsets (i+1, weight, total - w[i+1])

def promising(i,weight, total):
if((weight + total >= W) and
(weight == W or weight + w[i+1] <= W)):
return True
else:
return False

Comment and share

백트랙킹과 n-Queens 문제

  • backtracking

    • 임의의 집합에서 주어진 기준대로 원소의 순서를 선택하는 문제를 푸는 데 적합함
    • 트리 자료구조의 변형된 깊이우선탐색
    • 모든 문제 사례에 대해서 효율적이지 않지만 많은 문제 사례에 대해 효율적
  • 미로찾기 문제를 트리 탐색 문제로 해석 (DFS를 통한 preorder 방식)

  • 상태공간트리 (State Space Tree)

  • 백트래킹 기법

    • 상태공간트리를 깊이우선탐색으로 탐색
    • 방문중인 노드에서 더 하위 노드로 가면 해답이 없을 경우엔 해당 노드의 하위 트리를 방문하지 않고 부모 노드로 되돌아감
  • 유망함 (promising)

    • 방문중인 노드에서 하위 노드가 해답을 발견할 가능성이 있으면 유망
    • 하위 노드에서 해답을 발견할 가능성이 없으면 유망하지 않음
  • 백트래킹과 가지치기

    1. 상태공간트리를 DFS로 탐색
    2. 방문 중인 노드가 유망한지 체크
    3. 만약 유망하지 않으면 부모 노드로 돌아감
    4. 그리고 유망하지 않는 하위 트리를 가지치기함

슈도 코드

1
2
3
4
5
6
7
8
def checknode (node v): 
node u;
if (promising(v)):
if (v에 해답이 있다면)
해답을 출력
else
for (v의 모든 자식 노드)
checknode(u);
  • 백트래킹 알고리즘의 구현

    • 상태공간트리를 실제로 구현할 필요는 없음
    • 현재 조사중인 가지의 값에 대해만 추적
    • 상태공간트리는 암묵적으로 존재한다고 가정
  • n-Queens 문제

    • 8-Queens의 일반화 문제
    • n x n 체스보드에 n개의 퀸을 배치하는 문제
      → 어떤 퀸도 다른 퀸에게 잡아먹지 않도록 배치할 것
  • n-Queens를 backtracking approach로 해결하기

    • 임의의 집합에서 기준에 따라 원소의 순서를 선택
    • 임의의 집합: 체스보드에 있는 n^2^개의 가능한 위치
    • 기준: 새로 놓을 퀸이 다른 퀸을 위협할 수 없음
    • 원소의 순서: 퀸을 놓을 수 있는 n개의 위치
  • 4-Queens 문제 (n = 4)

    • 일단 같은 행에는 놓을 수 없다고 가정하면 후보 해답은 4(가로 4칸) x 4(가로 4칸) x 4(가로 4칸) x 4(가로 4칸) = 256 가지의 탐색 공간이 있음
  • n-Queen 문제의 해결

    • 같은 행에는 퀸을 놓지 않는다(기본 가정)
    • 같은 열이나 같은 대각선에 놓이는지를 확인
    • 같은 열을 체크하기
      → col[i]: i번째 행에서 퀸이 놓여있는 열의 위치
      → col[k]: k번째 행에서 퀸이 놓여있는 열의 위치
      → 따라서, col[i] == col[k] 이라면 유망하지 않다
    • 대각선 체크하기
      → 왼쪽에서 위협하는 퀸과 오른쪽에서 위협하는 퀸은 |i - k|값으로 판단할 수 있다

파이썬으로 n-Queens 문제를 위한 백트래킹 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def n_queens (i, col):
n = len(col) - 1
if (promising(i, col)):
if (i == n):
print(col[1: n+1])
else:
for j in range(1, n+1):
col[i+1] = j
n_queens(i+1, col)

def promising (i, col):
k = 1
flag = True
while (k < i and flag):
if (col[i] == col[k] or abs(col[i] - col[k]) == (i - k)):
flag = False
k += 1
return flag

Comment and share

허프만 코드와 허프만 알고리즘

  • 이진 코드 (binary code)

    • ASCII / UNICODE와 같은 fixed-length 이진 코드
    • variable-length 이진 코드
  • 최적 이진 코드 문제 (optimal binary code)

    • 주어진 파일에 있는 문자들을 이진코드로 표현할 때 필요한 최소한의 이진 코드는?
  • 전치 코드 (prefix code)

    • 한 문자의 코드 워드가 다른 문자의 코드 워드의 앞부분이 될 수 없다
      → 01이 ‘a’의 코드워드라면 011은 ‘b’의 코드워드가 될 수 없다
    • 모든 전치코드는 리프노드가 코드 문자인 이진 트리로 표현 가능하다
    • 파일을 디코딩 할 때 뒷부분을 미리 볼 필요가 없음
  • 허프만 알고리즘 : 허프만 코드에 해당하는 이진 트리를 구축하는 Greedy approach

    1
    2
    3
    4
    5
    6
    7
    // n: 주어진 데이터 파일 내 문자의 개수
    // PQ: **빈도수가 낮은 노드를 먼저 리턴하는** 우선순위 큐(min-heap)
    1. PQ는 빈도수로 오름차순 정렬되어 있다
    2. PQ에서 두 개의 인자를 pop하여 처음 pop한 인자(p)를 left 노드로,
    다음 인자(q)를 right 노드로 하는 새 노드를 구축한다
    3. 새 노드의 빈도수는 자식 노드(p, q)의 빈도수의 합이 된다
    4. 이 노드를 PQ에 다시 삽입한다 (오름차순에 맞게 위치함)
  • 최적 이진 코드를 위한 이진 트리의 구축

    • 데이터 파일을 인코딩하는 데 필요한 비트 수 계산
    • 주어진 이진트리는 T
    • {v1, v2, …, vn} : 데이터 파일 내 문자들의 집합
    • frequency(vi): 문자 vi가 파일 내에서 나타나는 빈도수
    • depth(vi): 이진 트리 T에서 문자 vi 노드의 깊이
    • bits(T) = 모든 frequency(vi) * depth(vi) 의 합 (0 < i < n)

파이썬으로 허프만 코드와 허프만 알고리즘

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
class HuffNode:

def __init__ (self, symbol, freq):
self.symbol = symbol
self.freq = freq
self.left = None
self.right = None

def preorder(self):
print(self.freq, end=" ")
if(self.left is not None):
self.left.preorder()
if(self.right is not None):
self.right.preorder()

def inorder(self):
if(self.left is not None):
self.left.inorder()
print(self.freq, end=" ")
if(self.right is not None):
self.right.inorder()

def huffman (n, PQ):
for _ in range(n - 1):
p = PQ.get()[1]
q = PQ.get()[1]
r = HuffNode(' ', p.freq + q.freq)
r.left = p
r.right = q
PQ.put((r.freq, r))
return PQ.get()[1]

# 파이썬에는 Priority Queue가 있어 이 자료구조를 사용할 것

Comment and share

마감시간이 있는 스케쥴 짜기

  • 마감시간과 함께 여러개의 작업이 주어지고 마감시간 내에 마치면 보상이 주어짐
  • 이 보상이 최대가 되는 스케쥴을 작성하는 문제

Greedy Approach로 해결하기

  • 보상에 따라서 비오름차순으로 작업을 정렬하고 각 작업을 순서대로 하나씩 가능한 스케쥴에 포함시킴

슈도 코드로 작성해보기

1
2
3
4
5
6
7
S = ∅
while (답을 구하지 못함):
다음 작업을 선택
if (이 작업을 추가했을 때 S가 적절함)
이 작업을 S에 추가
if (더 이상 남은 작업이 없다면)
답을 구하였으니 return
  • 적절함 의 정의
    • 어떤 순서는 그 순서 내의 모든 작업이 데드라인 이전에 시작될 때 적절한 순서라 한다
      예) [4,1]은 정렬된 데드라인이라 적절하나 [1,4]는 부적절함
    • 어떤 집합은 그 집합의 원소들로 하나 이상의 적절한 순서를 만들 수 있을 때 적절한 집합이라 한다
      예) 집합 {1,4}는 [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
def schedule (deadline):
n = len(deadline) - 1
J = [1]
for i in range(2, n + 1):
K = insert(J, i, deadline)
if(feasible(K, deadline)):
J = K[:] # 파이썬에서 배열의 깊은 복사
return J

def feasible(K, deadline):
for i in rage(1, len(K) + 1):
if (i > deadline[K[i - 1]]):
return False
return True


def insert(J, i, deadline):
K = J[:]
for j in range(len(J), 0, -1):
if (deadline[i] >= deadline[K[j-1]]):
j += 1
break
K.insert(j - 1, i)
return K

Comment and share

Harry Kim

author.bio


author.job