Transcript Document

Sorting
Ikjun Yeom
여러 키에 의한 정렬 (1)
 K1,K2,...,Kt(K1은 최대 유효 키, Kt는 최소 유효 키)의 여러
개의 키를 갖는 레코드의 정렬
– 모든 레코드 쌍 i, j에 대하여
1
t
1
t
• i<j, ( Ki ,..., Ki )  ( K j ,..., K j ) 이 성립하면 레코드 R1,...,Rn의 리스
트는 키 K1,...,Kt로 정렬된 것
 카드 뭉치를 정렬하는 문제
– 두 개의 키 (무늬, 숫자)에 대한 정렬 문제
• K1[무늬] : ♣ < ◆ < ♥ < ♠
• K2[숫자] : 2<3<4<...<10<J<Q<K<A
정렬된 카드 뭉치  2♣,.....,A♣,......,2♠,...,A♠
2
여러 키에 의한 정렬 (2)
 MSD(most-significant-digit-first) 정렬
– 최대 유효 숫자 우선 정렬
• 먼저 최대 유효 키 K1으로 정렬  K1에 대해 같은 값을 가
지는 여러 레코드 파일(pile)들이 만들어짐
• 각 파일에 대해 독립적으로 K2로 정렬  K1,K2에 대해 같은
값을 가지는 서브 파일(sub pile)들이 만들어짐
• 각 서브파일에 대해서는 K3으로 정렬
• 최종적으로 이렇게 얻어진 파일들을 합친다.
3
여러 키에 의한 정렬 (3)
 LSD(least-significant-digit-first) 정렬
– 최소 유효 숫자 우선 정렬
• 카드 숫자 값(키 K2)에 따라 13개의 파일을 만듦
• 3들을 2들 위에, king들을 queen들 위에, ace들을 king들 위에 올려놓음
• 카드 뭉치를 거꾸로 놓고 안정된 정렬 방법을 이용하여 무늬(K1)에 따라
4개의 파일로 만듦
• 4개의 파일들은 각각 키 K2에 따라 정렬되게 함
• 4개의 파일을 합침
 LSD가 MSD보다 더 단순
– 생성된 파일과 서브 파일을 독립적으로 정렬할 필요가 없으므로 오
버헤드가 적게 든다.
4
여러 키에 의한 정렬 (4)
 기수 (radix) 정렬
– 어떤 기수 r을 이용하여 정렬 키를 몇 개의 숫자로 분해
• r = 10 : 키를 십진수로 분할
• r = 2 : 키를 이진수로 분할
– 기수-r 정렬에서는 r개의 빈(bin)이 필요
• (why?) 정렬되어야 하는 레코드가 R1,...,Rn일 때, 레코드의 키는
기수-r을 이용하여 분할  0~(r-1) 사이의 d개의 숫자를 가진 키
가 된다.
• 각 빈의 레코드는 빈의 첫 레코드를 가리키는 포인터와 마지막
레코드를 가리키는 포인터를 통해 체인으로 연결되며, 체인은 큐
처럼 동작
5
여러 키에 의한 정렬 (5)
int radixSort(element a[], int link[], int d, int r, int n)
{
int front[r], rear[r];
int i, bin, current, first, last;
first = 1;
for(i = 1; i < n; i++) link[i] = i+1;
link[n] = 0;
for(i=d-1; i >=0; i--)
{
for(bin = 0; bin < r; bin++) front[bin] = 0;
for(current = first; current; current = link[current])
{
bin = digit[a[current], i, r);
if(front[bin] == 0) front[bin] = current;
else link[rear[bin]] = current;
rear[bin] = current;
}
for(bin++; bin < r; bin++)
if(front[bin])
{link[last] = front[bin]; last = rear[bin]; }
link[last] = 0;
}
return first;
}
LSD 기수 6정렬
여러 키에 의한 정렬 (6)
 radixSort의 분석 :
– 전체 연산 시간 : O(d(n+r))
• d 패스를 처리하면서 각 패스의 연산 시간은 O(n+r)임
– d 값은 기수 r의 선택과 가장 큰 키에 의해 정해짐
– r의 선택을 달리하면 연산 시간이 달라진다.
7

여러 키에 의한 정렬 (7)
기수 정렬의 예
–
범위가 [0, 999]인 십진수를 정렬 (d=3, r=10)
a[1]
a[2]
a[3]
a[4]
a[5]
a[6]
a[7]
a[8]
a[9]
a[10]
179
208
306
93
859
984
55
9
271
33
e[6]
e[7]
e[8]
e[9]
(a) 초기 입력
e[0]
e[1]
e[2]
e[3]
e[4]
e[5]
9
33
271
859
93
984
55
306
208
179
f[0]
f[1]
f[2]
f[3]
f[4]
f[5]
f[6]
f[7]
f[8]
f[9]
179
93
33
984
55
306
208
179
859
9
(b) 첫번째-패스 큐들 (First-pass
queues) 과 결과 체인
8
여러 키에 의한 정렬 (8)
e[0]
e[1]
e[2]
e[3]
e[4]
e[5]
e[6]
e[7]
e[8]
e[9]
9
208
306
33
859
179
55
271
984
93
f[0]
f[1]
f[2]
f[3]
f[4]
f[5]
f[6]
f[7]
f[8]
f[9]
306
208
9
33
55
859
271
179
984
93
(c) 두 번째-패스 큐들 (Second-pass queues) 과 결과 체인
9
여러 키에 의한 정렬 (9)
e[0]
e[1]
e[2]
e[3]
e[4]
e[5]
e[6]
e[7]
e[8]
e[9]
859
984
93
55
271
33
9
179
208
306
f[0]
f[1]
f[2]
f[3]
f[4]
f[5]
f[6]
f[7]
f[8]
f[9]
9
33
55
93
179
208
271
306
859
984
(d) 세 번째-패스 큐들 (Third-pass queues) 과 결과 체인
10
리스트정렬 (1)
 많은 레코드로 된 리스트를 정렬할 때는 데이타
의 이동을 최소화하도록 해야 함
– 합병 정렬, 삽입 정렬
• 순차 리스트 보다는 연결 리스트에 동작하도록 변경
• 추가적 링크 필요
• 물리적 재배치 대신 링크 필드만 수정
– 반드시 원하는 정렬 순서대로 레코드를 물리적으로 재
배치해야할 경우
• 연결 리스트 정렬을 먼저 수행
• 리스트에 명세된 순서에 따라 레코드들을 물리적으로 재배치
– 재배치는 추가 공간을 사용하여 선형 시간 내에 수행 가능
11
리스트정렬 (2)
void listSort1(element a[], int linka[], int n, int first)
{
int linkb[MAX_SIZE];
int i, current, prev = 0;
element temp;
for ( current = first; current; current = linka[current])
{
linkb[current] = prev;
prev = current;
}
for(i = 1; i < n; i++)
{
if(first != i) {
if ( linka[i]) linkb[linka[i]] = first;
linka[linkb[i]] = first;
SWAP(a[first], a[i], temp);
SWAP(linka[first], linka[i], temp);
SWAP(linkb[first], linkb[i], temp);
}
first = linka[i];
}
}
이중 연결 리스트를 이용한 레코드 재배치
12
리스트 정렬 (3)
 listSort1의 분석
– 리스트에 n개의 레코드가 있고, 각 레코드가 m워
드로 이루어져 있을 때
• 전체 연산 시간 : O(mn)
– 체인 first를 이중 연결 리스트로 변환하는데 O(n)시간
– 한번 바꾸는데 드는 비용(레코드의 이동) : O(m)
• 최악의 경우 : 3(n-1)의 레코드 이동이 일어남
– R2<...<Rn, R1>Rn인 경우
13
리스트 정렬 (4)
 정렬된 연결 리스트의 예
– 입력 리스트 : (26, 5, 77, 1, 61, 11, 59, 15, 48, 19)
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
26
5
77
1
61
11
59
15
48
19
linka
9
6
0
2
3
8
5
10
7
1
(a) 리스트 정렬을 한 연결 리스트, first=4
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
26
5
77
1
61
11
59
15
48
19
linka
9
6
0
2
3
8
5
10
7
1
linkb 10
4
5
0
7
2
9
6
1
8
(b) 역방향 링크를 만든, (a)에 대응되는 이중 연결 리스트, first=4
14
리스트 정렬 (5)
 listSort1의 예제 (1)
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
77
26
61
11
59
15
48
19
linka
2
6
0
9
3
8
5
10
7
4
linkb
0
4
5
10
7
2
9
6
4
8
(a) listSort1의 for 루프의 첫 번째 반복 후의 구성, first=2
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
77
26
61
11
59
15
48
19
linka
2
6
0
9
3
8
5
10
7
4
linkb
0
4
5
10
7
2
9
6
4
8
(b) 두 번째 반복 후의 구성, first=6
15
리스트 정렬 (6)
 listSort1의 예제 (2)
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
11
26
61
77
59
15
48
19
linka
2
6
8
9
3
0
5
10
7
4
linkb
0
4
2
10
7
5
9
6
4
8
(c) 세 번째 반복 후의 구성, first=8
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
11
15
61
77
59
26
48
19
linka
2
6
8
10
3
0
5
9
7
4
linkb
0
4
2
6
7
5
9
10
4
8
(d) 네 번째 반복 후의 구성, first=10
16
리스트 정렬 (7)
하나의 링크 필드만 사용한 레코드 재배치
void listSort2(element a[], int link[], int n, int first)
{
int i;
element temp;
for(i=1; i<n; i++)
{
while(first < i) first = link[first];
int q = link[first];
if(first != i)
{
SWAP(a[i], a[first], temp);
link[first] = link[i];
link[i] = first;
}
first = q;
}
}
– M.D.MacLaren이 제시한 방법으로 listSort1을 수정
– 링크 필드를 추가로 필요로 하지 않음
– first가 항상 i보다 크거나 같아야 함
17
리스트 정렬 (8)
 listSort2의 예제 (1)
– 입력 리스트 : (26, 5, 77, 1, 61, 11, 59, 15, 48, 19)
– 리스트 정렬 후 그림 7.10(a)와 같은 배치를 얻음
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
77
26
61
11
59
15
48
19
link
4
6
0
9
3
8
5
10
7
1
(a) listSort2의 for 루프의 첫 번째 반복 후의 구성, first=2
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
77
26
61
11
59
15
48
19
link
4
6
0
9
3
8
5
10
7
1
(b) 두 번째 반복 후의 구성, first=6
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
11
26
61
77
59
15
48
19
link
4
6
6
9
3
0
5
10
7
1
(c) 세 번째 반복 후의
18 구성, first=8
리스트
정렬
(9)
listSort2의 예제 (2)
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
11
15
61
77
59
26
48
19
link
4
6
6
8
3
0
5
9
7
1
(d) 네 번째 반복 후의 구성, first=10
i
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
key
1
5
11
15
19
77
59
26
48
61
link
4
6
6
8
10
0
5
9
7
3
 listSort2의 분석
–
–
–
–
(e) 다섯 번째 반복 후의 구성, first=1
레코드 이동 순서 : listSort1과 동일
최악의 경우 3(n-1)의 레코드 이동
총 비용 : O(nm)
while 루프의 전체 연산시간 : O(n)
 listSort2가 listSort1보다 공간과 시간적인 면에서 우수
– 두 레코드 교환 시 listSort1이 더 많은
일을 해야 함
19
테이블 정렬 (1)
 리스트 정렬 기법은 퀵 정렬이나 히프 정렬에는 적당하지 않음
– 히프 정렬에서는 히프를 순차적으로 표현하는 것이 핵심
– 이러한 정렬 방법을 위해
• 한 레코드에 대응하는 한 엔트리로 구성된 보조 테이블 t를 유지
• 테이블의 엔트리는 레코드의 간접 참조를 할 수 있게 함
 테이블 정렬
– 정렬 시작 시 : t[i]=i, 1≤i ≤n
– 정렬 함수가 a[i]와 a[j]를 교환해야 한다면 테이블의 엔트리 t[i]와 t[j]만 교
환되게 함
– 정렬 끝나면 : a[t[1]]은 키가 가장 작은 레코드
a[t[n]]은 키가 가장 큰 레코드
– 정렬된 레코드의 순열 : a[t[1]], a[t[2]],....,a[t[n]]
– t에 나타난 순열에 따라 레코드를 물리적으로 재배치해야 될 경우도 있다.
20
테이블 정렬 (2)
정렬 전의 보조 테이블 t
1
2
3
4
5
R1
50
R2
9
R3
11
R4
8
R5
3
5
4
2
3
1
정렬 이후의 테이블 t

순열 t[0],t[1],...,t[n-1]에 대응하는 레코드들을 재배치 하는 함수
– 모든 순열은 분리된 사이클로 만들어짐
• 원소 i에 대한 사이클 : i,t[i],t2[i],...,tk[i] (tj[i]=t[tj-1[i]],t0[i]=1,tk[i]=i)
• 위의 그림의 순열 t는 R1과 R5를 포함하는 사이클과 R4,R3,R2를 포함하는
사이클을 가짐
21
테이블 정렬 (3)
void tableSort(element a[], int n, int t[])
{
int i, current, next;
element temp;
for(i=1; i<n; i++)
if(t[i] != i) {
temp = a[i]; current = i;
do {
next = t[current]; a[current] = a[next];
t[current] = current; current = next;
}while(t[current]!=i)
a[current] = temp;
t[current] = current;
}
}

tableSort의 분석
– 각 레코드가 m개의 워드의 기억장소를 사용한다면
– 전체 연산 시간 : O(mn)
22
테이블 정렬 (4)

테이블 정렬의 예
– (a)의 테이블 t를 가지고 시작한다고 가정
R1
R2
R3
R4
R5
R6
R7
R8
key
35
14
12
42
26
50
31
18
t
3
2
8
5
7
1
4
6
(a) 초기 구성
key
12
14
18
42
26
35
31
50
t
1
2
3
5
7
6
4
8
(b) 첫 번째 사이클의 재배치 후 구성
key
12
14
18
26
31
35
42
50
t
1
2
3
4
5
6
7
8
(c) 두 번째 사이클의 재배치 후 구성
23