链表分类
1.单向链表:每个元素只知道下一个元素是谁
2.双向链表:每个元素知道上一个和下一个元素
3.循环链表:通常链表尾节点tail指向的都是null,而循环链表的tail指向头结点head
链表内还有一种特殊的节点叫哨兵节点,它不存储数据,通常用作头尾,用来简化边界判断
单向链表的实现(java)
首先我们要思考在java中什么是可以代表链表节点的?
第一考虑的就是一个节点不但要存储值还要有指向下一个节点的指针,也就只有类才能实现了
1.创建节点及其构造方法:
private static class Node {
int value;
Node next;//指向下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
2.哨兵节点的引入:
哨兵节点可以大大减少链表实现的代码量. 使其头部节点不为null
private Node head = new Node(666,null);
3.尾部添加:
首先在一个链表当中是无法直接找到尾部节点的,只能通过一个个节点的遍历才能找到下一个的节点,直到下一个节点为null才能结束
public Node findlast() {
Node p;
for (p = head; p.next != null; p = p.next) {
}
return p;
这样就能找到最后一个节点,再实行添加就行了
public void addlast(int value) {
Node last = findlast();
last.next = new Node(value, null);
}
下一个节点的指针指向也是null
4.根据索引查找:
在链表当中是没有索引的,所以我们可以在遍历链表的时候记录一个变量的值随链表遍历的值就行了
public Node findindex(int index) {
int i = -1;
for (Node p = head; p != null; p = p.next, i++) {
if (index == i) {
return p;
}
}
return null;
}
因为有哨兵节点所以i是从-1开始的(哨兵节点为head)
而当我们输入时,不存在那个索引了返回null,获取时抛出异常
public IllegalArgumentException illg(int index){
return new IllegalArgumentException(String.format("index[%d],不合法",index));
}
这里先把异常方法写好
查找是要提取节点的值,返回value就行了
public int get(int index) {
if (findindex(index) == null) {
throw new IllegalArgumentException(String.format("index[%d],不合法",index));
}
return findindex(index).value;
}
5.插入
根据索引插入数值:
首先我们要知道前一个索引的节点才能够实现
我们可以采用前面的findindex方法来查找,这个时候哨兵节点就可以发挥作用了, 如果没有如果没有哨兵节点的话那么还要对index=0进行区分处理;
插入时只要改变前一个节点的指针和定义插入节点的指针指向下一个节点就行了
public void insert(int index,int value)throws IllegalArgumentException{
Node perv = findindex(index - 1);
if(perv==null){
throw illg(index);
}
perv.next=new Node(value,perv.next);
}
6.删除:
删除和插入一样的想法,找出要删除的节点前一个节点和后一个节点项链就行了,索引不存在就抛出索引异常
public void remove(int index)throws IllegalArgumentException{
Node perv = findindex(index - 1);
if(perv==null){
throw illg(index);
}
Node remove = perv.next;
if(remove==null){
throw illg(index);
}
perv.next=remove.next;
}
有哨兵节点也不用考虑索引为0的情况了
7.遍历:
遍历的方法有很多种,这里就讲一个函数接口consumer的遍历方法,consumer是一个消费型接口.用accept消费一个指定泛型的数据
public void loop(Consumer<Integer> consumer) {
for (Node p = head.next; p != null; p = p.next) {
consumer.accept(p.value);
}
}
在测试类当中
s1.loop(Consumer-> System.out.println(Consumer));
就需要传递消费方法体,这里采用Lamber方法实现
这样一个单向链表的增删改查就全部实现了
双向链表的实现思想
刚刚我们学习了单向链表的实现,那么双向链表也就是多了一个指针节点.一个tail和head用于指向,再对tail定义为哨兵节点就行了.
static class Node {
Node prev;//上一个节点
int value;//值
Node next;//下一个节点
//构造方法
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
插入的话就是指针指向比单向链表要多换一个而已
public void insert(int index,int value){
Node prev = findnode(index - 1);
if(prev==null){
throw illg(index);
}
Node next=prev.next;
Node inserted = new Node(prev, value, next);
prev.next=inserted;
next.prev=inserted;
}
后面的循环链表也是同样的道理