文章内容和代码来自《漫画算法》和数据结构教材。现进行一下代码编写练习。
1.双边循环法
/**
* 双边循环法,从左右两端分别向中间进行比较和交换数据 递归实现
*/
void quickSortV1(int[] arr, int start, int end) {
// 递归结束条件
if (start >= end) {
return;
}
// 得到基准元素pivot位置
int pivotIndex = partitionV1(arr, start, end);
// 根据基准元素,分成两部分进行递归
quickSortV1(arr, start, pivotIndex - 1);
quickSortV1(arr, pivotIndex + 1, end);
}
/**
* 一轮比较与交换divide and conquer
*
* @param arr
* @param start
* @param end
* @return
*/
private int partitionV1(int[] arr, int start, int end) {
int pivot = arr[start];
int left = start;
int right = end;
while (left != right) {
// 控制right指针比较并左移
while (left < right && arr[right] > pivot) {
right--;
}
// 控制left指针比较并左移
while (left < right && arr[left] <= pivot) {
left++;
}
// 交换left,right指向的元素
if (left < right) {
int p = arr[left];
arr[left] = arr[right];
arr[right] = p;
}
}
// pivot和指针重合点交换
arr[start] = arr[left];
arr[left] = pivot;
return left;
}
@Test
public void fun1() {
// int[] arr=new int[]{4,4,6,5,3,2,8,1};
int[] arr = new int[] { 49, 38, 65, 97, 76, 13, 27, 49 };
quickSortV1(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
2.单边循环法
/**
* 单边循环法,
* 1.设置一个mark指针,表示小于pivot数据区域的边界
* 2.从左向右开始遍历与pivot比较,如果大于等于pivot,继续向后
* 3.如果小于,则mark后移一位,并与小于pivot元素的位置进行交换
* 4.直到一轮遍历结束,最后将mark位置元素与pivot所在位置元素交换位置。
*
*
*/
void quickSortV2(int[] arr, int start, int end) {
// 递归结束条件
if (start >= end) {
return;
}
// 得到基准元素pivot位置
int pivotIndex = partitionV2(arr, start, end);
// 根据基准元素,分成两部分进行递归
quickSortV2(arr, start, pivotIndex - 1);
quickSortV2(arr, pivotIndex + 1, end);
}
/**
* 分治 divide and conquer
*
* @param arr
* @param start
* @param end
* @return
*/
private int partitionV2(int[] arr, int start, int end) {
// 取第一个位置元素作为基准元素pivot
int pivot = arr[start];
int mark = start;
for (int i = start + 1; i <= end; i++) {
if (arr[i] < pivot) {
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
arr[start] = arr[mark];
arr[mark] = pivot;
return mark;
}
@Test
public void fun2() {
int[] arr = new int[] { 49, 38, 65, 97, 76, 13, 27, 49 };
quickSortV2(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
3.非递归法实现
/**
* 非递归的方式进行实现 (绝大多数的递归逻辑,都可以用栈的方式来代替)
*
* @param arr
* @param start
* @param end
*/
void quickSortV3(int[] arr, int start, int end) {
Stack<Map<String, Integer>> stack = new Stack<>();
Map<String, Integer> root = new HashMap<String, Integer>();
root.put("start", start);
root.put("end", end);
stack.push(root);
// 栈为空时结束循环
while (!stack.isEmpty()) {
Map<String, Integer> item = stack.pop();
int pivotIndex = partitionV3(arr, item.get("start"), item.get("end"));
// 根据基准元素分成2部分,把每一部分的起止下标入栈
if (item.get("start") < pivotIndex - 1) {
Map<String, Integer> left = new HashMap<>();
left.put("start", item.get("start"));
left.put("end", pivotIndex - 1);
stack.push(left);
}
if (pivotIndex + 1 < item.get("end")) {
Map<String, Integer> right = new HashMap<>();
right.put("start", pivotIndex + 1);
right.put("end", item.get("end"));
stack.push(right);
}
}
}
private int partitionV3(int[] arr, int start, int end) {
int pivot = arr[start];
int mark = start;
for (int i = start + 1; i <= end; i++) {
if (arr[i] < pivot) {
mark++;
int p = arr[mark];
arr[mark] = arr[i];
arr[i] = p;
}
}
arr[start] = arr[mark];
arr[mark] = pivot;
return mark;
}
@Test
public void fun3() {
int[] arr = new int[] { 49, 38, 65, 97, 76, 13, 27, 49 };
quickSortV3(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
数据结构教材的算法转换为C语言
#include
using namespace std;
/*
数据结构(C语言版) 严蔚敏,吴伟民
的快速排序算法转换为代码
*/
int Partition(int arr[], int low, int high){
int pivot = arr[low];// 第一个记录作为pivot
while(low<high){
while(low<high && arr[high]>=pivot){
--high;
}
arr[low]=arr[high];
while(low<high && arr[low]<=pivot){
++low;
}
arr[high]=arr[low];
}
arr[low]=pivot;
return low;
}
void Qsort(int arr[], int low, int high){
if(low < high){
int pivotLoc = Partition(arr, low, high);
Qsort(arr, low, pivotLoc-1);
Qsort(arr, pivotLoc+1,high);
}
}
int main(int argc, char* argv[]) {
int arr[]={49,38,65,97,76,13,27,49};
Qsort(arr,0,7);
for(int i=0;i<8;i++){
cout<<arr[i]<<",";
}
return 0;
}
分治过程如下图:
总结:快速排序的平均时间复杂度为O(nlogn)。漫画算法的快排和数据结构教材的快排算法略有区别,主要体现在数据元素交换方式上。对于基准数据pivot的选择,代码中默认选择数组第一个元素,但是若初始记录序列按关键字有序或基本有序时,快排将蜕化为冒泡排序,其时间复杂度为O(n2),为改进之,通常依三者取中的法则来选取枢轴记录pivot,即比较第一个,最后一个,中间一个元素的大小,取中间者为pivot;漫画算法里面还提到了随机选择一个元素作为基准元素作为pivot,然而也依然有极小的几率选到数列的最大值或最小值,影响分治的效果。
其实就是每一轮交换数据后,返回pivot当前位置,根据pivot位置将一个数组分成2个子数组,对子数组继续递归处理,直到子数组长度变为1。