~目录
一.结点的定义
二.双向链表的定义
三.双向链表的操作
1.头插法
2.尾插法
3.任意位置插入(第一个数据节点为0号下标 )
4.查找是否包含关键字key是否在单链表当中
5.得到单链表的长度
6.打印单链表
7.删除第一次出现关键字为key的节点
8.删除所有值为key的节点
单链表的基本操作详解:
一.结点的定义
在c语言中,我们使用一个结构体来定义一个结点。但是在java中,我们使用一个类(class)来定义结点。
其中data表示当前结点中存放的数据,next表示当前结点的下一个结点,pre表示的是当前结点的前一个结点。这里还定义了一个构造方法用于初始化data的值。
public class ListNode {
public int data;
ListNode next;
ListNode prev;
public ListNode(int data) {
this.data = data;
}
}
二.双向链表的定义
这里我们定义的单链表为不带头结点的非循环双向链表,但是要有一个虚拟的头结点,来表示我们这个单链表的第一个结点。同时还有一个尾结点,表示链表的最后一个结点。
public class RealLinkedList {
public ListNode head;
public ListNode tail; //指向链表的最后一个结点,有了tail以后,尾插法的时间复杂度变成O(1)
}
三.双向链表的操作
下面要介绍的是双向链表的相关操作,这些函数放在上面的单链表RealLinkedList中。经测试,所有功能均能正确实现
1.头插法
就是在最前插入,注意对链表为空的特判
这里需要注意对tail的处理。头插法的话,tail始终是最开始那个head,所以只需要赋值一次
先创建一个node,把数据放进这个node,再把这个node头插
public void addFirst(int data){
ListNode node = new ListNode(data);
if (this.head == null){
this.head = node;
this.tail = node; //头插法的话,tail始终是最开始那个head,所以只需要赋值一次
}else{
node.next = this.head;
this.head.prev = node;
this.head = node;
}
}
2.尾插法
就是在最后面插入,注意对链表为空的特判
先创建一个node,把数据放进这个node,再把这个node尾插
注意:在双向链表中,我们引入了tail结点表示链表的最后一个结点,所以和单链表不同,此时我们不需要再像单链表那样去寻找最后一个结点,直接让tail.next = node即可,注意这里要对tail不断得进行更新
public void addLast(int data){
ListNode node = new ListNode(data);
if (this.head == null){
this.head = node;
this.tail = node;
}else{
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
}
3.任意位置插入(第一个数据节点为0号下标 )
就主要是对index的特判,如果index不合法(<0或者>length),返回false,若index为0,那就是头插,若Index为length,那就是尾插。
其他位置不需要像单链表那样去找到index前面的结点了,直接去找Index当前结点即可
public boolean addIndex(int index,int data){
if (index < 0 || index > this.size()){
return false;
}
if (index == 0){
this.addFirst(data);
return true;
}
if (index == this.size()){
this.addLast(data);
return true;
}
ListNode node = new ListNode(data);
ListNode cur = searchIndex(index);
node.prev = cur.prev;
node.next = cur;
cur.prev.next = node;
cur.prev = node;
return true;
}
searchIndex(int index)函数:
public ListNode searchIndex(int index){
ListNode cur = this.head;
int i = 0;
while (i < index){
cur = cur.next;
i++;
}
return cur;
}
4.查找是否包含关键字key是否在单链表当中
这个就很简单了,直接一轮循环结束
public boolean contains(int key){
if (this.head == null){
System.out.println("链表为空");
return false;
}
ListNode p = this.head;
while(p != null){
if(p.data == key){
return true;
}
p = p.next;
}
return false;
}
5.得到单链表的长度
这个也是非常简单,一轮循环结束
public int size(){
ListNode p = this.head;
int len = 0;
while (p != null){
p = p.next;
len++;
}
return len;
}
6.打印单链表
也是一轮循环结束,这里我是每个结点的数据一行这样打印了,也可以打印成一行
public void display(){
ListNode cur = head;
while (cur != null){
System.out.println(cur.data);
cur = cur.next;
}
}
7.删除第一次出现关键字为key的节点
主要还是一些特判,1.链表为空;2.key值为头结点上的data;3.寻找到最后一个结点的时候防止空指针异常
注意特判里面要return,以为只需要删除第一次出现的结点。
这里要注意,和单链表不同,这里如果要删除的是第一个结点,光head = head.next不够,还要把prev置空, 是因为单链表head=head.next后,已经没有引用是第一个结点的了,堆中的空间会被jvm自动回收,而这里的还有prev引用着第一个结点,固需要置空
此外就是在处理cur.next.prev的时候,如果此时的cur是最后一个结点,则会出现空指针异常,固需要判断此时cur.next是否为空,同时也要注意对tail的值进行更新
其他的位置,直接找值为key的结点就行,不需要像单链表那样去找它的前一个及诶点
public void remove(int key){
if (this.head == null){
System.out.println("链表为空");
return ;
}
if (this.head.data == key){ //如果要删除的是第一个结点,进行一波特判
this.head = head.next;
this.head.prev = null;
//注意这里要把prev置空,在单向链表中不需要置空
// 是因为单链表head=head.next后,已经没有引用是第一个结点的了,堆中的空间会被自动回收,而这里的还有prev引用着第一个结点,固需要置空
return;
}
ListNode cur = this.head;
while (cur != null){
if (cur.data == key){
cur.prev.next = cur.next;
if (cur.next == null){
this.tail = cur.prev; //如果删除的是最后一个结点,不要忘记对tail的值进行修改
break;
}
cur.next.prev = cur.prev; //如果cur为最后一个结点,这里会出现空指针异常
break;
}
cur = cur.next;
}
}
8.删除所有值为key的节点
有了上面的基础,这个就比较简单了,把上面删除第一个值为key结点的代码稍加改动即可。把找到data就break的break删除即可;但是很重要,详细的过程可以参考本人单链表的博客,有详细的图解。
public void removeAllKey(int key){
if (this.head == null){
System.out.println("链表为空");
return ;
}
ListNode cur = head.next;
while (cur != null){
if (cur.data == key){
cur.prev.next = cur.next;
if (cur.next == null){
this.tail = cur.prev; //如果删除的是最后一个结点,不要忘记对tail的值进行修改
break;
}
cur.next.prev = cur.prev; //如果cur为最后一个结点,这里会出现空指针异常
}
cur = cur.next;
}
if (this.head.data == key){
this.head = this.head.next;
this.head.prev = null;
}
}