跟着左神学算法:P3 认识 O(NlogN) 排序

297 阅读2分钟

P3:认识 O(NlogN) 排序

用递归方法求数组内最大值

public int findMax(int[] array, int l, int r) {
    if (l == r) {
        return array[l];
    }
    int mid = l + (r - l) >> 1;
    int lMax = findMax(array, l, mid);
    int rMax = findMax(array, mid + 1, r);
    return Math.max(lMax, rMax);
}

求中点技巧

mid=(l+r)÷2mid = (l + r) \div 2

这个方法问题在于 (l + r) 可能会溢出,可以采用下面这个式子

mid=l+(rl)÷2mid = l + (r - l) \div 2

这样解决了溢出问题,可以再进一步

mid=l+((rl)>>1);mid = l + ((r - l) >> 1);

用移位运算代替除法,有可能会更快(具体要看编译器优化)

递归的深度

array = [1,2,3,4,5]
[1,2,3,4,5]
[1,2,3][4,5]
[1,2][3][4][5]
[1][2]

先计算左边节点,在计算右边节点,子节点向上返回,和二叉树的后序遍历相同,栈的最大深度就等于树的深度

master公式求递归时间复杂度

master公式

T(N)=aT(Nb)+O(Nd)T(N) = a * T(\frac{N}{b}) + O(N^d)

上面那个递归中,a = 2,b = 2,d = 0。符合 master 公式

  • log(b, a) > d 时间复杂度是
O(Nlog(b,a))O(N^{log(b, a)})
  • log(b, a) = d 时间复杂度是
O(NdlogN)O(N^d * logN)
  • log(b, a) < d 时间复杂度是
O(Nd)O(N^d)

归并排序(merge sort)

   public int[] mergeSort(int[] array, int l, int r) {
        if (l == r) {
            return array;
        }
        int mid = l + ((r - l) >> 1);
        int[] lArray = mergeSort(array, l, mid);
        int[] rArray = mergeSort(array, mid + 1, r);
        return merge(array, l, r, mid);
    }

    private int[] merge(int[] array, int l, int r, int mid) {
        int[] help = new int[r - l + 1];
        int lPoint = l;
        int rPoint = mid + 1;
        for(int t = 0; t < help.length; t++) {
            if(lPoint <= mid && rPoint <= r) {
                if(array[lPoint] < array[rPoint]){
                    help[t] = array[lPoint++];
                } else{
                    help[t] = array[rPoint++];
                }
            } else if(lPoint == (mid + 1) && rPoint <= r) {
                help[t] = array[rPoint++];
            } else if(rPoint == (r + 1) && lPoint <= mid>) {
                help[t] = array[lPoint++];
            }
        }
        for(int t = 0; t < help.length; t++) {
            array[l + t] = help[t];
        }
        return array;
    }

归并排序时间复杂度

a = b =2, d = 1; 时间复杂度 O(NlogN)

求一个数组的小和

现在有一个数组 [2,1,5,8,9,6,3,4],每个数字左边比它小的数就是它小和的个数,例如 6 左侧有 2,1,5 三个数,6的小和就是3,将数组中所有数的小和加起来就是数组的小和

public int smallSum(int[] array) {
    return mergeSort(array, 0, array.length - 1);
}

public int mergeSort(int[] array, int l, int r) {
    if (l == r) {
        return 0;
    }
    int mid = l + ((r - l) >> 1);
    int lSum = mergeSort(array, l, mid);
    int rSum = mergeSort(array, mid + 1, r);
    //把左子树的结果+右子树的结果+merge的结果返回
    return lSum + rSum + merge(array, l, r, mid);
}

private int merge(int[] array, int l, int r, int mid) {
    int[] help = new int[r - l + 1];
    int sum = 0;
    int lPoint = l;
    int rPoint = mid + 1;
    for(int t = 0; t < help.length; t++) {
        if(lPoint <= mid && rPoint <= r) {
            if(array[lPoint] < array[rPoint]){
                //和归并排序相比,添加小和计算
                sum += (r - rPoint + 1) * array[lPoint];
                help[t] = array[lPoint++];
            } else{
                help[t] = array[rPoint++];
            }
        } else if(lPoint == (mid + 1) && rPoint <= r) {
            help[t] = array[rPoint++];
        } else if(rPoint == (r + 1) && lPoint <= mid) {
            help[t] = array[lPoint++];
        }
    }
    for(int t = 0; t < help.length; t++) {
        array[l + t] = help[t];
    }
    return sum;
}

求逆序对数量(LeetCode剑指Offer51题)

    public int reversePairs(int[] nums) {
        if(nums == null || nums.length == 0) {
            return 0;
        }
       return recursion(nums, 0, nums.length - 1);
   }

   private int recursion(int[] nums, int l, int r) {
       if(l == r) {
           return 0;
       }
       int mid = l + ((r - l) >> 1);
       int lNum = recursion(nums, l, mid);
       int rNum = recursion(nums, mid + 1, r);
       return lNum + rNum + merge(nums, l, r, mid);
   }

   private int merge(int[] nums, int l, int r, int mid) {
       int[] help = new int[r - l + 1];
       int num = 0;
       int lPoint = l;
       int rPoint = mid + 1;
       for(int t = 0; t < help.length; t++) {
           if(lPoint <= mid && rPoint <= r) {
               if(nums[lPoint] > nums[rPoint]) {
                   num += (r - rPoint + 1);
                   help[t] = nums[lPoint++];
               } else {
                   help[t] = nums[rPoint++];
               }
           } else if(lPoint == (mid + 1) && rPoint <= r) {
                help[t] = nums[rPoint++];
           } else if(lPoint <= mid && rPoint == (r + 1)) {
               help[t] = nums[lPoint++];
           }
       }
       for(int t = 0; t < help.length; t++) {
           nums[l + t] = help[t];
       }
       return num;
   }

快速排序

问题一

给定一个数组 arr 和一个数 num,把小于等于 num 的数放在数组左边,把大于等于 num 的数放在数组右边

public int[] sort(int[] arr, int num) {
    if(arr == null || arr.length = 0) {
        return arr;
    }
    int tmp = 0;
    int l = 0;
    int r = arr.length - 1;
    while(l < r) {
        //左右指针一起向中间移动,找到左边大于 num 右边小于等于 num 的位置
        while(l < r && arr[l] <= num) {
            l++;
        }
        while(l < r && arr[r] > num) {
            r--;
        }
        //交换值
        tmp = arr[l];
        arr[l] = arr[r];
        arr[r] = tmp;
        l++;
        r--;
    }
}

问题二

给定一个数组 arr 和一个数 num,把小于 num 的数放在数组左边,把大于等于 num 的数放在数组右边,把等于 num 的数放在数组中间

    public int[] sort(int[] arr, int num) {
        //小于 num 的左边界
        int lBoard = -1;
        //大于 num 的右边界
        int rBoard = arr.length;
        //指针
        int lPoint = 0;
        while (lPoint < rBoard) {
            if (arr[lPoint] < num) {
                swap(arr, lBoard + 1, lPoint);
                lPoint++;
                lBoard++;
            } else if (arr[lPoint] == num) {
                lPoint++;
            } else {
                swap(arr, lPoint, rBoard - 1);
                rBoard--;
            }
        }
        return arr;
    }

    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

快排算法(qsort)

    public int[] qsort(int[] arr) {
        if(arr == null || arr.length <2) {
            return arr;
        }
        return qsort(arr, 0, arr.length - 1);
    }
    public int[] qsort(int[] arr, int l, int r) {
        if(l == r) {
            return arr;
        }
        int num = arr[l];
        int[] board = partition(arr, l , r, num);
         if (board[0] >= l && board[0] <= r) {
            qsort(arr, l, board[0]);
        }
        if (board[1] >= l && board[1] <= r) {
            qsort(arr, board[1], r);
        }
        return arr;
    }

    private int[] partition(int[] arr, int l, int r, int num) {
        //小于 num 的左边界
        int lBoard = l - 1;
        //大于 num 的右边界
        int rBoard = r + 1;
        //指针
        int lPoint = l;
        while (lPoint < rBoard) {
            if (arr[lPoint] < num) {
                swap(arr, lBoard + 1, lPoint);
                lPoint++;
                lBoard++;
            } else if (arr[lPoint] == num) {
                lPoint++;
            } else {
                swap(arr, lPoint, rBoard - 1);
                rBoard--;
            }
        }
        int[] res = new int[]{lBoard, rBoard};
        return res;
    }

    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }