Quick sort of C++ algorithm recovery training

Quick Sort (Quick Sort) is a sorting algorithm based on the idea of ​​​​divide and conquer. It divides the array to be sorted into two sub-arrays, and all elements of one sub-array are smaller than the elements of the other sub-array, and then the two sub-arrays are sorted. The array is sorted recursively, eventually sorting the entire array. Quicksort is an in-place sorting algorithm with a time complexity of O ( nlogn ) O(nlogn)O(nlogn)

The following is a classic quicksort algorithm implemented in C++ :

#include <vector>
#include <iostream>
using namespace std;

int partitionSimple(vector<int>& array, int left, int right)
{
    
    
    if (left >= right)
    {
    
    
        return -1;
    }

    // Use the value of index `right` as the pivot
    const int pivot = array[right];
    int lessBound = left - 1;
    for (int i = left; i < right; i++)
    {
    
    
        // If the current element is not more than then pivot value,
        // then swap it with the less part's next value, and make the less part add 1
        if (array[i] <= pivot)
        {
    
    
            swap(array[i], array[++lessBound]);
        }
    }
    // At last, swap the pivot with the next element of less part
    swap(array[lessBound + 1], array[right]);
    return lessBound + 1;
}

void quickSortSimple(vector<int>& array, int left, int right)
{
    
    
    if (left >= right)
    {
    
    
        return;
    }

    const int pivotIndex = partitionSimple(array, left, right);
    quickSortSimple(array, left, pivotIndex - 1);
    quickSortSimple(array, pivotIndex + 1, right);
}

void quickSort(vector<int>& array)
{
    
    
    quickSortSimple(array, 0, array.size() - 1);
}

The implementation process of classic quick sort can be divided into two steps:

  • Split sub-problems: select an element as the reference value ( pivot), divide the array to be sorted into two sub-arrays, the elements in one sub-array are less than or equal to the reference value, and the elements in the other sub-array are greater than or equal to the reference value.
  • Merge solution: quickly sort (recursively) the two sub-arrays separately, and finally merge the two sorted sub-arrays into one sorted array.

The recursive part is actually easier to understand and implement, so now the problem can be simplified as: given an array and a reference value, how to place elements less than or equal to the reference value on the left side of the array, and place elements greater than the reference value in the array right?

My implementation idea is to use a conceptual variable such as an index of the boundary that is less than or equal to the pivot value , corresponding to the code lessBound, and its corresponding element and its left part are both less than or equal to pivotthe value. In the subsequent array traversal process, if the traversed element satisfies such a condition, the element is lessboundswapped with the last bit of , and then lessboundthe range of is expanded by one bit. The core idea is similar to the fast and slow pointers: that is, lessboundit plays the role of the slow pointer and ithe role of the fast pointer.

Finally, the array has been divided into two sub-arrays, one of which has elements less than or equal to the reference value, and the other sub-array whose elements are greater than the reference value. Then recurse on the two subarrays separately.

The time complexity of quicksort is O ( nlogn ) O(nlogn)O ( n l o g n ) wherennn is the length of the array to be sorted. Quick sort divides the array to be sorted into two sub-arrays each time, the elements in one sub-array are less than or equal to the reference value, and the elements in the other sub-array are greater than or equal to the reference value.

Since quick sort divides the array to be sorted into two sub-arrays each time, the height of the recursive tree is logn lognl o g n . The maximum size of the subproblem handled by each node isnn , so the time complexity of quicksort isO ( nlogn ) O(nlogn)O(nlogn)

It should be noted that in the worst case, the time complexity of quicksort is O ( n 2 ) O(n^2)O ( n2 )At this time, the array to be sorted is already ordered or nearly ordered, and the reference value selected each time is the smallest or largest element in the array. To avoid worst-case scenarios, the following optimizations can be employed:

  • Pick a base value at random.
  • Median-of-three partitioning: Select an element from the left end, right end, and middle position of the subarray, and select their middle value as the reference value.

pivotIn addition, starting from the algorithm itself, the classic quick sort uses a certain value as the reference value, and the essence of the algorithm is to determine the subscript index of this within a cycle . From this point of view, it can be considered to finalize the positions of all values ​​equal to and within this period pivot, and this batch number is not considered during recursion.

The corresponding implementation in C++:


vector<int> partitionOptimized(vector<int>& array, int left, int right)
{
    
    
    if (left >= right)
    {
    
    
        return {
    
    -1, -1};
    }

    int pivot = array[right];
    int lessBound = left - 1, moreBound = right;
    int i = left;
    while (i < moreBound)
    {
    
    
        if (array[i] == pivot)
        {
    
    
            i++;
        }
        else if (array[i] < pivot)
        {
    
    
            swap(array[++lessBound], array[i++]);
        }
        else
        {
    
    
            // array[i] > pivot
            swap(array[--moreBound], array[i]);
        }
    }
    swap(array[right], array[moreBound++]);
    return {
    
    lessBound, moreBound};
}

void quickSortOptimized(vector<int>& array, int left, int right)
{
    
    
    if (left >= right)
    {
    
    
        return;
    }

    vector<int> bounds = partitionOptimized(array, left, right);
    quickSortOptimized(array, left, bounds[0]);
    quickSortOptimized(array, bounds[1], right);
}

void quickSort(vector<int>& array)
{
    
    
    quickSortOptimized(array, 0, array.size() - 1);
}

The most notable difference of the new algorithm is that partitionthe return value of is an array, which saves pivotthe boundary of less than and pivotthe boundary of greater than, and they are also the basis for a new round of recursion. When calculating these two boundaries (within the partition), an array needs to be split into: pivotthe part that is less than, pivotthe part that is equal to, and pivotthe part that is greater than. At this time, three pointers are mainly used, which respectively point to pivotthe boundary of the part that is less than, pivotthe boundary of the part that is greater than, and the current traversal element. If the current element is less than pivot, similar to the previous idea, exchange the current element with the next bit that is less than the boundary, expand the boundary of the smaller one, and continue to traverse the next element; if the current element is equal, continue to traverse the next element, and others are pivotnot change; if the current element is greater than the value pivot, then the current element needs to be exchanged with the next bit greater than the boundary, and the greater boundary is reduced by one bit. Note that it is still necessary to investigate the size of the swapped element at this time, that is, not to continue the traversal of an element.

Of course, although it is an optimization, this idea is only slightly faster than the classic quick sort when there are repeated elements in the array. In essence, the complexity of the algorithm has not changed, nor has it changed the problem that the quick sort depends on the status of the array.

Using the method of randomly fetching the reference value can indeed improve this problem, but fetching the random number itself requires certain instructions, and its consumption is also a problem that needs to be considered and weighed.

おすすめ

転載: blog.csdn.net/xcinkey/article/details/130051110