Java教程

数据结构与算法 学习笔记

本文主要是介绍数据结构与算法 学习笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

油管上的CS61B的视频
学习代码

随看随记

  • Java 值传递 赋值
  • 对于递归来讲,最重要的就是 base case(基础)和 neighbor(邻居)
  • 对于List的递归 尽量去判断其列表本身 list 多去迭代几次而不是 其list.rest
  • 对于List的迭代 尽量用迭代器 并不适用this 关键字
  • 静态方法只能改变静态变量,因为要遵守黄金法则。
  • java.util.Arrays.equals 可以比较两个数组内所有的项的内容是否相等。
  • 对于一个SLList来讲,SLList的地址是sentinel的地址,通过sentinel来对整个包装列表进行操作。

Hello World

  • Java中所有的代码都必须写在Class中
  • 在这里插入图片描述
  • .java -> 编译(javac)->.class->解释器 -> 运行代码

Defining and Using Classes

  • 当人为编译 DogLauncher时 java会自动编译Dog 因为DogLauncher需要有Dog依赖才能继续运行
  • 在这里插入图片描述
  • 1
  • 仅仅声明 并未实例化
  • 仅仅实例化 但是没有对象赋值 无法使用 会直接被java当作垃圾回收
  • 将一个已经实例化以后的狗分配给已经声明的变量

References, Recursion, and Lists

  • 在这里插入图片描述

  • 205.75 被储存到32位中 前16位是小数点前 后16位是小数点后

  • 在这里插入图片描述
    在这里插入图片描述

  • 声明 和 new 的创建对象

Java List 链表表示

原始List创建

  • 在这里插入图片描述
  • IntList内存可视化
  • 正向创建链表 从开头开始 正向建表 到结束。

带有构造函数的List创建

在这里插入图片描述

  • IntList内存可视化
  • 反向列表 从结束开始创立 从写法上 感觉这种更加简单

获取List的长度l.size()(递归方法)

-在这里插入图片描述

  • size方法递归可视化
  • 通过多次递归 寻找IntList中的null 在返回时 依次叠加

获取List的长度l.size()(迭代方法)

  • 在这里插入图片描述

  • size方法迭代可视化

  • 在 iterativeSize中 p 可以直接通过l的指针this来得到 我好久没遇见这种写法了

总结练习

  • 在效率等方面 迭代更好 但是递归的代码更简洁
  • 在这里插入图片描述

-get的递归写法内存图解

  • 注意学习一下递归的写法,要注意base case(基本盘)和neighbor(邻居 ),将beighbor想 base case上靠即可。

习题

  • 两个函数的可视化
  • 主要是迭代在List中的利用
  • 迭代要针对当前list 而不是而不是list.rest
  • List的迭代不要使用this 关键字

SLList 列表的包装类

  • 设计LIst包装类的目的就是 隐藏掉对象 只通过索引就可以找到相应的存储内容。

addFirst() 和 getFirst()

  • 其很重要的一点就是利用 addFirst 和 getFirst 来进行列表的填充和获取列表的首个的内容
  • 上面两个方法的可视化

安全与密封

  • 通过将IntNode 嵌入到SLList中来进行包装 并将其设置成私有和静态 来拒绝外部的直接访问,提高了SLList的安全性
public class MySLList {

    // 在外界不需要关注并且需要保护的情况下 需要将其设置成私有
    // 在外界不需要操作 嵌套类的时候 可以将其设置成 静态 static
    // 将static 看成 never looks outwards 
    private static class MyIntNode {

        public int item;
        public MyIntNode next;

        public MyIntNode(int i, MyIntNode n) {
            item = i;
            next = n;
        }
    }

    private MyIntNode first;

    public MySLList(int x) {
        first = new MyIntNode(x, null);
    }

    /**
     * 返回列表中的第一项
     */
    public int getFirst(){

        return first.item;
    }

    /**
     * 添加一个int 到list的最前面
     */
    public void addFirst(int x){
        first = new MyIntNode(x, first);
    }


    public static void main(String[] args) {
        MySLList L = new MySLList(10);
        L.addFirst(15);

    }
}

addLast() 和 size()

  • 两个方法的可视化 size的迭代的方法也在里面
  • 在包装后类如果想实现递归 就需要 建立一个私有的对外非公开的方法来代替原有的方法进行递归操作 用其输入参数的来进行递归操作
  • 个人感觉在包装以后 迭代操作会更加容易一点 但是递归需要建立私有静态类的思想要了解。

Cache

  • 使用递归来获得size的方法复杂度过高 因此引入size成员变量 来进行缓存 每次进行 add操作的时候仅仅需要操作size get的时候仅仅需要 return this.size 就可以了 大大减小了时间的消耗

Empty List

  • 主要解决的问题 在存在空构造之后 会有可能存在第一个节点的对象为null,需要额外用if语句进行条件判断。如下所示,因此引入哨兵节点。
  • 内存可视化
/**
 * @description:
 * @program: CS61B
 * @author: paleatta
 * @date: 2021-05-18 09:38
 * @version: jdk1.8
 **/
public class MySLList {

    // 在外界不需要关注并且需要保护的情况下 需要将其设置成私有
    // 在外界不需要操作 嵌套类的时候 可以将其设置成 静态 static
    // 将static 看成 never looks outwards
    private static class MyIntNode {

        public int item;
        public MyIntNode next;

        public MyIntNode(int i, MyIntNode n) {
            item = i;
            next = n;
        }

    }

    private MyIntNode first;
    private int size;

    public MySLList() {
        first = null;
        size = 0;
    }

    public MySLList(int x) {
        first = new MyIntNode(x, null);
        size = 0;
    }

    /**
     * 添加一个int到list的最前面
     */
    public void addFirst(int x) {
		first = new MyIntNode(x, first);
        size++;
    }

    /**
     * 迭代
     * 添加一个int到list的最后面
     */
    public void addLast(int x) {

        if (first == null){
            first = new MyIntNode(x, null);
            return ;

        }
        MyIntNode p = first;
        while (p.next != null) {
        	p = p.next;
        }

            p.next = new MyIntNode(x, null);
        size++;
    }

    /**
     * 通过缓存来减小时间上的消耗
     */
    public int cacheSize() {
        return size;

    }
    public static void main(String[] args) {
        MySLList L1 = new MySLList();
        L1.addFirst(10);
        L1.addFirst(5);
        L1.addFirst(20);
        int size = L1.cacheSize();
        System.out.println(size);

    }
}

Sentinel Node

  • 对于SLList来说,用哨兵节点来当作第一个List的结点,在空构造中使用,将哨兵节点的next置为空而不是直接将first置为null,从而避免了空指针开始的问题。应该也是目前List第一个都是无用指针的之一吧。
  • 带有哨兵节点的可视化
/**
 * @description:
 * @program: CS61B
 * @author: paleatta
 * @date: 2021-05-18 09:38
 * @version: jdk1.8
 **/
public class MySLList {

    // 在外界不需要关注并且需要保护的情况下 需要将其设置成私有
    // 在外界不需要操作 嵌套类的时候 可以将其设置成 静态 static
    // 将static 看成 never looks outwards
    private static class MyIntNode {

        public int item;
        public MyIntNode next;

        public MyIntNode(int i, MyIntNode n) {
            item = i;
            next = n;
        }

    }

    private MyIntNode sentinel;
    private int size;

    public MySLList() {
        //这里的-1 是随意的 可以随便取 因为用不到
        sentinel = new MyIntNode(-1, null);
        size = 0;
    }

    public MySLList(int x) {
        sentinel = new MyIntNode(x, null);
        size = 1;
    }

    /**
     * 返回列表中的第一项
     */
    public int getFirst() {

        return sentinel.next.item;
    }

    /**
     * 添加一个int到list的最前面
     */
    public void addFirst(int x) {
        sentinel.next = new MyIntNode(x, sentinel.next);
        size++;
    }

    /**
     * 迭代
     * 添加一个int到list的最后面
     */
    public void addLast(int x) {


        MyIntNode p = sentinel.next;
        while (p.next != null) {
            p = p.next;
        }

        p.next = new MyIntNode(x, null);


        size++;
    }

    /**
     * 通过缓存来减小时间上的消耗
     */
    public int cacheSize() {
        return size;

    }


    /**
     * 老师的版本
     * 主要是由与SLList本身不是MyIntNode类
     * 无法进入递归 因此由多写了一个私有的对外部不开放的函数
     * 用其参数的来进行递归
     */
    public int size() {
        return size(sentinel.next);
    }

    private static int size(MyIntNode p) {
        if (p.next == null) return 1;
        return size(p.next) + 1;
    }


    /**
     * 递归
     * 返回SLList的长度
     */
    public int MySize() {

        int size = 0;
        MyIntNode p = sentinel.next;
        while (p != null) {
            size++;
            p = p.next;
        }
        return size;
    }


    public static void main(String[] args) {
//        MySLList L = new MySLList(15);
//        L.addFirst(10);
//        L.addFirst(5);
//        L.addFirst(20);


        MySLList L1 = new MySLList();
        L1.addFirst(10);
        L1.addFirst(5);
        L1.addFirst(20);

        int size = L1.size();
        System.out.println(size);

    }
}
  • 哨兵节点中要注意:
  1. 在原来SLList中是使用first作为链表的第一个节点,但是在这里的第一个节点不是sentinal,而是sentinal.next,哨兵节点本身不作为链表中的一员,仅仅作为开头,放置出现null而产生了,其item也毫无作用
  2. 只要生成以一个SLList 其sentinel的地址是不会改变的,因此可以将其设置为final

Invariants

在这里插入图片描述

DLList

introduce

  • 对于原来的单向列表来讲,在针对于最后一位,或者列表中的某一位进行添加、更新或者删除时需要迭代整个链表,比较复杂。在更新或添加最后节点虽然可以用添加Last缓存进行加速,但是对其他的操作依然无法优化。
    private final MyIntNode sentinel;
    private MyIntNode last;
    private int size;
  • 因此引入双向链表DLList

Doubly Linked Lists

  • 因为是双向链表,在最开始时候,list的的头尾的next可能是null,也可能是sentinel node ,为了解决这个问题:
    -在这里插入图片描述
  • 在引入双向链表时要注意最后一个的节点的问题有以上两种方案
  • 和前面的哨兵节点一样,在链表的最后添加一个哨兵节点。
  • 将最后的哨兵节点的next和sentinel.next相连接,变成循环链表。
  • 在这里插入图片描述

Generic (泛型)

  • 在以前我们建立的list仅仅能用于int类型,但是数据类型有很多,因此引入泛型来让其更加通用
public class MySLList<Generic> {

    // 在外界不需要关注并且需要保护的情况下 需要将其设置成私有
    // 在外界不需要操作 嵌套类的时候 可以将其设置成 静态 static
    // 将static 看成 never looks outwards
    private class MyGenericNode {

        public Generic item;
        public MyGenericNode next;

        public MyGenericNode(Generic i, MyGenericNode n) {
            item = i;
            next = n;
        }

    }

    private final MyGenericNode sentinel;
    private MyGenericNode last;
    private int size;
 }

array Overview

  • 在这里插入图片描述
  • 数组有长度 但是数组没有方法 创建时 有且仅有一个引用。
  • 2D Arrays

二维数组的内存显示

  • array vs class
  • 对于array来讲 其只是一段连续的存储空间,其寻找存储内容需要序号
  • 对于class来讲 在不使用反射的条件下 其是一段强编码后进行存储 其只能对于属性进行操作 无法对于索引进行操作
    他们都作为引用类型 其在栈中的存储类型都是32位的引用地址。
  • java vs other language
  • 与其他语言的数组相比,Java数组:
    对于“slicing”没有特殊的语法(例如在Python中)。
    无法缩小或扩展(例如在Ruby中)。
    没有成员方法(例如Javascript)。
    必须仅包含相同类型的值(与Python不同)。

AList

Why Use Array

  • 对于双向列表来说 很难实现用索引来寻找列表中相应的对象,其中一种的解决方法就是借助数组的结构进行实现。

Alist

  • 最基础的Alist版本
public class MyAList {

    private int[] items;
    private int size;

    public MyAList() {

        items = new int[100];
        size = 0;

    }

    //添加到末尾 扩容时默认扩容100
    public void addLast(int item) {
        items[size] = item;
        size++;
    }

    public int getLast() {
        return items[size - 1];
    }

    public int get(int index) {
        return items[index];
    }

    public int size() {
        return size;
    }
}
  • removeLast 删除嘴都一项
  • 在AList中是通过Size确定数组的长度的 虽然最后一个item没有修改 但是在使用时已经将其忽略掉 并且在下次使用addLast的时候会将其覆盖掉 因此可以互用管它 也可以像注释里一样 设计一个逻辑删除位 但是不是必要的。
    /**
     * removeLast()
     */
    public int removeLast() {

        int x = items[size];
        //items[size - 1] = 0;
        size--;

        return x;
    }
  • Alist的扩容问题
  • 数组的长度在内存中本身是固定的 我们看起来长度在变化是将size作为AList的长度来看待。
  • 当数组的长度不足时 需要进行扩容补充。
  • 我看有一种代码风格是写两个重载函数,一个有参,一个缺参,缺少参数的是走默认配置,完全参数的是呗其他调用,类似这样,不知道这样是不是一个好习惯。
  • 添加一个数组是我自己加的 还没有进行测试。
    /**
     * 默认扩容100
     */
    private void resize() {
        resize(size + 100);
    }

    /**
     * 调整容量到目标容量
     * @param capacity
     */
    private void resize(int capacity) {

        int[] temp = new int[capacity];
        System.arraycopy(items, 0, temp, 0, size);
        items = temp;

    }

    //添加到末尾 扩容时默认扩容100
    public void addLast(int item) {

        if (size >= items.length) resize();
        items[size] = item;
        size++;
    }

    //添加数组
    public void addLast(int[] is) {

        if (size + is.length >= items.length) resize(size + is.length);

//        items[size] = item;

        System.arraycopy(is, 0, items, size, is.length);
        size += is.length;
    }

resize的速率问题

  • 每次达到数组大小限制时,便开始调整数组大小。这非常慢,因为将数组复制到新数组意味着我们必须对每个项目执行复制操作。最糟糕是,由于我们仅调整了一个额外的框的大小,因此,如果我们选择添加另一项,则每次添加到数组时都必须再次执行此操作。

  • 相较于LinkedList来说 AList每次都需要扩容和复制,花费的时间更多,而LinkedList来说,更多的时间是操作指针,其复杂度不高,花费的时间也就更少。

  • 在这里插入图片描述

  • 提高调整大小性能而不是添加一个额外的框,我们可以创建一个包含size * FACTOR项的新数组,其中FACTOR可以是任何数字,例如2。我们将在本课程的稍后部分讨论为什么这样做很快。

  • 原来的扩容是+size 是线性的 现在用* 是几何增长 大大减小了时间

    private void resize(int capacity) {

        int[] temp = new int[capacity];
        System.arraycopy(items, 0, temp, 0, size);
        items = temp;

    }

Generic 通用AList

  • 将智能存储整数型转化成能存储通用类型的数组。

主要注意以下几点

  • java不允许创建泛型数组对象 因此只能创建object[] 并将其类型强制转化成Generic[]
  • removeLast() 在只有int类型是我们认为可以不对超出的size的那一位进行操作,但是在Generic中,将超出的那一位置为null可以让GC回收其引用对象在堆中的垃圾,节约空间
public class MyAList<Generic> {

    private Generic[] items;
    private int size;

    public MyAList() {

        items = (Generic[]) new Object[100];
        size = 0;

    }

    /**
     * 默认扩容100
     */
    private void resize() {
        resize(size * 2);
    }

    /**
     * 调整容量到目标容量
     *
     * @param capacity
     */
    private void resize(int capacity) {

        Generic[] temp = (Generic[]) new Object[capacity];
        System.arraycopy(items, 0, temp, 0, size);
        items = temp;

    }

    //添加到末尾 扩容时默认扩容100
    public void addLast(Generic item) {

        if (size >= items.length) resize();
        items[size] = item;
        size++;
    }

    //添加数组
    public void add(Generic[] is) {

        if (size + is.length >= items.length) resize(size + is.length);

//        items[size] = item;

        System.arraycopy(is, 0, items, size, is.length);
        size += is.length;
    }


    public Generic getLast() {
        return items[size - 1];
    }

    public Generic get(int index) {
        return items[index];
    }

    public int size() {
        return size;
    }

    /**
     * removeLast()
     */
    public Generic removeLast() {

        Generic returnItem = items[size];
        items[size] = null;
        size--;

        return returnItem;
    }
}

Testing

JUnit

  • 关于assert的小知识
	import static org.junit.Assert.*;
	//对两个数组进行断言判定 
	//如果两个数组相同 那么通过
	//不相同则返回异常
  	assertArrayEquals(expected, input);
  • 其效果和下面是一样的在这里插入图片描述

选择排序 Selection Sort

  • 在这里插入图片描述

在这里插入图片描述

  • 选择排序的编写加测试的步骤如上
public class MySort {
    /**
     * 实现选择排序一共由三个步骤
     * 1 寻找最小的项
     * 2 把它移动到最前面
     * 3 重复进行1和2步骤 (使用递归?)
     * @param x
     */
    //对外排序的接口 其实际是调用 sort重载方法来实现的
    public static void sort(String[] x ){
        sort(x,0);
    }
    private static void sort(String[] x ,int start) {
        if (start == x.length) return;
        /*1. 寻找最小的项目*/
        int smallest = findSmallest(x , start);
        /*2. 把它移动到最前面*/
        swap(x,start,smallest);
        /*3. 递归排序剩下的部分*/
        sort(x,start+1);
    }
    /*1. 寻找最小的项目,从start开始*/
    private static int findSmallest(String[] x , int start) {
        int smallestIndex = start;
        for (int i = start; i < x.length; i++) {
            int flag = x[smallestIndex].compareTo(x[i]);
            if (flag > 0) smallestIndex = i;
        }
        return smallestIndex;
    }
    /*2. 交换指定的两个item*/
    private static void swap(String[] x, int a, int b) {
        String temp = x[a];
        x[a] = x[b];
        x[b] = temp;
    }
}

Inheritance, Implements

继承与重载

  • 两者各有用武之地
  • 对于继承来讲 在有上下级关系的时候比较好用 例如课中的AList 和SLList实现了(继承的一种)List中的方法,可以减少写重载函数的代码,看起来更加美观,逻辑上也更加舒服。
  • 重载是两个方法之间,根据签名不同进行区分的相同函数名的方法。重载主要强调参数不同,继承主要强调上下级关系。
  • 继承一般来说都是使用实现的子类或继承的子类。在子类中创建对象后通过调用方法,对父类的函数进行重写。
	//声明中虽然使用的是List 是父类 ,但是实际上是调用了子类的对象,因此用父类的声明,但是走的是子类的方法。
	// 这里可以结合java堆栈的知识相关先联想一下,就比较容易理解。
    List<String> someSLList = new SLList<>();
    List<String> someAList = new AList<>();

default Methods and override default method

  • 可以通过default实现接口的向下重写 这个意思表示 在AList对象,还是在SLList对象中都会调用接口的print方法
public interface List<Item> {

    default public void  print(){
        for (int i = 0; i < size(); i++) {
            System.out.print(get(i));
        }
        System.out.println();
    }
}


    public static void main(String[] args) {
        List<String> someSLList = new SLList<>();
        someSLList.addFirst("elk1");
        someSLList.addLast("dwell");
        someSLList.addLast("on");
        someSLList.addLast("existential");
        someSLList.addLast("crises");
        someSLList.print();

        List<String> someAList = new AList<>();
        someAList.addFirst("elk2");
        someAList.addLast("dwell2");
        someAList.addLast("on");
        someAList.addLast("existential");
        someAList.addLast("crises");
        someAList.print();

    }
  • 其 输出是这样的 (先不要关注elk2 没有 这是小问题)在这里插入图片描述
  • 由于这么写对于SLList的print效率很低,因此需要在SLList对print方法进行重写。
	@Override
	public void print() {
	 	System.out.println("THIS IS THE OVERRIDDEN VERSION.");
	 	Node p = sentinel.next;
	 	while (p != null) {
	 		System.out.print(p.item + " ");
	 		p = p.next;
		}
	}

    public static void main(String[] args) {
        List<String> someSLList = new SLList<>();
        someSLList.addFirst("elk1");
        someSLList.addLast("dwell");
        someSLList.addLast("on");
        someSLList.addLast("existential");
        someSLList.addLast("crises");
        someSLList.print();

        List<String> someAList = new AList<>();
        someAList.addFirst("elk2");
        someAList.addLast("dwell");
        someAList.addLast("on");
        someAList.addLast("existential");
        someAList.addLast("crises");
        someAList.print();

    }
  • 在AList中没有写print方法,因此 在AList中不会打印语句
    在这里插入图片描述
  • 因此我认为在SLList中编写了print 方法后会,default关键字会失效 虽然在List中有其声明 但是 其已经不走父类的方法了 只看继承对象的方法了。

Dynamic Methods Selection and Overrided

  • 在这里插入图片描述
  • 在java语言中,包括静态类型和动态类型。静态类型是生命是的类型,其声明以后是不可以改变的。动态类型是在new的时候创建的,是动态类型的,可以接收继承自他的类型。在后面进行调用的时候就是调用对象(动态)的方法。动态类型是runtime类型。
  • 在这里插入图片描述
  • greet只在父类中存在。sniff在父类中有,但是由于子类也有sniff且签名相同,因此在Dog类中将其重写了。在flatter方法中,因为其签名不相同,因此没有将其重写,可以当作重载函数处理,父类的入参是Animal类型,Dog的入参是Dog类型。

Conclusion

  • 对于继承来说,其最重要的是实现类之间的关系,而不是某个类用有哪些方法。但是接口也可以方便去拓展功能。
  • 在这里插入图片描述
  • 在这里插入图片描述

Extends

  • 在这里插入图片描述
	//构造函数也可以这么写
    private final SLList<Item> lostItems = new SLList<>();
    //=========================================
    //可以不写super 在编译的时候会自动使用父类的无参构造,写不写super();都一样。
        public VengefulSLList() {
//        super();
        lostItems = new SLList<>();
    }

继承打破

  • 在这里插入图片描述
  • 当进入bark方法时,会调用this.barkMany的方法,因为动态类型是子类,因此会走重写后的方法,因此会再次进入重写后的barkMany方法,因此会进入死循环。

保守编译与强制转换

  • 编译器会非常保守的仅使用声明的静态类型进行编译判定。因此会出现动态类型可以通过,但是由于静态类型中不存在此方法而编译失败。以及在类型转换的时候,父类的对象无法转换成声明的子类类型。
  • 在这里插入图片描述
  • 强制转换cast,强制转换是程序员麻痹编译器的一种方式,有时强制转换是父类到子类,可以转换,但是有时是两个不相关的类,无法转换,这是很危险地。因此需要小心使用强制转换。

方法传递

  • Java中的方法传递通常使用调用对象方法去进行获得。

  • 通过实现接口的方式,对子类进行方法的调用,以及对象方法的调用。也就是我们常说的多态性。

  • 在这里插入图片描述

Conlusion

  • 在这里插入图片描述

编译器规则

Compiler allows the memory box to hold any subtype.
Compiler allows calls based on static type.
Overriden non-static methods are selected at runtime based on dynamic type.
For overloaded methods, the method is selected at compile time.

  • 在这里插入图片描述
  • 在代码中不应该出现
  • 超类和子类有相同的变量名。
  • 超类和子类有相同的静态方法,且其前名相同。这是override是不生效的,因为override只对成员函数生效。

interface 的多态

  • 在这里插入图片描述
  • 在这其中的本质是虽然其声明的静态类型有所变化,但是其动态类型(runtime type) 是没有变化的,一直是Dog类型。因此其虽然在代码的过程中其声明类型变化,但是其可以转换回Dog类型。
  • 其感觉更像是,Dog在实现了Comparable 这个接口以后,可以通过Comparable 的类型对Dog类的对象进行比较。

Comparable 的好处

  • 主要是为了解决类型的强制转换问题,以及无法转化的类型被要求强制转化而导致的error。
  • 可以通过增加泛型来减少强制转换在这里插入图片描述
  • 在这里插入图片描述

Comparator 比较器

  • Comparator 和 Comparable的区别,比较器是生成一个比较器对象,用来比较两个对象。comparable 是提供一个对象,将其和自己进行比较。
  • 实现了比较器以后,两个对象之间的比较只需要通过比较器即可。通过调用比较器的方法来实现比较。
  • 一般来说,比较器是私有的,大都仅通过类的静态方法来获取一个比较器。
public class MyDog implements Comparable<MyDog> {
    public String name;
    private int size;

    public MyDog(String name, int size) {
        this.name = name;
        this.size = size;
    }

//    @Override
    public int MyCompare(Object item) {
        MyDog dog = (MyDog) item;
        return size - dog.size;
    }

    @Override
    public int compareTo(MyDog dog) {
        return size - dog.size;
    }

    private static class NameComparator implements Comparator<MyDog> {
        @Override
        public int compare(MyDog o1, MyDog o2) {
        	// 这里的compareTo不是上面的方法
        	// 是实现了Comparator以后的自带的方法
            return o1.name.compareTo(o2.name);
        }
    }

    public static Comparator<MyDog> getNameComparator(){
        return new NameComparator();
    }
}
public class MyMaximizer {
    public static Comparable max(Comparable[] items){
        int maxDex = 0;
        for (int i = 0; i < items.length; i += 1) {
            int cmp = items[i].compareTo(items[maxDex]);
            if (cmp > 0) {
                maxDex = i;
            }
        }
        return items[maxDex];
    }
}
public class MyDogLauncher {
    public static void main(String[] args) {
        MyDog d1 = new MyDog("Elyse", 3);
        MyDog d2 = new MyDog("Sture", 9);
        MyDog d3 = new MyDog("Benjamin", 15);
        MyDog[] myDogs = new MyDog[]{d1, d2, d3};

        MyDog d = (MyDog) MyMaximizer.max(myDogs);
        System.out.println(d.name);
        Comparator<MyDog> nc = MyDog.getNameComparator();
        int flag = nc.compare(d1, d2);
        System.out.println(flag);
        if (flag > 0) {
            System.out.println(d1.name);
            return;
        }
        System.out.println(d2.name);
    }
}

Clonclusion

  • Comparator 和 Comparable之间没有任何关系,实现了Comparable便是可以实现比较,Comparator 表示可以实现两个对象之间的特殊比较。
  • 在这里插入图片描述
  • 接口的功能相当于创造一个回调函数

List and Set in Java

Basic ArraySet

  • Github里面都有代码,我这里就不copy了。
  • 对于开头添加null来讲,将其作为异常抛出,或者将其作为一个对象存入,都是可行的策略 只要做好文档即可。

iterable 和 iterator

  • 对于一个类能实现增强for循环有两个必要条件
  • 该类实现了Itreable,并重写其相关方法,返回迭代器。
  • 迭代器中重写了next方法和hasnext方法,并使其有作用
  • 如果没有继承Iterable 但是完成了所有后面的步骤,则可以使用迭代器iterator进行比较难看的迭代操作。
        MyArraySet<Integer> aset = new MyArraySet<>();
		…………………………………………………………
        Iterator<Integer> iterator = aset.iterator();
        while (iterator.hasNext())
            System.out.println(iterator.next());

  • 继承了Iterable 就可以进行增强for循环了
        MyArraySet<Integer> aset = new MyArraySet<>();
		…………………………………………………………
        //iteration
        for (int i : aset) {
            System.out.println(i);
        }
  • 如果你打过断点,你就会发现,起始增强for循环也是走的迭代器。在
        for (int i : aset) {
        //首先调用public Iterator<T> iterator() {} 获得迭代器
        //调用hasNext()方法 查看是否为true
        //如果为true调用iterator.next(),索引移动到i+1 返回第i个item
        //为false则跳出循环
            System.out.println(i);

        }
  • 其实iterable 和 iterator 与 Comparable 和Comparator有相似处。
  • 私自获得比较器或迭代器 , 通过实现able的方法 来进行和自身相关的操作。尤其是在书写代码方面,有很高的相似度。

toString

  • String StringBuilder StringBuffer 异同
  • String 内存浪费内存严重
  • StringBuffer 内存不浪费 线程安全 效率低
  • StringBuilder 内存不浪费 线程不安全 效率高
  • 通常情况下建议使用StringBuilder 要求线程安全的情况下使用StringBuffer 减少直接使用String 尽量从Stringbuilder 或者StringBuffer转换过去。

equals 和 ==

  • equals的默认写法就是 仅仅通过判断“==”返回布尔值。
public boolean equals(Object obj) {
    return (this == obj);
}
  • 注意T和this的类型并不相同,为什么还可以成功
  • 因为迭代器会进入迭代器的hasNext()方法,判定否继续执行。和本身的T 与 this的类型无关
	//equals的代码
    @Override
    public boolean equals(Object other) {
        if (other == this) return true;
        if (other == null) return false;
        if (other.getClass() != this.getClass()) return false;

        MyArraySet<T> o = (MyArraySet<T>) other;
        if (o.size != this.size) return false;

	//注意这里T和this的类型并不相同,为什么还可以成功
        for (T item : this) {
            if (!o.contains(item)) return false;
        }

        return true;
    }

Do better about String

  • 查了一下,String.join是一次性申请好所有的内存,因此效率会比“+”高很多。
    @Override
    public String toString() {
        List<String> stringItems = new ArrayList<>();
        for (T x : this) {
            stringItems.add(x.toString());
        }
        return "{"+String.join(",", stringItems)+"}";
    }
这篇关于数据结构与算法 学习笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!