在划分阶段治:快速排序
在合并阶段治:合并排序
算法运行时间的递推公式:T(n)=aT(n/b)+f(n)
一个规模为n的实例可以划分为b个规模为n/b的实例,其中a个实例是需要求解的。f(n)是一个函数,表示将问题分解成小问题和将结果合并起来所消耗的时间。(比如:a=b=2,f(n)=1)
具体解析参考:
mergesort(A[0,n-1],first,last)
//递归调用mergesort对数组A[0,n-1]进行排序
//输入:无序数组A[0,n-1],first数组起点,last数组终点
//输出:非降序数组A[0,n-1]
int mid = (first + last)/2 //初始时mid = (0 + n-1)/2
mergesort(A[],0,mid) //①左子序
mergesort(A[],mid+1,n-1) //①右子序
merge(A[],first,last) //②合并
递推公式:n>1时,T(n)=2T(n/2)+f(n);T(1)=0
其中最坏情况下f(n)=n-1,得到:
n>1时,T w w (n)=2T(n/2)+n-1;T w w (1)=0
注意这里的空间复杂度是:n
有的书上是在merge()合并有序数列时分配临时数组,但是过多的new操作会非常费时。因此作了下小小的变化。只在test()中new一个临时数组,后面的操作都共用这一个临时数组。
public class Main {
public static void main(String[] args) {
test();
}
/**
* 递归合并排序
* @param array
* @param first
* @param last
* @param temp 辅助数组
*/
public static void mergesort(int array[], int first, int last, int[] temp) {
if(first < last){
int mid = (first + last)/2;
mergesort(array, first, mid, temp);//左子列排序
mergesort(array, mid +1, last, temp);//右子列排序
merge(array, first, last, mid, temp);//二路合并
}
}
/**
* 将两个有序数组合并为一个有序数组
* @param array
* @param first
* @param last
* @param mid
* @param temp 辅助数组
*/
public static void merge(int[] array, int first, int last, int mid, int[]temp) {
int p = first;
int q = mid + 1;
int n = mid;
int m = last;
int k = 0;
while(p<=n && q<=m){
if(array[p] <= array[q]){
temp[k++] = array[p++];
}else {
temp[k++] = array[q++];
}
}
while(p <= n){
temp[k++] = array[p++];
}
while(q <= m){
temp[k++] = array[q++];
}
//temp赋值到array中,此时k代表temp有多少个有效元素
for(int i=0; i/**
* 测试用例
*/
public static void test() {
//int[] array = {1,2,3,4,5};
//int[] array = {5,4,3,2,1};
int[] array = {8,5,3,1,1,7,2,5,9,4};
int n = array.length;
//注:如果辅助数组在merge中,则每次合并都要new出一个
int[] temp = new int[n];
mergesort(array, 0, n-1, temp);
for(int i=0; i" ");
}
}
}
quicksort(A[l,r])
//递归调用quicksort对数组A[l,r]进行快速排序
//输入:数组A[0,n-1]的子数组A[l,r],由左右下标l和r定义
//输出:非降序排列的子数组A[l,r]
if(r{
s<——Partion(A[l,r],p)//①s是分裂的位置
quicksort(A[l,s-1])//②
quicksort(A[s+1,r])//②
}
——>Partion()中是如何排序的呢?
不同的Partion()有不同的方法,这里讨论两种快速排序的方法。
根据《算法设计与分析基础》中的伪代码。
HoarePartion(A[l,r])
//以第一个元素作为中轴
//输入:数组A[0,n-1]的子数组A[l,r],由左右下标l和r定义
//输出:A[l,r]的一个划分,分裂点的位置作为返回值
p<——A[l]
i<——l+1
j<——r
repeat
repeat i<——i+1 until A[i]>=p
repeat j<——j-1 until A[j]<=p
swap(A[i],A[j])
until i>=j //分裂点条件
swap(A[i],A[j])//撤销最后一次交换
swap(A[l],A[j])//分裂点元素交换,完成一次划分
return j
——>思想:
分别从子数组的两边进行扫描(除中轴点p,从第二个元素开始左到右扫描用指针i表示;从右到左扫面用指针j表示),当遇到A[i]大于等于中轴的元素,且遇到A[j]小于等于中轴的元素,则暂停扫描。
——>为什么需要等于呢?
当遇到相同的元素时,可以是数组分的更平均,便于减小分治问题规模。
——>i的扫描会可能会越过子数组的边界?
需要对i检查下标越界的可能性。而j不会越界,因为有中轴界限。
——>所有?
扫描暂停条件(注意与递归停止条件:l>r区别)是:A[i]大于等于中轴元素,A[j]小于等于中轴元素。接下来分为三种情况处理:
这里的2和3可以结合起来,只有i≥j,就交换中轴和A[j]的位置。——>递归停止条件,在代码中表示跳出循环。
public class Main {
public static void main(String[] args) {
test();
}
/**
* 霍尔快速排序
* @param A
* @param l
* @param r
*/
public static void quicksort(int[] A,int l,int r) {
if(l < r){
int p = A[l];//轴点元素
int i = l+1;
int j = r;
//不能写成while(i<=j)。注意i=j的情况,1、1时失效。
//因为,i和j都指向第二个1,造成死循环。
while(true){
//i作为指针从左到右扫描,且不能超过j
while(A[i] < p){
i++;
if(i >= r){
break;
}
}
//j作为指针从右到左扫描
while(A[j] > p){
j--;
}
if(i < j){
swap(A, i, j);
i++;
j--;
}else {
break;
}
}
//分裂点条件
if(i >= j){
//j作为分裂点,A[j]与轴点元素交换
swap(A, l, j);
quicksort(A, l, j-1);
quicksort(A, j+1, r);
}
}
}
/**
* 交换数组中的元素
*/
public static void swap(int A[], int i, int j) {
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
/**
* 测试用例
*/
public static void test() {
//int[] array = {1,2,3,4,5};
//int[] array = {5,4,3,2,1};
//int[] array = {1,1};
int[] array = {8,5,3,1,1,7,2,5,9,4};
int n = array.length;
quicksort(array, 0, n-1);
for(int i=0; i" ");
}
}
}
递推公式:n>1时,T(n)=2T(n/2)+f(n);T(1)=0
最好情况:f(n)=n,n>1时,T b b (n)=2T(n/2)+n-1;T b b (1)=0
最坏情况:就是已排好的升序。
T w w (n)=(n+1)+n+…+3=(n+1)(n+2)/2-3=Θ(n 2 2 )
平均情况:
T a a (n)=1.39nlog 2 2 n
1 quicksort(A, lo, hi)
2 if lo < hi
3 p = partition(A, lo, hi)
4 quicksort(A, lo, p - 1)
5 quicksort(A, p + 1, hi)
6
7 partition(A, lo, hi)
8 pivot = A[hi]
9 i = lo //place for swapping
10 for j = lo to hi - 1
11 if A[j] <= pivot
12 swap A[i] with A[j]
13 i = i + 1
14 swap A[i] with A[hi]
15 return i
——>思想:
首先选择表头作为中间元素temp。然后,从j开始扫描,遇到小于temp的停止扫描,将A[i](此时的i在中间元素位置,并保存在temp中)与A[j]交换,然后i++。接着,从i开始扫描,遇到大于temp的停止扫描,将A[j]与A[i]交换,然后j- -。以此类推,直到i与j交叉或相遇,将temp赋值到A[i]中。
public static void quicksort_general(int[] A,int l,int r){
if (l < r)
{
int i = l;
int j = r;
int p = A[l];
while (i < j)
{
//i
while(i < j && A[j] >= p) // 从右向左找第一个小于x的数
j--;
if(i < j)
A[i++] = A[j];
while(i < j && A[i] < p) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
A[j--] = A[i];
}
A[i] = p;
quicksort_general(A, l, i-1);
quicksort_general(A, i+1, r);
}
}