Java数据结构-List-栈-队列-二叉树-堆
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 1. 前置知识
- 1.1 yield关键字
- 1.2 var关键字
- 1.3 密封类
- 1.4 接口中的私有方法
- 1.5 instance
- 1.5 包装类
- 1.6 装箱和拆箱
- 1.7 泛型
- 2. List
- 2.1 ArrayList顺序表
- 2.1.1 ArrayList遍历
- 2.2 链表
- 2.2.1 LinkedList的使用
- 3. 栈
- 4. 队列
- 5. 二叉树
- 6. PriorityQueue优先级队列(堆)
- 6.1 覆写基类的equals
- 6.2 基于Comparble接口类的比较
- 6.3 基于比较器比较
- 总结
前言
1. 前置知识
1.1 yield关键字
public static void main(String[] args) {String data = "one";int result = switch (data){case "one": yield 1;case "two": yield 2;default: yield 3;};System.out.println(result);}public static void main2(String[] args) {String data = "one";int result = switch (data){case "one"->1;case "two"->2;default -> 3;};System.out.println(result);}public static void main3(String[] args) {String data = "one";int result = 1;switch (data){case "one": result=1;break;case "two":result=1;break;default : result=1;break;};System.out.println(result);}
1.2 var关键字
public static void main(String[] args) {var age =10;var salary ="hello";}
自动推导类型
但是var不能用于声明类的字段
声明变量必须要初始化,不能初始化为null
var不能作为返回值类型
1.3 密封类
final class Person {}
Person 这个类不能被继承:密封类
sealed class Person permits GB{}non-sealed class GB extends Person{}
表示Person是一个密封类,不能被继承,但是可以被GB继承
non-sealed 表示这不是一个密封类,可以被继承
继承了密封类,那么自己这个类就要说明一下到底是不是密封类
sealed修饰的类,必须要有子类,这个子类可以为sealed可以为non-seald,但是不能为什么都不说
只有被允许的类,才可以被继承
1.4 接口中的私有方法
interface A{void test();default void test2(){}static void test3(){}private void test4(){}
}
后面三种方法,实现接口是不用重写这三种方法的,这三种方法也是可以有方法体的
只有第一个不能有方法体
1.5 instance
if (obj instanceof String){String string = (String) obj;}if(obj instanceof String str){}
这两个是一样的效果
1.5 包装类
指的是基本数据类型对应的类类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
1.6 装箱和拆箱
int a = 10;Integer ii = a;//装箱,显示装箱Integer iii = Integer.valueOf(a);//自动装箱int b = ii;//自动拆箱int bi = ii.intValue();//手动拆箱
double c = ii.doubleValue();
Integer a = 100;Integer b = 100;System.out.println(a == b);Integer c = 200;Integer d = 200;System.out.println(c == d);

首先这两个都是装箱
都调用了方法valueOf

可以看出,如果i在一定范围之内,直接返回这个已经创建好的Integer-----》
已经创建好了一个Integer的数组cache
low是-128
high是127
所以i在-128到127之内,就会在cache这个数组里面取到Integer的值
如果不在的话,就创建一个新的Integer返回—》每个类的地址都不一样
Integer a = 127;Integer b = 127;System.out.println(a == b);Integer c = 128;Integer d = 128;System.out.println(c == d);

1.7 泛型
泛型:就是适用于许多许多类型
把类型(int,byte)参数化
class MyArray<T>{public Object[] array= new Object[10];public void setValue(int pos, T value){array[pos] = value;}public T getValue(int pos){return (T)array[pos];}
}
MyArray<Integer> a = new MyArray<Integer>();a.setValue(1,1);a.setValue(2,2);a.setValue(3,3);Integer value = a.getValue(1);System.out.println(value);

class MyArray<T>{public T[] array= (T[]) new Object[10];public void setValue(int pos, T value){array[pos] = value;}public T getValue(int pos){return (T)array[pos];}
}
也可以这样
T[] ts = new T[5];//是不对的
MyArray<Integer> a = new MyArray<>();
第二个Integer可以不写,可以推导出实例化需要的类型实参为 Integer
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
尖括号里面只能写类,不能写简单类型
泛型到底是怎么编译的?
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
因为java规定的就是,在new数组的时候,就必须是一个确定类型的数组,所以写new T[5]从语法上来说就已经出错了,因为你写new T(),你知道这个类型的构造方法是不带参数的吗
class MyArray<T>{public T[] array= (T[]) new Object[10];public void setValue(int pos, T value){array[pos] = value;}public T getValue(int pos){return (T)array[pos];}public T[] getArray(){return array;}
}
MyArray<String> myArray = new MyArray<>();String[] strings = myArray.getArray();

这样直接就报错了,为什么呢,以前可以转换,那是因为,Object是所有类的父类
但是Object[]不会所有数组的父类
所以Object[]不是String[]的父类,是不能进行强转的
泛型的上界:在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束
public class MyArray<E extends Number> {
...
}
只接受 Number 的子类型或者number自己作为 E 的类型实参
public class MyArray<E extends Comparable<E>> {
...
}
E必须是实现了Comparable接口的----》意思就是这个类型E是可以进行比较的
就是E类型对应的对象可以来比较的,一般类型是不能比较的
class ALg<T extends Comparable<T>> {public T findMax(T[] array){T max = array[0];for (int i = 1; i < array.length; i++) {if (array[i].compareTo(max) > 0) {max = array[i];}}return max;}
}

我们看到MyArray没有实现接口Comparable,所以不能泛型
泛型方法
class ALg2 {public<T extends Comparable<T>> T findMax(T[] array){T max = array[0];for (int i = 1; i < array.length; i++) {if (array[i].compareTo(max) > 0) {max = array[i];}}return max;}
}
ALg2 aLg2 = new ALg2();Integer[] arr = {1,2,3,4,5,6,7,8,9};Integer max = aLg2.findMax(arr);System.out.println(max);
这里不用指定泛型,因为会自动推导的
class ALg3 {public static <T extends Comparable<T>> T findMax(T[] array){T max = array[0];for (int i = 1; i < array.length; i++) {if (array[i].compareTo(max) > 0) {max = array[i];}}return max;}
}
Integer max1 = ALg3.findMax(arr);
Integer max1 = ALg3.<Integer>findMax(arr);
也可以这样
2. List
2.1 ArrayList顺序表
List是一个接口,继承自Collection。
Collection也是一个接口
继承Iterable

List<Integer> list = new ArrayList<>();
因为ArrayList实现了ArrayList,所以可以这样定义
ArrayList就是顺序表
List是个接口,并不能直接用来实例化。
如果要使用,必须去实例化List的实现类。在集合框架中,ArrayList和LinkedList都实现了List接口。
顺序表其实就是一个数组
List<Integer> list = new ArrayList<>();list.add(1);list.add(2);System.out.println(list);

ArrayList<Integer> arrayList = new ArrayList<>();List<Integer> list = new ArrayList<>();
List是一个接口
ArrayList是一个类
list 只能访问list里面的方法
而ArrayList只能访问ArrayList里面的方法,相比来说,ArrayList里面的防范更多

List<Integer> list = new ArrayList<>();List<Integer> list2 = new ArrayList<>(list);List<Integer> list3 = new ArrayList<>(3);
这是三种不同的构造方法
容量达到3就进行扩容
boolean add(E e) 尾插 e
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) 尾插 c 中的元素
E remove(int index) 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element) 将下标 index 位置元素设置为 element
void clear() 清空
boolean contains(Object o) 判断 o 是否在线性表中
int indexOf(Object o) 返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List subList(int fromIndex, int toIndex) 截取部分 list,没有重新生成一个新的,意思是subList返回的数组和原数组是同一个部分的内容
因为list是Collection的子类,所以可以直接传入list类型的
list2.addAll(list3);
如果删除10下标元素
remove(10)
如果删除10这个元素
remove(new Integer(10))
remove(Integer.valueOf(10))
2.1.1 ArrayList遍历
第一种就是直接打印
list3.add(1);list3.add(2);list3.add(3);for (int i = 0; i < list3.size(); i++) {System.out.println(list3.get(i));}
for (Integer integer : list2) {System.out.println(integer);}
最后一种就是使用迭代器
Iterator<Integer> it = list2.iterator();while (it.hasNext()) {System.out.println(it.next());}
it迭代器指向的位置是第一位置的前一个位置,也就是-1的位置
只要实现了Iterable,就可以使用迭代器,就是使用foreach
Iterator<Integer> it2 = list2.listIterator();while (it2.hasNext()) {System.out.println(it.next());}
这个是专门用于list的Iterator
Iterator是listIterator的父类
ListIterator<Integer> it3 = list2.listIterator(list2.size());while (it3.hasPrevious()) {System.out.println(it3.previous());}
这个就是倒着遍历,传入整型参数表示从哪个迭代器开始
但是只有ListIterator有previous,Iterator是没有这个方法的,所以用ListIterator接收呢
iterator()不能传入整型参数,默认从0开始
- ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。—》链表
这个比较适合查找,根据下标进行查找
链表适合插入和删除
2.2 链表
class ListNode{public int val;public ListNode next;ListNode(int x){val = x;}
}
对象名存储的就是地址
ListNode listNode1 = new ListNode(1);ListNode listNode2 = new ListNode(2);listNode1.next = listNode2;
new对象的时候,next默认是null的

ListNode head = listNode1 ;
可以弄一个head
head = head.next();
这样就可以行走了,就可以从第一个遍历到第二个节点了
ListNode cur= head;while (cur!=null){System.out.println(cur.val);cur= cur.next;}
2.2.1 LinkedList的使用
LinkedList是java库中的链表,是双向链表
LinkedList<Integer> list = new LinkedList<>();List<Integer> list2 = new LinkedList<>();

- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
LinkedList() 无参构造
public LinkedList(Collection<? extends E> c) 使用其他集合容器中元素构造List
List<Integer> list2 = new LinkedList<>();list2.add(1);list2.add(2);List<Integer> list3 = new LinkedList<>(list2);
extends E表示E或者E的子类,E在这里指的就是Integer
所以构造方法可以传入参数
Collection<Integer>
E就是
List<Integer> list3 = new LinkedList<>(list2);
中的Integer
而?是
List<Integer> list2 = new LinkedList<>();
中的Integer
所以可以传,可以这样构造
ArrayList<Integer> list4 = new ArrayList<>();list4.add(1);List<Integer> ret = new LinkedList<>(list4);
这样也可以,是因为ArrayList也实现了Collection接口,所以可以传
而且ArrayList的?也是Integer所以可以传
boolean add(E e) 尾插 e
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) 尾插 c 中的元素
E remove(int index) 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element) 将下标 index 位置元素设置为 element
void clear() 清空
boolean contains(Object o) 判断 o 是否在线性表中
int indexOf(Object o) 返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List subList(int fromIndex, int toIndex) 截取部分 list
LinkedList<Integer> list = new LinkedList<>();List<Integer> list2 = new LinkedList<>();list2.add(1);list2.add(2);list2.add(1,3);System.out.println(list2);


clear就是清空了
for (Integer integer : list2) {System.out.println(integer);}
for (int i = 0; i < list2.size(); i++) {System.out.println(list2.get(i));}
Iterator<Integer> iterator = list2.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}
Iterator<Integer> iterator2 = list2.listIterator();while (iterator2.hasNext()) {System.out.println(iterator2.next());}
反正和前面的ArrayLis是一样的

3. 栈
Stack<Integer> stack = new Stack<>();stack.push(1);stack.push(2);stack.push(3);//入栈,返回值就是入栈元素3stack.pop();//出栈,返回值就是出栈元素3Integer peek = stack.peek();//获取栈顶元素,不删除
boolean empty = stack.isEmpty();
手动实现的话,就用数组—》顺序栈
用链表实现的话—》链式栈
4. 队列
Queue是个接口,底层是通过链表实现的
Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
Queue<Integer> queue = new LinkedList<>();queue.offer(1);queue.offer(2);queue.offer(3);//放入队列元素System.out.println(queue.poll());//取出对头元素,System.out.println(queue.peek());//获取对头元素,不删除System.out.println(queue.peek());

queue.isEmpty();
offer就是尾插
手动实现的话,就用LinkedList就可以实现了

或者自己定义节点也可以
两个队列来实现栈


两个栈来实现队列
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>();
这是一个双端队列
Deque也是双端队列,只不过这个是接口
Deque<Integer> deque = new ArrayDeque<>();
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

双端队列,就是两边都可以进出
Deque<Integer> deque = new LinkedList<>();//链式deque.offerFirst(1);Deque<Integer> deque2 = new ArrayDeque<>();//顺序
LinkedList和ArrayDeque都可以当做栈和队列使用
5. 二叉树
class TreeNode{int val;public TreeNode left;public TreeNode right;TreeNode(int x){val = x;}
}
还有前序遍历(根左右),中序遍历(左根右),后序遍历(左右根)
层序遍历
6. PriorityQueue优先级队列(堆)
队列是一种先进先出(FIFO)的数据结构
操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列
PriorityQueue底层使用了堆这种数据结构
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
小根堆:根节点的大小,小于孩子节点

根节点大于左右孩子—》大根堆
将元素存储到数组中后,可以根据二叉树章节的性质对树进行还原。假设i为节点在数组中的下标,则有:
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,多线程的情况下使用这个
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();priorityQueue.offer(1);priorityQueue.offer(34);priorityQueue.offer(3);System.out.println(priorityQueue.peek());//获取第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素
PriorityQueue默认是一个小根堆

传入比较器就可以控制是大根,还是小根了
. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为
6. PriorityQueue底层使用了堆数据结构
. PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素
ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(list);priorityQueue.offer(5);priorityQueue.offer(34);priorityQueue.offer(6);System.out.println(priorityQueue.peek());//获取第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素

boolean offer(E e)
插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时
间复杂度 ,注意:空间不够时候会进行扩容
E peek() 获取优先级最高的元素,如果优先级队列为空,返回null
E poll() 移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size() 获取有效元素的个数
void clear() 清空
boolean isEmpty()检测优先级队列是否为空,空返回true
我们自己传入一个Comparator,就可以实现大根堆了

默认的Integer实现了Comparator,

但是它的Comparator是小根的形式
所以我们要搞一个大根堆的Comparator
class IntCmp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);}
}
这个就是原来的小根
class IntCmp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}
}
这样就是大根了
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());priorityQueue.offer(5);priorityQueue.offer(34);priorityQueue.offer(6);System.out.println(priorityQueue.peek());//获取第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素System.out.println(priorityQueue.poll());//删掉第一个元素

这样就成功了


我们发现,堆的扩容是,如果原来容量小于64,就扩大两倍+2
如果大于64,就是扩大50%,就变为原来的150%

优先级队列的扩容说明:
如果容量小于64时,是按照oldCapacity的2倍方式扩容的
如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的
如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容
优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够
进行比较,为了简单起见,我们只是插入了Integer类型,那优先级队列中能否插入自定义类型对象呢?
在Java中,基本类型的对象可以直接比较大小。
Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。 那为什么==可以比较?
:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而==默认情况下调用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。

有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?
6.1 覆写基类的equals
class Card{public int rank;@Overridepublic boolean equals(Object o) {if(this == o) return true;//自己和自己比较返回trueif(o==null||!(o instanceof Card)){return false;}Card card = (Card) o;return rank == card.rank;}}
覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。
6.2 基于Comparble接口类的比较
class Card implements Comparable<Card>{public int rank;@Overridepublic boolean equals(Object o) {if(this == o) return true;//自己和自己比较返回trueif(o==null||!(o instanceof Card)){return false;}Card card = (Card) o;return rank == card.rank;}@Overridepublic int compareTo(@NotNull Card o) {return this.rank - o.rank;}
}
这个是从小到大的排序
这样就可以了
Card card = new Card(1);Card card2 = new Card(2);System.out.println(card.compareTo(card2));

6.3 基于比较器比较
class RankComparator implements Comparator<Card>{@Overridepublic int compare(Card o1, Card o2) {return o2.rank-o1.rank;}
}
RankComparator rankComparator = new RankComparator();System.out.println(rankComparator.compare(card, card2));

Object.equals 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否
Comparable.compareTo
需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于
内部顺序----》可以调用Arrays.sort()方法来对数组进行排序了
Comparator.compare
需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性
强
Card[] cards = new Card[]{card, card2};Arrays.sort(cards);Arrays.sort(cards, rankComparator);
这两个都是可以的
