CS61B - List #1 实现一个单向链表

这篇笔记记录了如何从零开始构建链表数据结构,从最基础的 IntList 逐步优化到更完善的 SLList(单链表)。通过引入封装、哨兵节点(sentinel)等技术,解决了直接操作节点的安全性和空列表处理问题,并实现了基本的增删查操作。

从0构建一个链表

IntList

使用链表,构建一个列表:

    class Node{
    public int item;
    public Node? next;

    public Node(int value, Node? next = null){
        this.item = value;
        this.next = next;
      }
    }

SLList

显然,直接在列表里添加函数方法太过于简陋,且无法实现不需要遍历列表即可获取列表长度的方法,于是我们使用新的类将其封装

原始SLList

public class SLList {
    public Node first;

    public SLList(int x) {
        first = new Node(x, null);
    }

    /** Adds an item to the front of the list. */
    public void addFirst(int x) {
        first = new Node(x, first);
    }
}

但是这样做存在两个问题:

  • first节点对外裸露

  • 若创建空列表,由于first节点为null,无法正确addLast

为了解决上面两个问题,可以把public改为private,并创建一个哨兵节点(虚拟头节点),我们令这个哨兵节点始终位于最前端且被实例化,可以规避由于null导致无法addLast的问题

修改后的SLList

class SLList{
    //Node
    class Node{
    public int item;
    public Node? next;

    public Node(int value, Node? next = null){
        this.item = value;
        this.next = next;
      }
    }

    //SLList    

    private Node sentinel;
    public int size = 0;

    public SLList(){
        sentinel = new Node(0,null);
    }

    public SLList(int item){
        sentinel = new Node(0,null);
        sentinel.next = new Node(item,null);
        size += 1;
    }

    public void AddFirst(int item){
        sentinel.next = new Node(item,sentinel.next);
        size += 1;
    }

    public void AddLast(int item){
        Node p = sentinel;
        while(p.next != null){
            p = p.next;
        }
        p.next = new Node(item,null);
        size += 1;
    }

    public int GetSize(){
        return size;
    }

    public int GetItem(int index)
    {
        Node p = sentinel;
        while(index >= 0 && p.next != null){
            p = p.next;
            index--;
        }
        return p.item;
    }
}

修改后的SLList也有需要优化的地方:

  • 现在这个SLList是单向的,也就是说不管访问哪个节点都会从头开始遍历,当我们想要访问或添加/删除靠后的节点时,每次访问都需要先经过不必要的遍历,不是吗?

评论