728x90
반응형

그리디 문제 중 큰 수의 법칙 문제를 풀어보자.

 

문제

큰 수의 법칙은 다양한 수로 이루어진 배열이 있을 때 주어진 수들을 M번 더하여 가장 큰 수를 만드는 법칙이다. 단, 배열의 특정한 인덱스에 해당하는 수가 연속해서 K번을 초과하여 더해질 수 없는 것이 이 법칙의 특징이다.

 

입력 조건

N : 배열의 크기

M : 숫자가 더해지는 횟수

K : 연속 가능한 수의 갯수

 

출력

첫째 줄에 입력한 값에 따라 다르다.

 

배열 2,4,5,4,6 이 있을 때 M=8, K=3 이면 6+6+6+5+6+6+6+5 = 46 이 된다.

배열 3,4,3,4,3 이 있을 때 M=7, K=2 이면 4+4+4+4+4+4+4 = 28 이 된다.

 

나의 풀이

# 큰 수의 법칙
# 다향한 수로 이루어진 배열이 있을 때 주어진 수들을 M번 더하여 가장 큰 수를 만드는 법칙

# 문제 (예를 들어 설명)
# 2,4,5,4,6 의 배열이 있을 때 M(더하는 횟수)이 8이고, K(한 숫자가 연속해서 더해질 수 있는 갯수)가 3 일 때 합이 가장 큰 경우는
# 6 + 6 + 6 + 5 + 6 + 6 + 6 + 5 = 46 이 된다. (4 같은 경우는 2개 존재하므로 다른 숫자로 취급한다.) 최대가 되는 수를 구하라.

N,M,K = map(int,input().split())
data = list(map(int,input().split()))   # 리스트 입력 받는 방법

data.sort() # 정렬

sum=0
count = 1
for i in range(0,M):
    if count%K!=0:
        sum += data[N-1]
    else:
        sum += data[N-2]
    count +=1

print(sum)

나는 배열을 정렬시켜놓고 끝값과 그 앞의 값만 구하고 count % K 의 값에 따라 6을 더할지 5를 더할지 반복하였다.

전형적인 초보자의 방법이란다... 뜨끔

 

책 풀이

N,M,K = map(int,input().split())
data = list(map(int,input().split()))   # 리스트 입력 받는 방법
data.sort()

first = data[N-1]
second = data[N-2]

# 가장 큰 수가 더해지는 횟수 계산
count = int(M/(K+1)) * K
count += M% (K+1)

result = 0
result += count *first
result += (M-count) * second

print(result)

이 정답 보면서 느낀게 생각이 반복문에 박혀있다 보니까 저런 생각이 떠오르지 않았다는고 다른 문제 많이 풀어봐야겠구나라고 생각했다.

위 정답은 간단하다.

{6+6+6+5} 의 수열이 반복되는 상황이고

수열의 길이는 K+1 (여기선 4) 이면

수열의 갯수는 M/(K+1)가 되며

가장 큰 수가 등장하는 수는 수열의 갯수( M/(K+1) ) * 연속 수(K) 로 M/(K+1) * K 가 되고

나누어 떨어지지 않는 경우에는 나머지 만큼 가장 큰 수를 추가로 더해줘야 하므로

int ( M / (K+1) )* K + M % (K+1) 이 된다.

 

 

 

 

728x90
반응형
728x90
반응형

greedy : 탐욕적인

 

그리디 알고리즘은 현재 상황에서 지금 당장 좋은 것만 고르는 방법이다.

 

매 순간 가장 좋아 보이는 것을 선택하며, 현재의 선택이 나중에 미칠 영향에 대해서는 생각하지 않는다.

 

이 책의 예제를 풀어보자.

 

3-1 거스름돈

Q. 500원, 100원, 50원, 10원짜리 동전이 무한히 존재한다고 가정할 때 손님한테 거슬러 줘야할 돈이 N원이고 N원의 동전의 최소 개수를 구하라 (N은 10의 배수)

 

A. 1260원을 거슬러 줘야한다고 생각해보자. 큰 동전 부터 500원은 2개, 100원은 2개, 50원은 1개, 10원은 1개로 총 6개의 동전이 필요로 한다. %와 //를 잘 활용해서 코딩해보자.

 

# 그리디 알고리즘 :  현재 상황에서 지금 당장 좋은 것만 고르는 방법

# 문제
# 500원, 100원, 50원, 10원짜리 동전이 무한히 존재한다고 가정할 때 손님한테 거슬러 줘야할 돈이 N원이고 N원의 동전의 최소 개수를 구하라 (N은 10의 배수)

# 나의 풀이

N = int(input())

five = N//500
N = N%500
one = N//100
N=N%100
fifty =N//50
N=N%50
ten=N//10
N=N%10

print(five+one+fifty+ten)

# 정답 풀이

N = int(input())
count =0

coin_types=[500,100,50,10]

for coin in coin_types:
    count += N // coin
    N %= coin

print(count)

 

 

그리디 알고리즘은 결국 무식하게 하나하나 해보는 방법이기 때문에 효율적인 답찾기는 아닐 수 있다. 하지만 위 문제 같은 경우 가지고 있는 동전 중에서 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문에 그리디 알고리즘을 사용할 수 있다.

 

코딩 테스트 문제를 만났을 때, 바로 문제 유형을 파악하기 어렵다면 그리디 알고리즘을 의심하고, 문제를 해결할 수 있는 탐욕적인 해결법이 존재하는 지 고민해보자.

728x90
반응형
728x90
반응형

최단 경로 알고리즘

: 정점 u와 정점 v를 연결하는 경로 중에서 간선들의 가중치 합이 최소가 되는 경로

- 간선이 없으면 가중치를 무한대로 처리

 

다익스트라 알고리즘

: 시작 정점 v에서 모든 다른 정점까지의 최단 경로 찾음

> 시작 정점 v : 최단경로탐색의 시작 정점

> 집합 S: 시작 정점 v로부터의 최단 경로가 이미 발견되 정점들의 집합

> dist배열 : S에 있는 정점만을 거쳐서 다른 정점으로 가는 최단거리를 기록하는 배열

- 매 단계에서 최소 distance 인 정점을 S에 추가

- 새로운 정점이 S에 추가되면 dist 갱신

728x90
반응형
728x90
반응형

프림 알고리즘이란?

: 하나의 정점에서부터 시작하여 트리를 단계적으로 확장

- 현재의 신장 트리 집합에 인접한 정점 중 최저 간선으로 연결된 정점 선택하여 신장 트리 집합에 추가

- 이 과정을 n-1개의 간선을 가질 때 까지 반복

 

과정

1> 그래프에서 시작 정점을 선택하여 초기 트리를 만든다.

2> 현재 트리의 정점들과 인접한 정점들 중에서 간선의 가중치가 가장 작은 정점 v를 선택한다.

3> 이 정점 v와 이때의 간선을 트리에 추가한다.

4> 모든 정점이 삽입될 때 까지 2번으로 이동한다.

 

코드

INF = 9999

def getMinVertex(dist, selected):
    minv = 0
    mindist = INF
    for v in range(len(dist)):
        if not selected[v] and dist[v] < mindist:
            mindist = dist[v]
            minv = v
    return minv

def MSTPrim(vertex, adj):
    vsize = len(vertex)
    dist =[INF]*vsize   # dist: [INF,INF, ...]
    selected = [False]*vsize    # selceted[False,False, ...]
    dist[0] = 0 # dist: [0, INF, INF, ...]

    for i in range(vsize):  # 정점의 수 만큼 반복
        u = getMinVertex(dist,selected)
        selected[u] = True  # u는 이제 선택됨
        print(vertex[u],end=' ')    # u를 출력
        for v in range(vsize):  # 내부 루프
            if adj[u][v] != None:   # (u,v) 간선이 있으면 dist[v] 갱신
                if selected[v] == False and adj[u][v]<dist[v]:
                    dist[v] = adj[u][v]

    print()

# 테스트 코드
vertex = ['A','B','C','D','E','F','G']

weight =[
    [None,29,None,None,None,10,None],
    [29,None,16,None,None,None,15],
    [None,16,None,12,None,None,None],
    [None,None,12,None,22,None,18],
    [None,None,None,22,None,27,25],
    [10,None,None,None,27,None,None],
    [None,15,None,18,25,None,None]
]

MSTPrim(vertex,weight)

 

시간 복잡도: O(n^2)

728x90
반응형
728x90
반응형

크루스칼 알고리즘 (Kruskal)

- 그리디 알고리즘 

: 그 순간에 최적이라고 생각되는 것을 선택

- 시간복잡도 : e log e

 

- 과정

1> 그래프의 모든 간선을 가중치에 따라 오름차순으로 정렬한다.

2> 가장 가중치가 작은 간선 e를 뽑느다.

3> e를 신장트리에 넣었을 때 사이클이 생기면 넣지 않고 2번으로 이동한다.

4> 사이클이 생기지 않으면 최소 신장 트리에 삽입한다.

5> n-1개의 간선이 삽입될 때 까지 2번으로 이동한다. 

 

 

- union-find 알고리즘을 활용

   > union : 두 집합의 합집합을 만드는 연산

   > find: 원소가 속한 집합을 찾는 연산

=> 대표값을 비교함으로써 순환구조 판단

ex) 

 

 

코드

# union-find

parent =[]  # 각 노드의 부모노드 인덱스
set_size=0  # 전체 집합의 개수

def init_set(nSets):    # 집합의 초기화 함수
    global set_size,parent  # 전역변수 사용을 위함
    set_size = nSets    # 집합의 개수
    for i in range(nSets):  # 모든 집합에 대해
        parent.append(-1)   # 각각이 고유의 집합(부모가 -1)

def find(id):   # 정점 id가 속한 집합의 대표번호 탐색
    while parent[id]>=0:    # 부모가 있는 동안 (-1이 아닌 동안)
        id=parent[id]   # id를 부모 id로 갱신
    return id   # 최종 id 반환. 트리의 맨 위 노드의 id임

def union(s1,s2):   # 두 집합을 병합(s1을 s2에 병합시킴)
    global set_size # 전역변수 사용을 위함
    parent[s1] = s2     # s1을 s2에 병합시킴
    set_size = set_size - 1 # 집합의 개수가 줄어듦

# Kruskal

def MSTKruskal(vertex,adj): # 매개변수: 정점리스트, 인접행렬
    vsize=len(vertex)   # 정점의 개수
    init_set(vsize) # 정점 집합 초기화
    eList =[]   # 간선 리스트

    for i in range(vsize-1):    # 모든 간선을 리스트에 넣음
        for j in range(i+1,vsize):
            if adj[i][j] != None:
                eList.append((i,j,adj[i][j]))   # 튜플로 저장

    eList.sort(key=lambda e: e[2], reverse=True)    # 간선 리스트를 가중치의 내림차순으로 정렬: 람다 함수 사용

    edgeAccepted = 0

    while edgeAccepted < vsize-1:   # 정점 수 - 1 개의 간선
        e = eList.pop(-1)   # 가장 작은 가중치를 가진 간선
        uset = find(e[0])   # 두 정점이 속한 집합 번호
        vset = find(e[1])

        if uset != vset:    # 두 정점이 다른 집합의 원소라면
            print("간선 추가 : (%s,%s,%d)"%(vertex[e[0]],vertex[e[1]],e[2]))    # 간선추가 출력
            union(uset,vset)    # 두 집합을 합함
            edgeAccepted +=1    # 간선이 하나 추가됨

# 테스트 코드
vertex = ['A','B','C','D','E','F','G']

weight =[
    [None,29,None,None,None,10,None],
    [29,None,16,None,None,None,15],
    [None,16,None,12,None,None,None],
    [None,None,12,None,22,None,18],
    [None,None,None,22,None,27,25],
    [10,None,None,None,27,None,None],
    [None,15,None,18,25,None,None]
]

MSTKruskal(vertex,weight)
728x90
반응형
728x90
반응형

이진 탐색 알고리즘은 정렬된 배열에서 반으로 쪼개가며 숫자를 찾는 알고리즘이다.

 

과정을 살펴보자.

 

찾고자 하는 수를 target 라 두고 배열의 중간 인덱스 값을 mid 라고 두자.

 

1. target 은 arr[mid] 보다 큰가?

2. target 은 arr[mid] 보다 작은가?

3. target 은 arr[mid] 와 같은가?

 

1인 경우 mid 의 오른쪽 부분을, 2인 경우 mid 의 왼쪽 부분을, 3인 경우 return 하면 된다.

 

재귀를 배웠으니 재귀를 활용하여 구현해보자.

 

def BinarySearch(arr,target,left,right):
    mid = (left + right)//2
    if target<arr[mid]:
        return BinarySearch(arr,target,left,mid-1)
    elif target>arr[mid]:
        return BinarySearch(arr,target,mid+1,right)
    elif target == arr[mid]:
        return True # 값이 존재한다.
    else:
        return False  # 값이 존재하지 않는다.

 

간단하게 구현 가능하다.

728x90
반응형

+ Recent posts