Data structure | |Quick sort, two-way quick sort, three-way quick sort

Quick sort, two-way quick sorting, three-way quick sorting


1. Quick Sort

1. Concept

Quick sort uses the idea of ​​divide and conquer to sort data

  1. Choose a benchmark

  2. Place the value smaller than the reference value on the left of the reference value, and the rest (greater than or equal to) on the right

  3. Then continue to divide the left and right sides until the length of the divided interval is 1.

2. Time complexity

Quick sorting is O(logN) when dividing the interval, and each time it takes O(N) to sort

So the time complexity of fast sorting is O(NlogN) , which is the case when the interval can be roughly divided into equal parts for the reference value

If the data is already in order, every time you divide and conquer, there may be no data on one side, but data on the other side. This is the same as bubble sorting, and the time complexity is O(N^2)

In summary:

The best time complexity: O(NlogN)

Worst time complexity: O(N^2)

3. Suitable scenarios

Quick sort is suitable for situations where the data is out of order. When the data is in order, the time complexity is most likely O(N^2), which does not reflect the advantages of fast sorting.

4. Optimization

  1. When divided into small areas, insert sorting is used instead of quick sorting

  2. For the recursive implementation of the fast row can be changed to non-recursive, using the stack to achieve

  3. Every time you select the reference value, you no longer directly select the first or last element.

You can take the middle method of three numbers, or choose a benchmark value randomly

5. Realization

There are three implementation methods for the partion function of fast sorting:

  1. Left and right pointer method

  2. Digging method

  3. Front and back pointer method

The method of front and back pointers is used here to achieve

//前后指针法
int Partion(int array[], int left, int right)
{
    int cur = left;
    int prev = cur - 1;
    while (cur < right)
    {
        if (array[cur] <= array[right] && ++prev != cur)
        {
            std::swap(array[cur], array[prev]);
        }
        cur++;
    }
    std::swap(array[++prev], array[right]);
    return prev;
}
void QuickSort(int array[], int left, int right)
{
    if (array == nullptr)
    {
        return;
    }
    if (left < right)
    {
        int index = Partion(array, left, right);
        QuickSort(array, left, index - 1);
        QuickSort(array, index + 1, right);
    }
}

Other implementation methods and optimized code: https://github.com/YKitty/LinuxDir/blob/master/DS/Sort/Sort.c

2. Two-way fast queue

1. The reason

For fast sorting, if there are too many data elements and the size of the elements are very close, when the left and right divide and conquer, one side will be full of data, and the other side will have no data , which will lead to reduced efficiency and time complexity Becomes O(N^2)

Example: Use the template to test the time of merge sort and quick sort, set an array of 1000000, the array elements are randomly selected between 0-10, then it will take 0.290727s to use merge and 171.151s to fast sort, yes, you don’t have Wrong. When the quick sort is optimal, it is o(nlgn), and at this time it obviously degenerates to the level of o(n^2). Why is that?

For the quick sort I wrote above, all the data less than or equal to the benchmark value is placed on the left, and the data greater than the benchmark is placed on the right, then there will be problems. No matter when the condition is greater than or equal to or less than or equal to, when there are too many repeated elements in the array, there are too many elements equal to the reference value, then the array will be divided into two parts that are extremely unbalanced, because the part equal to the reference value is always concentrated On the side of the array.

At this time, the use of two-way fast row can be optimized to prevent the reduction of efficiency.

Problems solved by the two-way quick row:

Do not let all elements equal to the reference value be concentrated on one side of the array.

2. Thought

  1. We put all the elements less than the reference value on the left side of the array, and the elements greater than the reference value on the right side of the array

  2. For the left with a index left , on the right has a index right

  3. When left is less than the reference value , it keeps going backward ++ until it encounters a left greater than or equal to the reference value ; when right is greater than the reference value , it keeps going forward - until it encounters a right less than or equal to the reference value

  4. At this time , the data elements at left and right are swapped , and then left++, right--

  5. Continue to execute No. 2 until left==right stops

  6. In this case, the exchange and left position reference value elements, returned to the reference value can be

This kind of thinking, even if there are many repeated data elements, they can be separated almost equally, and it will not cause a huge amount of data on one side and no data on the other side.

3. Suitable scenarios

When there are too many repeated elements in the array , you can’t use fast sorting, just use two-way fast sorting

4. Realization

When implementing, only need to change, the partion function

int Partion_Two(int array[], int left, int right)
{
    int l = left;
    int r = right - 1;
    while (1)
    {
        while (l <= r && array[l] < array[right])
        {
            l++;
        }
        while (l <= r && array[r] > array[right])
        {
            r--;
        }
        if (l > r)
        {
            break;
        }
        std::swap(array[r], array[l]);
    }
    std::swap(array[l], array[right]);
    return r;
}

Note: Find the one that is not less than on the left, and the one that is not greater on the right, and then perform judgment and exchange

3. Three-way fast queue

1. Thought

Divide the array into three parts, smaller than the reference value, greater than the reference value, and equal to the reference value

Record the three subscripts:

lt: The last subscript less than the reference value

gt: The first subscript greater than the reference value

index: the subscript being traversed

index is less than the benchmark value: exchange index and lt+1, lt++

index is greater than the reference value: exchange index and gt-1, gt--

index is equal to the benchmark value: index++

End condition: index==gt

The last step is to exchange benchmark values:

swap(arr[lt], arr[right])

Continued interval:

[left,lt-1]

[gt, right]

 

2. Realization

void QuickSort_Three(int array[], int left, int right)
{
	if (array == nullptr)
	{
		return;
	}

	if (right <= left)
	{
		return;
	}

	int lt = left - 1;
	int gt = right;
	int index = left;
	int key = array[right];
	while (index < gt)
	{
		if (array[index] < key)
		{
			std::swap(array[index], array[lt + 1]);
			lt++;
			index++;
		}
		else if (array[index] > key)
		{
			std::swap(array[index], array[gt - 1]);
			gt--;
		}
		else
		{
			index++;
		}
	}
	std::swap(array[index], array[right]);
	QuickSort_Three(array, left, lt );
	QuickSort_Three(array, gt, right);
}

 

Guess you like

Origin blog.csdn.net/qq_40399012/article/details/89059038