이진 탐색 ( Binary Search )

이진 탐색이란? 

정렬되어있는 배열에서 사용

 

처음과 끝 인덱스의 중간 지점보다 찾고자하는 값이 

  • 작으면 : 처음 = 중간, 끝은 그대로
  • 크면    : 처음은 그대로, 끝 = 중간
  • 같으면 : 반환

 

이진 탐색 vs 이진 탐색(검색) 트리

  • 임의의 데이터들 -> 순서대로 정렬한 후 이진 탐색
  • 데이터를 입력할 때부터 정렬 -> 이진 탐색(검색) 트리

 

코드

python 코드

####### Binary Search ######
import random
from timeit import default_timer as timer

def binary_search(x, v):
    start = 0
    end = len(x) - 1
    while start<=end:
        mid = (start+end) // 2
        if x[mid] == v : return mid
        elif x[mid] < v : start = mid + 1
        else: end = mid - 1
    return -1

x = random.sample(range(5000), 1000)
x.sort()
value = x[800]

start = timer()
index = binary_search (x, value)
print(timer() - start)

print('value ', value, 'found ', index)
print(True if index >=0 and x[index] == value else False)

 

c++ 코드

#include <iostream>
#include <algorithm>
using namespace std;

int A[10];

int binary_search(int target)
{
	int start = 0, end = 9;
	int mid;
	while (start <= end)
	{
		mid = (start + end) / 2;
		
		if (A[mid] == target) return mid;
		else if (A[mid] > target) end = mid - 1;
		else start = mid + 1;
	}
	return -1;
}

int main()
{
	srand(time(NULL));

	for (int i = 0; i < 10; i++)
	{
		A[i] = rand() % 100;
	}
	
	sort(A, A + 10);
	int index = binary_search(A[3]);

	cout << "A[3] = " << A[3] << endl;
	if (index > -1)
		cout << "index = " << index << ' ' << "A[index] = " << A[index] << endl;
	else
		cout << "error";

}

 

이진 검색 트리 ( Binary Search Tree )

이진 검색 트리란?

왼쪽 자식은 부모보다 작고 오른쪽 자식은 부모보다 큼

 

  • 각 노드는 하나의 키 값을 가진다.
  • 각 노드의 키 값은 모두 달라야 한다.
  • 각 노드는 최대 두 개의 자식 노드를 갖는다.
  • 각 노드의 키 값은 왼쪽의 모든 노드의 키 값보다 크고 오른쪽의 모든 노드의 키 값보다 작다.

 

Insert, Search, Delete

Insert

작으면 왼쪽 크면 오른쪽으로 넣음

 

Search

작으면 왼쪽 크면 오른쪽 찾음

 

Delete

1. 자식 노드가 0개인 경우 : 그냥 지움

2. 자식 노드가 1개인 경우 : 부모와 자식 이어줌

3. 자식 노드가 2개인 경우 :

  • 오른쪽 자식에서 제일 작은 노드를 찾는다. ( 왼쪽 자식보다 큰 노드 중에서 가장 작은 노드 )
  • delete하고 위에서 찾은 노드를 위로 올린다.
  • 빈 노드는 자식이 최대 하나이므로 위에서 설명한 것처럼 잇는다.

 

 

이진 검색 트리의 단점

데이터 입력 순서에 따라 성능이 달라진다.

 

python 코드

import random
from timeit import default_timer as timer

class Node(object):
    def __init__(self, key, parent):
        self.key = key
        self.left = None
        self.right = None
        self.parent = parent
        
def insert(node, key, parent):
    if node is None : 
        node = Node(key, parent)
    elif key < node.key : 
        node.left = insert(node.left, key, node)
    else : node.right = insert(node.right, key, node)
    return node

def search(node, key):
    if node is None or node.key == key: return node
    if key < node.key : return search(node.left, key)
    return search(node.right, key)

def delete_node(node):
    if node.left is None and node.right is None : 
        return None
    elif node.left is not None and node.right is None :
        return node.left
    elif node.left is None and node.right is not Node :
        return node.right
    else:
        s = node.right
        while s.left is not None :
            sparent = s
            s = s.left
        node.key = s.key
        if s == node.right : 
            s.right.parent = node
            node.right = s.right
        else : 
            s.right.parent = sparent
            sparent.left = s.right
    return node
            


def delete(node) : 
    if node.parent is None : node = None
    elif node is node.parent.left : node.parent.left = delete_node(node)
    else : node.parent.right = delete_node(node) 
        

x = [10,20,30]
value = 30

root = None
for i in x:
    root = insert(root, i, None)
    
start = timer()
found = search(root, value)
print(timer()-start)

if found is not None:
    print('value', value, 'found', found.key)
    print(True if found.key == value else False)
    
delete(search(root, value))
found = search(root, value)

 

 

 

AVL-트리

AVL-트리란?

  • 가장 초기에 나온 균형 잡힌 이진 검색 트리
  • 균형 이진 트리 : 각 노드의 왼쪽/오른쪽 서브 트리의 높이 차가 1 이하
    • 높이 차가 2 이상이 될 경우 단일 회전 / 이중 회전

 

회전

- 단일 회전

  • 자식 노드가 존재하는 부모 노드의 방향이 동일한 경우

오른쪽 / 왼쪽

 

- 이중 회전

  • 자식 노드가 존재하는 부모 노드의 방향이 동일하지 않은 경우
    • 회전을 통해 방향을 일치 시킨 후 단일 회전

 

 

 

스플레이 트리

스플레이 트리란?

  • 탐색, 삽입, 삭제 시 스플레이를 하는 Tree
  • zig 회전 / zig-zig 회전 / zig-zag 회전

 

zig 회전

zig-zig 회전

zig-zag 회전

 

 

 

레드 블랙 트리 (Red Black Tree)

레드 블랙 트리란?

  • root는 블랙
  • 완전 이진 트리 ( NIL 사용 )
  • 모든 leaf는 블랙 
  • 레드의 자식은 블랙 ( 레드 연속 2개 불가 )
    • 최소 높이는 모두 블랙인 경우
    • 최대 높이는 레드-블랙 교대인 경우
  • root - leaf까지의 모든 경로는 같은 개수의 블랙
    • 최악의 경우에도 최소 높이의 2배 이하
  • 왼쪽 회전 / 오른쪽 회전

 

 

노드 삽입 (N이 새 노드 & 레드 노드)

case 1: 빈 트리

  • N을 root & 블랙으로

 

case 2: P가 블랙

  • 그냥 삽입

 

case 3: P가 레드

  • case 3-1 : U가 레드

1, 2, 3

 

4, 5

(만약 G가 root라면 3단계까지만 진행해도 됨)

 

  • case 3-2 : U가 블랙
    • case 3-2-1 : P가 오른쪽 자식, N이 오른쪽 자식
    • case 3-2-2 : P가 오른쪽 자식, N이 왼쪽 자식
    • case 3-2-3 : P가 왼쪽 자식, N이 왼쪽 자식
    • case 3-2-4 : P가 오른쪽 자식, N이 오른쪽 자식
    •  

 

 

'22-1학기 > 알고리즘' 카테고리의 다른 글

4. 선택 알고리즘  (0) 2022.04.06
3. 정렬  (0) 2022.03.22
2. 점화식과 알고리즘 복잡도 분석  (0) 2022.03.14
1. 알고리즘 표기법  (0) 2022.03.14

선택 알고리즘이란?

n개 중 i번째로 작은 원소를 찾는 알고리즘

 

방법 1

1. 퀵 정렬을 사용해서 pivot 기준으로 작으면 왼쪽, 크면 오른쪽으로 나눈다.

2. 

  • pivot의 위치가 i보다 크면 왼쪽 집단에서 다시 퀵 정렬을 수행
  • pivot의 위치가 i보다 작으면 오른쪽 집단에서 다시 퀵 정렬을 수행
    • 이때, 오른쪽의 맨 앞의 원소를 0번째라고 하면 i-k째 원소를 찾아야함
  • pivot의 위치가 i와 같으면 pivot을 반환

이 방법의 경우 최악의 경우(pivot이 집단의 제일 작거나 큰 값으로 선택됐을 경우)에는 $ \Theta (n^2) $ 의 시간복잡도를 가진다.

 

방법2

1. 원소를 5개씩 그룹으로 만들고 각 그룹의 중간값을 찾는다.                             => $ \Theta (n) $

2. 각 그룹의 중간값의 중간값 M을 찾는다.                                                     => $ T ( \lceil{ n \over 5 }) $

3. M보다 큰 경우는 최소 $ {3 \over 5} x {n \over 2} - 2 = {3n \over 10} - 2 $  

   나머지 경우는 $ n - ({3n \over 10} - 2) = {7n \over 10} + 2 $

   따라서 M보다 큰 값인지 작은 값인지 알 수 없는 경우는 최대 $ {7n \over 10} + 2 $  => $ T({7n \over 10} + 2)

==> 시간 복잡도

 

'22-1학기 > 알고리즘' 카테고리의 다른 글

5. 검색 트리  (0) 2022.04.06
3. 정렬  (0) 2022.03.22
2. 점화식과 알고리즘 복잡도 분석  (0) 2022.03.14
1. 알고리즘 표기법  (0) 2022.03.14

정렬은 비교, 중복, 탐색을 위해서 필요함.

선택 정렬

시간 복잡도

$O(n^2)$

$ (n-1) + (n-2) + ... + 1 + cn = {n(n-1) \over 2} + cn = O(n^2) $

선택정렬이란?

가장 큰 수를 선택한 후 

1. 1~n까지 가장 큰 수를 찾는다.

2. 가장 큰 수와 마지막 수의 위치를 교환한다.

3. 정렬된 수를 제외하고 정렬될 때까지 1~2를 반복한다.

 

c++ 코드

void selection_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	int largest_index;

	for (int i = SIZE - 1; i > -1; i--)
	{
		largest_index = i;
		for (int j = i; j > -1; j--)
		{
			if (arr[largest_index] < arr[j])
				largest_index = j;
		}
		int temp = arr[largest_index];
		arr[largest_index] = arr[i];
		arr[i] = temp;
	}
}

 

Python 코드

import random
from timeit import default_timer as timer

def selection_sort(x):
    for last in range(len(x)-1, 0, -1):
        largest = 0
        for i in range(1, last+1):
            if x[i] > x[largest]:
                largest = i
        x[largest], x[last] = x[last], x[largest]

def test(x):
    for i in range(1, len(x)):
        if x[i-1] > x[i]:
            return False
    return True


data = [1, 3, 5, 6, 8, 2, 4, 10, 7, 9] 
start = timer()
selection_sort(data)
print(timer() - start)
print(data)
print(test(data))

 

버블 정렬

시간 복잡도

Worst : $O(n^2)$

Best   : $O(n)$

$ (n-1) + (n-2) + ... + 1 + cn = {n(n-1) \over 2} + cn = O(n^2) $

버블정렬이란?

1. 두 장씩 비교하면서 큰 것을 오른쪽으로

2. 맨 오른쪽이 제일 큼

3. 맨 오른쪽 빼고 정렬될 때까지 1~2 반복

 

c++ 코드

void bubble_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	bool flag = false; // stop option

	for (int i = SIZE - 1; i > -1; i--)
	{
		for (int j = 0; j < i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = true;
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		// stop option
		if (!flag) break;
	}
}

 

python 코드

import random
from timeit import default_timer as timer


def bubble_sort(A):
    sorted = True # Termination conditions
    for last in range(len(A) - 1, 1, -1):
        for i in range(last):
            if A[i] > A[i + 1]:
                A[i], A[i + 1] = A[i + 1], A[i]
                sorted = False
        if sorted:
            break


def test(A):
    for i in range(1, len(A)):
        if A[i - 1] > A[i]:
            return False
    return True

x = random.sample(range(10000), 100)
start = timer()
bubble_sort(x)
print(timer() - start)
print(x)
print(test(x))

 

삽입 정렬

시간 복잡도

Worst : $O(n^2)$

Best   : $O(n)$

$ 1 + 2 + ... + (n-1) = {n(n-1) \over 2} = O(n^2) $

삽입 정렬이란?

현재 위치 앞의 수들을 보고 현재 수가 들어갈 수 있는 곳을 찾아서 삽입

c++ 코드

void insertion_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	for (int i = 1; i < SIZE; i++)
	{
		int loc = i - 1; //0
		int now = arr[i]; //3
		while (loc >= 0 && now < arr[loc])
		{
			arr[loc + 1] = arr[loc];
			loc--;
		}
		arr[loc + 1] = now;

	}

	print(3, arr);
}

 

Python 코드

def insertion_sort(A):
    for i in range(1, len(A)):
        loc = i - 1
        new_item = A[i]
        while new_item < A[loc] and loc > -1:
            A[loc+1] = A[loc] # A[loc+1], A[loc] = A[loc], A[loc+1]
            loc-=1
        A[loc+1] = new_item

data = [1, 3, 5, 6, 8, 2, 4, 10, 7, 9] 
start = timer()
insertion_sort(data)
print(timer() - start)
print(data)
print(test(data))

 

 

병합 정렬

시간 복잡도

$O(nlogn)$

 

마스터 정리 유형 3으로 시간 복잡도를 계산할 수 있다.

 

* 마스터 정리 유형 3 근사 버전: 

$ T(n) = aT({n \over b}) + f(n) $

$ h(n) = n^{log_ba}$ 라고 할 때, ${f(n) \over h(n)} = \Theta (1)$ 이면 $T(n) = \Theta (h(n)logn) $

 

따라서, 

$ T(n) = 2T({n \over 2}) + c_1n+c_2 , a = 2, b = 2, f(n) = c_1n+c_2 \\ h(n) = n^{log_2 2} $

$ {c_1n+c_2 \over n^{log_2 2}} = \Theta (1) $ 이므로 $T(n) = \Theta (n^{log_2 2} logn) = \Theta (nlogn)$

병합 정렬이란?

https://gmlwjd9405.github.io/2018/05/08/algorithm-merge-sort.html

아래 코드에서 Divide 부분이 merge_sort_fuc이고

Combine 부분이 merge라고 생각하면 된다.

c++ 코드

void merge(int list[], int left, int mid, int right)
{
	int merge_arr[SIZE];
	int i, j, k;
	i = left;
	j = mid + 1;
	k = left;

	while (i <= mid && j <= right)
	{
		if (list[i] < list[j])
			merge_arr[k++] = list[i++];
		else
			merge_arr[k++] = list[j++];
	}

	while (i <= mid)
		merge_arr[k++] = list[i++];

	while (j <= right)
		merge_arr[k++] = list[j++];

	for (int i = left; i <= right; i++)
		list[i] = merge_arr[i];
}

void merge_sort_func(int list[], int left, int right)
{
	int mid;

	if (left < right)
	{
		mid = (left + right) / 2;
		merge_sort_func(list, left, mid);
		merge_sort_func(list, mid + 1, right);
		merge(list, left, mid, right);
	}
}

void merge_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	merge_sort_func(arr, 0, SIZE - 1);

	print(4, arr);
}

 

python 코드

import random
from timeit import default_timer as timer

def merge_sort(A, p, r):
    if p<r:
        q = (p+r)// 2
        merge_sort(A, p, q)
        merge_sort(A, q+1, r)
        merge(A, p, q, r)

def merge(A, p, q, r):
    i = p
    j = q+1
    t = 0
    tmp = A[:]
    while i<=q and j<=r:
        if A[i] <= A[j]:
            tmp[t] = A[i]
            i+=1
        else :
            tmp[t] = A[j]
            j+=1
        t+=1

    while i<=q:
        tmp[t] = A[i]
        t+=1
        i+=1

    while j<=r:
        tmp[t] = A[j]
        t+=1
        j+=1

    i = p
    t = 0

    while i<=r:
        A[i] = tmp[t]
        i+=1
        t+=1

def test(A):
    for i in range(1, len(A)):
        if A[i - 1] > A[i]:
            return False
    return True

x = random.sample(range(10000), 100)
start = timer()
merge_sort(x, 0, len(x)-1)
print(timer() - start)
print(x)
print(test(x))

 

퀵 정렬

시간 복잡도

$ O(nlogn) $

 

Worst

worst case는 한 개씩 쪼개지는 경우이다.

퀵 정렬의 경우 n개의 데이터가 있으면 피벗을 제외하고 n-1개의 데이터들과 비교하게 되므로 

$ O(n^2) $

$ T(n) = T(n-1) + c_1n+c_2 $ 

 

Best

best case는 반씩 쪼개지는 경우이다.

$ T(n) = 2T({n \over 2}) + c_1n + c_2 $

마스터 정리 유형 3으로 시간 복잡도를 계산할 수 있다.

 

* 마스터 정리 유형 3 근사 버전: 

$ T(n) = aT({n \over b}) + f(n) $

$ h(n) = n^{log_ba}$ 라고 할 때, ${f(n) \over h(n)} = \Theta (1)$ 이면 $T(n) = \Theta (h(n)logn) $

 

따라서, 

$ T(n) = 2T({n \over 2}) + c_1n+c_2 , a = 2, b = 2, f(n) = c_1n+c_2 \\ h(n) = n^{log_2 2} $

$ {c_1n+c_2 \over n^{log_2 2}} = \Theta (1) $ 이므로 $T(n) = \Theta (n^{log_2 2} logn) = \Theta (nlogn)$

 

Average

$ T(n) = T(i-1) + T(n-i) + c_1n + c_2 $

$ T(n) = {1 \over n} \sum_{i=1}^n (T(i-1) + T(n-i)) + c_1n+ c_2 $

$ T(n) = {2 \over n} \sum_{k=0}^{n-1} T(k) + c_1n +c_2 $

$ T(n) = \Theta (nlogn) $

 

퀵 정렬이란?

pivot을 기준으로 pivot보다 작으면 왼쪽, 크면 오른쪽으로 정렬.

왼쪽, 오른쪽 배열에 대해서도 똑같이 진행

 

https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html

c++ <algorithm>의 sort() 함수의 기반이 되는 알고리즘

 

c++ 코드

void quick_sort_func(int list[], int left, int right)
{
	if (left >= right) return;

	int pivot = left;
	int i = left + 1, j = right;

	while (i <= j)
	{
		while (i <= right && list[pivot] > list[i]) i++;
		while (j >= left && list[pivot] < list[j]) j--;

		if (i > j) {
			int temp = list[j];
			list[j] = list[pivot];
			list[pivot] = temp;
		}
		else {
			int temp = list[i];
			list[i] = list[j];
			list[j] = temp;
		}
	}


	quick_sort_func(list, left, j - 1);
	quick_sort_func(list, j + 1, right);

}

void quick_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	quick_sort_func(arr, 0, SIZE - 1);

	print(5, arr);
}

 

python 코드

import random
from timeit import default_timer as timer

def partition(A, p, r):
    x = A[r]
    i = p
    for j in range(p, r):
        if A[j] <= x:
            A[i], A[j] = A[j], A[i]
            i+=1

    A[i], A[r] = A[r], A[i]
    return i


def qsort(A, p, r):
    if p<r:
        q = partition(A,p,r)
        qsort(A, p, q-1)
        qsort(A, q+1, r)

def quick_sort(A):
    qsort(A, 0, len(A)-1)

def test(A):
    for i in range(1, len(A)):
        if A[i-1] > A[i]:
            return False
    return True

x = random.sample(range(10000), 100)
print(x)
start = timer()
quick_sort(x)
print(timer() - start)
print(x)
print(test(x))

 

힙 정렬

시간 복잡도

$ O(nlogn) $

힙 정렬이란? 

최대 힙 / 최소 힙을 사용해서 정렬하는 것.

아래 코드에서는 최대 힙을 사용하였다.

최대 힙은 맨 위가 트리에서 가장 큰 원소이므로 최대 힙이 만들어지면 맨 위의 원소를 제외하고 또다시 최대 힙을 만드는 것을 반복하며 정렬한다.

 

c++ 코드

/*
* parent = (i-1)/2
* left = i*2 + 1
* right = i*2 + 2
*/

void make_heap(int n, int arr[])
{
	for (int i = (n - 2) / 2; i > -1; i--)
	{
		int now = i;
		int left = i * 2 + 1, right = i * 2 + 2;
		int child = left;
		if (arr[left] < arr[right] && right < n) child = right;

		while (arr[child] > arr[now] && child < n)
		{
			int temp = arr[now];
			arr[now] = arr[child];
			arr[child] = temp;

			now = child;
			left = now * 2 + 1, right = now * 2 + 2;
			if (arr[left] < arr[right] && right < n) child = right;

		}
	}
}

void heap_sort(int n)
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	make_heap(n, arr);
	
	for (int i = n - 1; i > -1; i--) {
		int temp = arr[i];
		arr[i] = arr[0];
		arr[0] = temp;
		make_heap(i, arr);
	}
	
	cout << "<heap_sort>" << endl;
	for (int i = 0; i < n; i++)
		cout << arr[i] << ' ';
}

 

기수 정렬 ( Radix sort )

시간 복잡도

$ O(n) $

n개의 원소를 k자릿수만큼 비교 => $ O(n) $을 $ k $번 반복 = $ O(n) $

 

기수 정렬이란? 

입력이 모두 K 자릿수 이하의 자연수인 경우 자릿수 별로 안정성을 유지하며 정렬한다.

이때, 값이 같은 원소는 정렬 전후 순서가 바뀌지 않는다.

 

 

c++ 코드

int A[15] = { 123,2154,222,4,283,1560,1061,2150,3154,33,2234,345,677,8911,1 };

void rsort(int m, int n)
{
	queue<int> bucket[10];

	for (int i = 0; i < n; i++)
	{
		int index = A[i] / pow(10, m);
		index %= 10;
		bucket[index].push(A[i]);
	}

	int index = 0;

	for (int i = 0; i < 10; i++)
	{
		while (!bucket[i].empty())
		{
			A[index++] = bucket[i].front();
			bucket[i].pop();
		}
	}
}

void radix_sort()
{
	
	int k = 4;

	for (int i = 0; i < 4; i++)
		rsort(i, 15);

	cout << "<radix_sort>" << endl;
	for (int i = 0; i < 15; i++)
		cout << A[i] << ' ';
	cout << endl;
}

 

python 코드

import random
from timeit import default_timer as timer

def rsort(A, m):
    buckets = [[] for _ in range(10)]
    for v in A:
        index = v // (10 ** m)
        index %= 10
        buckets[index].append(v)

    res = []
    for bucket in buckets:
        res.extend(bucket)
    return res

def radix_sort(A, k):
    for i in range(0, k):
        A = rsort(A, i)
    return A

def test(A):
    for i in range(1, len(A)):
        if A[i-1] > A[i]:
            return False
    return True

x = random.sample(range(10000),100)
start = timer()
x = radix_sort(x, 4)
print(timer() - start)
print(x)
print(test(x))

 

계수 정렬 ( Counting Sort )

시간 복잡도

$ O(n) $

 

$ \Theta (k) + \Theta (n) + \Theta (k) + \Theta (n) $

$ k \le O(n) $ => $ O(n) $

 

계수 정렬이란? 

  • 입력 값이 모두 k 이하인 경우
  • $ k \le O(n) $ 인 경우에만 의미가 있음
  • 같은 값이 몇 개인지 세어서 위치 결정

해당 수 앞에 몇 개가 존재하는지를 세어서 정렬

 

c++ 코드

void counting_sort()
{
	int n = 10;
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };
	int count[11] = {};
	int sortarr[10] = {};

	for (int i = 0; i < n; i++) 
		count[arr[i]]++;

	for (int i = 1; i < 11; i++) 
		count[i] += count[i - 1];

	for (int i = 0; i < n; i++)
	{
		count[arr[i]]--;
		sortarr[count[arr[i]]] = arr[i];
	}

	cout << "<counting_sort>" << endl;
	for (int i = 0; i < n; i++)
		cout << sortarr[i] << ' ';
	cout << endl;
}

 

python 코드

import random
from timeit import default_timer as timer

def counting_sort(A, k):
    B = [0] * len(A)
    C = [0] * (k+1)
    for v in A:
        C[v] += 1
    for i in range(1, k+1):
        C[i] += C[i-1]
    for i in range(len(A)-1, -1, -1):
        v = A[i]
        C[v] -= 1
        B[C[v]] = v
    return B

def test(A):
    for i in range(1, len(A)):
        if A[i-1] > A[i]:
            return False
    return True

x = random.choices(range(50), k=100)
start = timer()
print(x)
x = counting_sort(x, 49)
print(timer() - start)
print(x)
print(test(x))

 

팀 정렬

팀 정렬이란? 

  • 2002년 Tim Peters 가 개발
  • 파이썬 버전 2.3부터 표준 정렬 알고리즘으로 사용
  • 데이터가 정렬된 정도에 따라 삽입 정렬과 병합 정렬 사이를 전환하는 적응형 알고리즘

 

 

시간복잡도 비교

 

전체 코드

#include <iostream>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

#define SIZE 10

void print(int option, int list[])
{
	switch (option) {
	case 1:
		cout << "<selection sort>" << "\n";
		break;
	case 2:
		cout << "<bubble sort>" << "\n";
		break;
	case 3:
		cout << "<insertion sort>" << "\n";
		break;
	case 4:
		cout << "<merge sort>" << "\n";
		break;
	case 5:
		cout << "<quick sort>" << "\n";
		break;
	}

	for (int i = 0; i < SIZE; i++)
		cout << list[i] << ' ';

	cout << "\n";
}

void selection_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	int largest_index;

	for (int i = SIZE - 1; i > -1; i--)
	{
		largest_index = i;
		for (int j = i; j > -1; j--)
		{
			if (arr[largest_index] < arr[j])
				largest_index = j;
		}
		int temp = arr[largest_index];
		arr[largest_index] = arr[i];
		arr[i] = temp;
	}

	print(1, arr);
}

void bubble_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	bool flag = false; // stop option

	for (int i = SIZE - 1; i > -1; i--)
	{
		for (int j = 0; j < i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = true;
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		// stop option
		if (!flag) break;
	}

	print(2, arr);
}

void insertion_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	for (int i = 1; i < SIZE; i++)
	{
		int loc = i - 1; //0
		int now = arr[i]; //3
		while (loc >= 0 && now < arr[loc])
		{
			arr[loc + 1] = arr[loc];
			loc--;
		}
		arr[loc + 1] = now;

	}

	print(3, arr);
}


////////////////////////////////////////////////////////////////
void merge(int list[], int left, int mid, int right)
{
	int merge_arr[SIZE];
	int i, j, k;
	i = left;
	j = mid + 1;
	k = left;

	while (i <= mid && j <= right)
	{
		if (list[i] < list[j])
			merge_arr[k++] = list[i++];
		else
			merge_arr[k++] = list[j++];
	}

	while (i <= mid)
		merge_arr[k++] = list[i++];

	while (j <= right)
		merge_arr[k++] = list[j++];

	for (int i = left; i <= right; i++)
		list[i] = merge_arr[i];
}

void merge_sort_func(int list[], int left, int right)
{
	int mid;

	if (left < right)
	{
		mid = (left + right) / 2;
		merge_sort_func(list, left, mid);
		merge_sort_func(list, mid + 1, right);
		merge(list, left, mid, right);
	}
}

void merge_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	merge_sort_func(arr, 0, SIZE - 1);

	print(4, arr);
}

//////////////////////////////////////////////////////////////
void quick_sort_func(int list[], int left, int right)
{
	if (left >= right) return;

	int pivot = left;
	int i = left + 1, j = right;

	while (i <= j)
	{
		while (i <= right && list[pivot] > list[i]) i++;
		while (j >= left && list[pivot] < list[j]) j--;

		if (i > j) {
			int temp = list[j];
			list[j] = list[pivot];
			list[pivot] = temp;
		}
		else {
			int temp = list[i];
			list[i] = list[j];
			list[j] = temp;
		}
	}


	quick_sort_func(list, left, j - 1);
	quick_sort_func(list, j + 1, right);

}

void quick_sort()
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	quick_sort_func(arr, 0, SIZE - 1);

	print(5, arr);
}
//////////////////////////////////////////////////////////////

/*
* parent = (i-1)/2
* left = i*2 + 1
* right = i*2 + 2
*/

void make_heap(int n, int arr[])
{
	for (int i = (n - 2) / 2; i > -1; i--)
	{
		int now = i;
		int left = i * 2 + 1, right = i * 2 + 2;
		int child = left;
		if (arr[left] < arr[right] && right < n) child = right;

		while (arr[child] > arr[now] && child < n)
		{
			int temp = arr[now];
			arr[now] = arr[child];
			arr[child] = temp;

			now = child;
			left = now * 2 + 1, right = now * 2 + 2;
			if (arr[left] < arr[right] && right < n) child = right;

		}
	}
}

void heap_sort(int n)
{
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };

	make_heap(n, arr);
	
	for (int i = n - 1; i > -1; i--) {
		int temp = arr[i];
		arr[i] = arr[0];
		arr[0] = temp;
		make_heap(i, arr);
	}
	
	cout << "<heap_sort>" << endl;
	for (int i = 0; i < n; i++)
		cout << arr[i] << ' ';
	cout << endl;
}

//////////////////////////////////////////////////////////
int A[15] = { 123,2154,222,4,283,1560,1061,2150,3154,33,2234,345,677,8911,1 };

void rsort(int m, int n)
{
	queue<int> bucket[10];

	for (int i = 0; i < n; i++)
	{
		int index = A[i] / pow(10, m);
		index %= 10;
		bucket[index].push(A[i]);
	}

	int index = 0;

	for (int i = 0; i < 10; i++)
	{
		while (!bucket[i].empty())
		{
			A[index++] = bucket[i].front();
			bucket[i].pop();
		}
	}
}

void radix_sort()
{
	int k = 4;

	for (int i = 0; i < 4; i++)
		rsort(i, 15);

	cout << "<radix_sort>" << endl;
	for (int i = 0; i < 15; i++)
		cout << A[i] << ' ';
	cout << endl;
}

/////////////////////////////////////////////////////////////
void counting_sort()
{
	int n = 10;
	int arr[10] = { 1,3,5,6,8,2,4,10,7,9 };
	int count[11] = {};
	int sortarr[10] = {};

	for (int i = 0; i < n; i++) 
		count[arr[i]]++;

	for (int i = 1; i < 11; i++) 
		count[i] += count[i - 1];

	for (int i = 0; i < n; i++)
	{
		count[arr[i]]--;
		sortarr[count[arr[i]]] = arr[i];
	}

	cout << "<counting_sort>" << endl;
	for (int i = 0; i < n; i++)
		cout << sortarr[i] << ' ';
	cout << endl;
}

int main()
{
	selection_sort();
	bubble_sort();
	insertion_sort();
	merge_sort();
	quick_sort();
	heap_sort(10);
	radix_sort();
	counting_sort();

	return 0;
}

 

'22-1학기 > 알고리즘' 카테고리의 다른 글

5. 검색 트리  (0) 2022.04.06
4. 선택 알고리즘  (0) 2022.04.06
2. 점화식과 알고리즘 복잡도 분석  (0) 2022.03.14
1. 알고리즘 표기법  (0) 2022.03.14

점화식

점화식이란?

  • 어떤 함수를 자신과 똑같은 함수를 이용해 나타내는 것

점화식의 점근적 분석 방법

  • 반복 대치 : 반복해서 집어 넣는다.

  • 추정 후 증명 : 추정 후 경계 조건 + 귀납적 가정과 전개를 이용해 증명한다. 이때 잘못 증명하지 않도록 주의해야 한다.
    • 예시  
      • 추정 : $T(n) \le 2T({n \over 2}) + n$ 의 점근적 복잡도는 $T(n) = O(nlogn)$이다. 즉, 충분히 큰 $n$에 대하여 $T(n) \le\ cnlogn$인 양의 상수 $c$가 존재한다.
      • 증명  : 
        • 경계 조건 : $T(2) \le c2log2$인 $c$가 존재  <------필수!
        • 귀납적 가정과 전개 : $ n \over 2 $에 대해 추정이 맞으면, 즉 $T({n \over 2}) \le c{n \over 2}log{n \over 2}$ 이라면 $T(n) \le 2T({n \over 2}) + n \\ \le 2c({n \over 2})log{n \over 2} + n = cnlogn - cnlog2 + n \\ = cnlogn + n(1-clog2) \le cnlogn $ $( c \ge {1 \over log2} )$
  • 마스터 정리 : $ T(n) = aT(\frac{n}{b} ) + f(n) $ 꼴을 가진 재귀식에 적용된다. 이때 $ a, b, f(n) $은 알고 있다.

실제 정의
근사 버전

 

'22-1학기 > 알고리즘' 카테고리의 다른 글

5. 검색 트리  (0) 2022.04.06
4. 선택 알고리즘  (0) 2022.04.06
3. 정렬  (0) 2022.03.22
1. 알고리즘 표기법  (0) 2022.03.14

Θ-표기법

  • 상한과 하한이 존재
  • f(n)의 최고차항이 $n^r$ 이면 계수 무시.

 

O-표기법

  • 상한만 존재
  • $\Theta$ 표기법에 속함
  • f(n)의 최고차항이 $n^r$ 이하이면 계수 무시.

 

 

 

Ω-표기법

  • 하한만 존재
  • $\Theta$ 표기법에 속함
  • f(n)의 최고차항이 $n^r$ 이상이면 계수 무시.

 

기타

  • o-표기법 (리틀 오): f(n)의 최고차항이 $n^r$ 미만이면 계수 무시
  • ω-표기법 (리틀 오메가) : f(n)의 최고차항이 $n^r$ 초과이면 계수 무시

 

 

 

 

 

'22-1학기 > 알고리즘' 카테고리의 다른 글

5. 검색 트리  (0) 2022.04.06
4. 선택 알고리즘  (0) 2022.04.06
3. 정렬  (0) 2022.03.22
2. 점화식과 알고리즘 복잡도 분석  (0) 2022.03.14

+ Recent posts