数据结构与算法

  1. 数据结构:研究数据在内存中存储的结构
  2. 算法:实现增删改查的方法
    1. 解决问题的实际方法
    2. 算法的好坏评价标准:时间复杂度和空间复杂度
  3. 时间复杂度:算法所用时间的一个映射
    1. 时间复杂度得出的是一个数学问题
    2. 数据总量 x (x 足够大),从而消耗的执行次数 y 之间存在的关系

LeetCode 算法题目:

  1. 达到一百道题目,一天三道题
  2. 达到三百道题目,一天五道题
  3. 达到五百道题目,一天十道题

数组代码讲解:

  1. 当创建对象时,对象里面的所有东西都会被创建
    1. 当有数组时,也会创建,基本类型数组默认值为 0
  2. 当方法操作的全局变量时只能操作对象里的变量
  3. 当引用类型不再被指引,Java 会触发回收机制
  4. 大括号:在遇到 } 时,里面新声明的变量要出栈
  5. 数组:
    1. 数组删除数据不是弹栈形式,而是改变有效边界,此数据还在该位置,只是无效。等下一个数据直接覆盖它
    2. 基本数据类型的一维数组:物理地址必须连续
    3. 其他复杂型数组逻辑上连续
    4. 无序数组:
      1. 删除:直接把最后的数据和删除的数值交换,再改变边界值
  6. 内存的重要性和代码速度严格挂钩

数组:

  1. 无序数组:
    1. 查询时间复杂度:O(n)
  2. 有序数组:
    1. 二分查找时间复杂度:O(log n)
    2. 当无序数组变得有序数组之后,需要消耗 O(N log n) 的时间,有些得不偿失
  3. 时间复杂度大小关系:
    1. O(1) < O(long n) < O(n) < O(n long n) < O(n^2)
  4. 哈希表(H):
    1. 通过 n % arr.length 公式为下标存储数据
  5. 二叉树:
    1. 特点:每一个叶子节点的路径都是不重复的
    2. 满二叉树、完全二叉树、···
    3. 时间复杂度不稳定:介于 O(log n) - O(n)
  6. 平衡二叉树:
    1. 在二叉排序树的基础之上而来
    2. 要求左右子树的高度(层数) 差绝对值不能超过 1,一旦超过就会触发平衡策略
    3. 平衡策略:LL、LR、RL、RR 型旋转
    4. 不平衡时:
      1. 时间复杂度:接近 O(log n)
      2. 平衡调整太浪费系统资源
    5. 步骤:
      1. 不平衡的点朝着造成不平衡的节点走两步
      2. 选择造成不平衡最近的节点进行处理:
      3. RL旋转:
      4. LR 旋转:后两个节点整体旋转
  7. 红黑树:
    1. 最优二叉树 ---> 内存最优树
    2. 时间复杂度:接近 O(log n),且比较稳定
    3. 特点:
      1. 红黑树的跟节点一定是黑色的
      2. 红黑树的叶子节点是黑色的(系统自带的叶子节点)
      3. 从根节点出发,到任意一个叶子节点,所走过的路径上黑色的数量是相同的
      4. 红色节点的下一个节点一定是黑色节点
    4. 结论:
      1. 最长路径不会超过最短路径的 2 倍

链表:

  1. 链表是由节点组成
  2. 创建链表:
    1. 单项链表:有数值域和 next 域
    2. value:存储此节点的值
    3. next:存储下一节点的地址
  3. 构造器:
    1. 创建对象的时候,给对象赋初始值
    2. 名字与类名相同,没有返回值
  4. 引用数据类型在内存中存储地址时,必须和自己本身类型相同
    1. 在内存中存储方式相同

排序算法:

  1. 冒泡排序:
    1. 前后两个数值之间进行对比排序
      public void sort(int[] arr){
              for (int i = 0; i < arr.length; i++) {
                  for (int j = 0; j < arr.length - 1 - i; j++) {
                      if(arr[j] > arr[j+1]){
                          int tmp = arr[j];
                          arr[j] = arr[j+1];
                          arr[j+1] = tmp;
                      }
                  }
              }
          }
  2. 快速排序:
    public static void sort(int[] arr,int left,int right){
        if(left >= right){
            return;
        }
    
        int i = left;    //定义左游标
        int j = right;    //定义右游标
        int base = arr[left];    //定义基准数
    
        while(i != j){
            while (arr[j] >= base && i<j){    //找到比基准数小的值
                j--;    
            }
            while (arr[i] <= base && i<j){    //找到比基准数大的值
                i++;
            }
    
            swap(arr, i,j);    //交换两值
        }
        
        //交换基准数
        arr[left] = arr[i];
        arr[i] = base;
    
        sort(arr,left,i-1);    //排序左数组
        sort(arr,i+1,right);    //排序右数组
    }
    
    public static void swap(int[] arr,int i,int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
  3. 选择排序(简单选择排序):
    1. 找到待排序数组中最小的值,将其放入待排序数组的首位进行交换
      1. 最小值:比较查找
  4. 插入排序:
    1. 把当前排序数组中的值插入到已经排好的数组中
      1. 假设数组中第一个元素是已经排好序的
      2. 在待排序数组中挨个插入,如果比排好序的数值大,就将数值进行交换,再插入数值
    2. 缺点:
      1. 插入排序过程中,如果后边的数值比较小,前边的数值比较大
      2. 就可能导致小数据向前移动次数比较多,进而把整个算法的效率降低
  5. 希尔排序:
    1. 将小数据尽量向前移动
    2. 步骤:
      1. 分组根据数组长度而定,一般是:2,4,6,8,···
      2. 两两分组,间隔是数组长度的一半
      3. 四四分组,在组内进行排序
      4. ·······
    3. 注意递归停止条件,注意越界
    4. 步骤1:
      1. 假设将待排序数组中的第一个数作为基准数
      2. 定义 i,j 游标,分别指向待排序数组的第一位和最后一位
      3. 让 j 游标先移动,去找比当前基准数小的数据,找到后停止
      4. 让 i 游标移动,找比当前基准数大的数据,找到后停止
      5. 判断两个游标是否相遇:
        1. 未相遇:i 和 j 指向的值进行交换,继续执行3,4,5步
        2. 相遇:相遇位置的数和基准数进行交换
          1. 此时基本数的左边都比基准数小,右边都比基准数大
      6. 步骤2:
        1. 以基本数为界,将数组拆分左右两部分,重新执行步骤1
        2. 当拆分成每个数组只有一个值时,就变得有序了
    5. 时间复杂度:n log n
  6. 归并排序:
    1. 对数组进行拆分,当拆分的数组只有一个元素的时候停止
    2. 开始向上排序合并
  7. 基数排序(桶的思想):
    1. 有九个桶
    2. 先按个位排序,将个位相同的数据放入相同的桶
    3. 然后按桶的从左向右顺序,从一个桶中的数据从小到大挨个取出组成数组
    4. 再按十位排序,将十位相同的数据放入相同的桶,再和同上步骤取出
      public static void radixSort(int[] arr) {
          //找最大数
          int max = arr[0];
          for (int i = 1; i < arr.length; i++) {
              if (arr[i] > max) {
                  max = arr[i];
              }
          }
      
          int count = (max + "").length();  //排序次数
      
          int[][] sorkets = new int[10][arr.length];  //十个桶
          int[] sorketcount = new int[10];    //每个桶有效个数
      
      
          for (int k = 0, n = 1; k < count; k++, n *= 10) {   //控制排序次数
              //将元素放入桶
              for (int j = 0; j < arr.length; j++) {
                  int num = arr[j] / n % 10;
                  sorkets[num][sorketcount[num]++] = arr[j];
              }
      
              //取出元素
              int index = 0;
              for (int i = 0; i < sorketcount.length; i++) {
                  //判断当前桶中是否有元素
                  if (sorketcount[i] != 0) {
                      for (int j = 0; j < sorketcount[i]; j++) {
                          arr[index++] = sorkets[i][j];
                      }
                      sorketcount[i] = 0;     //取空元素后将有效个数置为0
                  }
              }
          }

递归算法:

  1. 本质:
    1. 方法调用其本身(有爆栈情况)
    2. 必须有判断方法的出口
  2. 作用:
    1. 做循环
  3. 递归的思想:
    1. 反向思考
  4. 递归的实现:
    1. 确定递归函数的参数和返回值以及返回值类型
      1. 变量有哪些参数就有哪些
    2. 找到递归的终止条件
    3. 递归的逻辑实现

哈夫曼编码以及二叉树的遍历:

  1. 哈夫曼编码:
    1. 来自于哈夫曼树(本质是二叉树)
    2. 对传输的数据进行压缩
  2. 哈夫曼树:
    1. 特点:
      1. 哈夫曼二叉树的特点:        
        1. 整棵树的带权路径和是最小的
        2. 带权路径:权值 * 从根节点开始到节点走过的节点树
      2. 要求所有子节点都必须有权值:
        1. 权值越大的值离根节点越近
      3. 权值的意义:
        1. 当权值越大,分配的路径越短
  3. 哈夫曼树的构建:
    1. 从权值小的开始向上构建
  4. 哈夫曼编码的构建:
    1. 假设左边走是1,右边走是 0
    2. 到 y 的路径就是:1001
  5. 二叉树的遍历:
    1. 深度优先遍历:
      1. 先序遍历:根节点先左子树,再右子树
      2. 中序遍历:先左,后中,再右遍历
      3. 后序遍历:先左,后右,再中遍历
    2. 广度优先遍历:
      1. 借助队列进行:先进先出

栈和队列:

  1. 栈:
    1. 特点:先进后出
    2. 实现:数组或链表实现
    3. 出栈没有真正意义的删除,只是游标向前移动
  2. 队列:
    1. 本质:先进先出

泛型:

  1. 泛型:泛指一切类型
  2. 使用方法:
    1. 在类名后面加 <E> 符号
    2. 在创建对象时要声明创建的类型
      1. 创建对象的类型应该是包装类型

B树:

  1. 作用:主要用于磁盘当中做数据查询
  2. 特点:
    1. 高度低,节约寻址次数
    2. 每个节点最多有 M-1 个 key 值,并且可以升序排列
      1. key值:帮助节点排序
    3. 每个节点最多能有 M 个子节点
    4. 根节点至少有两个子节点
  3. 和红黑树对比:
    1. B树:节省寻址次数,增加对比次数
    2. 红黑树:节省对比次数,增加寻址次数
  4. 优先级:
    1. 每个节点都包含 key 和 value,因此我们根据 key 查找 value 时,只需要找到 key 点在的位置,就能找到 value 值
    2. 但 B+ 树只有叶子节点存储数据,索引每一个次查找,都必须一次一次的查找,一直找到树的最深处,也就是叶子节点的深度,才能找到 value 值

B+树:

  1. 特点:
    1. 非叶子节点只能存储 key 值,不存储 value 值
    2. 树的所有叶子节点构成一个有序链表,可以按照 key 排序的次序依次遍历全部数据
  2. 优点:
    1. B+ 树非叶子节点上不包含真正的数据,只当做索引使用,因此在内存相同的情况下,能存放更多的 key 值
    2. B+ 树的叶子节点都是相连接的,因此对整棵树的遍历只需要一次线性遍历叶子节点即可
    3. 由于数组顺序排列并且相连,所以以便于区间查找和搜索。
      1. 而 B树则只需要进行每一层的递归遍历
    4. B+ 树是数据库的底层数据结构
      1. 来提高数据库的查询效率
    5. B+ 树在数据库中的应用
      1. 可以基于某张表的某个字段建立索引,提高查询效率
      2. 未建立索引查询:
        1. 查询数据时需要从第一条数据开始,一直查询找到该值才停止
      3. 建立索引:只要找到比查找值小的节点的 key 值,依次向后遍历链表查找即可,效率非常高

有序二叉树的构建和遍历:

  1. 构建方式:
    1. 递归方式
    2. 非递归方式
  2. 步骤:
    1. 首先判断当前节点要和插入的节点的大小
    2. 如果小于当前节点,判断左子树是否有数据
      1. 没有数据时直接插入
      2. 如果有数据,向左走,重复上述 1,2 步骤
    3. 如果大于当前节点,判断左子树是否有数据
      1. 没有数据时直接插入
      2. 如果有数据,向左走,重复上述 1,2 步骤
        public class Tree {
            public TreeNode root;
        
            @Override
            public String toString() {
                return "Tree{" +
                        "root=" + root +
                        '}';
            }
        
            public void insert(int val){
                TreeNode newNode = new TreeNode(val);
        
                if(root == null){
                    root = newNode;
                    return;
                }
        
                TreeNode tempNode = root;
        
                while(true){
                    //插入值小于当前节点
                    while(newNode.value <= tempNode.value){
                        if(tempNode.left == null){
                            tempNode.left = newNode;
                            return;
                        }else{
                            tempNode = tempNode.left;
                        }
                    }
        
                    //插入值大于当前节点值
                    while(newNode.value > tempNode.value){
                        if(tempNode.right == null){
                            tempNode.right = newNode;
                            return;
                        }else{
                            tempNode = tempNode.right;
                        }
                    }
                }
            }

完全二叉树的删除:

  1. 计算机中没有真正意义上的删除,只是把指针置空
  2. 步骤:
    1. 删除叶子节点:
      1. 找到想要删除的节点 targeNode
      2. 找删除的节点是否有父节点 parent(父节点是否存在)
      3. 要删除的节点是父节点的左子树还是右子树
      4. 如果左子树,则 parent.left = null
      5. 如果是右子树,则 parent.right = null
    2. 删除只有一个子节点的节点:
      1. 找到要删除的节点是否 targeNode 
      2. 找删除的节点是否有父节点 parent(父节点是否存在)
      3. 确定该节点待删除的节点是有左子树还是右子树
      4. 要删除的节点是父节点的左子树还是右子树
      5. 如果删除的节点是父节点的左子树,且被删除的节点有左子树:
        1. parent.left = targeNode.lenft
      6. 如果删除的节点是父节点的左子树,且被删除的节点有右子树:
        1. parent.left = targeNode.right
      7. 如果删除的节点是父节点的右子树,且被删除的节点有左子树:
        1. parent.right = targeNode.left
      8. 如果删除的节点是父节点的右子树,且被删除的节点有右子树:
        1. parent.right = targenNode.right
    3. 删除有两个子树的节点:
      1. 找到要删除的节点 targeNode
      2. 找到要删除的节点的父节点 parent (考虑父节点是否存在)
      3. 从 targeNode 找到右子树最左侧的节点
        1. 或者左子树最右侧的节点
      4. 用临时变量 temp 记录该值
      5. 删除最小节点
      6. 将要删除的节点的值替换为 temp
            //删除节点
            public void delete(TreeNode node,int val){
                //判空
                if(node == null){
                    return;
                }
        
                //删除根节点
                if(node.left == null && node.right == null){
                    node = null;
                    return;
                }
                //查找删除节点
                TreeNode targeNode = findTargeNode(node,val);
                if(targeNode == null){
                    System.out.println("没有该节点");
                    return;
                }
        
                //查找父节点
                TreeNode parentNode = findParent(node,val);
                if(parentNode == null){
                    System.out.println("没有找到父节点");
                }
        
                if(targeNode.left == null && targeNode.right == null){  //删除叶子节点
                    //父节点的左子节点
                    if(parentNode.left != null && parentNode.left.value == val){
                        parentNode.left = null;
                    }else if(parentNode.right != null && parentNode.right.value == val){  //该节点为父节点的右子节点
                        parentNode.right = null;
                    }
                } else if (targeNode.left != null && targeNode.right != null) { //待删除节点有两个子节点
                    int data = findRightMin(targeNode.right);
                    targeNode.value = data;
                }else {     //删除有一个子节点的节点
                    //当父节点的左节点是删除节点时
                    if(parentNode.left != null && parentNode.left.value == val){
                        //删除节点有左孩子
                        if(targeNode.left != null){
                            parentNode.left = targeNode.left;
                            targeNode = null;
                        }else {     //有右孩子
                            parentNode.left = targeNode.right;
                            targeNode = null;
                        }
                    } else {    //父节点的右节点为删除节点
                        //删除节点有左孩子
                        if(targeNode.left != null){
                            parentNode.right = targeNode.left;
                            targeNode = null;
                        }else {     //有右孩子
                            parentNode.right = targeNode.right;
                            targeNode = null;
                        }
                    }
                }
            }
        }
        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值