자바스크립트 알고리즘 문제풀이(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

최단 경로와 다익스트라 알고리즘

  • 최단 경로 문제: Revisited
    • 모든 정점의 쌍에 대한 최단 경로 구하기 (플로이드 알고리즘)
    • 단일 정점에서 모든 다른 정점으로의 최단경로 구하기 (다익스트라 알고리즘)

다익스트라 알고리즘 슈도 코드

1
2
3
4
5
6
7
8
9
Y = {v1};
F = ∅
while (답을 구하지 못함):
Y에 속한 정점만 중간에 거쳐 가는 정점으로 하여
v1에서 최단경로를 가진 정점 v를 V - Y에서 선택한다
새로운 정점 v를 Y에 추가한다
최단경로 상에서 v로 가는 간선을 F에 추가한다
if(Y == V):
답을 구했으니 return
  • W[i][j]: 그래프 G의 인접행렬
  • touch[i]: Y에 속한 정점들만 중간에 거치도록 하여 v1에서 vi로 가는 현재 최단경로 상의 마지막 간선을 (v, vi)라고 할 때, Y에 속한 정점 v의 인덱스
  • length[i]: Y에 속한 정점들만 중간에 거치도록 하여 v1에서 vi로 가는 현재 최단 경로의 길이

파이썬으로 다익스트라 알고리즘

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
def dijkstra (W):
n = len(W) - 1
F = []
touch = [-1] * (n + 1)
length = [-1] * (n + 1)
for i in range(2, n+1):
touch[i] = 1
length[i] = W[1][i]

for _ in range(n - 1):
minValue = INF
for i in range(2, n + 1):
if(0 <= length[i] and length[i] < minValue):
minValue = length[i]
vnear = i
edge = (touch[vnear], vnear, W[touch[vnear]][vnear])
F.append(edge)
for i in range(2, n + 1):
if (length[i] > length[vnear] + W[vnear][i]):
length[i] = length[vnear] + W[vnear][i]
touch[i] = vnear
length[vnear] = -1
return F

def length (F):
total = 0
for e in F:
total += e[2] # (1, 5, 1) 처럼 들어오기 때문에 가중치는 index 2번에 있음
return total

Comment and share

서로소 집합과 크루스칼 알고리즘

  • 크루스칼 알고리즘
  1. 해답의 집합을 공집합으로 두고, V의 서로소 집합을 생성한다. E를 비내림차순으로 정렬한다.
  2. 정렬된 E 집합에서 간선 e = (i, j)를 선택하고 두 정점 i, j가 속한 집합 p, q를 찾아서 p, q가 같으면 e를 버리고 다르면 F에 e를 포함시킨 후에 p, q를 합친다.
  3. |F| = n - 1이면 종료, 아니면 2단계를 반복한다.
  • 사이클 탐지는 어떻게 할까?
    → 교집합이 공집합인 두 집합 A, B, 즉 서로소 집합을 만든다
    → 두 개의 원소가 같은 집합에 속하는지 판단하는 알고리즘: Union-Find 알고리즘

파이썬으로 Union-Find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DisjointSet:
def __init__ (self, n):
self.U = []
for i in range(n):
self.U.append(i)

def equal (self, p, q):
if (p == q):
return True
else:
return False

def find (self, i):
j = i
while (self.U[j] != j):
j = self.U[j]
return j

def union (self, p, q):
if (p < q):
self.U[q] = p
else:
self.U[p] = q

크루스칼 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
def kruskal (n, E):
F = []
dset = DisjointSet(n)
while (len(F) < n - 1):
edge = E.pop(0)
i, j = edge[0], edge[1]
p = dset.find(i)
q = dset.find(j)
if (not dset.equal(p, q)):
dset.union(p, q)
F.append(edge)
return F

Comment and share

Harry Kim

author.bio


author.job