一,单边循环法
上一节,我们学习到双边循环法,找到基准元素,与左右两边比较大小,将数列分成两部分。
此节,我们一起来学习什么是单边循环法。
双边循环法虽然直观,但是代码实现相对繁琐。而单边循环法,从名字来看就知道只从数组的一边对元素进行遍历和交换。同样给出以下数列:
4 7 3 5 6 2 8 1
首先,同样也是选择一个基准元素,并设置一个mark指针指向数列的开始位置,这个mark指针代表小于基准元素的区域边界。
基准元素:4
1,接着从基准元素的下一个位置开始遍历数组。
2,如果遍历到的元素大于基准元素,就继续往后遍历。
3,如果遍历到的元素小于基准元素,则需要进行两步:一,把mark指针右移1位,因为小于基准元素的区域边界增大了1。二,让最新遍历到的元素和指针所在位置的元素交换位置,因为最新遍历的元素归属于小于基准元素的区域。
上述数列的步骤:
1,首先遍历到元素7,7>4,所以继续遍历。
2,遍历到元素3,3<4,所以mark指针(突然想到mark老师),右移1位。
3,让元素3和mark指针所在位置的元素交换,因为元素3归属于小于基准元素的区域。
4,继续遍历,5>4。
5,6>4,继续遍历。
6,2<4,mark指针右移,元素2和mark指针所在位置的元素交换,因为元素2归属与小于基准元素的区域。
7,8>4继续遍历。
8,1<4,mark指针右移,元素1和mark指针所在位置的元素交换,因为元素1归属与小于基准元素的区域。
9,最后把基准元素交换到mark指针所在的位置,本轮就结束了。
public static void quickSort(int[] arr, int startIndex, int endIndex) {
//递归结束条件,,startindex大于或等于endindex时
if (startIndex >= endIndex) {
return;
}
//得到基准元素
int pivotIndex = parttion(arr, startIndex, endIndex);
//根据基准元素,分成两部分进行递归排序。
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
/**
* 分治单边循环法
*
* @param arr 待交换的数组
* @param startIndex 起始下标
* @param endIndex 结束下标
* @return
*/
private static int parttion(int[] arr, int startIndex, int endIndex) {
//取第一个位置(也可以随机取)的元素作为基准元素、
int piovot = arr[startIndex];
int mark = startIndex;
for (int i = startIndex + 1; i <= endIndex; i++) {
if (arr[i] < piovot) {
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
//piovt和指针重合点交换
arr[startIndex] = arr[mark];
arr[mark] = piovot;
return mark;
}
public static void main(String[] args) {
int[] arr = new int[]{4, 4, 6, 5, 3, 2, 8, 1};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
结果:
可以看出单边循环法使用一个for循环就搞定,是不是比双边循环要简单的多?
二,非递归实现快速排序
双边循环法和单边循环法都是以递归为基础,快速排序也可以以非递归的方式来实现。
从空间复杂度的学习中我们知道代码的调用本身就是一个方法调用栈,有出栈和入栈操作。如果把原本的递归实现转化成一个栈的实现,在栈中存储每一次方法调用的参数。
如下代码:
private static void quickSort(int[] arr, int startIndex, int endIndex) {
//用一个集合栈来代替递归的函数栈
Stack<Map<String,Integer>> quickSortStack =new Stack<Map<String,Integer>>();
//整个梳理中起止下标,以哈希的形式入栈
Map rootParam = new HashMap();
rootParam.put("sartIndex",startIndex);
rootParam.put("endIndex",endIndex);
quickSortStack.push(rootParam);
//循环结束条件:栈为空时
while (!quickSortStack.isEmpty()){
//栈顶元素出栈,得到起止下标
Map<String,Integer> param =quickSortStack.pop();
//得到基准元素
int pivotIndex =parttion(arr,param.get("startIndex"),param.get("endIndex"));
//根据基准元素分成两部分,把每一部分的起止下标入栈
if (param.get("startIndex")<pivotIndex-1){
Map<String,Integer> leftParam = new HashMap<String, Integer>();
leftParam.put("startIndex",param.get("startIndex"));
leftParam.put("endIndex",pivotIndex-1);
quickSortStack.push(leftParam);
}
if (pivotIndex+1<param.get("endIndex")){
Map<String,Integer> rightParam = new HashMap<String, Integer>();
rightParam.put("startIndex",pivotIndex+1);
rightParam.put("endIndex",param.get("endIndex"));
quickSortStack.push(rightParam);
}
}
}
/**
* 分治单边循环法
*
* @param arr 待交换的数组
* @param startIndex 起始下标
* @param endIndex 结束下标
* @return
*/
private static int parttion(int[] arr, int startIndex, int endIndex) {
//取第一个位置(也可以随机取)的元素作为基准元素、
int piovot = arr[startIndex];
int mark = startIndex;
for (int i = startIndex + 1; i <= endIndex; i++) {
if (arr[i] < piovot) {
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
//piovt和指针重合点交换
arr[startIndex] = arr[mark];
arr[mark] = piovot;
return mark;
}
public static void main(String[] args) {
int[] arr = new int[]{4, 4, 6, 5, 3, 2, 8, 1};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
小结:和递归实现相比,非递归实现代码的变动只发生在quickSort方法中,该方法引入了一个存储Map类型元素的栈,用于存储每一次交换时的起始下标和结束下标。每一次循环,都会让栈顶元素出栈,通过上一个单边循环法中的parttion方法进行分治,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈,当栈为空时,说明排序已经完成了,退出循环。
额,先消化消化。。。。