数据结构与算法

        一、ArrayList扩容规则:

1.使用add();方法进行元素添加:

(1)初始数组长度为0;无参构造数组长度为0;

(2)添加数据,先扩容10,之后扩容为原基础的1.5倍(准确说是位数转换为二进制数,右移一位再加上原位数);

(3)[0,10,15,22,33,49,73,109,163,244,366....]。

2.使用addAll();方法进行元素添加:

(1)初始数组长度为0;

(2)若添加元素个数小于基础扩容个数,则同add()方法扩容规则一致。若添加元素个数大于基础扩容个数,则在元素个数和相近的扩容个数里选较大值作为扩容个数。

二、复杂度分析: 

1.时间复杂度:

(1)估算方法:

  • 事后分析法(简单,但不可取);
  • 事前分析法(用)
  1. 随着输入规模的增大,算法的常数操作可以忽略不记;
  2. 随着输入规模的增大,与最高次项相乘的常数可以忽略;
  3. 最高次项的指数大的,随着n的增长,结果也会变得增长特别快;
  4. 算法函数中n的最高次幂越小,算法效率越高;

(2)大O记法

  • 在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n变化情况并确定T(n)的量级,就是算法的时间量度,记作T(n)=Of(n),他表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。
  1. 执行次数=执行时间。
  2. 随着规模n的增大,T(n)增长最慢的算法就是最优算法。
  • 大O记法规则:
  1. 用常数1取代运行时间中的所有加法常数;
  2. 在修改后的运行次数中,只保留高阶项;
  3. 如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数。
  4. 例如:3次=O(1);  n+3=O(n);  n^2+2=O(n^2);
  • 复杂度排序:O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)

2.空间复杂度:

三、排序

1.Comparable接口:

2.冒泡排序:

(1)比较原理:

  • 比较相邻的元素,如果前一个元素比后一个元素大,就交换这两个元素的位置;
  • 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值。

 图片来源:黑马程序员网络课程。

  •  冒泡排序最优化代码实现:每轮冒泡时,最后一次交换索引可作为下一轮冒泡的比较次数,如果这个值为0,表示整个数组有序,直接退出外层循环即可。

public class Joe {
    public static void main(String[] args){
        int[] a = {5,9,7,4,1,3,2,8};
        bubble(a);
}

    public static void bubble(int[] a){
        int n = a.length-1;
        while (true){
            int last = 0;  //表示最后一次交换索引位置
            for (int i = 0; i < n; i++) {
                System.out.println("比较次数"+i);
                if (a[i] > a[i+1]){
                    swap(a,i,i+1);
                    last = i;
                }
            }
            n=last;
            System.out.println(Arrays.toString(a));
            if (n == 0){
                break;
            }
        }
    }


    public static void swap(int[] a, int i ,int j){
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}

3.选择排序

(1)排序原理:

  • 将要排序的数组视作两个部分,已排序和未排序部分,从索引0的位置开始,作为最小值,一次与未排序部分的数组元素进行比较,如果有比原最小值更小的元素,则将最小元素交换,将新的最小值是做最小值继续向后比较,最终确定出数组中的最小值,将它与索引0处的元素位置互换,此时将最小值所在区域视为已排序部分,后续元素依次实现上述排序方法,知道整个数组已排序。

(2)选择排序和冒泡排序的比较:

  • 二者时间复杂度都是O(n^2);
  • 选择排序一般要快于冒泡,因为交换次数少;
  • 但如果集合有序度高,冒泡优于选择;
  • 冒泡属于稳定排序算法,而选择属于不稳定排序(选择排序如果同一组数据经历多次排序,后面的排序方法可能会打乱之前的排序结果。)

4.插入排序

(1)排序原理:

  • 同选择排序一样,分为有序区和无序区两部分;
  • 找到未排序区中的第一个元素,向已排序的组中进行插入;
  • 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放到这个位置,其他的元素向后移动一位。
  • 代码实现:
        public static void main(String[] args){
            int[] a = {5,9,7,4,1,3,2,8};
            insert(a);
    }
        public static void insert(int[] a){
            //i代表待插入的元素索引
            for (int i = 0; i < a.length; i++) {
                int t = a[i];    //代表待插入的元素值
                int j = i-1;     //代表已排序区的元素索引
                while (j >= 0){
                    if (t < a[j]){
                        a[j+1] = a[j];
                    }else {
                        break;    //退出循环,减少比较次数
                    }
                    j--;
                }
                a[j+1] = t;
                System.out.println(Arrays.toString(a));;
            }
        }

(2)与选择排序比较:

  • 二者时间复杂度都是O(n^2);
  • 大部分情况下,插入优于选择;
  • 有序集合插入的时间复杂度为O(n);
  • 插入属于稳定算法。

5.希尔排序

(1)排序原理: 

  • 选定一个增长量h,按照增长量h作为分组依据,对数据分组;
  • 对分好的每一组数据完成插入排序;
  • 减小增长量,最小减为1,重复上述操作

6.快速排序

(1)排序原理:

  • 每一轮选一个基准点进行分区
  1. 让小于基准点的元素进入一个分区,大于基准点的元素进行另一个分区;
  2. 当分区完成时,基准点元素的位置就是其最终位置;
  3. 在子分区内重复以上过程,直至子分区元素个数少于等于1。

(2)实现方式:

  • 单边排序快排(lomuto罗穆托分区方案)
  1. 选择最右元素作为基准点;
  2. j指针负责找到比基准点小的元素,一旦找到则与i进行交换;
  3. i指针维护小于基准点元素的边界,也是每次交换的目标索引;
  4. 最后基准点与i交换,i即为分区位置。
  5.  代码实现:
public static void main(String[] args){
        int[] a = {5,3,7,2,9,8,1,4};
        quick(a,0,a.length-1);
}

    public static void quick(int[] a,int l,int h){
        if (l >= h){
            return;
        }
        int p = partition(a, l, h); // p索引值
        quick(a,l,p-1);
        quick(a,p+1,h);
    }
    public static int partition(int[] a,int l,int h){
        int pv = a[h];
        int i = l;
        for (int j = l; j < h; j++) {
            if (a[j] < pv){
                if (i != j) {
                    swap(a, i, j);
                }
                i++;
            }
        }
        if (i != h) {
            swap(a, h, i);
        }
        System.out.println(Arrays.toString(a)+"i"+i);
        // 返回值代表基准点元素返回的正确索引,用它确定下一轮分区边界
        return i;
    }
  • 双边排序:
  1. 选择最左边元素作为基准点元素;
  2. j指针负责从右向左找比基准点小的元素,i指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至i,j相交。
  3. 最后基准点与i(此时的i与j相等)交换,i即为分区位置
  4. 实现:
@SuppressWarnings("all")
public class Joe {
    public static void main(String[] args){
        int[] a = {5,3,7,2,9,8,1,4};
        quick(a,0,a.length -1 );
}
    private static void quick(int[] a, int l, int h){
        if (l >= h){
            return;
        }
        int p = partition(a, l, h); // p索引值
        quick(a,l,p-1);
        quick(a,p+1,h);
    }

    private static int partition(int[] a, int l, int h){
        int pv = a[l];
        int i = l;
        int j = h;
        while (i < j){
            //j 从右找小的
            while (i< j && a[j] > pv){
                j--;
            }
            //i 从左找大的
            while (i < j && a[i] <= pv){
                i++;
            }
            swap(a, i ,j);
        }
        swap(a,l,j);
        System.out.println(Arrays.toString(a)+"j="+j);
        return j;
    }

(3) 特点:

  • 平均时间复杂度是O(nlog2n),最坏的时间复杂度是O(n^2);
  • 数据量较大时,优势非常明显;
  • 属于不稳定排序;

7.递归

(1)定义:

  • 程序调用自身的编程技巧称为递归
  • 递归的典型例子是数字的阶乘。数字 N 的阶乘是 1 到 N 之间所有整数的乘积。
@SuppressWarnings("all")
public class Joe {
    public static void main(String[] args){
        long result = factorial(10);
        System.out.println(result);
    }

    public static long factorial(int n){
        if (n == 1){
            return 1;
        }
        return n*factorial(n-1);
    }
}
  • 练习:使用递归列出windows目录下的所有文件夹及所有文件
     getAllFile(new File("D:\\WeddingPics"));
        }
    
                public static void getAllFile(File dir) {
                    File[] files = dir.listFiles();
                    if (files != null) {
                        for (File f : files) {
                            // 如果获取的File类型是目录,则进行递归调用
                            if (f.isDirectory()) {
                                System.out.println("目录:" + f);
                                getAllFile(f);
                            } else {
                                // 如果获取的File类型是文件,则直接打印输出
                                System.out.println("文件:" + f);
                            }
                        }
                    }
                }
            }
     

四、二叉树

1.二叉树入门

(1)树的基本定义:

  • 树是由n(n>=1)个有限节点组成的一个具有层次关系的集合。
  • 数具有以下特点:
  1. 每个结点有零个或者多个子结点;
  2. 没有父节点的结点为根结点;
  3. 每一个非根结点只有一个父结点;
  4. 每个结点及其后代结点整体上可以看作是一棵树,称为当前结点的父结点的一个子树;
  • 树的相关术语:
  1. 结点的度:一个结点含有的子树的个数称为该结点的度;
  2. 叶结点:度为0的节点称为叶结点,也可以叫做终端结点;
  3. 分支结点:度不为0的节点称为分支结点,也可以叫做非终端结点;
  4. 结点的层次:从根结点开始,根节点的层次为1,根的后继层次为2,以此类推;
  5. 结点的层序编号:从上到下,从左到右排成线性序列,编成连续的自然数;
  6. 树的度:树中所有结点的度的最大值;
  7. 树的高度(深度):树中结点的最大层次;
  8. 森林:m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树;
  9. 孩子结点:一个结点的直接后继;
  10. 双亲结点(父结点):一个结点的直接前驱;
  11. 兄弟结点:统一双亲结点的孩子结点间。

(2)二叉树的基本定义:

  • 度不超过2的树(每个节点最多有两个子结点);
  • 满二叉树:一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树;
  • 完全二叉树:叶结点只能出现在最下层和此下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

(3)二叉查找树的创建:

  • 二叉树的结点类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值