[알고리즘] 9일차 (ATM 인출 시간 계산하기, K번째 수 구하기) #백준11399번 #백준11004번

클라우드·2023년 9월 25일
0

알고리즘

목록 보기
9/35
post-thumbnail

04-3 삽입 정렬

  • 삽입 정렬은 이미 정렬된 데이터 범위에 정렬되지 않은 데이터를 적절한 위치에 삽입시켜 정렬하는 방식이다. 시간 복잡도는 O(n^2)로 느린 편이지만 구현하기가 쉽다.

[삽입 정렬 과정]
1. 현재 index에 있는 데이터 값을 선택한다.
2. 현재 선택한 데이터가 정렬된 데이터 범위에 삽입될 위치를 탐색한다.
3. 삽입 위치부터 index에 있는 위치까지 shift 연산을 수행한다.
4. 삽입 위치에 현재 선택한 데이터를 삽입하고 index++ 연산을 수행한다.
5. 전체 데이터의 크기만큼 index가 커질 때까지, 즉 선택할 데이터가 없을 때까지 반복한다.

  • 적절한 삽입 위치를 탐색하는 부분에서 이진 탐색 등과 같은 탐색 알고리즘을 사용하면 시간 복잡도를 줄일 수 있다.

📌 문제 018) ATM 인출 시간 계산하기

시간 제한 1초, 실버 III, 백준 11399번

인하은행에는 ATM이 1대밖에 없다. 지금 이 ATM앞에 N명의 사람들이 줄을 서있다. 사람은 1번부터 N번까지 번호가 매겨져 있으며, i번 사람이 돈을 인출하는데 걸리는 시간은 Pi분이다.
사람들이 줄을 서는 순서에 따라서, 돈을 인출하는데 필요한 시간의 합이 달라지게 된다. 예를 들어, 총 5명이 있고, P1 = 3, P2 = 1, P3 = 4, P4 = 3, P5 = 2 인 경우를 생각해보자. [1, 2, 3, 4, 5] 순서로 줄을 선다면, 1번 사람은 3분만에 돈을 뽑을 수 있다. 2번 사람은 1번 사람이 돈을 뽑을 때 까지 기다려야 하기 때문에, 3+1 = 4분이 걸리게 된다. 3번 사람은 1번, 2번 사람이 돈을 뽑을 때까지 기다려야 하기 때문에, 총 3+1+4 = 8분이 필요하게 된다. 4번 사람은 3+1+4+3 = 11분, 5번 사람은 3+1+4+3+2 = 13분이 걸리게 된다. 이 경우에 각 사람이 돈을 인출하는데 필요한 시간의 합은 3+4+8+11+13 = 39분이 된다.
줄을 [2, 5, 1, 4, 3] 순서로 줄을 서면, 2번 사람은 1분만에, 5번 사람은 1+2 = 3분, 1번 사람은 1+2+3 = 6분, 4번 사람은 1+2+3+3 = 9분, 3번 사람은 1+2+3+3+4 = 13분이 걸리게 된다. 각 사람이 돈을 인출하는데 필요한 시간의 합은 1+3+6+9+13 = 32분이다. 이 방법보다 더 필요한 시간의 합을 최소로 만들 수는 없다.
줄을 서 있는 사람의 수 N과 각 사람이 돈을 인출하는데 걸리는 시간 Pi가 주어졌을 때, 각 사람이 돈을 인출하는데 필요한 시간의 합의 최솟값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 사람의 수 N(1 ≤ N ≤ 1,000)이 주어진다. 둘째 줄에는 각 사람이 돈을 인출하는데 걸리는 시간 Pi가 주어진다. (1 ≤ Pi ≤ 1,000)

5 # 데이터 개수
3 1 4 3 2

출력

첫째 줄에 각 사람이 돈을 인출하는데 필요한 시간의 합의 최솟값을 출력한다.

32

1단계 문제 분석

  • ATM에서 모든 사람이 가장 빠른 시간에 인출하는 방법을 그리디 방식으로 해결해 보자. ATM 앞에 있는 사람 중 인출 시간이 가장 적게 걸리는 사람이 먼저 인출할 수 있도록 순서를 정하는 것이 곧 그리디 방식이다.
  • 인출 시간을 기준으로 값을 오름차순으로 정렬하자.
  • N의 최댓값이 1,000이고, 시간 제한이 1초이므로 시간 복잡도가 O(n^2) 이하인 정렬 알고리즘 중 아무거나 사용해도 된다.
  • 삽입 정렬을 활용해보자.

2단계 슈도 코드

N(사람 수)
A(자릿수별로 구분해 저장한 리스트)
S(A 합 배열: 각 사람이 인출을 완료하는 데 필요한 시간을 저장)

for i를 1~N만큼 반복:
	for j를 i-1~0까지 뒤에서부터 반복:
    	현재 범위에서 삽입 위치 찾기
    for j를 i~insert_point+1까지 뒤에서 반복:
    	삽입을 위해 삽입 위치에서 i까지 데이터를 한 칸씩 뒤로 밀기
    삽입 위치에 현재 데이터 저장

for i를 1~N만큼 반복:
	A 리스트를 통한 합 배열 S 만들기

S 리스트의 각 데이터값을 모두 합해 결과 출력

3단계 코드 구현

N = int(input())
A = list(map(int, input().split()))
S = [0] * N

for i in range(1, N): # 삽입 정렬
    insert_point = i
    insert_value = A[i]
    for j in range(i-1, -1, -1): # i-1~0 까지 역순으로 반복
        if A[j] < A[i]:
            insert_point = j + 1
            break
        if j == 0:
            insert_point = 0
    for j in range(i, insert_point, -1): # i~insert_point+1 까지 역순으로 반복
        A[j] = A[j-1]
    A[insert_point] = insert_value

S[0] = A[0]

for i in range(1, N): # 합 배열 만들기
    S[i] = S[i-1] + A[i]

sum = 0

for i in range(0, N): # 합 배열 총합 구하기
    sum += S[i]

print(sum)

04-4 퀵 정렬

  • 퀵 정렬은 기준값 pivot을 선정해 해당 값보다 작은 데이터와 큰 데이터로 분류하는 것을 반복해 정렬하는 알고리즘이다. 기준값이 어떻게 선정되는지가 시간 복잡도에 많은 영향을 미치고, 평균적인 시간 복잡도는 O(nlogn)이며 최악의 경우에는 시간 복잡도가 O(n^2)이 된다.

[퀵 정렬 과정]
1. 데이터를 분할하는 pivot을 설정한다.
2. pivot을 기준으로 다음 a~e 과정을 거쳐 데이터를 2개의 집합으로 분리한다.
a. start가 가리키는 데이터가 pivot이 가리키는 데이터보다 작으면 start를 오른쪽으로 1칸 이동한다.
b. end가 가리키는 데이터가 pivot이 가리키는 데이터보다 크면 end를 왼쪽으로 1칸 이동한다.
c. start가 가리키는 데이터가 pivot이 가리키는 데이터보다 크고, end가 가리키는 데이터가 pivot이 가리키는 데이터보다 작으면 start, end가 가리키는 데이터를 swap하고 start는 오른쪽, end는 왼쪽으로 1칸씩 이동한다.
d. start와 end가 만날 때까지 a~e를 반복한다.
e. start와 end가 만나면 만난 지점에서 가리키는 데이터와 pivot이 가리키는 데이터를 비교하여 pivot이 가리키는 데이터가 크면 만난 지점의 오른쪽에, 작으면 만난 지점의 왼쪽에 pivot이 가리키는 데이터를 삽입한다.
3. 분리 집합에서 각각 다시 pivot을 선정한다.
4. 분리 집합이 1개 이하가 될 때까지 과정 1~3을 반복한다.

  • 퀵 정렬의 시간 복잡도는 비교적 준수하므로 코딩 테스트에서도 종종 응용한다. 재귀 함수의 형태로 직접 구현해 볼 것을 추천한다.

📌 문제 019) K번째 수 구하기

시간 제한 2초, 실버 V, 백준 11004번

수 N개 A1, A2, ..., AN이 주어진다. A를 오름차순 정렬했을 때, 앞에서부터 K번째 있는 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 5,000,000)과 K (1 ≤ K ≤ N)이 주어진다.
둘째에는 A1, A2, ..., AN이 주어진다. (-10^9 ≤ Ai ≤ 10^9)

5 2 # 데이터 개수, K번째 수
4 1 2 3 5

출력

A를 정렬했을 때, 앞에서부터 K번째 있는 수를 출력한다.

2

1단계 문제 분석

  • pivot을 정하는 방법
    • pivot == k: k번째 수를 찾은 것이므로 알고리즘을 종료한다.
    • pivot > k: pivot의 왼쪽 부분에 k가 있으므로 왼쪽(S ~ pivot-1)만 정렬을 수행한다.
    • pivot < k: pivot의 오른쪽 부분에 k가 있으므로 오른쪽(pivot+1 ~ E)만 정렬을 수행한다.
  • 배열의 중간 위치를 pivot으로 설정하자.

2단계 슈도 코드

N(숫자의 개수) K(K번째 수)
A(숫자 데이터 저장 배열)

# 별도의 함수 구현 부분
퀵 정렬 함수(시작, 종료, k):
	피벗 구하기 함수(시작, 종료):
    if 피벗 == k: 종료
    elif k < 피벗: 퀵 정렬 수행하기(시작, 피벗-1, k)
    else: 퀵 정렬 수행하기(피벗+1, 종료, k)
    
피벗 구하기 함수(시작, 종료):
	데이터가 2개인 경우는 바로 비교하여 정렬
    M(중앙값)
    중앙값을 시작 위치와 swap
    pivot(피벗)을 시작 위치 값 A[S]로 저장
    i(시작점), j(종료점)
    
    while i <= j:
    	피벗보다 큰 수가 나올 때까지 i 증가
        피벗보다 작은 수가 나올 때까지 j 감소
        찾은 i와 j 데이터를 swap
        
    피벗 데이터를 나뉜 두 그룹의 경계 index에 저장하기
    경계 index 리턴

퀵 정렬 실행
k번째 데이터 출력

3단계 코드 구현

import sys
input = sys.stdin.readline
N, K = map(int, input().split())
A = list(map(int, input().split()))

def quickSort(S, E, K):
    global A
    if S < E:
        pivot = partition(S, E)
        if pivot == K: # K번째 수가 pivot이면 더는 구할 필요 없음
            return
        elif K < pivot: # K가 pivot보다 작으면 왼쪽 그룹만 정렬
            quickSort(S, pivot - 1, K)
        else:
            quickSort(pivot + 1, E, K) # K가 pivot보다 크면 오른쪽 그룹만 정렬

def swap(i, j):
    global A
    temp = A[i]
    A[i] = A[j]
    A[j] = temp

def partition(S, E):
    global A
    
    if S + 1 == E:
        if A[S] > A[E]:
            swap(S, E)
        return E
    
    M = (S + E) // 2
    swap(S, M)
    pivot = A[S]
    i = S + 1
    j = E

    while i <= j:
        while pivot < A[j] and j > 0:
            j = j - 1
        while pivot > A[i] and i < len(A) - 1:
            i = i + 1
        if i <= j:
            swap(i, j)
            i = i + 1
            j = j - 1
    
    # i == j 피벗의 값을 양쪽으로 분리한 가운데에 오도록 설정하기
    A[S] = A[j]
    A[j] = pivot
    return j

quickSort(0, N - 1, K - 1)
print(A[K - 1])
profile
안녕하세요 :)

0개의 댓글