面向百度编程之 手撕链表

那对于链表结构,我们在项目中用到的不如数组频繁,但是面试是个重点,为什么面试官喜欢考我们链表呢?想必大家对这个问题很感兴趣,因为链表灵活、涉及到的边界条件多,又加上很多细节点,对应聘者是一个考验。今天就和大家一起来 手写 一下 链表结构。

1.熟悉结构

首先我们要知道链表的结构以及每个节点的结构,这是我们手写链表的第一步,也是学习链表的第一步。我们知道,每个链表时这样表示的:

那每个节点结构是由数据域针域组成,数据域是存放数据的,而指针域存放下一结点的地址。

我们可以通过数据域访问到我们要的数据,而通过指针域访问到当前结点以后的结点,那么将这些结点串起来,就是一个链表。

那么用代码怎么来表示呢?

我们通常会用到函数,我们如果将一个函数抽象成一个结点,那么我们给函数添加两个属性,一个属性是存放数据的属性data,另一个属性是存放指向下一个结点的指针属性next

类似下面这种

public class Node<T> {
  
  private T value; //当前节点的值
  
  private String next; //下个节点的地址

  ....
}

2.理清思路

如果你把链表的结构搞得明明白白了,恭喜你,成功的进入第二关,但是你只超越了百分之20的人,继续加油。

既然链表的结构弄明白了,那么我们开始理思路,我们就先拿最简单的单链表开刀,我们要完成两个操作,插入数据和删除数据。

如果我想插入数据,你可能会问,往哪里插呢?有几种插入的方法?

开始想,插入到单链表的头部算一种。

然后插入到单链表的中间算一种。

插入到单链表尾部又算一种。

所有可能的情况就三种。那么删除呢?想必你也想到了,也一共三种,删除头部、删除中间部分、删除尾部。

如果你觉的现在可以写代码了,那你就错了,虽然我们的思路非常清晰,但是面试官仅仅考我们思路吗?其实这一关你只打败了百分之50%的人,最重点、最主要的是在下一个部分,边界条件。

3.边界条件

边界条件是这五个步骤最容易犯错的一部分,因为通常考虑的不全面,导致了最后的面试未通过。如果想做好这一部分,也不难,跟着小鹿的方法走。

3.1 输入边界

首先我们先考虑用户输入的参数,比如传入一个链表,我们首先要判断链表是否为空,如果为空我们就不能让它执行下边的程序。再比如插入一个结点到指定结点的后边,那么你也要判断输入的结点是否为空,而且还要判断该结点是否存在该链表中。对于这些输入值的判断,小鹿给他同一起个名字叫做输入边界。

3.2 特殊边界

特殊边界考虑到一些特殊情况,比如插入数据,我们插入数据一般考虑到插入尾部,但是突然面试官插入到头部,插入尾部的代码并不适用于插入到头部,所以呢需要考虑这种情况,删除节点也是同样思考。其实特殊边界最主要考虑到一些逻辑上的特殊情况,考察应聘者的考虑的是否全面。小鹿给他起个名字叫做特殊边界。

4手写代码

4.1 定义结点(类class)

public class Node<T> {

  private T value;

  private Node<T> next;

  public T getValue() {
    return value;
  }

  public void setValue(T value) {
    this.value = value;
  }


  public Node<T> getNext() {
    return next;
  }

  public void setNext(Node<T> next) {
    this.next = next;
  }

  /**
   * @param node
   */
  public Node(T value) {
    super();
    this.value = value;
    this.next = null;
  }
  
  @Override
  public String toString() {
    return  value + "," + next;
  }

  
}

4.2 增加结点

咱们就以单链表中部添加数据为例子,分解成每个步骤,每个步骤对应代码如下:

1、保存临时地址(4结点的地址),需要进行遍历查找到3结点,也就是下列代码的currentNode 结点。

2、创建新结点,将新结点(5结点)的指针指向下一结点指针(4结点地址,已经在上一步骤保存下来了)

 

3、3 的结点地址指向新结点(5结点)

指定位置插入指定元素代码入下

 /**
   * 插入元素(指定元素向后插入)
   * 
   * @param data 指定的元素
   * @param objectData 需要插入的元素
   * @return
   */
  public void InsertNode(T data, T objectData) {
    if (this.headNode != null) {
      Node<T> currentNode = this.headNode;
      while (currentNode != null && currentNode.getValue() != data) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      if (currentNode == null || currentNode.getValue() != data) {
        // 跳出循环时未找到指定的元素
        throw new RuntimeException("插入失败,没有指定的元素!");
      }
      // 将当前元素的下个元素 引用 到 指定元素
      Node<T> node = new Node<T>(objectData);
   // 将指定元素的下个元素的 引用 交给 插入的元素
      Node<T> next = currentNode.getNext();
      
      currentNode.setNext(node);
      node.setNext(next);

    } else {
      throw new RuntimeException("插入失败,没有指定的元素!");
    }
  }

4.3 删除节点

删除节点也分为三种,头部、中部、尾部,咱们就删除中间结点为例进行删除,我们详细看步骤操作。

1、我们先看删除的全部动画,然后再分步拆分。

2、断开3结点的指针(断开3结点相当于让2结点直接指向4结点)

 

3、让结点2的指针指向4结点,完成删除。

删除元素代码如下

/**
   * 删除指定的元素
   * 
   * @param data 指定的元素
   * @return
   */
  public void deleteNode(T data) {
    if (this.headNode != null) {
      if (this.headNode.getValue() == data) {
        this.headNode = null;
      }

      Node<T> currentNode = this.headNode;
      while (currentNode != null && currentNode.getNext() != null
          && currentNode.getNext().getValue() != data) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      if (currentNode == null || currentNode.getNext() == null || currentNode.getNext().getValue() != data) {
        // 跳出循环时未找到指定的元素
        throw new RuntimeException("删除失败,没有指定的元素!");
      }

      // 将当前元素的下个元素 引用 到 下下个元素
      currentNode.setNext(currentNode.getNext().getNext());

    } else {
      throw new RuntimeException("删除失败,没有指定的元素!");
    }
  }

 

完整的代码如下



/**
 * @author liyong
 * @version 2019年11月5日
 */
public class LinkList<T> {

  private Node<T> headNode;

  
  /**
   * 
   */
  public LinkList() {
    super();
  }

  
  /**
   * @param headNode
   */
  public LinkList(Node<T> headNode) {
    super();
    this.headNode = headNode;
  }

  public Node<T> getHeadNode() {
    return headNode;
  }

  public void setHeadNode(Node<T> headNode) {
    this.headNode = headNode;
  }

  /**
   * 根据某个值查找对应的节点
   * 
   * @param data
   * @return Node<T>
   */
  public Node<T> findNodeByValue(T data) {
    if (this.headNode == null) {
      return null;
    } else {
      Node<T> currentNode = this.headNode;
      while (currentNode != null && currentNode.getValue() != data) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      // 判断跳出循环时是否相等
      if (currentNode == null) {
        return null;
      }
      return currentNode.getValue() == data ? currentNode : null;
    }
  }

  /**
   * 插入元素(指定元素向后插入)
   * 
   * @param data 指定的元素
   * @param objectData 需要插入的元素
   * @return
   */
  public void InsertNode(T data, T objectData) {
    if (this.headNode != null) {
      Node<T> currentNode = this.headNode;
      while (currentNode != null && currentNode.getValue() != data) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      if (currentNode == null || currentNode.getValue() != data) {
        // 跳出循环时未找到指定的元素
        throw new RuntimeException("插入失败,没有指定的元素!");
      }
      // 将当前元素的下个元素 引用 到 指定元素
      Node<T> node = new Node<T>(objectData);
   // 将指定元素的下个元素的 引用 交给 插入的元素
      Node<T> next = currentNode.getNext();
      
      currentNode.setNext(node);
      node.setNext(next);

    } else {
      throw new RuntimeException("插入失败,没有指定的元素!");
    }
  }

  /**
   * 在头部插入元素
   * 
   * @param data 需要插入的元素
   * @return
   */
  public void InsertNodeBeforeHead(T data) {
    Node<T> node = new Node(data);
    node.setNext(headNode);
    this.setHeadNode(node);
  }

  /**
   * 在尾部插入元素
   * 
   * @param data 需要插入的元素
   * @return
   */
  public void InsertNodeAfterBody(T data) {
    Node<T> node = new Node(data);
    if (this.headNode != null) {
      Node<T> currentNode = this.headNode;
      while (currentNode.getNext() != null) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      currentNode.setNext(node);
    } else {
      this.setHeadNode(node);
    }
  }


  /**
   * 删除指定的元素
   * 
   * @param data 指定的元素
   * @return
   */
  public void deleteNode(T data) {
    if (this.headNode != null) {
      if (this.headNode.getValue() == data) {
        this.headNode = null;
      }

      Node<T> currentNode = this.headNode;
      while (currentNode != null && currentNode.getNext() != null
          && currentNode.getNext().getValue() != data) {
        // 当前节点不为null && 当前节点值与data不相等
        currentNode = currentNode.getNext();
      }
      if (currentNode == null || currentNode.getNext() == null || currentNode.getNext().getValue() != data) {
        // 跳出循环时未找到指定的元素
        throw new RuntimeException("删除失败,没有指定的元素!");
      }

      // 将当前元素的下个元素 引用 到 下下个元素
      currentNode.setNext(currentNode.getNext().getNext());

    } else {
      throw new RuntimeException("删除失败,没有指定的元素!");
    }
  }

  /**
   * 删除头部元素
   * 
   * @return
   */
  public void deleteHeadNode() {
    if (this.headNode != null) {
      Node<T> currentNode = this.headNode;
      this.setHeadNode(currentNode.getNext());
    } else {
      throw new RuntimeException("删除失败,没有头部元素!");
    }
  }

  /**
   * 删除尾部元素
   * 
   * @return
   */
  public void deleteLastNode() {
    if (this.headNode != null) {
      Node<T> currentNode = this.headNode;
      if(currentNode.getNext() == null){
        this.setHeadNode(null);
      }
      
      while (currentNode.getNext() != null && currentNode.getNext().getNext() != null) {
        currentNode = currentNode.getNext();
      }
      if(currentNode.getNext().getNext() == null){
        currentNode.setNext(null);
      }
    } else {
      throw new RuntimeException("删除失败,没有尾部元素!");
    }
  }


  @Override
  public String toString() {
    return "LinkList:" + headNode;
  }
  
  
}

测试代码

public static void main(String[] args) {
    LinkList<String> list = new LinkList<>();
    
    list.InsertNodeAfterBody("aaa");
    list.InsertNodeAfterBody("bbb");
    list.InsertNodeAfterBody("ccc");
    
    System.out.println(list);
    System.out.println("--------------------分割线-------------------------");
    
    list.InsertNode("bbb", "test");
    System.out.println("--------------------向某个元素后面增加-------------------------");
    System.out.println(list);
    
    list.InsertNodeBeforeHead("head");
    System.out.println("--------------------向头部元素后面增加-------------------------");
    System.out.println(list);
    
    list.InsertNodeAfterBody("last");
    System.out.println("--------------------向尾元素后面增加-------------------------");
    System.out.println(list);
    
    list.deleteNode("test");
    System.out.println("--------------------删除某一元素-------------------------");
    System.out.println(list);
    
    list.deleteHeadNode();
    System.out.println("--------------------删除头部元素-------------------------");
    System.out.println(list);
    
    list.deleteLastNode();
    System.out.println("--------------------删除尾部元素-------------------------");
    System.out.println(list);
    
    System.out.println("--------------------查找元素-------------------------");
    System.out.println(list.findNodeByValue("bbb"));
    
  }

输出结果

LinkList:aaa,bbb,ccc,null
--------------------分割线-------------------------
--------------------向某个元素后面增加-------------------------
LinkList:aaa,bbb,test,ccc,null
--------------------向头部元素后面增加-------------------------
LinkList:head,aaa,bbb,test,ccc,null
--------------------向尾元素后面增加-------------------------
LinkList:head,aaa,bbb,test,ccc,last,null
--------------------删除某一元素-------------------------
LinkList:head,aaa,bbb,ccc,last,null
--------------------删除头部元素-------------------------
LinkList:aaa,bbb,ccc,last,null
--------------------删除尾部元素-------------------------
LinkList:aaa,bbb,ccc,null
--------------------查找元素-------------------------
bbb,ccc,null

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值