数据结构之链表(Java)

目录

自身的特点

适合解决的问题

时间复杂度

不适合的应用场景

链表常用类型

单链表

循环链表

双向链表

单向链表和双向链表如何选择

双向循环链表

链表 VS 数组

检查链表的常用边界

代码

代码练习1 (反转单链表)

代码练习2(检测是否是循环链表)

代码练习3(合并两个有序链表)

代码练习4(删除链表倒数第n个节点)

代码练习5(查找链表中间节点)

  • 自身的特点

    • 不需要连续的内存空间,它可以通过指针将零散的内存碎片串联起来使用
    • 每个零散的内存块相当于一个节点,为了将每个节点都串联起来,每个节点除了需要存储数据外,还需要记录链表上的下个节点的地址,记录下个节点地址的指针叫做后继指针
  • 适合解决的问题

    • 数据插入
      • 并不需要为了保持内存的连续性而搬移节点
      • 时间复杂度 O(1)
      • 注意如果从中间指定位置插入,虽然插入时间复杂度是 O(1),但插入前需要查找这个位置,这个过程是O(n)的,根据复杂度中的加法法则,插入的时间复杂度是O(n)
    • 数据删除
      • 并不需要为了保持内存的连续性而搬移节点
      • 时间复杂度 O(n)
      • 注意如果从中间指定位置删除,虽然删除时间复杂度是 O(1),但删除前需要查找这个位置,这个过程是O(n)的,根据复杂度中的加法法则,删除的时间复杂度是O(n)
      • 删除时,必须知道删除节点的前驱节点,因为要改变前驱节点的下一个节点,这个过程也是O(n),因为要遍历整个链表
  • 时间复杂度

    • 插入与删除 O(n)
    • 随机访问 O(n)
  • 不适合的应用场景

    • 随机访问
      • 有序内存不是连续性的,所以就不能通过下标随机访问,随机访问只能遍历各个节点,在找到对应位置的节点
      • 时间复杂度 O(n)
  • 链表常用类型

  • 单链表

    • 头结点:记录链表的基地址,有了它,可以遍历整个链表
    • 尾结点:最后一个节点,如果指针的下一节点地址是空地址NULL,说明这个节点是最后一个节点
    • 前驱节点:无,找到前驱节点必须在遍历一边链表,时间复杂度 O(n)
  • 循环链表

    • 尾结点:最后一个节点的下一节点地址是头结点
  • 双向链表

    • 常用
    • 单链表只有一个方向,每个节点只有下一节点指针,无法定位上一节点,双向链表顾名思义,不但有下一节点指针,还有上一节点指针(prev
    • 优点:找到前驱节点,时间复杂度O(1),这是个优势,单向链表在插入删除时,找打前驱节点的时间复杂度是 O(n) ,双向链表就优化了这点
    • 缺点:占用空间比单向链表大,因为会记录个前驱节点。
    • java 的LinkedHashMap 用的就是双向链表
  • 单向链表和双向链表如何选择

    • 内存空间小,采用单向链表,以时间换空间
    • 内存空间足够,在意时间,用双向链表,以空间换时间
  • 双向循环链表

    • 双向链表的尾结点指向头结点
  • 链表 VS 数组

    • 内存使用苛刻,选数组,链表会有额外的空间存储指针,并且对链表频繁的删除、插入,会导致内存频繁的申请与释放,容易造成内存碎片,java语言就会频繁的GC
    • 数组大小固定,容易越界
  • 检查链表的常用边界

    • 如果链表为空时,链表是否能正常工作
    • 如果链表只包含一个节点时,链表是否能正常工作
    • 如果链表只包含两个节点时,链表是否能正常工作
    • 代码逻辑在处理头结点和尾节点时,代码是否能正常工作
  • 代码

  • 代码练习1 (反转单链表

public class Node {
    private int data;
    private Node next;

    public Node(int data) {
        this.data = data;
    }

    protected Node() {
    }

    protected int getData() {
        return data;
    }

    protected void setData(int data) {
        this.data = data;
    }

    protected Node getNext() {
        return next;
    }

    protected void setNext(Node next) {
        this.next = next;
    }
}



/**
 * 反转单链表
 */
public class Reverse {
    static Node head = new Node();

    private void insert(int value) {
        Node node = new Node(value);
        if (head == null) {
            head.setNext(node);
        }
        node.setNext(head.getNext());
        head.setNext(node);
    }

    private Node reverse() {
        Node pre = head;
        Node curr = new Node();
        Node next;
        while (pre.getNext() != null) {
            // 将原链表的首节点拿出来
            next = pre.getNext();
            // 将首节点的下一节点定义为首节点
            pre.setNext(next.getNext());

            next.setNext(curr.getNext());
            curr.setNext(next);
        }
        return curr;
    }

    private void print(Node node) {
        Node tmp = node.getNext();
        while (tmp != null) {
            System.out.println(tmp.getData());
            tmp = tmp.getNext();
        }
    }

    public static void main(String[] args) {
        Reverse reverse = new Reverse();
        for (int i = 0; i < 11; i++) {
            reverse.insert(i);
        }
        reverse.print(reverse.reverse());
    }

}
  • 代码练习2(检测是否是循环链表)

/**
 * 检测是否是循环链表
 */
public class CheckRing {
    static Node head = new Node();

    // 循环链
    private void insert(int value) {
        Node node = new Node(value);
        if (head == null) {
            head.setNext(node);
            node.setNext(head);
        }
        node.setNext(head.getNext());
        head.setNext(node);
    }

    private Boolean checkRing() {
        // 慢, 这个节点会慢慢的前进
        Node slow = head;
        // 这个节点会快点前进
        Node fast = head.getNext();
        // 如果存在环, 快节点是不会存在null的
        while (fast.getNext() != null && fast != null) {
            slow = slow.getNext();
            fast = fast.getNext().getNext();
            // 当快节点和慢节点相遇, 说明有环
            if (slow == fast) {
                return true;
            }
        }
        return false;
    }


}
  • 代码练习3(合并两个有序链表)

/**
 * 合并链表
 */
public class MergeLink {

    /**
     * 合并两个有序了列表
     *
     * @param link1 第一个有序列表, 没有头结点
     * @param link2 第二个有序列表, 没有头结点
     * @return
     */
    private Node merge(Node link1, Node link2) {
        // 申请一个头节点
        Node newLink = new Node(0);
        // 让这个节点不断的去变化
        Node tmp = newLink;
        while (link1 != null && link2 != null) {
            if (link1.getData() < link2.getData()){
                tmp.setNext(link1);
                link1 = link1.getNext();
            }else {
                tmp.setNext(link2);
                link2 = link2.getNext();
            }
            tmp = tmp.getNext();
        }

        return newLink.getNext();
    }


}
  • 代码练习4(删除链表倒数第n个节点)

/**
 * 删除链表倒数第 n 个节点
 */
public class DeleteNode {
    Node head = new Node();

    // 删除倒数第n个节点
    private Node deleteNodeOfReciprocal(Node link, int n) {
        // 一次遍历法
        // 两个节点一直保持这1的差距,当快节点的下一节点是空时,返回慢节点
        Node slow = link;
        Node fast = link;
        int i = 1;

        while (i < n) {
            fast = fast.getNext();
            i++;
        }

        while (fast.getNext() != null) {
            fast = fast.getNext();
            slow = slow.getNext();
        }
        return slow;

    }
}
  • 代码练习5(查找链表中间节点)

/**
 * 查找中间节点
 */
public class MiddleNode {

    private Node findMiddleNode(Node list) {
        // 两个节点,一个快一个慢,快的一次走两步,慢的一次走一步,快的走完后,去慢节点
        Node fast = list;
        Node slow = list;

        while (fast != null && fast.getNext() != null) {
            fast = fast.getNext().getNext();
            slow = slow.getNext();
        }

        return slow;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值