自学b站左神的算法课,记录一下。
排序原理及流程图就不介绍了,看代码 和 看介绍 是两个概念。
想学习的话可以复制到编译器自己玩玩。
(快排讲解的比较详细,欢迎勘误毕竟自己的思考可能有缺陷!)
归并排序
/**
* 归并排序
* @author hozier
*
*/
public class MergeSort {
public static void process(int arr[] , int L , int R ) {
//排序
if( L == R )
{
return;
}
int mid = L + ((R - L) >> 1 ); //求中点(避免溢出)
process(arr , L , mid);
process(arr , mid + 1 , R);
merge(arr , L , mid , R); //排序 归并。
}
public static void merge(int[] arr , int L , int M , int R ) {
int help[] = new int[R - L + 1]; // 为什么是 R-L+1? 假设 L= 2 ,R = 7 则数组角标为:[0,1,2,3,4,5] 长度6。
int i = 0;
int p1 = L;
int p2 = M + 1;
while( p1 <= M && p2 <= R ) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
// 后++ 先赋值后加。 所以并不影响help[]内元素的值,而因为在循环中++了,后续也能将整个数组遍历一边。
}
//关于这两个while循环: 当上面的循环不走了,一定是因为不满足&& , p1 大于 M or p2大于R 导致循环结束,
//此时一定会走下面while循环中的某一个。
while(p1 <= M) {
help[i++] = arr[p1++] ;
}
while(p2 <= R) {
help[i++] = arr[p2++];
}
//关于 i 、p1 、 p2 :i只决定 help数组的角标,因为help[]是我们定义的辅助数组,用来排序。 而排序的值由arr[]提供,具体取那一个由p1、p2 作为角标提供。
for(i = 0 ; i < help.length ; i++) {
arr[L + i] = help[i];
}
}
}
归并排序的衍生:小和问题
/**
* 小和问题
* @author hozier
*
*/
public class SmallSum {
public static int smallSum(int[] arr) {
if(arr == null || arr.length < 2) {
return 0;
}
return process (arr, 0 , arr.length - 1);
}
// arr[L ... R] 既要排好序 也要求小和
public static int process(int arr[] , int L , int R ) {
if( L == R ) {
return 0;
}
int mid = L + ((R - L)>> 1); //中点
return
process(arr , L , mid) +
process(arr , mid + 1, R) +
merge(arr , L , mid , R);
}
public static int merge(int arr[] , int L , int M , int R) {
int help[] = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
int res = 0;
while(p1 <= M && p2 <= R) {
res += arr[p1] < arr[p2] ? (R - p2 + 1)*arr[p1] : 0; // (R - p2 + 1)为右边有几个比p1大,*p1就是p1的小和数;
// res之所以放在前面是因为后续的p1++会导致数据进一
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; // 归并 ; 如果p1 == p2 ,则需要将p2填进help[]中,所以使用 < 号.
}
while(p1 <= M) {
help[i++] = arr[p1++];
}
while(p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L+i] = help[i];
}
return res;
}
}
快排
前置:荷兰国旗问题
/**
* 荷兰国旗问题
* @author hozier
*
* @implNote 给定一个数组arr,和一个数sum。请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
* 要求:时间复杂度为O(N),额外空间复杂度为O(1).
*
*/
public class HollandFlagQuestion {
public static int[] partition( int arr[] , int l ,int r , int num ) {
int less = l - 1;
int more = r + 1;
while(l < more) {
if(arr[l] < num) {
swap(arr , ++less ,l++);
}else if(arr[l] > num) {
swap(arr , l , --more);
}else {
l++;
}
}
return new int[] {less+1 , more - 1};
}
public static void swap(int arr[] , int a , int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
根据荷兰问题衍生出快排方法
/**
* 快排 --> 基于荷兰国旗问题的进化
* @author hozier
*
*/
public class QuickSort {
public static void quickSort(int arr[]) {
if(arr == null || arr.length < 2) {
return;
}
quickSort(arr , 0 , arr.length - 1);
}
public static void quickSort(int[] arr , int L , int R ) {
if(L < R) {
swap(arr , L + (int)Math.random()*(R - L + 1) , R );
int[] p = partition(arr , L , R ); // 返回一个长度为二的p[]:其值为 arr[]的左边界、右边界
quickSort(arr ,L , p[0] - 1);
quickSort(arr , p[1]+1 , R);
}
}
/* partition: 分治,方法 */
public static int[] partition(int[] arr , int L , int R ) {
int less = L - 1; // “<区” 右边界
int more = R; // “>区” 左边界
while(L < more ) { // L 为当前数的位置, L<more用于判断边界是否交合,没交和就走判断,交合了说明整合区间已经判断完毕 方法停止。
if(arr[L] < arr[R]) {// 当前数 < 划分值
//++less; // 为什么要交换?在自己思考的时候会突然陷入一种误区:既然当前数<划分值 时区间才会推进,为什么不直接++呢?因为
//L++; // ++的效率要比swap高吧,但是换成如此的代码后排序却会出错,为什么?这是为了确保 ''相等情况下" 代码的合理性。
// 当划分值为5 ,区间有5 的时候,如果直接++,那岂不是将5也包含进<区间了? 故当数组中没有相同元素时这样写是可以的哦。
swap(arr , ++less , L++);
}else if(arr[L] > arr[R]) { // 当前数 > 划分值
swap(arr , -- more , L );
}else { // 当前数 == 划分值
L++;
}
}
swap(arr , more ,R);
return new int[] {less +1 , more};
// 着重注意此区间, 此为递归的核心。我们知道,在荷兰国旗中返回的区间为{less+1,more-1},为什么呢?
// 因为less代表的是下次运算时数组的右边界,more为~的左边界。
// 而仔细看递归方法中的形参:(L , p[0]-1) ; (p[1]+1 , R), 之所以+1就是+1-1后结果为less,
// 而more为什么不用-1呢,原因在于 我们运算初期将划分值移到了右边,在循环完毕后将其换了回来,
// 这个过程其实就是将等于划分值的数放在了右边界上,相当于右边界-1
// 当然 less+1 和 p[0] -1 的加减是可以省略的,加上可能是因为和more相互参照吧(但是不加感觉更好理解)
}
/* 交换方法 */
public static void swap(int arr[] , int a , int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
解析中各值不相等的情况(即使用 ++less和 L++的情况)
测试:
/**
* 测试各算法
* @author hozier
*
*/
public class TestAll {
static int arr[] = new int[]{1,3,6,-10,9,2,-5,67,32,1, 99 , -20 , 1}; //10个数
static int arrE[] = new int[] {1,2,2,2,2,2,2,4};//{1,3,4,2,5};
/**
* 测试mergeSort 归并排序
*/
@Test
public void test01() {
MergeSort mergeSort = new MergeSort();
mergeSort.process(arr, 7, 9);
for (int i : arr) {
System.err.print(" " + i);
}
}
/**
* 测试 小和问题
*/
@Test
public void test02() {
SmallSum smallSum = new SmallSum();
System.out.println(smallSum.smallSum(arrE));
}
/**
* 测试 荷兰国旗问题
*/
@Test
public void test03() {
HollandFlagQuestion hf= new HollandFlagQuestion();
int[] arr = {1,5,3,6,2,8,9,10,3,5};
int num = 5;
hf.partition(arr , 0 , 9 ,5);
for (int i : arr) {
System.out.println(i);
}
}
/**
* 测试 快排
*/
@Test
public void test04() {
QuickSort qs = new QuickSort();
int[] arr = {1,3,2,4,6,-2,-7,5,9};
qs.quickSort(arr);
for (int i : arr) {
System.out.print(" "+i);
}
}
}