zoukankan      html  css  js  c++  java
  • 第一章:基础算法

    第一章:基础算法

    排序

    快速排序

    分治算法

    1. 确定分界点x(有三种方法,a、直接取左边界q[l],b、取中间值q[((l +r)/ 2)], c、随机)
    2. 调整区间,将区间划分为两段,左边所有的数都是小于等于x,右边所有的数大于等于x
    3. 递归处理左右两段
    public void quickSort(int q[], int l, int r){
        if (l >= r) return;
    		//数值x为分界数
      	//移动策略:左右两个指针都是先分别往右、往左移动,再进行比较,所以这里给i和j的初始赋值是l-1,和 r+1
        int i = l - 1, j = r + 1, x = q[l + r >> 1];
       //while循环的目的在于使得下标i左边所有的数都是小于x的,j下标右边所有的数都是大于x的
        while (i < j){
            do i ++ ; while (q[i] < x);
            do j -- ; while (q[j] > x);
            if (i < j) {
    					//交换去q[i] 与 q[j]
              q[i] = q[i] ^ q[j];
              q[j] = q[i] ^ q[j];
              q[i] = q[i] ^ q[j];
            }
        }
      	//递归
        // 注意:这里j不能换成i
        quickSort(q, l, j);
      	quickSort(q, j + 1, r);
    }
    

    归并排序

    1. 确定分界点:(mid = (l +r) / 2)
    2. 递归排序 leftright,使得leftright 有序
    3. 归并,将leftright 两个有序的数列合并成一个有序的数组
    /**
    * 归并排序代码模板
    * @param q 待排序的模板
    * @param l 起始点下标
    * @param r 终点下标
    */
    void mergeSort(int q[], int l, int r){
        if (l >= r) return;
    		
      	//中间结点下标
        int mid = l + r >> 1;
        mergeSort(q, l, mid);
        mergeSort(q, mid + 1, r);
    
        int k = 0, i = l, j = mid + 1;
      	//比较left 和 right 的值,将较小的值放到tmp中
        while (i <= mid && j <= r) {
            if (q[i] < q[j]) {
              	tmp[k ++ ] = q[i ++ ];
            } else {
             		tmp[k ++ ] = q[j ++ ]; 
            }
        }
    
    		//rigth 部分已经遍历完了,但是 left 还剩余部分,则将left 剩下的数放到tmp中
        while (i <= mid) {tmp[k ++ ] = q[i ++ ];}
        //left 部分已经遍历完了,但是 right 还剩余部分,则将right 剩下的数放到tmp中
        while (j <= r) {tmp[k ++ ] = q[j ++ ];}
        //将已经排序好的临时变量tmp,复制到数组q中
        for (i = l, j = 0; i <= r; i ++, j ++ ) {q[i] = tmp[j];}
    }
    

    堆排序

    /**
     * 堆分两种:
     * 1、小根堆
     *          此代码思路:
     *              先将数组视为完全二叉树,将该完全二叉树转化为小根堆。在小根堆的基础上进行排序
     *           但是根据上课老师的思路:
     *              实现堆还存在另外一种思路:
     *                          那就是边遍历数组边新建小根堆
     * 2、大根堆
     *          思路同上
     */
    import java.util.*;
    public class Sort {
        public static void main(String[] args) {
            int[] arr2 = new int[]{3, 2, 3, 1, 2, 4, 5, 5, 6};
            heapSort(arr2);
            System.out.println(Arrays.toString(arr2));
        }
    
        /**
         * 堆排序,(大堆根)从小到大排序
         *
         * @param array
         */
        public static void heapSort(int[] array) {
            //1、把无序数组构建成最大堆
            /*
             * 注意:
             *   for循环中i的值为什么是从(array.length / 2)开始
             *   根据堆排序的思想,我们知道要从最后一个非叶子节点开始进行下沉调整,然后向前遍历
             *   所以:i = array.length / 2 -1 ,且 i--
             * */
            for (int i = array.length / 2 - 1; i >= 0; i--) {
                downAdjust(array, i, array.length - 1);
            }        
    
            //2、循环删除堆顶元素,移到集合尾部,调整堆产生新的堆顶
            for (int i = array.length - 1; i > 0; i--) {
                //最后一个元素和第一个元素进行交换
                int temp = array[i];
                array[i] = array[0];
                array[0] = temp;
                //"下沉"调整最大堆
                downAdjust(array, 0, i - 1);
            }
    
        }
    
        /**
         * "下沉"调整
         *
         * @param array       待调整堆
         * @param parentIndex 要下沉的父节点
         * @param endIndex    堆的有效大小
         */
        public static void downAdjust(int[] array, int parentIndex, int endIndex) {
            //temp保存父节点值,用于最后的赋值,
            int temp = array[parentIndex];
            //根据父节点的位置计算出子节点的位置
            //下面计算的是左孩子的位置
            int childIndex = 2 * parentIndex + 1;
    
            /*
                当childIndex < length 成立时,存在左孩子
                如果也存在右孩子,先定位到左右孩子中较大的那一项
            */
            while (childIndex <= endIndex) {
                if (childIndex + 1 <= endIndex && array[childIndex + 1] > array[childIndex]) { //取当前节点中较大的那个
                    childIndex++;
                }
                /*
                    1、若父节点大于孩子节点,则退出当前循环
                    2、若父节点小于孩子节点,我们一开始想到的就是将父节点与孩子的值进行交换,
                    但是,我们应该考虑到该函数的作用在于:将父节点下沉到最小的位置上
                    如果孩子节点还存在着孩子节点,我们暂且称之为孙子节点,
                    若孙子节点的值是也是大于父节点,那么我们应当将父节点下沉到孙子节点处,
                    以此类推,直到父节点大于子节点,或是不存在子节点,才结束
                    3、最后将父子节点值赋值到子节点
                */
                if (temp > array[childIndex]) { //tmp 为父节点
                    //父节点大于左右子节点
                    break;
                } else {
                    //父节点小于左右子节点
                    array[parentIndex] = array[childIndex];
                    parentIndex = childIndex;
                    childIndex = 2 * parentIndex + 1;
                }
            }
            array[parentIndex] = temp;
        }
    }
    

    二分

    如果有单调性那么肯定可以二分,如果没单调性,也可以二分。二分的本质是边界,而不是单调性

    整数二分

    // 检查x是否满足某种性质
    //x 为下标
    bool check(int x) {
      /* ... */
    } 
    
    // 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
    int binarySearch01(int l, int r){
        while (l < r)
        {
            //区别
            int mid = l + r >> 1;
            if (check(mid)) r = mid;    //check()判断mid是否满足性质
            else l = mid + 1;
        }
        return l;
    }
    
    // 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
    int binarySearch02(int l, int r){
        while (l < r){
          	//区别
            int mid = l + r + 1 >> 1;
            if (check(mid)) l = mid; //check()判断mid是否满足性质
            else r = mid - 1;
        }
        return l;
    }
    

    练习:789. 数的范围

    浮点数二分

    //检查x是否满足某种性质
    bool check(double x) {
      /* ... */
    } 
    
    double binarySearch03(double l, double r){
        const double eps = 1e-8;   // eps 表示精度,取决于题目对精度的要求
        while (r - l > eps)
        {
            double mid = (l + r) / 2;
            if (check(mid)) r = mid;
            else l = mid;
        }
        return l;
    }
    

    前缀和

    一维前缀和

    //下标一定从1开始
    S[0] = 0;//可以帮助处理边界
    S[i] = a[1] + a[2] + ... a[i]
    a[l] + ... + a[r] = S[r] - S[l - 1]
    

    二维前缀和

    //S[i, j] = 第i行j列格子左上部分所有元素的和
    //注意:数组S下标都是从1开始,以避免边界问题
    S[i,j] = S[i - 1, j] + S[i, j - 1] - S[i - 1, j - 1] + a[i][j]
      
    //以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
    S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
    //因为S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] 多减了一个S[x1 - 1, y1 - 1], 所以最后加上了
    //S[x1 - 1, y1 - 1]
    

    差分

    一维差分

    给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
    

    二维差分

    给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
    S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
    

    双指针算法

    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        //O(n^2)
      }
    }
    

    双指针算法的核心在于优化暴力搜索,将其时间复杂度O(n^2),转化成O(n)

    //i,j均为下标
    for (int i = 0, j = 0; i < n; i ++ ){
      	//check(i,j) 检查i,j之间存在的某种关系是否成立
        while (j < i && check(i, j)) j ++ ;
    
        // 具体问题的逻辑
    }
    //常见问题分类:
    //    (1) 对于一个序列,用两个指针维护一段区间
    //    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
    

    位运算

    求n的二进制数的第k位数字: n >> k & 1
    返回n的最后一位1(最右边的1)的二进制数:lowbit(n) = n & -n == n & (~n + 1)
    举例:
    		x = 10100 lowbit(x) = 100
    		x = 10010 lowbit(x) = 10
    

    离散化

    ArrayList<Integer> alls = new ArrayList<>(); // 存储所有待离散化的值
    Collections.sort(all); // 将所有值排序,从小到大
    alls.subList(0, unique(alls));   //提取非重复元素
    
    // 二分求出x对应的离散化的值
    int find(int x){ // 找到第一个大于等于x的位置
        int l = 0, r = alls.size() - 1;
        while (l < r){
            int mid = l + r >> 1;
            if (alls.get(mid) >= x) r = mid;
            else l = mid + 1;
        }
        return r + 1; // 映射到1, 2, ...n
    }
    
    //去重
    //将不重复的数放到List的前部分,返回不重复元素的最右边的下标
    //注意:此时的list已经被排序过了
    static int unique(List<Integer> list) {
        int j = 0;
        for (int i = 0; i < list.size(); i++) {
          if (i == 0 || list.get(i) != list.get(i - 1)) {
            list.set(j, list.get(i));
            j++;
          }  
        }
        return j;
    } 
    

    区间合并

    // 将所有存在交集的区间合并
    void merge(ArrayList<Pairs> segs){
      	//存储结果
        ArrayList<Pairs> res;
      
    		//按照Pairs默认的排序方法进行排序
        Collections.sort(seg);
    		
      	//st为起始, ed为结束
        //我们维护的临时区间的左右位置
        int st = -2e9, ed = -2e9; // -2e9: 2 * 10 ^ 9;
        //遍历
        for (Pairs seg : segs)
            //维护的区间的最右边位置小于seg的最左边的位置,那么则把该区间加入到答案中
            if (ed < seg.first){
                if (st != -2e9) {
    	             res.add(new Pairs(st, ed)); 
                }
                //更新临时区间
                st = seg.first, ed = seg.second;
            }else {
               //存在交集,更新区间最右边
    	         ed = Math.max(ed, seg.second); 
            }
    		//防止输入数组里是没有任何区间的
        if (st != -2e9) res.add(new Pairs(st, ed));
    
        segs = res;
    }
    
    //存储区间的start和end
    class Pairs implements Comparable {
        int first;
        int second;
    
        public Pairs(int first, int second) {
            this.first = first;
            this.second = second;
        }
      
        //添加从小到大排序的方法,当first相同时,则按照second从小到大排序
      	//那么Collection的排序会按照该方法排序
        @Override
        public int compareTo(Object o) {
            Pairs pairs = (Pairs) o;
            if (pairs.first < first)
                return 1;
            else if (pairs.first == first) {
                if (pairs.second < second)
                    return 1;
                else {
                    return -1;
                }
            } else {
                return -1;
            }
        }
    }
    
  • 相关阅读:
    常见sql注入的类型
    sql-labs 18-20(sqlmap注入)
    sqli-labs11-17(手注+sqlmap)
    Android Zygote进程启动分析
    android点击桌面App图标activity启动流程
    Android启动过程
    android主线程ActivityThread
    mac上写c++
    Gradle发布项目到 maven 之novoda/bintray-release(3)
    Gradle发布项目到 maven 之gradle-bintray-plugin(2)
  • 原文地址:https://www.cnblogs.com/zhangyuestudying/p/13449656.html
Copyright © 2011-2022 走看看