排序算法是算法最最最基础的内容,希望自己的基础可以扎实

package com.company;

import java.util.Arrays;

public class Sort {


    /**
     * 归并排序
     * 为什么可以O(nlogn),因为所有的比较都没有被浪费,但冒泡或者其他排序就会浪费很多比较的过程
     *
     * @param arr
     * @param L
     * @param R
     */

    public static void process(int[] arr, int L, int R) {
        if (L == R) {
            return;
        }
        int M = L + ((R - L) >> 1);
        // 左侧排好
        process(arr, L, M);
        // 右侧排好
        process(arr, M + 1, R);
        // 然后最终合并到一起?这里没有需要mid
        merge(arr, L, M, R);
    }

    public static void merge(int[] nums, int L, int M, int R) {
        // 辅助空间(外排序)
        int[] help = new int[R - L + 1];
        // 给help使用的,用来填数字
        int i = 0;
        // 用来指示数组下标
        int p1 = L;
        int p2 = M + 1;
        //进入循环的前提条件是不发生越界
        while (p1 <= M && p2 <= R) {
            // 这个写法真的太精妙了,其实就是用两个数组不断拷贝的过程
            help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
        }// 然后最终会有一个先越界;就看谁先耗完,谁没越界,谁就吧自己的数目全部拷贝到help里面
        while (p1 <= M) {
            help[i++] = nums[p1++];
        }
        while (p2 <= R) {
            help[i++] = nums[p2++];
        }
        // 把help拷贝到arr;
        for (i = 0; i < help.length; i++) {
            nums[L + i] = help[i];
        }
    }

    /**
     * 求小和,数组每个数左边比她小的之和/右边多少个数比它大
     */
    public static int littleSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process2(arr, 0, arr.length - 1);
    }

    // 既要排序,又要求小和
    public static int process2(int[] nums, int l, int r) {
        // 首先把不需要求小和的情况写下来,就是数组长度为0,为什么不能用nums?其实我觉得可以
        // 还是要注重模块化实现,就是说每个方法仅仅完成一小部分的功能
        if (l == r) {
            return 0;
        }
        int m = l + ((r - l) >> 1);

        return (// 左侧产生的小和
                process2(nums, l, m)
                        // 右侧产生小和
                        + process2(nums, m + 1, r)
                        // 合并产生的小和
                        + merge2(nums, l, m, r));
    }

    // 什么时候作为参数传入呢?是不是因为前面写了m,所以后面就可以直接作为参数传入?
    public static int merge2(int[] nums, int l, int m, int r) {
        // 辅助空间(外排序)
        int[] help = new int[r - l + 1];
        // 指针指向help数组下标
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        // 储存结果集
        int littleSum = 0;
        // 都不越界的时候
        while (p1 <= m && p2 <= r) {
            // 只有左组比右小,才能增加小和;增加的是当前右组比p1大的数目,它乘以当前左组的数就是小和增加的量。
            littleSum += nums[p1] < nums[p2] ? (r - p2 + 1) * nums[p1] : 0;
            // 右组大于等于左组,先拷贝右组
            help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
            while (p1 <= m) {
                help[i++] = nums[p1++];
            }
            while (p2 <= r) {
                help[i++] = nums[p2++];
            }
            for (i = 0; i < help.length; i++) {
                nums[l + 1] = help[i];
            }
        }
        return littleSum;
    }


    /**
     * 求逆序对(这个确实考很多)
     */


    /**
     * 选择排序
     *
     * @param nums
     */
    public static void selectSort(int[] nums) {
        // 剔除不需要排序的方法
        if (nums == null || nums.length < 2) {
            return;
        }
        for (int i = 0; i < nums.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[minIndex]) {
                    minIndex = j;
                }
            }
            swap(nums, i, minIndex);
        }
    }


    /**
     * 数组中有一个数出现奇数次,其他都出现偶数次,请问怎么找到这个数?
     */

    public static void yihuo(int[] nums, int a, int b) {
        int eor = 0;
        for (int i = 0; i < nums.length; i++) {
            eor = eor ^ nums[i];
            // 最后的eor就是出现了奇数次,因为剩下的两个一模一样的都被异或掉了
        }
    }

    /**
     * 数组中有2个数出现奇数次,其他都出现偶数次,请问怎么找到这个数?
     */
    public static void yihuo2(int[] nums) {
        //设目标数字为ab,其他都为偶数次
        int eor = 0;
        for (int num : nums) {
            eor ^= num;
            // 最后的eor就是a^b;且a!=b,且他们在某一位的数字是不一样的
        }

        int lastOne = eor & (~eor + 1);
        int rightOne = 0;
        for (int num : nums) {
            if ((lastOne & num) == 0) {
                // 这个地方还是不能直接让num==rightOne;这是因为符合条件的num不止一个?
                rightOne ^= num;
            }
        }
        int anotherOne = eor ^ rightOne;
        System.out.println(rightOne);
        System.out.println(anotherOne);
    }

    /**
     * 插入排序
     *
     * @param nums
     */
    public static void insert(int[] nums) {
        // 想让从0-i上面是有序的
        for (int i = 0; i < nums.length; i++) {
            // J>=0,代表它不越界;nums[j]>nums[j+1] 代表需要交换位置(妙啊)
            for (int j = i - 1; j >= 0 && nums[j] > nums[j + 1]; j--) {
                swap(nums, nums[j], nums[j + 1]);
            }
        }
    }

    public static void swap(int[] nums, int a, int b) {
        // 异或:相同为0;不同为1的运算;也可以理解为无进位相加
        // 同一批数异或结果一定是一样的,哪怕顺序都不一样
        // 使用异或就不需要交换
        // 能这么干的前提是,ab在内存中属于不同的区域,也就是a位置必须不等于b位置,不然这个位置上面的数字就等于0了
        // 平时就好好的按照正常的写,来个temp变量,因为以自己的水平还不能保证a和b永远都不一样
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

    /**
     * getMax 使用递归的方法求数组最大值
     */
    public static int getMax(int[] nums) {
        return process4(nums, 0, nums.length - 1);
    }

    public static int process4(int[] nums, int l, int r) {
        int mid = l + ((r - l) << 1);
        int left = process4(nums, l, mid);
        int right = process4(nums, mid + 1, r);
        return Math.max(left, right);
    }


    /**
     * quickSort,认为最右边的是number,<=number放左边,>放右边
     * 荷兰国旗的递归版本
     * 快排最差情况的原因:划分值打在最差的地方;打在中间,时间复杂度最好;因此必须随机选择这个target
     */
    public static void quickSort(int[] nums) {
        if (nums == null || nums.length < 2) {
            return;
        }
        quickSort(nums, 0, nums.length - 1);
    }

    public static void quickSort(int[] nums, int l, int r) {
        // 递归开始条件
        if (l < r) {
            swap(nums, l + (int) Math.random() * (r - l + 1), r);
            // 自己思考一下
            int[] partition = partition(nums, l, r);
            quickSort(nums, l, partition[0]);
            quickSort(nums, partition[1] + 1, r);
        }
    }

    public static int[] partition(int[] nums, int l, int r) {
        int lpivot = l - 1;
        int rpivot = r;
        // 还有数据没有排序
        while (l < rpivot) {
            if (nums[l] < nums[r]) {
                swap(nums, ++lpivot, l++);
            } else if (nums[l] > nums[r]) {
                swap(nums, rpivot--, l);
            } else {
                l++;
            }
        }
        swap(nums, rpivot, r);
        return new int[]{lpivot + 1, rpivot};
    }


    /**
     * 堆排序,堆结构比较重要;堆在逻辑概念上是完全二叉树的结构;
     * 完全二叉树的定义:不满的话从左到右填满
     * 把堆填满然后删除为0;就是排序的过程
     */
    public static void HeapSort(int[] nums) {
        if (nums == null || nums.length < 2) {
            return;
        }
        // 把数组搞成大根堆
        for (int i = 0; i < nums.length; i++) {
            heapInsert(nums, i);
        }
        int heapSize = nums.length;
        swap(nums, 0, --heapSize);

        while (heapSize > 0) {
            heapify(nums, 0, heapSize);
            swap(nums, 0, --heapSize);
        }
    }

    public static void heapInsert(int[] nums, int index) {
        while (nums[index] > nums[(index - 1) / 2]) {
            swap(nums, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
    public static void heapify(int[] nums, int index, int heapSize) {
        int left = index * 2 + 1;
        // 说明我有孩子,判断左孩子有没有越界;因为先填充左再填充右,左孩子的下标比右孩子小,所以只要有左孩子就有孩子
        while (left < heapSize) {
            // left+1 右孩子下标,<heapSize 证明它存在;
            // 两个孩子谁的下标最大,把它赋值给largest
            int largest = left + 1 < heapSize && nums[left + 1] > nums[left] ? left + 1 : left;
            // 父亲和孩子谁的值大,谁把自己的下标给largest
            largest = nums[largest] > nums[index] ? largest : index;

            if (largest == index) {
                break;
            }
            swap(nums, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }




    public static void main(String[] args) {
        // 和数据量无关的就是常数操作
        // 时间复杂度,其实自己也不是特别的熟悉;一般先看指标,如果指标一样就通过实践判
//       如果指标一样就通过实践判
        int[] nums = new int[]{3, 1, 2, 9, 7, 4};
        HeapSort(nums);
        System.out.println(Arrays.toString(nums));
    }
}