- 为了避免插人和删除的线性开销,我们需要保证表可以不连续存储,否则表的每个部分都可能需要整体移动。
- 链表由一系列节点组成,这些节点不必在内存中相连。每一个节点均含有表元素和到包含 该元素后继元的节点的链(link)。我们称之为next链。最后一个单元的next链引用null。
- 为了执行printList或find(x),只要从表的第一个节点开始然后用一些后继的next链遍历 该表即可。这种操作显然是线性时间的,和在数组实现时一样,不过其中的常数可能会比用数组实现时要大。find操作不如数组实现时的效率高;find(i)花费O(i)的时间并以这种明显的方式遍历链表而完成。
- remove方法可以通过修改一个next引用来实现。
insert方法需要使用new操作符从系统取得一个新节点,此后执行两次引用的调整。其中的虚线表示原来的next引用。
我们看到,在实践中如果知道变动将要发生的地方,那么向链表插人或从链表中删除一项的操作不需要移动很多的项,而只涉及常数个节点链的改变。
- 在表的前端添加项或删除第一项的特殊情形此时也属于常数时间的操作,当然要假设到链表前端的链是存在的。只要我们拥有到链表最后节点的链,那么在链表末尾进行添加操作的特殊情形(即让新的项成为最后一项)可以花费常数时间。典型的链表拥有到该表两端的链。 删除最后一项比较复杂,因为必须找出指向最后节点的项,把它的next链改成null,然后再更 新持有最后节点的链。在经典的链表中,每个节点均存储到其下一节点的链,而拥有指向最后节 点的链并不提供最后节点的前驱节点的任何信息。
测试代码
package top.itcourse.list;
public class Node<T> {
// 存储的数据
private T data;
// 存储的下一个元素的引用
private Node<T> next = null;
// 构造器
public Node(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
}
package top.itcourse.list;
public class SingleList<T> {
// 链表头结点
private Node<T> head;
// 链表长度
private int length = 0;
/**
* 在链表末尾添加节点。
* @param value:新值
*/
public void add(T value) {
Node<T> node = new Node<T>(value);
// 第一次添加
if(head == null) {
head = node;
// 追加
} else {
Node<T> auxNode = head;
while(auxNode.getNext() != null) {
auxNode = auxNode.getNext();
}
auxNode.setNext(node);
}
length++;
}
/**
* 在指定下标位置插入元素。
* @param preIndex:指定插入元素的下标(下标从0开始)。
* @param value:新元素值
*/
public void add(int index, T value) {
// 检测下标合法性
checkIndex(index);
length++;
// 插入下标为0
if(index == 0) {
Node<T> node = new Node<>(value);
node.setNext(head);
head = node;
return ;
}
// 下标其它情况
Node<T> auxNode = head;
for(int i = 1; i < index; ++i) {
auxNode = auxNode.getNext();
}
Node<T> node = new Node<>(value);
node.setNext(auxNode.getNext());
auxNode.setNext(node);
}
/**
* 下标越界检测
* @param index:待检测的下标
*/
private void checkIndex(int index) {
if(index > length || index < 0) {
throw new RuntimeException("下标越界异常");
}
}
/**
* 遍历链表。
*/
public void printList() {
Node<T> auxNode = head;
while(auxNode != null) {
System.out.print(auxNode.getData() + "\n");
auxNode = auxNode.getNext();
}
System.out.println();
}
/**
* 返回当前链表长度。
* @return:返回当前链表长度。
*/
public int size() {
return length;
}
/**
* 移除所有指定的元素。有就移除,没有就不管。判断是否存在的唯一标准是两者的equals方法返回true,且不为null。
* @param e:待移除的元素。
*/
public void remove(T e) {
// 为null
if(e == null) {
return ;
}
// 移除头结点
if(head.getData().equals(e)) {
head = head.getNext();
}
// 移除其它结点
Node<T> auxNode = head;
Node<T> preNode = null;
while(auxNode != null) {
// 找到了元素
if(e.equals(auxNode.getData())) {
preNode.setNext(auxNode.getNext());
}
// 可能会被移除结点的前一个结点
preNode = auxNode;
auxNode = auxNode.getNext();
}
length--;
}
/**
*
* @param e:待查找的元素。唯一标准是两个对象之间的equals方法返回true。如果找到元素返回第一次出现对应下标,否则返回-1。
* @return:返回e所在的下标。
*/
public int find(T e) {
int index = 0;
Node<T> auxNode = head;
while(auxNode != null) {
if(auxNode.getData().equals(e)) {
return index;
}
auxNode = auxNode.getNext();
index++;
}
return -1;
}
}
其它
关注下方微信公众号,
回复:
DS-AA-Java.code
欢迎加入交流群:451826376
更多信息:www.itcourse.top