目录
摘要
- 分治法, 问题“逐步生成”的思想----将大问题分解,分成几个与他结构相同的小问题,不断分解,直至问题自然解决或者提高算法效率;
- 分治法的三步骤:分解,解决,合并
- 归并排序和快速排序很类似,都涉及到了分治的思想。但归并分解容易合并难,注重合并;快排分解困难,不用合并(快排在原数组上排列,而归并需建立辅助helper数组)
- 快排的“解决”部分, 在每次分解的时候就发别完成了排序, 原理类似左右大小两辆车相向而来,之间的距离就是未排好的数据,定义两个left和right指针不断运动,left>right时停止.
快速排序
1. 原理: 类似撞车,左边的“小车”和右边的“大车”相向而来,之间的距离就是未排好的数值,大于参考主元pivot(参考数)的就丢到大车这边, 小于的就丢到小车这边, 辆车开过了,交叉的时候就代表排序结束, 交换跑到小的这边的arr[right], 并把right当中间数下标传出去。
2. 解决步骤: 这里用问题“逐步生成”的思想,功能分作了两部分、两个方法:
* 1. partition: 分割成两栏, 左小右大, 返回一个下标给quick_sort方法做中位数. 分割成一边大一边小、再返回主元pivot(中间数)下标的;
* 2. quick_sort: 拆分, 递归向下拆分数组至只剩两个, 递归后向上回溯合并,变得有序 (每次都分割时都调整为左小右大,当向下细分到只有两个时, 全部就排好了 );
单向扫描分区的方法实现partition:
单项扫描(一边主动走一边被动移)的思路: 索引0为主元(参考数),定义一个未扫描数据的最左边界指针left、最右边界值指针right,只有最左边界指针在扫描、在动; 遇到大于主元的值全都丢到右边,left指针和right指针此时指向的值交换,left不动继续比较,右边界指针right前进一位。
public static void quick_sort(int[] arr, int prior, int row){
if(prior<row) { //结束条件:prior==row,自然结束
int mid = partition01(arr, prior, row);
quick_sort(arr, prior, mid - 1);
quick_sort(arr, mid + 1, row); //这里的递归不用出栈(回溯)
}
}
private static int partition01(int[] arr, int prior, int row) {
int pivot=arr[prior]; //定义一个参考的主元
int left=prior+1; //定义一个未扫描数据的最左边界指针
int right=row; //最右边界指针
while(left<=right) { // 当二指针重合时,其实单项扫描工作已经完成了,之后right交错指向了一个小于主元pivot的值
if (arr[left] <= pivot)
left++;
else {
util.util.swap(arr,left,right); //注意,这里的left与right是指针,交换的是指向的值
right--;
}
}
util.util.swap(arr,prior,right); //right与left交错指向了一个小于pivot值,可以随意与主元pivot交换
return right;//right此时就成了中位数
}
双向扫描分区的方法实现partition:
双向扫描(两边都在主动走)的思路是: 头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换, 继续扫描,直到左侧无大元素,右侧无小元素。 边界值的问题考虑较多,所以还是使用单项扫描比较容易理解。
public static int partition02(int[] arr, int prior, int row){
int pivot=arr[prior]; //定义一个参考的主元
int left=prior+1;
int right=row;
while(left<=right) { // 指针交错,停止迭代
//得考虑极端边界情况,如果主元pivot恰好最大/最小,边界值指针会冲出去越界
while (left<=right && arr[left]<=pivot) left++;
while (left<=right && arr[right]>=pivot) right--;
if(left<right) //如果过界了就不能交换了
util.util.swap(arr,left,right);
}
util.util.swap(arr,right,prior);
return right; //中位数
}
归并排序
原理步骤:
* 1. merge: 合并左右有序数组, 创建辅助盘,不断比较左右两边最小的值,决定谁先入栈(因为这个方法需要左右分别都有序)
* 2. merge_sort: 拆分数组, 再递归向下拆分两个子数组; 结束递推, 合并回溯回来的两个子数组
/**
* 1. merge: 合并左右有序数组, 创建辅助盘,不断比较左右两边最小的值,决定谁先入栈(因为这个方法需要左右分别都有序)
* 2. merge_sort: 拆分数组, 再递归向下拆分两个子数组; 结束递推,合并回溯回来的两个子数组
*/
public class MergeSort {
public static void merge_sort(int[] arr,int left,int right){
if (left<right){ //1. 退出条件特性解:最后只剩下了一个数不用排了
//2. 划分左右栏
int mid=(left+right)/2;
merge_sort(arr,left,mid);
merge_sort(arr,mid+1,right);
//3. 融合数组
merge(arr,left,mid,right);
}
}
private static void merge(int[] arr, int low, int mid, int high) {
//1. 深拷贝创建一个辅助数组,已排序好的左右两栏,比较左右最小值,较小的数据先覆盖原数组
int[] helper= Arrays.copyOfRange(arr,0,arr.length); //区间左闭右开
int current=low; //原数组栈顶指针
int left=low; //左边最小的首指针
int right=mid+1;
//2. 比较左右栏的最小值谁先入栈
while(left<=mid&&right<=high){
if (helper[left]<=helper[right]) {
arr[current] = helper[left];
left++;
current++;
}else {
arr[current] = helper[right];
right++;
current++;
}
}
//3. 若某一边数值先入栈完时,右栏倒是不用处理了(辅助盘拷贝原数组,剩下的几个大数字的位置-->||刚好是一样的),左边就得单独入栈
while(left<=mid){
arr[current]=helper[left];
left++;
current++;
}
}
public static void main(String[] args) {
int[] arg=new int[8];
for (int i = 0; i < arg.length; i++) {
arg[i]=(int)(Math.random()*20);
}
merge_sort(arg,0,arg.length-1);
System.out.println(Arrays.toString(arg));
}
}