Joinc 팀블로그 리눅스 메뉴얼 정리 Joinc 위키
댓글

Recent Comments

Powered by Disqus
팀블로그 카테고리
  전체 (1105)
   공지사항 (1)
   검색엔진 (21)
   기술동향 (58)
   게임 (2)
   독서 (6)
   리눅스 (12)
   보안 (1)
   사회문제 (22)
   어셈블리 (43)
   영화 (3)
   오픈소스 (10)
   음악 (9)
   인물 (1)
   포인터 (4)
   프로그래머 (23)
   팀블로그 (20)
   테터툴즈 (29)
   C/C++ (152)
   FireFox (11)
   Gimp (2)
   Google (98)
   Java (13)
   Perl (2)
   Pthread (11)
   STL (13)
   TCP/IP (8)
   Tools (31)
   Web2.0 (42)
   Wiki (1)
«   2010/07   »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
2008/01/24 19:56

다중 Level Merged Sort

소개

여기에서는 다중의 머지소트(Merged Sort)에 대해서 생각해 보도록 하겠다. 머지소트를 하기 위해서는 3개의 버퍼가 필요하다는 것을 알고 있을 것이다.

merged.png

  1. A에 자료를 집어 넣고
  2. B에 자료를 집어 넣은다음
  3. A와 B를 Merge 해서 결과를 C에 넣는다.
  4. 새로운 데이터가 있다면 다시 A에 넣고
  5. A와 C를 Merged 해서 결과를 B에 놓는다.
  6. 위의 순서를 반복한다.

이러한 일반적인 merged sort는 그다지 어려울 것이 없다고 생각된다. 그러나 다음과 같은 경우를 생각해볼 수 있다.

mmerged.png

3개의 A, B, C 그룹이 있고, C그룹의 데이터들을 머지소트한 결과와 B그룹의 데이터들을 머지소트한 결과를 다시 머지 소트하고, 이 결과를 다시 A그룹의 머지소트한 결과의 머지소트해서 최종 결과를 가져온다.

이러한 요구는 같이 가중치(Level)를 달리하는 서로다른 그룹에 대해서 머지소트하고 이들 그룹을 다시 머지소트해서 Score를 산출해야 하는 검색엔진등에서 발생할 수 있을 것이다.

간단히 생각하자면, 각 그룹을 머지소트하는데 3개의 버퍼가 필요하므로, 3x2 = 6개의 버퍼로 필요한 계산을 할 수 있을 것이다. 그러나 이 경우 대량의 메모리를 낭비할 수 있다는 문제가 발생한다. 머지소트해야할 데이터가 1,000,000의 레코드로 이루어져 있고, 각각의 레코드의 크기가 20byte 정도라면 한번의 머지소트를 위해서 120M의 메모리를 소비해야 한다. 여기에 동시접속까지 제어해야 하는 서비스에 사용될 엔진이라면, 10명이 접속했을 때, 1.2기가의 메모리를 소비하게 된다.

이러한 경우 memory풀을 유지해서, 할당하는 등의 방법을 고려해야 겠으나 우선적으로 사용한 메모리의 양을 줄일 필요가 있다. 이 방법에 대해서 고민해보기로 했다.

버퍼 레퍼런스 관리

이 문제를 해결하기 위해서 가능한 최소의 버퍼를 만들어 레퍼런스를 이용해서 재사용하는 방법을 생각해 보았다. 버퍼풀을 유지하기 위한 방법이라고 볼 수 있을 것이다.

doublemerged.png

여기에는 4개의 버퍼가 사용된다.
  1. Buff Manager가 4개의 버퍼중 3개를 얻어낸 다음 Lv1 Merged Sorter 에게 돌려준다.
    1. Lv1 Merged Sorted는 주어진 3개의 버퍼를 이용해서 Merged Sorted를 한다.
    2. 마지막으로 결과를 저장한 버퍼의 레퍼런스(주소값)을 저장한다.
  2. Buffer Manager가 4개의 버퍼중 3개를 얻어낸 다음 Lv2 Merged Sorted 에게 돌려준다.
    1. 이때 Lv1 Merged Sorter의 최종결과물이 저장된 버퍼는 사용하면 안된다.
    2. 우리는 Lv1 Sorter의 결과물 버퍼의 레퍼런스를 알고 있으므로, 이것을 제외한 3개의 버퍼를 Lv2 Sorter에게 할당할 수 있다.
    3. Lv2 Sorter은 주어진 3개의 버퍼를 이용해서 merged Sorted를 수행한다.
    4. 최종결과를 저장한 버퍼의 레퍼런스를 저장한다.
  3. 이제 두개의 버퍼에 Lv1 Sorter 과 Lv2 Sorter의 결과물이 있다. 두개를 merged해서 사용가능한 버퍼에 결과물을 저장한다.

다음은 구현코드다. 아이디어가 실현되는지를 확인하기 위한 코드로 데이터는 int형으로 대신했으며, 실제 merged sort를 하지 않고, 2개의 int형 버퍼에 있는 값을 더해서 남는 버퍼에 저장하도록 했다. 실제 구현은 int형대신, 클래스나 구조체와 같은 자료구조가 들어가고, 덧셈연산대신에 Merged Sort 연산이 들어갈 것이다. 어쨋든 아이디어를 테스트하는데에는 무리가 없을 것으로 생각된다.

아이디어 구현에 집중하기 위해서 STL을 사용했다.

  1. 4개의 int형 버퍼중에 3개의 int형 버퍼를 할당한다.
    1. 첫번째 버퍼에 1이 들어간다.
    2. 두번째 버퍼에 2가 들어간다.
    3. 3번째 버퍼에 1+2=3 이 들어간다.
    4. 4번째 버퍼에 3이 들어간다.
    5. 5번째 버퍼에 3+3=6 이 들어간다.
    6. 즉 101번을 루프를 돌면 5050이라는 값이 나와야 한다.
  2. 4개의 int형 버퍼중에, 이전 결과값이 저장된 버퍼를 제외한 3개를 할당한다.
    1. 1번을 반복한다.
  3. 2개의 버퍼가 모두 찼다면, 이걸 다시 더해서 남는 버퍼에 넣는다.

#include <iostream> 
#include <vector>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

#define BUFSIZE 4

class Buff
{
private:
int currentIdx;
vector<int *> LBuf;
int *tmpptr;

public:
Buff()
{
currentIdx = 0;
}

void Set(int *a)
{
LBuf.push_back(a);
}

void Clear()
{
LBuf.clear();
}

// Merged Sort를 한다.
// 여기에서는 + 연산을 한다.
void SetUnion(int a)
{
int current = (currentIdx+1)%3;
int prev = (currentIdx)%3;
int tmp = (currentIdx+2)%3;

if (*LBuf[prev] != -1)
{
currentIdx++;
}
*LBuf[current] = a;
*LBuf[tmp] = a + *LBuf[prev];
printf("Debug %d + %d = %d\n", *LBuf[current], *LBuf[prev], *LBuf[tmp]);
currentIdx ++;

tmpptr = LBuf[(currentIdx)%3];
}

// 결과가 저장된 버퍼의 레퍼런스를 넘겨준다.
int *GetSumResult()
{
return tmpptr;
}

int TmpIdx()
{
return currentIdx;
}
};

class BufManager
{
private:
int *a;
vector<Buff> NodeBuf;
int checkIdx;

public:

BufManager()
{
// 버퍼를 4개 할당한다.
a = (int *)malloc(sizeof(int) * BUFSIZE);
checkIdx = 3;
}

void Run()
{
Buff lBuff1, lBuff2;

// 각각의 Buff1에 3개씩의 버퍼의 레퍼런스를 할당한다.
for (int i = 0; i < BUFSIZE; i++)
{
a[i] = -1;
if (&a[i] != &a[checkIdx])
{
lBuff1.Set(&a[i]);
}
}
NodeBuf.push_back(lBuff1);

for (int i = 0; i < BUFSIZE; i++)
{
a[i] = -1;
if (&a[i] != &a[checkIdx])
{
lBuff1.Set(&a[i]);
}
}
NodeBuf.push_back(lBuff2);

int idxflag = 0;
int idx;

// 2개의 그룹이 있다고 가정하고 루프를 돌렸다.
for (int i = 0; i < 2; i++)
{
Buff Bf;
idxflag = i % 2;
printf("Node Number %d\n", idxflag);
getchar();

// 101번의 merged Sort가 이루어진다고 가정한다.
for (int k = i; k < 101+i; k++)
{
NodeBuf[idxflag].SetUnion(k);
}

// merged Sort가 끝났다면, 버퍼를 초기화 한다.
NodeBuf[idxflag].Clear();
checkIdx = NodeBuf[idxflag].TmpIdx();

// 2개의 버퍼에 결과값이 만들어졌다면, 이 두개를 다시 Merged sort한다.
// 아래의 코드는 SetUnion 메서드를 사용하도록 바꿀 수 있을 것이다.
if (i > 0)
{
printf("RESULT : %d : %d\n", *NodeBuf[0].GetSumResult(), *NodeBuf[1].GetSumResult() );
getchar();
}

// 다음 Level의 Merged Sort를 위해서 3개의 메모리의 레퍼런스를 할당한다.
// 이때 이전에 결과값이 있는 메모리는 사용하지 않도록 한다.
for (int k = 0; k < BUFSIZE; k++)
{
// 주소값이 같지 않을 경우에만 할당한다.
if (&a[k] != NodeBuf[idxflag].GetSumResult())
{
a[k] = -1;
printf("SET %d\n", k);
Bf.Set(&a[k]);
}
}
NodeBuf[idxflag?0:1] = Bf;
printf("Current Result : %d\n", *NodeBuf[idxflag].GetSumResult());
getchar();
}
}
};

int main(int argc, char **argv)
{
BufManager *MyBuff;
MyBuff = new BufManager();
MyBuff->Run();
}

약간 수정된 코드

동일하게 SetUnion 메서드를 사용하도록 약간 수정했다.
#include <iostream> 
#include <vector>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

#define BUFSIZE 4

class Buff
{
private:
int currentIdx;
vector<int *> LBuf;
int *tmpptr;
public:

Buff()
{
currentIdx = 0;
}

void Set(int *a)
{
LBuf.push_back(a);
}

void Clear()
{
LBuf.clear();
}

void SetUnion(int a)
{
int current = (currentIdx+1)%3;
int prev = (currentIdx)%3;
int tmp = (currentIdx+2)%3;

if (*LBuf[prev] != -1)
{
currentIdx++;
}
*LBuf[current] = a;
*LBuf[tmp] = a + *LBuf[prev];
printf("Debug %d + %d = %d\n", *LBuf[current], *LBuf[prev], *LBuf[tmp]);
currentIdx ++;

tmpptr = LBuf[(currentIdx)%3];
}

int *GetSumResult()
{
return tmpptr;
}
int TmpIdx()
{
return currentIdx;
}
};

class BufManager
{
private:
int *a;
vector<Buff> NodeBuf;
int checkIdx;

public:

BufManager()
{
a = (int *)malloc(sizeof(int) * BUFSIZE);
checkIdx = 3;
}

void Run()
{
Buff lBuff1, lBuff2;

for (int i = 0; i < BUFSIZE; i++)
{
a[i] = -1;
if (&a[i] != &a[checkIdx])
{
lBuff1.Set(&a[i]);
}
}
NodeBuf.push_back(lBuff1);

for (int i = 0; i < BUFSIZE; i++)
{
a[i] = -1;
if (&a[i] != &a[checkIdx])
{
lBuff1.Set(&a[i]);
}
}
NodeBuf.push_back(lBuff2);

int idxflag = 0;
int idx;
for (int i = 0; i < 3; i++)
{
Buff Bf;
idxflag = i % 2;
printf("Node Number %d\n", idxflag);
getchar();
for (int k = 0; k < 10; k++)
{
NodeBuf[idxflag].SetUnion(k);
}

NodeBuf[idxflag].Clear();

checkIdx = NodeBuf[idxflag].TmpIdx();
if (i > 0)
{
int another = idxflag?0:1;

printf("RESULT : %d : %d\n", *NodeBuf[idxflag].GetSumResult(), *NodeBuf[another].GetSumResult() );
NodeBuf[idxflag].SetUnion(*NodeBuf[another].GetSumResult());
printf("Current Result : %d\n", *NodeBuf[idxflag].GetSumResult());
getchar();
}

for (int k = 0; k < BUFSIZE; k++)
{
if (&a[k] != NodeBuf[idxflag].GetSumResult())
{
a[k] = -1;
Bf.Set(&a[k]);
}
}
NodeBuf[idxflag?0:1] = Bf;

//printf("Current Result : %d\n", *NodeBuf[idxflag].GetSumResult());
getchar();
}
}
};

int main(int argc, char **argv)
{
BufManager *MyBuff;
MyBuff = new BufManager();
MyBuff->Run();
}

결론

버퍼풀을 이용한 merged sort는 유연하게 확장가능하다는 장점이 있다. 검색엔진을 예로 들자면, 어절이 분리될 경우, 2개 이상의 트리를 가지게 될건데, 이렇게 다수의 트리를 가지는 데이터들을 merged sort 하길 원한다면, 5개의 버퍼를 가지는 풀을 유지하면 될 것이다.

:::
2007/09/20 20:31

구글 입사자 문제

명확한 커트라인은 공개 되지 않았으나, 대략 속문속답으로 5개 이상의 정답을 끌어내야 한다는 군요. 해서 속전속결로 풀어봤습니다. 속전속결이기 때문에 틀린답이 있을 수도 있습니다.

1 스쿨버스에 골프공이 몇개 들어 갈까요

가정
스쿨버스의 크기를 1000cm X 500Cm X 200Cm 라고 가정을 한다. 골프공의 크기는 10Cm^3이다.
들어가는 골프공의 갯수는
100 * 50 * 20 = 100000 개

걸린시간 약 1분

2 당신의 운명은

당신이 5센트 정도의 크기로 작아져 버립니다. 질량은 지금 현재의 밀도를 유지하고 있습니다. 그리고 당신
은 "유리제조 믹서기" 에 던져 집니다. 믹서기의 날은 60초 후에 움직이기 시작 합니다. 자 이제 당신은 어떻게
하실건가요?

일단 문제가 좀이상한데, 질량이 그대로이면 밀도는 당연히 높아질 수 밖에 없겠죠 ? 어떤 대상체에 흠집을 내려면, 대상체보다 밀도가 더 높아야 하는데, 위의 경우 밀도가 매우 높아진 상태이기 때문에, 흠집을 낼 수 없음.

3 시애틀에 있는 모든 창유리를 청소하는 의뢰를 받은 당신, 얼마를 청구하시겠습니까?

...

4 machine's stack가 메모리안에서 늘어가는지 줄어드는지를 어떻게 알 수 있나요?

문제가 명확치 않음


5 당신의 8살된 조카에게 "데이터 베이스"에 관해 세가지 문장으로 설명하시오.

  1. 동화책을 찾기 좋게 하려면, 정리를 잘해야 하지 ?
  2. 그래서 책장에다가 정리를 하잖니.
  3. 이렇게 책장 같이 잘 정리하게끔 도와주는것을 데이터 베이스라고 한단다.!?

걸린시간 1분 이내

6 시계의 시침과 분침은 하루 몇번 겹치게 됩니까?

26 번 ?

걸린시간 1분 이내

7 불확실

당신은 A지점으로부터 B지점까지 가지않으면 안되는 상황입니다. 게다가 거기에 도착할 수 있을지도 확실치
않은 상황. 당신은 어떻게 하실 겁니까?

8 정리정돈

셔츠로 도배된 옷장이 있습니다. 지정된 어느 셔츠를 찾는것은 매우 힘듭니다. 간단하게 셔츠를 찾을 수 있는 당신만의 정리법을 가르쳐 주십시오.

  1. 옷장 위층 부터 봄,여름,가을,겨울로 나눈다.
  2. 각 층에서 다시 용도별로 나눈다. (출근, 외출, 기타등등)
디렉토리와 비슷

9 어떤 마을에

100쌍의 부부가 있습니다. 남편들은 전원 바람을 피고 있습니다. 아내들은 전원 "자기의 남편" 이외의 남자들이 바람을 피고 있다고 생각합니다. 이 마을의 정해진 법률은 외도와 간통을 허락하지 않습니다. 또한, 누구든 자기의 남편이 바람 피고 있다는 사실을 알게되면, 즉시 자신의 남편을 죽이는 규율이 있습니다. 이 마을의 여성들은 "규율"을 어기지 못합니다. 어느 날, 마을의 왕비가 말했습니다. 이 마을에는 외도하고 있는 남자가 적어도 한명은 있다. 자, 이 마을 에는 무슨 일이 벌어질까요?


10 남녀성비

어떤 나라에서 사람들은 태어나는 아이모두 "남자아이"만을 원하고 있었습니다. 그런 연유로 그 나라의 어느 가정에서도 남자아이가 태어 날 때까지 계속 아이를 낳았습니다. 이 나라의 남녀 인구 비율은 어떻게 될까요?

50:50

걸린 시간 1분 이내

11 확률

고속도로에서 30분동안 승용차가 존재할 확률이 0.95 라고 할 때, 10분동안 존재할 확률은 얼마가 될까요? (확률은 일정하다고 가정합니다.)

0.95

걸린시간 1분 이내

12 시계를 보니 3시 15분입니다. 시침과 분침사이의 각도는? 0도는 아님

1/3600 에서 3599/3600 사이

13 로프건너기

4명의 사람들이 몹시 흔들리는 로프로 만든 다리를 "밤중"에 건너 캠프에 돌아가야 하는 운명에 처했습니다. 불행하게도 손전등은 하나 밖에 없고, 17분 밖에 사용하지 못합니다. 다리는 손전등없이 건너는게 불가능 할 정도로 위험하고, 동시에 두 명만이 건널 수 있습니다. 게다가 네 사람 모두 걷는 속도가 틀립니다.

한 사람은 다리를 건너는데 1분이 걸리며, 다른 한 사람은 2분, 세번째 사람은 5분이 걸리며, 마지막 사람은 10분 걸립니다. 어떻게 하면 17분 이내에 전원 무사히 건널 수 있을까요?

  1. 1분과 2분 사람이 건너편으로 간다. - 2분
  2. 1분 사람이 이편으로 건너온다. - 3 분
  3. 5분과 10분이 건너편으로 간다. - 13 분
  4. 2분 사람이 이편으로 온다. - 15분
  5. 2분과 1분이 건너편으로 간다. - 17분

걸린시간 5분 정도

14 내기

당신과 친구들이 파티를 즐기고 있습니다. 당신을 포함해 10명입니다. 친구중 한명이 당신에게 내기를 제안 합니다. 당신과 같은 생일인 친구가 열명중에 있다면 당신이 1달러를 받습니다. 당신과 생일이 같은 사람이 없으면 내기를 건 친구가 2달러 가져가게 됩니다. 당신은 이 내기를 하시겠습니까?

하지 않는다. 생일을 아는 친구가 없고, 확률상 지는 게임

걸린시간 1분 정도.

15 전 세계에 피아노 조율사는 몇명 존재할까요 ?

... 지맘

걸린시간 1분

16 저울

당신은 똑같은 사이즈의 볼을 8개 갖고 있습니다.그 중에 7개는 같은 무게를 지니고 있으나, 한개는 다른 것에 비해 살짝 무겁습니다. 저울을 2번만 사용해 살짝 무거운 이 볼을 찾아내려면 어떻게 해야 할까요?

  1. 볼 하나를 제외하고, 3:3 으로 저울을 잰다. 만약 수평을 이룬다면, 남아있는 볼이 무거운 볼이므로 OK
  2. 한쪽으로 기울었다면, 기운쪽 3개에서 다시 하나를 뺀다음 1:1로 저울을 잰다. 수평을 이루었다면, 남아있는 볼이 무거운 볼, 기울었다면 기운쪽 볼을 선택하면 된다. OK

걸린 시간 : 1분이내

17 권리 나누기

5인의 해적이 있고, 그들은 1위부터 5위까지 상하관계가 존재합니다. 1위의 해적에게는 100개의 금화를 어떻게 나눌 것인지에 대한 제안을 할 권리를 가지고 있습니다. 나머지 해적들은 그 제안에 투표할 권리를 가지고 있으며, 찬성이 반을 못 넘을 경우 1위의 해적은 살해 됩니다. 1위의 해적에 최대의 금화를 분배하고, 또한 살아남을려면 어떻게 해야 하나요? (힌트 : 한명의 해적이 차지하는 금화는 결국 98%)
:::
2007/06/11 21:02

알고리즘 : Merged Sort(합병정렬)

이 문서는 수정될 수 있습니다. 최신문서는 Joinc Wiki에서 확인하세요.

합병정렬 (Merged Sort)

merged sort 혹은 합병정렬로 불리우는 이 정렬알고리즘은 O(nlogn)의 시간복잡도를 보여주는 정렬 알고리즘이다. 기본적으로 합병정렬은 정렬이된 양쪽의 원소들을 합병하는 식으로 이루어진다. 이를 위해서 분할 정복 알고리즘을 사용한다.

아래의 visual sort 애플릿을 이용하면 합병정렬 알고리즘의 원리를 쉽게 이해할 수 있을 것이다. 아래의 애플릿을 보려면 JRE가 설치되어 있어야 한다.



합병정렬은 다음의 아이디어를 이용해서 실행시간을 줄이고 있다.
  1. 커다란 하나의 목록을 가진 정렬보다, 작은 목록을 가진 정렬이 더 빠르다.
  2. 이미 정렬된 두개의 목록을 정렬하는 데에는 매우 작은 시간이 소모된다. 왜냐하면 두개의 목록이 이미 정렬되어 있다면, 목록을 계속 증가시키면서, 다른 버퍼에 넣어주기만 하면 되기 때문이다.

만약 두개의 이미 정렬된 배열이 주어졌다면, 다음과 같은 방식으로 합병정렬을 구현할 수 있을 것이다. 아래는 C++ 구현이다. 각각의 분할된 원소에 대해서 아래의 코드를 적용하면 될 것이다. 돌아가는데 큰 문제는 없는 코드이지만, 최적화 되지 않았다. 구현원리를 이해하는데 중점을 두었다.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>

using namespace std;

// Merged Sort에 사용될 데이터 객체
class Data
{
public:
int currentIdx;
int *data;
int size;

Data(int *indata, int insize = 0)
{
data = indata;
size = insize;
currentIdx = 0;
}

void Input(int a)
{
data[currentIdx] = a;
currentIdx++;
}

int Size()
{
if (size == 0)
return currentIdx;
return size;
}
};

// Merged Sort 클래스
class MergedSort
{
private :
int BUFSIZE;
int *buff;

Data *InputData;

int *buffIdx;
int flag;
int flagidx;
int inputsize;
Data *inputData;
Data *outputData;
vector<Data *> dataVector;

public :
MergedSort()
{
BUFSIZE = 30;
flag = 0;
flagidx = 0;
inputsize = 0;
};

void Sort(int *a, int *b, int sizea, int sizeb)
{
int bufsize;
int idx;
inputData = new Data(a, sizea);
dataVector.push_back(inputData);

inputData = new Data(b, sizeb);
dataVector.push_back(inputData);

buff = (int *)malloc(sizeof(int)*(sizea + sizeb));
memset(buff, 0x00, sizeof(int)*(sizea+sizeb));
outputData = new Data(buff);

bufsize = BUFSIZE - 1;

int topdoc = -1;
while(true)
{
idx = dataVector[flag]->currentIdx;
if (inputsize > bufsize)
{
printf("Input Buffer Excess\n");
break;
}
if (idx >= dataVector[flag]->size)
{
flagidx++;
flag = flagidx%2;
idx = dataVector[flag]->currentIdx;
int max = dataVector[flag]->size;
while(idx < max)
{
outputData->Input(dataVector[flag]->data[idx]);
idx++;
}
break;
}
if (dataVector[flag]->data[idx] > topdoc)
{
topdoc = dataVector[flag]->data[idx];
flagidx++;
flag = (flagidx)%2;
}
else if (dataVector[flag]->data[idx] == topdoc)
{
outputData->Input(dataVector[flag]->data[idx]);
dataVector[flag]->currentIdx++;
idx = dataVector[flag]->currentIdx;

topdoc = dataVector[flag]->data[idx];

flagidx++;
flag = flagidx%2;
dataVector[flag]->currentIdx++;
}
else
{
outputData->Input(dataVector[flag]->data[idx]);
dataVector[flag]->currentIdx++;
}
}
}
Data *get()
{
return outputData;
}
};

int main(int argc, char **argv)
{
MergedSort *DataSort;
Data *rtvData;
int a[] = {1, 5, 6, 8, 10, 22, 24, 29, 31, 33, 49, 50, 100, 200, 400, 410, 412, 413};
int b[] = {5, 10, 20, 30, 50, 51};

DataSort = new MergedSort();
DataSort->Sort(a, b, sizeof(a)/sizeof(int), sizeof(b)/sizeof(int));
rtvData = DataSort->get();
printf("Size is %d\n", rtvData->Size());
for (int i = 0; i < rtvData->Size(); i++)
{
printf("%d : %d\n", i, rtvData->data[i]);
}
}

:::
2007/06/01 10:11

정렬 알고리즘 - bubble sort(버블 소트)

이 문서는 수정될 수 있습니다. 최신문서는 Joinc Wiki를 참고하세요.

버블 소트
는 인접원소를 비교해서 더 큰 원소를 뒤로 보내는 방식을 사용한다. 병 밑바닥에서 생긴 버블이 위로 올라오면서 점점 더 커지는 것과 비슷하기 때문에 버블소트라고 명명하게 되었다.

매우 간단하게 구현할 수 있지만, 많은 경우에 있어서 매우 비효율적이라는 단점을 가진다.

예를들어 퀵소트는 평균 시간복잡도가 O(nlogn) 인데 비해서 버블소트는 항상 O(n^2) 이라는 최악의 시간복잡도를 보여주기 때문이다. - 물론 몇 가지 수정사항을 포함시키면 복잡도를 줄일 수 있지만 예외로 한다 -

정렬해야할 데이터의 갯수가 매우 적은 경우가 아니라면 퀵소트가 사용하기에 가장 무난한 성능을 보여준다. 여러가지 상황에 따른 소트 알고리즘의 선택에 대해서는 따로 문서를 작성해 보도록 하겠다.

다음은 퀵소트가 이루어지는 과정을 Visual 하게 보여주고 있다.


다음은 C언어로 구현된 퀵소트 알고리즘이다.
#include <stdio.h>

void b_sort(int data[], int size)
{
int tmp = 0;
int i, j;
int comparison = 0;
int swap = 0;

for (i = 0; i < size-1; i++)
{
for (j = 0; j < (size-1); j++)
{
comparison++;
if (data[j] > data[j+1])
{
tmp = data[j+1];
data[j+1] = data[j];
data[j] = tmp;
swap++;
}
}
}
printf("Comparisons %d\n", comparison);
printf("Swap %d\n", swap);
}
위의 코드는 버블소트 알고리즘을 그대로 표현해주고 있으며 O(n^2)의 시간복잡도를 보여준다. 이 방식으로 하자면 완전히 정렬된 데이터의 정렬에 대해서도 O(n^2)의 시간복잡도를 보여준다. 단지 Swap과정만이 생략될 뿐이다.

여기에 약간의 조건을 주면 시간복잡도를 줄일 수 있으며, 완전히 정렬된 데이터에 대해서는 O(n)의 시간복잡도를 가지도록 개선할 수도 있다. 힌트를 주자면 swap이 일어났는지를 검사하는 flag를 둬서, swap이 일어나지 않았다면 정렬이 끝난것으로 판단하는 것이다. 이 방법은 직접 테스트해보기 바란다.
:::
2007/05/26 22:17

비쥬얼하게 이해하는 정렬(Sort) 알고리즘

이 문서는 수정될 수 있습니다. 최신 문서는 Joinc Wiki 에서 확인하세요.

정렬과정을 비쥬얼하게 보여줌으로써, 각종 정렬알고리즘을 보다 쉽게 이해할 수 있습니다.


자바애플릿 프로그램은 http://math.hws.edu/TMCM/java/xSortLab 에서 배포되고 있습니다. 이 문서를 제대로 읽기 위해서는 JRE가 설치되어 있어야 합니다. JRE는 http://www.java.com/en/download/help/5000010400.xml 에서 다운로드 받을 수 있습니다.
:::
2007/05/25 23:47

알고리즘 - Quick Sort

최신문서는 Joinc wiki 에서 확인하실 수 있습니다.

Quick 정렬은 버블정렬과 함께, 가장 쉽게 응용할 수 있는 정렬기법이다. 평균적으로 O(n log n)번의 비교를 수행하며, 최악의 경우에 O(n^2)의 비교를 수행하도록 되어 있다.

정렬할 데이터가 이미 준비되어 있으며, 모든 데이터를 정렬해야 할경우 가장 빠른 수행속도를 보여주는 알고리즘으로 평가되고 있다.
소트효율
가장 비효율적인 '''버블소트'''는 O(N^2)이고, 퀵소트는 평균 O(NlogN)이다.
아무리 뛰어난 정렬 알고리즘을 개발한다고 하더라도, 데이터의 갯수가 N이면 O(NlogN)보다 더 좋을 수
없다는 것이 증명되어 있다.

즉 정렬알고리즘의 lower bound는 O(NlogN)이다.

단 최대값이 정해져 있는 데이터라면 counting 방식을 쓸수 있고 - counting sort, bucket sort, radix sort - 이 경우 O(n)이 보장될 것이다.

퀵정렬은 다음과 같은 방식으로 진행이 된다.
  1. 주어진 데이터 목록에서 임의의 원소를 고른다. 이 원소를 피봇이라 한다.
  2. 피봇을 기준으로 피봇의 앞에는 피봇보다 작은 숫자가 오도록 하고, 피봇 뒤에는 피봇보다 큰 원소
들이 오도록 한다. 이것을 분할이라고 한다. 분할이 끝난뒤에 피봇은 더 이상 움직일 필요가 없이 그 자리에 고정된다.
  1. 1,2의 과정을 재귀수행한다. 한번의 피봇이 선택되어서 분할이 이루어질 때마다. 반드시 고정되는 값이 생성이 되므로, 이 알고리즘은 반드시 끝난다는 것을 보장할 수 있다.

Sorting_quicksort_anim.gif

위의 프로세스가 퀵소트 알고리즘의 핵심으로 머리속으로 이해했다면, 구현하는 것도 그리 어렵지 않을 것이다. 구현에도 몇가지 방법이 있는데, 그 중에서 별도의 버퍼를 필요로 하지 않는 내부분할 퀵소트가 널리 사용되고 있다. 이 방법은 정렬을 위한 임시버퍼를 필요로 하지 않으므로 메모리를 할당하고 유지하기 위한 비용을 고려할 필요가 없다는 장점이 있다.

다음은 C로 작성된 퀵정렬 알고리즘이다.
void quickSort(int numbers[], int array_size);
void q_sort(int numbers[], int left, int right);

void quickSort(int numbers[], int array_size)
{
q_sort(numbers, 0, array_size -1);
}

void q_sort(int numbers[], int left, int right)
{
int pivot, l_hold, r_hold;
l_hold = left;
r_hold = right;
pivot = numbers[left]; // 0번째 원소를 피봇으로 선택
while (left < right)
{
// 값이 선택한 피봇과 같거나 크다면, 이동할 필요가 없다
while ((numbers[right] >= pivot) && (left < right))
right --;

// 그렇지 않고 값이 피봇보다 작다면,
// 피봇의 위치에 현재 값을 넣는다.
if (left != right)
{
numbers[left] = numbers[right];
}
// 왼쪽부터 현재 위치까지 값을 읽어들이면서
// 피봇보다 큰 값이 있다면, 값을 이동한다.
while ((numbers[left] <= pivot) && (left < right))
left ++;
if (left != right)
{
numbers[right] = numbers[left];
right --;
}
}
// 모든 스캔이 끝났다면, 피봇값을 현재 위치에 입력한다.
// 이제 피봇을 기준으로 왼쪽에는 피봇보다 작거나 같은 값만 남았다.
numbers[left] = pivot;
pivot = left;
left = l_hold;
right = r_hold;

// 재귀호출을 수행한다.
if (left < pivot)
q_sort(numbers, left, pivot - 1);
if (right > pivot)
q_sort(numbers, pivot+1, right);
}

int main(int argc, char **argv)
{
int data[] = {3,7,8,5,2,1,9,5,4};
int i;
quickSort(data, 9);
for (i =0; i < 9; i++)
{
printf("%d\n", data[i]);
}
}
하나하나씩 따라가는게 퀵소트 알고리즘을 이용하는 가장 좋은 방법이다.

quick.png

두꺼운 테두리로 둘러쌓인 숫자가 선택된 피벗이다. 빨간색은 피벗보다 큰 원소, 파란색은 피벗보다 작은 원소다.
  1. 피벗으로 3을 선택했다.
  2. 배열의 가장 오른쪽 부터, 피벗보다 작은 수가 있는지 비교한다.
  3. 1이 피벗보다 작으므로 swap 한다.
  4. 이제 왼쪽으로 가서, 인덱스를 증가시키면서 피벗보다 큰수가 있는지 확인한다.
  5. 7이 피벗보다 크므로 swap 한다.
  6. 다시 오른쪽으로 가서, 인덱스를 감소시키면서 피벗보다 작은 수가 있는지 확인한다.
  7. 2와 3을 swap 한다.
  8. 왼쪽에서 인덱스를 증가시키면서 피벗과 비교한다.
  9. 8이 3보다 크므로 swap 한다.
  10. 이제 q_sort 함수를 재귀호출한다. 인자는 data[], 3, 9가 될것이다.


[http]퀵소트 wikipedia

:::