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