单链表
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。对于每个节点来说,除了存储其本身的信息(数据值)之外,还需存储一个指示其直接后继的结点的引用。存储数据元素信息的部分称为数据域,存储直接后继结点引用的部分成为指针域 。
优点——增、删速度快。
缺点——不能随机访问。
链表是一种数据结构,和数组同级。比如,Java中我们使用的ArrayList,其实现原理是数组。而LinkedList的实现原理就是链表了。链表在进行循环遍历时效率不高,但是插入和删除时优势明显。下面对单向链表做一个介绍。
实际上是由节点(Node)组成的,一个链表拥有不定数量的节点。其数据在内存中存储是不连续的,它存储的数据分散在内存中,每个结点只能也只有它能知道下一个结点的存储位置。由N各节点(Node)组成单向链表,每一个Node记录本Node的数据及下一个Node。向外暴露的只有一个头节点(Head),我们对链表的所有操作,都是直接或者间接地通过其头节点来进行的。
/**
* 单链表的实现
*
* @author yp
* @date 2017年5月1日
*/
public class LinkedList<E> {
private Node<E> head = null;// 每个链表都存有一个空值的头结点。
private Node<E> tail = null;// 链表的最后一个节点,尾节点
private int size = 0; // 当前链表中的节点数目。
/**
* 建立一个空链表(建立其头节点)
*/
public LinkedList() {
head = new Node<E>();
tail = head;
}
/**
* 在链表尾部插入数据
*
* @param e 待插入的数据
*
* @return
*/
public boolean add(E e) {
Node<E> node = new Node<E>(e);// 将要插入的值封装成一个节点
tail.setNext(node);
tail = tail.getNext();
++size;// 链表大小增1
return true;
}
/**
* 插入元素
*
* @param index
* 待插入的位置
* @param e
* 待插入的元素
* @return
*/
public boolean insert(int index, E e) {
validateIndex(index);
Node<E> newNode = new Node<E>(e);// 将插入的值封装一个节点
Node<E> proNode = getNode(index-1);
newNode.setNext(proNode.getNext());
proNode.setNext(newNode);
++size;
return true;
}
/**
* 获取指定位置的节点值
*
* @param index
* 欲获取值的节点的下标
* @return
*/
public E get(int index) {
validateIndex(index);
Node<E> node = getNode(index);
return node.getValue();
}
/**
* 删除指定位置的节点
*
* @param index
* @return
*/
public boolean delete(int index) {
Node<E> curNode = null;
if (0 == index) { //如果删除的链表头
curNode = head.getNext(); // 获取链表头的节点
Node<E> nextNode = curNode.getNext(); // 链表头的下一个节点
head.setNext(nextNode);// 设置为头节点
} else {
validateIndex(index);
curNode = getNode(index);// 获取待删除节点
Node<E> preNode = getNode(index - 1);// 获取待删除节点的前一个节点
// 将待删除的节点的前一个节点指向待删除的下一个节点。
preNode.setNext(curNode.getNext());
}
curNode.setNext(null);// 将待删除的节点的下一个节点为空。
--size;
return true;
}
/**
* 设置指定位置节点的值
*
* @param index
* 待设置值的节点下标
* @param e
* 设置的值
*/
public void set(int index, E e) {
validateIndex(index);
Node<E> node = getNode(index);
node.setValue(e);
}
/**
* 获取链表的大小
*
* @return
*/
public int size() {
return size;
}
/**
* 获取指定位置的节点
*
* @param i
* 欲获取节点的下标
* @return
*/
private Node<E> getNode(int index) {
validateIndex(index);
// 从头结点开始不断的获取下一个节点, 直到获取到指定的节点
Node<E> node = head;
for (int i = 0; i < index+1; i++) {
node = node.getNext();
}
return node;
}
/**
* 验证下标是否合法
*
* @param index
*/
private void validateIndex(int index) {
if (index < 0 || index > size) {
throw new RuntimeException("无效的下标:" + index);
}
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
Node<E> node = head;
while (node != tail) {
str.append(node.getNext().getValue()+" ");
node = node.getNext();
}
return str.toString();
}
}
/**
* 链表中的节点,data代表节点的值,next是指向下一个节点引用
*
* @author yp
* @date 2017年5月1日
*/
class Node<E> {
private Node<E> next = null;
private E e;// 节点对象,即内容
public Node() {
}
public Node(E e) {
this.e = e;
}
public void setNext(Node<E> next) {
this.next = next;
}
public Node<E> getNext() {
return next;
}
public E getValue() {
return e;
}
public void setValue(E e) {
this.e = e;
}
@Override
public String toString() {
return "" + e;
}
}
测试
public static void main(String[] args) {
LinkedList<String> list=new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.add("h");
list.add("i");
list.add("j");
list.add("k");
System.out.println("初始长度:"+list.size()+"初始为:"+list.toString());
list.insert(1, "ab");
System.out.println("指定位置插入数据之后, 长度为:"+list.size()+"插入之后数组为:"+list.toString());
list.delete(1);
System.out.println("删除指定位置数据之后, 长度为:"+list.size()+"删除之后数组为:"+list.toString());
System.out.println("获取指定位置的值"+list.get(1));
list.set(1, "aaa");
System.out.println("设置指定位置的值之后"+list.toString());
}
}