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