文章目录

  • 前言
  • 一、双向链表是什么?
  • 二、案例
  • 1.节点类Node实现
  • 2.双向链表类DoubleLinkedLinst
  • 3.获取链表头,尾节点方法
  • 4.无脑增加方法add
  • 5.两个遍历方法(从头遍历和从尾遍历)
  • 6.修改节点upDate方法
  • 7删除方法delete()
  • 8.[重点菜]按顺序添加
  • 测试
  • 总结



前言

双向链表的增删改查,其中难点在于按顺序增加,如何保持节点中指向上一个节点的pre也能被修改成功


提示:以下是本篇文章正文内容,下面案例可供参考

一、双向链表是什么?

java 双向链表插入的时间复杂度 java双向链表排序_数据结构

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点

二、案例

使用带 head 头的双向链表实现 –水浒英雄排行榜

1.节点类Node实现

代码如下(示例):

/**
 * 节点类 data域 next域 pre域
 */
public class Node {
    String name;//名字
    String nikeName;//昵称
    Integer no;//排序
    Node next;//下一个节点的内存地址
    Node pre;//上一个节点的内存地址

    public Node(String name, String nikeName, Integer no) {
        this.name = name;
        this.nikeName = nikeName;
        this.no = no;
    }
	//重写了toString方便遍历后输出节点的详细信息
    @Override
    public String toString() {
        return "Node{" +
                "name='" + name + '\'' +
                ", nikeName='" + nikeName + '\'' +
                ", no=" + no +
                '}';
    }
}

2.双向链表类DoubleLinkedLinst

代码如下(示例):
包含头结点 以及无参数构造方法,在创建双向链表对象的时候自动创建data为空的头节点

public class DoubleLinkedList {
    Node head;//双向链表中的头结点

    public DoubleLinkedList() {
        //双向链表无参构造,构造头结点
        this.head = new Node(null, null, 0);
    }
}

3.获取链表头,尾节点方法

  1. 获取头
//获取链表头
    public Node getHead(){
        return head;
    }
  1. 获取链表尾部

通过遍历获取链表的最后一个节点

//获取双向链表的尾节点
    public Node getLastNode() {
        Node lastNode = null;
        if (head.next == null) {
            System.out.println("链表为空");
        }
        Node temp = head.next;
        //遍历链表
        while (true) {
            if (temp == null) {
                break;
            }
            lastNode = temp;
            temp = temp.next;
        }
        return lastNode;
    }

这两个方法作用是为了后文两种遍历方法(从头遍历和从尾遍历)做铺垫

4.无脑增加方法add

找到链表的最后节点,在他后面加上新节点

/无脑添加方法add
    public void add(Node newNode) {
        /**
         * 思路
         * 找到链表的最后有效节点让其next属性等于newNode
         * 让newNode的pre属性等于最后的有效节点
         */
        Node temp = head;//创建辅助节点
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        temp.next = newNode;
        newNode.pre = temp;
    }

5.两个遍历方法(从头遍历和从尾遍历)

  1. 从头遍历show()
//遍历链表
    public void show() {
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
//        Node temp = head;//创建辅助节点
//        while (true) {
//            if (temp.next == null) {
//                break;
//            }
//            temp = temp.next;
//            System.out.println(temp);
//        }//两种循环方式均可以
        Node temp = head.next;
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
  1. 从尾部遍历
//遍历链表根据pre
    public void showByPre() {
        Node lastNode = getLastNode();//获取链表尾部节点
        while (true) {
            if (lastNode == null | lastNode.no == 0) {
                break;
            }
            System.out.println(lastNode);
            lastNode = lastNode.pre;
        }
    }

以上的遍历方法是为了更好地检验我们顺序添加的方法addByNo是否在添加完成节点后的next和pre都赋值正确

6.修改节点upDate方法

public void upDate(Node node) {
        if (head.next == null) {
            System.out.println("链表为空,没有能被修改的数据");
            return;
        }
//        Node temp=head.next;
//        boolean flag=false;//标记
//        //通过遍历找到被修改的节点
//        while (true){
//            if (temp==null){
//                break;
//            }
//            if (temp.no==node.no){
//                flag=true;
//                break;
//            }
//            temp=temp.next;
//        }
        Node temp = head;//创建辅助节点
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == node.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next.name = node.name;
            temp.next.nikeName = node.nikeName;
        } else {
            System.out.println("没找到对应点no");
        }
    }

两种遍历方式分别找到被修改节点和被修改节点的前一个节点

7删除方法delete()

public void delete(int no) {
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        /**
         * 双向链表的删除分为三步 举例 连续节点1 2 3
         * 第一步,如果要删除中间节点2,找到节点2
         * 第二步 让2.next.pre=2.pre 让节点3的pre指向1
         * 第二步 让2.pre.next=2.next; 让1的next指向3
         */
        Node temp = head.next;//创建辅助节点
        boolean flag = false;//常见标记如果flag为false 就是没找到
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.no == no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.pre.next = temp.next;
            temp.next.pre = temp.pre;
        } else {
            System.out.println("链表中没有这个no");
        }
    }

通过遍历找到被删除节点,将其前后两个节点的next和pre修改,让两者跳过被删除节点

8.[重点菜]按顺序添加

//根据编号添加
    public void addByNo(Node node) {
        /**
         * 思路
         * 遍历数组找到node.no的后一个位置temp.next,再将node加到temp其后面,亦可以说成将node添加到temp.next前面都一样
         */
        Node temp = head;//创建辅助节点
        boolean flag = false;//创建标记
        while (true) {
            if (temp.next == null) {
                break;//说明找到最后,直接就加在链表尾部了
            }
            if (temp.next.no > node.no) {//找到添加位置
                break;
            }
            if (temp.next.no == node.no) {
                flag = true;//说明找到node.no已经存在不能重复
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("链表中已存在节点");
        } else {
            node.next = temp.next;
            node.pre = temp;
            temp.next = node;
            if (node.next != null) {
               node.next.pre = node;//此处为了避免temp指向
              //head时,链表中没有数据时候会出现的空指针异常
            }
        }
    }

为了保证我们添加后的next和pre都要指向正确的位置所以必须做出判断

情况1当我们的双向链表没有有效节点时,我们添加节点 ,

这时我们只需要改动三个属性,新节点后并没有其他节点,所以后面节点的pre不需要修改,强行执行代码会出现空指针异常

java 双向链表插入的时间复杂度 java双向链表排序_java_02


情况2当 链表中有节点,同时我们的新节点要添加在head节点之后,这时必须执行第四行代码,让新节点后一个节点的pre正常赋值

java 双向链表插入的时间复杂度 java双向链表排序_数据结构_03

测试

public class Test {
    public static void main(String[] args) {
        DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
        Node n1=new Node("1","1",1);
        Node n2=new Node("2","2",2);
        Node n3=new Node("3","3",3);
        Node n4=new Node("4","4",4);

        doubleLinkedList.addByNo(n2);
        doubleLinkedList.addByNo(n4);
        doubleLinkedList.addByNo(n3);
        doubleLinkedList.addByNo(n1);

        doubleLinkedList.show();
        System.out.println("通过pre遍历后");
        doubleLinkedList.showByPre();
    }
}

java 双向链表插入的时间复杂度 java双向链表排序_java 双向链表插入的时间复杂度_04


这里可以看出通过next属性或者pre属性 都可以正常遍历链表


总结

如有问题或建议,欢迎留言