大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨🌾!
||Data Structure||
未来村村长正推出一系列【Data Structure】文章,将从解读数据结构的角度上分析Java集合的源码。因为CSDN上的大多数描述java集合的文章,关注点在于其源码和方法,很少从对数据结构的讲解为切入点进行分析。以此为契机,未来村村长希望能从数据结构开始讲起,分析java集合是如何使用和如何实现的。
文章目录
一、单向链表
最基础的链表即为单向链表,链式结构也是后续数据结构的基础。在Java中,没有指针类型我们通过类来实现链式连接。实现单向链表,我们首先定义一个节点。
class Node<E>{
E e;
Node next;//存放下一个节点的地址
//构造器
public Node(E e){
this.e = e;
this.next = null;
}
}
1、定义
定义一个单向链表,有三个属性分别代表头结点、尾节点和链表大小。
public class LinkedList<E>{
private Node<E> first;//定义头结点
private Node<E> last;//定义尾节点
private int szie;//链表存储的节点数量
//链表为空判断
public boolean isEmpty(){return first==null;}
}
2、尾插
public void add(E e){
Node<E> newNode = new Node<>(e);//为传入的数据建立节点
//若为空,则将插入的元素作为头尾节点
if(isEmpty){
first = newNode;
last = newNode;
}else{
last.next = newNode;
last = newNode;
}
}
3、删除
public void delete(Node<E> delNode){
Node<E> newNode;
Node<E> tmp;
//如果被删除节点为头结点
if(first.e.equals(delNode.e)){
first = first.next;
}
//如果被删除节点为尾节点
else if(last.e.equals(delNode.e){
newNode = first;
while(newNode.next!=last) newNode = newNode.next;
newNode.next = last.next;
last = newNode;
}
//删除的是中间节点
else{
newNode = first;
tmp = first;
while(!newNode.e.equals(delNode.e)){
tem = newNode;
newNode = newNode.next;
}
tem.next = delNode.next;
}
}
4、单向链表的反转(常考算法)
①
②
③
public void reverse(){
Node<E> current = first;//定义游标节点
Node<E> before = null;//定义指向游标节点之前的节点
while(current!=null){
last = before;
before = current;
current = current.next;
before.next = last;
}
current = before;
first = current;
while(last.next!=null){
last = last.next;
}
}
我们通过while循环,每次循环我们关注last和first的变化,第一次循环我们将last取出作为前一个节点(第一次为新头节点),before指向游标所在节点,然后游标找到下一个节点,我们让before所在节点指向我们取出的节点。就是不断地将最后一个节点拿出来,然后让先拿出的节点指向后拿出的节点。重点在于拿出再连接。
二、循环链表
循环链表的不同之处在于插入和删除,下面举例插入第一个元素时,循环链表的做法。
if(isEmpty()){
first = newNode;
last = newNode;
last.next = first;
}
我们只要在单链表的基础上,让last.next指向first而不是null即可。
三、双向链表
双向链表就是可以从前往后,也可以从后往前的链表,LinkedList的数据结构基础就是双向链表。我们来看其节点声明。
class Node<E>{
E e;
Node<E> pre;
Node<E> next;
Node(E e){
this.e = e;
this.pre = null;
this.next = null;
}
因为LinkedList的基础为双向链表,所以我们直接到LinkedList去探究其源码实现。
四、LinkedList
首先我们来看看LinkedList的基础使用。
public class IndexMain {
public static void main(String args[]) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("未来村村长01");
linkedList.add("未来村村长02");
linkedList.add("未来村村长03");
linkedList.add("未来村村长05");
linkedList.add(3,"未来村村长04");
linkedList.remove(3);
linkedList.remove();
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
输出的结果如下:
这说明LinkedList插入默认为尾插,可以指定index进行插入。但是删除默认从头开始,即“先进先出”。这里有牵扯到队列的知识。从下面class LinkedList中可以知道,其实现了List和Deque接口,一个是对线性表的接口,一个是队列的接口,所以其删除默认为“先进先出”。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
1、基本属性定义和方法
(1)基本属性
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;//尾节点
public LinkedList() {//无参构造
}
public LinkedList(Collection<? extends E> c) {//有参构造
this();
addAll(c);
}
(2)节点定义
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;
}
}
2、插入
(1)add()
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast():尾插
void linkLast(E e) {
final Node<E> l = last;//用一个空间点赋值last
final Node<E> newNode = new Node<>(l, e, null);//新节点
last = newNode;//让last成为新节点
if (l == null)
first = newNode;//如果last是空,则让头结点也赋值新节点
else
l.next = newNode;//原来的last的next赋值新节点
size++;
modCount++;
}
(2)add(index,item)
public void add(int index, E element) {
checkPositionIndex(index);//判断该位置是否正确
if (index == size)
linkLast(element);//如果插入的位置是last,采用尾插
else
linkBefore(element, node(index));//如果不是,则寻找节点位置
}
node(index)
LinkedList中将节点的定位封装起来了,使用循环找到对应位置的节点,并返回该节点。
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
linkBefore()
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;//pred为index节点的前驱节点
final Node<E> newNode = new Node<>(pred, e, succ);//新节点定义构造赋值前驱为index节点的前驱节点,后驱为index
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;//index节点的前驱节点为newNode
size++;
modCount++;
}
(3)push()
和add()方法相同。
public void push(E e) {addFirst(e);}
public void addFirst(E e) {linkFirst(e);}
3、删除
(1)remove()
public E remove() {return removeFirst();}
public E removeFirst() {
final Node<E> f = first;//用空节点装旧的头节点
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
unlinkFirst:取消头节点的连接
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;//next节点赋值头节点的下一个节点
f.item = null;
f.next = null; // help GC
first = next;//让下一个节点赋值头结点
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
(2)remove(index)
public E remove(int index) {
checkElementIndex(index);//检查index是否正确
return unlink(node(index));//node(index)同上,找到对应index的节点
}
unlink
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;//index的下一个节点
final Node<E> prev = x.prev;//index的上一个节点
if (prev == null) {
first = next;
} else {
prev.next = next;//上一个节点的下一个节点指向index的下一个节点
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;//下一个节点的上一个节点指向index的上一个节点
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
(3)pop()
public E pop() {return removeFirst();}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}