当前位置:   article > 正文

【数据结构Java版】链表之单链表的实现_java 单链表的实现

java 单链表的实现

目录

一、ArrayList的缺陷

二、链表

(1)链表的概念及结构

(2)链表的类型

1.单向or双向

2.带头结点or不带头结点

3.循环or不循环 

 (2)单链表的实现

1.定义链表  

2.遍历链表 

3.关于链表的插入、删除操作

 三、小结


 一、ArrayList的缺陷

       由于ArrayList底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。为之java集合中又引入了LinkedList,即链表结构。 


二、链表

(1)链表的概念及结构

        链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

 注意点:

1、链式结构在逻辑上是连续的,但是在物理不一定连续

2、现实中的结点一般都是从上申请出来

3、从堆上申请的空间,是按照一定策略来分配的,两次申请的空间可以连续,可以不连续

(2)链表的类型

1.单向or双向

2.带头结点or不带头结点

3.循环or不循环 

小贴士:重点掌握两种

 1.无头单向非循环链表:

        结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。这种结构在笔试面试中出现很多。

2.无头双向链表:

        在Java的集合框架库中LinkedList底层实现就是无头双向循环链表 


 (2)单链表的实现

1.定义链表  

        结点(Node):装元素的一种结构,至少包含:元素本身、指向下一个结点的线索。

        当下,我们都是使用头结点来表示一条链表Node head = null;头结点不存在,链表中一个结点都没有,链表中一个元素都没有,即链表是一条空的(empty)链表。

  1. // 这里定义的是结点对象,不是链表对象
  2. // 我们当前的实现中,结点对象中就两个东西,分别:元素(int)、指向下一个结点对象的线索(引用)
  3. public class Node {
  4. public int val; // 元素,类型是 int
  5. public Node next; // 类型是引用,Node 类型的引用:指向一个 Node 对象
  6. // next:下一个
  7. // 当 next == null 时,代表这个结点是所在链表的最后一个结点
  8. //构造方法
  9. public Node(int val) {
  10. this.val = val;
  11. this.next = null;
  12. }
  13. }

 2.遍历链表 

a.按照从前到后的顺序,依次访问链表中的元素 ,打印一条链表(依赖遍历链表中的所有元素)

         让 cur 指向 head 指向的对象(链表的头结点对象),当cur!=null时,表示链表中当前结点是存在的,用变量elements保存该结点的元素,打印出来,然后改变cur的指向使其指向下一个结点。cur 会经历链表中的每个结点:按照从前往后的顺序,每个结点只会经过一次。

  1. public static void printAllElements(Node head){
  2. Node cur=heaad;
  3. while(cur!=null){
  4. int elements=cur.val;
  5. System.out.printf(elements);
  6. cur=cur.next;
  7. }
  8. }

b.统计链表中的元素个数

        每个结点都会经过一次,所以每个元素都会经过一次,count++ 会被指向元素个数。这里使用for循环,也可以用while循环来表示。

  1. private static int sizeOf(Node head){
  2. int count=0;
  3. for(Node cur=head;cur!=null;cur=cur.next){
  4. count++;
  5. }
  6. return count;
  7. }

c.查找链表中存在的某个元素

        目前只能做到从前往后遍历,无法做到从后往前遍历(代价很大)。所谓从前到后查找,就是要遍历链表中的每个元素,和 target 进行对比。cur.val==target说明找到了,就是 cur 目前指向的结点中包含和 target 相等的元素。如果走到最后,返回一个特殊的null引用,表示没有找到。

  1. private static Node searchElemtents(Node head ,int target){
  2. Node cur=head;
  3. while(cur!=null){
  4. if(cur.val==target){
  5. return cur;
  6. }
  7. cur=cur.next;
  8. }
  9. return null;
  10. }

d.查找链表中所有的和某元素相等的结点

        可以用一个顺序表将所有结点保存下来。当找到符合条件的元素时就使用ArrayList中的add()添加方法,将其加入list中,最后结果返回list。

ArrayList详解:http://t.csdn.cn/dieWE

  1. private static List<Node> searchAllNode(Node head,int target){
  2. List <Node> list = new ArrayList <>();
  3. for(Node cur = head;cur!=null;cur=cue.next){
  4. if(cur.val==target){
  5. list.add(cur);
  6. }
  7. }
  8. return list;
  9. }

e.找到链表从前往后数,第n个结点(元素)的操作

1)链表中至少有 3 个结点

        要想找到第三个结点,cur需要往后走两次。如图所示 :

  1. private static Node findThirdNode (Node head) {
  2. Node cur = head;
  3. // 向后跳 2 步
  4. cur = cur.next;
  5. cur = cur.next;
  6. return cur;
  7. }

2)链表中至少有 n 个结点

        要想找到第n个结点,cur需要往后走n-1次。

  1. private static Node findNNode (Node head,int n) {
  2. Node cur = head;
  3. for(int i=0;i<n-1;i++){
  4. cur=cur.next;
  5. }
  6. return cur;
  7. }

3)链表的长度可能小于 n

此刻 cur 可能是 null,当 cur == null 时,cur.next 就会空指针异常。所以循环条件要多一个cur!=null。

两种可能:

cur == null:说明链表的长度 < n

cur != null,说明,循环了 n - 1 次,找到了第 n 个结点

两种情况,都可以通过返回 cur 进行处理

  1. private static Node findNNode (Node head,int n) {
  2. Node cur = head;
  3. for(int i = 0;i<n-1 && cur != null ;i++){
  4. cur=cur.next;
  5. }
  6. return cur;
  7. }

f.找到链表从后往前数,第n个结点(元素)的操作

1)找到链表的倒数第1个结点

        前提条件:链表中至少有 1 个结点

        最后一个结点的特征就是它指向为空,没有下一个结点。我们就可以将其cur.next!=null作为循环条件。

  1. private static Node findLastFirstNode(Node head) {
  2. Node cur = head;
  3. while (cur.next != null) {
  4. cur = cur.next;
  5. }
  6. return cur;
  7. }

2)找到链表的倒数第2个结点

        前提条件:链表中至少有 2 个结点

        倒数第二个结点的特征就是它下一个结点的指向为空。我们就可以将其cur.next.next!=null作为循环条件。

  1. private static Node findLastSecondNode(Node head) {
  2. Node cur = head;
  3. while (cur.next.next != null) {
  4. cur = cur.next;
  5. }
  6. return cur;
  7. }

3)找到链表的倒数第3个结点

        前提条件:链表中至少有3个结点

        同理cur.next.next.next!=null作为循环条件。

  1. private static Node finfLastThirdNode(Node head) {
  2. Node cur = head;
  3. while (cur.next.next.next != null) {
  4. cur = cur.next;
  5. }
  6. return cur;
  7. }

3.关于链表的插入、删除操作

a.头插、头删

头插操作

1)头插元素

        先把元素装入到对应的结点中;将创建出来的新结点和链表中的结点产生关系。

  1. private static Node headInsertionElement (Node head,int e){
  2. Node node=new Node(e);
  3. node.next=head;
  4. return node;
  5. }

2)头插结点

        头插结点就是把指定结点插入单链表头部。

  1. private static Node headInsertionNode(Node head,Node node){
  2. node.next=head;
  3. return node;
  4. }

头删操作

        将第一个结点删除,注意链表是空的(empty),没法删除结点,要抛出异常

ps:不理解异常操作的小朋友可以看这篇哦 http://t.csdn.cn/nRUyW

  1. private static Node headDelete(Node head){
  2. if(head!=null){
  3. throw new RuntimeException("链表是空的,无法进行头删操作");
  4. }
  5. return head.next;
  6. }

b.尾插、尾删

尾插操作

1)尾插元素

        把元素放入结点中;分情况处理:一是链表是一个空的链表,没有头结点;二是链表中至少有一个结点。

  1. private static Node tailInsertElement(Node head ,int e){
  2. Node node=new Node(e);
  3. if(head==null){
  4. node.next==null;
  5. return node;
  6. }
  7. Node last=head;
  8. while(last.next!=null){
  9. last=last.next;//找最后一个结点
  10. }
  11. last.next=node;
  12. node.next=null;//这句话可以省略
  13. return head;
  14. }

2)尾插结点

        尾插结点和尾插元素的思路是一致的,只是少了一步将元素放入新结点中。

  1. private static Node tailInsertElement(Node head ,Node node){
  2. if(head==null){
  3. node.next==null;
  4. return node;
  5. }
  6. Node last=head;
  7. while(last.next!=null){
  8. last=last.next;//找最后一个结点
  9. }
  10. last.next=node;
  11. node.next=null;//这句话可以省略
  12. return head;
  13. }

尾删操作

        尾删操作有三种情况:一是空链表;二是只有一个结点;三是至少有两个结点。对于空链表,通过抛出异常进行抱怨。只有一个结点是,可以返回null表示经过删除后,链表变为空链表了。三则需要找到倒数第二个结点,将倒数第二个结点的指向变为null。

  1. private static Node tailDeleteNode(Node node){
  2. if(head==null){//空链表
  3. throw new RunTimeException("空链表,不能进行删除");
  4. }
  5. if(head.next==null){//只有一个结点
  6. return null;
  7. }
  8. Node lastOfLast=head;
  9. while(lastOfLast.next.next!=null){//找到倒数第二个结点
  10. lastOfLast=lastOfLast.next;
  11. }
  12. lastOfLast.next=null;//删除最后一个结点
  13. return head;
  14. }

c.指定删除、插入

1)将e插入到到指定结点之后

        先找到的 node 的下个结点,记为 nextOfNode;再找到 node 的下一个的下一个结点,记为 nextOfNext;让 node 的 next 指向 下下个结点。

  1. private static void insertElements(Node node,int e){
  2. Node newNode = new Node(e);
  3. newNode.next=node.next;
  4. node.next=newNode;
  5. }


 三、小结

        无头单链表的概念和操作相较而言是比较简单的,我们需要勤加练习,理解头结点和指向关系。

        下面为大家整理了一些单链表的oj题供大家练习理解。

http://t.csdn.cn/cFe7R    206. 反转链表

http://t.csdn.cn/kivkD      203. 移除链表元素

http://t.csdn.cn/52UNu    876. 链表的中间结点

http://t.csdn.cn/Fx1XM    21. 合并两个有序链表

http://t.csdn.cn/WxzGm  链表中倒数第k个结点_牛客题霸_牛客网  

http://t.csdn.cn/MMwUC 链表分割_牛客题霸_牛客网

http://t.csdn.cn/rN6b4     链表的回文结构_牛客题霸_牛客网


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/620791
推荐阅读
相关标签
  

闽ICP备14008679号