Java链表的使用

本文深入探讨Java链表的使用,包括单链表、双向链表和循环链表的特点及操作方法,如插入、删除、查询和修改节点。通过具体代码示例,详细解释了不同链表类型在各种操作中的效率差异。

Java链表的使用

前言

说明:

  • 语言:Java
  • 环境:IntelliJ IDEA
  • JDK版本:1.8
  • 源码:GitHub
  • 链表的插入、查询、排序通常涉及算法,本文重点是探究链表,并非算法,因此代码只是以最通俗易懂的方式编写

在学习Java链表的使用之前,需要先了解Java引用类型的使用

int a = 10;
int b = a;
b++;
System.out.println(a);

上面这段代码的运行结果为:10。在int b = a;这个操作中,实际是将a的值赋给b,所以b++;不会影响a的值,这是基本数据类型的特性。

//Employee是描述员工信息的实体类
Employee employee1 = new Employee(1,"xiaoming",30);
Employee employee2 = employee1;
employee2.seteName("liumei");
System.out.println(employee1.toString());

上面这段代码的运行结果为:Employee{eId=1, eName='liumei', eAge=30}。这就说明,在Employee employee2 = employee1;操作中,使得employee1和employee2共享相同的数据,因此两个变量任意一个改变数据,都会影响对方。

实际上,new是在内存中划分一个空间来存储数据,而接收new的employee1变量只是一个指针,指向这个内存空间,当把这个变量赋给变量employee2时,employee2也将是指向和employee1相同内存空间的指针,因此操作这两个变量,实际上都是对同一内存空间的操作。

单链表

单链表

特点:

  • 拥有next指针,指向下一个节点
  • 只能单向的从头结点为起点操作链表
  • 在链表首部插入节点效率高,在链表尾部插入节点效率低

插入节点:

     /**
     * 在链表尾部插入数据
     */
    public boolean addLast(Employee employee){
        Node node = new Node(employee); //创建要插入的节点
        Node temp = this.head;  //创建辅助指针指向head节点
        while (true){   //移动辅助指针到当前链表最后一个节点
            if(temp.next==null){break;}
            temp = temp.next;
        }
        temp.next = node;   //将要插入的节点插入到链表尾部
        this.length++;
        return true;
    }

    /**
     * 在链表首部插入数据
     */
    public boolean addFirst(Employee employee){
        Node node = new Node(employee); //创建要插入的节点
        Node temp = this.head;  //创建辅助指针指向head节点
        if(isEmpty()){    //链表为空情况
            this.head.next = node;  //直接在head后插入节点
        }else{  //链表不为空情况
            temp = temp.next;   //移动temp到第一个有效节点
            this.head.next = node;  //将要插入节点插入到head节点后
            node.next = temp;   //将其他节点连接到刚插入的节点上
        }
        this.length++;
        return true;
    }

    /**
     * 在index节点之后插入节点,索引从0开始
     */
    public boolean addIndex(Employee employee,int index){
        if(isEmpty()||index>=this.length||index<0){ return false;}//空链表、索引格式范围不正确则插入失败
        Node node = new Node(employee); //创建要插入的节点
        Node temp = this.head;  //创建辅助指针指向head节点
        for (int i = 0;i<=index;i++){   //将辅助指针移动到目标节点
            temp = temp.next;
        }
        node.next = temp.next;  //让新节点的next指向目标节点之后的节点
        temp.next = node;   //让辅助指针指向新节点
        this.length++;
        return true;
    }

删除节点:

     /**
     * 删除最后一个节点
     */
    public boolean deleteLast(){
        if(isEmpty()){return false;}
        Node temp = this.head;  //创建辅助指针指向head节点
        while (true){   //移动辅助指针到当前链表最后一个节点的前一个节点
            if(temp.next.next==null){break;}
            temp = temp.next;
        }
        temp.next = null;   //断开与最后一个节点的的连接
        this.length--;
        return true;
    }

    /**
     * 删除第一个节点
     */
    public boolean deleteFirst(){
        if(isEmpty()){return false;}
        Node temp = this.head.next;  //创建辅助指针指向第一个有效节点
        this.head.next = temp.next; //将头节点连接到第一个有效节点之后的节点
        this.length--;
        return true;
    }

    /**
     * 删除指定索引节点,索引从0开始
     */
    public boolean deleteIndex(int index){
        if(isEmpty()||index>=this.length||index<0){ return false;}//空链表、索引格式范围不正确则删除失败
        Node temp = this.head;  //创建辅助指针指向头结点
        for (int i = 0;i<index;i++){    //将辅助指针移动到目标节点之前的节点
            temp = temp.next;
        }
        temp.next = temp.next.next; //让辅助指针指向目标节点之后的节点
        this.length--;
        return true;
    }

查询节点:

    /**
     * 查看index位置的数据,索引从0开始
     */
    public Employee getEmployee(int index){
        if(isEmpty()||index>=this.length||index<0){ return null;}//空链表、索引格式范围不正确则查询失败
        Node temp = this.head;  //创建辅助指针指向头结点
        for (int i = 0;i<=index;i++){    //将辅助指针移动到目标节点
            temp = temp.next;
        }
        return temp.data;   //返回数据
    }

修改节点:与查询类似,需要定制规则

双向链表

双向链表

特点:

  • 拥有next指针和previous指针,next指针指向下一个节点,previous指针指向前一个节点
  • 允许双向操作链表
  • 双向链表通常拥有头结点和尾结点,这样从双端插入数据效率都很高
  • 由于允许双向操作链表,所以查询效率高于单链表

插入节点:

    /**
     * 在链表尾部插入数据
     */
    public boolean addLast(Employee employee){
        Node node = new Node(employee); //创建要插入的节点
        if(isEmpty()){  //如果链表为空
            this.head.next = node;  //将head和last节点连接到node
            this.last.pre = node;
            node.next = this.last;  //将node的next和pre连接到last和head
            node.pre = this.head;
        }else{  //如果链表不为空(注意顺序)
            node.next = this.last;  //将node连接到last和当前最后一个节点
            node.pre = this.last.pre;
            this.last.pre.next = node;  //将last和当前最后一个节点连接到node
            this.last.pre = node;
        }
        this.length++;
        return true;
    }

    /**
     * 在链表首部插入数据
     */
    public boolean addFirst(Employee employee){
        Node node = new Node(employee); //创建要插入的节点
        if(isEmpty()){  //如果链表为空
            this.head.next = node;  //将head和last节点连接到node
            this.last.pre = node;
            node.next = this.last;  //将node的next和pre连接到last和head
            node.pre = this.head;
        }else{  //如果链表不为空(注意顺序)
            node.next = this.head.next;  //将node连接到head和当前第一个节点
            node.pre = this.head;
            this.head.next.pre = node;  //将head和当前第一个节点连接到node
            this.head.next = node;
        }
        this.length++;
        return true;
    }

    /**
     * 在index节点之后插入节点,索引从0开始
     */
    public boolean addIndex(Employee employee,int index){
        if(isEmpty()||index>=this.length||index<0){return false;}//空链表、索引格式范围不正确则插入失败
        Node node = new Node(employee);;
        Node temp;
        if((index+1)<=this.length/2){   //如果要插入的位置小于链表总长度一半,则在前半段插入
            temp = this.head;
            for (int i = 0;i<=index;i++){
                temp = temp.next;
            }
            node.next = temp.next;
            node.pre = temp;
            temp.next.pre = node;
            temp.next = node;
        }else{  //如果要插入的位置大于链表总长度的一半,则在后半段插入
            temp = this.last;
            for (int i = 0;i<=this.length-(index+1);i++){
                temp = temp.pre;
            }
            node.next = temp.next;
            node.pre = temp;
            temp.next.pre = node;
            temp.next = node;
        }
        this.length++;
        return true;
    }

删除节点:

    /**
     * 删除最后一个节点
     */
    public boolean deleteLast(){
        if(isEmpty()){return false;}
        this.last.pre.pre.next = this.last; //将最后一个节点的前一个节点的next指向last
        this.last.pre = this.last.pre.pre;//将last的pre指向最后一个节点的前一个节点
        this.length--;
        return true;
    }

    /**
     * 删除第一个节点
     */
    public boolean deleteFirst(){
        if(isEmpty()){return false;}
        this.head.next.next.pre = this.head;//将第一个节点的后一个节点的pre指向head
        this.head.next = this.head.next.next;//将head的next指向第一个节点的后一个节点
        this.length--;
        return true;
    }

    /**
     * 删除指定索引节点,索引从0开始
     */
    public boolean deleteIndex(int index){
        if(isEmpty()||index>=this.length||index<0){return false;}//空链表、索引格式范围不正确则删除失败
        Node temp;
        if((index+1)<=this.length/2){   //如果要插入的位置小于链表总长度一半,则在前半段删除
            temp = this.head;
            for (int i = 0;i<=index;i++){
                temp = temp.next;
            }
            temp.pre.next = temp.next;
            temp.next.pre = temp.pre;
        }else{  //如果要插入的位置大于链表总长度的一半,则在后半段删除
            temp = this.last;
            for (int i = 0;i<=this.length-(index+1);i++){
                temp = temp.pre;
            }
            temp.pre.next = temp.next;
            temp.next.pre = temp.pre;
        }
        this.length--;
        return true;
    }

查找节点:

    /**
     * 查看index位置的数据,索引从0开始
     */
    public Employee getEmployee(int index){
        if(isEmpty()||index>=this.length||index<0){return null;}//空链表、索引格式范围不正确则删除失败
        Node temp;
        if((index+1)<=this.length/2){   //如果要插入的位置小于链表总长度一半,则在前半段查找
            temp = this.head;
            for (int i = 0;i<=index;i++){
                temp = temp.next;
            }
            return temp.data;
        }else{  //如果要插入的位置大于链表总长度的一半,则在后半段查找
            temp = this.last;
            for (int i = 0;i<=this.length-(index+1);i++){
                temp = temp.pre;
            }
            return temp.data;
        }
    }

修改节点:与查询类似,需要定制规则

循环链表

循环链表

特点:

  • 拥有next指针,指向下一个节点
  • 链表的最后一个节点的next指向第一个节点,形成循环
  • 为了实现环的完整性,循环链表通常不需要头节点
  • 只能进行一个方向的循环

循环链表的增删改查需要根据需求确定,普通实现可类比将最后一个节点的next连接到首节点的单链表。实际上需要根据需求定制在环的什么位置插入,在什么位置删除,以及判定循环一圈和循环终止的条件

双向循环链表

双向循环链表

特点:

  • 拥有next指针和previous指针,next指针指向下一个节点,previous指针指向前一个节点
  • 链表的最后一个节点的next指向第一个节点,第一个节点的previous指正指向最后一个节点,形成循环
  • 为了实现环的完整性,循环链表通常不需要头节点
  • 可以进行双向的循环

双向循环链表的增删改查需要根据需求确定,普通实现可类比将最后一个节点的next连接到首节点,首节点的previous连接在最后一个节点的双向链表。实际上需要根据需求定制在环的什么位置插入,在什么位置删除,以及判定循环一圈和循环终止的条件

转载于:https://my.oschina.net/rawlins/blog/3102322

### Java 链表使用方法 在 Java 中,链表通常通过自定义节点类来实现。以下是一个简单的单链表节点类定义: ```java class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } ``` #### 创建链表 可以通过创建节点并将它们连接起来来创建链表: ```java ListNode head = new ListNode(1); ListNode second = new ListNode(2); ListNode third = new ListNode(3); head.next = second; second.next = third; ``` #### 遍历链表 使用循环遍历链表中的每个节点: ```java ListNode current = head; while (current != null) { System.out.println(current.val); current = current.next; } ``` #### 插入节点 在链表中插入新节点,例如在链表头部插入节点: ```java ListNode newNode = new ListNode(0); newNode.next = head; head = newNode; ``` #### 删除节点 删除链表中的节点,例如删除指定值的节点: ```java ListNode prev = null; ListNode current = head; int target = 2; while (current != null) { if (current.val == target) { if (prev == null) { head = current.next; } else { prev.next = current.next; } break; } prev = current; current = current.next; } ``` ### LeetCode 对 Java 链表的考察方式 #### 链表的基本操作 LeetCode 会考察链表的插入、删除、反转等基本操作。例如,LeetCode 206 题反转链表,需要将链表中的节点顺序反转。 ```java class Solution { public ListNode reverseList(ListNode head) { ListNode pre = null; ListNode cur = head; while (cur != null) { ListNode nxt = cur.next; cur.next = pre; pre = cur; cur = nxt; } return pre; } } ``` #### 链表的合并分割 如 LeetCode 21 题合并两个有序链表,需要将两个有序链表合并成一个新的有序链表;LeetCode 86 题分隔链表,需要将链表按给定值进行分割。 ```java class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode current = dummy; while (l1 != null && l2 != null) { if (l1.val < l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } if (l1 != null) { current.next = l1; } if (l2 != null) { current.next = l2; } return dummy.next; } } ``` #### 链表的查找判断 LeetCode 会考察链表中特定节点的查找,如链表的中间节点(LeetCode 876 题);还会考察链表的一些特性判断,如判断链表是否为回文链表(LeetCode 234 题)[^3]。 ```java class Solution { public ListNode middleNode(ListNode head) { ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; } } ``` #### 链表的相交环 考察链表是否相交(LeetCode 160 题)以及链表是否存在环(LeetCode 141 题)。对于链表相交问题,可以使用哈希表来解决[^2]。 ```java import java.util.HashSet; import java.util.Set; class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { Set<ListNode> set = new HashSet<>(); ListNode nodeA = headA; while (nodeA != null) { set.add(nodeA); nodeA = nodeA.next; } ListNode nodeB = headB; while (nodeB != null) { if (set.contains(nodeB)) { return nodeB; } nodeB = nodeB.next; } return null; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值