下面我们来实现以下不带头结点的双向链表
1)在集合框架里面,有一个集合类,LinkedList就是用双向链表来进行表示的
2)每一个节点,至少有3个域,至少有一个域保存了上一个节点也就是前驱信息
3)对于单链表来说,我们想要进行删除一个节点,必须知道它的前一个结点,但是对于双向链表来说,就不需要知道它的前驱结点
4)况且双向链表多了一个引用last指向这个双向链表的尾巴节点,所以尾插法是很方便的
此时head节点也就是头结点就是0X987
我们想要实现增删查改功能
class ListNode{
int data;
ListNode next;//这是保存下一个节点的地址
ListNode front;//这是保存一个前驱结点的地址
public ListNode(int data)
{
this.data=data;
}
}
class DoubleLinkedList{
ListNode head=null;//指向双向链表的头节点
ListNode tail=null;//指向双向链表的尾结点
//上面的两个指针都是属于双向链表的里面的元素
}
1)打印双向链表:
public void display(){ Node current=this.head; while(current!=null){ System.out.println(current.data); current=current.next; } }
2)打印双向链表的长度:
public int GetSize(){ int count=0; Node current=this.head; while(current!=null){ count++; current=current.next; } return count; }
3)查看某一个关键字是否在双向链表里面
public boolean contains(int data){ Node current=this.head; while(current!=null){ if(current.data==data){ return true; } current=current.next; } return false; }
1.首先我们来进行实现头插法和尾插法
1)头差法和尾插法都是需要进行判断插入是否是第一个节点,就是你的head和Last是否为空
2)在我们进行头插法的时候,进行设置第一个节点的时候,别忘了让tail指针指向结点,况且以后再进行头插法也就是说进行使用头插法插入结点的时候的时候,就不在需要动尾巴节点了
3)在我们进行尾插法的时候,需要进行设置第一个结点的时候,别忘了让head指向第一个节点,况且我们在进行尾插的时候,头节点都不需要在进行改变了
public void addFront(int data) {
ListNode node=new ListNode(data);
if(head==null)
{
this.head=node;
tail=node;
}else{
head.front=node;
node.next=head;
this.head=node;
}
}
public void addLast(int data)
{ ListNode node=new ListNode(data);
if(head==null)
{
this.head=node;
this.tail=node;
}else{
node.front=tail;
this.tail.next=node;
this.tail=node;
}
}
2.删除第一次出现的节点Key
(特殊处理头结点,尾结点,中间位置点,以及头结点只有一个的情况)
2.1)当我们在单链表实现这个题的时候,我们必须要找到需要删除的关键字的前驱,假设Key=34,我们要找到关键字为Key的节点,还要找到前驱,单链表删除结点需要特殊考虑头节点
2.2)那么在双向链表我们只需要查询到指定删除的节点就可以了,换个角度当我们想要进行删除34这个结点的时候,只需要走到34这个节点的位置就可以了
我们进行最好考虑的情况就是删除中间节点:
current.prev.next=current.next;
current.next.prev=current.prev;
从上面的代码我们可以看出来,想要进行删除一个节点,需要用到这个节点的前驱节点和后继节点,但是头节点是没有前驱结点的,尾巴节点是没有后继节点的,所以这两种情况需要我们进行特殊考虑
public void removeKey(int data){
Node current=this.head;
while(current!=null){
if(current.data==data){
if(current==head){//如果说删除的是头节点
head=head.next;
head.prev = null;
}
}else if(current==tail){//删除的是尾巴节点
tail=tail.prev;
tail.next=null;
return;
}else{//删除的是中间节点
current.prev.next=current.next;
current.next.prev=current.prev;
return;
}
}else{
current=current.next;
}
}
但是这个代码还是存在着一定的问题:
if(current==head){//如果说删除的是头节点 head=head.next; head.prev = null; }
1)如果我们在这里面要删除的是头结点,况且在整个代码中只有一个节点,这个双向链表中的唯一的头节点的front和next 域都是空的,如果我们此时执行这一段代码肯定就会发生空指针异常;
2)所以我们应该在这里面再次加上一个条件,并且别忘了把tail置为空
if(current.data==data){ if(current==head){ head=head.next; if(head!=null) {//说明这个链表除了头节点后面还有其他的节点 head.prev = null; return; }else {//说明head==null,head此时指向了空,这时候只需要把tail指向空即可 tail=null; return; } }
3)删除所有出现过的关键字Key:
public void removeKey(int data){ Node current=this.head; while(current!=null){ if(current.data==data){ if(current==head){ head=head.next; if(head!=null) { head.prev = null; }else { tail=null;//tail也是一个全局变量应该置为空 } }else if(current==tail){ tail=tail.prev; tail.next=null; }else{ current.prev.next=current.next; current.next.prev=current.prev; } } current=current.next; } }
如果我们把删除所有节点的代码写成这样:
public void removeKey(int data){ Node current=this.head; while(current!=null){ if(current.data==data){ if(current==head){ head=head.next; if(head!=null) { head.prev = null; }else { tail=null; } }else if(current==tail){ tail=tail.prev; tail.next=null; }else{ current.prev.next=current.next; current.next.prev=current.prev; } }else { current = current.next; } } }
因为删除完这个节点之后,current还是指向要被删除的节点,有进入到了循环,还是做着和刚才删除节点一样的事情,current一直是指向一个节点,所以就进行了死循环,所以我们应该让current删除之后向后走一步
3.向双向链表中插入元素(给定一个位置)
我们先定义一个结点current指向头节点,先走index位置,我们向这个位置插入一个节点,就把这个节点设置成该位置,原位置的节点都一步向后移动
1)如果是插入的下标是第一个位置,那么就采用头插法
2)如果插入的位置是最后一个位置,就采用尾插法
public void addIndex(int index,int data)
{
if(index<0||index>size())
{
throw new UnsupportedOperationException("你所插入的下标位置不合法");
}else{
Node current=this.head;
for(int i=0;i<index;i++){//当i走了index位置的时候跳出循环
{
current.current.next;
}
if(current==head)
{
addFront(data);
}else if(current==tail)
{
addLast(data);
}else{
ListNode node=new ListNode(data);
node.next=current;
node.front=current.front;
current.front.next=node;
current.front=node;
}
}
}
4.清空双向链表中的所有节点
1)直接让head和last置为空
2)我们在这里面的核心思路是,既然要清空所有节点,那么我们终将要破坏链表的结构,所以我们可以让head一直向后走,head每走一步,我们都让head的next域和front域都变成空
public void clear() { ListNode curNext=null; while(head!=null) { curNext=head.next; head.front=null; head.next=null; head=curNext; } //注意我们此时要让所有的指向链表的节点都变成空 tail=null;为了防止两边的结点还有人引用 }
class ExectorOperator{
static class Test{
public static void run(){
System.out.println("我想要成为大佬");
}
}
class Test{
public static void main(String[] args) {
Test test=null;
test.run();
}
}
}
静态的方法是不依赖于对象的,这个代码虽然会报警告,但是还是可以正常运行的,并且可以正常打印结果
但是下面这个代码的结果,就不能正常运行,因为局部变量必须要进行初始化才可以通过编译
但是如果说成员变量:可以不被进行初始化操作,引用类型的默认值是null,简单类型比如说int--->0,char--->"/u0000",boolean---->false,静态变量不能在方法里面
package OperateNode;
class Test{
public int GetMethod(){
static int i=0;
i++;
return i;
}
}
public class ExectorOperator {
public static void main(String[] args) {
Test test=new Test();
test.GetMethod();
}
}
1)但是上面这个代码连编译都无法进行编译通过
2)静态的东西是类加载的时候初始化的,如果是一般方法在类加载的时候不会初始化
package OperateNode;
class Test{
@Override
public String toString() {
System.out.println("我是重写了ToString方法");
return "生命在于运行";
}
public static void main(String[] args) {
Test test=new Test();
System.out.println(new Test());
}
}
//此时打印正常打印ToString