面向百度編程之 手撕鏈表

那對於鏈表結構,我們在項目中用到的不如數組頻繁,但是面試是個重點,爲什麼面試官喜歡考我們鏈表呢?想必大家對這個問題很感興趣,因爲鏈表靈活、涉及到的邊界條件多,又加上很多細節點,對應聘者是一個考驗。今天就和大家一起來 手寫 一下 鏈表結構。

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章