上一章说过了,顺序存储有一个弱点:在作插入或删除操作时,需要移动大量元素。所以这次我们用链式方式实现一下线性表,来体会一下线性链表在做插入时的便捷。
这里先实现链表中的单链表,也就是一个节点只有一个指针指向它的下一个节点。
下面先创建一个节点类,这个类中包含两个参数,一个参数用来保存值,另一个保存它的下一个节点。
public class LinkedNode<T> {
// 保存下一个节点
public LinkedNode<T> next = null;
//保存前一个节点,在双向链表中使用
public LinkedNode<T> pre = null;
// 保存值
public T value;
public LinkedNode() {
super();
}
public LinkedNode(T value) {
super();
this.value = value;
}
public LinkedNode(LinkedNode<T> next, T value) {
super();
this.next = next;
this.value = value;
}
}
接下来,我们需要用到上一章讲到的ListBase接口,我们创建这个接口的实现类。
public class LinkedList<T> implements ListBase<LinkedNode<T>> {
private int mSize = 0;
private LinkedNode<T> root = new LinkedNode<>();
// 定义几个异常
private Exception mSTZeroException = new Exception("数值必须大于0");
private Exception mBTLengthException = new Exception("超出列表长度");
private Exception mNotInListException = new Exception("元素不在列表中");
private Exception mFirstException = new Exception("已经是第一个元素");
private Exception mLastException = new Exception("已经是最后一个元素");
public LinkedList() {
}
@Override
public void clearList() {
root.next = null;
mSize = 0;
}
@Override
public boolean isEmpty() {
return root.next == null;
}
@Override
public int size() {
return mSize;
}
@Override
public LinkedNode<T> get(int index) throws Exception {
if(index > mSize - 1)
throw mBTLengthException;
if(index < 0)
throw mSTZeroException;
LinkedNode<T> currentNode = root.next;
// 按顺序找到节点
for(int i=0; i<index; i++) {
currentNode = currentNode.next;
}
return currentNode;
}
@Override
public LinkedNode<T> preElem(LinkedNode<T> t) throws Exception {
if(mSize == 0) {
return null;
}
int position = -1;
for(int i=0; i<mSize; i++) {
if(get(i).equals(t)) {
position = i;
break;
}
}
if(position == 0)
throw mFirstException;
if(position == -1)
throw mNotInListException;
return get(position - 1);
}
@Override
public LinkedNode<T> nextElem(LinkedNode<T> t) throws Exception {
if(t.next == null)
throw mLastException;
return t.next;
}
@Override
public void add(int index, LinkedNode<T> insertNode) throws Exception {
mSize++;
if(index > mSize - 1)
throw mBTLengthException;
if(index < 0)
throw mSTZeroException;
// 如果根节点为空,那么将值插入给根节点
if(root.next == null) {
root.next = insertNode;
return;
}
LinkedNode<T> preNode = root.next;
// 通过遍历找到要插入的位置
for(int i=0; i<index - 1; i++) {
preNode = preNode.next;
}
// 执行插入操作
insertNode.next = preNode.next;
preNode.next = insertNode;
}
@Override
public void remove(int index) throws Exception {
if(index > mSize - 1)
throw mBTLengthException;
if(index < 0)
throw mSTZeroException;
mSize--;
if(mSize == 0) {
root.next = null;
return;
}
// 如果要删除第一个元素,那么需要把根节点换位置
if(index == 0) {
root.next = root.next.next;
return;
}
LinkedNode<T> preNode = root.next;
// 找到要删除元素的前一个元素
for(int i=0; i<index-1 ; i++) {
preNode = preNode.next;
}
// 执行删除操作
preNode.next = preNode.next.next;
}
@Override
public void printAll() {
if(root.next == null)
return;
LinkedNode<T> currentNode = root.next;
while(currentNode != null) {
System.out.print(currentNode.value + " ");
currentNode = currentNode.next;
}
}
}
就此,单链表就已经实现成功了。
使用起来也很容易。
public static void main(String[] args) throws Exception {
LinkedList<String> list = new LinkedList<>();
for(int i=0; i<20; i++) {
list.add(i, new SinglyLinkedNode<String>("" + i));
}
System.out.println(list.get(0).value);
System.out.println(list.preElem(list.get(0)).value);
list.printAll();
}
总结
在实现中我有明显的感觉,在执行定位以及查找工作时,用链表十分的麻烦,而在定位完成后执行插入或删除操作时,则十分的便利。另外nextElem和preElem这两个方法,虽然功能十分相近,但是实现起来确实十分的不同,执行nextElem只要找到当前节点的next就可以,而要实现preElem则需要遍历整个线性表,正因为如此,出现了双向链表。
下一章我们将用Java来实现循环链表。