Java-双向链表的底层实现
双向链表,顾名思义,就是这是一个双向的链表,其相比于单向链表的优点:
· 以往我们在单向链表里进行增删改查时,都需要记录修改节点的前一个节点,因为我们是单向的,‘只进不退’嘛
·双向链表对这些增删改查操作就很方便了,我们只需要找到这个要修改的节点,对这个节点的前一个节点改一改,后一个节点改一改就完成了
接下来,我们来实现一个双向链表:
1.和单向链表一样,我们需要描述出链表中的一个个 ‘节点’,也就是静态内部类ListNode,这个节点里存着三样东西:
·该节点自身的值- int val
·该节点的前一个节点的地址- ListNode prev
·该节点的下一个节点的地址- LIstNode next
public class MyLinkedList {
static class ListNode{
public int val;
public ListNode prev;
public ListNode next;
public ListNode(int val){
this.val = val;
}
}
}
在写各种方法前,我们再准备一个头节点head和一个尾节点last
public ListNode head; public ListNode last;
接下来,我们定义一个包含要实现的所有方法的接口IList:
public interface IList {
void addFirst(int data);
void addLast(int data);
void addIndex(int index,int data);
boolean contains(int key);
void remove(int key);
void removeAllKey(int key);
int size();
void clear();
void display();
void display2(MyLinkedList.ListNode Newhead);
}
一、size()
@Override
public int size() {
int count = 0;
ListNode cur = head;
while(cur!=null){
count++;
cur = cur.next;
}
return count;
}
定义一个计数器count,通过cur遍历整个链表,记录这个链表的总长度
二.addFirst(int data)
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
if(head==null){
head = node = last;
}else{
node.next = head;
head.prev = node;
head = node;
}
}
1.若头节点为空,则代表这条链表为空,我们让new出来的node节点同时成为head和last即可
2.头节点不为空,我们让node节点的next指向原来的头节点head,让原来head的prev里面的null指向这个新的头节点node,最后定义head = node(让node成为新的头节点)
三、addLast(int data)
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
if(head==null){
head = node = last;
}else{
last.next = node;
node.prev = last;
last = node;
}
}
1.若头节点为空,则代表这条链表为空,我们让new出来的node节点同时成为head和last即可
2.同样的,我们让node节点的prev指向原来的last节点,last的next节点指向新的尾节点node,最后定义last = node 即可
四、addIndex (int index,int data)
@Override
public void addIndex(int index, int data) {
//处理index越界的问题
int len = size();//链表长度
if(index<0||index>len){
throw new RuntimeException("插入位置index不合法,索引越界!");
}
if(index==0){
addFirst(data);
return;
}
if(index==len){
addLast(data);
return;
}
//接下来的情况为,插入链表中间的情况,我们需要找到index号节点的位置
ListNode cur = head;
ListNode node = new ListNode(data);
while(index!=0){
cur = cur.next;
index--;
}
//循环结束,此时cur对应index号节点
//我们的目标是将node节点插入到 P 和 cur 的中间
ListNode P = cur.prev;
P.next = node;
node.prev = P;
node.next = cur;
cur.prev = node;
}
1.我们先处理了index,插入位置不合法的情况,处理方法:抛出一个运行时异常
2.如果index==0或者index==size(),我们调用一下前面写的addFirst或者addLast就return,很方便
3.我们通过循环找到了index号节点,并对其前后都进行操作
五、contains(int key)
@Override
public boolean contains(int key) {
if(head==null){
return false;
}
ListNode cur = head;
while(cur!=null){
if(cur.val==key){
return true;
}
cur = cur.next;
}
return false;
}
1.若头节点为null,也就是链表为空,那么什么都不包含,false
2.通过cur遍历整个链表,如果找到某节点的val == key了,return 一个 true,如果遍历完都没找到则返回false
六、remove (int key)
@Override
public void remove(int key) {
if(head==null){
return;
}
if(head.val==key){
head = head.next;
/*注意此时这个head是新的头节点,也就是原来链表中的第二个节点*/
if(head!=null){
head.prev = null;
return;
}else{
last = null;
}
return;
}
if(last.val==key){
last = last.prev;
last.next = null;
return;
}
ListNode cur = head;
while(cur!=null){
if(cur.val==key){
cur.prev.next = cur.next;
return;
}
cur = cur.next;
}
//走到这里都没找打cur.val = key 的情况,说明我遍历的整个链表都没找到删除目标,那么执行return
return;
}
1.如果链表为空(head==null) 那么return
2.如果头节点就是我要删除的目标,那么我们考虑该链表是否为单节点链表,为什么呢?
·正常思路:head = head.next, 新的头节点 head.prev = null
·单节点链表的情况:我们先正常让 head = head.next,此时新头节点head指向了null!
(单节点链表:head = last, head.prev = null, head.next = null)
3.处理措施:我们先正常 head = head.next,此时 head = null,而单向链表中head = last,但是我们要指出来,last = null,这样我们就成功处理好了单节点的情况
4.我们在删除尾节点时也会遇到单节点情况,不过这种情况已经被删除head节点处理过了,接下来我们画图得出正常处理思路接口
5.遍历链表,找到删除目标节点cur,执行 cur.prev.next = cur.next
七、removeAllKey (int key)
@Override
public void removeAllKey(int key) {
if(head==null){
return;
}
while(head!=null&&head.val==key){
head = head.next;
}
if(head==null){ //如果此时新的head节点为null,说明该链表为单节点链表,处理方法和在remove里一样
last = null;
return;
}
//执行到这说明不是单节点链表,由于我们已经检查过了head.val ?= key,所以我们让cur = head.next,就不检查head了
ListNode cur = head.next;
while(cur!=null){
if(cur.val==key){
cur.prev.next = cur.next;
if(cur.next!=null){
//cur没有变,还是这个要删除的节点,不过此时已经删了,但我们还可以查看,cur的下一个节点是否为null
//如果cur.next!=null,说明cur这个节点不是尾节点
cur.next.prev = cur.prev;
}else{
//此时cur为尾节点
last = cur.prev;
}
}
cur = cur.next;
}
}
1.老样子,判断链表是否为空
2.处理单节点:
·我们先检查第一个节点是否为要删除的节点
·如果此时是要删除的节点,我们像remove一样,head = head.next,再检查新的head是否为null,为null则说明是单节点,让last = null,执行return即可
3.接下来准备遍历链表,我们让cur = head.next,为什么不是head,因为我们在处理单节点的时候就检查了head.val ?= key,是不是不重要,反正已经检查了head节点,如果==key,我们早就通过head = head.next处理过了,不是也不重要
4.while循环内又分为cur,也就是删除目标,是否为尾节点,画图即可
八、clear,display
较简单的三个方法,不做过多描述
@Override
public void clear() {
ListNode cur = head;
while(cur!=null){
ListNode curN = cur.next;
cur.next = null;
cur.prev =null;
cur = curN;
}
head = last = null;
}
@Override
public void display() {
ListNode cur = head;
while(cur!=null){
System.out.print(cur.val+" ");
cur = cur.next;
}
}
@Override
public void display2(ListNode Newhead) {
ListNode cur = Newhead;
while(cur!=null){
System.out.print(cur.val+" ");
cur = cur.next;
}
}
写到这里,我们的双向链表就实现了,感谢你的阅读~
更多推荐

所有评论(0)