192
Algorithms & Data Structure 김영기 책임 네트워크 사업부 SE Lab

알고리즘과 자료구조

  • Upload
    -

  • View
    10.865

  • Download
    2

Embed Size (px)

DESCRIPTION

Data Structures and Alogrithms

Citation preview

Page 1: 알고리즘과 자료구조

Algorithms & Data Structure

김영기 책임네트워크 사업부SE Lab

Page 2: 알고리즘과 자료구조

Contents

1 Data Structure

1.1 알고리즘 성능 측정

1.2 순환 (Recursion)

1.4 스택(Stack)

2.1 정렬 (Sort)

1.3 리스트(List)

2.2 트리 (Tree)

1.5 큐(Queue)

2 Algorithm

1.6 우선 순위 큐(Priority queue)

2.3 그래프(Graph)

2.5 탐색(Search)

2.4 해싱(Hashing)

2.7 NP-완비

2.6 동적 프로그래밍 (Dynamic Programming)

Page 3: 알고리즘과 자료구조

알고리즘 성능 측정

Page 4: 알고리즘과 자료구조

알고리즘 성능 분석

알고리즘 성능 분석 기법

수행 시간 측정

두 개의 알고리즘의 실제 수행 시간을 측정한다.

실제로 구현이 필요

동일 하드웨어를 사용하여 측정해야 함

알고리즘 복잡도 분석

직접 구현하지 않고서도 수행시간을 분석하는 방법

알고리즘이 수행하는 연산의 횟수를 측정하여 비교한다.

일반적으로 연산의 횟수는 n의 함수

시간 복잡도(time complexity) : 수행 시간 분석

공간 복잡도(space complexity) : 수행 시 필요로 하는 메모리 공간 분석

연산의 수 = 83n+2

연산의 수 =265n2 +6

프로그램 A 프로그램 B

워드2008 워드2012

Page 5: 알고리즘과 자료구조

복잡도

알고리즘 복잡도 분석

시간 복잡도는 알고리즘을 이루고 있는 연산들이 몇 번 수행되는지 숫자로 표시

산술 연산, 대입 연산, 비교 연산, 이동 연산의 기본적인 연산

수행 시간이 입력의 크기에 따라 변화면 안 된다. 기본 연산만

알고리즘이 수행하는 연산의 개수를 계산하여 두 개의 알고리즘 비교 가능

연산의 수행횟수는 고정된 숫자가 아니라 입력의 개수 n에 대한 함수

시간복잡도 함수라고 하고 T(n) 이라고 표기

알고리즘 A 알고리즘 B 알고리즘 C

대입연산 1 n + 1 n*n + 1

덧셈연산 n n*n

곱셈연산 1

나눗셈연산

Total 2 2n + 1 2n2 + 1

입력의 개수 n

연산

의횟

수알고리즘 A

알고리즘 B

알고리즘 C

Page 6: 알고리즘과 자료구조

알고리즘 차수 비교

알고리즘 차수 비교

가정

f와 g를 자연수 집합 N에서 실수 R로의 함수,

즉 f, g : N R

R+ 는 양의 실수 집합

O (big oh)

f보다 차수가 작거나 같은 함수 전체의 집합

(big theta)

f와 같은 차수를 갖는 함수들의 집합

Ω (big omega)

f보다 차수가 크거나 같은 함수 전체의 집합

O(f) = {gcR+, n0N, nn0 , g(n)cf(n)}

(f) = {gg O(f), fO(g)}

(f) = {gcR+, n0N, nn0 , g(n) cf(n)}

f

(f): 적어도 f보다빠르게 증가하는 함수들

(f): f와 같은 비율로증가하는 함수들즉, (f) = (f) O(f)

O(f): f보다 작은 비율로증가하는 함수들

Page 7: 알고리즘과 자료구조

Big O 표기법

Big O 표기법

자료의 개수가 많은 경우 차수가 가장 큰 항의 영향이 크고, 다른 항들은 상대적

으로 무시 가능 시간 복잡도 함수에서 가장 영향이 큰 항만을 고려해도 충분 !!!

Big O 표기법 : 연산의 횟수를 점근적(대략적)으로 표기한다.

주어진 함수의 가장 높은 항만 끄집어 내고, 계수를 1로 하면 된다.

예제

g(n) = 5이면 0(1)이다.

g(n) = 2n+5이면 이 알고리즘은 0(n)이다.

g(n) = 3n2+100이면, O(n2)이다.

G(n) = 5*2n+10n+100이면 O(2n)이다.

n=1000인 경우

T(n)= n2 + n + 1

99% 1%

■ 수학적 정의

임의의 상수 N0와 c가 있어서 N≥N0인 N에 대해서,

c⋅f(N) ≥ g(N)이 성립하면 g(N) = O(f(N))이라 한다.

Page 8: 알고리즘과 자료구조

Big O 표기법

Big O 표기법 종류

O(1) : 상수형

O(logn) : 로그형

O(n) : 선형

O(nlogn) : 로그선형

O(n2) : 2차형

O(n3) : 3차형

O(nk) : k차형

O(2n) : 지수형

O(n!) : 팩토리얼형

시간복잡도n

1 2 4 8 16 32

1 1 1 1 1 1 1

logn 0 1 2 3 4 5

n 1 2 4 8 16 32

nlogn 0 2 8 24 64 160

n2 1 4 16 64 256 1024

n3 1 8 64 512 4096 32768

2n 2 4 16 256 65536 4294967296

n! 1 2 24 40326 20922789888000 26313×1033

1 2 4 8 16 32 64 128 256

65536

32768

16384

8192

4096

2048

1024

512

256

128

64

32

16

8

4

2

f(n)=2n

f(n)=n3

f(n)=n2

f(n)=nlog2n

f(n)=n

f(n)=log2n

n

f(n)

Page 9: 알고리즘과 자료구조

Big 표기법, Big 표기법

Big 표기법

Big 는 함수의 하한을 표시한다

n ≥ 5이면, 2n+1 <10n 이므로 n = (n)

Big 표기법

Big 는 함수의 하한인 동시에 상한을 표시

f(n)=O(g(n))이면서, f(n)= (g(n))이면 f(n)= (n)이다

n ≥ 1이면, n ≤ 2n+1 ≤ 3n이므로 2n+1 = (n)

■ 수학적 정의

모든 n≥n0에 대하여 c1|g(n)| ≤ |f(n)| ≤ c2|g(n)|을 만족하는

3개의 상수 c1, c2와 n0가 존재하면 f(n)=θ(g(n))이다.

■ 수학적 정의

모든 n≥n0에 대하여 |f(n)| ≥ c|g(n)|을 만족하는 2개의 상수 c와

n0가 존재하면 f(n)=Ω(g(n))이다

입력의 개수 n

연산

의수

상한

하한

n0

f(n)

0(f(n))

(f(n))

Page 10: 알고리즘과 자료구조

Best, Average, Worst Cases

알고리즘의 수행 시간

입력 자료의 집합에 따라 다를 수 있다.

정렬 알고리즘의 수행 시간은 입력 집합에 따라 다를 수 있다.

Case 구분

최선의 경우(best case): 수행 시간이 가장 빠른 경우

실제로 의미가 없는 경우가 많다.

평균의 경우(average case): 수행시간이 평균적인 경우

계산하기가 상당히 어렵다.

최악의 경우(worst case): 수행 시간이 가장 늦은 경우

가장 널리 사용되며, 계산하기 쉽다.

응용에 따라 중요한 의미를 가질 수 있다.

최악의 경우

최선의 경우

평균적인 경우

A B C D E F G

입력 집합

수행

시간

100

50

Page 11: 알고리즘과 자료구조

순환 (Recursion)

Page 12: 알고리즘과 자료구조

순환(Recursion)

순환이란 …

어떤 알고리즘이나 함수가 자기 자신을 호출하여 문제를 해결하는 기법

예제 : Factorial의 정의

Factorial의 C언어 구현

n! =1 n=0

n* (n-1)! 1>=0

int factorial(int n)

{

if(n==1) return(1);

else return (n*factorial(n-1));

}

3!은? 3!=3*2! 1!=12!=2*1!

2!는?3!는? 1!는?

26 1

factorial(3) = 3*factorial(2)

= 3*2*factorial(1)

= 3*2*1

= 3*2

= 6

Page 13: 알고리즘과 자료구조

순환(Recursion)

순환 호출의 내부적인 구현

Factorial(2){

if(2==1) return 1;else return(2*factorial(1));

}

Factorial(2){

if(2==1) return 1;else return(2*factorial(1));

}

Factorial(3){

if(3==1) return 1;else return(3*factorial(2));

}

Factorial(3){

if(3==1) return 1;else return(3*factorial(2));

}

Factorial(3){

if(3==1) return 1;else return(3*factorial(2));

}

Factorial(2){

if(1==1) return 1;…

}

Factorial(2){

…else return(2*1);

}

Factorial(3){

if(3==1) return 1;else return(3*factorial(2));

}

Factorial(3){

if(3==1) return 1;else return(3*2);

}

(1) (2) (3)

(4) (5)

int factorial(int n)

{

if(n==1) return(1);

else return (n*factorial(n-1));

}

순환 알고리즘의 구현

순환을멈추는 부분

순환호출을 하는 부분

Page 14: 알고리즘과 자료구조

순환(Recursion)

순환과 반복

프로그래밍 언어의 되풀이 방법에는 순환과 반복이 있다.

많은 경우, 순환은 반복으로 변경이 가능하다.

순환은 반복에 비해 명확하고 간결하게 표현되나, 수행 속도는 느린 단점이 있다.

알고리즘 설명은 순환으로, 구현은 반복으로 하는 경우가 많다.

반복적인 Factorial 계산 프로그램과 순환 원리

3!=3*2! 1!=12!=2*1!

2!는? 1!는?

2 1

1!=12!=2*13!=3*2*14!=4*3*2*1

반복순환

반복의 구현은

for, while을 이용

int factorial_iter(int n)

{

int k, v=1;

for(k=n; k>0; k--)

v=v*k;

return (v);

}

순환 코드는

수행 시간에 약점

int factorial(int n)

{

if(n==1) return(1);

else return (n * factorial(n-1));

}

문제 부분

해결 부분

순환의 원리는분할 정복(Divide and conquer) 즉, 문제의 크기를작게 만든다는 것이다.

Page 15: 알고리즘과 자료구조

거듭 제곱값 계산

𝒙𝒏을 구하는 함수

순환의 경우 호출 할 때마다 문제의 크기가 반으로 줄어든다.

double power (double x, int n)

{

int i ;

double r = 1.0 ;

for(i=0; i<n; i++)

r = r*x ;

return (r) ;

}

double power (double x, int n)

{

if( n==0 ) return 1;

else if ( (n%2) == 0)

return power(x*x, n/2);

else return x*power(x*x, (n-1)/2);

}

반 복 순 환

2500계산 1000000번 수행 수행시간 : 7.11 초 2500계산 1000000번 수행 수행시간 : 0.47 초

반복적인 함수 power 순환적인 함수 power

시간 복잡도 O(n) O(log2n)

실제 수행 속도 7.17초 0.47 초

Page 16: 알고리즘과 자료구조

Fibonacci sequence

피보나치 수열의 계산

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …

0 n = 0

1 n = 1

fib(n-2) + fib(n-1) otherwise

fib(n) =

Fib(0) Fib(1) Fib(0) Fib(1) Fib(0) Fib(1) Fib(1) Fib(2)

Fib(0) Fib(1)

Fib(1) Fib(2)Fib(0) Fib(1) Fib(1) Fib(2) Fib(2) Fib(3)

Fib(2) Fib(3) Fib(3) Fib(4)

Fib(4) Fib(5)

Fib(6)■ 함수 호출

n=25 : 25만

n=30 : 300백만

Page 17: 알고리즘과 자료구조

Fibonacci sequence

피보나치 수열 계산 함수

이 경우 어느 함수가 더 효율적인가 ?

int fib (int n)

{

if (n<1) return n;

else{

int i, tmp, current=1; last=0;

for(i=2; i<=n; i++){

tmp = current;

current += last;

last = tmp;

}

return current;

}

int fib(int n)

{

if(n==0) return 0;

if(n==1) return 1;

return (fib(n-1) + fib(n-1));

}

반 복 순 환

Page 18: 알고리즘과 자료구조

The Tower of Hanoi

하노이 탑

이동 조건

한 번에 하나의 원판만 이동할 수 있다.

맨 위에 있는 원판만 이동할 수 있다.

크기가 작은 원판 위에 큰 원판이 쌓일 수 없다.

중간의 막대를 임시적으로 이용할 수 있으나 앞의 조건들을 지켜야 한다.

하노이 탑 확장

원판이 늘어나는 경우

막대의 수가 늘어나는 경우

Page 19: 알고리즘과 자료구조

The Tower of Hanoi

하노이 탑 문제에 대한 의사코드

순환이 되는 부분을 찾는 것이 Key !!!

void hanoi_tower(int n, char from, char tmp, char to)

{

if(n==1) {

from에서 to로 원판을 옮긴다.

}

else{

① from의 맨밑에 원판을 제외한 나머지 원판들을 tmp로 옮긴다.

// hanoi_tower(n-1, from, to, tmp);

② from에 있는 한 개의 원판을 to로 옮긴다.

③ tmp의 원판들을 to로 옮긴다.

// hanoi_tower(n-1, tmp, from, to);

}

}

원래 문제의축소된 형태이다.

Page 20: 알고리즘과 자료구조

The Tower of Hanoi

하노이 탑 문제에 대한 전체 코드

#include <stdio.h>

void hanoi_tower(int n, char from, char tmp, char to)

{

if(n==1) {

printf(“원판 1을 %c 에서 %c 로 옮긴다 \n”, from, to);

}

else{

hanoi_tower(n-1, from, to, tmp);

printf(“원판 %d 를 %c 에서 %c 로 옮긴다 \n”, n, from, to);

hanoi_tower(n-1, tmp, from, to);

}

}

int main(){

hanoi_tower(4, ‘A’, ‘B’, ‘C’);

return 0;

}

원판 1를 A에서 B로 옮긴다. 원판 2를 A에서 C로 옮긴다. 원판 1를 B에서 C로 옮긴다. 원판 3를 A에서 B로 옮긴다. 원판 1를 C에서 A로 옮긴다. 원판 2를 C에서 B로 옮긴다. 원판 1를 A에서 B로 옮긴다. 원판 4를 A에서 C로 옮긴다. 원판 1를 B에서 C로 옮긴다. 원판 2를 B에서 A로 옮긴다. 원판 1를 C에서 A로 옮긴다. 원판 3를 B에서 C로 옮긴다. 원판 1를 A에서 B로 옮긴다. 원판 2를 A에서 C로 옮긴다. 원판 1를 B에서 C로 옮긴다.

Page 21: 알고리즘과 자료구조

리스트(List)

Page 22: 알고리즘과 자료구조

리스트(List)

리스트, 선형 리스트

목록이나 도표처럼 여러 데이터를 관리할 수 있는 자료형을 추상화

순서를 가진 항목들의 모임

※ 집합: 항목간의 순서의 개념이 없음

데이터 삽입, 삭제, 검색 등 필요 작업을 가함

스택과 큐는 리스트의 특수한 경우에 해당

리스트의 연산

),...,,( 110 nitemitemitemL

연 산 설 명

Insert(Position, Data) 데이터를 해당 위치(Position)에 넣기

Delete(Position) 해당 위치(Position)의 데이터를 삭제

Retrieve(Position, Data) 해당 위치(Position)의 데이터를 Data 변수에 복사

Create( ) 빈 리스트 만들기

Destroy( ) 리스트 없애기

IsEmpty( ) 빈 리스트인지 확인 (아무 것도 안 적혔는지 확인)

Length( ) 몇 개의 항목인지 계산 (몇 개나 적혔는지 세기)

Page 23: 알고리즘과 자료구조

리스트 구현 방법

배열을 이용하는 방법

구현이 간단

삽입, 삭제 시 오버헤드

항목의 개수 제한

연결리스트를 이용하는 방법

구현이 복잡

삽입, 삭제가 효율적

크기가 제한되지 않음

A B C D n

A B C배열 이용

연결 리스트 이용

하나의 노드가 데이터와

링크로 구성되어 있고 링크가

노드들을 연결한다

Page 24: 알고리즘과 자료구조

Array List

1차원 배열에 항목들을 순서대로 저장

L=(A, B, C, D, E)

삽입연산

삽입위치 다음의 항목들을 이동하여야 함.

삭제연산

삭제위치 다음의 항목들을 이동하여야 함

A B C D E

0 1 2 3 4 5 6 7 8 9

A B C D E

0 1 2 3 4 5 6 7 8 9

A B D E

0 1 2 3 4 5 6 7 8 9

N

N

Page 25: 알고리즘과 자료구조

ArrayListType의 구현

구현 코드

항목들의 타입은 element로 정의

list라는 1차원 배열에 항목들을 차례대로 저장

length에 항목의 개수 저장

typedef int element;

typedef struct {

int list[MAX_LIST_SIZE]; // 배열 정의

int length; // 현재 배열에 저장된 항목들의 개수

} ArrayListType;

// 리스트 초기화

void init(ArrayListType *L)

{

L->length = 0;

}

int is_empty(ArrayListType *L) // 리스트가 비어있으면 1, 아니면 0을 반환

{

return L->length == 0;

}

int is_full(ArrayListType *L) // 리스트가 가득 차 있으면 1을 반환, 아니면 0을 반환

{

return L->length == MAX_LIST_SIZE;

}

Page 26: 알고리즘과 자료구조

ArrayListType의 구현

삽입과 삭제

// position: 삽입하고자 하는 위치

// item: 삽입하고자 하는 자료

void add(ArrayListType *L, int position, element item)

{

if( !is_full(L) && (position >= 0) &&

(position <= L->length) ){

int i;

for(i=(L->length-1); i>=position;i--)

L->list[i+1] = L->list[i];

L->list[position] = item;

L->length++;

}

}

// position: 삭제하고자 하는 위치

// 반환값: 삭제되는 자료

element delete(ArrayListType *L, int position)

{

int i;

element item;

if( position < 0 || position >= L->length

)

error("위치 오류");

item = L->list[position];

for(i=position; i<(L->length-1);i++)

L->list[i] = L->list[i+1];

L->length--;

return item;

}

Page 27: 알고리즘과 자료구조

연결 리스트(Linked List)

연결 리스트

리스트의 항목들을 노드(node)라고 하는 곳에 분산하여 저장

다음 항목을 가리키는 주소도 같이 저장

노드 (node) : <항목, 주소> 쌍

노드는 데이터 필드와 링크 필드로 구성

데이터 필드 – 리스트의 원소, 즉 데이터 값을 저장하는 곳

링크 필드 – 다른 노드의 주소 값을 저장하는 장소 (포인터)

메모리에서의 노드의 물리적 순서가 리스트의 논리적 순서와 일치할 필요 없음

A

B

C

D

E

Page 28: 알고리즘과 자료구조

연결 리스트의 장단점

구조

노드 = 데이터 필드 + 링크 필드

Head Pointer

리스트의 첫 번째 노드를 가르키는 변수

노드의 생성

필요 시 동적 메모리 생성 이용

장점

삽입, 삭제가 보다 용이하다.

연속된 메모리 공간이 필요 없다.

크기 제한이 없다

단점

구현이 어렵다.

오류가 발생하기 쉽다.

A

B

C

D

E

A

B

C

D

E

X

N

X

X

메인 메모리

메인 메모리

삽입 연산

삭제 연산

Data LinkHeadPointer

Page 29: 알고리즘과 자료구조

연결 리스트의 종류

단순 연결 리스트

원형 연결 리스트

이중 연결 리스트

A B C D NULLHead Pointer

A B C DHead Pointer

A B C DHead Pointer

Page 30: 알고리즘과 자료구조

단순 연결 리스트

단순 연결 리스트

하나의 링크 필드를 이용 연결

마지막 노드의 링크 값은 NULL

A B C D NULLHead Pointer

insert_node(L, before, new)

if L = NULLthen L←newelse new.link←before.link

before.link←new

C D C E D

E

삽입 연산

remove_node(L, before, removed)

if L ≠ NULLthen before.link←removed.link

destroy(removed)

C E D

C E D

before

before

before before

removed

after

after

after

new

new after

removed

Page 31: 알고리즘과 자료구조

단순 연결 리스트 구현

단순 연결 리스트 구현

데이터 필드: 구조체로 정의

링크 필드: 포인터 사용

노드의 생성: 동적 메모리 생성 라이브러리 malloc 함수이용

데이터 필드와 링크 필드 설정

두 번째 노드 생성과 첫 번째 노드와의 연결

typedef int element;typedef struct ListNode {

element data;struct ListNode *link;

} ListNode;

p1

ListNode *p1;p1 = (ListNode *)malloc(sizeof(ListNode));

p1->data = 10;p1->link = NULL;

10 np1

ListNode *p2;p2 = (ListNode *)malloc(sizeof(ListNode));p2->data = 20;p2->link = NULL;p1->link = p2;

10p1 20 n

헤드포인터(head pointer):

연결 리스트의 맨 처음 노드를

가리키는 포인터

Page 32: 알고리즘과 자료구조

연결 리스트 연산

삽입

헤드포인터가 함수 안에서 변경되므로 헤드포인터의 포인터 필요

삽입의 3가지 경우

head가 NULL인 경우: 공백 리스트에 삽입

head가 NULL이라면 현재 삽입하려는 노드가 첫 번째 노드가 된다. 따라서 head의 값만 변경

p가 NULL인 경우: 리스트의 맨 처음에 삽입

일반적인 경우: 리스트의 중간에 삽입

new_node의 link에 p->link값을 복사한 다음, p->link가 new_node를 가리키도록 한다

void insert_node(ListNode **phead, ListNode *p, ListNode *new_node)

phead: 헤드 포인터 head에 대한 포인터p: 삽입될 위치의 선행노드를 가리키는 포인터, 이 노드 다음에 삽입된다.new_node: 새로운 노드를 가리키는 포인터

head가 NULL이라면 현재삽입하려는 노드가 첫 번째 노드가된다. 따라서 head의 값만 변경

p1

new_node

10p1 20 nx

new_node

Page 33: 알고리즘과 자료구조

연결 리스트 연산

삭제 연산

삭제의 2가지 경우

p가 NULL인 경우: 맨 앞의 노드를 삭제

연결 리스트의 첫 번째 노드를 삭제한다. 헤드포인터 변경

p가 NULL이 아닌 경우: 중간 노드를 삭제

removed 앞의 노드인 p의 링크가 removed 다음 노드를 가리키도록 변경

//phead: 헤드 포인터 head의 포인터//p: 삭제될 노드의 선행 노드를 가리키는 포인터//removed: 삭제될 노드를 가리키는 포인터

void remove_node(ListNode **phead, ListNode *p, ListNode *removed)

10 20 n11xlist

removed

10 20 n11list

removed

x

p

Page 34: 알고리즘과 자료구조

단순 연결 리스트 구현

삽입 연산 코드

삭제 연산 코드

// phead: 리스트의 헤드 포인터의 포인터// p : 선행 노드// new_node : 삽입될 노드void insert_node(ListNode **phead, ListNode *p, ListNode *new_node){

if( *phead == NULL ){ // 공백리스트인 경우new_node->link = NULL;*phead = new_node;

}else if( p == NULL ){ // p가 NULL이면 첫번째 노드로 삽입

new_node->link = *phead;*phead = new_node;

}else { // p 다음에 삽입

new_node->link = p->link;p->link = new_node;

}}

void remove_node(ListNode **phead, ListNode *p, ListNode *removed){

if( p == NULL )*phead = (*phead)->link;

elsep->link = removed->link;

free(removed);}

Page 35: 알고리즘과 자료구조

연결 리스트 연산

방문 연산

리스트 상의 노드를 순차적으로 방문

반복과 순환 기법 모두 사용 가능

void display(ListNode *head){

ListNode *p=head;while( p != NULL ){

printf("%d->", p->data);p = p->link;

}printf("\n");

}

void display_recur(ListNode *head){

ListNode *p=head;if( p != NULL ){

printf("%d->", p->data);display_recur(p->link);

}}

[반복 버전]

[순환 버전]

Page 36: 알고리즘과 자료구조

연결 리스트 연산

탐색 연산

특정한 데이터 값을 갖는 노드를 찾는 연산

A B C D NULLHead Pointer

p

ListNode *search(ListNode *head, int x){

ListNode *p;p = head;while( p != NULL ){

if( p->data == x ) return p; // 탐색 성공p = p->link;

}return p; // 탐색 실패일 경우 NULL 반환

}

Page 37: 알고리즘과 자료구조

연결 리스트 연산

합병 연산

2개의 리스트를 합하는 연산

A B C D nHead 1

J K L Q nHead 2

ListNode *concat(ListNode *head1, ListNode *head2){

ListNode *p;if( head1 == NULL ) return head2;else if( head2 == NULL ) return head1;else {

p = head1;while( p->link != NULL )

p = p->link;p->link = head2;return head1;

}}

Page 38: 알고리즘과 자료구조

연결 리스트 연산

역순 연산

리스트의 노드들을 역순으로 만드는 연산

A n B C D nHead

r q p

x x x

ListNode *reverse(ListNode *head){

// 순회 포인터로 p, q, r을 사용ListNode *p, *q, *r;p = head; // p는 역순으로 만들 리스트q = NULL; // q는 역순으로 만들 노드while (p != NULL){

r = q; // r은 역순으로 된 리스트. r은 q, q는 p를 차례로 따라간다.q = p;p = p->link;q->link =r; // q의 링크 방향을 바꾼다.

}return q; // q는 역순으로 된 리스트의 헤드 포인터

}

Page 39: 알고리즘과 자료구조

원형 연결 리스트

원형 연결 리스트

마지막 노드의 링크가 첫번째 노드를 가리키는 리스트

한 노드에서 다른 모든 노드로의 접근이 가능

보통 헤드포인터가 마지막 노드를 가리키게끔 구성

리스트의 처음이나 마지막에 노드를 삽입하는 연산이 단순 연결 리스트에 비하여 용이

A B C DHead

A B C D

Head 1

Page 40: 알고리즘과 자료구조

원형 리스트 연산

삽입 (처음에 삽입)

A B C D

Head

B

x②

// phead: 리스트의 헤드 포인터의 포인터// p : 선행 노드// node : 삽입될 노드

void insert_first(ListNode **phead, ListNode *node){

if( *phead == NULL ){*phead = node;node->link = node;

}else {

node->link = (*phead)->link;(*phead)->link = node;

}}

node

Page 41: 알고리즘과 자료구조

원형 리스트 연산

삭제 (끝에 삽입)

A B C D

Head

B

x②

node

// phead: 리스트의 헤드 포인터의 포인터// p : 선행 노드// node : 삽입될 노드

void insert_last(ListNode **phead, ListNode *node){

if( *phead == NULL ){*phead = node;node->link = node;

}else {

node->link = (*phead)->link;(*phead)->link = node;*phead = node;

}}

x③

Page 42: 알고리즘과 자료구조

이중 연결 리스트

이중 연결 리스트

하나의 노드가 선행 노드와 후속 노드에 대한 두 개의 링크를 가지는 리스트

링크가 양방향이므로 양방향으로 검색이 가능

단점은 공간을 많이 차지하고 코드가 복잡

실제 사용되는 이중연결 리스트의 형태

헤드노드+ 이중연결 리스트+ 원형연결 리스트

삽입이나 삭제 시 반드시 선행 노드가 필요

단순 연결 리스트의 문제점: 선행 노드를 찾기가 힘들다

헤드 노드(Head node)

데이터를 가지지 않고 단지 삽입, 삭제 코드를 간단하게 할 목적으로 만들어진 노드

헤드 포인터와의 구별 필요

공백상태에서는 헤드 노드만 존재

Head node

llink data rlink

typedef int element;typedef struct DlistNode {

element data;struct DlistNode *llink;struct DlistNode *rlink;

} DlistNode;

Page 43: 알고리즘과 자료구조

이중 연결 리스트

삽입

before

x

x③① ②④

// 노드 new_node를 노드 before의 오른쪽에 삽입한다.

void dinsert_node(DlistNode *before, DlistNode *new_node){

new_node->llink = before;new_node->rlink = before->rlink;before->rlink->llink = new_node;before->rlink = new_node;

}

Page 44: 알고리즘과 자료구조

이중 연결 리스트

삭제

// 노드 removed를 삭제한다.

void dremove_node(DlistNode *phead_node,DlistNode *removed)

{if( removed == phead_node ) return;removed->llink->rlink = removed->rlink;removed->rlink->llink = removed->llink;free(removed);

}

xx

xx

Page 45: 알고리즘과 자료구조

연결 리스트를 이용한 리스트 ADT 구현

How to ?

리스트 ADT의 연산을 연결리스트를 이용하여 구현

리스트 ADT의 add, delete 연산의 파라미터는 위치

연결리스트의 insert_node, remove_node의 파리미터는 노드 포인터

상황에 따라 연산을 적절하게 선택하여야 함

add(항목의 위치) delete(항목의 위치)

insert_node(노드 포인터) remove_node(노드 포인터)

리스트 ADT

연결 리스트

사용자

Page 46: 알고리즘과 자료구조

리스트 ADT 구현

리스트 선언

is_empty

get_length

typedef struct {ListNode *head; // 첫 번째 노드를 가르키는 헤드 포인터int length; // 연결 리스트 내 존재하는노드의 개수

} ListType;

ListType list1; // 리스트 ADT 생성

int is_empty(ListType *list){

if( list->head == NULL ) return 1;else return 0;

}

// 리스트의 항목의 개수를 반환한다.

int get_length(ListType *list){

return list->length;}

Page 47: 알고리즘과 자료구조

리스트 ADT 구현

add 연산

새로운 데이터를 임의의 위치에 삽입

항목의 위치를 노드 포인터로 변환해주는 함수 get_node_at 필요

// 리스트안에서 pos 위치의 노드를 반환한다.ListNode *get_node_at(ListType *list, int pos){

int i;ListNode *tmp_node = list->head;if( pos < 0 ) return NULL;for (i=0; i<pos; i++)

tmp_node = tmp_node->link;return tmp_node;

}

// 주어진 위치에 데이터를 삽입한다.void add(ListType *list, int position, element data){ListNode *p;if ((position >= 0) && (position <= list->length)){

ListNode*node= (ListNode *)malloc(sizeof(ListNode));if( node == NULL ) error("메모리 할당에러");node->data = data;p = get_node_at(list, position-1);insert_node(&(list->head), p, node);list->length++;

}}

Page 48: 알고리즘과 자료구조

리스트 ADT 구현

delete 연산의 구현

임의의 위치의 데이터를 삭제

항목의 위치를 노드 포인터로 변환해주는 함수 get_node_at 필요

// 주어진 위치의 데이터를 삭제한다.

void delete(ListType *list, int pos){

if (!is_empty(list) && (pos >= 0) && (pos < list->length)){ListNode *p = get_node_at(list, pos-1);remove_node(&(list->head),p,(p!=NULL)?p->link:NULL);list->length--;}

}

Page 49: 알고리즘과 자료구조

스택(Stack)

Page 50: 알고리즘과 자료구조

스택(Stack)

스택

선형리스트 구조의 특별한 형태로, 데이터의 삽입과 삭제가 리스트 한쪽 끝에서만

일어나는 자료구조

후입선출 (LIFO:Last-In First-Out)

가장 최근에 들어온 데이터가 가장 먼저 나감

A

C

삽입

(Push)

삭제

(Pop)

top

스택의 동작구조

B

bottom

요소

(element)

Page 51: 알고리즘과 자료구조

스택 추상데이터타입(ADT)

스택의 용도

에디터에서 되돌리기 기능, 함수 호출에서 복귀주소 기억

스택의 연산

객체: n개의 element형의 요소들의 선형 리스트

연산:

▪ create() ::=스택을 생성한다.

▪ is_empty(s) ::= 스택이 비어있는지를 검사한다.

▪ is_full(s) ::= 스택이 가득 찼는가를 검사한다.

▪ push(s, e) ::= 스택의 맨 위에 요소 e를 추가한다.

▪ pop(s) ::= 스택의 맨 위에 있는 요소를 삭제한다.

▪ peek(s) ::= 스택의 맨 위에 있는 요소를 삭제하지 않고 반환한다.

초기상태

push(A) push(B) push(C) pop()

A A

B

A

B

C

A

B

Page 52: 알고리즘과 자료구조

배열을 이용한 스택의 구현

스택 구현

1차원 배열 stack[ ]

스택에서 가장 최근에 입력되었던 자료를 가리키는 top 변수

가장 먼저 들어온 요소는 stack[0]에, 가장 최근에 들어온 요소는 stack[top]에 저장

스택이 공백상태이면 top은 -1

Is_empty, is_full 연산

공백상태

A

B

C

D

3

2

1

0

-1

3

2

1

0

-1TOP

A

B

3

2

1

0

-1

TOP

TOP

is_empty(S)

if top = -1

then return TRUE

else return FALSE

is_full(S)

if top = (MAX_STACK_SIZE-1)

then return TRUE

else return FALSE

Page 53: 알고리즘과 자료구조

배열을 이용한 스택의 구현

push 연산

pop 연산

push(S, x)

if is_full(S)

then error "overflow"

else top←top+1

stack[top]←x

pop(S, x)

if is_empty(S)

then error "underflow"

else e←stack[top]

top←top-1

return e

push(C)

A

B

A

B

CTOP

TOP

pop(C)

A

B

A

B

CTOP

TOP

Page 54: 알고리즘과 자료구조

연결 스택

연결리스트를 이용하여 구현한 스택

장점 : 크기가 제한되지 않음

단점 : 구현이 복잡하고 삽입이나 삭제 시간이 오래 걸림

연결 스택(Linked Stack)

A

B

3

2

1

0

-1

TOP

C

C B

A n

TOP

typedef int element;

typedef struct StackNode {

element item;

struct StackNode *link;

} StackeNode;

typedef struct {

StackNode *top;

} LinkedStackType;

요소타입

노드타입

연결 스택의 관련 데이터

Page 55: 알고리즘과 자료구조

연결 스택에서의 연산

push(), pop()

// 삽입 함수

void push(LinkedStackType *s, element item) {

StackNode *temp=(StackNode*)malloc(sizeof(StackNode));

if( temp == NULL ){

fprintf(stderr, "메모리 할당에러\n");

return;

}

else{

temp->item = item;

temp->link = s->top;

s->top = temp;

}

}

// 삭제 함수

element pop(LinkedStackType *s)

{

if( is_empty(s) ) {

fprintf(stderr, "스택이 비어있음\n");

exit(1);

}

else{

StackNode *temp=s->top;

int item = temp->item;

s->top = s->top->link;

free(temp);

return item;

}

}

A

C B A nTop

Temp

② ①

C B A n

Top

Temp

Page 56: 알고리즘과 자료구조

스택의 응용: 괄호검사

괄호의 종류

괄호의 종류: 대괄호 (‘[’, ‘]’), 중괄호 (‘{’, ‘}’), 소괄호 (‘(’, ‘)’)

조건

왼쪽 괄호의 개수와 오른쪽 괄호의 개수가 같아야 한다.

같은 괄호에서 왼쪽 괄호는 오른쪽 괄호보다 먼저 나와야 한다.

괄호 사이에는 포함 관계만 존재한다.

잘못된 괄호의 사용 예

(a(b), a(b)c), a{b(c[d]e}f)

if( ( i==0 ) && (j==0 ) 비교 비교

오류

{ A [ (i+1 ) ]=0; }

비교비교 비교성공

( ((

(

(

(

(

(

{ {[

{[

{

{[(

{[(

((

Page 57: 알고리즘과 자료구조

큐(Queue)

Page 58: 알고리즘과 자료구조

큐(QUEUE)

대기열을 모델링

먼저 들어온 데이터가 먼저 나가는 자료구조

선입선출(FIFO: First-In First-Out)

용어

줄의 맨 앞을 큐 프런트(Queue Front)

맨 뒤를 큐 리어(Queue Rear)

큐 리어에 데이터를 삽입하는 작업 = 큐 애드(Add)

큐 프런트의 데이터를 삭제하는 작업 = 큐 리무브(Remove)

삽입 삭제 검색

ADT 리스트 Insert Delete Retrieve

ADT 스택 Push PopGetTop

(PeekTop)

ADT 큐 Add(Enqueue)Remove

(Dequeue)GetFront

(PeekFront)

전단(front) 후단(rear)

Page 59: 알고리즘과 자료구조

큐 ADT

큐 ADT

삽입과 삭제는 FIFO순서를 따른다.

삽입은 큐의 후단에서, 삭제는 전단에서 이루어진다

큐의 응용

시뮬레이션의 대기열(공항에서의 비행기들, 은행에서의 대기열)

통신에서의 데이터 패킷들의 모델링에 이용

프린터와 컴퓨터 사이의 버퍼링

스택과 마찬가지로 프로그래머의 도구

객체: n개의 element형으로 구성된 요소들의 순서 있는 모임

연산:

▪ create() ::= 큐를 생성한다.

▪ init(q) ::= 큐를 초기화한다.

▪ is_empty(q) ::= 큐가 비어있는지를 검사한다.

▪ is_full(q) ::= 큐가 가득 찼는가를 검사한다.

▪ enqueue(q, e) ::= 큐의 뒤에 요소를 추가한다.

▪ dequeue(q) ::= 큐의 앞에 있는 요소를 반환한 다음 삭제한다.

▪ peek(q) ::= 큐에서 삭제하지 않고 앞에 있는 요소를 반환한다.

생산자 버퍼 소비자

Page 60: 알고리즘과 자료구조

배열을 이용한 큐

선형큐

배열을 선형으로 사용하여 큐를 구현

삽입을 계속하기 위해서는 요소들을 이용시켜야 함

문제점이 많아 사용되지 않음

원형큐

배열을 원형으로 사용하여 큐를 구현

front : 첫번째 요소 하나 앞의 인덱스

rear : 마지막 요소의 인덱스

[-1] [0] [1] [2] [3] [4] [5] [-1] [0] [1] [2] [3] [4] [5]

front rear front rear

rear

a

b

front

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

Page 61: 알고리즘과 자료구조

원형큐의 삽입과 삭제

reara

front

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

rear

a

b

front

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

rear

b

front

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

2. A 삽입

rearfront

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

1. 초기 상태

4. A 삭제3. B 삽입

Page 62: 알고리즘과 자료구조

공백 상태, 포화 상태

공백 상태

front == rear

포화 상태

front % M == (rear+1) % M

공백 상태와 포화 상태를 구별하기위해서는 하나의 공간은 항상 비워둔다

rearfront

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

1. 공백 상태

rearfront

d

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

1. 포화 상태

e

fg

rearfront

[0]

[1]

[2]

[3] [4]

[5]

[6]

[7]

1. 초기 상태

a

bc d

e

fg

a

bc

h

Page 63: 알고리즘과 자료구조

큐의 연산

Modulo 연산을 사용

인덱스를 원형으로 회전 시킴

// 공백 상태 검출 함수

int is_empty(QueueType *q){

return (q->front == q->rear);

}

// 포화 상태 검출 함수

int is_full(QueueType *q){

return ((q->rear+1)%MAX_QUEUE_SIZE == q->front);

}

// 삽입 함수

void enqueue(QueueType *q, element item){

if( is_full(q) )

error("큐가 포화상태입니다");

q->rear = (q->rear+1) % MAX_QUEUE_SIZE;

q->queue[q->rear] = item;

}

// 삭제 함수

element dequeue(QueueType *q) {

if( is_empty(q) )

error("큐가 공백상태입니다");

q->front = (q->front+1) % MAX_QUEUE_SIZE;

return q->queue[q->front];

}

Page 64: 알고리즘과 자료구조

연결 큐(Linked Queue)

연결 큐 : 연결 리스트로 구현된 큐

front 포인터는 삭제와 관련되며 rear 포인터는 삽입

front는 연결 리스트의 맨 앞에 있는 요소를 가리킨다

rear 포인터는 맨 뒤에 있는 요소를 가리킨다

큐에 요소가 없는 경우에는 front와 rear는 NULL

A B C D NULL

rearfront

삽입 삭제

A B C DNULL

rearfront temp

A B C DNULL

rearfront

A B C DNULL

rearfront

A B C DNULL

rearfronttemp

Page 65: 알고리즘과 자료구조

덱(Deque)

덱 (Double-ended Queue)

큐의 전단(front)와 후단(rear)에서 모두 삽입과 삭제가 가능한 큐

덱 ADT

전단(front) 후단(rear)

add_front

delete_front

get_front

add_rear

delete_rear

get_rear

객체: n개의 element형으로 구성된 요소들의 순서있는 모임

연산:

▪ create() ::= 덱을 생성한다.

▪ init(dq) ::= 덱을 초기화한다.

▪ is_empty(dq) ::= 덱이 공백상태인지를 검사한다.

▪ is_full(dq) ::= 덱이 포화상태인지를 검사한다.

▪ add_front(dq, e) ::= 덱의 앞에 요소를 추가한다.

▪ add_rear(dq, e) ::= 덱의 뒤에 요소를 추가한다.

▪ delete_front(dq) ::= 덱의 앞에 있는 요소를 반환한 다음 삭제한다

▪ delete_rear(dq) ::= 덱의 뒤에 있는 요소를 반환한 다음 삭제한다.

▪ get_front(q) ::= 덱의 앞에서 삭제하지 않고 앞에 있는 요소를 반환한다.

▪ get_rear(q) ::= 덱의 뒤에서 삭제하지 않고 뒤에 있는 요소를 반환한다.

Page 66: 알고리즘과 자료구조

덱의 연산

C

A

ddd_front(dq, A)

A

ddd_rear(dq, B)

B

A

ddd_front(dq, C)

B

C A

ddd_rear(dq, D)

B D

A

delete_front(dq)

B D

A

delete_rear(dq)

B

front rear

front rear

front rear

front rear

front rear

front rear

Page 67: 알고리즘과 자료구조

덱의 구현

덱의 구현

양쪽에서 삽입, 삭제가 가능하여야 하므로 일반적으로 이중연결 리스트 사용

typedef int element; // 요소의 타입

typedef struct DlistNode { // 노드의 타입

element data;

struct DlistNode *llink;

struct DlistNode *rlink;

} DlistNode;

typedef struct DequeType { // 덱의 타입

DlistNode *head;

DlistNode *tail;

} DequeType;

Page 68: 알고리즘과 자료구조

덱에서의 삽입

void add_rear(DequeType *dq, element item) {

DlistNode *new_node = create_node(dq->tail, item, NULL);

if( is_empty(dq))

dq->head = new_node;

else

dq->tail->rlink = new_node;

dq->tail = new_node;

}

void add_front(DequeType *dq, element item){

DlistNode *new_node = create_node(NULL, item, dq->head);

if( is_empty(dq))

dq->tail = new_node;

else

dq->head->llink = new_node;

dq->head = new_node;

}

tail

new node삽입전 삽입후

tail

Page 69: 알고리즘과 자료구조

덱에서의 삭제

// 전단에서의 삭제

element delete_front(DequeType *dq)

{

element item;

DlistNode *removed_node;

if (is_empty(dq)) error("공백 덱에서 삭제");

else {

removed_node = dq->head; // 삭제할 노드

item = removed_node->data; // 데이터 추출

dq->head = dq->head->rlink; // 헤드 포인터 변경

free(removed_node); // 메모리 공간 반납

if (dq->head == NULL) // 공백상태이면

dq->tail = NULL;

else // 공백상태가 아니면

dq->head->llink=NULL;

}

return item;

}

tail

삭제전 삭제후

n tailheadhead

Page 70: 알고리즘과 자료구조

우선순위 큐(Priority queue)

Page 71: 알고리즘과 자료구조

우선순위 큐(Priority queue)

우선순위 큐

우선순위를 가진 항목들을 저장하는 큐

FIFO 순서가 아니라 우선순위가 높은 데이터가 먼저 나가게 된다.

가장 일반적인 큐로써, 스택이나 큐로 구현이 가능하다.

응용 분야

시뮬레이션 시스템(여기서의 우선 순위는 대개 사건의 시각이다.)

네트워크 트래픽 제어

운영 체제에서의 작업 스케쥴링

자료구조 삭제되는 요소

스택 가장 최근에 들어온 데이터

큐 가장 먼저 들어온 데이터

우선순위큐 가장 우선순위가 높은 데이터 > >

Page 72: 알고리즘과 자료구조

우선순위 큐 ADT

가장 중요한 연산은

insert 연산(요소 삽입), delete 연산(요소 삭제)이다.

우선순위 큐는 2가지로 구분

최소 우선순위 큐

최대 우선순위 큐

객체 : n개의 element형의 우선 순위를 가진 요소들의 모임

연산 :

▪ create() ::=우선 순위큐를 생성한다.

▪ init(q) ::= 우선 순위큐 q를 초기화한다.

▪ is_empty(q) ::= 우선 순위큐 q가 비어있는지를 검사한다.

▪ is_full(q) ::= 우선 순위큐 q가 가득 찼는가를 검사한다.

▪ insert(q, x) ::= 우선 순위큐 q에 요소 x를 추가한다.

▪ delete(q) ::= 우선 순위큐로부터 가장 우선순위가 높은 요소를 삭제하고 이 요소를 반환한다.

▪ find(q) ::= 우선 순위가 가장 높은 요소를 반환한다.

Page 73: 알고리즘과 자료구조

우선순위 큐 구현

구현 방법

배열을 이용한 우선순위 큐

연결리스트를 이용한 우선순위 큐

히프(heap)를 이용한 우선순위 큐

표현 방법 삽 입 삭 제

순서없는 배열 O(1) O(n)

순서없는 연결 리스트 O(1) O(n)

정렬된 배열 O(n) O(1)

정렬된 연결 리스트 O(n) O(1)

히프 O(logn) O(logn)

9

7 6

5 4 3 2

2 1 3

1 2 3 7 5 n

헤드 포인트1 2 3 7 5

0 1 2 3 4 5 6

COUNT

Page 74: 알고리즘과 자료구조

히프(Heap)

히프

히프란 노드들이 저장하고 있는 키들이 다음 식을 만족하는 완전이진트리

최대 히프(max heap)

부모 노드의 키 값이 자식 노드의 키 값보다 크거나 같은 완전 이진 트리

key(부모노드) ≥key(자식노드)

최소 히프(min heap)

부모 노드의 키 값이 자식 노드의 키 값보다 작거나 같은 완전 이진 트리

key(부모노드) ≤key(자식노드)

9

7 6

5 4 2

3

3

2 1

1

4 2

7 5 3

9

3

7 8

최대 히프 최소 히프

Page 75: 알고리즘과 자료구조

히프의 구현 방법

히프 구현 배열 이용

완전이진트리이므로 각 노드에 번호를 붙일 수 있다

이 번호를 배열의 인덱스라고 생각

부모노드와 자식노드를 찾기가 쉽다.

왼쪽 자식의 인덱스 = (부모의 인덱스)*2

오른쪽 자식의 인덱스 = (부모의 인덱스)*2 + 1

부모의 인덱스 = (자식의 인덱스)/2

9

7 6

5 4 2

3

3

2 1

1

2 3

4 5 6 7

8 9 10

9 7 6 5 4 3 2 2 1 3

0 1 2 3 4 5 6 7 8 9 10

왼쪽 자식 인덱스=(2*3)=6오른쪽 자식 인덱스 = (2*3)+1=7

Page 76: 알고리즘과 자료구조

히프의 높이

N개의 노드를 가지고 있는 히프의 높이는 O(logn)

히프는 완전 이진 트리

마지막 레벨 h를 제외하고는 각 레벨의 i에 2i-1개의 노드 존재

9

7 6

5 4 2

3

3

2 1

1

2 3

4 5 6 7

8 9 10

깊이 노드의 개수

1 1=20

2 1=21

3 1=22

4 1=23

Page 77: 알고리즘과 자료구조

히프 연산

Upheap 알고리즘

Downheap 알고리즘

insert_max_heap(A, key)

heap_size ← heap_size + 1;

i ← heap_size;

A[i] ← key;

while i ≠ 1 and A[i] > A[PARENT(i)] do

A[i] ↔ A[PARENT];

i ← PARENT(i);

delete_max_heap(A)

item ← A[1];

A[1] ← A[heap_size];

heap_size←heap_size-1;

i ← 2;

while i ≤ heap_size do

if i < heap_size and A[LEFT(i)] > A[RIGHT(i)]

then largest ← LEFT(i);

else largest ← RIGHT(i);

if A[PARENT(largest)] > A[largest]

then break;

A[PARENT(largest)] ↔ A[largest];

i ← CHILD(largest);

return item;

Page 78: 알고리즘과 자료구조

히프에서의 삽입

upheap 연산

새로운 키 k의 삽입 연산 후 히프의 성질이 만족되지 않을 수 있다

upheap는 삽입된 노드로부터 루트까지의 경로에 있는 노드들을 k와 비교, 교환

함으로써 히프의 성질을 복원한다.

키 k가 부모노드보다 작거나 같으면 upheap는 종료한다

히프의 높이가 O(logn)이므로 upheap연산은 O(logn)이다.

9

7 6

5 4 2

3

3

2 1 8

9

7 6

5 8 2

3

3

2 1 4

9

8 6

5 7 2

3

3

2 1 4

Page 79: 알고리즘과 자료구조

히프에서의 삭제

downheap 연산

최대히프에서의 삭제는 가장 큰 키 값을 가진 노드를 삭제하는 것을 의미

따라서 루트노드가 삭제된다

루트노드를 삭제한다

마지막노드를 루트노드로 이동한다.

루트에서부터 단말노드까지의 경로에 있는 노드들을 교환하여 히프 성질을 만족

시킨다.

9

7 6

5 4 2

3

3

2 1

3

7 6

5 4 23

2 1

7

3 6

5 4 23

2 1

7

5 6

3 4 23

2 1

Page 80: 알고리즘과 자료구조

히프 정렬(Heap Sort)

히프 정렬

히프 정렬이 유용한 경우는 전체 자료를 정렬이 아니라 가장 큰 값 몇 개

만 필요할 때이다.

먼저 정렬해야 할 n개의 요소들을 최대 히프에 삽입

한번에 하나씩 요소를 히프에서 삭제하여 저장하면 된다.

삭제되는 요소들은 값이 증가되는 순서(최소히프의 경우)

요소를 히프에 삽입/삭제할 때 시간 : O(logn)

요소의 개수가 n개이므로 전체적으로 O(nlogn)시간이 걸린다.

// 우선 순위 큐인 히프를 이용한 정렬

void heap_sort(element a[], int n)

{

int i;

HeapType h;

init(&h);

for(i=0;i<n;i++){

insert_max_heap(&h, a[i]);

}

for(i=(n-1);i>=0;i--){

a[i] = delete_max_heap(&h);

}

}

Page 81: 알고리즘과 자료구조

최대 히프의 시간 복잡도

최대 히프는 완전 이진 트리

노드의 수가 n 일때 높이는 log n

추가: log n

최악의 경우 단말로 부터 루트까지 삽입될 위치 탐색

삭제: log n

최악의 경우 루트부터 단말까지 교환이 일어 남

Page 82: 알고리즘과 자료구조

정렬 (Sort)

Page 83: 알고리즘과 자료구조

정렬 (Sort)

정렬(Sort)

물건을 크기 순으로 오름차순이나 내림차순으로 나열하는 것

컴퓨터 공학분야에서 가장 기본적이고 중요한 알고리즘중의 하나

가장 많이, 그리고 가장 잘 알려진 알고리즘

자료 탐색에 있어서 필수적이다.

정렬의 단위

정렬의 기준 : 정렬 키(Sort Key) 필드

이름 학번 주소 연락처

학생들의 레코드

필드 필드 필드 필드

키(key)레코드

Page 84: 알고리즘과 자료구조

정렬 알고리즘

많은 정렬 알고리즘 존재

단순하지만 비효율적인 방법

삽입정렬, 선택정렬, 버블정렬 …

복잡하지만 효율적인 방법

퀵정렬, 히프정렬, 합병정렬, 기수정렬 …

모든 경우에 최적인 알고리즘은 없다

응용에 맞추어 선택

정렬 알고리즘의 평가

비교 횟수

이동 횟수

정렬의 분류

내부 정렬과 외부 정렬

안정 정렬(Stable Sorting)과 불안정 정렬(Unstable Sorting)

직접 정렬(Direct Sorting)과 간접 정렬 (Indirect Sorting)

Page 85: 알고리즘과 자료구조

선택 정렬(Selection Sort)

선택 정렬

정렬이 안된 숫자들 중에서 최소값을 선택하여 배열의 첫번째 요소와 교환

선택 정렬 분석

숫자의 개수 n

최소값을 선택하는데 걸리는 시간 : 0(n)

전체 시간 복잡도 : 0(n2)

안정성을 만족하지 않는다

selection_sort(A, n)

for i←0 to n-2 do

least ← A[i], A[i+1],..., A[n-1] 중에서 가장 작은 값의 인덱스;

A[i]와 A[least]의 교환;

i++;

5 3 8 1 2 7

1 3 8 5 2 7

1 2 8 5 3 7

1 2 3 5 8 7

1 2 3 5 8 7

1 2 3 5 7 8

1 2 3 5 7 8

Page 86: 알고리즘과 자료구조

버블정렬(Bubble Sort)

버블 정렬

인접한 레코드가 순서대로 되어 있지 않으면 교환

전체가 정렬 될 때까지 비교/교환 계속

BubbleSort(A, n)

for i←n-1 to 1 do

for j←0 to i-1 do

j와 j+1번째의 요소가 크기순이 아니면 교환

j++;

i--;

#define SWAP(x, y, t) ( (t)=(x), (x)=(y), (y)=(t) )

void bubble_sort(int list[], int n)

{

int i, j, temp;

for(i=n-1; i>0; i--){

for(j=0; j<i; j++)

/* 앞뒤의 레코드를 비교한 후 교체 */

if(list[j]>list[j+1])

SWAP(list[j], list[j+1], temp);

}

}

Page 87: 알고리즘과 자료구조

버블 정렬(Bubble Sort)

버블 정렬 분석

비교횟수

버블정렬의 비교횟수는 최상, 평균, 최악의 경우에도 항상 일정 :

이동 횟수 (평균 : O(n2))

최악 : 역순정렬 = 3* 비교

최상 : 이미 정렬 = 0 5 3 8 1 2 7

3 5 1 2 7 8

3 1 2 5 7 8

1 2 3 4 5 8

1 2 3 5 7 8

1 2 3 5 7 8

1 2 3 5 7 8

초기상태

정렬완료

스캔1

스캔2

스캔3

스캔4

스캔5

𝒊=𝟏

𝒏−𝟏𝒏(𝒏 − 𝟏)

𝟐= 𝑶(𝒏𝟐)

5 3 8 1 2 7

3 5 8 1 2 7

3 5 8 1 2 7

3 5 8 1 2 7

3 5 1 8 2 7

3 5 1 2 8 7

3 5 1 2 7 8

초기상태

스캔완료

5와 3교환

교환없음

8과 1을교환

8과 2를 교환

8과 7을 교환

Page 88: 알고리즘과 자료구조

삽입 정렬(Insertion Sort)

삽입 정렬

정렬되어 있는 부분에 새로운 레코드를 적절한 위치에 삽입하는 과정을 반복

많은 이동 레코드가 클 경우 불리

안정된 정렬 방법 (이미 정렬되어 있으면 효율적)

1 3 5 8 2 7

정렬된 부분 미정렬 부분

Algorithm InsertionSort(list, n):

Input: n개의 정수를 저장하고 있는 배열 list

Output: 정렬된 배열 list

for i←1 to n-1 do

{

정렬된 리스트에서 list[i]보다 더 큰요소들이동;

list[i]를 정렬된 리스트의 적절한 위치에 삽입;

i++;

}

Page 89: 알고리즘과 자료구조

삽입 정렬(Insertion Sort)

삽입 정렬의 복잡도

비교 : n-1

이동 : 2(n-1)

최악의 경우 : 역순으로 정렬

비교 :

이동

// 삽입정렬

void insertion_sort(int list[], int n)

{

int i, j, key;

for(i=1; i<n; i++){

key = list[i];

for(j=i-1; j>=0 && list[j]>key; j--)

list[j+1] = list[j]; /* 레코드의 오른쪽 이동 */

list[j+1] = key;

}

}

𝒊=𝟏

𝒏−𝟏𝒏(𝒏 − 𝟏)

𝟐= 𝑶(𝒏𝟐)

𝒏(𝒏−𝟏)

𝟐+ 𝟐(𝒏 − 𝟏) = 𝑶(𝒏𝟐)

5 3 8 1 2 7

5 3 8 1 2 7

3 5 8 1 2 7

3 5 8 1 2 7

1 3 5 8 2 7

1 2 3 5 8 7

1 2 3 5 7 8

초기상태

정렬완료

3을 삽입

8은 이미 제자리에

1을 삽입

2를 삽입

7을 삽입

Page 90: 알고리즘과 자료구조

분할 정복(Divide and Conquer)

분할 정복

문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서

원래의 문제를 해결하는 전략

분리된 문제가 아직도 해결하기 어렵다면, 즉 충분히 작지 않다면 분할정복방법

을 다시 적용

재귀호출을 이용하여 구현

1. 분할(Divide): 배열을 같은 크기의 2개의 부분 배열로 분할한다.

2. 정복(Conquer): 부분배열을 정렬한다. 부분배열의 크기가 충분히 작지 않으면 재귀호출을 이용하여

다시 분할정복기법을 적용한다.

3. 결합(Combine): 정렬된 부분배열을 하나의 배열에 통합한다.

입력파일: 27 10 12 20 25 13 15 22

1.분할(Divide): 배열을 27 10 12 20 과 25 13 15 22의 2개의 부분배열로 분리

2.정복(Conquer): 부분배열을 정렬하여 10 12 20 27 과 13 15 22 25를 얻는다.

3.결합(Combine): 부분배열을 통합하여 10 12 13 15 20 22 25 27을 얻는다.

Page 91: 알고리즘과 자료구조

퀵정렬(Quick Sort)

퀵정렬

평균적으로 가장 빠른 정렬 방법 : 분할 정복 사용

퀵정렬은 전체 리스트를 2개의 부분리스트로 분할하고, 각각의 부분리스트를

다시 퀵정렬로 정렬

5 3 8 4 9 1 6 2 7

5 3 8 4 9 1 6 2 7

피봇

피봇보다 작은 값 피봇보다 큰 값

void quick_sort(int list[], int left, int right)

{

if(left<right){

int q=partition(list, left, right);

quick_sort(list, left, q-1);

quick_sort(list, q+1, right);

}

}

3 정렬할 범위가 2개 이상의 데이터이면

4 partition 함수를 호출하여 피벗을 기준으로 2개의 리스트로 분할한다.

partition 함수의 반환 값은 피봇의 위치가 된다.

5 left에서 피봇 위치 바로 앞까지를 대상으로 순환 호출한다

(피봇은 제외된다).

6 피봇 위치 바로 다음부터 right까지를 대상으로 순환 호출한다

(피봇은 제외된다).

Page 92: 알고리즘과 자료구조

퀵정렬(Quick Sort)

Partition 함수

피봇(pivot): 가장 왼쪽 숫자라고 가정

두 개의 변수 low와 high를 사용

low는 피봇보다 작으면 통과, 크면 정지

high는 피봇보다 크면 통과, 작으면 정지

정지된 위치의 숫자를 교환 : low와 high가 교차하면 종료

5 3 8 4 9 1 6 2 7

5 3 8 4 9 1 6 2 7

5 3 2 4 9 1 6 8 7

5 3 2 4 9 1 6 8 7

5 3 2 4 1 9 6 8 7

5 3 2 4 1 9 6 8 7

5 3 2 4 1 9 6 8 7

1 3 2 4 5 9 6 8 7

Left Right

피봇

Low

Low

Low

Low

Low

Low

Low

Low

High

High

High

High

High

High

High

High

Stop

Page 93: 알고리즘과 자료구조

퀵정렬(Quick Sort)

Partition 함수의 구현

int partition(int list[], int left, int right)

{

int pivot, temp;

int low,high;

low = left;

high = right+1;

pivot = list[left];

do {

do

low++;

while(low<=right &&list[low]<pivot);

do

high--;

while(high>=left && list[high]>pivot);

if(low<high) SWAP(list[low], list[high], temp);

} while(low<high);

SWAP(list[left], list[high], temp);

return high;

}

Page 94: 알고리즘과 자료구조

퀵정렬(Quick Sort)

퀵정렬 과정

5 3 8 4 9 1 6 2 7

1 3 2 4 5 9 6 8 7

1 3 2 4 5 7 6 8 9

1 2 3 4 5 6 7 8 9

1 2 3 4 5 6 7 8 9

피봇

Page 95: 알고리즘과 자료구조

퀵정렬(Quick Sort)

퀵정렬 분석

최상의 경우 : 거의 균등한 리스트로 분할되는 경우

패스 수 : Log2n

각 패스 안에서 비교횟수 : n

총 비교 횟수 : n Log2n

총 이동 횟수 : 비교 횟수에 비하여 무시 가능

최악의 경우 : 불균등한 리스트로 분할되는 경우

패스 수 : n

각 패스 안에서의 비교횟수 : n

총 비교 횟수 : n2

총 이동 횟수 : 비교 횟수에 비하여 무시 가능

(1 2 3 4 5 6 7 8 9) 1 (2 3 4 5 6 7 8 9) 1 2 (3 4 5 6 7 8 9) 1 2 3 (4 5 6 7 8 9) 1 2 3 4 (5 6 7 8 9)

... 1 2 3 4 5 6 7 8 9

Page 96: 알고리즘과 자료구조

합병 정렬(Merge Sort)

합병 정렬

리스트를 두 개로 나누어, 각각을 정렬한 다음, 다시 하나로 합치는 방법

분할 정복 기법에 바탕

27 10 12 20 25 13 15 22

10 12 13 15 20 22 25 27

27 10 12 20 25 13 15 22

10 12 20 27 13 15 22 25

27 10 25 13

10 27 13 25

12 20 15 22

12 20 15 22

27 2512 1510 1320 22

Page 97: 알고리즘과 자료구조

합병 정렬(Merge Sort)

합병 정렬

합병정렬에서 실제로 정렬이 이루어지는 시점은 2개의 리스트를 합병하는 단계

merge_sort(list, left, right)

if left < right

mid = (left+right)/2;

merge_sort(list, left, mid);

merge_sort(list, mid+1, right);

merge(list, left, mid, right);

1

1 2

1 2 3

1 2 3 4

1 2 3 4 5

1 2 3 4 5 6

1 2 3 4 5 6 7 8

2 5 7 8

2 5 7 8

5 7 8

5 7 8

5 7 8

7 8

7 8

1 3 4 6

3 4 6

3 4 6

4 6

6

6

배열 A 배열 B 배열 C

Page 98: 알고리즘과 자료구조

합병 정렬(Merge Sort)

합병 알고리즘

merge(list, left, mid, last):

// 2개의 인접한 배열 list[left..mid]와 list[mid+1..right]를 합병

b1←left;

e1←mid;

b2←mid+1;

e2←right;

sorted 배열을 생성;

index←0;

while b1≤e1 and b2≤e2 do

if(list[b1]<list[b2])

then

sorted[index]←list[b1];

b1++;

index++;

else

sorted[index]←list[b2];

b2++;

index++;

요소가 남아있는 부분배열을 sorted로 복사한다;

sorted를 list로 복사한다;

2 5 7 8 1 3 4 6

Low

i j

Mid+1 HighMid

list

1 2 3 4sorted

i

정렬된 리스트 정렬된 리스트

Page 99: 알고리즘과 자료구조

합병 정렬(Merge Sort)

합병 정렬 분석

비교 횟수

합병정렬은 크기 n인 리스트를 정확히 균등 분배하므로 퀵정렬의 이상적인 경우와 마

찬가지로 정확히 logn개의 패스를 가진다.

각 패스에서 리스트의 모든 레코드 n개를 비교하여 합병하므로 n 번의 비교 연산이 수

행된다.

따라서 합병정렬은 최적, 평균, 최악의 경우 모두 큰 차이 없이 nlogn번의 비교를 수행

하므로 O(nlogn)의 복잡도를 가지는 알고리즘이다.

합병정렬은 안정적이며 데이터의 초기 분산 순서에 영향을 덜 받는다.

이동 횟수

배열을 이용하는 합병정렬은 레코드의 이동이 각 패스에서 2n번 발생하므로 전체 레코

드의 이동은 2nlogn번 발생한다. 이는 레코드의 크기가 큰 경우에는 매우 큰 시간적 낭

비를 초래한다.

그러나 레코드를 연결 리스트로 구성하여 합병 정렬할 경우, 링크 인덱스만 변경되므로

데이터의 이동은 무시할 수 있을 정도로 작아진다.

따라서 크기가 큰 레코드를 정렬할 경우, 연결 리스트를 이용하는 합병정렬은 퀵정렬을

포함한 다른 어떤 정렬 방법보다 매우 효율적이다

Page 100: 알고리즘과 자료구조

기수 정렬(Radix Sort)

기수 정렬

레코드를 비교하지 않고 정렬

단순히 자리수에 따라 버킷에 넣었다가 꺼내면 정렬됨

O(nlogn)이라는 이론적인 하한선을 깰 수 있는 유일한 방법

기수 정렬은 O(kn) 의 시간 복잡도를 가지는데 대부분 k<4 이하

정렬할 수 있는 레코드의 타입이 한정

레코드의 키들이 동일한 길이를 가지는 숫자나 문자열로 구성

0

1

2

3

4

5

6

7

8

9

8 2 7 3 5 2 3 5 7 8

2

3

5

7

8

Page 101: 알고리즘과 자료구조

기수 정렬(Radix Sort)

Sorting a sequence of 4-bit integers

1001

0010

1101

0001

1110

0010

1110

1001

1101

0001

1001

1101

0001

0010

1110

1001

0001

0010

1101

1110

0001

0010

1001

1101

1110

Page 102: 알고리즘과 자료구조

기수 정렬(Radix Sort)

기수 정렬

버킷은 큐로 구현

버킷의 개수는 키의 표현 방법과 밀접한 관계

이진법을 사용한다면 버킷은 2개.

알파벳 문자를 사용한다면 버킷은 26개

십진법을 사용한다면 버킷은 10개

32비트의 정수의 경우, 8비트씩 나누어 기수정렬의 개념을 적용한다면

필요한 버킷 수는 256개, 대신에 필요한 패스의 수는 4개로 십진수 표현보다 줄어 듬.

RadixSort(list, n):

for d←LSD의 위치 to MSD의 위치 do

{

d번째 자릿수에 따라 0번부터 9번 버킷에 집어놓는다.

버킷에서 숫자들을 순차적으로 읽어서 하나의 리스트로 합친다.

d++;

}

Page 103: 알고리즘과 자료구조

계수 정렬(Counting Sort)

계수 정렬

선형 시간에 정렬하는 효율적인 알고리즘

항목의 순서를 결정하기 위해 집합에 각 항목이 몇 개씩 있는지 세는 작업한다

속도가 빠르고 안정적이다.

입력 키가 어떤 범위에 한정 될 때 사용 가능

예) 입력이 0부터 K사이의 정수

카운트를 위한 충분한 공간을 할당하려면 집합 내의 가장 큰 수를 알아야 한다.

0 3 2 1 3 2 1 2 2 3Input A:N=10M=4

Count array C:- all elements in input between 0 and 3

1

2

4

3

0

1

2

3 3332222110

10987654321

Output sorted array:

Page 104: 알고리즘과 자료구조

정렬 알고리즘의 비교

알고리즘 최선 평균 최악

삽입 정렬 O(n) O(n2) O(n2)

선택 정렬 O(n2) O(n2) O(n2)

버블 정렬 O(n2) O(n2) O(n2)

쉘 정렬 O(n) O(n1.5) O(n1.5)

퀵 정렬 O(nLog2n) O(nLog2n) O(n2)

히프 정렬 O(nLog2n) O(nLog2n) O(nLog2n)

합병 정렬 O(nLog2n) O(nLog2n) O(nLog2n)

기수 정렬 O(dn) O(dn) O(dn)

Page 105: 알고리즘과 자료구조

트리 (Tree)

Page 106: 알고리즘과 자료구조

트리 개념

트리 (Tree)

계층적인 구조(Hierarchical Structure)를 나타내는 자료 구조

트리는 부모(Parent)-자식(Child) 관계로 이루어진다.

응용 분야

계층적인 조직 표현, 파일 시스템 등

트리에서 나타나는 용어들

일반 트리와 이진 트리

노드(node): 트리의 구성요소

루트(root): 부모가 없는 노드(A)

서브트리(subtree): 하나의 노드와 그 노드들의 자손들로 이루어진 트리

단말노드(terminal node): 자식이 없는 노드(A,B,C,D)

비단말노드: 적어도 하나의 자식을 가지는 노드(E,F,G,H,I,J)

자식, 부모, 형제, 조상, 자손 노드: 인간과 동일

레벨(level): 트리의 각층의 번호

높이(height): 트리의 최대 레벨(3)

차수(degree): 노드가 가지고 있는 자식 노드의 개수

A

B C D

E F G H I J

Level 1

Level 2

Level 3

데이터 데이터링크1 링크1… 링크2링크 N

이진 트리일반 트리

Page 107: 알고리즘과 자료구조

이진 트리 정의

모든 노드가 2개의 서브 트리를 가지고 있는 트리

서브 트리는 공집합일 수 있다.

이진 트리의 노드에는 최대 2개까지의 자식 노드가 존재

모든 노드의 차수가 2 이하가 된다. 구현이 편리

이진 트리에는 서브 트리간 순서가 존재

이진트리(Binary Tree)

Tleft Tright

Page 108: 알고리즘과 자료구조

이진트리의 분류

포화 이진트리(Full binary tree)

트리의 각 레벨에 노드가 다 차있는 이진 트리

전체 노드 개수 = 𝑖=0𝑘−1 2𝑖 = 2k-1

노드의 번호는 레벨 단위로 왼쪽에서 오른쪽으로

완전 이진트리(Complete binary tree)

높이가 h일 때 레벨 1부터 모두 노가 채워져 있고,

마지막 레벨 h에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워져 있는 경우

A

B C

D E F G

A

B C

D E F G

H I J

A

B C

D F G

I

포화 이진트리 완전 이진트리 기타 이진트리

Page 109: 알고리즘과 자료구조

이진트리의 특성

노드 개수가 n이면 간선의 개수는 n-1

높이가 h인 이진트리의 경우,

최소 h개의 노드를 가지며, 최대 2h-1개의 노드를 가진다.

+

* /

a b c d

노드의 개수 : 7간선의 개수 : 6

1

2

3

높이=3

+

* /

a b c d

21-1=1

22-1=2

23-1=4

최대 노드 개수=3 최대 노드 개수 = 21-1 + 22-1 + 23-1 = 1+2+4 =7

Page 110: 알고리즘과 자료구조

이진트리의 특성

n개의 노드를 가지는 이진트리의 높이는 최대 n이거나 최소 [log2(n+1)]

1

2

3

높이=3

1

2 3

4 5 6 7

최대 높이=7

최소 높이 =3

4

5

6

7

Page 111: 알고리즘과 자료구조

이진트리의 표현

배열 표현법

모든 이진트리를 포화이진로 가정하고 각 노드에 번호를 붙여서 그 번호를 배열

의 인덱스로 삼아 노드의 데이터를 배열에 저장하는 방법

링크 표현법

포인터를 이용하여 부모 노드가 자식노드를 가리키도록 하는 방법

A

B C

D E F

1

32

4 65

A

B

C

D

E

F

0

1

2

3

4

5

6

7

8

A

B

C

D

A

B

C

D

0

1

2

3

4

5

6

7

8

데이터

B

A

C

n E nn D n n F n

n = nullA n

B n

C n

n D n

Page 112: 알고리즘과 자료구조

L

V

R

이진트리의 순회

순회(Traversal) : 트리의 노드를 체계적으로 방문하는 것

전위 순회 (Preorder traversal) : VLR

자손노드보다 루트노드를 먼저 방문한다.

중위 순회 (Inorder traversal) : LVR

왼쪽 자손, 루트, 오른쪽 자손 순으로 방문한다.

후위 순회 (Postorder traversal) : LRV

루트노드보다 자손을 먼저 방문한다.

전체 트리나 서브 트리나

구조는 동일하다. !!!

Page 113: 알고리즘과 자료구조

전위 순회

루트를 먼저 방문하는 순회 방법

알고리즘 설명

1. 노드 X가 NULL이면 더 이상 순환 호출을 하지 않는다.

2. X의 데이터를 출력한다.

3. X의 왼쪽 서브 트리를 순환 호출하여 방문한다.

4. X의 오른쪽 서브 트리를 순환 호출하여 방문한다.

// 전위 순회

preorder( TreeNode *root ){

if ( root ){

printf("%d", root->data ); // 노드 방문

preorder( root->left );// 왼쪽서브트리 순회

preorder( root->right );// 오른쪽서브트리 순회

}

}

1

2 3

4 5 6 7

1 2 4 5 3 6 7

Page 114: 알고리즘과 자료구조

중위 순회

왼쪽 서브트리 루트 오른쪽 서브트리 순으로 방문

알고리즘 설명

1. 노드 X가 NULL이면 더 이상 순환 호출을 하지 않는다.

2. X의 왼쪽 서브 트리를 순환 호출하여 방문한다.

3. X의 데이터를 출력한다.

4. X의 오른쪽 서브 트리를 순환 호출하여 방문한다.

// 중위 순회

inorder( TreeNode *root ){

if ( root ){

inorder( root->left );// 왼쪽서브트리 순회

printf("%d", root->data ); // 노드 방문

inorder( root->right );// 오른쪽서브트리 순회

}

}

1

2 3

4 5 6 7

4 2 5 1 6 3 7

Page 115: 알고리즘과 자료구조

후위 순회

왼쪽 서브 트리 오른쪽 서브 트리 루트

알고리즘 설명

1. 노드 X가 NULL이면 더 이상 순환 호출을 하지 않는다.

2. X의 왼쪽 서브 트리를 순환 호출하여 방문한다.

3. X의 오른쪽 서브 트리를 호출하여 방문한다.

4. X의 데이터를 출력한다.

// 후위 순회

postorder( TreeNode *root ){

if ( root ){

postorder( root->left );// 왼쪽서브트리 순회

postorder( root->right );// 오른쪽서브트리순회

printf("%d", root->data ); // 노드 방문

}

}

1

2 3

4 5 6 7

4 5 2 6 7 3 1

Page 116: 알고리즘과 자료구조

수식 트리

수식 트리 : 산술식을 트리 형태로 표현한 것

비단말노드 : 연산자(Operator)

단말노드 : 피연산자(Operand)

or

< <

a b c d

-

a x

b c

+

a b

(a) (b) (c)

수식 a + b a - (b × c) (a < b) or (c < d)

전위순회 + a b - a × b c or < a b < c d

중위순회 a + b a - b × c a < b or c < d

후위순회 a b + a b c × - a b < c d < or

어떤 순회 방법이적합한가 ?

Page 117: 알고리즘과 자료구조

수식 트리 계산

후위 순회를 사용

서브트리의 값을 순환호출로 계산

비단말 노드를 방문할 때 양쪽 서브트리의 값을 저장된 연산자를 이용하여 계산

한다.

알고리즘 설명

1. 수식 트리가 공백이면

2. 그냥 복귀

3. 그렇지 않으면 왼쪽 서브 트리를 계산하기 위한 evaluate를 순환 호출. 이때 파라미터는 왼

쪽 자신 노드가 된다.

4. 같은 방식으로 오른쪽 서브 트리를 계산한다.

5. 루트 노드의 데이터 필드에서 연산자를 추출한다.

6. 추출된 연산자를 가지고 연산을 수행해서 반환한다.

+

* /

3 2 5 6

evaluate(exp)

if exp = NULL

then return 0;

else x←evaluate(exp->left);

y←evaluate(exp->right);

op←exp->data;

return (x op y);

Page 118: 알고리즘과 자료구조

이진트리 연산

노드 개수 구하기

탐색 트리안의 노드의 개수를 계산

각각의 서브 트리에 대하여 순환 호출한 다음 반환되는 값에 1을 더하여 반환

높이 구하기

서브 트리에 대하여 순환 호출하고, 서브 트리들의 반환 값 중 최대값을 구한다

int get_node_count(TreeNode *node)

{

int count=0;

if( node != NULL )

count = 1 + get_node_count(node->left)

+ get_node_count(node->right);

return count;

}

6

3 2

1 1 1

int get_height(TreeNode *node)

{

int height=0;

if( node != NULL )

height = 1 + max(get_height(node->left),

get_height(node->right));

return height;

}

HleftHright

높이=3

H=1+ max(Hleft ,Hright)

Page 119: 알고리즘과 자료구조

이진 탐색 트리(Binary search tree)

이진 탐색 트리

탐색 작업을 효율적으로 하기 위한 자료

Key(왼쪽 서브트리) ≤ Key(루트노드) ≤ Key(오른쪽 서브트리)

이진탐색을 중위 순회하면 오름차순으로 정렬된 값을 얻을 수 있다.

18

7 26

3 12

27

31왼쪽서브트리 오른쪽서브트리

루트보다작은 값

루트보다큰 값

Page 120: 알고리즘과 자료구조

이진트리 : 탐색연산

탐색 연산

비교한 결과가 같으면 탐색이 성공적으로 끝남

비교한 결과,

주어진 키 값이 루트 노드의 키 값보다 작으면 탐색은 이 루트 노드의 왼쪽 자식을

기준으로 다시 시작

주어진 키 값이 루트 노드의 키 값보다 크면 탐색은 이 루트 노드의 오른쪽 자식을

기준으로 다시 시작

search(x, k)

if x=NULL

then return NULL;

if k=x->key

then return x;

else if k<x->key

then return search(x->left, k);

else return search(x->right, k);

18

7 26

3 12

27

31

12 탐색

Page 121: 알고리즘과 자료구조

이진트리 : 삽입연산

삽입 연산

삽입을 위해서는 먼저 탐색을 수행하는 것이 필요

탐색에 실패한 위치가 바로 새로운 노드를 삽입하는 위치

insert_node(T,z)

p←NULL; //p : 부모 노드 포인터

t←root; // t: 탐색 포인터

while t≠NULL do

p←t;

if z->key < p->key

then t←p->left;

else t←p->right;

if p=NULL

then root←z;// 트리가 비어있음

else if z->key < p->key

then p->left←z

else p->right←z

18

7 26

3 12

27

31

9 탐색

NULL

18

7 26

3 12

27

31

9 삽입

9

Page 122: 알고리즘과 자료구조

이진트리 : 삭제연산

삭제 연산 Case

Case 1 : 삭제하려는 노드가 단말 노드일 경우

Case 2 : 삭제하려는 노드가 하나의 서브 트리만 가지고 있는 경우

Case 3 : 삭제하려는 노드가 두 개의 서브 트리 모두 가지고 있는 경우

Case 1 : 삭제하려는 노드가 단말 노드일 경우

단말노드의 부모노드를 찾아서 연결을 끊는다.

35

18 68

7 26 99

3 12 22 30

35

18 68

7 26 99

3 12 22

Page 123: 알고리즘과 자료구조

이진트리 : 삭제연산

Case 2 : 삭제하려는 노드가 하나의 서브 트리만 가지고 있는 경우

해당 노드를 삭제하고 서브 트리는 부모 노드에 붙여준다.

Case 3 : 삭제하려는 노드가 두 개의 서브 트리 모두 가지고 있는 경우]

삭제 노드와 가장 비슷한 값을 가진 노드를 삭제 노드 위치로 가져온다

35

18 68

7 26 99

3 12 22 30

35

18 99

7 26

3 12 22 30

35

18 68

7 26 99

3 12 22 30

왼쪽 서브트리에서제일 큰 값

왼쪽 서브트리에서제일 큰 값

35

18 68

7 26 99

3 12 22 30

35

22 68

7 26 99

3 12 30

Page 124: 알고리즘과 자료구조

이진트리 : 성능

이진 탐색 트리 연산 성능

탐색, 삽입, 삭제 연산의 시간 복잡도는 트리의 높이 h에 비례한다

최선의 경우

이진 트리가 균형적으로 생성되어 있는 경우

h = log2n

최악의 경우

한쪽으로 치우친 경사 이진 트리의 경우

h = n-1

순차탐색과 시간복잡도가 같다.

1

2

3

높이=n-1

1

2 3

4 5 6 7

높이=log2n

Page 125: 알고리즘과 자료구조

그래프 (Graph)

Page 126: 알고리즘과 자료구조

그래프 (Graph)

연결되어 있는 객체간의 관계를 표현하는 자료 구조

예 : 전기회로, 프로젝트 관리, 지도에서 도시의 연결

그래프 이론 (Graph Theory)

그래프를 문제 해결의 도구로 이용하는 연구 분야

그래프 역사

1800년대 Euler에 의해 창안

그래프 (Graph)

S

BK J

ID

DA

C

B

ba

dcg

e

f

위치 : 정점(node)

다리 : 간선(edge)

Page 127: 알고리즘과 자료구조

그래프 용어

그래프는 (V, E)로 표시된다.

V는 정점(Vertices)들의 집합

E는 간선(Edge)들의 집합

정점과 간선은 모두 관련되는 데이터를 가질 수 있다.

무방향 간선 (Undirected Graph) : (A, B) = (B, A)

방향 간선(Directed Graph) : <A, B> ≠ <B, A>

가중치 그래프(Weighted Graph), 네트워크(Network) : 간선에 비용이나 가중치가 할당

S

BK J

ID

9

9

7

6

5

5

5

1

가중치 표시

S

BK J

ID

방향 표시

S

BK J

ID

방향/가중치 표시

9

9

7

6

5

5

5

1

A B

A B

A B1200

Page 128: 알고리즘과 자료구조

그래프 표현의 예

표현의 예

V(G1) ={0,1,2,3} E(G1) = {(0,1), (0,2), (0,3), (1,2), (2,3)}

V(G2) ={0,1,2,3} E(G2) = {(0,1), (0,2)}

V(G3) ={0,1,2} E(G3) = {<0,1>, <1,0>, <1,2>}

용어

인접 정점(Adjacent vertex) : 간선에 의해 연결된 정점

차수 (Degree) : 정점에 연결된 다른 정점의 개수

경로 (Path)는 정점의 나열로 표현

단순 경로 : 0,1,2,3

사이클 (Cycle) : 0,1,2,0

경로의 길이 : 경로를 구성하는 사용된 간선의 수

0 3

1 2

0 3

1 2

0

1

2G1 G2 G3

완전 그래프란 모든정점이 연결되어 있는그래프를 말한다.

Page 129: 알고리즘과 자료구조

그래프의 표현

그래프를 표현하는 2가지 방법

인접행렬(Adjacent Matrix) : 2차원 배열 사용 표현

인접리스트(Adjacency List) : 연결리스트를 사용 표현

인접행렬 방법

If (간선(i, j)가 그래프1에 존재) M[i][j] = 1, 그렇지 않으면 M[i][j] = 0

0 3

1 2

0 3

1 2

0

1

2G1 G2 G3

0 1 1 1

1 0 1 0

1 1 0 1

1 0 1 0

0 1 1 0

1 0 0 0

1 0 0 0

0 0 0 0

0 1 0

1 0 1

0 0 0

Page 130: 알고리즘과 자료구조

그래프의 표현

인접리스트 방법

각 정점에 인접한 정점들을 연결리스트로 표현

0 3

1 2

G1

0 3

1 2

G2

0

1

2G3

n

n

1

2

3

4

1

2

3

4

1

2

3

1 2 3 n

0 2 n

0 1 3 n

0 2 n

1 2 n

0 2 n

1 n

0 n

0 n

가중치가 있는 경우는어떻게 표현 ?

Page 131: 알고리즘과 자료구조

그래프 탐색

탐색

가장 기본적인 연산으로

하나의 정점으로부터 시작하여 차례대로 모든 정점을 한번씩 방문

많은 문제의 경우 단순히 그래프의 노드를 탐색하는 것으로 해결된다.

예) 특정 정점에서 다른 정점으로 갈 수 있는지 없는지, 회로의 연결 등

그래프 탐색의 2가지 방법

깊이 우선 탐색 (DFS : Depth-First Search)

너비 우선 탐색 (BFS : Breadth-First Search)

1

2 3

4 5 76

BFS

1

2 3

4 5 76

DFS

Page 132: 알고리즘과 자료구조

깊이 우선 탐색

깊이 우선 탐색

depth_first_search(v)

v를 방문되었다고 표시;

for all u ∈ (v에 인접한 정점) do

if (u가 아직 방문되지 않았으면)

then depth_first_search(u)

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

(a) (c) (d)(b)

(e) (g) (h)(f)

Page 133: 알고리즘과 자료구조

깊이 우선 탐색

깊이 우선 탐색의 구현

순환 호출 이용

명시적인 스택 이용

// 인접 행렬로 표현된 그래프에 대한 깊이 우선 탐색

void dfs_mat(GraphType *g, int v)

{

int w;

visited[v] = TRUE; // 정점 v의 방문 표시

printf("%d ", v); // 방문한 정점 출력

for(w=0; w<g->n; w++) // 인접 정점 탐색

if( g->adj_mat[v][w] && !visited[w] )

dfs_mat(g, w); //정점 w에서 DFS 새로시작

}

// 인접 리스트로 표현된 그래프에 대한 깊이 우선 탐색

void dfs_list(GraphType *g, int v)

{

GraphNode *w;

visited[v] = TRUE; // 정점 v의 방문 표시

printf("%d ", v); // 방문한 정점 출력

for(w=g->adj_list[v]; w; w=w->link)// 인접 정점 탐색

if(!visited[w->vertex])

dfs_list(g, w->vertex); //정점 w에서 DFS 새로시작

}

Page 134: 알고리즘과 자료구조

너비 우선 탐색

너비 우선 탐색

breadth_first_search(v)

v를 방문되었다고 표시;

큐 Q에 정점 v를 삽입;

while (not is_empty(Q)) do

Q에서 정점 W를 삭제;

for all u ∈ (v에 인접한 정점) do

if ( u가 아직 방문되지 않았으면)

then u를 큐에 삽입 하고, u를 방문되었다고 표시;

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

0

13 4

2

(c) (d)(b)

(e) (g) (h)(f)

0

13 4

2

(a)1 1 2 1 2 4

2 4 4 3 3 1 2 4

Page 135: 알고리즘과 자료구조

너비 우선 탐색

너비 우선 탐색의 구현

인접 행렬 이용

void bfs_mat(GraphType *g, int v)

{

int w;

QueueType q;

init(&q); // 큐 초기화

visited[v] = TRUE; // 정점 v 방문 표시

printf("%d ", v);

enqueue(&q, v); // 시작 정점을 큐에 저장

while(!is_empty(&q)){

v = dequeue(&q); // 큐에 정점 추출

for(w=0; w<g->n; w++) // 인접 정점 탐색

if(g->adj_mat[v][w] && !visited[w]){

visited[w] = TRUE; // 방문 표시

printf("%d ", w);

enqueue(&q, w); // 방문한 정점을 큐에 저장

}

}

}

Page 136: 알고리즘과 자료구조

연결 성분 찾기

연결 성분

최대로 연결된 부분 그래프로 연결된 부분 그래프 중 크기가 최대인 것

그래프 탐색으로 구할 수 있다.

visited[i] = count;

0 3

1 4

2

visited

1

1

2

2

101234

void find_connected_component(GraphType *g)

{

int i;

count = 0;

for(i=0; i<g->n; i++)

if(!visited[i]){ // 방문되지 않았으면

count++;

dfs_mat(g, i);

}

}

Page 137: 알고리즘과 자료구조

신장 트리 (Spanning Tree)

신장 트리 (Spanning Tree)

그래프 내의 모든 정점을 포함하는 트리

신장 트리는 모든 정점들이 연결되어 있어야 하고, 사이클을 포함해서는 안됨

용도 : 최소의 링크를 사용하여 네트워크 구축 시, …

0

1 3 4

2

연결 그래프

0

1 3 4

2

0

1 3 4

2

0

1 3 4

2

신장 트리 중 일부

depth_first_search(v)

v를 방문되었다고 표시;

for all u ∈ (v에 인접한 정점) do

if (u가 아직 방문되지 않았으면)

then (v,u)를 신장 트리 간선이라고 표시;

depth_first_search(u)

Page 138: 알고리즘과 자료구조

최소비용신장트리(MTS)

최소비용신장트리(MST)

Minimum spanning tree

네트워크에 있는 모든 정점들을 가장 적은 간선과 비용으로 연결하는 신장 트리

MST 응용

도로 건설 – 도시를 모두 연결하면서 도로의 길이가 최소가 되도록 하는 문제

전기 회로 – 단자를 모두 연결하면서 전선의 길이가 가장 최소가 되도록 …

통신 – 전화선의 길이가 최소가 되도록 전화망을 구성하는 문제

배관 – 파이프를 모두 연결하면서 파이프의 총 길이가 최소가 되도록 연결

10

12

18

14

22

4

53

6

1715

Page 139: 알고리즘과 자료구조

MST 알고리즘

2가지 대표적인 알고리즘

Kruskal의 알고리즘

Prim의 알고리즘

탐욕적인 방법 (Greedy Method)

알고리즘 설계에서 있어서 중요한 기법 중의 하나

결정을 해야 할 때마다 그 순간에 가장 좋다고 생각되는 것을 해답으로 선택함으

로써 최종적인 해답에 도달

탐욕적인 방법은 항상 최적의 해답을 주는지를 반드시 검증해야 한다.

Kruskal의 알고리즘은 최적의 해답을 주는 것으로 증명

Page 140: 알고리즘과 자료구조

Prim의 MST 알고리즘

Prim의 MST 알고리즘

시작 정점에서부터 출발하여 신장 트리 집합을 단계적으로 확장해나가는 방법

시작 단계에서는 시작 정점만이 신장 트리 집합에 포함

앞 단계에서 만들어진 신장 트리 집합에, 인접한 정점들 중에서 최저 간선으로

연결된 정점을 선택하여 트리를 확장

이 과정은 트리가 n-1개의 간선을 가질 때까지 계속된다

간선 (a,b)와 간선 (f,e)의 가중치를

비교해 보면, (f,e)가 27로서 (a,b)의

29보다 낮다

따라서 (f,e) 간선이 선택되고, 정점 e가

신장 트리 집합에 포함된다.

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

Page 141: 알고리즘과 자료구조

Prim의 MST 알고리즘

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

Page 142: 알고리즘과 자료구조

Kruskal의 MST 알고리즘

Kruskal의 MST 알고리즘

최소 비용 신장 트리가 최소 비용의 간선으로 구성됨과 동시에 사이클을 포함하

지 않는다는 조건에 근거

각 단계에서 사이클을 이루지 않는 최소 비용 간선을 선택

그래프의 간선들을 가중치의 오름차순으로 정렬한다

정렬된 간선들의 리스트에서 사이클을 형성하지 않는 간선을 찾아서 현재의 최

소 비용 신장 트리의 집합에 추가

사이클을 형성하면 그 간선은 제외

Page 143: 알고리즘과 자료구조

Kruskal의 MST 알고리즘

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

f

ab

g

e

c

d

10

29

15

2527

22

12

16

8

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

10 12 15 16 18 22 25 27 29

af cd bg bc dg de eg ef ab

xx

Page 144: 알고리즘과 자료구조

kruskal의 MST 알고리즘의 구현

union-find 알고리즘

집합들의 합집합을 구하고 집합의 원소가 어떤 집합에 속하는지를 계산하는

알고리즘

여러 가지 방법으로 구현이 가능하다

Kruskal의 MST 알고리즘에서 사이클 검사에 사용된다.

a와 b가 같은 집합에 속함 a와 b가 다른 집합에 속함

사이클 형성 사이클이 형성되지 않음

Page 145: 알고리즘과 자료구조

최단경로 알고리즘

최단 경로(Shortest Path)

네트워크에서 정점 i와 정점 j를 연결하는 경로 중에서 간선들의 가중치 합이

최소가 되는 경로를 찾는 문제

간선의 가중치는 비용, 거리, 시간 등을 나타낸다.

4

0

51

6

2

3

310

72

5

4

1110

6

92

4

0 7 ∞ ∞ 3 10 ∞

7 0 4 10 2 6 ∞

∞ 4 0 2 ∞ ∞ ∞

∞ 10 2 0 11 9 4

3 2 ∞ 11 0 ∞ 5

10 6 ∞ 9 ∞ 0 ∞

∞ ∞ ∞ 4 5 ∞ 0

0 1 2 3 4 5 6

0

1

2

3

4

5

6

Page 146: 알고리즘과 자료구조

Dijkstra의 최단 경로 알고리즘

Dijkstra의 최단 경로 알고리즘

네트워크에서 하나의 시작 정점으로부터 모든 다른 정점까지의 최단 경로를 찾는

알고리즘

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

distance 배열: 최단 경로를 알려진 정점만을 통하여 각 정점까지 가는 최단경로의 길이

매 단계에서 가장 distance 값이 적은 정점을 S에 추가한다

v v

wu

ss

시작 노드

distance=7

distance=3

distance=5

① < (②+③)

①②

최단 경로

다른 경로

distance 값이최소인 노드

v

wu

s

새롭게추가된 노드

distance[u]distance[w]

weight[u][w]

distance 값 갱신

distance[w]=min(distance[w],distance[u]+weight[u][w])

Page 147: 알고리즘과 자료구조

Dijkstra의 최단 경로 알고리즘

// 입력: 가중치 그래프 G, 가중치는 음수가 아님.

// 출력: distance 배열, distance[u]는 v에서 u까지의 최단 거리이다.

shortest_path(G, v)

S←{v}

for 각 정점 w∈G do

distance[w]←weight[v][w];

while 모든 정점이 S에 포함되지 않으면 do

u←집합 S에 속하지 않는 정점 중에서 최소 distance 정점;

S←S∪{u}

for u에 인접하고 S에 있는 각 정점 z do

if distance[u]+weight[u][z] < distance[z]

then distance[z]←distance[u]+weight[u][z];

Page 148: 알고리즘과 자료구조

Dijkstra의 최단 경로 알고리즘

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

4

0

51

6

2

3

3 107

2

5

4

1110

6

92

4

S={0}distance[] = 0 7 ∞ ∞ 3 10 ∞

0 1 2 3 4 5 6

0

107

3

∞ ∞

0

105

3

𝟖 𝟏𝟒

S={0,4}distance[] = 0 5 ∞ 𝟏𝟒 3 10 𝟖

0 1 2 3 4 5 6

0

105

3

𝟖 𝟏𝟒

𝟗

0

105

3

𝟖 𝟏𝟐

𝟗

S={0,4,1}distance[] = 0 5 𝟗 𝟏𝟒 3 10 𝟖

0 1 2 3 4 5 6

S={0,4,1,6}distance[] = 0 5 𝟗 𝟏𝟒 3 10 𝟖

0 1 2 3 4 5 6

0

105

3

𝟖 𝟏𝟏

𝟗

0

105

3

𝟖 𝟏𝟏

𝟗

0

105

3

𝟖 𝟏𝟏

𝟗

S={0,4,1,6,2}distance[] = 0 5 𝟗 𝟏𝟏 3 10 𝟖

0 1 2 3 4 5 6

S={0,4,1,6,2,5}distance[] = 0 5 𝟗 𝟏𝟏 3 10 𝟖

0 1 2 3 4 5 6

S={0,4,1,6,2,5,3}distance[] = 0 5 𝟗 𝟏𝟏 3 10 𝟖

0 1 2 3 4 5 6

Page 149: 알고리즘과 자료구조

위상 정렬

방향 그래프에서 간선 <u, v>가 있다면 정점 u는 정점 v를 선행한다고 말한다

방향 그래프에 존재하는 각 정점들의 선행 순서를 위배하지 않으면서 모든 정점

을 나열하는 것을 방향 그래프의 위상 정렬(topological sort)이라고 한다.

예제 :

위상 정렬 : (0,1,2,3,4,5) , (1,0,2,3,4,5)

(2,0,1,2,4,5)는 위상 정렬이 아니다. 2번 정점이 0번 정점 앞에 오기 때문이다. 간선

<0,2>가 존재하기 때문에 0번 정점이 끝나야 만이 2번 정점을 시작할 수 있다.

위상정렬(Topological Sort)

U진입간선 진출간선

0

1

2

53

4

Page 150: 알고리즘과 자료구조

위상정렬(Topological Sort)

Input: 그래프 G=(V,E)

Output: 위상 정렬 순서

topo_sort(G)

for i←0 to do

if( 모든 정점이 선행 정점을 가지면 )

then 사이클이 존재하고 위상 정렬 불가;

선행 정점을 가지지 않는 정점 v 선택;

v를 출력;

v와 v에서 나온 모든 간선들을 그래프에서 삭제;

0

1

2

53

4

0 2

53

4

0 2

53

2

53 53 5

초기 상태 1 제거4 제거

0 제거 2 제거 3 제거

Page 151: 알고리즘과 자료구조

해싱(Hashing)

Page 152: 알고리즘과 자료구조

해싱 (Hashing)

해싱

키 값에 직접 산술적인 연산을 적용하여 항목이 저장되어 있는 테이블의 주소를

계산하여 항목에 접근

값의 연산에 의해 직접 접근이 가능한 구조를 해시테이블(hash table)이라 부르

며, 해시테이블을 이용한 탐색을 해싱(hashing)

사전구조(dictionary): 맵(map)이나 테이블(table)로 불리우기도 한다.

사전 구조는 다음과 같이 탐색 키와 탐색 키와 관련된 값의 2가지 종류의 필드를 가짐

영어 단어나 사람의 이름 같은 탐색 키(search key)

단어의 정의나 주소 또는 전화 번호 같은 탐색 키와 관련된 값(value)

객체 : 일련의 (key, value) 쌍의 집합

연산 :

- add(key, value) ::= (key, value)를 사전에 추가한다.

- delete(key) ::= key에 해당되는 (key, value)를 찾아서 삭제한다. 관련된 value를 반환한다.

만약 탐색이 실패하면 NULL를 반환한다.

- search(key) ::= key에 해당되는 value를 찾아서 반환한다.만약 탐색이 실패하면 NULL를 반환한다.

Page 153: 알고리즘과 자료구조

해싱의 구조

해시함수

탐색 키를 입력으로 받아 해시 주소(hash address)를 생성

해시 주소가 배열로 구현된 해시 테이블(hash table)의 인덱스가 된다.

예제

학생들에 대한 정보를 해싱으로 저장, 탐색

학번=탐색키,

학번은 5자리이고 앞의 2개의 숫자가 학과, 뒤의 3자리 숫자는 학과의 학생 번호

같은 학과 학생들만 저장된다고 가정하면 뒤의 3자리만 사용할 수 있다.

해시함수h(x)

해시테이블ht[]

0

1

2

M-1

해시주소

안철수

0

1

2

23

M-1

해시테이블h(01023)

Page 154: 알고리즘과 자료구조

해싱의 구조

해시테이블

해시테이블 ht는 M개의 버켓(bucket)으로 이루어지는 테이블로서 ht[0],

ht[1], ...,ht[M-1]의 원소를 가진다

하나의 버켓은 s개의 슬롯(slot)을 가질 수 있다

충돌(Collision)

서로 다른 두 개의 탐색 키 k1과 k2에 대하여 h(k1) = h(k2)인 경우

오버플로우(Overflow)

충돌이 버켓에 할당된 슬롯 수보다 많이 발생하게 되면 버켓에 더 이상 항목을 저장할

수 없게 됨

S개의 슬롯

M개의 버킷

버킷 0

버킷 1

버킷 M-1

슬롯 0 슬롯 S-1

Page 155: 알고리즘과 자료구조

실제 해싱

해시테이블

크기가 제한되어 있어, 탐색 키마다 하나의 공간을 할당할 수가 없다

충돌과 오버플로우 발생

해싱함수가 필요 : h(k)= k mod M

좋은 해시 함수 조건

충돌이 적어야 한다.

해시함수 값이 해시테이블의 주소 영역 내에서 고르게 분포되어야 한다.

계산이 빨라야 한다.

안철수

0

1

2

23

M-1

해시테이블h(01023)

h(01055)

Page 156: 알고리즘과 자료구조

제산 함수

h(k)=k mod M

해시 테이블의 크기 M는 소수(prime number)

폴딩 함수

hash_index=(short)(key ^ (key>>16))

이동 폴딩(shift folding)

경계 폴딩(boundary folding)

중간제곱함수

중간제곱함수는 탐색 키를 제곱한 다음, 중간의 몇 bit를 선택 해시 주소를 생성

비트추출함수

탐색 키를 이진수로 간주하여 임의의 위치의 k개의 비트를 해시 주소로 사용

숫자 분석 방법

키의 각각의 위치에 있는 숫자 중에서 편중되지 않는 수들을 해시 테이블의 크기

에 적합한 만큼 조합하여 해시 주소로 사용

해시함수

123 203 241 112 20

123 203 241 112 20 699

123 302 241 211 20 897

+ + + +

+ + + +

=

=

Page 157: 알고리즘과 자료구조

충돌 해결

충돌 해결 전략

완전해시(Perfect hashing)

한 개의 데이터 영역에 한 개의 키만이 대입 ->(일반적) 구현불가능

버킷 크기 크게 -> 메모리 용량 낭비

Open addressing(개방 주소법)

오버플로우가 일어났을 때 다른 데이터주소로 다시 해시 시키는 방법(반복 수행)

Closed addressing(폐쇄 주소법, 체인법)

같은 데이터 주소 내에서 끝까지 해결을 보는 방법

충돌 해결 방법

선형조사법: 충돌이 일어난 항목을 해시 테이블의 다른 위치에 저장한다

체이닝: 해시테이블의 하나의 위치가 여러 개의 항목을 저장할 수 있도록

해시테이블의 구조를 변경

Page 158: 알고리즘과 자료구조

선형 조사법

선형 조사법

충돌이 ht[k]에서 충돌이 발생했다면 ht[k+1]이 비어 있는지를 조사

만약 비어있지 않다면 ht[k+2]를 살펴본다.

이런 식으로 비어있는 공간이 나올 때까지 계속하는 조사하는 방법이다.

만약 테이블의 끝에 도달하게 되면 다시 테이블의 처음으로 간다.

조사를 시작했던 곳으로 다시 되돌아오게 되면 테이블이 가득 찬 것이 된다.

조사되는 위치

h(k), h(k)+1, h(k)+2,…

예 : h(k)=k mod 7

1단계 (8) : ∙h(8) = 8 mod 7 = 1(저장)

2단계 (1) : ∙h(1) = 1 mod 7 = 1(충돌발생)

∙(h(1)+1) mod 7 = 2(저장)

3단계 (9) : ∙h(9) = 9 mod 7 = 2(충돌발생)

∙(h(9)+1) mod 7 = 3(저장)

4단계 (6) : ∙h(6) = 6 mod 7 = 6(저장)

5단계 (13) :∙h(13) = 13 mod 7 = 6(충돌 발생)

1단계 2단계 3단계 4단계 5단계

[0] 13

[1] 8 8 8 8 8

[2] 1 1 1 1

[3] 9 9 9

[4]

[5]

[6] 6 6

환치 발생(단점) 해시 테이블 구조가 간단(장점)

Page 159: 알고리즘과 자료구조

이차 조사법(Quadratic probing)

이차 조사법

선형 조사법에서의 문제점인 집중과 결합을 크게 완화

선형 조사법과 유사하지만, 다음 조사할 위치를 다음 식에 의하여 결정

(h(k)+i*i) mod M

조사되는 위치

h(k), h(k)+1, h(k)+4,…

테이블의 크기가 소수여야 함(테이블의 반 정도의 영역만이 탐색가능)

아닐 경우 탐색영역 현저히 감소

Page 160: 알고리즘과 자료구조

이중 해싱법(Double Hashing)

이중 해싱법

오버플로우가 발생함에 따라 항목을 저장할 다음 위치를 결정할 때, 원래 해시

함수와 다른 별개의 해시 함수를 이용하는 방법이다

step=C-(k mod C)

h(k), h(k)+step, h(k)+2*step, …

(예) 크기가 7인 해시테이블에서 첫 번째 해시 함수가 k mod M이고.

오버플로우 발생시의 해시 함수step=5-(5 mod 5)

입력 파일 (8, 1, 9, 6, 13 )

1단계 (8) : ∙h(8) = 8 mod 7 = 1(저장)

2단계 (1) : ∙h(1) = 1 mod 7 = 1(충돌발생)

∙(h(1)+h‘(1)) mod 7 = (1+5-(1 mod 5)) mod 7 = 5(저장)

3단계 (9) : ∙h(9) = 9 mod 7 = 2(저장)

4단계 (6) : ∙h(6) = 6 mod 7 = 6(저장)

5단계 (13) :∙h(13) = 13 mod 7 = 6(충돌 발생)

∙(h(13)+h‘(13)) mod 7 = (6+5-(13 mod 5)) mod 7= 1(충돌발생)

∙(h(13)+2*h‘(13)) mod 7 = (6+2*2) mod 7= 3(저장)

1단계 2단계 3단계 4단계 5단계

[0]

[1] 8 8 8 8 8

[2] 9 9 9

[3] 13

[4]

[5] 1 1 1 1

[6] 6 6

Page 161: 알고리즘과 자료구조

체이닝(chaining)

체이닝

오버플로우 문제를 연결 리스트로 해결

각 버켓에 고정된 슬롯을 할당하는 것이 아니라 각 버켓에, 삽입과 삭제가 용이한 연결

리스트를 할당한다

버켓 내에서는 원하는 항목을 찾을 때는 연결 리스트를 순차 탐색한다

(예) 크기가 7인 해시테이블에 h(k)=k mod 7의 해시 함수를 이용하여,

8, 1, 9, 6, 13 을 삽입할 때에의 체이닝에 의한 충돌 처리

1단계 (8) : ∙h(8) = 8 mod 7 = 1(저장)

2단계 (1) : ∙h(1) = 1 mod 7 = 1(충돌발생->새로운 노드 생성 저장)

3단계 (9) : ∙h(9) = 9 mod 7 = 2(저장)

4단계 (6) : ∙h(6) = 6 mod 7 = 6(저장)

5단계 (13) :∙h(13) = 13 mod 7 = 6(충돌 발생->새로운 노드 생성 저장)

0

1

2

3

4

5

6

8 1 n

9

6 2 n

(1) (2)

(3)

(4) (5)

Page 162: 알고리즘과 자료구조

해싱의 성능 분석

적재 밀도(loading density)

적재 비율(loading factor)

저장되는 항목의 개수 n과 해시 테이블의 크기 M의 비율이다.

선형 조사법

실패한 검색 :

성공한 검색 :

체이닝

실패한 검색 : α

성공한 검색 :

α = 저장된항목의개수

해싱테이블의버킷의개수= 𝒏

𝑴

𝟏

𝟐𝟏 +

𝟏

𝟏+α 𝟐

𝟏

𝟐𝟏 +

𝟏

𝟏+α

1 + α𝟐

Page 163: 알고리즘과 자료구조

해싱과 다른 탐색 방법의 비교

탐색 삽입 삭제

순차 탐색 O(n) O(1) O(n)

이진 탐색 O(Log2n) O(Log2n+n) O(Log2n+n)

이진탐색 트리 균형 트리 O(Log2n) O(Log2n) O(Log2n)

경사 트리 O(n) O(n) O(n)

해싱 최선의 경우 O(1) O(1) O(1)

최악의 경우 O(n) O(n) O(n)

Page 164: 알고리즘과 자료구조

탐색(Search)

Page 165: 알고리즘과 자료구조

탐색 (Search)

탐색이란…

여러 개의 자료 중에서 원하는 자료를 찾는 작업

컴퓨터가 가장 많이 하는 작업 중의 하나

탐색을 효율적으로 수행하는 것은 매우 중요

탐색키(Search key): 항목과 항목을 구별해주는 키(key)

탐색을 위해 사용되는 자료 구조

배열, 연결 리스트, 트리, 그래프 등

탐색에 의한 문제 해결

문제의 해에 도달하기 위한 탐색과정을 직접 수행함으로써 보다 포괄적이며,

자동화된 해결방안

문제 해결 과정 중에 지적 판단이 요구되는 경우 탐색기법이 유용

문제 해결의 최적의 방법보다 적당한 방법을 찾는 것이 쉽다

Page 166: 알고리즘과 자료구조

순차 탐색(Sequential Search)

순차 탐색

가장 간단하고 직접적인 탐색 방법

정렬되지 않은 배열의 항목들을 처음부터 마지막까지 하나씩 검사하여 원하는

항목을 찾아가는 방법

int seq_search(int key, int low, int high)

{

int i;

for(i=low; i<=high; i++)

if(list[i]==key)

return i; // 탐색 성공

return -1; // 탐색 실패

}

9 5 8 3 7

8을 찾는 경우

9 5 8 3 7

9 5 8 3 7

9 5 8 3 7

2를 찾는 경우

9 5 8 3 7

9 5 8 3 7

9 5 8 3 7

9 5 8 3 7

탐색 성공

탐색 실패

9≠8이므로 탐색 계속

5≠8이므로 탐색 계속

8=8이므로 탐색 성공

9≠2이므로 탐색 계속

5≠2이므로 탐색 계속

8≠2이므로 탐색 계속

3≠2이므로 탐색 계속

7≠2이므로 탐색 계속더 이상 항목이 없으므로 실패

Page 167: 알고리즘과 자료구조

개선된 순차 탐색

How to ?

비교 횟수를 줄이기 위해 리스트의 끝에 찾고자 하는 키 값을 저장하고 반복문의

탈출 조건을 키 값을 찾을 때까지로 설정

int seq_search2(int key, int low, int high)

{

int i;

list[high+1] = key;

// 키값을 찾으면 종료

for(i=low; list[i] != key; i++)

;

if(i==(high+1)) return -1; // 탐색 실패

else return i; // 탐색 성공

}

9 5 8 3 7 8

8을 찾는 경우

9 5 8 3 7 8

9 5 8 3 7 8

9 5 8 3 7 2

2를 찾는 경우

9 5 8 3 7 2

9 5 8 3 7 2

9 5 8 3 7 2

9 5 8 3 7 2

탐색 성공

탐색 실패

9≠8이므로 탐색 계속

5≠8이므로 탐색 계속

8=8이므로 탐색 성공

9≠2이므로 탐색 계속

5≠2이므로 탐색 계속

8≠2이므로 탐색 계속

3≠2이므로 탐색 계속

2=2이지만 마지막 항목따라서 탐색 실패

Page 168: 알고리즘과 자료구조

이진탐색(Binary Search)

이진 탐색

정렬된 배열의 탐색에 적합

배열의 중앙에 있는 값을 조사하여 찾고자 하는 항목이 왼쪽 또는 오른쪽 부분

배열에 있는지를 알아내어 탐색의 범위를 반으로 줄임

(예) 10억 명중에서 이진 탐색을 이용하여 특정한 이름을 탐색

이진탐색은 단지 30번의 비교

순차 탐색에서는 평균 5억 번의 비교

1 3 5 6 7 9 11 20 30

1 3 5 6 7 9 11 20 30

1 3 5 6 7 9 11 20 30

1 3 5 6 7 9 11 20 30

1 3 5 6 7 9 11 20 30

5을 찾는 경우7과 비교

5<7이므로 앞부분만을 다시 탐색

5와3을비교

5>3이므로 뒷부분만을 다시 탐색

5=5이므로 탐색 성공

Page 169: 알고리즘과 자료구조

이진탐색 알고리즘

search_binary(list, low, high)

middle ← low에서 high사이의 중간 위치

if( 탐색값 ≠ list[middle] )

return TRUE;

else if (탐색값 < list[middle] )

return list[0]부터 list[middle-1]에서의 탐색;

else if (탐색값 > list[middle] )

return list[middle+1]부터 list[high]에서의 탐색;

2 6 11 13 18 20 22 27 29 30 34 38 41 42 45 47

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

2 6 11 13 18 20 22 27 29 30 34 38 41 42 45 47

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

2 6 11 13 18 20 22 27 29 30 34 38 41 42 45 47

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

2 6 11 13 18 20 22 27 29 30 34 38 41 42 45 47

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Low Highmiddle

middleLow High

middleLow High

middleLow High

Page 170: 알고리즘과 자료구조

색인순차탐색(indexed sequential search)

색인순차탐색

인덱스(index)라 불리는 테이블을 사용하여 탐색의 효율을 높이는 방법

인덱스 테이블은 주 자료 리스트에서 일정 간격으로 발췌한 자료를 가지고 있다

주 자료 리스트와 인덱스 테이블은 모두 정렬되어 있어야 한다

3 0

15 3

67 6

3

9

15

22

31

55

67

88

91

인덱스 테이블 주자료 테이블

0

1

2

3

4

5

6

7

8

0

1

2

Page 171: 알고리즘과 자료구조

균형이진탐색트리

이진탐색(binary search)과 이진탐색 트리(binary search tree)와의 차이점

이진 탐색과 이진 탐색 트리는 근본적으로 같은 원리에 의한 탐색 구조

이진 탐색은 자료들이 배열에 저장되어 있으므로 삽입과 삭제가 상당히 힘들다.

즉 자료를 삽입하고 삭제할 때마다 앞뒤의 원소들을 이동시켜야 한다.

이진 탐색 트리는 비교적 빠른 시간 안에 삽입과 삭제를 끝마칠 수 있는 구조

삽입, 삭제가 빈번히 이루어진다면 반드시 이진 탐색 트리를 사용하여야 한다.

이진탐색 트리에서의 시간복잡도

균형 트리: O(logn)

불균형 트리: O(n), 순차탐색과 동일

1

2 3

4 5 6 7

1

2

3

4

5

6

7

Page 172: 알고리즘과 자료구조

AVL 트리

AVL 트리

Adelson-Velskii와 Landis에 의해 1962년에 제안

각 노드에서 왼쪽 서브 트리의 높이와 오른쪽 서브 트리의 높이 차이가 1 이하인

이진 탐색 트리

트리가 비균형 상태로 되면 스스로 노드들을 재배치하여 균형 상태로 만든다

AVL 트리는 균형 트리가 항상 보장되기 때문에 탐색이 O(logn)시간 안에 끝나게 된다

균형 인수(balance factor)=(왼쪽 서브 트리의 높이 - 오른쪽 서브 트리의 높이)

모든 노드의 균형 인수가 ±1 이하이면 AVL 트리이다

7

5 9

3

1

균형인수 +2, -2

균형인수 +1, -1

균형 인수 0

AVL 트리 Non-AVL 트리

1

0

0

7

5 9

3

1

1

0

0 2

2

Page 173: 알고리즘과 자료구조

AVL 트리의 연산

AVL 트리의 연산

탐색연산: 이진탐색 트리와 동일

균형을 이룬 이진탐색 트리에서 균형 상태가 깨지는 경우

삽입 연산과 삭제 연산

삽입 연산 시에는 삽입되는 위치에서 루트까지의 경로에 있는 조상 노드들의 균

형 인수에 영향을 줄 수 있다.

따라서 즉 새로운 노드의 삽입 후에 불균형 상태로 변한 가장 가까운 조상 노드,

즉 균형 인수가 ±2가 된 가장 가까운 조상 노드의 서브 트리들에 대하여 다시 균형을

잡아야 한다.

균형인수 +2, -2

균형인수 +1, -1

균형 인수 0

7

5 9

3

1

1

0

0

7

5 9

3

1

1

0

02

2

Page 174: 알고리즘과 자료구조

AVL 트리의 삽입 연산

균형 트리로 만드는 방법

회전(rotation)

AVL 트리에서 균형이 깨지는 경우에는 다음의 4가지의 경우새로 삽입된 노드 N로부터 가장 가까우면서 균형 인수가 ±2가 된 조상 노드를 A라고 가정

LL 타입: N이 A의 왼쪽 서브 트리의 왼쪽 서브 트리에 삽입된다.

LR 타입: N이 A의 왼쪽 서브 트리의 오른쪽 서브 트리에 삽입된다.

RR 타입: N이 A의 오른쪽 서브 트리의 오른쪽 서브 트리에 삽입된다.

RL 타입: N이 A의 오른쪽 서브 트리의 왼쪽 서브 트리에 삽입된다.

7

3 9

1

1

0

0

5

0

0

Page 175: 알고리즘과 자료구조

회전 방법

회전 종류

LL 회전: A부터 N까지의 경로상의 노드들을 오른쪽으로 회전시킨다.

LR 회전: A부터 N까지의 경로상의 노드들을 왼쪽-오른쪽으로 회전시킨다.

RR 회전: A부터 N까지의 경로상의 노드들을 왼쪽으로 회전시킨다.

RL 회전: A부터 N까지의 경로상의 노드들을 오른쪽-왼쪽으로 회전시킨다.

A

B

T1 T2

T3

A

B

T1

T2

T3

B

A

T2 T3

T1

일반적인 LL 회전

A

T4

B

C

T2 T3T1

A

T4

B

C

T2

T3

T1

삽입 LL 회전

A

T4

C

BT2

T1

T3

RR 회전

C

BA

T2

T1

T3 T4

Page 176: 알고리즘과 자료구조

AVL Tree 삽입 예제(1/2)

8

7 9

2

1

1

0

0

7

0

(1) 7삽입

7

8

-1

0

(2) 8삽입

7

8

-2

1

9

0

RR 회전

8

7 9

0

0 0

(3) 9삽입

(4) 2삽입

8

7 9

2

2

2

1

0

(5) 1삽입

1

0

LL 회전

8

2 9

1 7

1

0 0

0 0

Page 177: 알고리즘과 자료구조

AVL Tree 삽입 예제(2/2)

(6) 5삽입

8

2 9

1 7

2

-1 0

0 1

5

0

LR 회전

7

2 8

1 5 9

0

0

0 0 0

-1

(7) 3삽입

7

2 8

1 5 9

1

-1

0 0

-1

3

1

(8) 6삽입

7

2 8

1 5 9

1

-1

0 0

-1

3

0

6

00

(9) 4삽입

7

2 8

1 5 9

2

-2

0 0

-1

3

1

6

0-1

40

RL 회전

7

3 8

2 5 9

1

0

1 0

-1

4

0

600

10

Page 178: 알고리즘과 자료구조

동적 프로그래밍(Dynamic Programming)

Page 179: 알고리즘과 자료구조

동적 프로그래밍(Dynamic Programming)

동적 프로그래밍

분할 정복법과 반대되는 접근 방식으로 상향식(Bottom-up) 해결법

분할 정복법과 같이 문제를 작은 여러 개로 나누어 이들 사례를 먼저 해결하고,

해결한 값을 보관하여, 나중에 이 값이 필요하면 다시 계산하지 않음

피보나치 수열

나누어진 부분들이 서로 연관이 있다.

f(5)를 계산하기 위해 f(2)를 세 번 사용?

즉, 분할 정복 방법을 적용하여 알고리즘을 설계하게 되면 같은 항을 한 번 이상

계산하는 결과를 초래하게 되므로 비 효율적

따라서, 이 경우에는 Divide & Conquer 기법은 적합하지 않다

Fib(0) Fib(1) Fib(0) Fib(1) Fib(1) Fib(2)

Fib(0) Fib(1)

Fib(1) Fib(2) Fib(2) Fib(3)

Fib(3) Fib(4)

Fib(5)

Page 180: 알고리즘과 자료구조

Matrix-Chain Multiplication

행렬 곱셈

i × j 행렬과 j × k행렬을 곱하기 위해서는 일반적으로 i × j × k번 만큼의 기본적

인 곱셈이 필요하다.

연쇄적으로 행렬을 곱할 때, 어떤 행렬곱셈을 먼저 수행하느냐에 따라서 필요한

기본적인 곱셈의 횟수가 달라지게 된다.

예를 들어서, 다음 연쇄 행렬 곱셈을 생각해 보자:

A1 × A2 × A3

A1의 크기는 10 × 100,

A2의 크기는 100 × 5

A3의 크기는 5 × 50

(A1 × A2) × A3 곱셈의 총 횟수는 7,500회

A1 × (A2 × A3) 곱셈의 총 횟수는 75,000 회

연쇄적으로 행렬을 곱할 때 기본적인 곱셈의 횟수가 가장 적게 되는

최적의 순서를 결정하는 알고리즘 개발이 목표이다

Page 181: 알고리즘과 자료구조

Matrix-Chain Multiplication

무작정 알고리즘

가능한 모든 순서를 모두 고려해 보고, 그 가운데에서 가장 최소를 택한다.

시간복잡도 분석: 최소한 지수(exponential-time) 시간

n개의 행렬(A1 , A2 ,…, An)을 곱할 수 있는 모든 순서의 경우의 수 = tn 개

만약 A1이 마지막으로 곱하는 행렬이라고 하면, 행렬 A2 ,…, An을 곱하는 경우의

수는 = tn-1 개

An이 마지막으로 곱하는 행렬이라고 하면, 행렬 A1,…, An-1을 곱하는 데는 또한

tn-1개의 가지 수가 있을 것이다.

• 그러면, tn ≥ tn-1 + tn-1 = 2 tn-1이고, t2= 1이라는 사실은 쉽게 알 수 있다.

• 따라서 tn ≥ 2tn-1 ≥ 22tn-2 ≥ … ≥ 2n-2t2 = 2n-2 = (2n)이다.

Page 182: 알고리즘과 자료구조

DP 기반 행렬 곱셈 – 설계

dk를 행렬 Ak의 열의 수라고 하자. 그러면 Ak의 행(row)의 수는 dk-1가 된다

A1의 행의 수는 d0라고 하면, 다음과 같이 재귀 관계식을 구축할 수 있다

M[i][j] =i < j일 때, (1 ≤ i < j ≤ n)

Ai부터 Ai까지의 행렬을 곱하는데 필요한 곱셈의 최소 횟수

Matrix-Chain Multiplication

⋯⋮ 𝑨𝒌 − 𝟏 ⋮⋯

⋮ 𝑨𝒌⋮

dk-1

dk-1

dk-2 dk-1

M[i][j]

min𝒊≤𝒌≤𝒋−𝟏

(𝑴 𝒊 𝒌 +𝑴 𝒌+ 𝟏 𝒋 + 𝒅𝒊− 𝟏𝒅𝒌𝒅)

0 i = j

i < j

(Ai … Ak)(Ak+1 … Aj)

k=i, i+1, … j-2, j-1

dk-1 × dk dk × dj

Page 183: 알고리즘과 자료구조

Matrix-Chain Multiplication

DP 기반 행렬 곱셈 – 적용 예

A2

2 × 3

A1

5 × 2

A3

3 × 4

A4

4 × 6

A5

6 × 7

A6

7 × 8

M[4][6] = min(M[4][4]+M[5][6]+4×6×8, M[4][5]+M[6][6]+4× 𝟕 ×8

= min(0+6×7×8+4×6×8, 4×6×7+0+4×7×8)

= min(528, 393) = 392

M[i][j] 1 2 3 4 5 6

1 0 30 64 132 226 348

2 0 24 72 156 268

3 0 72 198 366

4 0 168 392

5 0 336

6 0

Page 184: 알고리즘과 자료구조

Matrix-Chain Multiplication

DP 기반 행렬 곱셈 – 최적 순서의 구축

최적 순서를 얻기 위해 M[i][j]를 계산 시, 최소값을 주는 k값을 P[i][j]에 기억

예제

P[2][5] = 4인 경우의 최적 순서는 (A2 A3 A4)A5이다

구축한 P는 다음과 같다

따라서, 최적 분해는 (A1(((A2A3)A4)A5)A6) 이다.

P[i][j] 1 2 3 4 5 6

1 1 1 1 1 1

2 2 3 4 5

3 3 4 5

4 4 5

5 5

Page 185: 알고리즘과 자료구조

나는 그저 NP-완비이론이 흥미로운 발상이라고만 생각했다.그것이 지닌 잠재적 영향력은 제대로 인식하지 못했다. - 스티븐 쿡

때로는 어떤 것이 불가능하다는 사실이 유용할 때도 있다.- 레오나드 레빈

NP-완비

Page 186: 알고리즘과 자료구조

Inception

지금까지 살펴본 문제들은 시간 복잡도를 갖는 알고리즘이었다.

O(Logn), O(n), O(nlogn), O(n2), O(n3)…

하지만, 모든 문제에 대해 이와 같은 알고리즘이 존재하는 것은 아니다.

그래서..

NP-완비

NP-완전 문제

NP-완전성

NP-Complete

NP-Completeness

정지문제힐버트의 10번째 문제…

프레스버거 산술문제…

최소신장트리 문제최단거리 문제…

현실적인 시간 내에

풀 수 없는 문제들

현실적인 시간 내에

풀 수 있는 문제들

풀 수 없는 문제들(Unsolvable, Undecidable)

풀 수 있는 문제들(Solvable, Decidable)

NP-완비 문제들

여기에 속할 것이라고강력히 추정 !!!

Page 187: 알고리즘과 자료구조

NP란 무엇인가?

NP의 정의

P는 빨리 풀 수 있는 문제군

NP는 빨리 확인할 수 있는 문제군

미정다항 시간집단 문제(Nondeterministic Polynomial Time Problem)

YES/No 문제

판정 문제란 문제의 해가 “예/아니오”로 나오는 형태를 말한다.

그래프 G에서 길이가 K이하인 해밀토니안 경로가 존재하는가?

최적화 문제

최소치나 최대치를 구하는 형태의 문제를 최적화 문제라고 한다.

그래프 G에서 길이가 가장 짧은 해밀토니안 경로는 얼마인가?

어떤 집단의 문제들은 어려워

보이기는 하지만,

그 답을 보면 옳다는 것을 검증하기

쉬운 집단 문제

P

NP

Page 188: 알고리즘과 자료구조

NP-완전 이론/변환

NP-완전 이론

Yes/No 대답을 요구하는 문제에 국한되나, 최적화 문제와 밀접한 관계를 갖고

있다.

문제를 현실적인 시간 안에 풀 수 있는가에 관한 이론

해당 문제가 NP-완전 이론임이 확인되면,

최적의 해를 구하는 방법을 아직 모름

아직까지는 이 문제를 현실적인 시간에 풀 수 있는 방법이 없다고 생각하면 된다.

변환

판정 문제와 최적화 문제가 서로 관계가 있고, 최적화 문제에 비해 판정 문제가

쉽다.

변환은 다항식 시간에 이루어진다.

두 사례의 답은 일치한다.

문제 A 문제 B다항식 시간 변환

YESNO

YESNO

문제 A의 사례 α를 문제 B의 사례 β로 바꾸되,

이 성질에 만족하면 다항식 시간 변환이라고 하고,

α ≤ Pβ 로 표기한다.

Page 189: 알고리즘과 자료구조

NP-Complete에 관한 비유

상사가 아주 어려운 문제를 해결하라고 지시했다

1. 나는 답을 구할 수가 없습니다.

아마 나는 멍청한 모양입니다.

2. 나는 답을 구할 수가 없습니다.

왜냐하면 이 문제는 답이 없어요.

3. 나는 답을 구할 수가 없습니다.

그렇지만 저렇게 수많은 천재들도

이 문제를 해결할 수 없습니다.

Page 190: 알고리즘과 자료구조

NP-Hard/NP-Complete

NP-Hard

모든 NP 문제가 문제 L로 다항식 시간에 변환가능 하면, 문제 L은 NP-Hard이다.

NP-Hard 증명 예

해밀토니안 싸이클 문제가 NP-Hard임을 알고 있다(가정)

이를 이용해 TSP 문제가 NP-Hard임을 보일 수 있다.

NP-Complete

문제 L은 NP이다.

문제 L은 NP-Hard이다.

그렇다면, 문제 L은 NP-완전 문제 이다.

다항식 시간 변환α β 문제 L을 푸는

알고리즘

Yes

No

Yes

No

문제 A를 푸는 알고리즘

NP

P

NP-Hard

NP-Complete

P 부분은 추정

Page 191: 알고리즘과 자료구조

NP 이론의 유용성

NP 이론의 유용성

어떤 문제가 NP-Complete/Hard 임이 확인되면,

쉬운 알고리즘을 찾고자 하는 헛된 노력을 중지하고,

주어진 시간 내에 최대한 좋은 해를 찾는 알고리즘 개발에 집중한다.

Page 192: 알고리즘과 자료구조

References

도서