算法导论实践——快速排序
在学习算法导论之前,一直对快速排序有很大的畏惧感。还记得当时教我们数据结构的老师就硬给我们讲一堆算法,其中快速排序是真的叫我头疼。当时的感觉就是 我只看到了第一层,老师却在第五层
理论
- 分解:依据一个关键值(Pivot),把原数组分解成为两个子数组。其中一个子数组全部元素小于等于Pivot(后文称小数组)在Pivot前面;另外一个全部大于等于Pivot(后文称大数组),在Pivot后面。
- 解决:对两个子数组进行递归操作。
- 合并:因为我们是对原本的数组进行排序,(所谓原址排序),所以并不需要合并操作。
其中,分解操作很重要,算是算法的核心,算法导论给了我们一张图来诠释分解操作,我认为这幅图真的非常好。
下面,来诠释一下变量,自然就很好理解了。
- p,r :分别为数组的最小下标和最大下标
- 选择Pivot为A[r],也就是最后一个元素,图中为4
- i:小数组的最大下标,可以发现 i - p + 1 就是小数组的长度,因此,我把它称为一种小数组的相对长度
- j:大数组的最大下标,j - i - 1 就是大数组的长度,同理,我把它称为大数组的相对长度
容易发现,浅色区域的值都小于Pivot,深色区域的值都大于Pivot,最后,将Pivot与A[i+1]换一个位置,就能够知道数组该从哪个位置划分为两个数组了。
下面直接上代码。
C# 实现
FastSortUtil
using System;
using System.Collections.Generic;
using System.Text;
namespace Sort
{
public static class FastSortUtil
{
//快排函数
public static void FastSort(int [] A, int p, int r)
{
if (p < r)
{
int q = Partition(A, p, r); //得到Pivot所在位置下标
FastSort(A, p, q - 1);//对小数组递归快排
FastSort(A, q + 1, r);//对大数组递归快排
}
}
//分解步骤
private static int Partition(int[] A, int p, int r)
{
int pivot = A[r];//将数组最后一个元素作为Pivot
int i = p - 1;//i作为小数组最大下标
for(int j = p; j <= r - 1; j++)//j作为大数组最大下标
{
//发现A[j]不满足大数组条件,说明A[j]应该属于小数组
if (A[j] <= pivot)
{
i++;//小数组最大下标(相对长度)+1,用于承载A[j]。
Swap(A, i, j);//交换A[i]和A[j]
}
}
Swap(A, i + 1, r);//最后交换Pivot和A[i+1]
return i + 1;//返回Pivot当前的位置下标
}
//交换元素
private static void Swap(int[]A,int p, int q)
{
var mid = A[p];
A[p] = A[q];
A[q] = mid;
}
//输出结果
public static void GetResult(int[] A)
{
for(int i = 0; i < A.Length; i++)
{
Console.Write(A[i]);
Console.Write(" ");
}
}
}
}
Program
using System;
using Sort;
namespace Sort
{
class Program
{
static void Main(string[] args)
{
//在主函数调用即可
int[] A = { 1, 5, 6, 7, 1, 2, 3, 4 };
FastSortUtil.FastSort(A, 0, A.Length - 1);
FastSortUtil.GetResult(A);
}
}
}