Author: Harry
一、单链表
说到链表,我们可以结合数组的一些特性进行分析。数组和链表都有各自的特性,下面我们就先从数组的一些局限性进行分析。
数组
基本的数组操作我就不再讲了,我们可以定义一个大小为5的数组a,然后初始化一些数据a[0]=1,a[1]=2,a[2]=3,如图。
现在想要在数组的第三个位置插入一个数,这时数组执行的操作将a[2]的数据拿出,然后再插入到a[3],最后将数据插入a[2]。
如果插入数据时发现数组的容量已经满了,这时操作就更加麻烦了,需要初始化一个更加大容量的数组将原有的数据拷贝到新数组,然后再执行插入操作。
这里只说了插入操作,同样的删除操作也是一个麻烦的事情。这时候我们就可以使用链表进行操作,可以体现出链表的优势。
链表
通过上面看到一些数组的局限性,从而我们就想有没有什么方法可以避免数组的容量局限性。
这样我们就想到了链表,链表可以快速的实现增删改操作而不必关注扩容问题,链表的概念我就不过多赘述了,如图:
在链表中插入一项:
首先来看一下我定义的一个单链表节点:
1 | public class Node { |
这是一个简单的链表结构,包含的内容有data和next,后面会讲到双链表和泛型的结合使用。
这里是单链表的一些简单操作:
1 | import singlelink.module.Node; |
我们可以看到,链表在增删改的时候是比较好操作的,刚好避免了数组这一方面的不足,不用担心扩容的问题。
但是链表有优势的同时也有不足,在查找的时候,每一次都需要从头到尾遍历一次,使得时间复杂度变高。
二、双链表
双向链表,顾名思义就是可以通过对当前节点,可以知道上一个节点和下一个节点,如图:
链表的节点定如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class Node<T> {
private Node<T> left;
private Node<T> right;
private T data;
public Node(T data){
this.data = data;
}
public Node<T> getLeft() {
return left;
}
public void setLeft(Node<T> left) {
this.left = left;
}
public Node<T> getRight() {
return right;
}
public void setRight(Node<T> right) {
this.right = right;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
这里我使用到了泛型来定义节点,这里同样的含有data,只不过这里的data我没有指定类型,可以方便存储不同类型的数据,在例子中我使用到的是User对象,如下:
1 | public class User { |
链表中的操作如下:
1 | import doublelink.module.Node; |
我需要讲一下的是remove操作,这里我使用了两种remove的方法,一种的仿照JDK的remove方法,是通过equal比较对象是否为同一个,然后再删除,这个需要查找到已经存在的对象,才能删除。
另外一种是我使用了Comparator接口,重写了compare方法,从而使得删除数据更加灵活,只需要写一个比较器传入就可以remove了。
这里写了两个比较器,一个是比较所有内容,另外一个只比较名字,可以根据需求定义不同的比较器,灵活性更好。
1 | public class UserAllInfoComparator implements Comparator<User> { |
1 | public class UserNameComparator implements Comparator<User> { |
三、队列
说到队列,我们大概来看一下Java中容器的关系,如图:
单向队列
单向队列是单向的,只能从一边进来,另一边出去,而且是先进先出,与栈刚好相反。我们可以用数组模拟,也可以用链表实现。其原理如图:
双向队列
双向队列可以重两边进来,同样可以从两边出去,如图:
循环队列
这里有数组实现了一个简单循环队列:
1 | public class Deque { |
其实队列的实现使用链表比使用数组好很多,读者可以自己用链表实现一下。