2019独角兽企业重金招聘Python工程师标准>>>

前言

在这一章,我将介绍另外一种非常重要的线性数据结构--链表。在之前介绍的动态数组,栈和队列这三种数据结构,底层其实依托于静态数组。

链表的概括

虽然动态数组靠resize方法解决静态数组固定容量的问题,但依旧摆脱不了仍是静态数组的事实,而链表则与上述线性数据结构都不同,是一种真正的动态数据结构

链表为何如此重要

  • 最简单的动态数据结构
  • 更深入的理解引用(或者指针)
  • 更深入的理解递归
  • 辅助组成其他数据结构
    注:链表本身也是有它非常清晰的递归结构的,由于它天身这种递归性质,可以帮助大家更加深入的理解递归机制相应的数据结构。

具体来看看什么是链表

  • 链表,通常数据存储在**“节点”(Node)**中。
  • 对链表的节点来说只有两部分,一是存储真正的数据,而另一部分是node类型的对象next(next本身又是一个节点,连接起了下一个节点)。

类比火车,每个节点是一节车厢,在车厢中存储真正的数据。而车厢和车厢还要进行连接,以使得所有数据是整合在一起的。用户可以方便地在对这些数据上查询等进行其他操作,而数据和数据之间连接就是由这个next来完成的。

简单的图示一下

  • 比如,第一个节点存放了元素1,同时它有一个指向next(也就是用一个箭头来表示),指向了下一节点(node)
  • 第二个节点存放了元素2,以此类推……
  • 到最后个节点的next存储的是NULL
  • 如果一个节点的next是NULL,那就说明这个节点一定是最后一个节点,这就是链表。

链表的优缺点

优点

  • 真正的动态,不需要处理固定容量的问题

不必像静态数组一样,需要考虑开辟多少空间出来,同时还要考虑这个空间是否够用。对于链表来说,你需要存储多少个数据,你就可以生成多少个节点,把它们挂接起来,这便是动态的含义

缺点

  • 丧失了随机访问的能力

    链表不能像数组那样,从数组的索引中直接取出元素。在底层机制上,数组所开辟的空间,在内存中是连续分布的,所以可以直接寻找这个索引对应的偏移,直接计算出相应的数据所存储的内存地址,用O(1)的复杂度把这个元素取出来

    但是链表不同。链表是靠next一层层连接,所以在计算机的底层,每一个节点所在的内存位置是不同的。因此必须通过遍历,一层一层找到这个元素,这便是链表最大的缺点。

数组和链表的对比

  • 数组支持快速查询
  • 链表便是支持动态

链表的实现

对于链表来说,我们想要访问这个链表中所有的节点,就必须把链表的头(head)存储起来。
代码实现

public class LinkedList<E> {private class Node {public E e;public Node next;public Node(E e, Node next) {this.e = e;this.next = next;}public Node(E e) {this(e, null);}public Node() {this(null, null);}@Overridepublic String toString() {return e.toString();}}private Node head;private int size;public LinkedList(){head = null;size = 0;}
}

在链表头添加元素

下面我们来看下链表最重要的操作--如何为链表头添加元素

  1. 假设,要将666这个元素添加到链表中,
  2. 相应的需要在node节点里存放666这个元素,以及相应的next(指向)
  3. 然后node节点的next指向链表的头,即node.next=head(将head赋值给node.next)
  4. 最后head也指向存放666的node节点,即head=node

注: 整个过程在一个函数中执行,函数结束之后,相应node变量的块作用域也就结束了
代码实现

public void addFirst(E e){Node node = new Node(e);node.next = head;head = node;
}

在链表的中间添加新的元素

现在来处理稍微复杂一点的问题,在链表的中间添加新的元素

  1. 对于这个链表,要在这个链表索引(链表是无索引概念,只是借用索引这个概念来阐述)为2的地方添加一个新的元素666
  2. 首先遍历找到索引为2的前一个节点prev
  3. 然后prev.next指向存放2元素的节点,同时存放666节点node.next也指向它,因此得到node.next=prev.next(将prev.next赋值给node.next)
  4. 之后存放666节点node挂接起下一个节点,即prev.next=node(将node赋值给prev.next)
  5. 经过这样的操作,完成了对在链表中间添加新的元素

代码实现

// 在链表的index(0-based)位置添加新的元素e
// 在链表中不是一个常用的操作,通常仅供练习
public void add(int index, E e){if(index < 0 || index > size){throw new IllegalArgumentException("Add failed. Illegal index.");}if(index == 0){addFirst(e);}else{Node prev = head;for(int i = 0; i < index - 1; i++){// 把当前prev存的这个节点的下一个节点放进prev这个变量中// prev这个变量在链表中会一直向前移动,直到移动到index-1这个位置// 最后就找到了待插入的那个节点的前一个节点prev = prev.next;}Node node = new Node(e);node.next = prev.next;prev.next = node;size++;}
}

为链表设立虚拟头节点

在链表添加元素的过程中,我们遇到了在链表任意位置添加元素和在链表头添加元素,逻辑上有所不同。究其原因,是在链表添加过程中需要找到相应的前一个节点。因此,需要在链表中造一个虚拟头节点(dummy head)

代码实现

// 虚拟头节点
private Node dummyHead;
private int size;public LinkedList() {dummyHead = new Node(null, null);size = 0;
}// 在链表的index(0-based)位置添加新的元素e
// 在链表中不是一个常用的操作,通常练习用
public void add(int index, E e) {if (index < 0 || index > size) {throw new IllegalArgumentException("Add failed. Illegal index.");}Node prev = dummyHead;for (int i = 0; i < index; i++) {// 把当前prev存的这个节点的下一个节点放进prev这个变量中// prev这个变量在链表中会一直向前移动,直到移动到index-1这个位置// 最后就找到了待插入的那个节点的前一个节点prev = prev.next;}Node node = new Node(e);node.next = prev.next;prev.next = node;// 或者三面三行改成 perv.next= new Node(e, prev.next);size++;
}// 在链表头添加新的元素e
public void addFirst(E e) {add(0, e)
}// 在链表末尾添加新的元素e
public void addLast(E e) {add(size, e);
}

链表的查询、更新与遍历

继续为我们的链表添加更多的操作。那么,首先是获得链表的第index个元素。
代码实现

// 获取在链表的index(0-based)位置的元素e
// 在链表中不是一个常用的操作,通常练习用
public E get(int index){if(index < 0 || index >= size){throw new IllegalArgumentException("Get failed. Illegal index.");}Node cur = dummyHead.next;for(int i = 0; i < index; i++){cur.next = cur;}return cur.e;
}// 获得链表的第一个元素
public E getFirst(){return get(0);
}// 获得链表的最后一个元素
public E getLast(){return get(size - 1);
}// 修改在链表的index(0-based)位置的元素e
// 在链表中不是一个常用的操作,通常练习用
public void set(int index, E e){if(index < 0 || index >= size){throw new IllegalArgumentException("Update failed. Illegal index.");}Node cur = dummyHead.next;// 需要遍历到index节点for(int i = 0; i < index; i++){cur = cur.next;}// 再对index节点进行赋值cur.e = e;
}// 查找链表是否含有元素e
public boolean contains(E e){Node cur = dummyHead.next;// 判断cur节点是否为空,就意味着cur节点为有效节点while (cur != null){if(cur.e.equals(e)){return true;}cur = cur.next;}return false;
}

链表元素的删除

介绍了为链表添加元素,查询、更新元素,现在就插最后一个从链表中删除元素

  1. 要想删除索引为2的元素,需要找到索引为2的上一个节点prev,而prev.next便是待删除的节点(可称为delNode)
  2. prev.next指向delNode.next,即prev.next=delNode.next。也就是跳过了delNode节点
  3. 为了使得能够回收delNode节点的空间,因此需要将delNode.next置空,即delNode=null
  4. 这样一来,就完成了整个链表元素的删除操作

代码实现

// 从链表中删除index(0-based)位置的元素,返回删除的元素
// 在链表中不是一个常用的操作,通常仅供练习
public E remove(int index){if(index < 0 || index >= size){throw new IllegalArgumentException("Remove failed. Illegal index.");}Node prev = dummyHead;for(int i = 0; i < index; i++){prev = prev.next;}Node retNode = prev.next;prev.next = retNode.next;// 将retNode.next节点置空以便回收retNode.next = null;size--;return retNode.e;
}// 从链表中删除第一个元素,返回删除的元素
public E removeFirst(){return remove(0);
}// 从链表中删除最后一个元素,返回删除的元素
public E removeLast(){return remove(size - 1);
}

链表的时间复杂度分析

最后简单的分析一下,这个链表的时间复杂度。

  • 首先我们来看添加操作。如果向链表尾添加一个元素,则必须从链表头开始遍历每一个元素,因此是O(n)的时间复杂度
  • 但是如果向链表头添加一个元素,它是O(1)的时间复杂度
  • 对于删除操作来说是基本一样的分析过程。
  • 如果想要删除最后一个元素,就需要链表头遍历一次,因此时间复杂度是O(n)
  • 删除第一个元素,需要O(1)的时间可以搞定了。
  • 但是如果想要删除任意位置节点的话,平均来看是O(n/2)~O(n)
  • 由于链表不支持随机访问,所以要想修改某个位置的元素,就必须从头遍历,所以这个修改操作的时间复杂度是O(n)
    如图所示:

转载于:https://my.oschina.net/loubobooo/blog/2054646

初学数据结构--链表相关推荐

  1. C++数据结构链表的基本操作

    这篇文章主要为大家介绍了C++数据结构链表基本操作的示例过程有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪 首先创建好一个节点 typedef struct node {in ...

  2. c语言仓库管理系统链表,仓库管理系统 C语言 C++ 数据结构 链表 课程设计

    仓库管理系统 C语言 C++ 数据结构 链表 课程设计 #include #include #include #include #define MAX 64 typedef struct node{ ...

  3. 数据结构 - 链表 - 面试中常见的链表算法题

    数据结构 - 链表 - 面试中常见的链表算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图. ...

  4. 数据结构链表之符号表,Python3实现——8

    数据结构链表之符号表 符号表的介绍 之前章节介绍的顺序表和链表都是一个节点储存一个元素的表,但在日常生活中我们还有很多一次需要储存成对或多个值的情况,例如: 符号表最主要的目的将一对元素,用一个键和一 ...

  5. 数据结构链表之队列,Python3实现——7

    数据结构链表之队列 队列概述 定义:队列是一种基于先进先出(FIFO)的数据结构,队列只能在一段进行插入和删除操作的结构,第一个进入队列的元素在读取时会第一个被读取 队列可以使用顺序表(Python中 ...

  6. 数据结构链表之栈,Python3简单实现——5

    数据结构链表之栈 栈的概述 定义:栈是一种基于先进后出(FILO)的数据结构,是一种只能在一段进行插入和删除操作的特殊线性表. 引入名词:将数据存入栈的动作称为压栈,将数据取出栈的动作称为弹栈 栈的特 ...

  7. 数据结构链表例程_如何掌握RxJava例程的四个结构

    数据结构链表例程 by Ayusch Jain 通过Ayusch Jain 如何掌握RxJava例程的四个结构 (How to get a grip on the four constructs of ...

  8. 数据结构链表代码_代码简介:链表数据结构如何工作

    数据结构链表代码 Here are three stories we published this week that are worth your time: 这是我们本周发布的三个值得您关注的故事 ...

  9. python创建链表实例_python数据结构链表之单向链表(实例讲解)

    python数据结构链表之单向链表(实例讲解) 单向链表 单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域.这个链接指向链表中的下一个节点,而最后 ...

最新文章

  1. IDEA自动生成类注解,IDEA作者信息自动生成,IDEA类信息自动生成
  2. IGBT的MATLAB仿真
  3. optee中User TA的加载和运行
  4. 车来了赵祺:贴近业务,是DT时代第一驱动力
  5. ADS错误(Fatal)L6002U解决方法
  6. spring java配置_Spring Java配置
  7. windows环境 wildfly-10.1.0.Final 安装、配置、部署
  8. 超越SiamRPN++,SiamMan达到目标跟踪新SOTA
  9. box-shadow
  10. HTML初识HTML
  11. 自定义应用程序配置文件(app.config)
  12. jqGrid 操作一些总结(二)
  13. C语言 百炼成钢16
  14. xcode里面找不到头文件
  15. com.lowagie.text-2.1.7jar
  16. 蜂鸣器发出兰花草c语言程序,兰花草源程序
  17. 工程升级技术的学习和使用
  18. 世界首款胸腔植入物在人体内“存活”;药明生基美国费城扩建基地投入运营 | 医药健闻...
  19. python2和python3版本的区别
  20. 【EPS精品教程】EPS2016三维测图软件常用快捷键(建议收藏)

热门文章

  1. AI一分钟|阿里成立“罗汉堂”;vivo微信人脸识别支付下半年商用
  2. 微软披露拓扑量子计算机计划!
  3. 用可组合的构建块丰富用户界面?谷歌提出「可解释性」的最新诠释
  4. 冠军奖30万!刘强东搞了个“猪脸识别”比赛,中美两地同时启动(附比赛详细日程及赛题说明)
  5. 五年之后,你的企业是拥抱AI,还是已被淘汰
  6. 有了链路日志增强,排查Bug小意思啦!
  7. Java编程中,有哪些好的习惯从一开始就值得坚持?
  8. 这谁顶得住?mybatis十八连环问!
  9. 为什么微服务一定要有网关?
  10. IntelliJ IDEA 18 周岁,吐血推进珍藏已久的必装插件