快速排序思想
分而治之的思想,在需要排序的数值中 选取一个主元,然后其他数值与这个主元进行对比,比主元小的放主元的左边,比主元大的放主元右边。之后对主元俩边的子集进行递归处理。这样每一次的主元都是到这个序列中正确的位置。不像简单排序一样,都是临时位置。
主元的选择
选取合适的主元可以提高效率。一般我们是选取序列中最左,中间,最右 三个位置的数值进行比较,选取大小中间的那个。然后最小放最左边,最大放最右边。
当每一次选取的主元刚好是中间的数值,算法效率最快,时间复杂度达到O(NLogN)
选好主元后,将主元放到最右边。这样子集就是Left到right-1的部分。
思路
选取主元后放在子集的最后,
选取俩个指针i,j
i指向子集最左边位置,j指向子集最右边位置。
i位置的数值与主元比较,小于主元时i++,不然就停下来
j位置的数值与主元比较,大于主元时j- -,不然停下来
都停下来时,i与j互换,一直到 i>j时,表示已经走完了,然后将i 与 主元 互换。主元就到达序列的正确位置。
会出现这样一种情况,当i,j都指向同一个数值时,根据这个数值与主元比较会让i或j再走一步,这样i与主元互换就正确了。
程序
package quicksort;
public class Quicksort {
public int pivot(int a[], int left, int right) {
int center = (left + right) / 2;
if (a[left] > a[center])
swap(a, left, center);
if (a[left] > a[right])
swap(a, left, right);
if (a[center] > a[right])
swap(a, center, right);
// 头,中,尾取中位数,并且排好序
swap(a, center, right ); // 将主元放到最右边的位置
return a[right];
}
// 互换方法
public void swap(int a[], int i,int j) {
int b;int c;
b = a[i];
c = a[j];
a[i] = c;
a[j] = b;
}
public void quicksort(int a[], int left, int right) {
if(left >= right)
return;
int temp = pivot(a, left, right);
int i = left ;
int j = right-1;
while(i < j){
while ( a[i] < temp && i <= j) { // 左边位置,当大于等于主元时,停下来准备做交换
i++;
}
while (a[j] > temp && i <= j) { // 右边位置,小于等于主元时,停下来准备做交换
j--;
}
if (i < j) {
swap(a, i, j);
} else
break;
}
swap(a, i, right);// 找到正确位置,与主元互换
quicksort(a, left, i - 1);// 进行递归
quicksort(a, i + 1, right);
}
public void QuickSort(int a[]) {
int left = 0;
int right = a.length - 1;
quicksort(a, left, right);
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
int a[] = {6,8,7,5,4,9,12,8,7,1,5 };
Quicksort test = new Quicksort();
test.QuickSort(a);
for (int i : a) {
System.out.print(i + " ");
}
}
}
思考
当子集划分小到一定程度时,用简单的插入排序会更快,因为快速排序主要是递归,可以设定一个阈值,当子集小于一个数时,可以使用插入排序,结束递归。
当遇到数值与主元相等的时候,要不要交换呢?
如果交换的话,考虑极端情况,序列中数大都相等。这样会做很多无谓的交换,但是最后i和j会停在了序列中间位置,这样子集划分会平均一半,这样为递归减小了时间复杂度。平均时间复杂度为NLogN。
不交换的话,上述的情况,这样i直接到了右端,子集划分不平衡,递归时间复杂度大,最坏情况 N²。
所以还是交换好,这样快速排序 是不稳定的。