简单链表的实现
为了使用链表来提高插入、删除操作的效率,我们需要保证表可以不连续存储。
链表由一系列节点组成,每个节点可以在内存中不连续存储,每一个节点中都含有表元素和指向下一个节点的next链引用。
在链表中实现删除某一个节点,只需要将它前一个节点的next链引用指向要删除的节点的后一个节点就行。
在链表中实现在指定位置插入一个节点,只需要将该位置的前一个节点的next链引用指向需要插入的节点,并将新插入的节点的next链引用指向下一个节点。
简单链表实现类MyLinkedList:
Item为占位符,表示可以处理任意类型的数据。
API:
boolean isEmpty()//判断是否为空
int size()//获取链表节点数
boolean add (Item item)//add方法,向链表尾部添加一个节点,返回true
Item get (int index)//get方法,返回指定索引的节点的值
Item set(int index,Item item)//set方法,将指定索引的节点设为指定的值,并返回原来的值
Item remove (int index)//remove方法,删除指定索引的节点,返回删除的节点值
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyLinkedList<Item> implements Iterable<Item> {
private Node first;//头节点
private Node last;//尾节点
private int size;//链表中节点数量
//节点内部类
private class Node {
Item item;
Node next;
}
//判断是否为空
public boolean isEmpty() {
return size() == 0;
}
//获取链表节点数
public int size() {
return size;
}
//add方法,向链表尾部添加一个节点,返回true
public boolean add (Item item) {
//判断是否为空
if (size() == 0) {
//如果为空,创建新节点,并将头节点和尾节点都指向它
Node newNode = new Node();
newNode.item = item;
first = newNode;
last = newNode;
}else {
//如果不为空,创建新节点,并将原来的尾节点的next链指向它,并将它作为新的尾节点
Node newNode = new Node();
newNode.item = item;
last.next = newNode;
last = newNode;
}
//链表中节点数量+1
size++;
return true;
}
//get方法,返回指定索引的节点的值
public Item get (int index) {
Node n;
//判断是否越界
if (index <0 || index > size()) {
throw new ArrayIndexOutOfBoundsException();
}else {
//从头节点开始遍历链表,直到指定的索引
n = first;
for (int i = 0;i < index;i++) {
n = n.next;
}
}
return n.item;
}
//set方法,将指定索引的节点设为指定的值,并返回原来的值
public Item set(int index,Item item) {
Node n;
//判断是否越界
if (index <0 || index > size()) {
throw new ArrayIndexOutOfBoundsException();
}else {
//从头节点开始遍历链表,直到指定的索引
n = first;
for (int i = 0;i < index;i++) {
n = n.next;
}
}
Item oldItem = n.item;
n.item = item;
return oldItem;
}
//remove方法,删除指定索引的节点,返回删除的节点值
public Item remove (int index) {
Node n = null;
Item oldItem = null;
//判断是否越界
if (index <0 || index > size()) {
throw new ArrayIndexOutOfBoundsException();
}
//如果是头节点,则将头节点下一个节点作为新的头节点
if(index == 0) {
oldItem = first.item;
first = first.next;
}
//如果是尾节点,则将尾节点的上一个节点作为新的尾节点
if (index == size()-1) {
n = first;
//遍历链表,直到找到指定节点的前一节点
for (int i = 0;i < index -1;i++) {
n = n.next;
}
oldItem = n.next.item;
//将前一节点的next置为空,作为新的尾节点
n.next = null;
last = n;
}
if(index > 0 && index < size()-1) {
n = first;
//遍历链表,直到找到指定节点的前一节点
for (int i = 0;i < index -1;i++) {
n = n.next;
}
oldItem = n.next.item;
//将前一节点的next链指向下一个节点
n.next = n.next.next;
}
size--;
return oldItem;
}
//获取迭代器
public Iterator<Item> iterator() {
return new LinkedListIterator();
}
private class LinkedListIterator implements Iterator<Item>{
private Node current = first;
public boolean hasNext() {
return current != null;
}
public Item next() {
Node oldCurrent = current;
if (!hasNext()) {
throw new NoSuchElementException();
}
current = current.next;
return oldCurrent.item;
}
}
}
测试用例:
public class Test {
public static void main(String[] args) {
//创建单链表对象
MyLinkedList<Integer> myLinkedList = new MyLinkedList<Integer>();
//向单链表中添加元素
myLinkedList.add(0);
myLinkedList.add(1);
myLinkedList.add(2);
myLinkedList.add(3);
myLinkedList.add(4);
myLinkedList.add(5);
//遍历节点
System.out.print("链表元素:");
for (Integer integer : myLinkedList) {
System.out.print(integer + " ");
}
System.out.println();//换行
//判断是否为空
System.out.println("是否为空:" + myLinkedList.isEmpty());
//获取节点数
System.out.println("节点数为:" + myLinkedList.size());
//将索引为0的节点值设为9
myLinkedList.set(0, 9);
//获取索引为0的节点值
System.out.println("索引为0的节点值:" + myLinkedList.get(0));
//删除指定索引的节点
myLinkedList.remove(0);
//遍历节点
System.out.print("删除操作后的链表元素:");
for (Integer integer : myLinkedList) {
System.out.print(integer + " ");
}
}
}
运行结果:
在编写实际代码的时候,我们会发现当我们需要删除尾节点或者在尾节点前插入一个新节点的时候,效率是比较低的,尽管我们有一个专门的引用来保存尾节点,但是尾节点并没有包含前一个节点的任何信息,这使得我们不得不遍历整个节点来寻找尾节点的前一个节点。、
不过,使用双向链表(双链表)可以解决这个问题,我们在每个节点中不仅设置一个指向下一个节点的next链,还设置一个指向上一个节点的pre链,这样我们就可以快速的找到一个节点的上一个节点和下一个节点,可以更快的进行链表的操作。