데이터 구조 - 빠른 정렬 소개

빠른 정렬

퀵 정렬은 1962년 Hoare가 제안한 이진 트리 구조 교환 정렬 방법입니다. 퀵 정렬은 일반적으로 사용되는 정렬 알고리즘으로, 정렬할 시퀀스를 하나의 요소를 "기준 값"으로 선택하여 두 개의 하위 시퀀스로 나누는 것이 기본 아이디어입니다. 의 모든 요소는 기준 값보다 큽니다. 그런 다음 두 하위 시퀀스를 별도로 재귀적으로 정렬하고 마지막으로 정렬된 하위 시퀀스를 결합하여 완전한 순서 시퀀스를 얻습니다.

생각

정렬할 요소 시퀀스의 모든 요소를 ​​기준값으로 하고 정렬할 집합을 정렬 코드에 따라 두 개의 하위 시퀀스로 나눕니다. 오른쪽 서브시퀀스가 기준값보다 크면 가장 왼쪽 서브시퀀스가 모든 요소가 해당 위치에 배치될 때까지 과정을 반복합니다.여기에 이미지 설명 삽입

홀 버전

홀 버전 단일 패스 정렬 코드 구현

왼쪽과 오른쪽에 두 개의 첨자를 정의합니다. keyi가 맨 왼쪽에 있다고 가정하면 오른쪽이 먼저 갑니다. 오른쪽에서 keyi보다 작은 것을 찾으면 중지하십시오. 그런 다음 왼쪽에서 걷기 시작하고 왼쪽에서 keyi보다 큰 것을 찾으면 중지합니다. 왼쪽 값과 오른쪽 값을 바꿉니다. 그런 다음 왼쪽 포인터와 오른쪽 포인터가 만날 때까지 left와 keyi의 값을 교환합니다.
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

void QuickSort(int* arr,int begin, int end)
{
    
    
   	int left = begin;
    int right = end;
    int keyi = left;
    while (left < right)
    {
    
    
        //右边先走
        while (left < right && arr[right] >= arr[keyi])
        {
    
    
            right--;
        }
        while (left < right && arr[left] <= arr[keyi])
        {
    
    
           left++;
        }
        Swap(&arr[left], &arr[right]);
    }
    Swap(&arr[keyi], &arr[left]);
}

홀 버전 정렬 코드 구현

단일 패스 정렬이 첫 번째 keyi의 데이터를 스쿼트해야 하는 위치에 놓을 때 배열은 [begin, keyi-1], keyi, [keyi+1, end]의 세 부분으로 나눌 수 있습니다. 이때, 이진 트리의 재귀적 분할정복(Divide-and-Conquer) 발상에 따라 하위 구간의 데이터를 다시 왼쪽의 keyi로 선택하여 하위 구간이 성립할 때까지 싱글 패스 정렬을 수행한다. 존재하지 않습니다. 마지막으로 데이터를 정렬할 수 있습니다.
여기에 이미지 설명 삽입

정렬된 배열을 위한 최적화

이미 정렬된 배열을 정렬하는 경우 위의 코드는 치명적인 문제가 있습니다. 즉, 최적화되지 않은 퀵정렬의 시간 복잡도는 최악의 경우 O(N^2)입니다. 데이터 양이 많으면 재귀로 인해 스택 오버플로 문제가 발생합니다. 첫 번째 최적화 방법, 즉 keyi의 난수 선택을 소개합니다.

난수선택키i

배열 길이의 난수를 keyi로 취함으로써 완전한 순서 또는 거의 순서에 가까운 경우 빠른 정렬의 시간 복잡도 문제를 어느 정도 피할 수 있습니다. 처리 방법을 간단히 살펴보겠습니다.
여기에 이미지 설명 삽입

3개중 중간을 잡아라

가장 왼쪽, 가장 오른쪽 및 중간 위치의 값을 비교하여 중간 값을 첨자 keyi로 취합니다. 이와 같이 퀵소팅의 효율성 저하 문제는 오더 또는 부분 오더의 경우 어느 정도 최적화할 수 있다.

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

구덩이 파기

굴착 방법의 단일 패스 정렬 코드 구현

홀 씨의 방법은 비교적 모호하고 이해하기 어렵기 때문에 지금 소개할 굴착 방법이 있습니다. 이름에서 알 수 있듯이 굴착 방식은 빠른 정렬 아이디어를 기반으로 최적화된 버전입니다. 먼저 키가 가장 왼쪽에 있는 숫자라고 가정하고 피트는 왼쪽에 있습니다. 그런 다음 오른쪽에서 걷기 시작하여 키보다 작은 것을 만나면 구덩이에 넣고 구덩이 위치를 정지 위치로 변경하십시오. 왼쪽도 마찬가지입니다.
여기에 이미지 설명 삽입

void QuickSort1(int* arr, int begin, int end)
{
    
    
    int left = begin;
    int right = end;
    随机选key
    通过随机数来选择key,优化了顺序和逆序的情况下的时间复杂度
    //int randi = left + (rand() % (right - left));
    //
    //Swap(&arr[randi], &arr[left]);

    //三数取中优化
    int ret = GetMidNum(arr, left, right);
    if (left != ret)
        Swap(&arr[ret], &arr[left]);

    //左边做key
    int key = arr[left];
    int hole = left;
    while (left < right)
    {
    
    
        //右边先走
        while (left < right && arr[right] >= key)
        {
    
    
            right--;
        }
        //右边比key小就把它填到坑里,并改变坑位
        arr[hole] = arr[right];
        hole = right;
        while (left < right && arr[left] <= key)
        {
    
    
            left++;
        }
        //左边比key大就把它填到坑里,并改变坑位
        arr[hole] = arr[left];
        hole = left;
    }
    //最后坑位就是key应该蹲的位置
    arr[hole] = key;
}

발굴 방법 코드 구현

여기에 이미지 설명 삽입

전방 및 후방 포인터 방식

순방향 및 역방향 포인터 방법의 단일 패스 정렬 코드 구현

구현 아이디어는 다음과 같습니다. 먼저 prev와 cur 두 포인터를 정의합니다. 가장 왼쪽을 키로 사용하고 cur은 작은 값을 찾습니다.cur가 가리키는 내용이 키 ++prev보다 작을 때 prev의 값과 cur의 값을 교환합니다. 교환). cur로 찾은 값이 key보다 크면 ++cur. 이 방법의 일반적인 상황은 두 가지 유형으로 나뉩니다. 하나는 prev와 cur입니다. 2. Prev와 cur은 키 값으로 직접 구분됩니다. 그러면 이때 키보다 큰 값은 점차 뒤로, 키보다 작은 값은 점차 앞으로 던진다.
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

전면 및 후면 포인터 방식 코드 구현

여기에 이미지 설명 삽입

셀 간 최적화

우리는 빠른 정렬이 실제로 정렬을 위해 이진 트리 구조를 나누고 정복한다는 아이디어를 사용한다는 것을 알고 있습니다. 이진 트리 구조에서 높이가 h인 경우 최적의 경우(완전 이진 트리)를 고려하며 마지막 계층의 노드는 2^(h-1)입니다. 전체 나무의 절반을 차지하십시오. 끝에서 두 번째 계층의 노드 수는 2^(h-2)이며 전체 트리에서 노드의 25%를 차지합니다. 실제로 마지막 세 레이어의 재귀가 제거되는 한 재귀로 인한 손실은 일반적으로 줄어들고 효율성이 향상되며 스택 공간의 손실이 줄어들 수 있습니다. 로컬로 정렬된 작은 간격 내에서 최적화하기 위해 선택할 수 있는 정렬이 많이 있습니다. 그러나 이전에 배운 내용을 바탕으로 셀 간 최적화에 비해 힙 정렬 및 힐 정렬을 사용하는 이점이 명확하지 않음을 알 수 있습니다. 전자는 힙을 구축해야 하고 후자는 간격에 따라 미리 정렬해야 합니다. 실제로 셀 간 최적화를 위해 삽입 정렬을 선택하는 것이 가장 적합합니다. 로컬로 정렬된 간격에서 삽입 정렬의 효율성이 가장 높기 때문입니다.
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

퀵 정렬의 비재귀적 구현

스택을 사용하여 재귀가 필요한 간격을 저장한 다음 스택의 후입선출 특성에 따라 재귀 순서를 시뮬레이트합니다. 연속 정렬을 위해 간격을 하위 간격으로 변환합니다. 간격이 존재하지 않거나 간격에 숫자가 하나만 있을 때까지 간격의 첨자를 스택에 추가하지 마십시오.
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

퀵 정렬 기능 요약

1. 퀵 정렬은 포괄적인 성능이 우수하고 사용 시나리오가 다양한 정렬입니다.
둘째, 퀵 정렬의 시간복잡도는 O(N LogN)이다.
여기에 이미지 설명 삽입
최선의 경우 퀵 정렬의 시간복잡도는 O(nlogn)입니다. 정렬할 시퀀스를 2개의 서브시퀀스로 균등하게 나눌 수 있을 때 각각의 분할 연산은 시퀀스를 대략적으로 균등하게 나눌 수 있다. 총 비교 횟수는 O(N
LogN)입니다. 최악의 경우 퀵 정렬의 시간 복잡도는 O(n^2)입니다. 정렬할 시퀀스가 ​​이미 정렬되었거나 기본적으로 정렬된 경우(예를 들어 시퀀스가 ​​이미 오름차순이지만 선택된 기준 값은 항상 가장 작은 또는 가장 큰 요소임) 각 분할 작업은 하나의 서브 시퀀스만 잘라낼 수 있으며, 재귀 호출 계층 수는 n이고 각 계층에 필요한 비교 수는 n이므로 총 비교 수는 n^2입니다. 그러나 기본 값을 무작위로 선택하거나 중앙값 3과 같은 최적화 방법을 사용하면 최악의 경우가 발생할 가능성을 줄여서 퀵 정렬의 평균 성능을 향상시킬 수 있습니다. 실제 응용 프로그램에서는 퀵 정렬이 일반적으로 사용됩니다.
3. 퀵 정렬은 불안정한 정렬입니다.

코드 획득

추천

출처blog.csdn.net/m0_71927622/article/details/131339569