数据结构Java版:链表(基础篇)

简介

  • 链表

    1、线性表的一种,物理存储结构上非连续,是链式的存储结构。
    2、动态分配内存,链表在内存中是不连续的。
    3、插入和删除的效率高,查找的效率低。
    - 插入或删除不需要移动其他元素
    - 查询通过遍历定位元素,时间复杂度是O(n)

  • 数组

    1、把所有元素按次序依次存储,是顺序的存储结构。
    2、静态分配内存,数组在内存中是连续的。
    3、插入和删除的效率低,查找的效率高。
    - 插入和删除需要移动其他元素,时间复杂度是O(n)
    - 查询利用下标定位,时间复杂度是O(1)

一、定义

  • 链表结构示例

    在这里插入图片描述

  • 链表节点定义

public class ListNode {
		
    public int val;
    public ListNode next;

    public ListNode() {
    }

    public ListNode(int val) {
        this.val = val;
    }

    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

二、增删操作

1. 删除链表节点

删除前
删除后

  • 删除操作:
    1、遍历找到要待删除的节点,或者节点位置。
    2、记录待删除节点 curNode,前置节点 preNode。
    3、执行删除 preNode.next = curNode.next。
    如上图从上到下的过程。删除Node(4),执行:

     Node(3).next = Node(5)
    

2. 添加链表节点

  • 添加操作:

    1、遍历找到要待插入节点 node 的位置。
    2、记录待插入位置 curNode,前置节点 preNode。
    3、执行添加 node.next = curNode; preNode.next = node。
    如上图从下到上的过程。新增Node(4),执行 :

     Node(3).next = Node(4);
     Node(4).next = Node(5);
    

3. 增删案例

通过一段代码例子,来体验一下链表的增删操作。自定义一个链表类 LinkedNodeList。

  • 实现:
/*
自定义链表
 */
public class LinkedNodeList {

    private int size;
    private ListNode head;

    public LinkedNodeList() {
        this.size = 0;
        this.head = null;
    }

    /**
     * 添加操作
     */
    public void addToHead(ListNode node) {
        // 处理头节点
        node.next = this.head;
        this.head = node;

        // 处理节点个数
        this.size++;
    }

    public void addToTail(ListNode node) {
        if (head == null) {
            this.addToHead(node);
            return;
        }
        ListNode curNode = head;
        while (curNode.next != null) { // 循环结束后,curNode指向最后一个节点
            curNode = curNode.next;
        }
        curNode.next = node;
        size++;
    }

    /*
    定义: 插入节点到指定下标
    示例: 1->2->3
    操作: insert -> Node(7), 1
    结果: 1->7->2->3
     */
    public void insert(ListNode node, int index) {

        if (index == 0) { // 添加到头部
            this.addToHead(node);
            return;
        }

        if (index == size) { // 添加到尾部
            this.addToTail(node);
            return;
        }

        if (index < -1 || index > size) {
            throw new IllegalArgumentException(String.format("[error: index is out of range.][size: %s][index: %s]", size, index));
        }

        ListNode preNode = head;
        ListNode curNode = head;
        // 从0走到下标index需要走index步,走到index前一个节点需要index-1步
        while (index > 0) { // 循环index curNode移动到index位置
            preNode = curNode;
            curNode = curNode.next;
            index--;
        }
        node.next = curNode;
        preNode.next = node;
        size++;
    }

    /**
     * 删除操作
     */
    public void removeHead() {
        if (size == 0) {
            throw new NullPointerException("当前链表为空,不支持删除操作");
        }
        head = head.next;
        size--;
    }

    public void removeTail() {
        if (size == 0) {
            throw new NullPointerException("当前链表为空,不支持删除操作");
        }
        if (size == 1) {
            removeHead();
            return;
        }
        ListNode preNode = head;
        ListNode curNode = head;
        while (curNode.next != null) {
            preNode = curNode;
            curNode = curNode.next; // 循环结束,curNode是最后一个节点
        }
        preNode.next = null;
        size--;
    }

    public void removeByIndex(int index) {

        if (index == 0) { // 删除链表头节点
            this.removeHead();
            return;
        }
        if (index == size - 1) { // 删除链表尾节点
            this.removeTail();
            return;
        }

        if (index < -1 || index >= size) {
            throw new IllegalArgumentException(String.format("[error: index is out of range.][size: %s][index: %s]", size, index));
        }

        // 从0到下标index需要走index步
        ListNode preNode = head;
        ListNode curNode = head;
        if (index > 0) { // 循环index curNode移动到index位置
            preNode = curNode;
            curNode = curNode.next;
            index--;
        }
        // System.out.println("delete element: " + curNode.val);
        preNode.next = curNode.next;
        size--;
    }

    public ListNode head() {
        return this.head;
    }

    public int size() {
        return this.size;
    }

    public boolean empty() {
        return this.size == 0;
    }

    public void show() {
        ListNode curNode = head;
        while (curNode != null) {
            System.out.print(curNode.val + " ");
            curNode = curNode.next;
        }
        System.out.println();
    }
}    
  • 测试入口
public static void main(String[] args) {

       LinkedNodeList list = new LinkedNodeList();

       // add to head
       list.addToHead(new ListNode(1));
       list.addToHead(new ListNode(2));
       list.addToHead(new ListNode(3));
       System.out.print("添加节点到链表头部后结果: ");
       list.show();

       // insert by index
       list.insert(new ListNode(7), 1);
       System.out.print("插入节点 Node(7) 到下标1的位置到链表头部后结果: ");
       list.show();

       // add to tail
       list.addToTail(new ListNode(8));
       list.addToTail(new ListNode(9));
       System.out.print("添加节点到链表尾部后结果: ");
       list.show();

       System.out.println("-----------------------------");

       // delete head
       list.removeHead();
       System.out.print("删除链表头节点后结果: ");
       list.show();

       // delete tail
       list.removeTail();
       System.out.print("删除链表尾节点后结果: ");
       list.show();

       // delete by index
       list.removeByIndex(1);
       System.out.print("删除链表下标为1的节点后结果: ");
       list.show();

       System.out.println("最终链表长度为: " + list.size());
   }
  • 结果预览

     添加节点到链表头部后结果: 3 2 1 
     插入节点 Node(7) 到下标1的位置到链表头部后结果: 3 7 2 1 
     添加节点到链表尾部后结果: 3 7 2 1 8 9 
     -----------------------------
     删除链表头节点后结果: 7 2 1 8 9 
     删除链表尾节点后结果: 7 2 1 8 
     删除链表下标为1的节点后结果: 7 1 8 
     最终链表长度为: 3
    

三、常用基础操作

  • 示例:
    在这里插入图片描述

1. 寻找链表中间节点(快慢指针)

当节点个数为奇数没什么需要讨论,当为偶数时。会存在两个节点都可以称为中间节点.

	public ListNode getMidListNode(ListNode head) {
        if (head == null) {
            return null;
        }
        // 偏右
        ListNode fast = head;
        // 偏左
        // ListNode fast = head.next;

        ListNode slow = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

2. 反转链表(原址改变指向)

	/*
    input: 1->2->3->4->5

    left                    right
    // null                    1->2->3->4->5
    1->null                 2->3->4->5
    2->1->null              3->4->5
    3->2->1->null           4->5
    4->3->2->1->null        5
    5->4->3->2->1->null     null
     */
  
    private static ListNode reverse(ListNode head) {

        if (head == null) {
            return null;
        }

        ListNode left = null;
        ListNode right;
        while (head != null) {
            right = head.next;
            head.next = left;
            left = head;
            head = right;
        }
        return left;
    }

3. 巩固

通过一道题目来加强对链表基础操作的使用。

  • 描述:给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

  • 思路:
    1、将链表分成左右两个部分;
    2、对右半部分做翻转;
    3、比较左右两个链表是否相同(注意奇偶因素)。

  • 实现:

	/*
    1、找出链表的中间节点
    2、对后半段做 链表反转
    3、比较前后半段
    4、恢复链表
     */
    public static boolean isPalindrome(ListNode head) {
        // 结果
        boolean flag = true;

        ListNode midNodeList = findMidNodeList(head);
        // 右半段
        ListNode rightNode = midNodeList.next;
        ListNode reversedRightNode = reverseListNode(rightNode);
        ListNode left = head;
        ListNode right = reversedRightNode;
        while (right != null) {
            if (right.val != left.val) {
                flag = false;
                break;
            }
            right = right.next;
            left = left.next;
        }
        // 还原
        midNodeList.next = reverseListNode(reversedRightNode);
        return flag;
    }

    /*
    寻找链表的中间节点
    1->2->3->4     // 奇数返回2
    1->2->3->4->5  // 偶数返回3
     */
    private static ListNode findMidNodeList(ListNode head) {
        if (head == null) {
            return null;
        }
        // 虚节点,也可以使用上述的方法
        ListNode dummy = new ListNode(0, head);
        ListNode fast = dummy;
        ListNode slow = dummy;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    /*
    链表反转
    1->2->3->4->5->null
    left           right
    null           1->2->3->4->5->null
    1->null        2->3->4->5->null
    2->1->null     3->4->5->null
     */
    private static ListNode reverseListNode(ListNode head) {
        ListNode left = null;
        ListNode right;

        ListNode curNode = head;

        while (curNode != null) {
            right = curNode.next;
            curNode.next = left;
            left = curNode;
            curNode = right;
        }

        return left;
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值