package quick_sort;
import static java.lang.System.out;
public class Sort
{
public static<T extends Comparable<T>> void quickSort(T[] array,int p,int r)
{
if(p < r)
{
int q = partition(array,p,r);
quickSort(array,p,q-1);
quickSort(array,q+1,r);
}
}
private static<T extends Comparable<T>> int partition(T[] array,int p,int r)
{
T x = array[r];
int i = p-1;
for(int j = p;j<r; ++j)
{
if(array[j].compareTo(x) <= 0)
{
i++;
swap(array,i,j);
}
}
swap(array,i+1,r);
return i+1;
}
private static<T extends Comparable<T>> void swap(T[] array,int i, int j)
{
T tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
private static void disprint(Integer[] intArray, Character[] charArray)
{
for(int a: intArray)
out.print(a+"\t");
out.println();
for(char a: charArray)
out.print(a+"\t");
}
public static void main(String[] args)
{
Integer[] intArray = {2,3,4,1,7,45,21,12};
Character[] charArray = {'x','s','w','o','q','a','p','c'};
out.println("未快速排序前:");
disprint(intArray, charArray);
int r = intArray.length-1;
quickSort(intArray,0,r);
int r1 = charArray.length-1;
quickSort(charArray,0,r1);
out.println("\n快速排序后:");
disprint(intArray, charArray);
}
}
运行截图:
分解:数组A[p,r]被划分两个可能为空的子数组A[p,q-1]和A[q+1,r],使A[p,q-1]上的每个元素都小于等于A[q],且A[q]小于等于A[q+1,r]
上的每个元素,其中A[q]作为划分整个数组的轴值(pivot).
解决:通过递归调用快速排序,直到子数组只含有0个或一个元素,对子数组A[p,q-1]和A[q+1,r]排序.
合并:因为子数组都是原址排序,所以不需要合并操作,数组A[p,r]已经有序。
伪码:r为数组元素的个数,根据编程语言的数组特性,一般默认第一调用时传递参数(A,0,A.length-1).
QUICK-SORT(A,p,r)
if p < r
q = PARITION(A,p,r)
QUICK-SORT(A,p,q-1)
QUICK-SORT(A,q+1,r)
PARTITION(A,p,r)
x = A[r]
i = p-1
for j=p to r-1
if A[j] <= x
i++
exchange(A[i],A[j])
exchanage(A[i+1],A[r])
return i+1
验证算法正确性:
p i j |r
0 0 0 0 0 0 0 0 0 0 0 |0
算法在执行过程中必须维持这四个部分的条件:
[p,i]中的元素满足<=x
[i,j]中的元素满足>=x
[j,r)中的元素为任意大小
r为主轴
对任意for循环开始迭代,任意下标k必须遵循这几个原则循环不变量原则:
1、若k<q,则A[k] <= x.
2、若i+1<= k <=j-1,则A[k] >= x
3、若k=r,则A[k]==x
总的来说分位三个步骤:
迭代前的初始化->迭代中的保持循环不变量的稳定->迭代的终止条件
Ω Θ
最坏情况:
当划分产生的两个子问题分别包含了n-1和0个元素,不平衡的时间复杂度划分操作Θ(n),则T(n)=T(n-1)+T(O)+Θ(n)=T(n-1)+Θ(n)
最好情况划分:
两个子问题平均为n/2个元素,T(n)=2T(n/2)+Θ(n)
平衡的划分:
其实任何一种常数比例的划分都会产生深度为Θ(lgn)的递归树,其中每层的时间代价都是O(n),一次只要划分按照常数比例,算法的运行时间都是O(nlgn)--递归树可以证明
对于递归式的分解这里我们必须知道
1,代入法
2,递归树
3,主方法
这里先介绍下主方法求解递归式:
主方法为下面这种形式提供了情况下的解决方案:
T(n) = aT(n/b) + f(n)
a>=1和b>1都是常数,F(n)是渐进正函数,上式描述的是一种这样的算法运行时间:
它将规模为n的问题分解为a个子问题,每个子问题的规模n/b,a个子问题递归的进行求解每个问题的花费时间为T(n/b),f(n)包含问题的分解和子问题的合并的代价。
下面就是主定理:
令a>=1和b>1为常数,f(n)是一个函数,T(n)是定义在非负整数上的递归式: T(n) = aT(n/b) + f(n)
1,若对某个常数m>0有f(n)=O(n^logb^(a-m)),则T(n) = Θ(nlogb^a)
2,若f(n) = Θ(nlogb^a),则T(n)= Θ(logb^a lgn)
3,若对某个常数m>0有f(n)=Ω(n^logb^(a+m)),且对某个常数有c<1和所有足够大的n有af(n/b)<=cf(n),则T(n)=Θ(f(n))
其实简单的理解就是当在数量级上
有f(n)<n^logb^a时,就是情况一
有f(n)=n^logb^a时,就是情况二
有f(n)>n^logb^a时,就是情况三
所以
T(n) = 2T(n/2) + Θ(n) 有
a=2,b=2, f(n) = Θ(n), n^logb^a = n.由于 f(n)=Θ(n).
所以应用情况二: T(n) = Θ(nlgn)