初学数据结构--链表
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;}
}
在链表头添加元素
下面我们来看下链表最重要的操作--如何为链表头添加元素
- 假设,要将666这个元素添加到链表中,
- 相应的需要在node节点里存放666这个元素,以及相应的next(指向)
- 然后node节点的next指向链表的头,即
node.next=head
(将head赋值给node.next) - 最后head也指向存放666的node节点,即
head=node
注: 整个过程在一个函数中执行,函数结束之后,相应node变量的块作用域也就结束了
代码实现
public void addFirst(E e){Node node = new Node(e);node.next = head;head = node;
}
在链表的中间添加新的元素
现在来处理稍微复杂一点的问题,在链表的中间添加新的元素
- 对于这个链表,要在这个链表索引(链表是无索引概念,只是借用索引这个概念来阐述)为2的地方添加一个新的元素666
- 首先遍历找到索引为2的前一个节点prev,
- 然后
prev.next
指向存放2元素的节点,同时存放666节点node.next
也指向它,因此得到node.next=prev.next
(将prev.next赋值给node.next) - 之后存放666节点node挂接起下一个节点,即
prev.next=node
(将node赋值给prev.next) - 经过这样的操作,完成了对在链表中间添加新的元素
代码实现
// 在链表的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;
}
链表元素的删除
介绍了为链表添加元素,查询、更新元素,现在就插最后一个从链表中删除元素
- 要想删除索引为2的元素,需要找到索引为2的上一个节点
prev
,而prev.next
便是待删除的节点(可称为delNode) - 让
prev.next
指向delNode.next
,即prev.next=delNode.next
。也就是跳过了delNode节点 - 为了使得能够回
收delNode
节点的空间,因此需要将delNode.next
置空,即delNode=null - 这样一来,就完成了整个链表元素的删除操作
代码实现
// 从链表中删除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
初学数据结构--链表相关推荐
- C++数据结构链表的基本操作
这篇文章主要为大家介绍了C++数据结构链表基本操作的示例过程有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪 首先创建好一个节点 typedef struct node {in ...
- c语言仓库管理系统链表,仓库管理系统 C语言 C++ 数据结构 链表 课程设计
仓库管理系统 C语言 C++ 数据结构 链表 课程设计 #include #include #include #include #define MAX 64 typedef struct node{ ...
- 数据结构 - 链表 - 面试中常见的链表算法题
数据结构 - 链表 - 面试中常见的链表算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图. ...
- 数据结构链表之符号表,Python3实现——8
数据结构链表之符号表 符号表的介绍 之前章节介绍的顺序表和链表都是一个节点储存一个元素的表,但在日常生活中我们还有很多一次需要储存成对或多个值的情况,例如: 符号表最主要的目的将一对元素,用一个键和一 ...
- 数据结构链表之队列,Python3实现——7
数据结构链表之队列 队列概述 定义:队列是一种基于先进先出(FIFO)的数据结构,队列只能在一段进行插入和删除操作的结构,第一个进入队列的元素在读取时会第一个被读取 队列可以使用顺序表(Python中 ...
- 数据结构链表之栈,Python3简单实现——5
数据结构链表之栈 栈的概述 定义:栈是一种基于先进后出(FILO)的数据结构,是一种只能在一段进行插入和删除操作的特殊线性表. 引入名词:将数据存入栈的动作称为压栈,将数据取出栈的动作称为弹栈 栈的特 ...
- 数据结构链表例程_如何掌握RxJava例程的四个结构
数据结构链表例程 by Ayusch Jain 通过Ayusch Jain 如何掌握RxJava例程的四个结构 (How to get a grip on the four constructs of ...
- 数据结构链表代码_代码简介:链表数据结构如何工作
数据结构链表代码 Here are three stories we published this week that are worth your time: 这是我们本周发布的三个值得您关注的故事 ...
- python创建链表实例_python数据结构链表之单向链表(实例讲解)
python数据结构链表之单向链表(实例讲解) 单向链表 单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域.这个链接指向链表中的下一个节点,而最后 ...
最新文章
- IDEA自动生成类注解,IDEA作者信息自动生成,IDEA类信息自动生成
- IGBT的MATLAB仿真
- optee中User TA的加载和运行
- 车来了赵祺:贴近业务,是DT时代第一驱动力
- ADS错误(Fatal)L6002U解决方法
- spring java配置_Spring Java配置
- windows环境 wildfly-10.1.0.Final 安装、配置、部署
- 超越SiamRPN++,SiamMan达到目标跟踪新SOTA
- box-shadow
- HTML初识HTML
- 自定义应用程序配置文件(app.config)
- jqGrid 操作一些总结(二)
- C语言 百炼成钢16
- xcode里面找不到头文件
- com.lowagie.text-2.1.7jar
- 蜂鸣器发出兰花草c语言程序,兰花草源程序
- 工程升级技术的学习和使用
- 世界首款胸腔植入物在人体内“存活”;药明生基美国费城扩建基地投入运营 | 医药健闻...
- python2和python3版本的区别
- 【EPS精品教程】EPS2016三维测图软件常用快捷键(建议收藏)
热门文章
- AI一分钟|阿里成立“罗汉堂”;vivo微信人脸识别支付下半年商用
- 微软披露拓扑量子计算机计划!
- 用可组合的构建块丰富用户界面?谷歌提出「可解释性」的最新诠释
- 冠军奖30万!刘强东搞了个“猪脸识别”比赛,中美两地同时启动(附比赛详细日程及赛题说明)
- 五年之后,你的企业是拥抱AI,还是已被淘汰
- 有了链路日志增强,排查Bug小意思啦!
- Java编程中,有哪些好的习惯从一开始就值得坚持?
- 这谁顶得住?mybatis十八连环问!
- 为什么微服务一定要有网关?
- IntelliJ IDEA 18 周岁,吐血推进珍藏已久的必装插件