链表结构的LinkedList
一、什么是链表
1.概述
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
来源: [百度百科](“链表_百度百科 (baidu.com)”)
2.分类
3.优缺点
- 优点
1.插入删除速度快
2.没有长度限制,可存储无数的元素(看内存大小)
- 缺点
1.浪费内存空间
2.查找速度慢,需要前向或后向遍历
二、LinkedList
1.概述
LinkedList实现了list接口,与ArrayList的区别就在于它的数据结构,一个使用了链表结构,一个使用了数组,各有各的优点,也有各的缺点,就比如ArrayList 插入删除慢,获取快,而LinkedList获取慢,但是插入删除快,在不同的情况下采用不同的集合容器才能使编写的程序的效率更高。
2.线程安全
LinkedList是线程不同步的,也就是不安全的
3.自写LinkedList
博主写的这些类都只写了增删改这些方法,其他的方法可以自己试着写一下,而且都是按照自己的思路来去写,并不是解释jdk的源代码的
LinkedList有一个node的内部类,就是用来存放前向或者后向的元素的,先把这个node类给整出来
我们写成Nodes 类
属性分别是
- E value;
- Nodes prev;
- Nodes next;
这些属性分别代表了什么,请看下面代码的注释
private static class Nodes<E>{
E value; //用来保存当前值
Nodes<E> prev; //保存它的上一个节点
Nodes<E> next; //保存下一个节点
public Nodes(Nodes<E> prev,E value,Nodes<E> next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
- 私有属性
当nodes类创建好后,我们可以创建一下这个SelfLinkedList的私有属性了
private Nodes<E> first; // 这个只保存第一个元素,也就是最先添加进去的
private Nodes<E> last; // 这个只保存最后一个元素,也就是最新插入的那个
private int size; //这个依然是元素的个数
- 大体结构
私有属性出来了,那么我们今天的目标是完成增删改查这4个方法,所以方法也出来了,我们开始编写出来它的结构
public class SelfLinkedList<E> implements List<E> {
private Nodes<E> first;
private Nodes<E> last;
private int size;
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
@Override
public boolean add(E e) {
// TODO Auto-generated method stub
return true;
}
@Override
public E get(int index) {
// TODO Auto-generated method stub
return null;
}
@Override
public E set(int index, E element) {
// TODO Auto-generated method stub
return null;
}
@Override
public E remove(int index) {
// TODO Auto-generated method stub
return null;
}
private static class Nodes<E>{
E value; //用来保存当前值
Nodes<E> prev; //保存它的上一个节点
Nodes<E> next; //保存下一个节点
public Nodes() {
}
public Nodes(Nodes<E> prev,E value,Nodes<E> next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
}
- 方法行为
- 添加
@Override
public boolean add(E e) {
//1.首先先判断 当前要插入的值是不是第一个,first是用来保存第一个结点的,判断first是不是null就可以了
if(this.first == null) {
//如果是第一个的话,那么它的第一个和最后一个都应该是当前这个新建立的节点
//并且新的节点的前一个和后一个都应该为null,因为它前面后面都没得东西
//先创建新的节点
Nodes nodes = new Nodes(null,e,null);
//然后将nodes赋值给第一个和最后一个也就是first和last
this.first = nodes;
this.last = nodes;
}else {
//接下来是创建第二个或者非第一个的节点(就是第二个,第三个......)
//因为是新增,那么它肯定是当前链表的最后一个,也就是last是它
//而first已经判断过不是第一个了
//那么nodes的上一个肯定是第一个(或者就是未添加前的最后一个)了,下一个依旧为null
Nodes nodes = new Nodes(this.last,e,null);
this.last.next = nodes; //把这个保存到当前还未添加的结点的下一个(未添加前的那个最后一个节点,最后一个节点的next属性不是都为null吗,就是因为它是最后一个所以后面没东西,但是我们新添加了一个,那就说明它不是最后一个了,所以我们将它的后面的节点也就是next给一个值,就是我们新建立的这个节点)
this.last = nodes; //添加这个新的结点到下一个,也就是从这里开始已经添加了
}
//添加成功后增长当前元素的数量
this.size++;
return true;
}
- 删除
删除指定位置的结点只需要把这个指定结点的前一个结点的next改成当前这个结点的next就行
这段话,修改,删除,查询都适用,解释一下为什么会有一个判断this.size - (index +1) >= index
前向和后向遍历就是 如果当前这个指定的元素它离第一个近的话,那么就从0开始向后遍历,找到这个值就行,如果这个指定的元素它离最后一个近,那么就让他从最后一个开始,向前遍历。
这样的话,如果我们要删除第一个或者查询第一个,那么我们从0开始第一个就是它。但是如果我们要查或删最后一个呢??是不是得从0遍历到最后,判断它离哪个近可以节省时间和提高性能
@Override
public E remove(int index) {
// TODO Auto-generated method stub
//定义超出索引异常
if(index >=this.size || index < 0) {
throw new IndexOutOfBoundsException();
}else {
Nodes forNode; //当前指定的结点,还未遍历出来
if(this.size - (index +1) >= index ) {
//离第一个最近
forNode = this.first; //离第一个近了那么这个值肯定要是第一个了,这个是从头开始遍历的
for(int i = 0;i< index;i++) {
forNode = forNode.next; //然后一直向后遍历,直到遍历到那个index哪里,然后这个节点就是要删除的指定节点
}
}else {
//离最后一个最近
forNode = this.last;
for(int i = this.size;i>index+1;i--) {
//为什么这里的index+1了,因为index是索引,从0开始,而size是从1开始的,可以让size - 1 或者让index+1
forNode = forNode.prev; //然后一直向前遍历,直到遍历到那个index + 1 哪里,然后这个节点就是要删除的指定节点
}
}
//先根据这个节点的next来判断,它是不是最后一个,如果为null就是了
if(forNode.next == null) {
forNode.prev.next = null; //那么它前一个结点的下一个就是null
this.last = forNode.prev; //并且将last改成它前面一个
}else {
forNode.prev.next = forNode.next;//找到指定节点的前一个,既然已经删除了当前这个节点了,那么它前一个的节点肯定是这个节点的下一个了
forNode.next.prev = forNode.prev;//同理,它下一个的节点的前一个肯定也是它前一个结点了
}
this.size--; //删除成功,减少元素的个数
return (E) forNode.value; //返回被删除元素的那个值
}
}
- 修改
@Override
public E set(int index, E element) {
// TODO Auto-generated method stub
//定义超出索引异常
if(index >=this.size || index < 0) {
throw new IndexOutOfBoundsException();
}else {
Nodes forNode;
E oldValue; //记录未修改前的值
if(this.size - (index +1) >= index ) {
//这个解释和上面删除解释一样
//离第一个最近
forNode = this.first;
for(int i = 0;i< index;i++) {
forNode = forNode.next;
}
oldValue = (E) forNode.value; //把旧值赋给这个oldvalue
}else {
//离最后一个最近
forNode = this.last;
for(int i = this.size;i>index+1;i--) {
forNode = forNode.prev;
}
oldValue = (E) forNode.value;//把旧值赋给这个oldvalue
}
forNode.value = element; //把新值给这个指定的节点
return oldValue; //返回被修改的值
}
}
- 查询
@Override
public E get(int index) {
//定义超出索引异常
if(index >=this.size || index < 0) {
throw new IndexOutOfBoundsException();
}else {
Nodes forNode;
//这个遍历。。。和上面的还是一样的解释
if(this.size - (index +1) >= index ) {
//离第一个最近
forNode = this.first;
for(int i = 0;i< index;i++) {
forNode = forNode.next;
}
return (E) forNode.value; //唯一的区别就是,它直接把这个指定节点的值给返回了
}else {
//离最后一个最近
forNode = this.last;
for(int i = this.size;i>index+1;i--) {
forNode = forNode.prev;
}
return (E) forNode.value;
}
}
// TODO Auto-generated method stub
}
- 一块调试了
上面就是自己实现的LinkedList,有时候动手试一遍,比看的要香,还记得牢
如果有错的地方,希望各位大佬同行在评论区指正,知错就改,避免带歪看这篇文文章的人,谢谢
欢迎大家访问:http://www.fanxing.live