第二阶段 day08_ Collection 、List、泛型、数据结构

第二阶段 day08_ Collection 、List、泛型、数据结构

今日内容

  • 泛型
  • Collection集合
  • 迭代器
  • 数据结构
  • List集合

教学目标

能够说出集合与数组的区别
能够使用Collection集合的常用功能
能够使用迭代器对集合进行遍历
能够使用增强for循环遍历集合和数组
能够理解泛型上下限
能够阐述泛型通配符的作用
能够说出常见的数据结构
能够说出数组结构特点
能够说出栈结构特点
能够说出队列结构特点
能够说出单向链表结构特点
能够说出List集合特点
能够完成斗地主的案例

第一章 Collection集合

1.1 集合概述

在前面基础班我们已经学习过并使用过集合ArrayList ,那么集合到底是什么呢?

  • 集合:集合是java中提供的一种容器,可以用来存储多个数据。集合和数组既然都是容器,它们有什么区别呢?

  • 数组的长度是固定的。集合的长度是可变的。

  • 数组中存储的是同一类型的元素,可以存储任意类型数据。集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型。

1.2 集合常用类的继承体系

Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List 和java.util.Set 。其中, List 的特点是元素有序、元素可重复 ; Set 的特点是元素不可
重复。List 接口的主要实现类有java.util.ArrayList 和java.util.LinkedList , Set 接口的主要实现类
有java.util.HashSet 和java.util.LinkedHashSet 。从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述集合常用类的继承体系

从上面的描述可以看出JDK中提供了丰富的集合类库。

集合本身是一个工具,它存放在java.util包中。在Collection 接口定义着单列集合框架中最最共性的内容

1.3 Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e) : 把给定的对象添加到当前集合中 。

  • public void clear() :清空集合中所有的元素。

  • public boolean remove(E e) : 把给定的对象在当前集合中删除。

  • public boolean contains(Object obj) : 判断当前集合中是否包含给定的对象。

  • public boolean isEmpty() : 判断当前集合是否为空。

  • public int size() : 返回集合中元素的个数。

  • public Object[] toArray() : 把集合中的元素,存储到数组中

  • tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。

package com.witty.collection.demo01;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

/*
    Collection
 */
public class Test {
    public static void main(String[] args) {
        // 多态
        // list接口的集合
        Collection<String> list = new ArrayList<String>();
        // Set接口的集合
        Collection<String> set = new HashSet<String>();

        /*
            public boolean add(E e) 给定对象添加到集合中
            对应ArrayList添加返回的都是true
            对HashSet当添加重复元素时显示false
         */
        boolean listBo = list.add("李庚希"); // true
        System.out.println(listBo);

        boolean setBo = set.add("李逍遥");    // true
        System.out.println(setBo);

        // list可以添加重复元素,有序的
        System.out.println(list.add("李庚希"));    // true

        // 使用set无法添加重复元素 , 【无序的 hash 表】也叫 散列表
        System.out.println(set.add("李逍遥")); // false
        boolean setBo1 = set.add("林月如");    // true
        System.out.println(set);    // [林月如, 李逍遥]


        /*
            public void clear() :清空结婚
            public boolean remove(E e): 把给定对象在当前集合中删除
            remove方法在删除元素时,是根据元素的equals方法进行比较的
            只会找到第一个删除的元素

            如果是Set集合的集合,没有重复信息,因此删除时就没有这个信息了。
         */
        // list.clear();
        // System.out.println(list);   // []

        // boolean b = list.remove("李庚希");
        // System.out.println(b);  // true
        // System.out.println(list);   // [李庚希]  两个删除了一个

        Collection<Student> students = new ArrayList<>();
        students.add(new Student("张三", 20));
        students.add(new Student("李四", 21));
        students.add(new Student("王五", 22));

        System.out.println(students);
        students.remove(new Student("王五", 22));
        System.out.println(students);   // 删除成功

        /*
            public boolean contains(Object obj):判断当前集合是否包含给定的对象
            public boolean isEmpty():判断当前集合是否为空
            public int size():返回集合中元素的个数
         */
        boolean b = list.contains("李寻欢");   // 没有该元素
        System.out.println(b);  // false

        System.out.println(list.isEmpty()); // false 不为空
        System.out.println(list.size());    // 2

        System.out.println("---------------------");
        /*
            public Object[] toArray() :
         */
        Object[] objects = list.toArray();
        for (int i = 0; i < objects.length; i++) {
            System.out.print(objects[i] + " ");     // 李庚希 李庚希 
        }

    }
}

第二章 Iterator 迭代器

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口
java.util.Iterator 。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next() :返回迭代的下一个元素。
  • public boolean hasNext() :如果仍有元素可以迭代,则返回 true。

接下来我们通过案例学习如何使用Iterator迭代集合中元素:

package com.witty.collection.demo02iterator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        // 使用多态方式 创建集合
        Collection<String> coll = new ArrayList<String>();

        // 添加元素到集合
        coll.add("小猫");
        coll.add("小狗");
        coll.add("小熊猫");

        // 遍历
        // 使用迭代器 遍历, 每个集合对象都有自己的迭代器
        Iterator<String> it = coll.iterator();
        
        // 泛型 指的是 迭代出 元素的数据类型
        while (it.hasNext()) {  // 判断是否有迭代元素
            String s = it.next();   // 获取迭代的元素
            System.out.println(s);
        }
        String s1 = it.next();
        System.out.println(s1); // NoSuchElementException

    }
}

tips:

  1. 在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出
    java.util.NoSuchElementException没有集合元素异常。
  2. 在进行集合元素获取时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出
    ConcurrentModificationException并发修改异常.

2.2 迭代器的实现原理

我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hasNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

2.3 增强for

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。在遍历集合时,它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能用集合的删除方法对集合中的元素进行删除操作。
格式:

for (元素的数据类型 变量 : Collection 集合 or() 数组) {
	// 写操作代码
}

它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
代码演示

package com.witty.collection.demo03forEach;

import java.util.ArrayList;
import java.util.Collection;

// 增强for循环, 只能遍历数组或集合,不要在遍历的过程中对集合元素进行增删操作。
public class ForEachDemo01 {
    public static void main(String[] args) {
        int[] arr = {6, 3, 7, 5, 1};
        // 使用增强for遍历数组  快捷键iter
        for (int a : arr) { // a 代表数组中的每一个元素
            System.out.print(a + " ");

        }

        System.out.println();
        System.out.println("-------------------------------");

        Collection<String> coll = new ArrayList<String>();
        coll.add("月亮");
        coll.add("太阳");
        coll.add("星星");

        for (String s : coll) {
            System.out.print(s + " ");
        }

    }
}

tips:
增强for循环必须有被遍历的目标,目标只能是Collection或者是数组;
增强for(迭代器)仅仅作为遍历操作出现,不能对集合进行增删元素操作,否则抛出
ConcurrentModificationException并发修改异常

小结:Collection是所有单列集合的根接口,如果要对单列集合进行遍历,通用的遍历方式是迭代器遍历或增强for遍历。

第三章 泛型

3.1 泛型概述

在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时它们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
大家观察下面代码:

package com.witty.collection.demo04fanxing;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

//
public class GenericDemo {
    public static void main(String[] args) {
        Collection coll = new ArrayList();  // 没有指定类型
        coll.add("abc");
        coll.add("witty");
        coll.add(9);    // 由于集合么有指定任何限定类型,任何类型都可以向其中存放
        Iterator it = coll.iterator();  // 设置迭代器

        while (it.hasNext()) {
            // 需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
            String str = (String) it.next();
            System.out.println(str.length());   // 3 5 ClassCastException 因为9不是String类型,所以报错
        }


    }
}

程序在运行时发生了问题java.lang.ClassCastException。 为什么会发生类型转换异常呢? 我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。 怎么来解决这个问题呢?Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

  • 泛型:可以在类或方法中预知地使用未知的类型。

tips:泛型的作用是在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

3.2 使用泛型的好处

上一节只是讲解了泛型的引入,那么泛型带来了哪些好处呢?

  • 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
  • 避免了类型强转的麻烦。

通过我们如下代码体验一下:

package com.witty.collection.demo04fanxing;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class GenericDemo02 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("煤油");
        list.add("汽油");
        list.add("加油");

        // list.add(999);  //当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String str = it.next();
            // 当使用Iterator<String> 控制元素类型之后,就不需要强转了。获取到的元素直接就是String类型
            System.out.println(str.length());
        }
    }
}

tips:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

3.3 泛型的定义与使用

我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中将数据类型作为参数进行传递

定义和使用含有泛型的类

定义格式:

修饰符 class 类名<代表泛型的变量> { }

例如,API中的ArrayList集合:
泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。

class ArrayList<E> {
	public boolean add(E e) ;
	
	public E get(int index) ;
}

使用泛型:即什么时候确定泛型。

在创建泛型的时候确定泛型:

例如, ArrayList list = new ArrayList();
此时,变量E的值就是String类型,那么我们的类型就可以理解为:

class ArrayList<String> {
    public boolean add(String a) {}
    
    public String get(int index) {}
    ......
}

再例如:ArrayList list = new ArrayList();

此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

class ArrayList<Integer> {
    public boolean add(Integer e) {}
    
    public Integer get (int index) {}
    .......
}

含有泛型的方法

定义格式:

修饰符<代表泛型的变量> 返回值类型 方法名(参数列表) {}

例如:

public class MyGenericMethod {
    public <MVP> void show(MVP mvp) {
        System.out.println(mvp.getClass());
    }
    
    public <MVP> MVP show2(MVP mvp) {
        return mvp;
    }
}

调用方法时,确定泛型的类型:

package com.witty.collection.demo04fanxing;

public class GenericMethodDemo {
    public static void main(String[] args) {
        // 创建对象
        MyGenericMethod mm = new MyGenericMethod();

        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.34);

        /*
            class java.lang.String
            class java.lang.Integer
            class java.lang.Double
         */
    }
}

含有泛型的接口:

定义格式:

修饰符 interface接口名<代表泛型的变量> {}

例如:

package com.witty.collection.demo04fanxing;

public interface MyGenericInterface<E> {
    public abstract void add(E e);
    
    public abstract E getE();
}

使用格式:

1 定义类时确定泛型的类型

例如:

package com.witty.collection.demo05fanxingInterface;

public class MyImpl implements MyGenericInterface<String> {
    @Override
    public void add(String s) {
        // 方法
    }

    @Override
    public String getE() {
        return null;
    }
}

此时,泛型E的值是String类型

2 始终不确定泛型的类型,直到创建对象时,确定泛型的类型

例如:

package com.witty.collection.demo05fanxingInterface;

// 2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
public class MyImpl2<E> implements MyGenericInterface<E> {

    @Override
    public void add(E e) {

    }

    @Override
    public E getE() {
        return null;
    }
}

确定泛型:

package com.witty.collection.demo05fanxingInterface;

/*
使用 MyImpl2<E>
 */
public class GenericInterface {
    public static void main(String[] args) {
        // 创建对象  MyImpl2<E> 时确定类型
        MyImpl2<String> my = new MyImpl2<>();
        my.add("AA");
    }
}

小结:泛型是一种未知的数据类型,定义在类上的泛型,使用类的时候会确定泛型的类型,定义在方法上的泛型,会在使用方法的时候确定泛型,定义在接口上的泛型,需要使用接口的时候确定泛型。

3.4 泛型通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

通配符基本使用

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接受数据,不能往该集合中存储数据。
举个例子大家理解使用即可:

package com.witty.collection.demo06fanxing;

import java.util.ArrayList;
import java.util.Collection;

// 泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        getElement(list1);

        ArrayList<String> list2 = new ArrayList<>();
        getElement(list2);
    }

    public static void getElement(Collection<?> coll) {}

    // ? 代表可以接收任何类型
    // 泛型不存在继承关系 Collection<Object> list = new ArrayList<String>; 这是错误的
}

通配符高级使用----受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:

  • 格式: 类型名称 <? extends 类 > 对象名称
  • 意义: 只能接收该类型及其子类

泛型的下限:

  • 格式: 类型名称 <? super 类 > 对象名称
  • 意义: 只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类

package com.witty.collection.demo06fanxing;

import java.util.ArrayList;
import java.util.Collection;

public class Test02 {
    public static void main(String[] args) {
        
        Collection<Integer> list1 = new ArrayList<>();
        Collection<String> list2 = new ArrayList<>();
        Collection<Number> list3 = new ArrayList<>();
        Collection<Object> list4 = new ArrayList<>();
        
        // number类型及其子类 Integer 和Number
        getElement1(list1);
        // getElement1(list2); // 报错
        getElement1(list3);
        // getElement1(list4); // 报错

        // number类型和其父类 Number 和Object
        // getElement2(list1); // 报错
        // getElement2(list2);    // 报错
        getElement2(list3); 
        getElement2(list4);
    }

    // 泛型的上限:此时的泛型 ?, 必须是Number类型的子类
    public static void getElement1(Collection<? extends Number> coll) {}

    // 泛型的下限:此时的泛型 ?, 必须是Number类型或者Number类型的父类。
    public static void getElement2(Collection<? super Number> coll) {}
}

?表示泛型通配符,如果要对?泛型通配符的取值范围进行限制,可以使用泛型限定、

第四章 数据结构

4.1 数据结构介绍

数据结构 : 数据用什么样的方式组合在一起。

4.2 常见数据结构

数据存储的常用结构有:栈、队列、数组、链表和红黑树。我们分别来了解一下:

  • 栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。

简单的说:采用该结构的集合,对元素的存取有如下的特点

  • 后进先出。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口、出口的都是栈的顶端位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKDxP3HN-1596287511538)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728200346238.png)]

这里两个名词需要注意:

  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

队列:

  • 队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行取出并删除。
  • 简单的说,采用该结构的集合,对元素的存取有如下的特点:
    • 先进先出。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
    • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AN3IbwOw-1596287511542)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728200508636.png)]

数组

  • 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 查找元素快:通过索引,可以快速访问指定位置的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIuB7vyj-1596287511544)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728200741068.png)]

  • 增删元素慢
  • **指定索引位置增加元素:**需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIqwQ5Se-1596287511547)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728200726719.png)]

  • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gkdbo8pM-1596287511549)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728200918998.png)]

链表

  • 链表:linked list,由一系列结点node链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分一个是存储数据元素的数据域另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23wjt6Gh-1596287511551)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728201026458.png)]

  • 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  • 增删元素快:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0GJH0An-1596287511552)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728201111662.png)]

4.3. 树基本结构介绍

树具有的特点:

  1. 每一个节点有零个或者多个子节点
  2. 没有父节点的节点称之为根节点,一个树最多有一个根节点。
  3. 每一个非根节点有且只有一个父节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0f6FY0ur-1596287511553)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728201210497.png)]

名词含义
节点树中的一个元素
阶段的度节点拥有的子树的个数,二叉树的度不大于2
叶子节点度为0的节点,也称为终端节点
高度叶子节点的高度是1,叶子节点的父节点高度是2,以此类推,根节点高度最高
根节点在第一层,以此类推
父节点若一个节点含有子节点,则这个节点称之为其子节点的父节点
子节点子节点是父节点的下一层节点
兄弟节点拥有相同父节点的节点互称为兄弟节点
二叉树

如果树中的每个节点的子节点的个数不超过2,那么该树就是一个二叉树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruPuoeuS-1596287511555)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728201635924.png)]

二叉查找树

二叉查找树的特点:

  1. 左子树上所有的节点的值均小于它的根节点的值
  2. 右子树上所有的节点值均大于它的根节点的值
  3. 每一个子节点最多有两个子树
    案例演示(20,18,23,22,17,24,19)数据的存储过程;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GW6Ju1p5-1596287511557)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728201920405.png)]

遍历获取元素的时候可以按照"左中右"的顺序进行遍历;
注意:二叉查找树存在的问题:会出现"瘸子"的现象,影响查询效率

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UleRLA5V-1596287511559)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728202006125.png)]

平衡二叉树

概述

为了避免出现"瘸子"的现象,减少树的高度,提高我们的搜素效率,又存在一种树的结构:“平衡二叉树”
规则:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UdG8RXhm-1596287511562)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728202105018.png)]

如下图所示,左图是一棵平衡二叉树,根节点10,左右两子树的高度差是1,而右图,虽然根节点左右两子树高度差是0,但是右子树15的左右子树高度差为2,不符合定义,

所以右图不是一棵平衡二叉树。

旋转

在构建一棵平衡二叉树的过程中,当有新的节点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。

左旋:

左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u54xf1Fi-1596287511563)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203145698.png)]

右旋:
将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNJssgJH-1596287511564)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203234676.png)]

举个例子,像上图是否平衡二叉树的图里面,左图在没插入前"19"节点前,该树还是平衡二叉树,但是在插入"19"后,导致了"15"的左右子树失去了"平衡",
所以此时可以将"15"节点进行左旋,让"15"自身把节点出让给"17"作为"17"的左树,使得"17"节点左右子树平衡,而"15"节点没有子树,左右也平衡了。如下图,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVSE3Ns6-1596287511566)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203317250.png)]

由于在构建平衡二叉树的时候,当有新节点插入时,都会判断插入后时候平衡,这说明了插入新节点前,都是平衡的,也即高度差绝对值不会超过1。当新节点插入后,
有可能会有导致树不平衡,这时候就需要进行调整,而可能出现的情况就有4种,分别称作左左,左右,右左,右右。

左左
左左即为在原来平衡的二叉树上,在节点的左子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"10"节点的左子树"7",的左子树"4",插入了节点"5"或"3"导致失衡。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpwAQkju-1596287511567)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203639222.png)]

左左调整其实比较简单,只需要对节点进行右旋即可,如下图,对节点"10"进行右旋,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eU8K0fXS-1596287511569)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203728116.png)]

左右
左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的左子树"7",的右子树"9",

插入了节点"10"或"8"导致失衡。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHPE7k5G-1596287511570)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728203844900.png)]

左右的调整就不能像左左一样,进行一次旋转就完成调整。我们不妨先试着让左右像左左一样对"11"节点进行右旋,结果图如下,右图的二叉树依然不平衡,而右图就是接下来要
讲的右左,即左右跟右左互为镜像,左左跟右右也互为镜像。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eRfLiPiG-1596287511572)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728204928154.png)]

左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调
整成左左,然后再对左左进行调整,从而使得二叉树平衡。
即先对上图的节点"7"进行左旋,使得二叉树变成了左左,之后再对"11"节点进行右旋,此时二叉树就调整完成,
如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qklEMRv-1596287511573)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728204947931.png)]

右左
右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的右子树"15",的左子树"13",
插入了节点"12"或"14"导致失衡。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EAy2P1Pf-1596287511575)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728205006434.png)]

前面也说了,右左跟左右其实互为镜像,所以调整过程就反过来,先对节点"15"进行右旋,使得二叉树变成右右,之后再对"11"节点进行左旋,此时二叉树就调整完成,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJCi5Rk4-1596287511577)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728205054264.png)]

右右

右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"11"节点的右子树"13",的左子树"15",插入了节点

"14"或"19"导致失衡。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxRFbd6z-1596287511578)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728205135246.png)]

右右只需对节点进行一次左旋即可调整平衡,如下图,对"11"节点进行左旋。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmZi37Tl-1596287511579)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728205520267.png)]

红黑树
概述

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构,它是在1972年由Rudolf Bayer发明的,当时被称之为平衡二叉B树,后来,在1978年被Leoj.Guibas和Robert Sedgewick修改为如今的"红黑树"。它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,可以是红或者黑;

红黑树不是高度平衡的,它的平衡是通过"红黑树的特性"进行实现的;

红黑树的特性:

  1. 每一个节点或是红色的,或者是黑色的。

  2. 根节点必须是黑色

  3. 每个叶节点(Nil)是黑色的;(如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些
    Nil视为叶节点)

  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;
    如下图所示就是一个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNkODW3a-1596287511580)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728205710991.png)]

在进行元素插入的时候,和之前一样; 每一次插入完毕以后,使用黑色规则进行校验,如果不满足红黑规则,就需要通过变色,左旋和右旋来调整树,使其满足红黑规则;

第五章 List接口

我们掌握了Collection接口的使用后,再来看看Collection接口中的子类,他们都具备那些特性呢?
接下来,我们一起学习Collection中的常用几个子类( java.util.List 集合、java.util.Set 集合)。

5.1 List接口介绍

java.util.List 接口继承自Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口
的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以
通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一
致。
看完API,我们总结一下:
List接口特点:

  1. 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、
    22、33的顺序完成的)。
  2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

tips:我们在基础班的时候已经学习过List接口的子类java.util.ArrayList类,该类中的方法都是来自List中定义。

5.2 List接口中常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。

  • public E get(int index) :返回集合中指定位置的元素。

  • public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。

  • public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

    List集合特有的方法都是跟索引相关,我们在基础班都学习过。
    tips:我们之前学习Colletion体系的时候,发现List集合下有很多集合,它们的存储结构不同,这样就导致了这些集合它们有各自的特点,供我们在不同的环境下使用。

5.3 ArrayList集合

java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList 是最常用的集合。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

5.4 LinkedList集合

java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
LinkedList是一个双向链表,那么双向链表是什么样子的呢,我们用个图了解下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZdamL1m-1596287511581)(C:\Users\Data\AppData\Roaming\Typora\typora-user-images\image-20200728211550867.png)]

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:

  • public void addFirst(E e) :将指定元素插入此列表的开头。

  • public void addLast(E e) :将指定元素添加到此列表的结尾。

  • public E getFirst() :返回此列表的第一个元素。

  • public E getLast() :返回此列表的最后一个元素。

  • public E removeFirst() :移除并返回此列表的第一个元素。

  • public E removeLast() :移除并返回此列表的最后一个元素。

  • public E pop() :从此列表所表示的堆栈处弹出一个元素。

  • public void push(E e) :将元素推入此列表所表示的堆栈。

  • public boolean isEmpty() :如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

package com.witty.collection.demo07linkedlist;

import java.util.LinkedList;

public class Dmeo01LinkedList {
    public static void main(String[] args) {
        method1();
    }

    /*
        void addFirst(E e):在集合的开头位置添加元素
        void addLast(E e):在集合的尾部添加元素
     */
    private static void method1() {
        // 创建LinkedList对象
        LinkedList<String> list = new LinkedList<>();

        // 添加元素
        list.add("李志章");
        list.add("李杀币");
        list.add("李扣币");

        // 打印这个集合
        System.out.println("list:" + list); // list:[李志章, 李杀币, 李扣币]

        // 调用addFirst 在开头添加元素
        list.addFirst("李不去");
        System.out.println("list:" + list); // list:[李不去, 李志章, 李杀币, 李扣币]

        // 调用addLast方法在尾部添加元素
        list.addLast("李去不");
        System.out.println("list:" + list); // list:[李不去, 李志章, 李杀币, 李扣币, 李去不]
    }
}

5.5 LinkedList源码分析

LinkedList的成员变量源码分析

public class LinkedList<E> extends AbstractSequentialList<E>
	implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    transient int size = 0;
	/**
	*存储第一个节点的引用
	*/
	transient Node<E> first;
	/**
	* 存储最后一个节点的引用
	*/
	transient Node<E> last;
	//......
	//......
}

LinkedList的内部类Node类源码分析

public class LinkedList<E> extends AbstractSequentialList<E>
            implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
        //......
        private static class Node<E> {
            E item;//被存储的对象
            Node<E> next;//下一个节点
            Node<E> prev;//前一个节点
            //构造方法
            Node(Node<E> prev, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.prev = prev;
            }
        }
		//......
    }

LinkedList的add()方法源码分析

public boolean add(E e) {
	linkLast(e);//调用linkLast()方法
	return true;//永远返回true
}
void linkLast(E e) {
	final Node<E> l = last;//一个临时变量,存储最后一个节点
	final Node<E> newNode = new Node<>(l, e, null);//创建一个Node对象
	last = newNode;//将新Node对象存储到last
	if (l == null)//如果没有最后一个元素,说明当前是第一个节点
		first = newNode;//将新节点存为第一个节点
	else
        l.next = newNode;//否则不是第一个节点,就赋值到当前的last的next成员
	size++;//总数量 + 1
	modCount++;//
}

LinkedList的get()方法

public E get(int index) {
	checkElementIndex(index);//检查索引的合法性(必须在0-size之间),如果不合法,此方法抛出		异常
	return node(index).item;
}
Node<E> node(int index) {//此方法接收一个索引,返回一个Node
	// assert isElementIndex(index);
	if (index < (size >> 1)) {//判断要查找的index是否小于size / 2,二分法查找
		Node<E> x = first;// x = 第一个节点——从前往后找
		for (int i = 0; i < index; i++)//从0开始,条件:i < index,此循环只控制次数
			x = x.next;//每次 x = 当前节点.next;
		return x;//循环完毕,x就是index索引的节点。
	} else {
		Node<E> x = last;// x = 最后一个节点——从后往前找
		for (int i = size - 1; i > index; i--)//从最后位置开始,条件:i > index
			x = x.prev;//每次 x = 当前节点.prev;
		return x;//循环完毕,x就是index索引的节点
	}
}

第六章 集合综合案例

6.1 案例介绍

按照斗地主的规则,完成洗牌发牌的动作。 具体规则:
使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

6.2 案例分析

  • 准备牌:
    牌可以设计为一个ArrayList,每个字符串为一张牌。 每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。 牌由Collections类的shuffle方法进行随机序。
  • 发牌
    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
  • 看牌
    新节点存为第一个节点
    else
    l.next = newNode;//否则不是第一个节点,就赋值到当前的last的next成员
    size++;//总数量 + 1
    modCount++;//
    }

### LinkedList的get()方法

```java
public E get(int index) {
	checkElementIndex(index);//检查索引的合法性(必须在0-size之间),如果不合法,此方法抛出		异常
	return node(index).item;
}
Node<E> node(int index) {//此方法接收一个索引,返回一个Node
	// assert isElementIndex(index);
	if (index < (size >> 1)) {//判断要查找的index是否小于size / 2,二分法查找
		Node<E> x = first;// x = 第一个节点——从前往后找
		for (int i = 0; i < index; i++)//从0开始,条件:i < index,此循环只控制次数
			x = x.next;//每次 x = 当前节点.next;
		return x;//循环完毕,x就是index索引的节点。
	} else {
		Node<E> x = last;// x = 最后一个节点——从后往前找
		for (int i = size - 1; i > index; i--)//从最后位置开始,条件:i > index
			x = x.prev;//每次 x = 当前节点.prev;
		return x;//循环完毕,x就是index索引的节点
	}
}

第六章 集合综合案例

6.1 案例介绍

按照斗地主的规则,完成洗牌发牌的动作。 具体规则:
使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

6.2 案例分析

  • 准备牌:
    牌可以设计为一个ArrayList,每个字符串为一张牌。 每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。 牌由Collections类的shuffle方法进行随机序。
  • 发牌
    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
  • 看牌
    直接打印每个集合。 3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值