Programming/MFC2009. 11. 8. 04:08
CString temp = "2,30,100,2"; // 자를 문자열 ( ", "의 갯수는 변할 수 있다.)
 CString strTok;   
 
 int sepCount = GetFindCharCount(temp, ',');   // " , " 의 갯수를 세어온다.
 
 CString* temp1 = new CString[sepCount+1]; // 구해온 갯수만큼 동적 배열을 할당(CString배열)
 
 int cnt=0;
 while(AfxExtractSubString(strTok, temp, cnt, ','))  // 문자를 잘라준다. (AfxExtractSubString = strTok)
  temp1[cnt++] = strTok;                                       // 해당 배열에 순차적으로 넣어준다.
 
 for(int i=0; i<sepCount+1; i++)
  cout << (LPCSTR)temp1[i] << endl;                  // 화면에 출력한다.

 delete[] temp1;


요렇게... 간단하게 예를 들자면,
CString str = "192.168.0.1";
CString strA, strB, strC, strD;
AfxExtractSubString(strA, str, 0, '.'); // strA == "192"
AfxExtractSubString(strB, str, 0, '.'); // strB == "168"
AfxExtractSubString(strC, str, 0, '.'); // strC == "0"
AfxExtractSubString(strD, str, 0, '.'); // strD == "1"



  =========================================================================================


안녕하세요...
 
CString의 멤버 함수에 보면 Find 함수와 GetAt 함수를 이용하시면 원하시는
기능을 구현하실 수 있습니다.
아래와 같이 예제를 작성해 보았습니다.
 
    CString data;
    char temp[20];
    char temp_data;
    int devide_count = 0, devide_index = 0;
    int str_length = 0;
 
    data = "abcd,123,efgh,456,ijk,";
    str_length = data.GetLength();
 
    for(int i = 0; i < str_length; i++){
        // 데이터에서 ',' 값을 찾아 위치 값을 얻는다.
        devide_index = data.Find(',', devide_index);

        // devide_index값이 -1이라면 데이터에 ','값이 존재하지 않는것이므로
        // 함수를 종료한다.
        if(devide_index == -1) return;

        // 찾았다면 devide_index 다음 인덱스부터 검색을 하기 위해 devide_index++하여준다.
        devide_index++;
        devide_count++;

        if(devide_count == 2){
            // devide_count 값이 2라면 내가 원하는 위치의 데이터를 찾은 것이므로
            // 데이터의 남은 길이만큼 for문을 돈다.
            for(i = 0; i < str_length - devide_index; i++){
                // 데이터를 얻어 temp_data 저장한다.
                temp_data = data.GetAt(devide_index+i);
                // temp_data값이 ','값이 temp_data를 temp[i]에 저장한다.
                if(temp_data != ',') temp[i] = temp_data;
                else {
                    temp[i] = 0;
                    MessageBox(temp);
                    return;
                }
            }  
        }
    }
Posted by skensita
Programming/Windows Mobile2009. 10. 17. 14:01
Posted by skensita
Programming/C2009. 10. 15. 00:23
#define putchar(c) outbyte(c)   
static void printchar(char **str, int c)   
{   
 extern int putchar(int c);   
 if (str) {   
  **str = c;   
  ++(*str);   
 }   
 else (void)putchar(c);   
}   
#define PAD_RIGHT 1   
#define PAD_ZERO 2   
static int prints(char **out, const char *string, int width, int pad)   
{   
 register int pc = 0, padchar = ' ';   
 if (width > 0) {   
  register int len = 0;   
  register const char *ptr;   
  for (ptr = string; *ptr; ++ptr) ++len;   
  if (len >= width) width = 0;   
  else width -= len;   
  if (pad & PAD_ZERO) padchar = '0';   
 }   
 if (!(pad & PAD_RIGHT)) {   
  for ( ; width > 0; --width) {   
   printchar (out, padchar);   
   ++pc;   
  }   
 }   
 for ( ; *string ; ++string) {   
  printchar (out, *string);   
  ++pc;   
 }   
 for ( ; width > 0; --width) {   
  printchar (out, padchar);   
  ++pc;   
 }   
 return pc;   
}   
/* the following should be enough for 32 bit int */  
#define PRINT_BUF_LEN 12   
static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)   
{   
 char print_buf[PRINT_BUF_LEN];   
 register char *s;   
 register int t, neg = 0, pc = 0;   
 register unsigned int u = i;   
 if (i == 0) {   
  print_buf[0] = '0';   
  print_buf[1] = '\0';   
  return prints (out, print_buf, width, pad);   
 }   
 if (sg && b == 10 && i < 0) {   
  neg = 1;   
  u = -i;   
 }   
 s = print_buf + PRINT_BUF_LEN-1;   
 *s = '\0';   
 while (u) {   
  t = u % b;   
  if( t >= 10 )   
   t += letbase - '0' - 10;   
  *--s = t + '0';   
  u /= b;   
 }   
 if (neg) {   
  if( width && (pad & PAD_ZERO) ) {   
   printchar (out, '-');   
   ++pc;   
   --width;   
  }   
  else {   
   *--s = '-';   
  }   
 }   
 return pc + prints (out, s, width, pad);   
}   
static int print(char **out, int *varg)   
{   
 register int width, pad;   
 register int pc = 0;   
 register char *format = (char *)(*varg++);   
 char scr[2];   
 for (; *format != 0; ++format) {   
  if (*format == '%') {   
   ++format;   
   width = pad = 0;   
   if (*format == '\0') break;   
   if (*format == '%') goto out;   
   if (*format == '-') {   
    ++format;   
    pad = PAD_RIGHT;   
   }   
   while (*format == '0') {   
    ++format;   
    pad |= PAD_ZERO;   
   }   
   for ( ; *format >= '0' && *format <= '9'; ++format) {   
    width *= 10;   
    width += *format - '0';   
   }   
   if( *format == 's' ) {   
    register char *s = *((char **)varg++);   
    pc += prints (out, s?s:"(null)", width, pad);   
    continue;   
   }   
   if( *format == 'd' ) {   
    pc += printi (out, *varg++, 10, 1, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'x' ) {   
    pc += printi (out, *varg++, 16, 0, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'X' ) {   
    pc += printi (out, *varg++, 16, 0, width, pad, 'A');   
    continue;   
   }   
   if( *format == 'u' ) {   
    pc += printi (out, *varg++, 10, 0, width, pad, 'a');   
    continue;   
   }   
   if( *format == 'c' ) {   
    /* char are converted to int then pushed on the stack */  
    scr[0] = *varg++;   
    scr[1] = '\0';   
    pc += prints (out, scr, width, pad);   
    continue;   
   }   
  }   
  else {   
  out:   
   printchar (out, *format);   
   ++pc;   
  }   
 }   
 if (out) **out = '\0';   
 return pc;   
}   
/* assuming sizeof(void *) == sizeof(int) */  
int printf(const char *format, ...)   
{   
 register int *varg = (int *)(&format);   
 return print(0, varg);   
}   
int sprintf(char *out, const char *format, ...)   
{   
 register int *varg = (int *)(&format);   
 return print(&out, varg);   
}   
#ifdef TEST_PRINTF   
int main(void)   
{   
 char *ptr = "Hello world!";   
 char *np = 0;   
 int i = 5;   
 unsigned int bs = sizeof(int)*8;   
 int mi;   
 char buf[80];   
 mi = (1 << (bs-1)) + 1;   
 printf("%s\n", ptr);   
 printf("printf test\n");   
 printf("%s is null pointer\n", np);   
 printf("%d = 5\n", i);   
 printf("%d = - max int\n", mi);   
 printf("char %c = 'a'\n", 'a');   
 printf("hex %x = ff\n", 0xff);   
 printf("hex %02x = 00\n", 0);   
 printf("signed %d = unsigned %u = hex %x\n", -3, -3, -3);   
 printf("%d %s(s)%", 0, "message");   
 printf("\n");   
 printf("%d %s(s) with %%\n", 0, "message");   
 sprintf(buf, "justif: \"%-10s\"\n", "left"); printf("%s", buf);   
 sprintf(buf, "justif: \"%10s\"\n", "right"); printf("%s", buf);   
 sprintf(buf, " 3: %04d zero padded\n", 3); printf("%s", buf);   
 sprintf(buf, " 3: %-4d left justif.\n", 3); printf("%s", buf);   
 sprintf(buf, " 3: %4d right justif.\n", 3); printf("%s", buf);   
 sprintf(buf, "-3: %04d zero padded\n", -3); printf("%s", buf);   
 sprintf(buf, "-3: %-4d left justif.\n", -3); printf("%s", buf);   
 sprintf(buf, "-3: %4d right justif.\n", -3); printf("%s", buf);   
 return 0;   
}   

printf의 소스... 천천히 봐볼까나...
Posted by skensita
Programming/C2009. 10. 15. 00:05
char * ltoa (long val, char *buf, unsigned radix)
{
char *p; /* pointer to traverse string */
char *firstdig; /* pointer to first digit */
char temp;      /* temp char */
unsigned digval; /* value of digit */

p = buf;

if (radix == 10 && val < 0) {
   /* negative, so output '-' and negate */
   *p++ = '-';
   val = (unsigned long)(-(long)val);
}

firstdig = p;   /* save pointer to first digit */

do {
   digval = (unsigned) (val % radix);
   val /= radix;       /* get next digit */

   /* convert to ascii and store */
   if (digval > 9)
*p++ = (char) (digval - 10 + 'a');  /* a letter */
   else
*p++ = (char) (digval + '0');       /* a digit */
} while (val > 0);

/* We now have the digit of the number in the buffer, but in reverse
  order.  Thus we reverse them now. */

*p-- = '\\0';    /* terminate string; p points to last digit */

do {
   temp = *p;
   *p = *firstdig;
   *firstdig = temp;   /* swap *p and *firstdig */
   --p;
   ++firstdig; /* advance to next two digits */
} while (firstdig < p); /* repeat until halfway */

return buf;
}

출처 : http://cpueblo.com/programming/cpp/contents/99.html
Posted by skensita
Programming/MFC2009. 1. 31. 16:53

MFC

  1. m_vtFileName.clear();
    m_UserThumbnail.clear();
  2. // 파일 목록을 찾는다.
    CFileFind finder;
    CString strWildCard( m_strDirectory );
  3. strWildCard += "\\*.*";

  4. BOOL bWorking = finder.FindFile( strWildCard );
  5. while( bWorking )
    {
    bWorking = finder.FindNextFile();
  6.   if( finder.IsDots() || finder.IsDirectory() )
      continue;  
     else
     {
      CString filePath = finder.GetFileName();
      filePath.MakeLower();   //대문자 -> 소문자
  7.    //if( IsImageGDIPLUSValid( m_strDirectory + "\\" + filePath ) )  
      if( filePath.Find(".jpg") > 0)
       m_vtFileName.push_back( filePath );  
     }

    finder.Close();
  8.  

API

  1.  m_strThumbnailDir = dir;
    if( m_strThumbnailDir == "" )
    {
     //AfxMessageBox(_T("not define strpath!"));
     m_vtImageName->clear();
     return false;
    }
  2.  CString strExt;
    CString strName;
    CString strPattern;
  3.  BOOL bRC = TRUE;
  4.  HANDLE     hFind = NULL;
    WIN32_FIND_DATA   FindFileData;
    std::vector<CString> VectorImageNames;
  5.  if( m_strThumbnailDir[ m_strThumbnailDir.GetLength() - 1 ] == TCHAR('\\') )
     strPattern.Format( _T("%s*.*"), m_strThumbnailDir );
    else
     strPattern.Format( _T("%s\\*.*"), m_strThumbnailDir );
  6.  hFind = FindFirstFile(strPattern,&FindFileData);
    if(hFind == INVALID_HANDLE_VALUE)
    {
     LPVOID  msg;
     ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
          NULL,
          GetLastError(),
          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
          (LPTSTR)&msg,
          0,
          NULL);
     MessageBox((LPTSTR)msg, _T("fileload error"), MB_OK|MB_ICONSTOP);
     ::LocalFree(msg);
     return FALSE;
    }
    // filter off the system files and directories
    if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)  &&
     !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)     &&
     !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)     &&
     !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY))
    {   
     // test file extension
     strName = FindFileData.cFileName;
     strExt = strName.Right(3);
  7.   if( FindType( strExt ) ){
  8.    // save the image file name
      VectorImageNames.push_back( strName );
     }
    }
  9.  // loop through to add all of them to our vector
    while (bRC)
    {
     bRC = ::FindNextFile(hFind, &FindFileData);
     if (bRC)
     {
      // filter off the system files and directories
      if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)  &&
       !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)     &&
       !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)     &&
       !(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY))
      {
       // test file extension
       strName = FindFileData.cFileName;
       strExt = strName.Right(3);
  10.     if(FindType(strExt)){
        // save the image file name
        VectorImageNames.push_back(strName);
       }
      }
     }
     else
     {
      DWORD err = ::GetLastError();
      if (err !=  ERROR_NO_MORE_FILES)
      {
       LPVOID msg;
       ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, err,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&msg, 0, NULL);
       MessageBox((LPTSTR)msg, _T("fileload error"), MB_OK|MB_ICONSTOP);
       ::LocalFree(msg);
       ::FindClose(hFind);
       return FALSE;
      }
     }
    } // end of while loop
  11.  // close the search handle
    ::FindClose(hFind);
  12.  // update the names, if any
    if ( !VectorImageNames.empty() )
    {
     // reset the image name vector
     m_vtImageName->clear();
     *m_vtImageName = VectorImageNames;
     return TRUE;
    }
    else
    {
     m_vtImageName->clear();
     return TRUE;
    }
  13.  return FALSE; 
Posted by skensita
Programming/MFC2009. 1. 31. 15:13



Visual Studio 6에서 만들어진 다이알로그 베이스 MFC 프로그램.



Visual Studio 2008에서 만들어진 다이알로그 베이스 MFC 프로그램.


보는것처럼 버튼의 모양이 다른것을 확인할 수 있다. 이외에 여러 컨트롤들이 다른 모양으로 표시 될 것입다.

Visual Studio 6에서 만들어진 프로그램에 저런 컨터트롤을 사용하기 위해서는 아래의 방법을 거치면

같은 모양의 컨트롤을 볼 수 있습니다.

----------------------------------------------------------------------------------------------

해당 첨부된 파일이름을 적당히 바꾸시고, 에디터로 열으셔서

이름 같은거 바꿔주시면 일단 시작입니다.

그리고, 해당 프로젝트의 적당한 경로, res 같은 곳으로 복사해주세요.

1. 해당 프로젝트를 엽니다.

2. 리소스 편집창으로 이동합니다.

3. 우측 마우스 메뉴의 인서트를 선택합니다.

4. 해당 다이알로그의 Import 버튼을 클릭합니다.

5. 파일 열기 창의 하단부 Open As을 오토에서 커스텀으로 변경합니다.

6. 복사한 메니페스트 파일을 선택합니다.

7. 리소스타입을 24라고 숫자로 입력합니다. 

8. 해당 리소스 편집창에 24 타입으로 대충 IDR_241 과 같은 형태로 추가되었을 것입니다.
    해당 리소스의 프로퍼티를 열으셔서 위의 아이디를 숫자 1로 바꿔줍니다.

9. 빌드를 다시 수행합니다.

10. XP에 테마가 있는 상태라면 원하시는 모양으로 바뀐 다얄로그를 보실 수 있을겁니다.



메니페스트라의내용은 강좌란이나 인터넷에서 검색해 보면 깔끔한 자료를 찾으실 수 있을겁니다.

Posted by skensita
Programming/MFC2009. 1. 12. 13:32

1. Introduction

MFC를 사용해서 트레이 기반으로 동작하는 다이알로그 베이스드의 프로그램을 개발해 본 사람이라면 누구나 한번쯤은 이런 생각을 해 보았을것이다. 이놈의 모달 왜 시작하기만 하면 나타나지? ShowWindow API를 사용해서 SW_HIDE를 몇 군데 넣어보아도 뾰족한 수가 없었을 것이다. 최대한 노력을 해 보았자, 화면에 나타났다 금새 사라지는게 전부다.

그럼 왜 이런 현상이 나타날까? 그건 MFC 내부적으로 DoModal안에서 다이알로그를 Show하게 만들기 때문이다. 그렇다면 방법은 없을까? 불행하게도 일반적인 ShowWindow를 사용한 방법은 없다. 이 문서에서는 문제를 해결하는 다른 방법을 제시하고 있다. 그럼 2장에서 좀 더 자세히 살펴보기로 하자.

2. HOWTO

이미 1장에서 ShowWindow를 통한 답이 없다고 밝혔다. 그렇다면 도대체 무엇을 사용해야 깔끔하게 다이알로그를 제어할 수 있을까? 그 답은 바로 WM_WINDOWPOSCHAINGING에 있다. 보기에도 상당히 긴 이름을 가진 메시지이다. 아마도 윈도우 위치가 변경될때 발생할 것 같은 느낌을 주지 않는가? 그렇게 생각했다면 정답이다.

MSDN에 따르면 WM_WINDOWPOSCHANGING 메시지는 윈도우의 사이즈, 포지션, 또는 Z-order 순서가 변경되는 경우 윈도우에 통보된다고 한다. 그렇다면 윈도우의 거의 모든 외부 변화에 이 메시지가 발생한다고 보면된다. 이 메시지에 대해서 좀 더 살펴보면 다음과 같다.

WM_WINDOWPOSCHANGING

    WPARAM wParam : 사용하지 않음;
    LPARAM lParam : WINDOWPOS 구조체 포인터

typedef struct {
    HWND hwnd;
    HWND hwndInsertAfter;
    int x;
    int y;
    int cx;
    int cy;
    UINT flags;
} WINDOWPOS;

위는 해당 메시지의 wParam과 lParam및 lParam에 사용되는 구조체의 설명을 나타낸 것이다. 오늘 우리의 문제를 해결하는데 가장 중요한 것은 이 WINDOWPOS 구조체다. 그 중에서도 flags라고 할 수 있다.

flags는 윈도우 포지션과 관련된 다양한 값들을 가질 수 있다고 한다. 일반적으로 아래와 같은 값들의 조합으로 구성된다.

  • SWP_DRAWFRAME - 프레임을 그린다.
  • SWP_FRAMECHANGED
  • SWP_HIDEWINDOW - 윈도우를 숨긴다.
  • SWP_NOACTIVATE - 윈도우를 활성화 시키지 않는다.
  • SWP_NOCOPYBITS - 클라이언트 영역과 관련된 모든 정보를 무시한다.
  • SWP_NOMOVE - 현재 위치를 유지한다. (x,y 파라미터를 무시한다.)
  • SWP_NOOWNERZORDER - 소유주 윈도우의 Z-order를 변경하지 않는다.
  • SWP_NOREDRAW - 윈도우를 새로 그리지 않는다. 어떠한 페인팅 관련 메시지도 포스트 되지 않는다.
  • SWP_NOREPOSITION - SW_NOOWNERZORDER 플래그와 같음
  • SWP_NOSENDCHANGING - 윈도우가 WM_WINDOWPOSCHANGING 메시지를 받는 것을 막는다.
  • SWP_NOSIZE - 사이즈를 변경하지 않는다. (cx, cy 파라미터를 무시한다.)
  • SWP_NOZORDER - Z-order를 변경하지 않는다.
  • SWP_SHOWWINDOW - 윈도우를 출력한다.
z-order
윈도우는 일반적으로 2차원 평면 모니터에 출력된다. 그래서 x,y축이 기본적으로 존재한다. 거기에 더해서 윈도우 시스템에서는 z-order라는 개념이 있다. 이것은 수학과 마찬가지로 x,y축 외에 z축을 기준으로 한 순서가 된다. 윈도우로 따지면 특정 윈도우 뒤에 무슨 윈도우가 있는지를 기술하는 것을 z-order라고 생각하면 된다.

여기서 특히나 중요한 것은 SWP_SHOWWINDOW다. 화면에 윈도우가 출력되려고 하면 분명 WM_WINDOWPOSCHANGING 메시지가 포스팅 될 것이다. 당연히 넘어온 WINDOWPOS 구조체의 flags 필드값에는 SWP_SHOWWINDOW가 설정되어 있을 것이다. 그렇다면 값을 살짝 제거해 버리면 어떻게 될까? 아마도 MFC의 다이알로그 베이스드 프로그램이라면 아래와 같은 코드가 될 것이다.

void CHidDlgDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
    CDialog::OnWindowPosChanging(lpwndpos);
    
    // TODO: Add your message handler code here
    lpwndpos->flags &= ~SWP_SHOWWINDOW;
}

위와 같이 해두고 프로그램을 실행하면, 다이알로그가 화면에 출력되지 않을 것이다. 프로세스 목록에서 해당 프로그램을 종료시키자. 이것이 우리가 적용할 모달 다이알로그를 간단하게 숨기는 방법이다. 이것들을 합쳐서 좀 더 우아하게 사용하는 방법을 다음 장에서 살펴보도록 하자.

참고
기본적으로 다이알로그를 클래스 위저드로 열게 되면, 메시지 상자에 WM_WINDOWPOSCHANGING 메시지가 없다. 클래스 위저드의 마지막 탭에 위치한 클래스 인포로 가셔서 메시지 필터를 윈도우로 변경한후에 보면 WM_WINDOWPOSCHANGING 메시지가 추가되어 있는 것을 확인할 수 있다.

3. Codes

2장에서 살펴본 내용의 의하면 결국 우리는 기존의 ShowWindow행동을 무시하고 독자적인 행동을 해야 한다는 것을 알 수 있다. 쉽게 생각할 수 있는 방법으로 간단하게 멤버변수등을 하나 만들고 그 값에 따라서 WM_WINDOWPOSCHANGING 핸들러에서 SWP_SHOWWINDOW 플래그를 설정또는 제거해 주면 쉽게 될 것 이다. 하지만 이렇게 인터페이스를 분리시킬때에는 꼭 기존의 행동이랑 유사하게 내지는 기존의 행동을 통해서 변경시켜 주는 것이 좋다. 그렇게 해야 추후에 보더라도 헷갈리지 않기 때문이다.

이러한 작업을 가장 완벽하게 할 수 있는 곳은 아마도 ShowWindow일 것이다. CWnd의 ShowWindow를 재정의해서 사용하는 것이다. 하지만 이 경우 ShowWindow가 CWnd에 virtual함수로 선언되어져 있지 않기때문에 다이알로그를 CWnd포인터로 사용하는 경우에는 동작하지 않을 수 있다. 그렇게 되면 오류는 나지 않지만 결과가 이상하게 되므로 다른 사람이 쓰게 될 때 오해를 줄 수 있다. 따라서 ShowWindow를 오버라이드 하지 않고, 그와 비슷한 이름의 ShowWindowEx를 만들어 그놈을 사용하기로 하자. 아래는 간단하게 작성해본 샘플 코드다.

void CHidDlgDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
    CDialog::OnWindowPosChanging(lpwndpos);
    
    // TODO: Add your message handler code here
    if(m_bShowFlag)
        lpwndpos->flags |= SWP_SHOWWINDOW;
    else
        lpwndpos->flags &= ~SWP_SHOWWINDOW;

}

BOOL CHidDlgDlg::ShowWindowEx(int nCmdShow)
{
    m_bShowFlag = (nCmdShow == SW_SHOW);
    return (GetSafeHwnd()) ? ShowWindow(nCmdShow) : TRUE;
}

m_bShowFlag는 BOOL형으로 선언된 멤버 변수다. 이제 다이알로그의 ShowWindowEx 함수를 DoModal전에 사용하면, DoModal이 실행되더라도 다이알로그가 화면에 표시되지 않을 것이다.

주의해서 보아야 할 점 중에 하나는 ShowWindowEx의 마지막 호출은 ShowWindow로 이루어진다는 점이다. 하지만 DoModal 이전에 다이알로그는 생성되지 않으므로 그 경우에 ShowWindow를 호출하면 오류가 난다. 왜냐하면 아직 만들어진 윈도우 핸들이 없기 때문이다. 따라서 이 경우는 ShowWindow를 호출하지 않고 그냥 리턴해야 한다는 점을 기억해야 한다.


Posted by skensita
Programming/Algorithm2008. 12. 14. 20:53


6. 근사해법 (approximation algorithm)



해결하려는 문제마다 크기가 다르므로 한마디로는 말할 수 없지만 복잡도가 O(n)이나 O(n log n) 또는 O(n3) 등과 같이 문제의 크기 n 에 대해 제곱승 또는 세제곱승 정도의 복잡도를 가진 알고리즘이라면 실제로 사용가능하다. 제5장까지 설명한 알고리즘은 이 범위에 속하는 「효율적인」알고리즘이었으나 문제에 따라서는 이러한 효율적인 알고리즘이 없는 경우도 흔히 있다.

이와 같이 효율적인 알고리즘이 발견되지 않은 「어려운」문제에 대해서 이론적인 수법을 이용하여 그 어려운 정도를 알아내려는 연구가 오래전부터 많이 행해졌다. 효율적인 알고리즘을 발견해 내지 못하기 때문에, 그 문제에 대해서는 효율적인 알고리즘이 존재하지 않는다는 것을 증명하는 연구를 하는 것은 당연하다.

이러한 이론적인 연구를 복잡도 이론 (complexity  theory) 이라고 한다. 알고리즘에 대한 연구는 복잡도 이론에 관한 추상적인 것과 지금까지 설명한 알고리즘 개발을 목표로한 실제적이고 구체적인 것으로 나눌 수 있다.

복잡도 이론에서는 문제를 그 어려운 정도에 따라 분류하는 것이 주요 연구 테마이다. 여기서는 그 분류에 관해서 알려져 있는 사실과 해결되지 않는 문제점을 간단히 소개하기로 한다. 이들 연구의 성과는 효율적인 알고리즘을 설계한다는 목적과는 직접 관계가 없다. 그러나 본질적으로 어려운 문제에 대해서 효율적인 알고리즘을 개발하려는 필요없는 노력을 하지 않게 하고 다른 방향에서 문제를 해결하는 기회를 제공한다는 소극적인 효용이 있다.

복잡도 이론에서는 복잡도가 문제의 크기 n 의 다항식으로 평가되는 알고리즘을 효율적 (efficient) 알고리즘이라고 하고, 반대로 n 의 다항식이 아닌 지수 함수의 형태로 평가되는 알고리즘의 비효율적 (non-efficient) 알고리즘이라고 한다. 다항식과 지수 함수의 사이에 위치하는 알고리즘 (O(nlog n) 과 같은 북잡도를 가진 알고리즘) 에 대해서는 아직 충분히 연구가 되고 있지 않는 실정이다.
효율적인 알고리즘이 존재하는 문제를 쉬운 문제 (tractable problem) 라고 하고 효율적인 알고리즘이 존재하지 않는 문제를 어려운 문제 (intractable problem) 라고 한다.

쉬운 문제와 어려운 문제를 나누는 기준은 모든 경우를 생각해야 하는 것에 있다. 즉 모든 경우를 생각해야 하는 문제를 어려운 문제라고 하고 그렇지 않은 문제를 쉬운 문제라고 한다. 문제가 쉽다는 것을 증명하기 위해서는 효율적인 알고리즘을 개발하면 그것으로 증명이 된다. 반대로 어렵다는 것을 증명하기 위해서는 어떤 알고리즘을 이용하더라도 그 문제를 효율적으로 해결할 수 없다는 것을 증명할 필요가 있기 때문에 그다지 간단하지는 않다. 어렵다는 것이 증명되어 있는 문제는 몇 가지 있지만 그 수는 많지 않다. 이와 같이 다항식 시간의 알고리즘이 존재하지 않는다는 것이 알려져 있는 문제는 그 문제에 대한 효율적인 알고리즘을 개발하려고 할 때 필요없는 노력을 하지 않고 처음부터 포기할 수 있는 결단을 내리는데 도움이 되므로 이러한 의미에서는 다루기 쉽다.

그러나, 이보다 다루기 어려운 것은 쉬운지 어려운지가 (지금까지) 알려져 있지 않는 문제이다. 이들 문제에는 효율적인 알고리즘이 알려져 있지 않을 뿐만 아니라 다항식 시간의 알고리즘이 존재하지 않는다는 것도 증명되어 있지 않다. 그리고 실제로도 어려운 것을 하려고 하면 금방 이들 문제에 부딪힌다고 해도 과언이 아닐 정도로 이런 문제는 매우 많이 존재한다. 이와 같은 문제들을 NP 완전 문제 (NP-complete problem) 라고 한다.

일반적인 알고리즘에서는 각 스텝에서 어떤 조작을 한다는 것이 명확하게 정해져서 기술되어 있으므로 데이터를 입력하면 어떤 조작을 어떤 순서로 한다는 것을 알 수 있다. 이러한 알고리즘을 특히 결정성 알고리즘 (deterministic algorithm) 이라고 부른다. 결정성 알고리즘에 의해 다항식 시간에 해결할 수 있는 문제를 모두 클래스 P(class P) 에 속하는 문제라고 한다.

이에 대해서 NP 라고 하는 것은 비결정성 알고리즘 (nondeterministic algorithm) 을 이용하면 다항식 시간에 해결할 수 있는 것을 의미한다. 비결정성 알고리즘이란 개개의 스텝에서 취할 수 있는 경로가 여러 개로 나눠져 있어서 그 중에서 적당한 경로를 택해서 실행해 나가는 것이다. 경로를 택한다고 하더라도 if 문에 의한 조건 분기와 같이 어느 경로를 선택할 것인가에 대한 정보가 주어져 있는 것이 아니다. 아무튼 어느 경로든지 하나의 경로를 택해서 앞으로 실행해 나가는 수밖에 없다. 그리고 이들 경로 중 가장 적합한 경로를 잘 택했을 때에 다항식 시간으로 답을 찾아내는 문제의 집합이 클래스 NP (class NP) 이다.

그러나, 가장 좋은 경로를 택하면 다항식 시간으로 해결할 수 있다고는 하더라도 그런 경로를 잘 택할 수 있다는 보장도 없다. 어느 경로를 택하면 좋을지를 알 수 있는 방법이 없으므로 잘못된 경로를 택하면 오랜 시간을 필요로 할지도 모르고 답을 찾아내지 못할지도 모른다.

즉 정말도 다항식 시간에 해결할 수 있는 것은 어느 경로를 택하면 좋은지를 알 수 있는 것은 「신 (god)」뿐이다. 신이 아닌 사람이나 컴퓨터로서는 어느 경로를 택해서 앞으로 실행해 나가서 막히면 되돌아오는 등 모든 경로의 계산을 병행해서 나아가는 등의 수단을 사용할 수밖에 없다. 즉, 비결정성 알고리즘의 동작을 일반적인 결정성 알고리즘으로 모방해서 해결하는 셈이다.

이렇게 모방함으로써 복잡도의 정도도 바뀐다. 예를들면, 각 스텝에서 두 개씩 분기한다고 하면 원래의 비결정성 알고리즘인 경우 n 스텝에 해결할 수 있는 문제도 O(2n) 정도의 복잡도를 필요로 한다고 예상할 수 있다.

이상의 설명으로 크래스 NP 에 속하는 문제를 실제로 다항식 시간에 해결할 수 있다고 단언할 수 없다는 것은 이해할 수 있을 것이다.

결정성 알고리즘에 의해 다항식 시간에 해결할 수 있는 문제는 비결정성 알고리즘에 의해서도 다항식 시간에 해결할 수 있으므로 P NP 가 성립한다. 그러나 NP PNP 에 속하는 어떤 문제라도 다항식 시간에 해결할 수 있다면 P = NP 가 성립한다. 이것이 사실인지 아니면 P = NP 즉, 다항식시간에 해결할 수 있는 알고리즘이 존재하지 않는 문제가 NP 속에 있는지는 대문제이다. 이 문제는 P = NP 문제라고 불리는 문제로서 컴퓨터 과학 분야에서는 최대의 과제 중 하나이다.

많은 연구자는 P NP 즉, NP 속에는 효율적으로 해결할 수 없는 문제가 있다고 믿고 있다. 이것은 효율적인 알고리즘이 존재할 것같지 않은 문제가 NP 속에 많이 존재한다는 점과 비결정성을 그렇게 간단하게 결정성 알고리즘으로 바꿀 수 있다고는 생각하지 않는다는 등의 이유 때문이다. 그러나 아직 증명은 되지 않는 상황이다. 그러나 아직 증명은 되지 않는 상황이다.

NP-hard 문제란 NP 에 속하는 어떤 문제보다도 어렵거나 같은 정도로 어려운 문제를 말한다.

NP 완전 또는 NP-hard 문제에 대해서 현재 알려져 있는 알고리즘의 복잡도는 최악의 경우 입력 크기에 대해 지수 함수가 된다. 그래서 NP-hard 라는 것이 알려져 있는 최적화 문제에 대해서 구하는 답이 반드시 최적해가 아니라고 하더라도 최적해에 가까운 것이면 된다. 그러나 답을 출력할 때까지의 시간은 다항식 가능하다면 O(n) 또는 O(n2) 정도로 하는 것이 바람직하며 실용적으로는 이것으로 충분한 경우가 있다. 이와 같이 최적해에 가까운 답을 근사해라고 하고 그것을 구하는 (결정성) 알고리즘을 근사 알고리즘 (approximation algorithm) 이라고 한다.

근사 알고리즘을 평가하는 척도로는 일반적인 알고리즘과 마찬가지로 시간 복잡도와 영역 복잡도 이외에 그 알고리즘에서 얻어지는 답이 최적해에 어느 정도 가까운가를 평가하는 정밀도 (accuracy) 라는 것이 있다. 물론 정밀도와 복잡도면에서 모두 뛰어난 알고리즘이 있으면 가장 바람직하지만, 그렇지 않은 경우에는 정밀도는 좋지만 효율이 좋지 않은 알고리즘과 효율은 좋으나 정밀도가 나쁜 알고리즘을 문제의 상황에 따라서 잘 나눠서 사용할 필요가 있다.

여기서 다루려고 하는 문제는 순회 세일즈맨 문제에 다음과 같은 제한을 추가한 문제이다 (정점 x y 를 연결하는 변의 무게 (길이) 를 dxy 라고 표현한다.)


(1) 그래프상에 있는 각 변의 길이는 0이상
(2) 그래프상에 있는 임의의 세 정점 A, B, C 에 대해서, dAB + dBC dAC


첫 번째 조건은 임의의 한 정점에서 다른 정점을 방문했을 때, 도리어 값이 줄어 드는 경우는 없다는 것을 의미한다. 그리고, 두 번째 조건은 정점 A 에서 정점 C 를 방문할 때 정점 B 를 통해서 가는 것보다도 직접 가는 쪽이 좋다는 것을 의미한다. 이 식을 삼각 부동산이라고 한다.

이들 두 조건은 실제의 문제에 적용하는 상황을 생각하면 매우 자연스러운 조건이다. 이들 조건을 추가했을 때의 순회 세일즈맨 문제를 유클리드적 순회 세일즈맨 문제 (Euclidean traveling salesman problem) 라고 한다. 여기서는 그래프의 정점 수는 n 이라고 하고 변의 개수는 n(n - 1)/2 라고 한다. 이 문제의 경우에는 모든 두 정점 사이에는 변이 존재한다는 점을 주의해야 하는데, 변이 없다는 것은 dAB = ∞ 라고 해석할 수 있지만 이것은 두 번째 조건에 어긋난다.

위의 문제를 해결할 수 있는 가장 단순한 알고리즘 (단순 탐욕법) 을 소개하기로 한다.

우선 임의의 한 정점 (u 라고 한다) 을 택하고 정점 u 에 연결되어 있는 변 중에서 무게가 가장 적은 변 ((u, v) 라고 한다) 을 택해서 정점 v 로 이동한다. 이번에는 정점 v 에 연결되어 있는 변 중에서 무게가 가장 적은 것을 택하는데, 이 때 정점 v 에서는 변 (v, u) 를 대상에서 제외한다. 이와 같이 각 정점에서 무게가 가장 적은 변 (이미 지나온 정점으로 향하는 변을 제외) 을 선택하는 조작을 반복해서 마지막에 정점 u 에 되돌아 오게 되면 이것이 하나의 답이 된다.

그러나, 이 방법은 정밀도가 그다지 좋지 않으며, 얻어지는 경로의 길이가 최악의 경우에 최적해의 O(log n) 배나 되는 경우가 있다. 그러나 실제적인 순회 세일즈맨 문제의 경우에는 이와 같은 단순한 방법으로도 충분한 경우가 있다는 것이 알려져 있다.

다음은 이 알고리즘을 평가하기로 한다.

각 정점에서는 그 정점에 인접해 있는 변을 한번씩 조사하는데, 이 때 지금까지 지나온 정점으로 되돌아 가는 것을 제외한 변 중에서 가장 적은 변을 구한다. 각 정점에 인접해 있는 변의 개수는 n - 1 이므로 전체 복잡도는 O(n2) 이 된다.

이외에도 삽입법이라든가 최소목법 등의 방법을 이용하면 정밀도가 높은 답을 구할 수 있는데, 이들 방법에 대해서는 생략하기로 하고, 참고 문헌을 참고로 하기 바란다.

Posted by skensita
Programming/Algorithm2008. 12. 14. 20:51


5. 백트랙킹법 (backtracking)


어떤 문제의 최적해를 구하려고 하지만 모든 가능성을 조사하지 않고는 최적해를 얻을 수 없는 경우가 있다. 여기서는 모든 가능성을 조직적이고도 효율적으로 조사할 수 있는 기법인 백트랙킹법 (backtracking) 을 소개하기로 한다.

게임 목 (game tree) 과 식 변형 목 등을 완전한 나무의 형태로 만들어서 컴퓨터에 기억시킬 수는 없다. 예를들면, 장기라던가 바둑에서는 경우의 수가 너무 많아서 이들을 전부 기술해서 한꺼번에 컴퓨터에 기억시키는 것은 불가능하기 때문이다. 그러나, 이들 나무는 구조가 매우 규칙적이다. 즉, 하나의 정점 (정점은 경우, 수식, 논리식 등을 나타낸다) 에서 어느 정점으로 변이 접해 있는가가 여러 개의 규칙에 의해 명확하게 정해져 있으므로 나무를 만들어 가면서 조사를 할 수가 있다.
여기서는 백트랙킹을 설명하기 위해 3목이라는 게임을 이용하기로 한다. 3목이란 게임은 두 사람이 그림 16 과 같은 사각형에 바둑돌을 교대로 놓아서 세 개의 돌을 동일 직선상에 먼저 놓는 사람이 이기는 게임이다. 그림 16 에서 각 칸에 쓰여진 번호는 각 칸을 구별하기 위해서 붙여 놓은 식별자이다.



그림 16  3 목의 초기 상태


그림 16 과 같이 전혀 바둑돌이 없는 초기 상태에서 출발해서 게임의 진행 상황을 보기 위해 하나의 경로를 따라가 보자. 포석은 기계적으로 「각 칸에 할당된 번호가 적은 순」으로 비어 있는 곳에 교대로 흑과 백을 놓는다. 이 방법을 토대로 그림 17(a) 와 같이 혹은 제일 처음에 왼쪽 위에 돌을 두고, 백은 그 오른쪽 옆에 돌을 두어가면 일곱 번째에서 흑이 승리하게 된다.
이것은 하나의 시행 (trial) 에 지나지 않는다. 이렇게 하면 백이 패하게 되므로 백이 이길 수 있는 경우를 살펴보기 위해 백에 있어서 다른 방법을 모색해 보기로 하자. 이를 위해, 경우 E 로 「돌아와서」(이것을 백트랙 (backtrack) 이라고 한다) 다시 생각해 보면 그림 17(b) 와 같이 E 에서 F′ 로 나아갈 수가 있고, 이하 G′, H, I 로 진행하면 역시 흑이 이기게 된다.



그림 17  시행과 수정


그러면 조금 더 백트랙해서 다시 보기로 하자. G′ 까지 백트랙하면 다른 경로를 발견하게 되는데 (그림 17 (c)), 그쪽 경로를 택해서 나아가면 비길 수 있다.

이러한 『시행과 수정』은 나무의 일부분만을 사용해서 행할 수 있는데, 이처럼 숨겨져서 표면에는 나타나지 않는 나무를 암목적 나무 (implicit tree) 라고도 한다. 암묵적 나무를 그림 18 과 같이 (일부분이긴 하지만) 보이게 그리면 시행과 수정 모습을 금방 알 수 있다. 그림 18 에서 아래로 향하는 변의 게임 진행을 나타내고, 위로 향하는 변은 백트랙을 나타내며, 점선은 지금 단계에서는 아직 조사되지 않는 경로를 나타낸다.


그림 18  E 를 루트로 하는 나무


그림 18 까지 행한 것은 나무상에서의 변을 번호순으로 늘리거나 줄이면서 (백트랙) 각 경우에 도달한 것이다. 여기까지의 단계에서 경우 F 에서는 흑이 승리하고, 경우 G′ 에서는 비긴다는 것을 알 수 있다. 이후 경우 F′에서는 누가 이기는가를 알아보기 위해 계속해서 백트랙과 시행을 계속하면 경우 E 에서는 흑이 승리한다는 것을 알게 되고, 최종적으로는 초기 상태 (그림 14 R) 에서는 비긴다는 것을 알게 될 것이다.

여기서 중요한 것은 하나의 경로를 조사할 때는 거기에 나타나는 경우만을 기억해 두면 된다는 것이다. 아직 고려하지 않은 경우와 이전에 고려의 대상이었지만 백트랙킹에 의해 현재의 경로에 포함되지 않는 경우는 잊어 버려도 된다. 이와 같이 함으로써 완전한 나무를 만들어 낼 때보다는 훨씬 적은 기억 용량으로 전체를 계속 조사할 수가 있다.

이러한 「조사」를 능률적으로 행하기 위해서는 조사하지 않아도 되는 부분을 조사 대상에서 제거하면 되는데, 조사 대상을 제거하는 기법으로 α - β 변 제거라는 것이 있으나 여기서는 생략하기로 한다.

Posted by skensita
Programming/Algorithm2008. 12. 14. 20:48

4. 탐욕법 (Greedy Algorithm)


분할 통치법은 알고리즘의 전략으로서는 약간 특이한 것으로서 이것은 상황이 잘 파악된 부분을 넓혀감으로써 서서히 해답에 가까워진다는 방식이다. 이런 방식에서는 해답에 접근해 가는 방법에 따라 능률이 좋아지기도 하고 나빠지기도 한다. 예를들면 선형 탐색과 2진 탐색은 목표로 하는 레코드에 어떻게 접근할 것인가가 다르기 때문에 효율에 큰 차이가 생기는데, 선형 탐색에서는 레코드에 한 발씩 접근하고, 2 진 탐색에서는 n / 2, n / 4, n / 8, … 의 폭으로 대담하게 접근한다.

해답에 접근하는 방법은 문제마다 여러 가지가 있는데 이들을 분류하는 것은 어렵지만 일반적인 전략도 많이 알려져 있다. 탐욕법 (greedy algorithm) 은 그중에서 가장 단순한 것으로 현재의 상황에서 국소적으로 보아 가장 좋은 경우를 택해서 나아가는데, 그외의 경우를 무시함으로써 능률의 향상을 기대할 수 있다. 예를들면 앞에서 설명한 Dijkstra 알고리즘과 Prim 알고리즘은 현재 갖고 있는 지식을 이용해서 길이가 가장 짧은 경로 또는 변을 택하기 때문에 탐욕법의 알고리즘으로 분류된다.

탐욕법의 본질적인 문제점은 국소적인 관점에서 가장 좋은 선택이 대국적인 관점에서 보았을 때도 좋은 선택이라는 보증이 없다는 것이다. 즉 눈앞의 이익에 급급해서 큰 이익을 놓칠 우려가 있다. 그러나 문제에 따라서는 국소적인 최선과 대국적인 최선이 일치하는 경우가 있는데 그러한 경우에는 매우 유효한 기법이다. 위에서 설명한 두 가지 알고리즘이 경우 대국적인 최적해를 이 방법으로 구할 수 있다는 것은 각각 이미 증명된 대로이다.

탐욕법에서는 항상 대국적인 관점에서는 최적해를 얻을 수 있다고는 할 수 없는데, 인생과 마찬가지로 「욕심」이란 지금 당장은 좋은 결과를 얻을지 모르나 전체로서는 나쁜 결과를 초래할지도 모른다. 예로서 Dijkstra 알고리즘과 Kruskal 알고리즘에서 변의 무게가 음인 경우가 있을 때는 어떻게 되는지를 상기해 보자. 음의 무게를 가진 변이 존재하는 경우에는 Kruskal 의 최소목 알고리즘에는 영향이 없으므로 최소목을 제대로 구할 수 있지만, Dijkstra 알고리즘은 경우에 따라 최단 경로를 구하지 못하는 경우가 있다.

그림 12  음의 무게를 가진 그래프


예를들어 그림 12 와 같은 그래프에서는 b c 사이에 음의 무게를 가진 변이 있다. 출발점을 s 로 해서 Dijkstra 알고리즘을 적용하면 우선 a 까지의 길이가 1 인 최단 경로를 구하게 된다. 다음은 s a 에서 b c 로 향하는 변만을 조사하므로 b s 에 가장 가깝다고 생각한다. 이 「최단 경로」는 s a b 로 길이는 3 이다. 그 후에 s 에서 c 로의 최단 경로의 길이가 1 이라는 것을 알게 된다.
그러나 c 를 택하기 전에 b 를 선택한 「욕심많은」선택은 넓은 시야에서 보면 잘못된 것이다. s a c b 라는 경로의 길이는 2 이므로 b 의 최단 거리가 3 이라는 것은 잘못되었다.

문제에 따라서는 최적해를 구하는 탐욕 알고리즘이 존재하지 않을지 모르지만 상당한 확률로 최적해에 가까운 답을 구하는 탐욕 알고리즘이 존재하는 경우가 있다. 대개는 최적해보다도 수 퍼센트 정도 코스트가 커도 관계없다. 이와 같은 경우 최적해에 가까운 답을 구하는 보다 빠른 방법이 탐욕 알고리즘이 되는 경우도 많다. 모든 경우를 조사하지 않으면 최적해가 구해지지 않는 문제라면 탐욕 알고리즘과 그외의 발견적인 방법을 사용해서 (최적해가 아닐지도 모르지만), 최적해에 가까운 답을 구하는 것이 보다 현실적이다.
최적해를 구하기 위해서는 모든 가능성을 조사해야 하는데, 그렇게 되면 입력의 크기를 지수로 하는 시간이 걸린다는 유명한 문제 (순회 세일즈맨 문제) 를 소개하기로 하자.

순회 세일즈맨 문제 (traveling salesman problem, 이후 「TSP」라고 한다) 란 변에 무게가 할당된 무방향 그래프에서 변의 할당된 무게의 합이 가장 적은 폐로 (모든 정점을 단 한번만 포함하는 폐로) 를 구하는 문제이다. 이러한 폐로를 해밀턴 폐로(hamilton cycle) 라고 한다.

그림 13 에 순회 세일즈맨 문제의 한 예를 나타냈는데 이 그래프에는 정점이 6개 있다. 각 정점의 좌표가 주어져 있으므로 변의 길이를 그 변의 무게라고 한다. TSP 에서는 일반적으로 대상이 되는 그래프를 모든 두 정점 사이에 변이 존재하는 완전 그래프라고 가정하는데, 변의 무게가 유클리드의 거리가 아닌 일반적인 경우에는 존재하지 않는 변은 무게가 무한 대라고 생각하면 된다.

그림 13 순회 세일즈맨 문제의 예


그림 13 에 있는 6 도시를 순회하는 네 가지 폐로의 예를 그림 14 에 나타낸다. 그들 각 폐로의 길이는 52.13, 49.73, 48.39, 49.78 로 그림 6 ~ 14 에 나타낸 폐로 중에서 (c) 가 가장 짧다.

그림 14  폐로의 예


TSP 에는 실용적인 응용이 몇 가지 있는데, 이름이 나타내는 바와 같이 여러개의 지점을 방문하고 나서 출발점에 되돌아오는 코스를 정하는데 사용할 수 있다. 예를들면 우편 배달부가 다니는 코스는 TSP 를 사용해서 정할 수 있는데 이 경우 각 정점은 우체통의 장소를 나타내고 변의 코스트는 두 정점간의 이동 시간을 나타낸다.
TSP 에 대한 탐욕 알고리즘으로서는 kruskal 알고리즘의 변형을 생각할 수 있다. Kruskal 알고리즘과 마찬가지로 우선 가장 짧은 변을 선택한다. Kruskal 알고리즘에서는 선택하려는 변을 추가하더라도 폐로가 구성되지 않는 변을 하나씩 확정했다. TSP인 경우에는 선택하려는 변을 추가했을 때,

(1) 차수 (degree: 정점에 연결된 변의 수) 가 3 이상인 정점이 되지 않고,
(2) 폐로가 구성되지 않는 (단, 선택한 변의 수가 주어진 문제의 정점수와 같은 경우는 제외) 변을 확정한다.

위의 두 가지 조건을 이용해서 차례대로 변을 선택하면 서로 연결되어 있지 않은 경로가 여러 개 구성되고, 마지막 스텝에서 남은 하나의 경로가 닫혀서 폐로가 된다.
예를들면, 그림 13 에서는 변 (d, e) 가 길이 3 으로 가장 짧으므로 우선 이것을 선택한다. 다음은 길이가 5 인 변 (b, c), (a, b), (e, f) 를 조사하는데 이들의 순서는 아무래도 좋다. 세 개 다 조건을 만족하고 있으므로 탐욕 방법을 사용하면 세 개를 모두 확정해야 한다. 그리고 나서 남는 변 중에서 가장 짧은 것은 (a, c) 로 그 변의 길이는 7.08 이다. 그러나 변 (a, c) 는 (a, b) 및 (b, c) 와 함께 폐로를 형성하므로 확정할 수 없다. 마찬가지로 다음의 변 (d, f) 도 버린다. 다음에 변 (b, e) 를 조사하지만 이 변을 추가하면 b e 의 치수가 3이 되어 버리므로 지금까지 선택한 변과 합해도 폐로가 되지 않기 때문에 확정할 수 없다. 마찬가지로 (b, d) 도 버린다. 다음에 (c, d) 를 선택하면 a b c d e f 라는 하나의 경로가 구성되는데, 이 경로에 마지막으로 (a, f) 를 선택해서 추가하면 그림 15 와 같은 폐로가 완성된다.

그림 15  순회 세일즈맨 문제의 답

Posted by skensita