笔记:数据结构与算法

时间:2019-10-31
本文章向大家介绍笔记:数据结构与算法,主要包括笔记:数据结构与算法使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

数据结构与算法

常用排序算法

实现比较丑陋,勿喷啊

  • 冒泡排序:从前向后比较相邻的元素。如果前一个比后一个大,就交换他们两个,每一轮把一个最大的数运到数组最后面。

    public static int[] sort(int[] arr) {
        int len = arr.length;
        // 冒泡总次数
        for (int i = 1; i < len; i++) {
            boolean flag = true;
            // 每次冒泡过程
            for (int j = 0; j < len - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    MyUtils.swap(arr, j, j + 1);
                    flag = false;
                }
            }
            if (flag) {
                // 如果一个冒泡过程没改变,退出返回已经有序
                break;
            }
        }
        return arr;
    }
  • 选择排序:每次从未排序数组中找一个最小的元素,放到以有序数组后面

    public static int[] sort(int[] arr) {
        int len = arr.length;
        // 选择次数
        for (int i = 0; i < len - 1; i++) {
            int min = i;
            // 每次选择过程
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[min]) {
                    min = j;
                }
            }
            if (min != i) {
                MyUtils.swap(arr, i, min);
            }
        }
        return arr;
    }
  • 插入排序:每次把未排序的第一个数,插入到已排序数组的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

    public static int[] sort(int[] arr) {
        int len = arr.length;
        // 插入次数,left为未有序的左边
        for (int left = 1; left < len; left++) {
            int temp = arr[left];
            int right = left - 1;
            // right为有序部分的右边
            while (right >= 0 && temp < arr[right]) {
                arr[right + 1] = arr[right];
                right--;
            }
            // 判断是否需要插入
            if (right != left - 1) {
                arr[right + 1] = temp;
            }
        }
        return arr;
    }
  • 归并排序:将数组分成很多小份,然后依次合并

    public static int[] sort(int[] arr) {
        sort(arr, 0, arr.length - 1);
        return arr;
    }
    
    private static void sort(int[] arr, int left, int right) {
        if (left == right) {
            return;
        }
        // 等同于(right + left)/2
        int mid = left + ((right - left) >> 1);
        sort(arr, left, mid);
        sort(arr, mid + 1, right);
        // 已经分成了许多小份,开始合并
        merge(arr, left, mid, right);
    }
    
    private static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        // 左边右边通过辅助数组合并
        while (p1 <= mid && p2 <= right) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        // 左边没空加到后面
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        // 右边没空加到后面
        while (p2 <= right) {
            help[i++] = arr[p2++];
        }
        for (int j = 0; j < help.length; j++) {
            arr[left + j] = help[j];
        }
    }
  • 荷兰国旗问题:给定一个整数数组,给定一个值K,这个值在原数组中一定存在,要求把数组中小于K的元素放到数组的左边,大于K的元素放到数组的右边,等于K的元素放到数组的中间,最终返回一个整数数组,其中只有两个值,分别是等于K的数组部分的左右两个下标值

    public static int[] sort(int[] arr) {
        partiton(arr, 0, arr.length - 1);
        return arr;
    }
    
    public static int[] partiton(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right + 1;
        int pNum = arr[right];
        while (left < more) {
            if (arr[left] < pNum) {
                MyUtils.swap(arr, ++less, left++);
            } else if (arr[left] > pNum) {
                MyUtils.swap(arr, --more, left);
            } else {
                left++;
            }
        }
        return new int[]{less, more};
    }
  • 快速排序:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作,递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

    // 基于荷兰国旗问题的快排
    public static int[] sort(int[] arr) {
        sort(arr, 0, arr.length - 1);
        return arr;
    }
    
    public static void sort(int[] arr, int left, int right) {
        if (left < right) {
            int[] pIndexs = DutchFlag.partiton(arr, left, right);
            sort(arr, left, pIndexs[0]);
            sort(arr, pIndexs[1], right);
        }
    }
  • 堆排序:先建立大根堆,然后不停做heapify,也就是把未有序的最后一位和堆首互换,然后调整堆结构

    public static int[] sort(int[] arr) {
        int len = arr.length;
        buildBigHeap(arr, len);
        while (len > 0) {
            MyUtils.swap(arr, 0, --len);
            heapify(arr, 0, len);
        }
        return arr;
    }
    
    // 建立大根堆
    public static void buildBigHeap(int[] arr, int len) {
        for (int index = 0; index < arr.length; index++) {
            while (arr[index] > arr[(index - 1) / 2]) {
                MyUtils.swap(arr, index, (index - 1) / 2);
                index = (index - 1) / 2;
            }
        }
    }
    
    // 调整堆
    private static void heapify(int[] arr, int currRoot, int len) {
        int left = currRoot * 2 + 1;
        int right = currRoot * 2 + 2;
    
        while (left < len) {
            int largest = right < len && arr[left] < arr[right] ? right : left;
            largest = arr[largest] > arr[currRoot] ? largest : currRoot;
            if (largest == currRoot) {
                break;
            }
            MyUtils.swap(arr, currRoot, largest);
            currRoot = largest;
            left = currRoot * 2 + 1;
            right = currRoot * 2 + 2;
        }
    }

二叉树

前序 中序 后续 层级遍历

public static void pre(TreeNode root) {
    if (root != null) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        // 先进右再进左
        while (!stack.isEmpty()) {
            root = stack.pop();
            System.out.print(root.val + " -> ");
            if (root.right != null) {
                stack.push(root.right);
            }
            if (root.left != null) {
                stack.push(root.left);
            }
        }
    }
    System.out.println();
}

public static void preReur(TreeNode root) {
    if (root == null) {
        return;
    }
    System.out.print(root.val + " -> ");
    preReur(root.left);
    preReur(root.right);

}
public static void mid(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    // 左走到头了开始弹,然后去右
    while (root != null || !stack.isEmpty()) {
        if (root != null) {
            stack.push(root);
            root = root.left;
        } else {
            root = stack.pop();
            System.out.print(root.val + " -> ");
            root = root.right;
        }
    }
    System.out.println();
}


public static void midReur(TreeNode root) {
    if (root == null) {
        return;
    }
    midReur(root.left);
    System.out.print(root.val + " -> ");
    midReur(root.right);
}
public static void post(TreeNode root) {
    // 把线序遍历反过来,得到前右左,然后再反过来变成左右前
    if (root != null) {
        Stack<TreeNode> stackStack = new Stack<>();
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            root = stack.pop();
            stackStack.push(root);
            if (root.left != null) {
                stack.push(root.left);
            }
            if (root.right != null) {
                stack.push(root.right);
            }
        }
        while (!stackStack.isEmpty()) {
            System.out.print(stackStack.pop().val + " -> ");
        }
    }
    System.out.println();
}

public static void postReur(TreeNode root) {
    if (root == null) {
        return;
    }
    postReur(root.left);
    postReur(root.right);
    System.out.print(root.val + " -> ");
}
public static void level(TreeNode root) {
    if (root == null) {
        return;
    }
    LinkedList<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    TreeNode curr = null;
    while (!queue.isEmpty()) {
        curr = queue.pop();
        System.out.print(curr.val + " -> ");
        if (curr.left != null) {
            queue.add(curr.left);
        }
        if (curr.right != null) {
            queue.add(curr.right);
        }
    }
}

算法验证对数器

  • 准备样本随机生成器
  • 准备一个绝对正确但是复杂度不好的算法
  • 将待验证算法和绝对正确算法压测,比较

主定理与递归时间复杂度的计算

  • 主定理:如果有一个问题规模为 n,递推的子问题数量为 a,每个子问题的规模为n/b(假设每个子问题的规模基本一样),递推以外进行的计算工作为 f(n)(比如归并排序,需要合并序列,则 f(n)就是合并序列需要的运算量),那么对于这个问题有如下递推关系式:
  • 然后就可以套公式估算递归的时间复杂度

B树和B+树定义与区别

  • M阶B树
    • 定义
      • 任意非叶子结点最多只有M个儿子,且M>2
      • 根结点的儿子数为[2, M]
      • 除根结点以外的非叶子结点的儿子数为[M/2, M],向上取整
      • 非叶子结点的关键字个数=儿子数-1
      • 所有叶子结点位于同一层
      • k个关键字把节点拆成k+1段,分别指向k+1个儿子,同时满足查找树的大小关系
    • 特征
      • 关键字集合分布在整颗树中
      • 任何一个关键字出现且只出现在一个结点中
      • 搜索有可能在非叶子结点结束
      • 其搜索性能等价于在关键字全集内做一次二分查找
  • M阶B+树
    • 定义
      • 有n棵子树的非叶子结点中含有n个关键字(b树是n-1个),这些关键字不保存数据,只用来索引,所有数据都保存在叶子节点(b树是每个关键字都保存数据)
      • 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接
      • 所有的非叶子结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字
      • 通常在b+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点
      • 同一个数字会在不同节点中重复出现,根节点的最大元素就是b+树的最大元素
    • 特征
      • b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”
      • b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定(并不慢)
      • 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历

并查集

  • 用于解决
    • 两个元素是否在同一个集合(优化,查的过程中把路过的节点直接连头节点)
    • 合并两个元素所在的集合
  • 实现
    • 数组
    • 双HashMap

红黑树

  • 特点
    • 每个节点非红即黑
    • 根节点总是黑色的
    • 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
    • 每个叶子节点都是黑色的空节点
    • 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

跳跃表

  • 特点

    • 最底层包含所有节点的一个有序的链表
    • 每一层都是一个有序的链表
    • 每个节点都有两个指针,一个指向右侧节点(没有则为空),一个指向下层节点(没有则为空)
    • 必备一个头节点指向最高层的第一个节点,通过它可以遍历整张表

前缀树/字典树(Trie)

  • 用于解决
    • 常用于快速检索
    • 大量字符串的排序和统计
  • 基本性质
    • 根节点不包含字符,除根节点外每个节点只包含一个字符
    • 从根节点到某个节点,路径上所有的字符连接起来,就是这个节点所对应的字符串
    • 每个节点的子节点所包含的字符都不同

如何从暴力递归改动态规范

  • 首先写好一个暴力递归
    • 分析这个递归是否有重复计算
    • 分析这个递归的当前状态和之前递归计算的顺序是不是无关
    • 如果都满足就可以改成动态规划
  • 改写成DP
    • 找出递归中变化的参数
    • 确定递归的开始位置
    • 确定一些边界或者特殊情况
    • 抽象出一次递归的步骤,分析步骤和已经固定的边界的关系
    • 找出规律后coding

布隆过滤(Bloom Filter)

  • 解决问题:判断一个元素是否在一个集合中,优势是只需要占用很小的内存空间以及有着高效的查询效率

  • 原理:保存了很长的二进制向量,同时结合 Hash 函数实现

    • 首先需要k个hash函数,每个函数可以把key散列成为1个整数
    • 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
    • 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
    • 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
  • 特点

    • 只要返回数据不存在,则肯定不存在
    • 返回数据存在,只能是大概率存在
    • 不能清除其中的数据
  • 计算误差

    • 先根据样本大小n,可以接受的误差p,计算需要申请多大内存m

    • 再由m,n得到hash function的个数k

    • 再计算实际的误差p

原文地址:https://www.cnblogs.com/freshchen/p/11771840.html