用于自定义链表实现的插入方法?

时间:2018-12-12 16:15:59

标签: java data-structures linked-list insert nodes

我想要一些有关我编写的有关在链表中插入元素的方法的反馈。给出的链表类定义为:

public class List {
  private Node head;

  private static class Node {
    public String data;
    public Node next;

    public Node(String data, Node next) {
      //Assign data and next here
    }
    //Optional Node helper methods are written here
}
//List methods and data go here

基于此定义,我正在尝试创建一个insert方法。我不确定给出的定义是否包含size()方法,但我认为它没有,并尝试找到一种方法来在我的insert方法中查找大小(基本上是列表中有多少个节点) 。该方法具有签名public void insert(String s, int psn),当给出非法索引(psn)值(超出范围)时,此方法还应该引发IllegalArgumentException。我的猜测是psn的值范围是从0到第一个null元素出现的时间(如果我对此不正确,请更正我。这是我编写的方法:

public void insert(String s, int psn) {
  Node temp = head; //Save original head
  int c = 0;

  while (temp != null) { //Traverse linked list
   c++;
   temp = temp.next;
  }

  if (psn < 0 || psn >= c) { //Should throw exception when illegal value is used
    throw new IllegalArgumentException();
  } 

  if (psn == 0) { //Special case when inserting an element at the front of the list
    head = new Node(s, head);

  } else if (head != null) {
    Node current = head; //Save original head while traversing list

    while (current != null) {
      current = current.next;
      psn--;
    }

    if (current != null) { //We are at the position where we want to insert the element
      current.next = new Node(s, current.next);
    }
  }
}

有人可以告诉我在查找链表的长度时是否正确使用了第一个while循环,以及是否正确使用了例外情况?这是我最关心的部分。提前非常感谢您!

3 个答案:

答案 0 :(得分:1)

您始终可以通过在某些测试用例上运行代码来测试代码。但是,这是我对这段代码的评论:

  • 最后一个if语句ViewStart将始终返回false,因为在此之前的while循环将使if (current != null)在结尾处为空。
  • 我将检查current是否在第一个while循环之前,以减少计算的浪费。
  • 我建议您在类中保留一个名为psn < 0的字段,该字段将处理列表的大小。首先将其设置为0,然后在每个size和每个insert之后,您将更新此字段。

然后代码看起来像这样:

remove

答案 1 :(得分:0)

  • 是的,这是在不存储大小的情况下检索LinkedList中元素数量的一种正确方法。

  • 这是链表的一个缺点,即遍历O(n),但如果我们存储“ tail”元素,则插入1次。

  • 也请参阅Matan Kintzlinger的逻辑建议

This website has a great description of linked lists

答案 2 :(得分:0)

按现状,您的insert遍历整个列表两次,一个遍历整个列表,另一个遍历整个列表。最基本的实现没有任何数据可以帮助您喜欢每次写入都会更新一个size字段,因此只需遍历一次即可,因为如果您早于预期到达sentinel,您就会知道所需位置无效。这就是我的实现方式:

public void insert(String item, int position) {
  if(position == 0) {
    head = new Node(item, head);
    return;
  }

  List.Node previousNode = head;

  for(int i = 1; i < position && previousNode != null; ++i) {
    previousNode = previousNode.next;
  }

  if(position < 0 || previousNode == null) {
    throw new IllegalArgumentException("index " + position + " is out of bounds");
  }

  previousNode.next = new Node(item, previousNode.next);
}

其他一些注意事项是:

  • 请尝试使用描述性变量名,而不要使用psn之类的缩写,因为如果没有完整的上下文,它们可能很难阅读。在大多数现实情况下,优化人类可读性更为重要,因为调试和维护代码的人类越容易理解它,出现错误的可能性就越小。很多时候,变量名实际上是您首先了解的情况,并且是真正的明智之选!上一次,我试图使用外部npm库来破坏代码的原因是为什么?实际上是几天前。

  • 尝试使用guard clauses和早期返回值以避免arrow codeMultiple return points are not necessarily bad,如果您避免在方法中使用excessive cyclomatic complexity,发现它们并不是问题。例如,在这种情况下,在零位置插入的特殊情况是一次提前返回的机会,因为不需要检查,并且在完成后也无需执行任何操作。

  • 使您的异常尽可能有帮助。这意味着,当您因为捕获到另一个异常而引发异常时,将您捕获的那个异常按顺序包括在not to break the stack trace中;在这种情况下,不存在更早的异常,一条有用的消息可以帮助开发人员弄清楚发生了什么。在这种情况下,IllegalArgumentException并没有告诉我们什么原因导致抛出此异常,因此没有消息。几乎每个异常构造函数都允许消息和Throwable原因。