算法基础入门——归并排序、堆排序、快速排序、小和问题、有序数组的排序、荷兰国旗问题

package com.zuoshen.jichurumen.class02;

import java.util.PriorityQueue;

/**
 * @author ShiZhe
 * @create 2022-02-24 9:06
 */
public class code01 {

    /**
     * 归并排序
     * 思想是分而治之
     * 时间复杂度O(N*logN),额外空间复杂度O(N)
     * @param arr
     */
    public static void mergeSort(int[] arr, int left, int right) {
        if (arr == null || arr.length < 2) {
            return;
        }
        if (left == right) {
            return;
        }
        int mid = left + ((right - left) >> 1);
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }

    /**
     * 归并排序算法步骤:
     * 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
     * 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
     * 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
     * 重复步骤 3 直到某一指针达到序列尾;
     * 将另一序列剩下的所有元素直接复制到合并序列尾。
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        // 辅助空间,将2个有序序列比较后合成一个有序序列
        int[] help = new int[right - left +1];
        // 辅助空间下标
        int i = 0;
        // 第一个有序序列的指针
        int one = left;
        // 第二个有序序列的指针
        int two = mid + 1;
        // 利用2个指针比较大小
        while (one <= mid && two <= right) {
            help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++];
        }
        while (one <= mid) {
            help[i++] = arr[one++];
        }
        while (two <= right) {
            help[i++] = arr[two++];
        }
        for (i = 0; i < help.length; i++) {
            arr[left++] = help[i];
        }
    }

    /**
     * 小和问题
     * 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
     * 例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、2; 所以小和为1+1+3+1+1+3+4+2=16
     * 转换思路:求每一个数左边比当前小的数,也就等同于求每个数右边比当前大的数的个数
     */
    public static int smallSum(int[] arr, int left, int right) {
        if (arr == null || arr.length < 2){
            return 0;
        }
        if (left == right) {
            return 0;
        }
        int mid = left + ((right - left) >> 1);
        return smallSum(arr, left ,mid)
                + smallSum(arr,mid + 1, right)
                + smallSumMergeSort(arr, left, mid ,right);
    }

    public static int smallSumMergeSort(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int i = 0;
        int one = left;
        int two = mid + 1;
        //
        int res = 0;
        while (one <= mid && two <= right) {
            // 这一步最重要,左边有序序列的某值比右边有序序列的某值小,那右边有序序列某值之后的所有值都比左边某值大
            res = arr[one] < arr[two] ? res+arr[one] * (right - two + 1) :res;
            help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++];
        }
        while (one <= mid){
            help[i++] = arr[one++];
        }
        while (two <= right){
            help[i++] = arr[two++];
        }
        for (i = 0; i < help.length; i++) {
            arr[left++] = help[i];
        }
        return res;
    }

    /**
     * 堆排序
     * 第一步:生成大根堆
     * 第二步:把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始
     * @param arr
     */
    public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // 生成大根堆
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        int size = arr.length;
        swap(arr, 0, --size);
        // 调整,注意循环条件,使用异或调整,最后一个不换
        while (size > 1) {
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }

    /**
     * 将一个数组生成为大根堆
     * 时间复杂度为O(N)
     * @param arr
     * @param index
     */
    public static void heapInsert(int[] arr, int index) {
        // 当一个孩子的值大于父亲节点的值得时候交换,注意数组的下标从0开始
        // 左孩子是2 * i + 1,右孩子是2 * i + 2
        // 父亲节点是(i - 1) / 2

        // 注意(0 - 1) >> 1 = -1
//        while (arr[index] > arr[(index -1) >> 1]) {
//            swap(arr, index, (index -1) >> 1);
//            index = (index -1) >> 1;
//        }

        // (0 - 1) / 2 = 0
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) /2);
            index = (index - 1)/2 ;
        }
    }

    /**
     * 调整
     * 时间复杂度为O(N*logN)
     * @param arr
     * @param index
     * @param size
     */
    public static void heapify(int[] arr, int index, int size) {
        // index的左孩子
        int left = index * 2 + 1;
        while (left < size) {
            // 左孩子和右孩子中比较大小,得到大的下标
            int bigIndex = (left + 1) < size && arr[left] < arr[left + 1] ? left + 1 : left;
            // 孩子中大的与index相比较大小,得到最大的下标
            bigIndex = arr[bigIndex] > arr[index] ? bigIndex : index;
            // index为最大值,不需要调整,跳出循环
            if (bigIndex == index) {
                break;
            }
            swap(arr, index, bigIndex);
            index = bigIndex;
            left = index * 2 + 1;
        }
    }

    /**
     * 数组异或交换
     * 当输入的i和j相同时,交换结果均为0
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

    /**
     * 数组辅助交换
     * @param arr
     * @param i
     * @param j
     */
    public static void swap2(int[] arr,int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 对近乎有序的数组进行排序,近乎有序是,如果排好序的话,指每个元素移动的距离不超过k
     * @param arr
     * @param k
     */
    public static void sortedArrDistanceLessK(int[] arr, int k) {

        // PriorityQueue类提供堆数据结构的功能。默认是升序排列
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index = 0;
        // 将前k个放入堆中,排列有序
        for (; index < Math.min(arr.length, k); index++) {
            heap.add(arr[index]);
        }
        int i = 0;
        // 依次添加数组元素和将队首输出,保持堆中个数为k
        for (; index < arr.length; i++, index++) {
            heap.add(arr[index]);
            arr[i] = heap.poll();
        }
        // 将队列中最后k个依次输出
        while (!heap.isEmpty()) {
            arr[i++] = heap.poll();
        }
    }

    /**
     * 荷兰国旗问题
     * 给定一个数组arr,和一个数num,
     * 请把小于num的数放在数组的左边,
     * 等于num的数放在数组的中间,
     * 大于num的数放在数组的右边。
     * 要求额外空间复杂度O(1),时间复杂度O(N)
     * @param arr
     * @param left
     * @param right
     * @param num
     * @return
     */
    public static int[] netherlandsFlag(int[] arr, int left, int right, int num) {
        // less指向小于num的下标
        int less = left - 1;
        // more指向大于num的下标
        int more = right + 1;
        while (left < more) {
            if (arr[left] < num) {
                swap2(arr, ++less, left++);
            } else if (arr[left] > num) {
                swap2(arr, --more, left);
            } else {
                left++;
            }
        }
        // 返回的是等于num值得数组下标区间
        return new int[] {less + 1, more -1};
    }

    /**
     * 快速排序:对荷兰国旗问题的再应用,将数组最后一个值作为划分值
     * 划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低,不改进的快速排序时间复杂度为O(N^2)
     * 改进的快排就是等概率随机选取一个值作为划分值
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSort(int[] arr, int left, int right) {
        if (arr == null || arr.length < 2) {
            return;
        }
        if (left < right) {
            // 将某个值与最后的值做交换,等概率随机选取一个值作为划分值
            swap2(arr, left + (int) (Math.random() * (right - left + 1)), right);
            // 划分为3部分
            int[] p = partition(arr, left, right);
            // 递归调用
            quickSort(arr, left, p[0] - 1);
            quickSort(arr, p[1] + 1, right);
        }
    }

    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right;
        while (left < more) {
            if (arr[left] < arr[right]) {
                swap2(arr, ++less, left++);
            } else if (arr[left] > arr[right]) {
                swap2(arr, -- more, left);
            }
            else {
                left++;
            }
        }
        swap2(arr, more, right);
        // 注意这里将最后的值作为划分值,进行交换后,返回的p[1]应该为more,而不是more-1
        return new int[] {less + 1, more};
    }

    public static void main(String[] args) {
        // 归并排序
        int[] arr1 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
        mergeSort(arr1, 0, arr1.length - 1);
        for (int i = 0; i < arr1.length; i++) {
            System.out.println(arr1[i]);
        }

        // 小和问题
        int[] arr2 = {1, 3, 4, 2, 5};
        int small = smallSum(arr2, 0, arr2.length - 1);
        System.out.println(small);

        // 堆排序
        int[] arr3 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
        heapSort(arr3);
        for (int i = 0; i < arr3.length; i++) {
            System.out.println(arr3[i]);
        }
        // 注意:(0 - 1) / 2 = 0
        System.out.println((0-1)/2);
        // 注意:(0 - 1) >> 1 = -1
        System.out.println((0-1)>> 1);

        // 堆排序扩展
        int[] arr4 = {1, 3, 2, 4, 5, 6, 8, 7, 10, 9};
        sortedArrDistanceLessK(arr4, 2);
        for (int i = 0; i < arr4.length; i++) {
            System.out.println(arr4[i]);
        }

        // 荷兰国旗问题
        int[] arr5 = {0, 2, 0, 0, 2, 2, 1, 2, 1, 0};
        int[] partition = netherlandsFlag(arr5, 0, arr5.length - 1, 1);
        for (int i = 0; i < arr5.length; i++) {
            System.out.println(arr5[i]);
        }
        for (int i = 0; i < partition.length; i++) {
            System.out.println(partition[i]);
        }

        // 快排
        int[] arr6 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
        quickSort(arr6, 0, arr6.length -1);
        for (int i = 0; i < arr6.length; i++) {
            System.out.println(arr6[i]);
        }
    }
}

 

posted @ 2022-03-12 13:25  北漂的尘埃  阅读(24)  评论(0编辑  收藏  举报