首页 > 编程语言 >数据结构与算法

数据结构与算法

时间:2024-10-20 17:21:55浏览次数:3  
标签:arr null 删除 int 算法 targeNode 数据结构 节点

  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;
                        }
                    }
                }
            }
        }
        

标签:arr,null,删除,int,算法,targeNode,数据结构,节点
From: https://blog.csdn.net/SOS_suyan/article/details/143032856

相关文章

  • 【多种改进粒子群算法进行比较】基于启发式算法的深度神经网络卸载策略研究【边缘计算
    ......
  • 基于C++的 BP/CNN神经网络算法(不调包)
    目前玩深度学习的小伙伴,上来就是使用现有的深度学习框架(TensorFlow,keras,pytorch,caffe),增加网络层,就像搭积木似的,看似方便,实则有时不利于个人能力发展,要知道现在公司需要的算法工程师,不仅仅只是会搭积木(这种工作,入门几个月的人就可以干了),而是要深入底层,能优化代码,能自己搭。本文......
  • qt图像算法—图像的缩放之c++实现(不调包)
     1.基本原理  图像的缩放一般使用插值算法,而本章将介绍两种常用插值算法:最临近插值法和双线性插值法  1.最临近插值法  将浮点数的位置坐标,进行四舍五入找到原图像的整型坐标即可,具体操作可见下面的公式,其中原图像坐标为(x,y),输出图像坐标为(i,j),比例系数为fx和fy。......
  • qt图像算法—图像的种子算法之c++实现(不调包)
     1.基本原理  相互连通且颜色相近的像素集合可以被看成图像的区域,而区域填充就是将每一块图像区域用指定颜色填充,填充的算法有很多种,但今天的猪脚是种子算法。在使用种子算法的时候,我们要注意两点,第一点:连通像素的搜索分为四方向和八方向,根据应用自己选择就行;第二点:边界......
  • 代码随想录算法训练营第五天| 面试题02.07.链表相交、leetcode142 环形链表II
    1.leetcode面试题02.07.链表相交题目链接:面试题02.07.链表相交-力扣(LeetCode)文章链接:代码随想录1.1代码跟着老师写的一个版本,自己能理解思路了,但是写的话可能还是有一些难#Definitionforsingly-linkedlist.#classListNode:#def__init__(self,x):#......
  • 八大排序算法
    冒泡排序最简单的排序方法之一,且看其定义。定义:冒泡排序(BubbleSort)是一种简单的排序算法。它重复地遍历待排序的列表,比较每对相邻的项目,如果它们的顺序错误就把它们交换过来。遍历列表的工作是重复地进行直到没有再需要交换,也就是说该列表已经排序完成。这个算法的名字由来......
  • K近邻算法(KNN)的概述与实现
    K近邻算法(K-NearestNeighbors,简称KNN)是一种简单而有效的机器学习算法,广泛应用于分类和回归问题中。KNN的主要特点是不需要对数据进行显式的模型训练,它是一种基于实例的学习方法。当给定一个未标记的数据点时,KNN算法会寻找其在训练集中最接近的K个邻居,并根据这些邻居的标签来决......
  • 多任务学习算法在推荐系统中的应用
    粗略来看,推荐算法可以简单地分为召回和排序两个阶段。召回模块负责从海量的物品库里挑选出用户可能感兴趣的物品子集,过滤之后通常返回几百个物品。排序模块负责对召回阶段返回的物品集个性化排序,通常返回几十个物品组成的有序列表。总结起来,召回和排序有如下特点:召回层:候选集规......
  • 【大数据分析与挖掘算法】matlab实现——DBSCAN聚类方法
    实验六:DBSCAN聚类方法一、实验目的掌握DBSCAN聚类方法的基本理论,通过编程对实例进行聚类。二、实验任务对DBSCAN聚类方法进行编码计算,实例如下:三、实验过程1.DBSCAN聚类模型介绍:2.具体步骤介绍:四、实验结果实现平台:Matlab2022A实验代码:%示例数据data=......
  • C++编程-贪心算法2
    目录先言例题三:删数问题(NOI1994)题目描述算法分析标准程序-字符串String例题四:拦截导弹问题题目描述算法分析主要框架(标准程序)例题五:活动选择题目描述算法分析标准程序先言今天讲贪心算法的第3~5例题例题三:删数问题(NOI1994)题目描述【题目描述】输......