교환 정렬: 빠른 정렬

빠른 정렬(Quick Sort)도 일종의 교환 정렬로 정렬에서 분리 정책을 채택했다.

빠른 정렬의 주요 사상:

  • 대기 시퀀스에서 요소를 축 값(주원이라고도 함)으로 선택합니다.
  • 시퀀스의 나머지 요소는 축 값을 기준으로 좌우 두 부분으로 나뉩니다.왼쪽 요소는 축 값보다 크지 않고 오른쪽 요소는 축 값보다 작지 않습니다.축 값은 결국 두 부분의 분할에 있습니다.
  • 좌우 두 부분을 분할할 수 없을 때까지 반복적으로 분할한다.

  • 빠른 정렬의 알고리즘 사상을 통해 알 수 있듯이 이것은 귀속적인 과정이다.

    두 가지 질문:


    빠른 정렬을 철저히 이해하려면 두 가지 문제를 해결해야 한다.
  • 축 값은 어떻게 선택합니까?(축 값이 다르면 정렬에 영향을 줍니까?)
  • 어떻게 분할합니까?

  • 질문 1: 축 값 선택?
    축값의 중요성은 분할을 통해 서열을 가능한 한 길이가 같은 두 부분으로 나누어야 분치법이 작용할 수 있다는 데 있다.만약 축값이 서열의 최치라면 분할 후 원소가 모두 한쪽으로 도망가면 분치법은 무효가 된다.알고리즘 효율이 향상되지 않습니다. -다른 사람이 빠른 줄을 쓰는 것을 볼 때, 그의 축 값의 선택에 주의해라.
    문제2: 어떻게 분할합니까?
    이것은 구체적인 기교와 전략과 관련된다.잠시 후의 코드에서 우리는 하나하나 소개한다.

    버전 1 빠른 정렬:


    첫 번째 요소 또는 마지막 요소를 축 값으로 직접 선택합니다.이것도 국내 많은 교재 중의 작법이다.
    예:
    원래 순서 4 8 12 1 9 6
    아래 0 1, 2, 3, 4, 5
    축 값 pivot=4
    ij 초기화
    iji부동, 이동 j,while(i=pivot)j--; 
    요소 이동
    1   8   12   1   9   6
    ijj부동, 이동 i,while(i이동 요소 1 8 12 8 9 6
    i, j 다시 이동 j, i와 j가 만나 종료
    마지막 1 4 12 8 9 6 pivot
    축값 4의 좌우 두 부분을 이어서 분할하다.
    나는 네가 틀림없이 알아볼 것이라고 생각한다. 그리고 이 축의 값은 4, 정말 잘 선택하지 못했다. 왜냐하면 분할 후 왼쪽 부분에 원소가 하나밖에 없기 때문이다.
    어떤 사람들은 위의 방법은 구덩이를 파서 채우는 것이라고 말한다.이런 묘사는 정말 형상적이다.간단하게 설명하자면 먼저 첫 번째 원소를 축값으로 하고 변수 pivot로 축값을 저장한다. 이것이 바로 구덩이를 파는 것이다.이때 a[0]는 구덩이다.이어서 j를 이동하여 적당한 위치의 j를 a[0]에 채워서 a[j]는 새로운 구덩이가 되었다.낡은 구덩이가 메워지면 새로운 구덩이가 나타난다.i와 j가 만날 때까지 이 마지막 구덩이는pivot에 채워진다.이로써 두 번째 분할을 완성하였다.
    알아들었으니 코드를 두드려라!
    void QuickSort(int a[], int n)  // , 
    {
    	if (a && n > 1)
    	{
    		int i, j, pivot;  //pivot 
    		i=0, j = n - 1;
    		pivot = a[0];   // 
    		while (i < j)
    		{
    			while (i < j && a[j] >= pivot)
    			j--;
    			if (i < j)
    			a[i++] = a[j];
    			while (i < j && a[i] <= pivot)
    			i++;
    			if (i < j)
    			a[j--] = a[i];
    		}
    		a[i] = pivot;   // 
    		QuickSort(a, i);
    		QuickSort(a + i + 1, n - i -1);
    	}
    } 

    이제 마지막 원소를 축값으로 하는 코드를 생각해 보세요. 서두르지 말고 먼저 움직이세요!코드는 다음과 같습니다.
    void QuickSort(int a[], int n)
    {
    	if (a && n > 1)
    	{
    		int i, j, pivot;  //pivot 
    		i = 0, j = n - 1;
    		pivot = a[j];   // 
    		while (i < j)
    		{
    			while (i < j && a[i] <= pivot)
    				i++;
    			if (i < j)
    				a[j--] = a[i];
    			while (i < j && a[j] >= pivot)
    				j--;
    			if (i < j)
    				a[i++] = a[j];
    		}
    		a[i] = pivot;   // 
    		QuickSort(a, i);
    		QuickSort(a + i + 1, n - i - 1);
    	}
    }

    축 값 선택 정책


    축 값인 pivot가 무효가 되지 않도록 하기 위해서.우리는 pivot의 선택을 개선하기 위해 정책을 사용할 수 있습니다.

    전략 1:


    임의로 시퀀스에서 요소를 축 값으로 선택합니다. 
    int SelectPivot(int a[], int low, int high)
    {
    	int size = high - low + 1;
    	srand((unsigned)time(0));
    	return a[low + rand()%size];
    }

    수미 요소를 선택하는 것은 이 전략의 특례가 아니다!

    전략 2:


    무작위로 세 수를 선택하여 중위수를 얻다.
      
    int SelectPivot(int a[], int low, int high)
    {
    	int size = high - low + 1;
    	int p1, p2, p3;
    	srand((unsigned)time(0));
    	p1 = low + rand()%size;
    	p2 = low + rand()%size;
    	p3 = low + rand()%size;
    	/*
    	*   :
    	*   ,p1 ,
    	*   ,  p2  
    	*/
    	if(a[p1] > a[p2])
    		swap(p1, p2);
    	if(a[p1] > a[p3])
    		swap(p1, p3);
    	if(a[p2] > a[p3])
    		swap(p2, p3);
    	return a[p2];
    }

    그것의 한 가지 특례는 원래 서열의 첫 번째를 선택하는 것이다.
    꼬리, 중간 세 수를 세어 그것들의 중위수를 얻다.
    현재로서는 기본적으로 상용하는 것이 이 두 가지 전략에 있다.
    그러나 나는 한마디 해야 한다. 만약에 원 서열 중의 원소 자체가 무작위로 저장된 것이다. 즉, 각 원소가 각 위치에 나타날 확률이 같다는 것이다.그렇다면 특별히 수미원소를 선택하는 것과 무작위로 선택하는 것은 어떤 차이가 있을까?어떻게 보셨는지 모르겠어요.
    또 한마디 덧붙여야 한다. 무작위로 축 값을 선택한 후, 그것을 머리나 꼬리의 요소와 교환하는 것을 기억해야 한다.왜?알잖아!

    버전 2 빠른 정렬:


    이것도 알고리즘 도론의 판본이다.그것의 보편적인 방법은 꼬리 요소를 pivot로 선택하는 것이다.중점은 분할 함수:partition () 을 사용했다는 것이다.
    위조 코드는 다음과 같습니다.
    PARTITION(A, low, high)
    1.pivot<- A[high]//꼬리 요소를 축값으로 선택
    2.i<-low-1//low-1을 i에게 부여하고 아래와 같이
    3. for j<-low to high-1//j의 변화 범위 [low, high-1]
    4.      do if A[j] <= pivot
    5.            then i <- i+1
    6.            exchange A[i]<->A[j]
    7. exchange A[i+1} <-> A[high]
    8. return i+1;//분할된 위치를 반환합니다.
    그런 다음 전체 배열을 차례로 정렬합니다.
    QUICKSORT(A, low, high)
    1  if low < high
    2 then q <- PARTITION(A, low, high)//요소를 분할하는 것은 여기에 있습니다.
    3  QUICKSORT(A, low, q - 1)
    4  QUICKSORT(A, q + 1, high)
    만약 네가 위조 코드를 보는 것에 익숙하지 않다면, 내가 예를 들겠다. (아니면 위의 서열)
    원래 순서 4 8 12 1 9 6
    아래 표. - 1, 2, 3, 4, 5 축값. pivot은 6.
    ija[j]=a[0]=4<6, 다음 i++ 초기화;다시 swap(a[i], a[j]);다음 j++;
    교환 4 8 12 1 9 6
    계속 이동 j
    ij a[j]=a[3]=1<6, 다음...
    교환 4 1 12 8 9 6
                 i            j       
                 i                j   
    교환 41 68 9 12 마지막 swap(a[i+1], a[high]);또는 swap(a[i+1], a[j]);
    그래서 마지막으로 돌아온 건 i+1입니다.
    대백화로 위의 정렬 과정을 설명한다. 두 개의 바늘 i, j로 초기화하면 i=-1이다.j=0, 이어서 j를 끊임없이 증가시키고 a[j]>pivot를 만나면 i와 교환하여 j가 끝까지 가리킨다.
    더 직설적인 말: 처음부터 원래 서열을 훑어보고 축 값보다 작은 것을 만나면 서열 앞으로 교환한다.
    알아듣고 코드를 썼는데...
    int partition(int a[], int low, int high)
    {
    	int i, j;
    	i = low - 1;
    	j = low;
    	while (j < high)
    	{
    		if (a[j] < a[high])
    		swap(a[++i], a[j]);
    		j++;
    	}
    	swap(a[++i], a[high]);    //  
    	return i;  //  ++i,  i+1 
    }
    void quicksort(int a[], int low, int high)
    {
    	if (low < high)  // ,  
    	{
    		int i = partition(a, low, high);
    		quicksort(a, low, i - 1);
    		quicksort(a, i + 1, high);
    	}
    }
    void QuickSort(int a[], int n)
    {
    	if (a && n>1)
    		quicksort(a, 0, n - 1);	
    }

    문외한: 있는 Api 디자인을 보면 다음과 같다. QuickSort(int a[], int low, int high).사용자에게 0을 하나 더 쓰게 하다니!이렇게 하면 사용자를 고려하지 않는다.간결할수록 좋다.정렬은 그룹 이름과 그룹 크기만 주면 됩니다.
    위의 절차에 대해 다시 생각하기: 초기화 i=-1 보기;이상하지 않아요?왜 i는 반드시 -1부터 시작해야 하는지 i의 작용을 자세히 이해하면 i본은 0부터 시작할 수 있다는 것을 알게 될 것이다.이러한 방법의 파티션 () 방법은 다음과 같습니다.
    int partition(int a[], int low, int high)
    {
    	int i, j;
    	i = low;  // !
    	j = low;
    	while(j < high)
    	{
    		if (a[j] < a[high])
    		swap(a[i++], a[j]);
    		j++;
    	}
    	swap(a[i], a[high]);    //  
    	return i;  
    }

    다시 생각: 왜 j는 하이를 가리킬 수 없습니까?만약if(a[j]파티션 () 은 다음과 같습니다.
    int partition(int a[], int low, int high)
    {
    	int i, j;
    	i = low;
    	j = low;
    	while (j <= high)
    	{
    		if (a[j] <= a[high])
    		swap(a[i++], a[j]);
    		j++;
    	}
    	return i-1;   // i-1, ?
    }

    때때로 quicksort () 와partition () 을 함수로 쓰는 것은 더 이상 간단하지 않은 일이다.

    버전 3의 빠른 정렬:


    위에서 사용한 것은 모두 귀속의 방법이다. 귀속을 비귀속으로 바꾸는 것은 항상 간단하지 않고 사람을 흥분시킨다.이 판본은 바로 빠른 정렬의 비귀속 쓰기이다.
    void QuickSort(int a[], int low, int high)
    {
    	if (low < high)
    	{
    		stack<int> s;   // STL  
    		int l,mid,h;
    		mid = partition(a, low, high);
    		/*
    		  [low, mid-1]  [mid+1, high] 
    		 : ,  
    		*/ 
    		if (low < mid-1)
    		{
    			s.push(low);
    			s.push(mid - 1);
    		}
    		if (mid + 1 < high)
    		{
    			s.push(mid + 1);
    			s.push(high);
    		}
    		// ,  
    		while(!s.empty())
    		{
    			h=s.top();
    			s.pop();
    			l=s.top();
    			s.pop();
    			mid = partition(a, l, h);
    			if (l < mid - 1)
    			{
    				s.push(l);
    				s.push(mid - 1);
    			}
    			if (mid + 1 < h)
    			{
    				s.push(mid + 1);
    				s.push(h);
    			}	
    		}
    	}
    }

    이 비귀속적인 작법은 매우 재미있어서 기교가 매우 필요하다.잘 생각해 봐, 너는 알 수 있어.
    알림: 정렬할 하위 시퀀스의 맨 끝 요소를 창고에 저장하고 다음while 순환할 때 이 범위를 꺼내서 이 하위 시퀀스를partition 조작합니다.

    소결:


    빠른 정렬은 빠른 처리라고 불리며 시간 복잡도는 O(nlogn)이다.기본적으로 가장 좋은 정렬 방법이다.그것의 작법은 상기 세 가지 외에 대동소이하다.여기 봐.너는 반드시 그것을 철저히 이해했을 것이다.이상의 글쓰기는 모두 본인의 테스트를 거쳤는데, 당신의 테스트가 나와 같은지 모르겠습니다.
    전재는 출처를 밝히십시오. 본문 주소:http://blog.csdn.net/zhangxiangdavaid/article/details/25436609
    도움이 된다면, 기껏해야 하나!
    본 칼럼의 목록
  • 데이터 구조와 알고리즘 디렉터리
  • 모든 컨텐트의 디렉토리
  • CCPP Blog 디렉토리
  • 좋은 웹페이지 즐겨찾기