单向链表的原理和基本实现(Java实现)
源码链接:文章内源码
基本原理
链表是一种数据存储结构。在Java原生JDK中对于链表也进行了相应实现。
单向链表是链表的其中一种,其主要原理是:
- 链表是以节点(Node)的形式存储数据,节点对象中存储了要保存的数据。
- 单向链表中的每一个节点中都持有下一个节点的引用(Node next),通过上一个节点就可以找到下一个节点,依次串联,所以想要遍历整个单向链表就需要找到第一个节点。
- 链表不同于数组,在内存中不一定是连续的空间,由于是节点存储,可以利用内存中零散的空间进行保存,只需持有下一个节点的地址即可。
- 很多人都知道链表相较于List他增删快,查询慢,主要因为,链表的修改只需要将前一个节点和后一个节点中所持的前后节点的引用进行修改,而list在进行删除或者增加操作时,往往需要将集合中的元素进行移动,所以较为耗费时间较长,但是,实际上还是需要根据实际情况来判断,如果不考虑随机插入的情况,一直是在最后进行插入,list是非常快的,因为不需要进行元素的移动,但如果是随机插入或者是在靠前的位置进行插入,链表则有很大的优势,所以到底谁快谁慢还是要结合具体问题.
基本实现
在JDK中已经封装好了链表的集合容器,我们直接用就行了,但是手动的实现一些基本功能还是有助于更好的了解底层的原理加深对于这种数据结构的认识。
1. 节点类(Node)的定义
- 实现链表首先需要定义节点,根据上面的分析,单向链表中的节点应该要持有下个节点的引用和自身所要存储的数据值。在这里的数据层为了更好的通用,应该是要封装为Object对象,但为了方便这里没有进行封装。
/***
* 节点
*/
class Node{
//************数据层************
//可封装为Object
public int no;
public String name ;
public String nick;
//******************************
//下一个节点的引用
public Node next;
public Node(int no , String name , String nick){
this.no = no ;
this.name = name ;
this.nick = nick ;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
", nick='" + nick + '\'' +
'}';
}
}
2.定义LinkedList链表类
首先我们需要声明一个头结点Head,这个head不存放具体的数据,只是记录第一个节点的地址。
public class LinkedList {
//声明一个头结点.这个节点不放具体的数据,只记录第一个节点的地址
//根据上面的Node的构造方法初始化,next指针先不赋值.
private Node head = new Node(0 , "" , "");
}
2.1添加方法的实现
- 思路分析
- 首先我们需要声明一个第三变量temp来作为指针,开始时默认指向头结点,添加新节点时循环遍历链表,每循环一次,temp就后移一位,找到链表的最后一个节点,找到最后一个节点之后只需将原来的最后一个节点的next指向新加节点即可,判断是否是最后一个节点的条件应该是temp的next值为null,这样新加的节点就变成了最后一个节点,这里需要保证新加节点的next值为null.
public class LinkedList {
//声明一个头结点.这个节点不放具体的数据,只记录第一个节点的地址
private Node head = new Node(0 , "" , "");
/**
* 添加方法,这里的添加需要保证插入的Node是新的next是null,如果next的值不为空之前使用过指向另一个
* Node地址的话会出现添加一个却把之前链表都连起来的情况,如果恰巧形成了闭环,首尾相连会出现死循环的情况
* @param newNode
* @return
*/
public boolean add(Node newNode){
//声明一个第三变量用作指针,开始默认指向头结点
Node temp = head;
//循环遍历链表,将新节点加入到next为null的节点后,就是直接加入到最后一个节点的后面
while(true){
//如果当前节点的下一个节点是null ,说明该节点是最后一个节点
if(temp.next == null){
//直接将新节点添加到最后一个节点之后,next指向新节点
temp.next = newNode;
return true;
}
//将指针后移一位,指针指向下一个节点
temp = temp.next ;
}
}
}
2.2判断链表是否为空
- 这个只需要判断头结点的next是否为null即可
/**
* 判断链表是否为空的方法
* @return
*/
public boolean isEmpty(){
//如果头结点的下一个是空,就说明链表是空的
return head.next == null ;
}
2.3修改链表数据的方法
- 思路
- 首先要根据条件找到要修该的节点,这里用no值查询作为条件
- 仍然是声明第三变量temp作为指针,循环链表,如果no值与链表中的相同就说明找到了要修改的节点.
- 在这里修改有两种思路,第一种是temp指向当前节点,修改当前节点的数据,第二种是temp指向当前节点的上一个节点,也就是说temp.next才是要修改的节点,那么可以将temp.next指向新的节点,新的节点next指向temp.next.next,即原来要修改节点的next,就达到了替换的作用,这里采用第二种方式
public boolean update