Java 类库中其实是提供了链表的实现类的,但是如果自己来实现会不会很有成就感呢?
我们知道,Java 官方是没有指针的概念的,当然我们可以把对象的引用理解为指针,虽然与 C 或 C++ 中的指针概念不尽相同。想要自己实现链表,最重要的一步就是怎么表示一个链表中的结点。在 Java中,我们可以定义一个专门表示结点的类,最好是内部类,确保类的封装性与完整性。此结点类可定义如下:
class Node {
public Node(String name, int age) {
this.name = name;
this.age = age;
this.next = null;
}
private String name;
private int age;
private Node next;
}
此处结点的数据类型可以固定写在该类中,毕竟结点中存放的数据一般都是相同类型的。
确定了结点的表示方法,余下的关键问题就是链表的表示方法。我们可以定义一个头结点,初始化为null。然后对于链表增删改查操作都要在此基础上进行。
链表源代码如下:
/**
* 实现自己的链表类
*
* @author Wll
*
*/
public class LinkedListDemo {
public static void main(String[] args) {
LinkedListDemo list = new LinkedListDemo();
Node tom = new Node("Tom", 20);
Node jack = new Node("Jack", 21);
Node michael = new Node("Michael", 18);
Node lisa = new Node("Lisa", 20);
Node kitty = new Node("Kitty", 19);
list.add(tom);
list.add(jack);
list.add(michael);
list.add(lisa);
list.add(kitty);
System.out.println("+++++链表长度+++++");
System.out.println(list.size());
System.out.println("\n+++++遍历链表结果+++++");
list.traverse();
System.out.println("\n+++++删除第 5个结点+++++");
try {
System.out.println(list.delete(4));
list.traverse();
} catch (MyIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
}
System.out.println("\n+++++修改第 3 个结点+++++");
try {
System.out.println(list.modify(2, "Modified", 100));
list.traverse();
} catch (MyIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
}
System.out.println("\n+++++查找第 6 个结点+++++");
try {
System.out.println(list.indexOf(5));
} catch (MyIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
}
System.out.println("\n+++++插入第 3 个结点+++++");
Node lucy = new Node("Lucy", 22);
try {
System.out.println(list.insertAt(2, lucy));
list.traverse();
} catch (MyIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
}
System.out.println("\n+++++重置(清空)链表+++++");
System.out.println(list.reset());
list.traverse();
}
/**
* 在最后一个结点后面添加一个新结点
*
* @param node
* 待添加的新结点
* @return 若插入成功返回 true,否则 false
*/
public boolean add(Node newNode) {
Node p = head;
if (head == null) { // 添加到头部
head = newNode;
return true;
} else { // 添加到末尾结点后面
// 这里是 p.next,因为如果是 p, 则 while 循环结束后 p 必定为 null,p.next 就会空指针异常
while (p.next != null) {
p = p.next;
}
p.next = newNode;
return true;
}
}
/**
* 在任意位置插入一个新结点
*
* @param index
* 插入的位置
* @param newNode
* 插入的结点
* @return 若插入成功返回 true,否则 false
* @throws IndexOutOfBoundsException
*/
public boolean insertAt(int index, Node newNode) throws MyIndexOutOfBoundsException {
Node p = head;
int count = 1;
if (index < 0 || index > this.size()) {
throw new MyIndexOutOfBoundsException("插入异常,请检查下标范围!");
}
if (index == 0) {
newNode.next = head;
head = newNode;
return true;
} else {
while (p != null) {
if (count++ == index) {
newNode.next = p.next;
p.next = newNode;
return true;
}
p = p.next;
}
return false;
}
}
/**
* 删除一个已有结点
*
* @param index
* 该节点的索引,以 0 开始。第 2 个结点索引就是 1
* @return 若删除成功返回 true,否则返回 false
* @throws IndexOutOfBoundsException
*/
public boolean delete(int index) throws MyIndexOutOfBoundsException {
int count = 1;
Node p = head;
if (index < 0 || index > this.size() - 1) {
throw new MyIndexOutOfBoundsException("删除异常,请检查下标范围!");
}
if (index == 0) {
head = p.next;
return true;
} else {
while (p != null) {
if (count++ == index) {
p.next = p.next.next;
return true;
}
p = p.next;
}
return false;
}
}
/**
* 修改结点内容
*
* @param index
* 结点所在索引
* @param name
* 结点 name 属性
* @param age
* 结点 age 属性
* @return 若修改成功返回 true,否则返回 false
* @throws IndexOutOfBoundsException
*/
public boolean modify(int index, String name, int age) throws MyIndexOutOfBoundsException {
Node p = head;
int count = 0;
if (index < 0 || index > this.size() - 1) {
throw new MyIndexOutOfBoundsException("修改异常,请检查下标范围!");
}
while (p != null) {
if (count++ == index) {
p.name = name;
p.age = age;
return true;
}
p = p.next;
}
return false;
}
/**
* 查找指定索引处的结点
*
* @param index
* 结点索引
* @return 索引处结点
* @throws IndexOutOfBoundsException
*/
public Node indexOf(int index) throws MyIndexOutOfBoundsException {
Node p = head;
int count = 0;
if (index < 0 || index > this.size() - 1) {
throw new MyIndexOutOfBoundsException("查询异常,请检查下标范围!");
}
while (p != null) {
if (count++ == index) {
return p;
}
p = p.next;
}
return null;
}
/**
* 获得链表的大小
*
* @return 链表长度
*/
public int size() {
int count = 0;
Node p = head;
while (p != null) {
count++;
p = p.next;
}
return count;
}
/**
* 遍历链表
*/
public void traverse() {
Node p = head;
int count = 0;
while (p != null) {
System.out.println(p);
count++;
p = p.next;
}
if (count == 0) {
System.out.println("链表为空!");
}
}
/**
* 重置链表
*/
public boolean reset() {
head = null;
return true;
}
private Node head = null;
/**
* 内部结点类
*
* @author Wll
*
*/
private static class Node {
public Node(String name, int age) {
this.name = name;
this.age = age;
this.next = null;
}
@Override
public String toString() {
return this.getClass().getName() + "[Name=" + this.name + ", Age=" + this.age + "]";
}
private String name;
private int age;
private Node next;
}
}
class MyIndexOutOfBoundsException extends Exception {
private static final long serialVersionUID = 1L;
public MyIndexOutOfBoundsException(String msg) {
this.msg = msg;
}
@Override
public String getMessage() {
return this.msg;
}
private String msg;
}
虽然上边的代码可以实现链表的常用操作,但是可以想象当链表中的结点数太大时性能会有所下降。下面是对于提升上面代码执行效率的一点思考。
首先,每一次在末尾添加结点都会遍历整个链表。其次,对于链表长度的获取也需要遍历整个链表。这两个操作都会多耗费很多时间。所以更好的解决办法是定义一个变量记录链表长度,每次添加或删除结点的时候就将此变量加 1 或者减 1。除此之外,再定义一个指向末尾结点的引用,每次添加或删除结点时对应将此引用修改,这样在末尾添加结点时就可以节省遍历链表的时间。