分析Collection(三)链表结构的LinkedList

在这里插入图片描述

链表结构的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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值