【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记

打印 上一主题 下一主题

主题 854|帖子 854|积分 2562

本文内容为重写上一节课中的单链表,将其重构成更易于用户利用的链表,实现多种操作链表的方法。
1. 重构单链表SLList

在上一节课中编写的 IntList 类是裸露递归的形式,在 Java 中一样平常不会这么定义,因为如许用户大概必要非常了解引用,而且可以大概递归地思考,才能利用这个类。
因此我们必要实现一个更容易利用的单链表(Singly Linked List),首先我们定义节点类 IntNode:
  1. package CS61B.Lecture4;
  2. public class IntNode {
  3.     public int val;
  4.     public IntNode next;
  5.     public IntNode(int val, IntNode next) {
  6.         this.val = val;
  7.         this.next = next;
  8.     }
  9. }
复制代码
现在就可以创建单链表类 SLList:
  1. package CS61B.Lecture4;
  2. public class SLList {
  3.     private IntNode head;
  4.     public SLList(int val) {
  5.         this.head = new IntNode(val, null);
  6.     }
  7.     public static void main(String[] args) {
  8.         SLList L = new SLList(5);
  9.     }
  10. }
复制代码
之前用户利用单链表必要如许定义:IntList L = new IntList(5, null);,他必要有递归考虑的思想,必须指定所指向的下一个链表的空值。
由于 IntNode 类只有在 SLList 类中利用才有意义,且用户一样平常不会单独去利用 IntNode 类,因此可以将其作为嵌套类放入 SLList 中,而且可以利用 private 关键字控制其权限:
  1. package CS61B.Lecture4;
  2. public class SLList {
  3.     private static class IntNode {
  4.         public int val;
  5.         public IntNode next;
  6.         public IntNode(int val, IntNode next) {
  7.             this.val = val;
  8.             this.next = next;
  9.         }
  10.     }
  11.     private IntNode head;
  12.     public SLList(int val) {
  13.         this.head = new IntNode(val, null);
  14.     }
  15.     public static void main(String[] args) {
  16.         SLList L = new SLList(5);
  17.     }
  18. }
复制代码
注意到我们还将 IntNode 类定义为静态的,静态嵌套类与外部类的实例无关,它更像是一个独立的类,但逻辑上仍然属于外部类的范畴,静态嵌套类可以直接访问外部类的静态字段和方法,但不能直接访问外部类的实例字段和方法(除非通过外部类的实例),因此静态嵌套类通常用于逻辑上与外部类相关,但不必要依靠外部类实例的场景。
2. 实现操作链表的方法

现在我们实现操作链表的几种方法:
  1. package CS61B.Lecture4;
  2. public class SLList {
  3.     private static class IntNode {
  4.         public int val;
  5.         public IntNode next;
  6.         public IntNode(int val, IntNode next) {
  7.             this.val = val;
  8.             this.next = next;
  9.         }
  10.     }
  11.     private IntNode head;
  12.     public SLList(int val) {
  13.         this.head = new IntNode(val, null);
  14.     }
  15.     // 获取首部节点值
  16.     public int getFirst() {
  17.         return this.head.val;
  18.     }
  19.     // 在首部添加节点
  20.     public void addFirst(int val) {
  21.         this.head = new IntNode(val, this.head);
  22.     }
  23.     // 删除首部节点并返回其值
  24.     public int removeFirst() {
  25.         if (this.head == null) {
  26.             System.out.println("Already Empty!");
  27.             return 0;
  28.         }
  29.         int val = this.head.val;
  30.         this.head = this.head.next;
  31.         return val;
  32.     }
  33.     // 获取尾部节点值
  34.     public int getLast() {
  35.         IntNode p = this.head;
  36.         while (p.next != null) p = p.next;
  37.         return p.val;
  38.     }
  39.     // 在尾部添加节点
  40.     public void addLast(int val) {
  41.         IntNode p = this.head;
  42.         while (p.next != null) p = p.next;
  43.         p.next = new IntNode(val, null);
  44.     }
  45.     // 删除尾部节点并返回其值
  46.     public int removeLast() {
  47.         if (this.head == null) {
  48.             System.out.println("Already Empty!");
  49.             return 0;
  50.         }
  51.         int val;
  52.         if (this.head.next == null) {
  53.             val = this.head.val;
  54.             this.head = null;
  55.         } else {
  56.             IntNode p = this.head;
  57.             while (p.next.next != null) p = p.next;
  58.             val = p.next.val;
  59.             p.next = null;
  60.         }
  61.         return val;
  62.     }
  63.     // 获取链表长度
  64.     public int size() {
  65.         return this.size(this.head);  // 无法在该方法中直接实现递归,需要一个额外的辅助方法
  66.     }
  67.     // size()递归辅助方法
  68.     private int size(IntNode p) {
  69.         if (p.next == null) return 1;
  70.         return 1 + size(p.next);
  71.     }
  72.     // 重写输出SLList对象的信息
  73.     @Override
  74.     public String toString() {
  75.         if (this.head == null) return "[]";
  76.         StringBuilder res = new StringBuilder("SLList: [");
  77.         IntNode p = this.head;
  78.         while (p != null) {
  79.             res.append(p.val);
  80.             if (p.next != null) res.append(", ");
  81.             else res.append("]");
  82.             p = p.next;
  83.         }
  84.         return res.toString();
  85.     }
  86.     public static void main(String[] args) {
  87.         SLList L = new SLList(5);
  88.         L.addFirst(10);
  89.         System.out.println(L.getFirst());  // 10
  90.         L.addLast(15);
  91.         System.out.println(L.getLast());  // 15
  92.         System.out.println(L.size());  // 3
  93.         System.out.println(L);  // SLList: [10, 5, 15]
  94.         System.out.println(L.removeFirst());  // 10
  95.         System.out.println(L.removeLast());  // 15
  96.         System.out.println(L.size());  // 1
  97.         System.out.println(L);  // SLList: [5]
  98.     }
  99. }
复制代码
必要重点思考的是如何用递归的方式求解链表的长度,我们利用的是构造一个辅助方法 size(IntNode p) 使得用户可以通过 size() 方法直接获取链表长度。
3. 优化效率

我们现在关注 size() 方法,发现每次用户必要查看链表长度时都必要遍历一次链表,因此我们可以用一个变量实时记录链表的长度,也就是用空间换时间,如许每次查询只必要                                    O                         (                         1                         )                              O(1)                  O(1) 的时间复杂度,这也是上一节课中直接裸露递归的类 IntList 所无法实现的:
  1. package CS61B.Lecture4;
  2. public class SLList {
  3.     private static class IntNode {
  4.         public int val;
  5.         public IntNode next;
  6.         public IntNode(int val, IntNode next) {
  7.             this.val = val;
  8.             this.next = next;
  9.         }
  10.     }
  11.     private IntNode head;
  12.     private int size;
  13.     public SLList(int val) {
  14.         this.head = new IntNode(val, null);
  15.         this.size = 1;
  16.     }
  17.     // 获取首部节点值
  18.     public int getFirst() {
  19.         return this.head.val;
  20.     }
  21.     // 在首部添加节点
  22.     public void addFirst(int val) {
  23.         this.head = new IntNode(val, this.head);
  24.         this.size++;
  25.     }
  26.     // 删除首部节点并返回其值
  27.     public int removeFirst() {
  28.         if (this.head == null) {
  29.             System.out.println("Already Empty!");
  30.             return 0;
  31.         }
  32.         int val = this.head.val;
  33.         this.head = this.head.next;
  34.         this.size--;
  35.         return val;
  36.     }
  37.     // 获取尾部节点值
  38.     public int getLast() {
  39.         IntNode p = this.head;
  40.         while (p.next != null) p = p.next;
  41.         return p.val;
  42.     }
  43.     // 在尾部添加节点
  44.     public void addLast(int val) {
  45.         IntNode p = this.head;
  46.         while (p.next != null) p = p.next;
  47.         p.next = new IntNode(val, null);
  48.         this.size++;
  49.     }
  50.     // 删除尾部节点并返回其值
  51.     public int removeLast() {
  52.         if (this.head == null) {
  53.             System.out.println("Already Empty!");
  54.             return 0;
  55.         }
  56.         int val;
  57.         if (this.head.next == null) {
  58.             val = this.head.val;
  59.             this.head = null;
  60.         } else {
  61.             IntNode p = this.head;
  62.             while (p.next.next != null) p = p.next;
  63.             val = p.next.val;
  64.             p.next = null;
  65.         }
  66.         this.size--;
  67.         return val;
  68.     }
  69.     // 获取链表长度
  70.     public int size() {
  71.         return this.size;
  72.     }
  73.     // 重写输出SLList对象的信息
  74.     @Override
  75.     public String toString() {
  76.         if (this.head == null) return "[]";
  77.         StringBuilder res = new StringBuilder("SLList: [");
  78.         IntNode p = this.head;
  79.         while (p != null) {
  80.             res.append(p.val);
  81.             if (p.next != null) res.append(", ");
  82.             else res.append("]");
  83.             p = p.next;
  84.         }
  85.         return res.toString();
  86.     }
  87.     public static void main(String[] args) {
  88.         SLList L = new SLList(5);
  89.         L.addFirst(10);
  90.         System.out.println(L.getFirst());  // 10
  91.         L.addLast(15);
  92.         System.out.println(L.getLast());  // 15
  93.         System.out.println(L.size());  // 3
  94.         System.out.println(L);  // SLList: [10, 5, 15]
  95.         System.out.println(L.removeFirst());  // 10
  96.         System.out.println(L.removeLast());  // 15
  97.         System.out.println(L.size());  // 1
  98.         System.out.println(L);  // SLList: [5]
  99.     }
  100. }
复制代码
4. 修复addLast()

首先我们写一个无参的构造方法,如许用户可以大概创建一个空链表:
  1. public SLList() {
  2.     this.head = null;
  3.     this.size = 0;
  4. }
复制代码
现在我们尝试创建空链表,并用 addLast() 方法添加节点:
  1. SLList L = new SLList();
  2. L.addLast(10);
  3. System.out.println(L);
复制代码
会发现步伐报错了,因为空链表的 this.head 就为 null,在执行 while (p.next != null) 时无法获取 p.next,因此我们可以如许修改 addLast() 方法:
  1. // 在尾部添加节点
  2. public void addLast(int val) {
  3.     if (this.head == null) this.head = new IntNode(val, null);
  4.     else {
  5.         IntNode p = this.head;
  6.         while (p.next != null) p = p.next;
  7.         p.next = new IntNode(val, null);
  8.     }
  9.     this.size++;
  10. }
复制代码
5. 虚拟头节点

现在观察代码会发现有些方法里做了形如 if (this.head == null) 的特判,当工程代码量变大时如果风俗这么写会导致代码冗余复杂。
我们可以添加一个虚拟头节点,如许就不会存在头节点为空的情况,这种虚拟头节点也称哨兵节点,而且修改 remove 方法使其不返回节点值,因为可以通过 get 方法获取节点值。末了本节课的完整实当代码如下:
  1. package CS61B.Lecture4;
  2. public class SLList {
  3.     private static class IntNode {
  4.         public int val;
  5.         public IntNode next;
  6.         public IntNode(int val, IntNode next) {
  7.             this.val = val;
  8.             this.next = next;
  9.         }
  10.     }
  11.     private IntNode sentinel;  // 第一个节点在sentinel.next上
  12.     private int size;
  13.     public SLList() {
  14.         this.sentinel = new IntNode(0, null);
  15.         this.size = 0;
  16.     }
  17.     public SLList(int val) {
  18.         this.sentinel = new IntNode(0, new IntNode(val, null));
  19.         this.size = 1;
  20.     }
  21.     // 获取首部节点值
  22.     public int getFirst() {
  23.         IntNode p = this.sentinel;
  24.         if (p.next != null) p = p.next;
  25.         return p.val;
  26.     }
  27.     // 在首部添加节点
  28.     public void addFirst(int val) {
  29.         this.sentinel.next = new IntNode(val, this.sentinel.next);
  30.         this.size++;
  31.     }
  32.     // 删除首部节点
  33.     public void removeFirst() {
  34.         if (this.sentinel.next == null) return;
  35.         this.sentinel.next = this.sentinel.next.next;
  36.         this.size--;
  37.     }
  38.     // 获取尾部节点值
  39.     public int getLast() {
  40.         IntNode p = this.sentinel;
  41.         while (p.next != null) p = p.next;
  42.         return p.val;
  43.     }
  44.     // 在尾部添加节点
  45.     public void addLast(int val) {
  46.         IntNode p = this.sentinel;
  47.         while (p.next != null) p = p.next;
  48.         p.next = new IntNode(val, null);
  49.         this.size++;
  50.     }
  51.     // 删除尾部节点
  52.     public void removeLast() {
  53.         if (this.sentinel.next == null) return;
  54.         IntNode p = this.sentinel;
  55.         while (p.next.next != null) p = p.next;
  56.         p.next = null;
  57.         this.size--;
  58.     }
  59.     // 获取链表长度
  60.     public int size() {
  61.         return this.size;
  62.     }
  63.     // 重写输出SLList对象的信息
  64.     @Override
  65.     public String toString() {
  66.         StringBuilder res = new StringBuilder("SLList: [");
  67.         IntNode p = this.sentinel;
  68.         while (p.next != null) {
  69.             res.append(p.next.val);
  70.             p = p.next;
  71.             if (p.next != null) res.append(", ");
  72.         }
  73.         res.append("]");
  74.         return res.toString();
  75.     }
  76.     public static void main(String[] args) {
  77.         SLList L = new SLList();
  78.         System.out.println(L.size());  // 0
  79.         System.out.println(L);  // SLList: []
  80.         L.addLast(15);
  81.         System.out.println(L.getLast());  // 15
  82.         L.addFirst(10);
  83.         System.out.println(L.getFirst());  // 10
  84.         System.out.println(L.size());  // 2
  85.         System.out.println(L);  // SLList: [10, 15]
  86.         L.removeLast();
  87.         L.removeFirst();
  88.         L.removeLast();
  89.         L.removeFirst();
  90.         System.out.println(L.size());  // 0
  91.         System.out.println(L);  // SLList: []
  92.     }
  93. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我爱普洱茶

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表