퀵 소트 구현하기


퀵소트

  • 1번째 버전.
  • swap 함수는 아래 코드에서 생략함.
void quick_sort(int* v, int n)
{

    if(n <=1) return;
    int* pivot = v;
    int* left = v + 1;
    int* right = v + n - 1;
    while(1)
    {
        if(left == right) break;
        if(*left > *pivot)
        {
            while(*right > *pivot)
            {
                if(left < right) right--;
                else break;
            }
            if(left == right) break;
            swap(left, right);
            left ++;
        }
        else
        {
            left++;
        }
    }
    
    int* meet = left; // or right
    // 만난 값이 피봇보다 크면 만난 값왼쪽에 피봇을 넣는다.
    if(*meet > *pivot )
    {
        int k = meet - pivot - 1;
        for(int i = 0; i < k; i++)
        {
            swap(pivot + i, pivot + i + 1);
        }
        pivot = meet - 1;
        
    }
    //만난 값이 피봇보다 작으면 만난 값 오른쪽에 피봇을 넣는다.
    
    else
    {
        int k = meet - pivot;
        for(int i = 0; i < k; i++)
        {
            swap(pivot + i, pivot + i + 1);
        }
        pivot = meet;
    }
    
    // v이상 pivot 미만
    quick_sort(v, pivot - v);
    // 피봇을 초과하고 끝을 포함하는 배열, v+ n - 1은 끝 주소값 , pivot은 피봇값, 초과 이하이므로 + 0
    quick_sort(pivot + 1, v + n - 1 - (pivot));
}


  • 첫 번째 리팩토링
  • 마음에 썩 들진 않지만 while문을 하나만 사용했고, 조건문에는 하나의 조건문만 주어 작성해 가독성과 원치 않은 오류를 줄였다.
  • n이 2가되는 순간에 left와 right가 예상치못하게 자동으로 같아져 헤맸다.

void quick_sort(int* v, int n)
{
    print(v, n);
    if(n <=1) return;
    int* pivot = v;
    int* left = v + 1;  // left를 v로 잡을까 v + 1로 잡을까에 대해서 고민을 했다.
    int* right = v + n - 1;
    
    printf("pivot: %d left: %d right: %d\n", *pivot, *left, *right);
    
    while(1) // 하나의 while문에 담으려고 고민했다.
    {
        printf("=============================================\n");
        print_debug(v, n, left, right);
        if(left == right) break; // n = 2일때는 이미 left == right이기 때문에 확인해준다.
        if(*left < *pivot) // 조건문에는 하나의 조건만 담으려고 했다. 두개 이상의 조건이 나오면 어떤 조건에서 성립이 안되었는지 2번확인해야 하기 때문이다.
        {
            left ++;
            if(left == right) break;  // left를 변화시킬때마다 left == right를 확인해준다.
            
        }
        else
        {
            if(*right > *pivot)
            {
                right --;
                if(left == right) break; // right를 변화시킬때마다 left == right를 확인해준다.
            }
            else
            {
                puts("swap");
                swap(left, right);
                print_debug(v, n, left, right);
            }
            
        }
    }
    
    
    printf("\n\n");
    print(v, n);


    int* meet = left; // or right
    // 만난 값이 피봇보다 크면 만난 값왼쪽에 피봇을 넣는다.
    if(*meet > *pivot )
    {
        int k = meet - pivot - 1;
        printf("k = %d\n", k );
        for(int i = 0; i < k; i++)
        {
            swap(pivot + i, pivot + i + 1);
        }
        pivot = meet - 1;

    }
    //만난 값이 피봇보다 작으면 만난 값 오른쪽에 피봇을 넣는다.

    else
    {
        int k = meet - pivot;
        for(int i = 0; i < k; i++)
        {
            swap(pivot + i, pivot + i + 1);
        }
        pivot = meet;
    }
    print(v, n);

    
    // v이상 pivot 미만
    quick_sort(v, pivot - v);
    // 피봇을 초과하고 끝을 포함하는 배열, v+ n - 1은 끝 주소값 , pivot은 피봇값, 초과 이하이므로 + 0
    quick_sort(pivot + 1, v + n - 1 - (pivot));
    
}

  • 2번째 리팩토링
  • 코드가 한페이지에 보일정도로 확실히 짧아지고 가독성이 생겼다.
  • 연속되는 두 while문에서 조건문을 하나만 넣지 않고 두개를 넣어 작성했는데, 이어지는 while문에서도 같은 조건을 주었다. 그래서 원하는 일을 반복해서 하면서도 left != right라는 중요한 조건이 깨지지 않도록 했다. 마지막으로 left ==right일 경우를 잡아내는 코드를 작성해 break 시켰다.
  • left == right를 비교하는 if문이 전보다 2개가 줄게 되면서 코드 완결성이 더 높아졌다고 생각한다.
  • 또한 pivot의 위치를 정하는 코드에서도 swap을 무식하지만 간편하게 반복하는 것 대신 좀 더 고려해서 n = 2일 때도 적용할 수 있는 코드를 작성했는데 , 훨씬 더 나아졌다고 생각한다.

void quick_sort(int* v, int n)
{
    if(n <=1) return;
    
    int* pivot = v;
    int* left = v + 1;  // left를 v로 잡을까 v + 1로 잡을까에 대해서 고민을 했다.
    int* right = v + n - 1;
 
    
    while(1)
    {
        while(*left < *pivot && left < right)
        {
            left ++;
        }
        while(*right > *pivot && left < right)
        {
            right --;
        }
        if(left != right)
        {
            swap(left, right);
        }
        else if(left == right)
        {
            break;
        }
    }
    
    int* meet = left; // left와 right가 만난 값
    if(*meet > *pivot)
    {
        swap(pivot, meet - 1);
    }
    else
    {
        swap(pivot, meet);
    }
    
    // v이상 pivot 미만
    quick_sort(v, pivot - v);
    // 피봇을 초과하고 끝을 포함하는 배열, v+ n - 1은 끝 주소값 , pivot은 피봇값, 초과 이하이므로 + 0
    quick_sort(pivot + 1, v + n - 1 - (pivot));
    
}

####퀵소트를 짤 때 어려웠던 점

  • 어떤 조건에서 리턴을 넣어야 할지 몇 분간 고민했다. n이 2보다 작을 때인지 1이하일 때인지.
  • while문 안에 while문을 넣어야할까봐 고민을 했다.
  • while문의 조건에 두가지 조건을 넣으면 break가 걸렸을 때 어떤 조건을 만족 못해서 나온것인지 너무 햇갈렸다.
  • pivot과 left나 right가 만날 때 어떻게 처리해야 할지 고민했다.
  • 가장 어려웠던 문제는 left와 right를 전진시킬 때 각각 한번 씩 전진시켰다. 이때 운이 좋으면 left와 right가 만나고 끝이 나지만, 최악의 상황에는 left가 right보다 뒤에가는 상황이 발생했다.
  • 한 번 움직일 때마다 left와 right가 만나는지 확인하는 코드가 있으면 더 간단하고 가독성도 높은 코드가 될 것같다.
  • 특히 만났을 때 상황에 따라서 pivot값의 위치가 달라져야 하는 상황에서 고민을 많이했다.
  • 오류가 발생했을 때 디버깅하는 시간이 최소 1/3은 차지했던 것 같다.
  • 반복되는 작업이므로 계속 쓸 디버깅 툴을 기억하자ㅏ.
  • 포인터 주소값의 차를 구했을 때 이 값이 정말 주소값 간의 거리인지, 자료형의 크기로 이미 나누어 놓은 값인지 알지 못해 헤메었다.
  • 포인터의 자료형을 int*으로 정해야 할지 long*으로 정해야 할지 햇갈렸다.

잘한 점

  • 반복되는 작업시간을 줄이기 위해서 간단한 디버깅 툴을 만들었다.
  • left와 right에 | |를 씌워주고 각 출력시 일정한 띄어쓰기 간격을 적용해서 디버깅 효율이 높아졌다.






© 2017. by yunsu

Powered by dolphin