八大排序算法之六:快速排序(以中轴值作为排序基准)
算法思想
快速排序是对冒泡排序的一种改进:
通过一趟排序,将要排序的数据分割成独立的2部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法
对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到整个数据变成有序序列。
快排(以中轴数作为基准)的实现:
-
对序列进行处理的操作,
- 目的是:依据选择的基准(这里采用的是中轴值pivot),将序列切分称为两个子序列(因为是依照值进行切分的,所以两个子序列大小不一定相等)
- 具体的实现方式:
① 定义两个指针l、r,其中,l的初值为left,r的初值为right ;
② 确定此次划分的中轴值pivot = arr[(left +right)/2] ;
③ 当l < r的时候进行while循环,来进行对序列的按值划分:
<1>对l指针,从左向右遍历,直至遇到一个数,使得arr[l]<pivot不成立;
<2>对r指针,从右向左遍历,直至遇到一个数,使得arr[r]<pivot不成立;
此时,如果l < r,就交换l、r对应的元素的值;否则,退出交换的循环【 如果l == r,表明:此时l(也是r)指针的左右两侧的子序列已经满足:左侧的子序列中的值,都小于等于pivot;右侧子序列中的值,都大于等于pivot。】
== >直到退出进行元素交换的循环,完成了:基于中轴值pivot的,将一个序列按照元素值,划分为两个子序列的操作。
----在这个过程中,需要注意的是:
如果在某一次交换l、r对应的元素值之后,发现arr[l] == pivot,也就是进行交换之前arr[r] == pivot,应该将r指针向前移动一位;同理,如果在某一次交换l、r对应的元素值之后,发现arr[r] == pivot,也就是进行交换之前arr[l] == pivot,应该将l指针向后移动一位;
=>这是因为,交换之前该位的值 == pivot,(那么跟它进行数据交换的位的值就 == pivot).为防止特殊情况(交换之后该位的值仍==pivot),出现死循环,应该将对应的指针,沿着遍历的方向移动一位。
=>这一点比较难理解,但是一定要注意,不然的话,在运行过程中,很可能会出现死循环(当发生两个等于pivot的值互换的时候)!!!!!!
-
在对序列的处理完成后,就开始进入递归模块。 在这里需要注意的是:因为退出前边的处理模块的外层while循环,对应两种情况: 1)l>r 2)l==r
- 但是,进行递归操作,必须保证:向左递归 和 向右递归到的时候,他们的递归边界不能重合(否则容易发生栈溢出)
- 这是因为l==r退出前边的处理模块的外层while循环时,在进行递归之前,需要让l和r两个指针的位置错开。
—如果这么看不好理解的话,可以看代码实现,可能会比较好理解!!!
算法实现
// 实现快速排序的方法
public static void quickSort(int[] arr, int left, int right) {
int l = left;
int r = right;
int pivot = arr[(left + right) / 2];// 排序的中轴值
int temp = 0;//用于交换对应元素的临时变量
//是进行递归前的处理操作
while(l < r) {
//从左向右找到第一个大于等于pivot的值
while(arr[l] < pivot) {
l += 1;
}
//从右向左找到第一个小于等于pivot的值
while(arr[r] > pivot) {
r -= 1;
}
// System.out.println("l="+l+",r="+r);
//判断找到的两个值是否发生交叉或者重叠
if( l >= r ) {
break;
}
//交换对应的两个元素的值
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
// System.out.println("l="+l+",r="+r+",arr[l]="+arr[l]+",arr[r]="+arr[r]);
//判断发生交换的数,是否等于中轴值pivot,避免在下一轮循环时,在两个小的while循环处,形成死循环
if(arr[l] == pivot) {
r -= 1;
}
if(arr[r] == pivot) {
l += 1;
}
// System.out.println("l="+l+",r="+r);
}
// System.out.println("排序的pivot值为:"+ pivot +""
// + ",left="+left+",right="+right
// + ",l="+l+",r="+r
// + "排序的结果为:"+Arrays.toString(arr));
/*
* 开始进行递归操作
* 分为两个方向:向左递归,向右递归
* ----
* 但是,在进行递归操作之前,应该对于走出上面的外层while循环的情况进行判断:是满足l==r结束的循环;还是满足l>r结束的循环
* =>如果是l==r,标明结束循环的地方,正好是元素值==pivot的地方。
* 在下一轮的递归中,不应该取本次的中轴值pivot作为序列的边界上的值。
* 应该将l、r两个指针,与pivot的下标值错开:
* l += 1;r -= 1;
* =>如果是l>r,则可以直接进行递归调用,进入下一轮的递归操作中去
*/
if(l==r) {
l += 1;
r -= 1;
}
//进行递归调用
//向左递归
if(left < r) {
quickSort(arr, left, r);
}
//向右递归
if(l < right) {
quickSort(arr, l, right);
}
}
其实,之前总结过:以当前序列的第一个元素作为基准数进行排序的情况,链接如下:
快速排序----以第一个元素作为基准值
遇到难的东西,不要慌,进下心来,一点点理解透彻,是可以解决的!!!