一、递归和快速排序
-
之前这篇已经使用递归解决汉诺塔问题,感觉十分的爽。所以趁热打铁练习一下快速排序。
-
递归解决问题的关键是了解解决问题过程中N和N-1步的关系。并且假设N-1步已经完成了。
-
快速排序刚好满足。快速排序思路简单如下:
-
图可以看出。快速排序是不断的将数列,按照某个中间值为界限,将大的放在中间值右边,小的放在中间值左边。
-
这张图是为了表达这个意思。实际上可能不是这样移动的。但是思想表达清楚了。
-
这种套娃式的解决方法,使用递归是没毛病的。
二、抽象
-
我首先要找到那个中间值。
//获取中间值,<= 这个值的,放在左边。> 这个值的,放在右边。 int get_tmp_index(int arr_len) { return (arr_len/2); } //获取计算出来的中间数下标。 int tmp_index = get_tmp_index(arr_len);
-
然后我要调整这个数列。以中间值为界限,比它小的放在左边,大的放在右边。封装一个函数
//调整数组将<=这个下标对应数值的移动到它左边,把>它的移动到右边。 ajust_arr(arr,arr_len,tmp_index);
-
对左边的子数列和右边的子数列做同样的操作。
//然后同样的方法把前半部分排好。 quick_sort(arr,tmp_index); //然后同样的方法把后半部分排好。 quick_sort(&arr[tmp_index],arr_len-tmp_index);
-
排序,肯定要用到交换值和比较。这些已经在插入排序和希尔排序中练习过了。
//用来交换两个值 int swap(int *a ,int *b); //用来比较两个值 int compare(int *a,int *b);
三、实现
-
完整代码
#include<stdio.h> int print_arr(int *arr,int arr_len) { if(arr == NULL || arr_len == 0) { return -1; } int i = 0; for(i = 0;i < arr_len;i++) { printf("%d,",arr[i]); } printf("\n"); return 0; } //获取中间值,<= 这个值的,放在左边。> 这个值的,放在右边。 int get_tmp_index(int arr_len) { return (arr_len/2); } int swap(int *a ,int *b) { if( a == NULL || b == NULL) { return 0; } int tmp = *a; *a = *b; *b = tmp; return 0; } int compare(int *a,int *b) { if(a == NULL || b == NULL) { //要特殊处理,这里就按照相等算吧。。。理解一下 return 0; } if(*a > *b) { return 1; } else if( *a == *b) { return 0; } else { return -1; } return 0; } //调整这个数列。以中间值为界限,比它小的放在左边,大的放在右边。 int ajust_arr(int *arr,int arr_len,int index) { //数组不存在,或者数组只有一个,不需要调整。或者index 越界了,也直接不做了。 if(arr == NULL || arr_len <= 1 || index >= arr_len) { return 0; } //取出那个中间值。 int m_value = arr[index]; int i = 0,j = arr_len - 1; //然后是紧张刺激的调整环节。 while(i < j) { //先从前往后找比 中间值 大的,大就放后面和 j下标的位置 调换 for(; i < j ; i++) { if(compare(&arr[i],&m_value) > 0) { swap(&arr[i],&arr[j]); if(i == index) { index = j; } //交换之后,马上先跳出循环,从后往前找比它小的 break; } } //从后往前找,比中间值小的,找到了就和i 下标位置的数值调换。 for(; j > i;j--) { if(compare(&arr[j],&m_value) <= 0) { swap(&arr[i],&arr[j]); if(j == index) { index = i; } //交换后,马上break,再从前往后找。 break; } } } return index ; } int quick_sort(int *arr ,int arr_len) { //如果是长度只有1,那就不用排咯。 if(arr_len <= 1) { return 0; } //获取计算出来的中间数下标。 int tmp_index = get_tmp_index(arr_len); //调整数组将<=这个下标对应数值的移动到它左边,把>它的移动到右边。 tmp_index = ajust_arr(arr,arr_len,tmp_index); //然后同样的方法把前半部分排好。 quick_sort(arr,tmp_index); //然后同样的方法把后半部分排好。 quick_sort(&arr[tmp_index],arr_len-tmp_index); return 0; } int main() { int arr[] = {2,1,8,7,4,3,6,5}; int arr_len = sizeof(arr)/sizeof(arr[0]); print_arr(arr,arr_len); quick_sort(arr,arr_len); print_arr(arr,arr_len); return 0; }
-
调整数组的 ajust_arr 函数可以使用各种实现方法。
四、整理总结
-
我这里获取中间值直接arr_len/2来计算。实际上应该更加谨慎一些,因为这个中间值越是靠近中间,快速排序的效率就越高。 比如,{2,1,8,7,4,3,6,5},这个数组,如果第一次你选的是4,就非常的中间。排序效率很高。而如果你选的是8,是一个边上的值,那效率就很低。
-
整理一下使用到的思维因子
-
其实核心的地方是ajust_arr函数。
五、方法记忆
- 使用递归解决问题,就是看N和N-1之间的关系还有终止条件两个点。
- 对于快速排序而言,终止条件就是数列只有一个元素的时候。N和N-1的关系,就是取一个基准值,比它小的数字放在左边,比它大的数字放在右边。
- 和递归联系起来记忆。递归的关键点是两个。你会使用递归解决汉诺塔问题,就会解决快速排序的问题。
六、参考
- 之前的插入排序和而排序算法