快速排序是从冒泡排序演变而来,快速排序之所以快,是因为使用了分治法。
同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
快速排序是在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列的一边,比它晓得元素移动到数列的另一边,从而把数列拆解成两个部分。
在分治法的思想下,原数列在每一轮都被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止。
假如元素个数是n,那么平均情况下需要logn轮,因此快速排序的平均算法复杂度是O(nlogn),最坏情况是O(n^2)。
元素的交换:
1.选基准元素;2.数组分为大于和小于基准的两份。
实现方法:(1).双边循环法;(2).单边循环法。
首先选定基准元素pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素。
第1次循环:从right指针开始,让指针指向元素和基准元素做比较。如果大于或等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。
轮到left指针行动,让指针所指向的元素和基准元素做比较,如果小于或等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。
1.双边循环法:
using System;
namespace CSharpTest01
{
class Program
{
public static void QuickSort(int[] arr, int startIndex, int endIndex)
{
// 递归结束条件:startIndex大于或等于endIndex时
if (startIndex >= endIndex)
{
return;
}
// 得到基准元素位置
int pivotIndex =
}
/// <summary>
/// 分治(双边循环法)
/// </summary>
private static int partition(int[] arr, int startIndex, int endIndex)
{
// 取第1个位置(也可以选择随机位置)的元素作为基准元素
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
while (left != right)
{
// 控制right指针比较并左移
while (left < right && arr[right] > pivot)
{
right--;
}
// 控制left指针比较并右移
while (left < right && arr[left] <= pivot)
{
left++;
}
// 交换left和right指针所指向的元素
if (left < right)
{
int p = arr[left];
arr[left] = arr[right];
arr[right] = p;
}
}
}
static void Main(string[] args)
{
Console.ReadLine();
}
}
}
上述代码中,QuickSort通过递归的方式,实现了分而治之的思想。
Partition实现元素交换,让元素依据自身大小,分别交换到基准的左右。这里的交换方式是双边循环法。
2.单边循环法:
开始和双边循环法相似,首先选定基准元素pivot。同时,设置一个mark指针指向数列起始位置,这个mark指针代表小于基准元素的区域边界。
接下来,从基准元素的下一个位置开始遍历数组。如果遍历到的元素大于基准元素,就继续往后遍历 。
如果遍历到的元素小于基准元素,则需要做两件事:第一,把mark指针右移1位,因为小于pivot的区域边界增大了1;第二,让最新遍历到的元素和mark指针所在的元素交换位置,因为最新遍历的元素属于小于pivot的区域。
随后,让3和mark指针所在位置的元素交换。
按照这个思路,继续遍历。
using System;
using System.Collections;
namespace CSharpTest01
{
class Program
{
public static void QuickSort(int[] arr, int startIndex, int endIndex)
{
// 递归结束条件:startIndex大于或等于endIndex时
if (startIndex >= endIndex)
{
return;
}
// 得到基准元素
int pivotIndex = Partition(arr, startIndex, endIndex);
// 根据基准元素,分成两部分进行递归排序
QuickSort(arr, startIndex, pivotIndex - 1);
QuickSort(arr, pivotIndex + 1, endIndex);
}
/// <summary>
/// 分治(单边循环法)
/// </summary>
private static int Partition(int[] arr, int startIndex, int endIndex)
{
// 取第1个位置(也可以是随机位置)的元素作为基准元素
int pivot = arr[startIndex];
int mark = startIndex;
for (int i = startIndex+1; i <= endIndex; i++)
{
if (arr[i] < pivot)
{
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
arr[startIndex] = arr[mark];
arr[mark] = pivot;
return mark;
}
static void Main(string[] args)
{
int[] arr = new int[] { 4, 4, 6, 5, 3, 2, 8, 1 };
QuickSort(arr, 0, arr.Length - 1);
foreach (var item in arr)
{
Console.WriteLine(item);
}
}
}
}
3.非递归实现:
把原本的递归实现转化成一个栈的实现,在栈中存储每一次方法调用的参数。
using System;
using System.Collections;
using System.Collections.Generic;
namespace CSharpTest01
{
class Program
{
public static void QuickSort(int[] arr, int startIndex, int endIndex)
{
// 用一个集合栈来代替递归的函数栈
Stack<Dictionary<string, int>> quickSortStack = new Stack<Dictionary<string, int>>();
// 整个数列的起止下标,以哈希的形式入栈。
Dictionary<string, int> rootParam = new Dictionary<string, int>();
rootParam.Add("startIndex", startIndex);
rootParam.Add("endIndex", endIndex);
quickSortStack.Push(rootParam);
// 循环结束条件:栈为空时
while (quickSortStack.Count > 0)
{
// 栈顶元素出栈,得到起止下标
Dictionary<string, int> param = quickSortStack.Pop();
// 得到基准元素位置
int start = 0;
param.TryGetValue("startIndex", out start);
int end = 0;
param.TryGetValue("endIndex", out end);
int pivotIndex = Partition(arr, start, end);
// 根据基准元素分成两部分,把每一部分的起止下标入栈
if (start < pivotIndex - 1)
{
Dictionary<string, int> leftParam = new Dictionary<string, int>();
leftParam.Add("startIndex", start);
leftParam.Add("endIndex", pivotIndex-1);
quickSortStack.Push(leftParam);
}
if (pivotIndex + 1 < end)
{
Dictionary<string, int> rightParam = new Dictionary<string, int>();
rightParam.Add("startIndex", pivotIndex + 1);
rightParam.Add("endIndex", end);
quickSortStack.Push(rightParam);
}
}
}
/// <summary>
/// 分治(单边循环法)
/// </summary>
private static int Partition(int[] arr, int startIndex, int endIndex)
{
// 取第1个位置(也可以是随机位置)的元素作为基准元素
int pivot = arr[startIndex];
int mark = startIndex;
for (int i = startIndex+1; i <= endIndex; i++)
{
if (arr[i] < pivot)
{
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
arr[startIndex] = arr[mark];
arr[mark] = pivot;
return mark;
}
static void Main(string[] args)
{
int[] arr = new int[] { 4, 4, 6, 5, 3, 2, 8, 1 };
QuickSort(arr, 0, arr.Length - 1);
foreach (var item in arr)
{
Console.WriteLine(item);
}
}
}
}
每一次循环,都会让栈顶元素出栈,通过partition方法进行分治,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经结束,退出循环。