数据结构--链表

1. 链表原理

顺序表是元素在连续的内存空间上, 只要顺着内存继续往下走, 就能找到下一个节点
链表是在不连续的内存上
在这里插入图片描述
元素(element): 真实存于线性表中的内容,是我们关心的核心内容。
结点(node): 为了组织链表而引入的一个结构,除了保存我们的元素之外,还会保存指向下一个结点的引用

class Node {
    int val; // 保存我们的元素
    Node next; // 保存指向下一个结点的引用;其中尾节点的 next == null
}

在这里插入图片描述
当前结点(current / cur): 表示链表中某个结点。
前驱结点(previous / prev): 表示链表中某个结点的前一个结点;头结点没有前驱结点。
后继结点(next): 表示链表中某个结点的后一个结点;尾结点没有后继结点。

链表的头结点, 链表最开始的节点~
尤其是对单链表来说, 只要知道了链表的头结点就可以获取到链表的所有的元素!
通常情况下,特别喜欢用头结点来代指整个链表~

2. 链表种类

2.1 单向与双向链表

单向链表:只能够通过当前节点,找到下一个节点~,无法找到上一个节点
双向链表:通过当前节点,能找到下一个节点,也能找到上一个节点

2.2 带环与不带环链表

带环的链表: 最后一个元素不指向 null ,而是指向链表上的某个节点
不带环的链表: 最后一个元素指向 null

2.3 带傀儡节点与不带傀儡节点链表

dummy node 傀儡节点: 没啥卵用,不起实际作用 不实际存储数据,只是用来占个位置引用傀儡节点,目的是让代码写起来更简单~~
head node 头结点: 链表的第一个节点~

如果是不带傀儡节点的链表,表示空链表,就直接用 head =null 来表示
如果是带傀儡节点的链表,表示空链表(带傀儡节点,意味着无论如何)

衍生出8种链表

  1. 单向,带傀儡节点,带环的链表
  2. 单向,带傀儡节点,不带环的链表
  3. 单向,不带傀儡节点,带环的链表
  4. 单向,不带傀儡节点,不带环的链表
  5. 双向,带傀儡节点,带环的链表
  6. 双向,带傀儡节点,不带环的链表
  7. 双向,不带傀儡节点,带环的链表
  8. 双向,不带傀儡节点,不带环的链表

链表存在的最大意义:
相比于顺序表来说,链表往中间位置插入或者删除元素,都可以避免搬运,效率是比较高~

3. 链表的实现

3.1 定义节点

public class Node {
    int val;
    Node next;
    public Node(int val) {
        this.val = val;
    }

    @Override
    public String toString() {
        return "[" +
                 val +
                ']';
    }
}

3.2 链表的创建

Node n1 = new Node(1);
Node n3 = new Node(3);
Node n2 = new Node(2);
Node n6 = new Node(6);
n1.next = n3;
n3.next = n2;
n2.next = n6;
n6.next = null;
Node head = n1;

4. 实现双向链表

class Node {
    int val;
    Node prev = null;
    Node next = null;

    @Override
    public String toString() {
        return "{" + val +
                '}';
    }

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

// 实现一个双向链表
public class MyLinkedList {
    // 记录头结点位置
    private Node head;
    // 记录尾节点位置
    private Node tail;
    // 链表元素个数
    private int length;

    public MyLinkedList() {
        head = null;
        tail = null;
    }

    public int length() {
        return this.length;
    }
    // 插入节点
    // 头插
    public void addFirst(int val) {
        Node  newNode = new Node(val);
        // 空链表
        if (head == null) {
            head = newNode;
            tail = newNode;
            length++;
            return;
        }
        // 非空的情况
        newNode.next = head;
        head.prev = newNode;
        head = newNode;
        length++;
        return;
    }

    // 尾插
    public void addLast(int val) {
        Node newNode = new Node(val);
        // 空链表
        if (head == null) {
            head = newNode;
            tail = newNode;
            length++;
            return;
        }
        // 非空链表
        tail.next = newNode;
        newNode.prev = tail;
        tail = newNode;
        length++;
    }

    // 指定位置插入
    public void add(int index, int val) {
        // 先处理特殊情况
        if (index < 0 || index > length) {
            return;
        }
        // 处理头插
        if (index == 0) {
            addFirst(val);
            return;
        }
        // 处理尾插
        if (index == length) {
            addLast(val);
            return;
        }
        // 考虑一般情况
        // 此时需先找到下标
        Node nextNode = getNode(index);
        // 需要在 nextNode 之前插入
        Node newNode = new Node(val);
        Node prevNode = nextNode.prev;
        prevNode.next = newNode;
        newNode.prev = prevNode;
        newNode.next = nextNode;
        nextNode.prev = newNode;
        length++;
        return;

    }

    public void removeFirst() {
        // 考虑特殊情况
        if (head == null) {
            return;
        }
        if (head.next == null) {
            head = null;
            tail = null;
            length = 0;
            return;
        }
        // 删除头结点
        Node nextNode = head.next;
        nextNode.prev = null;
        head = nextNode;
        length--;
    }

    public void removeLast() {
        if (head == null) {
            return;
        }
        if (head.next == null) {
            head = null;
            tail = null;
            length = 0;
            return;
        }
        Node prevNode = tail.prev;
        prevNode.next = null;
        tail = prevNode;
        length--;
    }

    // 删除
    public void removeByIndex(int index) {
        if (index < 0 || index >= length) {
            return;
        }
        // 头删
        if (index == 0) {
            removeFirst();
            return;
        }
        // 尾删
        if (index == length - 1) {
            removeLast();
            return;
        }
        // 根据下标,找位置
        Node toRemove = getNode(index);
        // 记录前后位置
        Node prevNode = toRemove.prev;
        Node nextNode = toRemove.next;
        // 删除节点
        prevNode.next = nextNode;
        nextNode.prev = prevNode;
        length--;

    }

    // 按照值删除
    public void removeByValue(int val) {
        int index = indexOf(val);
        if (index == -1) {
            // 未找到 val
            return;
        }
        removeByIndex(index);
    }

    // 根据下标找节点
    public Node getNode(int index) {
        if (index < 0 || index >= length) {
            return null;
        }
        Node cur = head;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur;
    }

    // 查找
    public int get(int index) {
        if (index < 0 || index >=length) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return getNode(index).val;
    }

    public int indexOf(int value) {
        Node cur = head;
        for (int i = 0; i < length; i++) {
            if (cur.val == value) {
                return i;
            }
            cur = cur.next;
        }
        // 未找到
        return -1;
    }
    // 修改
    public void set(int index, int value) {
        if (index < 0 || index >= length) {
            throw new ArrayIndexOutOfBoundsException();
        }
        Node node = getNode(index);
        node.val = value;
    }

    public static void main(String[] args) {
        Node head = creatLinkedLisr();
        printNode(head);
        // 头插

    }

    private static void printNode(Node head) {
//        Node cur = head;
        for (; head != null; head =head.next ) {
            System.out.print(head.toString());
        }

    }

    private static Node creatLinkedLisr() {
        Node a = new Node(2);
        Node b = new Node(3);
        Node c = new Node(4);
        Node d = new Node(5);
        Node e = new Node(6);
        Node f = new Node(7);

        a.next = b;
        b.next = c;
        c.next = d;
        d.next = e;
        e.next = f;
        f.next =null;
        f.prev = e;
        e.prev = d;
        d.prev = c;
        c.prev = b;
        b.prev = a;
        a.prev = null;
        return a;

    }

}

5. 注意

  1. 链表的话由于不是数组,无法用 index 来直接取下标,实际上实现的时候,也可以实现 get / set
    方法来取下标,只不过比顺序表取下标操作要低效一些(Java标准库中的LinkedList 就是这么做的)
  2. 链表操作中,很多操作都是基于引用来进行的

顺序表:

  1. 空间连续、支持随机访问
  2. 中间或前面部分的插入删除时间复杂度O(N)
  3. 增容的代价比较大。

链表:

  1. 以节点为单位存储,不支持随机访问
  2. Java 中 LinkedList 的 add , remove 都需要先定位置, 遍历链表,才可以插入, 单从插入, 删除本身时间复杂度:O(1), 但加上遍历就是 O(N)
  3. 没有增容问题,插入一个开辟一个空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值