6.快速排序
算法思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
算法描述
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]的值赋给A[i];
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]的值赋给A[j];
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
算法的实现
这部分详细的讲的话,我把它划分为三个部分
1.如何选基准值
对于这个基准值的叫法有很多,基准值,关键字,枢纽元,但需要理解它最主要的作用就是划分数集中大于他和小与他的值
选择的准确直接关系到它的排序时间
有些算法说明中直接选取第一个值,其实这种做法非常不可取,为什么?比如当排序一个数组,数组基本有序,且是逆序,这样就会将快速排序的时间花费和冒泡没什么区别,比较不可取,一种安全的做法是随机选值,除非随机选择器每次都产生最差的key(这种情况基本不可能),所以比较安全(也可以将数的序列打断,让他随机,比如使用java.util.Collections中的shuffle方法)
还有比较好的方法就是取首位值,末尾值和中心值里面的中值,(比如1.4.3.5.2,取1,3,2里面的2)据统计这种取值可以减少5%的运行时间
/**
*@title: GetMid
*@description: TODO
*@author: lvhong
*@date: 2019年1月2日 上午8:45:29
*@param array
*@param left
*@param right
*@return
*@throws: ??
*/
public int GetMid(int [] array , int left , int right) {
int mid = left + ((right-left )>>1);
if(array[left]<array[right]) {
if(array[mid]<array[left]) {
return left;
}
else if(array[mid]>array[right]) {
return left;
}
else {
return mid;
}
}
else {
if(array[mid]>array[left]) {
return left;
}
else if(array[mid]<array[right]) {
return right;
}
else {
return mid;
}
}
}
2.如何分区
JAVA中主要是有顺序的序列中可以区分为动基数和不动基数,其他诸如链表的结构中可以使用前后指针追赶的方式来分区
不动基数
原理就是两个指针一个从前,一个从后遍历,左边的大于基准值时,右边开始找小于基准值的,之后两个值交换,最后两个指针
相等时将基准值加到i=j处
图示说明(取第一个为基准值)
实现(取最后一个为基准值)
private static int parttion1(int[] array , int left, int right) {
int key = right;
//right--;
while(left < right)
{
while(left < right && array[left] <= array[key])
{
++left;
}
while(left < right && array[right] >= array[key])
{
--right;
}
swap(array,left,right);
}
swap(array,left,key);
key=left;
return key;
}
动基数
就是在挖坑(也可以理解为用一个中间变量交换),将基准值处的值拿出来,从左找大于基准值的指针,将该处内容填到基准指处,从右往左遍历,找比基准值大的值,将里面的值填到左边指针处的坑,循环直到左边指针大于等于右边指针
图示说明(以第一个为基准值)
实现(取最后一个为基准值)
private static int parttion2(int[] array , int left, int right) {
int key=right;
//int key=getMid(array,left,right);
int keyElem = array[right];//初始坑位 ,把数挖出来
while(left < right)
{
while(left < right && array[left] <= keyElem)
{
++left;
}
array[right]=array[left];
while(left < right && array[right] >= keyElem)
{
--right;
}
array[left]=array[right];
}
array[right]=keyElem;
key=right;
return key;
}
3.递归和非递归的实现,
递归主要是用来划分子区间,非递归时,可以使用一个栈保存区间
Diss:按照某大佬的说法,递归改为非递归时,首要考虑用栈实现,因为递归本身是一个压栈的过程
private static int[] quickSortNotR(int[] array,int left,int right)
{
Stack<Integer> s=new Stack<Integer>();
s.push(left);
s.push(right);//后入的right,所以要先拿right
while(!s.empty())//栈不为空
{
int high = s.pop();
int low = s.pop();
int index = parttion1(array,low,high);
if((index - 1) > low)//左子序列
{
s.push(low);
s.push(index - 1);
}
if((index+1)<high)//右子序列
{
s.push(index + 1);
s.push(high);
}
}
return array;
}
算法改进
思路就是在选基准值时可以取中值和在递归的数据较小时用插入排序可以有效优化运行时间
数据量在5~20之间都可以进一步优化,至于原因,可以参照插入排序和快排的比较
使用的所有源码,包括简单测试
package test32;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Stack;
/**
* @author lvhong
* @title: test.java
* @package test32
* @description: sort test
* @date: 2018年12月29日 上午9:23:25
* @version: V1.0
*/
public class test {
public static void main(String sgf[]) {
int[] ints = new int[2000];
// int[] ints1 = new int[10000];
// int[] ints2 = new int[10000];
// int[] ints3 = new int[10000];
int length=ints.length;
int i = 0;
for(i=0;i<length;i++) {
int temp =(int)(Math.random()*10000+1);
ints[i]=temp;
// ints1[i]=temp;
// ints2[i]=temp;
// ints3[i]=temp;
}
// int[] me= {1,3,5,2,4};
// int length=me.length;
long startTime = System.currentTimeMillis();//获取当前时间
int aftersort[] = quickSortNotR(ints,0,length-1);
long endTime = System.currentTimeMillis();//获取当前时间
System.out.println("快速程序运行时间:"+(endTime-startTime)+"ms");//两个时间相减 = 程序运行时间
// for(i=0;i<length;i++) {
// System.out.println(aftersort[i]+" ");
// }
}
private static int[] quickSort(int[] array, int low, int high) {
if(low<high) {
int key=parttion1(array,low, high);
//int key=parttion2(array,low, high);
quickSort(array, low,key-1);
quickSort(array, key+1,high);
}
return array;
}
private static int[] quickSort2(int[] array, int low, int high) {
if(low<high) {
//int key=parttion1(array,low, high);
int key=parttion2(array,low, high);
quickSort(array, low,key-1);
quickSort(array, key+1,high);
}
return array;
}
private static void quickSort3(int[] array, int low, int high) {
int m=15;
if(high<=low+m) {
//insertSort(array,low,high);
return ;
}
//int key=parttion1(array,low, high);
int key=parttion2(array,low, high);
quickSort(array, low,key-1);
quickSort(array, key+1,high);
}
private static int[] quickSortNotR(int[] array,int left,int right)
{
Stack<Integer> s=new Stack<Integer>();
s.push(left);
s.push(right);//后入的right,所以要先拿right
while(!s.empty())//栈不为空
{
int high = s.pop();
int low = s.pop();
int index = parttion1(array,low,high);
if((index - 1) > low)//左子序列
{
s.push(low);
s.push(index - 1);
}
if((index+1)<high)//右子序列
{
s.push(index + 1);
s.push(high);
}
}
return array;
}
/**
*@title: GetMid
*@description: TODO
*@author: lvhong
*@date: 2019年1月2日 上午8:45:29
*@param array
*@param left
*@param right
*@return
*@throws: ??
*/
// java.util.Collections.shuffle();
private static int getMid(int [] array , int left , int right) {
int mid = left + ((right-left )>>1);
if(array[left]<array[right]) {
if(array[mid]<array[left]) {
return left;
}
else if(array[mid]>array[right]) {
return left;
}
else {
return mid;
}
}
else {
if(array[mid]>array[left]) {
return left;
}
else if(array[mid]<array[right]) {
return right;
}
else {
return mid;
}
}
}
private static void swap(int[] array, int i, int j) {
if(i!=j) {
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
/**
*@title: parttion
*@description: 左右指针
*@author: lvhong
*@date: 2019年1月2日 上午9:44:12
*@param array
*@param left
*@param right
*@return
*@throws: ??
*/
private static int parttion1(int[] array , int left, int right) {
int mid=getMid(array,left,right);
int key = right;
swap(array,key,mid);
int keyElem=array[key];
while(left < right)
{
while(left < right && array[left] <= keyElem)
{
++left;
}
while(left < right && array[right] >= keyElem)
{
--right;
}
if(array[left] >= key) {//如果不加,在左边的值全小于key时会出错
swap(array,left,right);
}
}
swap(array,left,key);
key=left;
return key;
}
/**
*@title: parttion2
*@description: 坑位法
*@author: lvhong
*@date: 2019年1月2日 上午10:03:33
*@param array
*@param left
*@param right
*@return
*@throws: ??
*/
//13524 31245
private static int parttion2(int[] array , int left, int right) {
int mid=getMid(array,left,right);
int key=right;
swap(array,right,mid);
int keyElem = array[key];//初始坑位 ,把数挖出来
while(left < right)
{
while(left < right && array[left] <= keyElem)
{
++left;
}
array[right]=array[left];
while(left < right && array[right] >= keyElem)
{
--right;
}
array[left]=array[right];
}
array[right]=keyElem; //这里填的坑应该是左右指针相交时的值,left和right无所谓
key=right;
return key;
}
}
简单总结
时间复杂度 最坏O(n^2) 和冒泡顺序雷同 所以怎么选基准值非常关键
最好O(nlonn)
空间复杂度 O(nlogn)
不满足稳定条件