网站首页 > java教程 正文
为什么要讲链表呢?这是因为java中有很多集合类底层都是通过链表来实现的。而且面试的时候,链表的实现是经常考的一个知识点。所以这篇文章的重点在于,如何使用代码去实现这些数据结构。但是这篇文章我不打算直接上来就讲链表,而是先从线性表开始。按照惯例先给出这篇文章的大致脉络吧。
首先,是对数据结构中线性表,做一个回顾。还讲了其两大存储结构,顺序存储结构和链式存储结构。
接下来,重点讲各种链表的介绍,以及常用方法和特点
最后,对java中使用链表的集合类,进行一个介绍。
当然,还有一些常见的面试题。
OK,开始。
一、线性表
为了很好的描述这个概念,先从一个例子开始吧,比如幼儿园的小朋友放学之后,都是手拉手排着队走出校园,这些小朋友从外表来看就像是一条链子,而线性表就和这个链子一样,这些小朋友就像是线性表里面的数据元素,从外面看一个接一个。比较正式的定义那就是:线性表:0个或者多个数据元素的有限序列。
刚刚我提到的都是从表面来看都是一样的,说明在内部的实现方式是不一样的,下面就是线性表的两种存储结构:顺序存储结构和链式存储结构。
- 顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
- 链式存储结构:地址可以连续也可以不连续的存储单元存储数据元素
今天主要讲的就是基于链式存储结构的链表。你可以这样理解,比如说你要找一个人,名字叫张三,你首先跑到A,发现没有,A告诉你B可能知道,你跑到了B。B说C可能知道,你跑到了C,张三果然在C那里,如果没有就这样一直不停的去找。一张图看一下
但是这只是基础,对线性表的描述,不想花费太多时间在这。下面用一张图表述吧。
有了上面这张图,相信对分类等都有了了解和认识了。下面开始今天的主题链表。
下面在对这两种存储结构进行一个对比。
二、链表
1、单链表
对于链表,里面主要有几个重要的信息,对于每一个节点(比如上一节讲到的A,B,C三处)都有下一个节点的地址和当前节点的数据。下面代码实现一下:
首先是节点的定义:
public class Node<T> { private T data; //节点的数据 public Node next; //指向的下一个节点 public Node(T data, Node next) { this.data = data; this.next=next; } ? public Node() { } public T getData() { return data; } private void setData(T data) { this.data = data; } }
接下来无非是对节点的增删改查罢了,先看基本的操作。
public class LinkList<T> { private Node head; //头结点 private Node tail; //尾结点 private int size; //链表长度 public LinkList() { head=null; tail=null; } //获取链表长度 public int getLength(){ return size; } //是否含有元素 public boolean isEmpty(){ return size==0; } //清空链表 public void clear(){ head=null; tail=null; size=0; } //通过索引获取节点 public Node getNodeByIndex(int index){ if(index<0||index>size-1){ throw new IndexOutOfBoundsException("索引越界"); } Node node=head; for(int i=0;i<size;i++,node=node.next){ if(i==index){ return node; } } return null; } }
下面就是正删改查了,但是有一个思路需要我们要牢记,这样在使用其他语言实现的时候,就能快速的掌握,这是我自己的方法。上面已经描述了通过索引查找元素,所以下面只考虑,插入删除操作就好了。
首先,我们要判断当前的空间是否满足增删该查的条件
接下来,判断增删改查的位置是否正确
最后,增删改查之后没有副作用产生。
第一个操作:插入(三种方式:头插入、尾插入、中间插入)
对于插入操作,使用一张图简单表述一下
//头插入 public void addAtHead(T element){ head=new Node<T>(element, head); //如果是空链表就变让尾指针指向头指针 if(tail==null){ tail=head; } size++; } //尾插入 public void addAtTail(T element){ //如果表为空 if(head==null){ head=new Node<T>(element, null); tail=head; }else{ Node node=new Node<T>(element, null); tail.next=node; tail=node; //尾节点后移 } size++; } //在指定位置插入元素 public void insert(T element,int index){ if(index<0||index>size){ throw new IndexOutOfBoundsException("索引越界"); } if(index==0){ addAtHead(element); }else if(index>0&&index<size-1){ //中间插入 Node nexNode=null; Node insNode=new Node<T>(element, nexNode); nexNode=getNodeByIndex(index); Node preNode=getNodeByIndex(index-1); preNode.next=insNode; insNode.next=nexNode; size++; }else { addAtTail(element); } }
第二种操作:删除
对删除操作同样一张图
public void delete(int index){ if(index<0||index>size-1){ throw new IndexOutOfBoundsException("索引越界"); } int flag=index-1; Node node=getNodeByIndex(index); if(flag < 0){ //flag<0说明删除的是第一个元素,将头结点指向下一个就行 head=head.next; node=null; }else{ //在这里才是真正的删除操作, Node preNode=getNodeByIndex(index-1); preNode.next=node.next; //如果删除的是最后一个元素,尾节点前移一位 if(index==size-1){ tail=preNode; } } size--; } //删除最后一个元素 public void remove(){ delete(size-1); }
小结:上述是对单链表的增删改查操作,对于其他链表关键就在于对单链表的更改。当然这里只给出了链表的代码实现。对于顺序存储结构的顺序表实现。同样也是这个道理。你只需要定义一个节点进行增删改查就好了。
2、循环链表
如果你对单链表的操作能够掌握的话,剩下的这几种链表,我相信你也能很快掌握,只不过把node中的基本数据改一下,增删的时候多一两行代码。对于循环链表,你可以想象成y一个闭环的链表。也就是最后一个元素又指向了第一个元素。其特点是这里的讨论重点。
特点
(1)适合合并两个循环链表:有了尾指针直接合并,比如说下面有两个循环链表。
现在使用尾指针合并他们。
(2)从表中任何一个节点出发,都能访问链表的全部节点。这一点很好理解。
3、双向链表
从名字就可以看出,每一个节点既指向前驱又指向后继。
这个双向链表相对于单链表还是比较复杂的,毕竟每个节点多了一个前驱,因此对于插入和删除要格外小心。双向链表的优点在于对每个节点的相邻接点操作时候,效率会比较高。
三、java中的线性表表
最典型的就是Collection接口了。下面还包括了一些他的实现类,比如List接口,ArrayList类和LinkList类,其底层都是实现了线性表。详细的我已经整理了一部分到我的微信公众号了(java的架构师技术栈)。因为我最近才开始真正的写文章,所以里面的内容我在一点一点丰富填充。
我们只需要知道Collection这些全部都是使用了线性表,因此在遇见面试题的时候,如果要求不能使用java中提供的集合类。那么我们应该学会使用上述的线性表表的操作去解决问题。
四、常见面试题,你会几个?
对于面试题,由于篇幅问题,所以这里只给出思路,本来我把代码放进来了,但是又删除了(原谅我颤抖的手)
1、单链表的创建和遍历
本题上面已经给出答案,这里不再说了。
2、求单链表中节点的个数
这一题相当于,遍历一遍链表
3、查找单链表中的倒数第k个结点(剑指offer,题15)
先计算出链表的长度size,然后直接输出第(size-k)个节点就可以了
4、查找单链表中的中间结点
你可以先获取整个链表的长度N,N/2就好了。
5、合并两个有序的单链表,合并之后的链表依然有序【出现频率高】
这个类似于归并排序,你创建一个新链表,然后把上面两个链表依次比较,插入新链表
6、单链表的反转【出现频率最高】(剑指offer,题16)
这个是对插入操作的考察,在上面写了三种插入操作实现方式,从头到尾遍历链表,取出当前链表节点,插入新链表的头结点。这样第一个取出的节点,在新链表就是最后一个
7、从尾到头打印单链表(剑指offer,题5)
8、判断单链表是否有环
这里也是用到两个指针,如果一个链表有环,那么用一个指针去遍历,是永远走不到头的。因此,我们用两个指针去遍历:first指针每次走一步,second指针每次走两步,如果first指针和second指针相遇,说明有环。
9、取出有环链表中,环的长度
10、单链表中,取出环的起始点(剑指offer,题56)。
11、判断两个单链表相交的第一个交点(剑指offer,题37)
12、 已知一个单链表中存在环,求进入环中的第一个节点
基本上就写这么多吧,由于是基础内容,也比较容易理解,在大学的时候都学到过,这里算是一个复习巩固吧。
有什么问题,还请批评指正。谢谢
- 上一篇: 手写java数据结构(栈)(java手写递归)
- 下一篇: java数据结构系列——你了解数据结构嘛?
猜你喜欢
- 2024-09-11 阿里架构师剖析:Redis常用数据类型对应的数据结构
- 2024-09-11 聊聊经典数据结构HashMap,逐行分析每一个关键点
- 2024-09-11 压箱底Redis面试集-48.Redis 的 ListPack 数据结构是什么?
- 2024-09-11 JAVA进阶知识学习-day03 数据结构&List集合&Set集合
- 2024-09-11 Java数据结构面试必问:HashMap 底层实现原理分析
- 2024-09-11 Java路径-31-Java数据结构(我的世界java路径错误怎么办)
- 2024-09-11 《数据结构》第九篇、java中ArrayList源码解析
- 2024-09-11 JDK源码分析--Object(jdk1.8源码详细介绍)
- 2024-09-11 「Java数据结构」Java对象的比较(java对比两个对象属性的变化)
- 2024-09-11 动图+源码,演示Java中常用数据结构执行过程及原理
你 发表评论:
欢迎- 05-02Go 中的 channel 与 Java BlockingQueue 的本质区别
- 05-02处理线上RabbitMQ队列阻塞(rabbitmq队列状态)
- 05-02实现延迟队列,这些你知道吗?(延迟队列 kafka)
- 05-02学无止境:AQS阻塞队列和条件队列是如何使用的?
- 05-02京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
- 05-02阻塞队列ArrayBlockingQueue的实现原理浅析
- 05-02高性能队列:Java Concurrent包中的BlockingQueue
- 05-02不允许还有Java程序员不了解BlockingQueue阻塞队列的实现原理
- 最近发表
-
- Go 中的 channel 与 Java BlockingQueue 的本质区别
- 处理线上RabbitMQ队列阻塞(rabbitmq队列状态)
- 实现延迟队列,这些你知道吗?(延迟队列 kafka)
- 学无止境:AQS阻塞队列和条件队列是如何使用的?
- 京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
- 阻塞队列ArrayBlockingQueue的实现原理浅析
- 高性能队列:Java Concurrent包中的BlockingQueue
- 不允许还有Java程序员不了解BlockingQueue阻塞队列的实现原理
- JAVA并发之BlockingQueue(阻塞队列)
- dify案例分享-API文档生成接口代码
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)