728x90
반응형

정렬Sorting

이란 데이터를 특정한 기준에 따라서 순서대로 나열하는 것을 말한다. 정렬을 이용해서 이진탐색도 가능해지는 것이다.

 

선택 정렬(Selection Sort) 

: 가장 먼저 생각이 날 수 있는 자연스러운 정렬 알고리즘

  각 위치에 어떤 값이 들어갈지 찾는다.

 

가장 작은 값을 찾아서 0번 인덱스에 놓고, 두 번째로 작은 값을 찾아서 1번 인덱스에 놓고, 세 번째로 작은 값을 찾아서 2번 인덱스에 놓고.....

즉, 데이터가 무작위로 여러 개 있을 때, 이중에서 가장 작은 데이터를 맨 앞에 있는 데이터와 바꾸고, 그 다음 작은 데이터를 앞에서 2번째 데이터와 바꾸는 과정을 반복하자.

array = [2,4,5,1,3,9,7,8]

for i in range(len(array)):
    min_index=i
    for j in range(i+1,len(array)):
        if array[min_index]>array[j]:
            min_index=j
    array[i],array[min_index]=array[min_index],array[i]

print(array)

선택 정렬의 시간 복잡도는 N-1번 만큼 가장 작은 수를 찾아서 맨 앞으로 보내야 한다. 또한 매번 가장 작은 수를 찾기 위해서 비교 연산이 필요하다. 연산 횟수는 N+(N-1)+(N-2) ... 이렇기 때문에 O(N^2)시간복잡도가 나온다.


삽입 정렬(Insertion Sort)

: 각 값이 어떤 위치에 들어갈지 찾는다

 

정렬 문제의 경우 "절대적인 좋은 답"이란 없다. 상황에 따른 각 알고리즘의 장단점을 파악해야 올바른 알고리즘을 선택할 수 있다. 

삽입 정렬은 선택 정렬처럼 동작 원리를 직관적으로 이해하기 쉽다. 삽입 정렬은 필요할 때만 위치를 바꾸므로 '데이터가 거의 정렬되어 있을 때' 훨씬 효율적이다. 선택 정렬은 현재 데이터의 상태와 상관없이 무조건 모든 원소를 비교하고 위치를 바꾸는 반면 삽입 정렬은 그렇지 않다. 

 

삽입 정렬은 특정한 데이터가 적절한 위치에 들어가기 이전에, 그 앞까지의 데이터는 이미 정렬되어 있다고 가정한다. 정렬되어 있는 데이터 리스트에서 적절한 위치를 찾은 뒤에, 그 위치에 삽입된다.

array = [7,5,9,0,3,1,2,8,6]

for i in range(1,len(array)):
    for j in range(i,0,-1):#인덱스 i부터 1까지 감소하며 반복하는 문법
        if array[j]<array[j-1]:
            array[j],array[j-1]=array[j-1],array[j]
        else:
            break
print(array)

 

삽입 정렬의 시간복잡도는 O(N^2)이다. 그러나 최선의 경우 O(N)의 시간 복잡도를 가진다. 모두 정렬되어있으면 O(N)의 시간복잡도를 가진다.


합병 정렬(Merge Sort)

 Divide and Conquer에서 대표적으로 설명하는 정렬 방식이다.

 Divide에서는 리스트를 왼쪽 반, 오른쪽 반으로 나눈다. 왼쪽 반을 정렬하는 부분 문제랑 오른쪽 반을 정렬하는 부분 문제로 나누어 주는 것이다.

 Conquer 단계에서는 왼쪽 리스트와 오른쪽 리스트를 각각 정렬해 준다.

 Combine 단계에서는 정렬된 두 리스트를 하나의 정렬된 리스트로 합병한다.

 

두 리스트를 하나로 Combine 할 때가 중요하다. 이미 정렬되었다고 가정하였을때 왼쪽이 제일 작게 정렬했다고 생각하자. 그럼 왼쪽과 오른쪽의 첫번째 값을 비교해 주어서 더 작은 것을 Combined된 리스트에 넣어준다.

 계속 하나씩 작은것부터 차례대로 넣어주면 한쪽 리스트만 남을거이다. 이것은 이미 정렬되어 있는 것이기 때문에 남은 것을 왼쪽부터 넣으면 된다. 이렇게 하면 하나의 정렬된 리스트가 된다.

 

def merge(list1, list2):
    i = 0
    j = 0

    # 정렬된 항목들을 담을 리스트
    merged_list = []

    # list1과 list2를 돌면서 merged_list에 항목 정렬
    while i < len(list1) and j < len(list2):
        if list1[i] > list2[j]:
            merged_list.append(list2[j])
            j += 1
        else:
            merged_list.append(list1[i])
            i += 1

    # list2에 남은 항목이 있으면 정렬 리스트에 추가
    if i == len(list1):
        merged_list += list2[j:]

    # list1에 남은 항목이 있으면 정렬 리스트에 추가
    elif j == len(list2):
        merged_list += list1[i:]

    return merged_list

# 테스트
print(merge([1],[]))
print(merge([],[1]))
print(merge([2],[1]))
print(merge([1, 2, 3, 4],[5, 6, 7, 8]))
print(merge([5, 6, 7, 8],[1, 2, 3, 4]))
print(merge([4, 7, 8, 9],[1, 3, 6, 10]))
[1]
[1]
[1, 2]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 3, 4, 6, 7, 8, 9, 10]

 

결론적으로 합병 정렬을 재귀적으로 구하면 다음과 같다.

def merge(list1, list2):
    merged_list=[]
    index1=0
    index2=0
    while index1<len(list1) and index2<len(list2):
        if list1[index1]<list2[index2]:
            merged_list.append(list1[index1])
            index1+=1
        else:
            merged_list.append(list2[index2])
            index2+=1
    merged_list=merged_list+list1[index1:]+list2[index2:]
    return merged_list
# 합병 정렬
def merge_sort(my_list):
    return merge(merge_sort(my_list[:len(my_list)//2]),merge_sort(my_list[len(my_list)//2:]))
# 테스트
print(merge_sort([1, 3, 5, 7, 9, 11, 13, 11]))
print(merge_sort([28, 13, 9, 30, 1, 48, 5, 7, 15]))
print(merge_sort([2, 5, 6, 7, 1, 2, 4, 7, 10, 11, 4, 15, 13, 1, 6, 4]))

퀵 정렬 (Quick Sort)

Divide and Conquer를 사용하는 또다른 정렬방법은 퀵 정렬(Quicksort)이다. 합병 정렬은 사실 Divide랑 Conquer단계는 생각하기 쉬우나 Combine이 어렵다. 퀵 정렬은 완전히 반대로 Divide과정이 되게 어렵다. 


 

1. Divide 

 

Partition에는 2단계가 있다. 먼저 pivot을 정해주는 것이다. 두 번째 단계는 이 pivot을 기준으로 값들을 새롭게 배치하는 것이다. pivot을 기준으로 더 작은 값들은 왼쪽으로, 더 큰 값들은 오른쪽으로 오게 하는 것이다. 

 

2. Conquer 

 

pivot 왼쪽과 pivot 오른쪽을 정렬해 준다. 그리고 왼쪽과 오른쪽을 재귀적으로 sort해주면 된다.

 

3. Combine

 

할 일이 없다


 

Divide 부분을 다시 한 번 살펴보자. 일단 partition함수는 parameter를 3개 받는다.

def partition(my_list,start,end):

 

 

 

변수를 3개 두어야 된다. start, big_index, i, pivot. start는 처음 인덱스, b는 pivot값보다 큰 인덱스, i는 계속 진행해가는 인덱스이다. 

 

아래는 Divide과정인 Partition 하는 과정을 담고 있다.

 

# 두 요소의 위치를 바꿔주는 helper function
def swap_elements(my_list, index1, index2):
    # 코드를 작성하세요.
    my_list[index1],my_list[index2]=my_list[index2],my_list[index1]

# 퀵 정렬에서 사용되는 partition 함수
def partition(my_list, start, end):
    # 코드를 작성하세요.
    pivot = end
    big_index=0
    for i in range(len(my_list)-1):
        if my_list[i]>my_list[pivot]:
            continue
        else:
            swap_elements(my_list,i,big_index)
            big_index+=1
    swap_elements(my_list,pivot,big_index)
    pivot=big_index
    return pivot

# 테스트 1
list1 = [4, 3, 6, 2, 7, 1, 5]
pivot_index1 = partition(list1, 0, len(list1) - 1)
print(list1)
print(pivot_index1)

# 테스트 2
list2 = [6, 1, 2, 6, 3, 5, 4]
pivot_index2 = partition(list2, 0, len(list2) - 1)
print(list2)
print(pivot_index2)

 

partition을 했으면 merge_sort처럼 나머지를 재귀함수를 통해서 구하면 된다.

# 두 요소의 위치를 바꿔주는 helper function
def swap_elements(my_list, index1, index2):
    my_list[index1], my_list[index2] = my_list[index2], my_list[index1]
# 퀵 정렬에서 사용되는 partition 함수
def partition(my_list, start, end):
    i = start
    pivot = end
    big_index = start
    while i < pivot:
        if my_list[i] <= my_list[pivot]:
            swap_elements(my_list, i, big_index)
            big_index += 1
        i += 1
    swap_elements(my_list, big_index, pivot)
    pivot = big_index
    return pivot
# 퀵 정렬 (start, end 파라미터 없이도 호출이 가능하도록 수정해보세요!)
def quicksort(my_list,start=0,end=None):
    if end==None:
        end=len(my_list)-1
    if end-start<1:
        return
    pivot = partition(my_list, start, end)
    quicksort(my_list,start,pivot-1)
    quicksort(my_list,pivot+1,end)
# 테스트 1
list1 = [1, 3, 5, 7, 9, 11, 13, 11]
quicksort(list1) # start, end 파라미터 없이 호출
print(list1)

# 테스트 2
list2 = [28, 13, 9, 30, 1, 48, 5, 7, 15]
quicksort(list2) # start, end 파라미터 없이 호출
print(list2)

# 테스트 3
list3 = [2, 5, 6, 7, 1, 2, 4, 7, 10, 11, 4, 15, 13, 1, 6, 4]
quicksort(list3) # start, end 파라미터 없이 호출
print(list3)


728x90
반응형

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

힙 자료구조  (0) 2021.05.04
DFS / BFS  (0) 2021.04.12
재귀 함수  (0) 2021.04.02
브루트 포스 (Brute Force)  (0) 2021.03.18
다이나믹 프로그래밍  (0) 2021.03.08
728x90
반응형

www.acmicpc.net/problem/1026

 

1026번: 보물

첫째 줄에 N이 주어진다. 둘째 줄에는 A에 있는 N개의 수가 순서대로 주어지고, 셋째 줄에는 B에 있는 수가 순서대로 주어진다. N은 50보다 작거나 같은 자연수이고, A와 B의 각 원소는 100보다 작거

www.acmicpc.net

문제

옛날 옛적에 수학이 항상 큰 골칫거리였던 나라가 있었다. 이 나라의 국왕 김지민은 다음과 같은 문제를 내고 큰 상금을 걸었다.

길이가 N인 정수 배열 A와 B가 있다. 다음과 같이 함수 S를 정의하자.

S = A[0]×B[0] + ... + A[N-1]×B[N-1]

S의 값을 가장 작게 만들기 위해 A의 수를 재배열하자. 단, B에 있는 수는 재배열하면 안 된다.

S의 최솟값을 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 N이 주어진다. 둘째 줄에는 A에 있는 N개의 수가 순서대로 주어지고, 셋째 줄에는 B에 있는 수가 순서대로 주어진다. N은 50보다 작거나 같은 자연수이고, A와 B의 각 원소는 100보다 작거나 같은 음이 아닌 정수이다.

출력

첫째 줄에 S의 최솟값을 출력한다.

예제 입력 1

5

1 1 1 6 0

2 7 8 3 1

예제 출력 1

18

힌트

A를 {1, 1, 0, 1, 6}과 같이 재배열하면 된다.

 

[문제 해설]

일단 B에 있는 수들을 재배열하면 안되고 A에 있는 수들을 재배열하여 최솟값을 구해야 한다. B에 있는 수들 중 제일 큰 수에 A의 제일 작은 수를 배치하면 된다. 사실 B를 정렬하면 sort(reverse=True)를 통하여 A와 곱하면 되지만 조건에서 그렇게 하지 말라고 하였기 때문에 새로운 방법을 고안해야된다. 

A배열의 제일 작은 수와 B배열의 제일 큰 수를 곱해주고 이 제일 작은수와 제일 큰수는 빼주고 다시 sort해서 반복해 주면 된다. 

n = int(input())

a = list(map(int, input().split()))
b = list(map(int, input().split()))

s = 0
for i in range(n):
    s += min(a_list) * max(b_list)
    a.pop(a_list.index(min(a)))
    b.pop(b_list.index(max(b)))

print(s)
728x90
반응형
728x90
반응형

www.acmicpc.net/problem/10814

 

10814번: 나이순 정렬

온라인 저지에 가입한 사람들의 나이와 이름이 가입한 순서대로 주어진다. 이때, 회원들을 나이가 증가하는 순으로, 나이가 같으면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 프로그램을

www.acmicpc.net

문제

온라인 저지에 가입한 사람들의 나이와 이름이 가입한 순서대로 주어진다. 이때, 회원들을 나이가 증가하는 순으로, 나이가 같으면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 프로그램을 작성하시오.

입력

첫째 줄에 온라인 저지 회원의 수 N이 주어진다. (1 ≤ N ≤ 100,000)

둘째 줄부터 N개의 줄에는 각 회원의 나이와 이름이 공백으로 구분되어 주어진다. 나이는 1보다 크거나 같으며, 200보다 작거나 같은 정수이고, 이름은 알파벳 대소문자로 이루어져 있고, 길이가 100보다 작거나 같은 문자열이다. 입력은 가입한 순서로 주어진다.

출력

첫째 줄부터 총 N개의 줄에 걸쳐 온라인 저지 회원을 나이 순, 나이가 같으면 가입한 순으로 한 줄에 한 명씩 나이와 이름을 공백으로 구분해 출력한다.

예제 입력 1

3

21 Junkyu

21 Dohyun

20 Sunyoung

예제 출력 1

20 Sunyoung

21 Junkyu

21 Dohyun

 

[문제 해설]

이 문제는 두 가지 조건이 있다. 먼저 나이순으로 정렬을 하고 그 다음 먼저 온 순서대로 정렬을 하면 된다. 

n = int(input())

data = []
for i in range(n):
    age, name = list(input().split())
    age=int(age)
    data.append((age, name, i))
data = sorted(data, key=lambda x: x[0])

for i in range(n):
    print(str(data[i][0]) + ' ' + data[i][1])

그리고 type에 따라 비교가 다르게 되기 때문에 나이는 int로 바꾸어서 계산해준다.

728x90
반응형
728x90
반응형

www.acmicpc.net/problem/1181

 

1181번: 단어 정렬

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

www.acmicpc.net

문제

알파벳 소문자로 이루어진 N개의 단어가 들어오면 아래와 같은 조건에 따라 정렬하는 프로그램을 작성하시오.

  1. 길이가 짧은 것부터
  2. 길이가 같으면 사전 순으로

입력

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

출력

조건에 따라 정렬하여 단어들을 출력한다. 단, 같은 단어가 여러 번 입력된 경우에는 한 번씩만 출력한다.

예제 입력 1

13

but

i

wont

hesitate

no

more

no

more

it

cannot

wait

im

yours

예제 출력 1

i

im

it

no

but

more

wait

wont

yours

cannot

hesitate

 

[문제 해설]

이 문제는 두 가지 해결해야 될 점이 있다. 먼저 길이가 짧은 순서대로 단어를 정렬해야 되고, 두 번째 그 정렬된 상태를 단어 사전 순서대로 정렬하면 된다. 두 가지 조건이 있으면 lambda 함수를 떠올려서 정렬하는 방법을 생각하면 된다.

그리고 중복을 제거해야 하기 때문에 set으로 바꾼뒤에 다시 list형식으로 바꾸어 준다.

n = int(input())
datas=[]
for _ in range(n):
    word = input()
    word_c = len(word)
    datas.append((word,word_c))
#중복 값 제거
datas=list(set(datas))

datas.sort(key=lambda word:(word[1],word[0]))

for i in range(len(datas)):
    print(datas[i][0])
728x90
반응형
728x90
반응형

www.acmicpc.net/problem/1931

 

1931번: 회의실 배정

(1,4), (5,7), (8,11), (12,14) 를 이용할 수 있다.

www.acmicpc.net

문제

한 개의 회의실이 있는데 이를 사용하고자 하는 N개의 회의에 대하여 회의실 사용 표를 만들려고 한다. 각 회의 I에 대해 시작시간과 끝나는 시간이 주어져 있고, 각 회의가 겹치지 않게 하면서 회의실을 사용할 수 있는 회의의 최대 개수를 찾아보자. 단, 회의는 한번 시작하면 중간에 중단될 수 없으며 한 회의가 끝나는 것과 동시에 다음 회의가 시작될 수 있다. 회의의 시작시간과 끝나는 시간이 같을 수도 있다. 이 경우에는 시작하자마자 끝나는 것으로 생각하면 된다.

입력

첫째 줄에 회의의 수 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N+1 줄까지 각 회의의 정보가 주어지는데 이것은 공백을 사이에 두고 회의의 시작시간과 끝나는 시간이 주어진다. 시작 시간과 끝나는 시간은 2^31-1보다 작거나 같은 자연수 또는 0이다.

출력

첫째 줄에 최대 사용할 수 있는 회의의 최대 개수를 출력한다.

예제 입력 1

11

1 4

3 5

0 6

5 7

3 8

5 9

6 10

8 11

8  12

2 13

12 14

예제 출력 1

4

 

[문제 해설]

출력에서 4는 힌트에 있듯이 (1,4), (5,7), (8,11), (12,14)을 이용한 경우이다. 배열에는 시작시간과 종료시간이 동시에 입력되어있고 동시에 고려해야 된다. 회의의 최대 개수를 구해야 한다. 파이썬 정렬을 다중 조건으로 하려고 할 때는 lambda함수를 사용하면 쉽게 할 수 있다.

 

#lambda함수

일반적으로 우리는 파이썬 내장 함수인 .sort() 또는 sorted()를 사용한다. 배열을 sort()를 이용하면 오름차순으로 바뀐다. 

sorted() 함수가 어떻게 작동하는지 자세히 살펴보자. 

data=[[1,2],[3,5],[3,4],[0,5],[1,7],[4,5]]
s_data=sorted(data)#[[0, 5], [1, 2], [1, 7], [3, 4], [3, 5], [4, 5]]

인자 없이 그냥 sorted()만 쓰면, 리스트 아이템의 각 요소 순서대로 정렬을 한다. key 인자를 함수에 넘겨주면 해당 함수의 반환 값을 비교하여 순서대로 정렬한다. 예를 들어 data의 첫 번째 요소로 정렬할 것인지, 두 번째 요소로 정렬할 것인지 정해줄 수 있다. 그리고 첫 번째 요소로 정렬하고 그다음 두 번째 요소로 정렬을 시도할 수도 있다.

data=[[1,2],[3,5],[3,4],[0,5],[1,7],[4,5]]
s_data=sorted(data) #[[0, 5], [1, 2], [1, 7], [3, 4], [3, 5], [4, 5]]
s2_data=sorted(data,key=lambda x:x[0])#[[0, 5], [1, 2], [1, 7], [3, 5], [3, 4], [4, 5]]
s3_data=sorted(data,key=lambda x:(x[0],x[1]))#[[0, 5], [1, 2], [1, 7], [3, 4], [3, 5], [4, 5]]

sorted()의 key의 인자로, 내가 정렬할 비교 함수를 넣어주면 된다. 비교 함수는 비교할 아이템의 요소를 반화하면 된다. lambda는 비교하는 익명 함수인 것이다. 

 

그럼 이 문제에서는 lambda함수를 사용할 수 있을까? 주어진 시작시간과 끝나는 시간들을 이용해서 가장 많은 회의의 수를 알기 위해서는 빨리 끝나는 회의 순서대로 정렬을 해야 한다. 빨리 끝날수록 뒤에서 고려해볼 회의가 많기 때문이다. 일단 시작 시간 기준으로 정렬을 하고 종료 시간 기준으로 정렬을 해준다. lambda함수를 사용해서 정렬한 뒤에 시작 시간이 제일 빠른 것을 저장해 둔 뒤 시작시간을 비교하여 만약 시작시간이 저장해둔 종료시간보다 크면 그 수업을 선택한 뒤 시작시간을 업데이트해준다. 코드는 다음과 같다.

n = int(input())
meeting=[]
for i in range(n):
    start,end= map(int,input().split())
    meeting.append((start,end))

#시작 시간 기준으로 정렬
meeting=sorted(meeting,key=lambda x:x[0])
#종료 시간 기준으로 정렬
meeting=sorted(meeting,key=lambda x:x[1])

start_time=0
result=0
for time in meeting:
    if time[0]>=start_time:
        start_time=time[1]
        result+=1

print(result)

 

 

 

728x90
반응형
728x90
반응형

www.acmicpc.net/problem/10989

 

10989번: 수 정렬하기 3

첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.

www.acmicpc.net

문제

N개의 수가 주어졌을 때, 이를 오름차순으로 정렬하는 프로그램을 작성하시오.

입력

첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.

출력

첫째 줄부터 N개의 줄에 오름차순으로 정렬한 결과를 한 줄에 하나씩 출력한다.

예제 입력 1

10

5

2

3

1

4

2

3

5

1

7

예제 출력 1

1

1

2

2

3

3

4

5

5

7

 

[문제 해설]

 오름차순으로 n개의 수를 정렬하면 된다. 물론 배열에 저장해서 오름차순으로 정렬하는 방법을 맨 처음 떠올리겠지만 메모리가 8MB밖에 안된다는 것이 이 문제의 함정이다. 따라서 계수정렬을 이용하였다.

 계수정렬(Count Sort)알고리즘은 특정한 조건이 부합할 때만 사용할 수 있지만 매우 빠른 정렬 알고리즘이다. 데이터의 개수가 N, 데이터 중 최댓값이 K일 떄, 계수 정렬은 최악의 경우에도 수행 시간 O(N+K)를 보장한다. 여기서 숫자의 크기가 10000보다 작기 때문에 계수정렬을 쓸 수 있는 것이다. 1부터 10000까지의 배열을 0으로 저장해두고 각 숫자가 나올때마다 크기를 1씩 증가시킨다. input을 받을때 같이 data에 숫자를 1씩 증가시키고 나중에 정답으로 출력시켜주면 된다.

import sys
input = sys.stdin.readline

n = int(input())
data=[0]*10001
for _ in range(n):
    num = int(input())
    data[num-1]+=1

for i in range(10000):
    if data[i]!=0:
        for j in range(data[i]):
            print(i+1)

 

728x90
반응형

+ Recent posts