新手入门快速排序算法

发布于:2022-12-21 ⋅ 阅读:(480) ⋅ 点赞:(0)

介绍

QuickSort 是一种分而治之的算法。它选择一个元素作为枢轴,并围绕选择的枢轴对给定数组进行分区。有许多不同版本的 quickSort 以不同的方式选择枢轴。

1.始终选择第一个元素作为枢轴
2.始终选择最后一个元素作为枢轴(在下面实现)
3.选择一个随机元素作为枢轴
4.选择中位数作为枢轴

quickSort (升序)中的关键过程是 partition()。分区的目标是,给定一个数组和一个数组的元素 x 作为枢轴,将 x 放在已排序数组中的正确位置,并将所有较小的元素(小于 x)放在 x 之前,并将所有较大的元素(大于比 x) 在 x 之后。所有这些都应该在线性时间内完成。
在这里插入图片描述

分区算法

分区的方法有很多种,下面的伪代码采用了 CLRS 书中给出的方法。逻辑很简单,我们从最左边的元素开始,跟踪更小或相等枢轴元素的索引为 i。在遍历时,如果我们找到一个较小的元素,我们将当前元素与 arr[i] 交换。否则,我们忽略当前元素。

递归快速排序函数的伪代码

/* low  –> Starting index,  high  –> Ending index */

quickSort(arr[], low, high) {

    if (low < high) {

        /* pi is partitioning index, arr[pi] is now at right place */

        pi = partition(arr, low, high);

        quickSort(arr, low, pi – 1);  // Before pi

        quickSort(arr, pi + 1, high); // After pi

    }

}

partition() 的伪代码

/* This function takes last element as pivot, places the pivot element at its correct position in sorted array, and places all smaller (smaller than pivot) to left of pivot and all greater elements to right of pivot */

partition (arr[], low, high)

{

    // pivot (Element to be placed at right position)
pivot = arr[high];  

 i = (low – 1)  // Index of smaller element and indicates the 
// right position of pivot found so far

for (j = low; j <= high- 1; j++){

 // If current element is smaller than the pivot
if (arr[j] < pivot){
        i++;    // increment index of smaller element
        swap arr[i] and arr[j]
   }
}

    swap arr[i + 1] and arr[high])
    return (i + 1)
}

partition() 的说明:

Consider: arr[] = {10, 80, 30, 90, 40, 50, 70}

Indexes: 0 1 2 3 4 5 6
low = 0, high = 6, pivot = arr[h] = 70
Initialize index of smaller element, i = -1
在这里插入图片描述
Traverse elements from j = low to high-1
j = 0: Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
i = 0
arr[] = {10, 80, 30, 90, 40, 50, 70} // No change as i and j are same
j = 1: Since arr[j] > pivot, do nothing
在这里插入图片描述
j = 2 : Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
i = 1
arr[] = {10, 30, 80, 90, 40, 50, 70} // We swap 80 and 30
在这里插入图片描述
j = 3 : Since arr[j] > pivot, do nothing // No change in i and arr[]
j = 4 : Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
i = 2
arr[] = {10, 30, 40, 90, 80, 50, 70} // 80 and 40 Swapped
在这里插入图片描述
j = 5 : Since arr[j] <= pivot, do i++ and swap arr[i] with arr[j]
i = 3
arr[] = {10, 30, 40, 50, 80, 90, 70} // 90 and 50 Swapped
在这里插入图片描述
We come out of loop because j is now equal to high-1.
Finally we place pivot at correct position by swapping arr[i+1] and arr[high] (or pivot)
arr[] = {10, 30, 40, 50, 70, 90, 80} // 80 and 70 Swapped
在这里插入图片描述
Now 70 is at its correct place. All elements smaller than 70 are before it and all elements greater than 70 are after it.
Since quick sort is a recursive function, we call the partition function again at left and right partitions
在这里插入图片描述
Again call function at right part and swap 80 and 90
在这里插入图片描述

代码实现

使用最后一个元素作为枢轴的快速排序的实现:

/* C++ implementation of QuickSort */
#include <bits/stdc++.h>
using namespace std;

// A utility function to swap two elements
void swap(int* a, int* b)
{
	int t = *a;
	*a = *b;
	*b = t;
}

/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
int partition (int arr[], int low, int high)
{
	int pivot = arr[high]; // pivot
	int i = (low - 1); // Index of smaller element and indicates the right position of pivot found so far

	for (int j = low; j <= high - 1; j++)
	{
		// If current element is smaller than the pivot
		if (arr[j] < pivot)
		{
			i++; // increment index of smaller element
			swap(&arr[i], &arr[j]);
		}
	}
	swap(&arr[i + 1], &arr[high]);
	return (i + 1);
}

/* The main function that implements QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		/* pi is partitioning index, arr[p] is now
		at right place */
		int pi = partition(arr, low, high);

		// Separately sort elements before
		// partition and after partition
		quickSort(arr, low, pi - 1);
		quickSort(arr, pi + 1, high);
	}
}

/* Function to print an array */
void printArray(int arr[], int size)
{
	int i;
	for (i = 0; i < size; i++)
		cout << arr[i] << " ";
	cout << endl;
}

// Driver Code
int main()
{
	int arr[] = {10, 7, 8, 9, 1, 5};
	int n = sizeof(arr) / sizeof(arr[0]);
	quickSort(arr, 0, n - 1);
	cout << "Sorted array: \n";
	printArray(arr, n);
	return 0;
}

// This code is contributed by rathbhupendra

使用第一个元素作为枢轴的快速排序的实现:

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

int partition(int arr[], int low, int high)
{
	int i = low;
	int j = high;
	int pivot = arr[low];
	while (i < j)
	{
		while (pivot >= arr[i])
			i++;
		while (pivot < arr[j])
			j--;
		if (i < j)
			swap(arr[i], arr[j]);
	}
	swap(arr[low], arr[j]);
	return j;
}

void quickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		int pivot = partition(arr, low, high);
		quickSort(arr, low, pivot - 1);
		quickSort(arr, pivot + 1, high);
	}
}

void printArray(int arr[], int size)
{
	for (int i = 0; i < size; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	int arr[] = {4, 2, 8, 3, 1, 5, 7,11,6};
	int size = sizeof(arr) / sizeof(int);
	cout<<"Before Sorting"<<endl;
	printArray(arr, size);
	quickSort(arr, 0, size - 1);
	cout<<"After Sorting"<<endl;
	printArray(arr, size);
	return 0;
}

时间复杂度分析

快速排序所花费的时间通常可以写成如下:

T ( n ) = T ( k ) + T ( n − k − 1 ) + θ ( n ) T(n) = T(k) + T(n-k-1) + \theta(n) T(n)=T(k)+T(nk1)+θ(n)

前两项用于两次递归调用,最后一项用于分区过程。 k 是小于枢轴的元素的数量。QuickSort 花费的时间取决于输入数组和分区策略。以下是三种情况:

最坏的情况:

最坏的情况发生在分区过程总是选择最大或最小元素作为枢轴时。如果我们考虑上面的分区策略,其中最后一个元素总是被选为枢轴,最坏的情况会发生在数组已经按升序或降序排序的情况下。以下是最坏情况的重现。

T ( n ) = T ( 0 ) + T ( n − 1 ) + θ ( n )   w h i c h   i s   e q u i v a l e n t   t o   T ( n ) = T ( n − 1 ) + θ ( n ) T(n) = T(0) + T(n-1) + \theta(n) \ which \ is \ equivalent \ to\ T(n) = T(n-1) + \theta(n) T(n)=T(0)+T(n1)+θ(n) which is equivalent to T(n)=T(n1)+θ(n)

上述递归的解是 θ ( n 2 ) \theta(n^2) θ(n2)

最好情况:

最好的情况发生在分区过程总是选择中间元素作为枢轴。以下是最佳情况的重现。

T ( n ) = 2 T ( n / 2 ) + θ ( n ) T(n) = 2T(n/2) + \theta(n) T(n)=2T(n/2)+θ(n)

上述递归的解是 θ ( n L o g n ) \theta(nLogn) θ(nLogn)

平均情况:

要进行平均情况分析,我们需要考虑数组的所有可能排列,并计算每个排列所花费的时间,这看起来并不容易。我们可以通过考虑分区将 O(n/9) 个元素放在一个集合中并将 O(9n/10) 个元素放在另一个集合中的情况来了解平均情况。以下是此案例的重现。

T ( n ) = T ( n / 9 ) + T ( 9 n / 10 ) + θ ( n ) T(n) = T(n/9) + T(9n/10) + \theta(n) T(n)=T(n/9)+T(9n/10)+θ(n)

上述递归的解是 θ ( n L o g n ) \theta(nLogn) θ(nLogn)

本文含有隐藏内容,请 开通VIP 后查看