以前也不少次写快排算法,但很久没用,基本快忘记是怎么一回事了,正好最近正在学习相关知识,遂决定把之前快忘记的东西重新捡回来。
快速排序(Quicksort)是对冒泡排序的改进版,基于分治技术的重要排序算法,也是实际工作中比较常用的一种优秀的排序算法,速度快、效率高。
快速排序的分治策略:
(1)划分:选定一个记录作为轴值,以轴值为基准将整个序列划分为两个子序列ri….ri-1和ri+1….rn 轴值的位置i在划分的过程中确定,并且前一个子序列中记录的值均小于或等于轴值,后一个子序列中记录的值均大于或等于轴值。
(2)求解子问题:分别对划分后的每一个子序列递归处理。
(3)合并:由于对子序列ri….ri-1和ri+1….rn的排序是就地进行的,所以合并不需要执行任何操作。
快速排序的基本思想:
通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分关键字都小,然后分别对这两部分继续进行递归调用函数本身,进行排序,直到整个序列有序。
在快速排序中,轴值的选择应该遵循平衡子问题的原则,使划分后的两个子序列的长度尽量相同,这是决定快速排序算法时间性能的关键。
这里我们假设以第一个记录作为轴值,对待排序序列进行划分的过程为:
1、初识化:取第一个记录作为基准,设置两个参数i, j分别用来指示将要与基准记录进行比较的左侧记录位置和右侧记录位置,也就是本次划分的区间。
2、右侧扫描过程:将基准记录与j指向的记录进行比较,如果j指向记录的关键码大,则j前移一个记录位置(即j–)。重复右侧扫描过程,直到右侧的记录小(即反序),若i < j,则将基准记录与j指向的记录进行交换。
3、左侧扫描过程:将基准记录与i指向的记录进行比较,如果i指向记录的关键码小,则i后移一个记录位置(i++)。重复左侧扫描过程,直到左侧的记录大,若i<(j)则将基准记录与i指向的记录交换。
4、重复2,3步骤,直到i与j指向同一个位置,也就是基准记录的最终的位置。
如图所示:
JAVA实现快排
public class QuictSort{
public static void main(String[] args)
{
int nums = 0;
int[] a = {25,13,51,7,30,18,29};
System.out.print("排序前的序列:");
for (int t : a)
{
System.out.print(t + "\t");
}
System.out.println();
quickSort(a,0, a.length - 1);
System.out.print("排序后的序列:");
for (int t : a)
{
System.out.print(t + "\t");
}
}
//一次划分
static int partition(int[] a,int start, int end)
{
int i = start;
int j = end;
int temp;
while(i < j)
{
while((i < j) && (a[i] >= a[j]))
{//将较大记录交换后面
//在右侧寻找比轴值记录位置数小的数
if(i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
j--;
}
while((i <j) && (a[i] >= a[j]))
{//将较小记录交换到前面
//在左侧寻找比轴值记录位置数大的数
if(i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
i++;
}
if(i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
return i; //i为轴值记录的最终位置
}
//快速排序
static void quickSort(int[] a, int start, int end)
{
if(start >= end || a == null)
{
return;
}
//调用第一次划分函数,进行问题分解,keyPpartition是轴值在序列中的位置
int keyPpartition = partition(a, start, end);
//递归对轴值左侧子序列进行快速排序
quickSort(a, start, keyPpartition -1);
//递归对轴值右侧子序列进行快速排序
quickSort(a, keyPpartition + 1, end);
}
}
Python实现快排
#!/usr/bin/python
#coding=utf-8
def partition(listNum, start, end) :
#一次划分
i = start
j = end
while i < j :
while i < j and listNum[i] < listNum[j] :
#在右侧寻找第一个比轴值小的数
j -= 1
if i < j :
tmp = listNum[i]
listNum[i] = listNum[j]
listNum[j] = tmp
i += 1
while i < j and listNum[i] < listNum[j] :
#左侧寻找第一个比轴值大的数
i += 1
if i < j :
tmp = listNum[i]
listNum[i] = listNum[j]
listNum[j] = tmp
j -= 1
return i #i为轴值记录的最终位置
def quickSort(listNum, start, end) :
if start < end :
#调用partition函数,进行划分,寻找轴值的位置
keyPpartition = partition(listNum, start, end)
#对左侧调用函数本身,递归进行快速排序
quickSort(listNum, start, keyPpartition - 1)
#对右侧调用函数本身,递归进行快速排序
quickSort(listNum, keyPpartition + 1, end)
def main() :
listNum = [25,13,51,7,30,18,29]
listLen = len(listNum)
print "排序前的序列:",
for i in listNum :
print i,
quickSort(listNum, 0, listLen - 1)
print "\n排序后的序列:",
for i in listNum :
print i,
#*******************主函数开始*********************
main()
执行结果:
排序前的序列:25 13 51 7 30 18 29
排序后的序列:7 13 18 25 29 30 51
在最好的情况下,每次划分定位一个记录后,该记录的左侧子序列与右侧子序列的长度相同。在具有n个记录的序列中,一次划分需要对整个待划分序列扫描一遍,则所需要时间为O(n)。设T(t)是对n个记录的序列进行排序的时间,每次划分后,正好待划分区间划分为长度相等的两个子序列,就会有:
因此,这快排的时间复杂度为O(nlog2n)。
在最坏的情况下,待排序列记录序列正序或逆序,每一次划分,得到的子序列都只比上一次划分少一个记录,此时,就必须经过n - 1次递归调用才能把所有记录定位一次,而且第i趟划分需要经过n - i次关键码的比较才能找到第i个记录的基准位置,因此总的比较次数为:
移动的次数小于等于比较次数,由此可以得出最坏情况下,时间复杂度为O(n2)。
计算一下平均时间复杂度,不难得出为O(nlog2n)。空间复杂度为O(log2n)。