资讯专栏INFORMATION COLUMN

数据结构与算法——链表

tracymac7 / 3231人阅读

摘要:今天来看另一种很基础的数据结构链表。其次是尾结点的指针,它指向了,表示链表结束。但是,如果我们要查找链表数据怎么办呢链表的内存不是连续的,不能像数组那样根据下标访问,所以只能通过遍历链表来查找,时间复杂度为。

1. 概述

前面说到了数组,利用连续的内存空间来存储相同类型的数据,其最大的特点是支持下标随机访问,但是删除和插入的效率很低。今天来看另一种很基础的数据结构——链表。链表不需要使用连续的内存空间,它使用指针将不连续的内存块连接起来,形成一种链式结构。

2. 单链表

首先来看看单链表,存储数据的内存块叫做节点,每个节点保存了一个 next 指针,指向下一个节点的内存地址,结合下图你就很容易看明白了:

其中有两个节点指针比较的特殊,首先是链表头节点的指针,它指向了链表的第一个节点的地址,利用它我们可以遍历得到整个链表。其次是尾结点的指针,它指向了 null ,表示链表结束。

不难看出,单链表的最大特点便是使用指针来连接不连续的节点,这样我们不用担心扩容的问题了,并且,链表的插入和删除操作也非常的高效,只需要改变指针的指向即可。

结合上图不难理解,单链表能够在 O(1) 复杂度内删除和添加元素,这就比数组高效很多。但是,如果我们要查找链表数据怎么办呢?链表的内存不是连续的,不能像数组那样根据下标访问,所以只能通过遍历链表来查找,时间复杂度为 O(n)。下面是单链表的代码示例:

public class SingleLinkedList {
    private Node head = null;//链表的头节点

    //根据值查找节点
    public Node findByValue(int value) {
        Node p = head;
        while (p != null && p.getData() != value)
            p = p.next;
        return p;
    }

    //根据下标查找节点
    public Node findByIndex(int index) {
        Node p = head;
        int flag = 0;
        while (p != null){
            if (flag == index)
                return p;
            flag ++;
            p = p.next;
        }
        return null;
    }

    //插入节点到链表头部
    public void insertToHead(Node node){
        if (head == null) head = node;
        else {
            node.next = head;
            head = node;
        }
    }
    public void insertToHead(int value){
        this.insertToHead(new Node(value));
    }

    //插入节点到链表末尾
    public void insert(Node node){
        if (head == null){
            head = node;
            return;
        }

        Node p = head;
        while (p.next != null) p = p.next;
        p.next = node;
    }
    public void insert(int value){
        this.insert(new Node(value));
    }

    //在某个节点之后插入节点
    public void insertAfter(Node p, Node newNode){
        if (p == null) return;
        newNode.next = p.next;
        p.next = newNode;
    }
    public void insertAfter(Node p, int value){
        this.insertAfter(p, new Node(value));
    }

    //在某个节点之前插入节点
    public void insertBefore(Node p, Node newNode){
        if (p == null) return;
        if (p == head){
            insertToHead(newNode);
            return;
        }
        //寻找节点p前面的节点
        Node pBefore = head;
        while (pBefore != null && pBefore.next != p){
            pBefore = pBefore.next;
        }

        if (pBefore == null) return;
        newNode.next = pBefore.next;
        pBefore.next = newNode;
    }
    public void insertBefore(Node p, int value){
        insertBefore(p, new Node(value));
    }

    //删除节点
    public void deleteByNode(Node p){
        if (p == null || head == null) return;
        if (p == head){
            head = head.next;
            return;
        }
        Node pBefore = head;
        while (pBefore != null && pBefore.next != p){
            pBefore = pBefore.next;
        }

        if (pBefore == null) return;
        pBefore.next = pBefore.next.next;
    }

    //根据值删除节点
    public void deleteByValue(int value){
        Node node = this.findByValue(value);
        if (node == null) return;
        this.deleteByNode(node);
    }

    //打印链表的所有节点值
    public void print(){
        Node p = head;
        while (p != null){
            System.out.print(p.getData() + "  ");
            p = p.next;
        }
        System.out.println();
    }
        //定义链表节点
    public static class Node{
        private int data;
        private Node next;

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

        public int getData() {
            return data;
        }
    }
}
3. 循环链表

循环链表和单链表的唯一区别便是链表的尾结点指针并不是指向 null,而是指向了头节点,这样便形成了一个环形的链表结构:

4. 双向链表

双向链表,顾名思义,就是链表不只是存储了指向下一个节点的 next 指针,还存储了一个指向前一个节点的 prev 指针,如下图:

为什么要使用这种具有两个指针的链表呢?主要是为了解决链表删除和插入操作的效率问题。

在单链表中,要删除一个节点,必须找到其前面的节点,这样就要遍历链表,时间开销较高。但是在双向链表中,由于每个节点都保存了指向前一个节点的指针,这样我们能够在 O(1) 的时间复杂度内删除节点。

插入操作也类似,比如要在节点 p 之前插入一个节点,那么也必须遍历单链表找到 p 节点前面的那个节点。但是双向链表可以直接利用前驱指针 prev 找到前一个节点,非常的高效。

这也是双向链表在实际开发中用的更多的原因,虽然每个节点存储了两个指针,空间开销更大,这就是一种典型的用空间换时间的思想。

下面是双向链表的代码示例:

public class DoubleLinkedList {
    private Node head = null;//链表的头节点

    //在某个节点之前插入节点,这里方能体现出双向链表的优势
    public void insertBefore(Node p, Node newNode) {
        if (p == null) return;
        if(p == head) {
            this.insertToHead(newNode);
            return;
        }
        
        newNode.prev = p.prev;
        p.prev.next = newNode;
        
        newNode.next = p;
        p.prev = newNode;
    }
    public void insertBefore(Node p, int value) {
        this.insertBefore(p, new Node(value));
    }
    
    //删除某个节点
    public void deleteByNode(Node node) {
        if(node == null || head == null) return;
        if (node == head) {
            head = head.next;
            if(head != null) head.prev = null;
            return;
        }
        Node prev = node.prev;
        Node next = node.next;
        prev.next = next;
        if(next != null) next.prev = prev;
    }
    
    //根据值删除节点
    public void deleteByValue(int value) {
        Node node = this.findByValue(value);
        if (node == null) return;
        this.deleteByNode(node);
    }
   
    //定义链表节点
    public static class Node{
        private int data;
        private Node prev;//链表的前驱指针
        private Node next;//链表的后继指针
        
        public Node(int data) {
            this.data = data;
            this.prev = null;
            this.next = null;
        }

        public int getData() {
            return data;
        }
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73690.html

相关文章

  • 学习数据结构算法链表

    摘要:本系列所有文章第一篇文章学习数据结构与算法之栈与队列第二篇文章学习数据结构与算法之链表第三篇文章学习数据结构与算法之集合第四篇文章学习数据结构与算法之字典和散列表第五篇文章学习数据结构与算法之二叉搜索树简单介绍链表链表一种常见的数据结构,可 本系列所有文章:第一篇文章:学习数据结构与算法之栈与队列第二篇文章:学习数据结构与算法之链表第三篇文章:学习数据结构与算法之集合第四篇文章:学习数...

    jerryloveemily 评论0 收藏0
  • 学习JavaScript数据结构算法(二):链表

    摘要:实现移除给定的元素要移除的元素返回值表示移除成功方法说明移除单向链表中某个位置的元素。的前端乐园原文链接寒假前端学习学习数据结构与算法二链表 本系列的第一篇文章: 学习JavaScript数据结构与算法(一),栈与队列第二篇文章:学习JavaScript数据结构与算法(二):链表第三篇文章:学习JavaScript数据结构与算法(三):集合第四篇文章:学习JavaScript数据结构与...

    lolomaco 评论0 收藏0
  • Java数据结构算法——链表(面试)

    摘要:前言数据结构与算法专题会不定时更新,欢迎各位读者监督。指针反转实现链表反转代码反转链表获取当前下下个元素测试代码部分用到了上篇文章数据结构与算法链表的代码段,请移步获取。 声明:码字不易,转载请注明出处,欢迎文章下方讨论交流。 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督。本文是上篇文章Java数据结构与算法——链表的扩展篇,介绍链表的特点,使用场景、链表的性能分析以...

    keke 评论0 收藏0
  • JS数据结构算法_链表

    摘要:上一篇数据结构与算法栈队列下一篇数据结构与算法集合字典写在前面说明数据结构与算法系列文章的代码和示例均可在此找到上一篇博客发布以后,仅几天的时间竟然成为了我写博客以来点赞数最多的一篇博客。 上一篇:JS数据结构与算法_栈&队列下一篇:JS数据结构与算法_集合&字典 写在前面 说明:JS数据结构与算法 系列文章的代码和示例均可在此找到 上一篇博客发布以后,仅几天的时间竟然成为了我写博客以...

    NeverSayNever 评论0 收藏0
  • 前端面试总结--数据结构算法

    摘要:链表前端的面试中,链表还是经常会被问到。这种数据结构非常方便,提供了便利店语法来访问它的元素。参考书籍推荐一个找组件的轮子工厂前端面试总结数据结构与算法一前端面试总结数据结构与算法二前端面试总结数据结构与算法三 链表 前端的面试中,链表还是经常会被问到。所以熟悉链表的结果以及链表操作的方法还是很重要的。说道存储多个元素,数组可能是最常用的数据结构。这种数据结构非常方便,提供了便利店[]...

    superPershing 评论0 收藏0
  • Java数据结构算法——链表

    摘要:前言数据结构与算法专题会不定时更新,欢迎各位读者监督。本文介绍另一种数据结构链表,包括链表的特点特点链表的创建删除插入和输出,文末给出代码和一道常见的关于链表的面试题。 声明:码字不易,转载请注明出处,欢迎文章下方讨论交流。 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督。本文介绍另一种数据结构——链表,包括链表的特点特点、链表的创建、删除、插入和输出,文末给出java...

    CKJOKER 评论0 收藏0

发表评论

0条评论

tracymac7

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<