集合、二叉树
回顾:
/** * */ package com.qfedu.Day17.HomeWork; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class HomeWork { @SuppressWarnings("rawtypes") public static void main(String[] args) { /*已知有一个工人Worker类 属性:姓名 年龄 工资, 行为 void work() a.在集合中添加三个工人对象,信息如下: zhang3 18 3000 li4 25 3500 wang5 22 3200 b. 在li4之前插入一个工人 信息为: zhao6 24 3300 c. 删除wang5的信息 d. 利用for循环遍历,打印几个中所有工人的信息 e. 利用迭代器遍历,对集合中的所有工人调用work方法 */ List list = new ArrayList(); //a. list中的元素存储的是地址 // 集合中只能存储对象-->引用的地址 list.add(new Worker("zhang3",18,3000)); list.add(new Worker("li4",25,3500)); list.add(new Worker("wang5",22,3200)); System.out.println(list); //b. 之所以集合可以存储任何数据类型--> 因为是父类Object // 向上转型 子类的引用赋值给了父类的对象 --> 只能调用父类的属性和方法,不能调用子类特有和方法 for(int i = 0; i<list.size();i++) { if(((Worker)(list.get(i))).getName().equals("li4")) { list.add(i, new Worker("zhao6",24,3000)); break; } } System.out.println(list); //c. for(int i = 0;i<list.size();i++) { if(((Worker)(list.get(i))).getName().equals("wang5")) { list.remove(i); break; } } /* * ps不使用下标的形式: 传入一个王五的对象 * 重写equals 和 hashcode * list.remove(new Worker("wang5",22,3200)); */ //d. for(Object obj : list) { System.out.println(obj); } //e. //1.获取迭代器对象 Iterator it = list.iterator(); //遍历集合 //2.判断迭代器中是否存在下一个元素 hasNext // 获取当前迭代器中对象next-->集合中的对象 while(it.hasNext()) { // it.next(); // it.next(); // //一次循环移动两次 --> 一共就三个元素 ((Worker)(it.next())).work(); } } } /** * */ package com.qfedu.Day17.HomeWork; public class Worker { //对当前类进行描述 --> 对象的描述 //类的作用 --> 用来存储描述对象的信息 //姓名 年龄 工资, 行为 void work() //属性的:定义成员变量 private String name; private int age; private int money; //构造方法时可以私有化 --> 单利 // --> 简单工厂 工具类 //构造方法 --> 无参 -->有参 //提供了成员变量快捷的赋值方式 public Worker() { //没有参数 //super(); 调用父类的构造方法(无参) //这里还可以使用一些调用语句(方法的) 不这样做 //初始化数组 ,集合初始化 //给成员变量一些初始值(固定的) } public Worker(String name,int age,int money) { this.name = name; this.age = age; this.money = money; } //getter和setter方法 --> 对外提供访问和修改操作 public String getName() { //获取当前成员变量 return name; } public void setName(String name) {//修改当前成员变量 this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } //描述行为 --> 方法 public void work() { System.out.println(name+"工人在工作拿到薪水:"+money); } /* * 打印自定义对象的信息 */ @Override public String toString() { return name; } }
List集合:
ArrayList集合 : 数组实现 查询和修改速度快 增加和删除速度慢
LinkedList集合 : 链表 --> 栈 队列(单向和双向) 增加和删除速度快 ,查询和修改速度慢
Vector集合: 不在使用了 线程安全的 但是效率低 数组
ArrayList集合 线程不安全 但是效率高
Collections工具类可以将当前集合修改为线程安全
4中遍历集合的方式
普通for 增强for 迭代器(标准迭代器 iterator 增强迭代器 listIterator) 枚举迭代器(使用的Vector不在使用)
List集合:有序存储(下标) 可以存储重复的值
Set集合
简述Hash(哈希表)
在一般的数组中,元素在数据中的引用位置是随机的,元素的取值和与元素的位置之间不存在确定关系
因此,在数组中查找特定的值,需要把查找值和数组中的元素进行一一比较
如果元素的值(value)和在数组中索引的位置(index)有一个确定的关系(Hash)
公式: index = hash(value);
那么么对于给定的值,只要调用上述Hash(value)方法,可以找到对应的index,能找到对应的元素Value
表推测:
index = 元素值/10-1;
如果数组中元素的遏制和索引的位置存在对应的关系,这样的数组就称为哈希表(桶结构)
一般情况下,我们是不会把哈希码(hashCode)作为元素在数组中所有的位置,哈希码和元素之间的某种映射关系(Map)
哈希表装满了,会进行扩容,提供了一个加载因子 --> 0.75
若创建一个hash表提供的空间是16,也就是当前到到16*0.75那么就进行扩容
hash表中是不允许存在相同对象的(值(元素)),提供equals和hashCode相同 --> index相同,不存在当前元素
/** * */ package com.qfedu.Day17.HashSet; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; public class HashSetDemo { public static void main(String[] args) { //1.HashSet是Set接口的实现类 //set是不允许存储重复值,排重 (排除重复) Set set = new HashSet(); // HashSet set = new Hash(); //1.向集合中添加元素 set.add("1"); set.add("1"); set.add("1"); set.add("1"); set.add("1"); set.add("1"); set.add("1"); set.add("1"); System.out.println(set); List list = new ArrayList(); list.add("2"); list.add("2"); list.add("2"); list.add("2"); list.add("2"); list.add("2"); list.add("2"); list.add("2"); list.add("2"); System.out.println(list); set.addAll(list);//存储的不是集合 是集合中元素 System.out.println(set); //清空集合 内容清空 集合存在 set.clear(); //判断集合是否为空 -->空没有元素 true 反之false System.out.println(set.isEmpty()); /* * boolean contains(Object o) 判断集合中是否存在指定元素 存在true 反之false boolean containsAll(Collection<?> c) 判读集合中是否存在指定集合中的元素 存在true 反之false */ //set同样支持Iterator迭代器 //但是:它只支持标准的迭代器 不支持listIterator //千万不要在迭代器中使用集合自身的增加和删除方法 //不然就会出现异常 //1.获取迭代器对象 Iterator it = set.iterator(); while(it.hasNext()) { System.out.println(it.next()); } //set中删除只有一种直接删除方式 //参数必须是一个对象 //这个方法是有返回值的 true证明删除成功 // false证明删除失败-->集合中没有这个对象 set.remove("2"); /* * 参考List * boolean removeAll(Collection<?> c) 删除传入参数集合中元素 ,保留剩余元素 boolean retainAll(Collection<?> c) 保留传入参数集合中的元素,删除剩余元素 */ //提供一个方法可以获得集合中所有元素的个数 int size = set.size(); //set集合除了Iterator迭代器方式可以遍历 //同样支持for循环遍历 -->只支持增强for循环 for(Object obj :set) { System.out.println(obj); } //ps:set并不支持使用循环的方式赋值 //set集合可以转变为数组 Object[] obj = set.toArray(); //需求:如下set集合中存储了字符串1-9 // 将集合转换为数组 // 打印数组中值 // 将数组中的值存到一个新的set集合中 } }
hashSet集合
hash表-->hash算法是看不到的
底层原码使用native这个关键字,是使用本地类库的关键字
操作系统,java有一个方法这个方法的实现不是java提供而是通过本地c或C++类库来完成计算
hash表插入和查找是很优秀
可以当hash表接近装满的时候,向数组一样进行扩容,会将小表转换为大表
加载因子:0.75 -->hash表的大小是16 也就是说当前 到大12的时候 就会扩容
HashSet使用的就是hash表的存储方式
不能存储重复值(hash表中是不能存相同元素),存储方式是无序(无序不等于随机)
如何使用HashSet集合 API
HashSet是有hash表支持,但是底层实现是HashMap
看HashSet包
/** * */ package com.qfedu.Day17.HashSet.SelfClass; import java.util.HashSet; import java.util.Set; class Person{ private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } //认定两个对象是同一个对象 //只要属性完全相同即可 /* */ @Override public boolean equals(Object obj) { Person other = (Person)obj; return this.name.equals(other.name) && this.age == other.age; } } class A{ @Override public boolean equals(Object obj) { return true; } } class B{ @Override public int hashCode() { return 1; } } class C{ @Override public boolean equals(Object obj) { return true; } @Override public int hashCode() { return 2; } } public class HashSetDemo2 { public static void main(String[] args) { Person p1 = new Person("小明", 18); Person p2 = new Person("小明", 18); System.out.println("若输出true证明当前对象和传入对象是同一个对象:"+p1.equals(p2)); //创建一个Set集合 Set set = new HashSet(); set.add(new A()); set.add(new A()); set.add(new B()); set.add(new B()); set.add(new C()); set.add(new C()); set.add(p1); set.add(p2); //Set集合是自动排重 --> 排除重复 System.out.println(set); } }
自定义类在Set集合中如何排重
自定义类所创建的对象,认为是同一个对象标准是什么?
对象中的属性是完全一致的,只要是属性一致我们就认为是同一个对象set就需要帮组我们排除重复
若在set集合中存储自定义类所创建的对象,若需要排重操作,就需要提供equals和hashcode重写
在hashSet中如何判断两个对象是是否相等
1.两个对象的equal比较结果必须是true,这说明是同一个对象
2.两个对象的hashcode必须也是相等的
在这里Hashcode值决定了对象所在哈希表中的位置
规定:重写equals和hashcode的原则,通过equals判断为是同一个对象,那么我们就认为当前两个对象地址值也需要是相同
重写equals就必须重写hashcode这样可以达到对象相等地址相等目的
当向hashSet中添加对象的手,先判断该对象和集合中对象的hashcode;
不等,直接把该对象存到hashcode值对应的指定位置
相等,在继续判断对象和集合对象中的equals是否相等,会出现桶挂载情况
hashCoed相同 equals为true 视为同一个对象
hashCode相同 equals 为false 非常的麻烦 将当前对象向链表一样串联起来
需求 创建一个Student类,提供属性 姓名和年龄 行为随意 重写toString
创建一个set集合,向set集合中存入Student类的对象
张三 18
李四 20
王五 25
张三 18
李四 20
打印集合查看效果
总结:
1.无论何时只要是比较两个自定义对象是否相等,那么久必须重写equals和hashcode
ps:使用系统生成即可 alt+shif+s --> create hashcode和 equals
2.将自定义对象存储到set集合中,需要排重时,当前类就必须提供equals和hashcode重写
不然set集合不会帮组我们完成排重
看SelfClass包
HashSet类是有一个子类的LinkedHashSet
LinkedHashSet也是Set接口的实现类
使用了链表和hash表双重的方式来实现的
即可以保证唯一性,也可以使用链表的形式来记录先后顺序
LinkedHashSet没有任何自己的特有方法,所有的使用方式仿照hashSet即可
方法参照Set接口中提供的方法即可
TreeSet --> 如何自定义比较(排序)
TreeSet也是Set接口的实现类,具备排重,排序
向TreeSet中存储数据,数据会按照从小到大的方式排序输出(升序)
TreeSet中的方法不是我们的重点
重点在于如何实现连个比较(排序)接口
ps:
二叉树 --> 红黑二叉树TreeSet实现方式
二叉树是一种非常重要的数据结构,它同时具有数组和链表各自的特点:
它可以像数组一样快速查找,也可以像链表一样快速添加。
但是他也有自己的缺点:删除操作复杂。
我们先介绍一些关于二叉树的概念名词。
二叉树:是每个结点最多有[两个子树]的有序树,在使用二叉树的时候,数据并不是随便插入到节点中的,
一个节点的左子节点的关键值必须小于此节点,右子节点的关键值必须大于或者是等于此节点,所以又称二叉查找树、二叉排序树、二叉搜索树。
ps:二叉树可以做为搜索算法的 hash表也可以 --> 寻路算法 --> TOP N --> 好友推荐算法
今日头条 --> 首席算法工程师 -->XXX -->中科院 --> 10次 --> java算法(有一定基础不然会疯 --> 数据结构)
/** * */ package com.qfedu.Day17.TreeSet.Comparable; public class Person implements Comparable{ private int age; private int height;// public Person(int age,int height) { super(); this.age = age; this.height = height; } public Person() { super(); // TODO Auto-generated constructor stub } public int getAge() { return age; } public void setAge(int age) { this.age = age; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "年龄"+age+","+"身高"+height; } /* * 是对对象需要排序的属性进行比较的方法 */ @Override public int compareTo(Object o) { Person other = (Person)o; //比较的是年龄 升序 必须满足 整数 负数和 零的关系 // if(this.age > other.age) { // return 1; //正数 就是升序 负数是降序 // }else if(this.age == other.age){ // //继续书写 年龄相等身高降序 // if(this.height > other.height) { // return -1; // }else if(this.height == other.height) { // //身高相等 , 体重升序 // return 0; // }else { // return 1; // } // // }else { // return -1; // } //String 引用类型若需要排序,都会提供比较方法 // String str1 = "abc"; // String str2 = "ABC"; // str1.compareTo(str2); // //用什么方式就可以得到正数,负数 或 零 //需要升序就用当前对象的值 - 传入对象的值 升序 //需要降序就用当前传入对象的值 - 当前对象的值 降序 return this.age - other.age; } } /** * */ package com.qfedu.Day17.TreeSet.Comparable; import java.util.TreeSet; public class PersonTest { public static void main(String[] args) { TreeSet set = new TreeSet(); set.add(new Person(10,180)); set.add(new Person(17,190)); set.add(new Person(12,150)); set.add(new Person(12,170)); set.add(new Person(9,165)); set.add(new Person(20,155)); System.out.println(set); } } /** * */ package com.qfedu.Day17.TreeSet.Comparable; import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { //1.默认排序的 升序(从小到大) //2.排重 --> 排除重复 //3.无序 //4.TreeSet的底层实现是一个红黑二叉树(中序遍历 左 根 右) //ps:数据若需要排序就一定要具备可比性整数和整数 小数和小数 // 整数和字符串,自定义类的对象进行比较 不具备就会出现异常 // 使用TreeSet就参考set集合的使用方式 TreeSet set = new TreeSet(); set.add(1); set.add(20); set.add(8); set.add(9); set.add("100"); set.add(11); set.add(55); set.add(32); set.add(44); System.out.println(set); } }
/** * */ package com.qfedu.Day17.TreeSet.Comparator; import java.util.Comparator; public class GZ implements Comparator{ /* * 制定规则 * 第一个参数是当前对象 --> this * 第二个参数是传入对象 --> other */ @Override public int compare(Object o1, Object o2) { Person pthis = (Person)o1; Person pother = (Person)o2; return pthis.getAge() - pother.getAge(); } } /** * */ package com.qfedu.Day17.TreeSet.Comparator; public class Person { private int age; public Person() { super(); // TODO Auto-generated constructor stub } public Person(int age) { super(); this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "年龄:"+age; } } /** * */ package com.qfedu.Day17.TreeSet.Comparator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { //第一种方式 //这才是使用自定义比较器(Comparator)的方式 TreeSet set = new TreeSet(new GZ()); set.add(new Person(18)); set.add(new Person(11)); set.add(new Person(14)); set.add(new Person(20)); set.add(new Person(16)); set.add(new Person(19)); System.out.println(set); //第二种方式 匿名内部类 TreeSet set1 = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { Person pthis = (Person)o1; Person pother = (Person)o2; return pthis.getAge() - pother.getAge(); } }); set1.add(new Person(18)); set1.add(new Person(11)); set1.add(new Person(14)); set1.add(new Person(20)); set1.add(new Person(16)); set1.add(new Person(19)); System.out.println(set1); //ps: Integer[] array = new Integer[10]; for(int i = 0;i<array.length;i++) { array[i] = (int)(Math.random()*100); } //给当前数组排序 升序 Arrays.sort(array); System.out.println(Arrays.toString(array)); //需要降序 --> lambda表达式 Arrays.sort(array, new Comparator() { @Override public int compare(Object o1, Object o2) { Integer ithis = (Integer)o1; Integer iother = (Integer)o2; return iother.compareTo(ithis); } }); System.out.println(Arrays.toString(array)); } }
完全二叉树:若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,
第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
深度——二叉树的层数,就是深度。
二叉树的特点总结:
(1)树执行查找、删除、插入的时间复杂度都是O(logN) ---> 大o算法
(2)遍历二叉树的方法包括前序、中序、后序
(3)非平衡树指的是根的左右两边的子节点的数量不一致
(4)在非空二叉树中,第i层的结点总数不超过 , i>=1;
(5)深度为h的二叉树最多有个结点(h>=1),最少有h个结点;
(6)对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
二叉树遍历分为三种
先序遍历
首先访问根,再先序遍历左子树,最后先序遍历右子树 根 --> 左 -- > 右 无 序
中序遍历
首先中序遍历左子树,再访问根,最后中序遍历右子树 左 --> 根 --.> 右 升 序
后序遍历
首先后序遍历左子树,再后序遍历右子树,最后访问根 左 --> 右 --> 跟
提供一个视频:实现一个二叉树,实现了中序遍历 --> 递归
提供自定义类的比较方式
若自定义类在TreeSet中需要按照某种需求进行排序就需要实现一个接口Comparable
Comparable接口强行对实现它的每个类的对象进行整体排序。
这种排序被称为类的[自然排序],类的 compareTo 方法被称为它的自然比较方法。
就要实现下面这个方法 --> 指定排序的规则(升序,降序)
int compareTo(T o)
比较此对象与指定对象的顺序。
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数
使用this 和 other 来表示当前对象和传入对象
this > other 正数 1 (升序)
this == other 零 0 (不变)
this < other 负数 -1 (降序)
ps: Comparable接口是对应TreeSet的无参构造方法
数值型数据 ---> 数字的大小
字符 ASCII码不够,Unicode值进行比较
字符串 Unicode比较
ps:unicode是包含ASCII码
看Comparable包
比较接口Comparator 自定义比较器
应用于 Collection.sort -->降序或升序排序
Arrays.sort -->降序或升序排序
int compare(T o1, T o2)
比较用来排序的两个参数。
返回值是int,只要是返回 正数,负数,零 就可以区分升序还是降序
第一个参数是当前对象this
第二个参数是传入对象other
使用this 和 other 来表示当前对象和传入对象
this > other 正数 1 (升序)
this == other 零 0 (不变)
this < other 负数 -1 (降序)
ps:Comparator对应的是TreeSet有参构造方法-->作为参数传递到TreeSet才可以使用
需求使用Comparator实现自定义排序,,Person类型属性age age从小大升序排序
看Comparator包
总结:
1.需要集合对我们自定义类进行排序操作,就必须实现Comparator/Comparable其中一个接口
千万不要双管齐下,没有用,Comparable会高于Comparator
2.TreeSet自带排序排重,存数据完成排序必须具备可比性
3.TreeSet的方法参考Set接口中方法即可
Set总结:
1.不允许重复值
2.都是线程不安全的
3.无序存储(无序不等于随机)
ps:LinkdeHashSet不是无序
HashSet是Set集合中最常用一个集合底层是哈希表实现查询效率高,当前集合也有扩容
若在HashSet中存储自定义类创建对象,需要排重的话重写equals和HashCode
LinkedHashSet是HashSet的子类,本身没有任何特殊的方法都是继承而来
使用没有HashSet频繁底层有链表和哈希表共同实现
TreeSet一般使用在需要进行排序时:
TreeSet自带排序升序,需要具备可比性
可以使用TreeSet对自定义类进行排序
需要实现以下两个接口之一即可Comparator/Comparable
这两个接口分别对应这不同的构造方法
Comparable --> TreeSet无参构造方法
CompareTo方法
Comparator --> TreeSet有参构造方法
Compare方法
Collection是List和Set父接口
Collection可以使用List或Set接口下的实现类来创建对象
Collection对我们的作用一般就是作为方法的参数而存在
若使用Collection作为方法的参数,此时即可以就说List集合 也可以接受 Set集合
泛型(genericity):
为什么要使用泛型?
案例1:
集合可以存储任何数据类型,必然集合中需要使用Object类型接受
1.若需要使用计算或是当前对象的特有属性和方法强制类型转换(向下转型)
2.向集合中存储元素,无法限制存储元素的数据类型
我们可以使用泛型来约束集合中存储的数据类型,并且使用了泛型后就不需要强制类型转换了(向下转型)
但是这样一来会出现一个问题,不能再存储任何数据类型,只能存储限制的数据类型对应的值
案例2:
设计一个点(Ponit),来封装坐标,要求坐标的数据类型可以支持String,int,double,float,
class Point{
private String x;
private String y;
}
class Point{
private int x;
private int y;
}
....
原则DRY 不要重复自己
可以通过创建对象时在决定Point的数据是什么,这样一来是不是就可以不用创建多个Point而只要建立一个类即可
class Point{
privare T x;
private T y;
}
new Point();
就可以使用泛型
什么是泛型:
1.泛型的含义就是代表任何数据类型,但是本身没有任何含义,就是一个站位符
2.在不能明确数据类型式,我们即可以使用这种占位符的形式来替代数据类型