一.前提引入
之前我们已经使用顺序存储结构实现了线性表,我们会发现虽然顺序表的查询很快,时间复杂度为
O(1),
但是增删的效率是比较低的,因为每一次增删操作都伴随着大量的数据元素移动。这个问题有没有解决方案呢?有,我们可以使用另外一种存储结构实现线性表,链式存储结构。
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
那我们如何使用链表呢?按照面向对象的思想,我们可以设计一个类,来描述结点这个事物,用一个属性描述这个
结点存储的元素,用来另外一个属性描述这个结点的下一个结点。
那我们如何使用链表呢?按照面向对象的思想,我们可以设计一个类,来描述结点这个事物,用一个属性描述这个结点存储的元素,用来另外一个属性描述这个结点的下一个结点。
结点API设计:
/**
* 节点类
* @param <T>
*/
public class Node<T> {
//存储元素
public T item;
//指向下一个节点
public Node next;
//构造方法;
public Node(T item,Node next){
//说明该节点存储的内容
this.item=item;
//说明该节点指向下一个节点
this.next=next;
}
}
生成链表:
public static void main(String[] args) {
//构建结点
Node<String> n1=new Node<>("张三",null);
Node<String> n2=new Node<>("李四",null);
Node<String> n3=new Node<>("王五",null);
Node<String> n4=new Node<>("赵六",null);
Node<String> n5=new Node<>("田七",null);
//生成链表
n1.next=n2;
n2.next=n3;
n3.next=n4;
n4.next=n5;
}
以上便介绍了链表在Java中的基本实现,下面将对几种形式的链表分别进行介绍。
二.单链表
1.基本介绍
单向链表是链表的一种,它由多个结点组成,每个结点都由一个
数据域
和一个
指针域
组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
2. 单链表API设计
public class LinkList<T> implements Iterable<T> {
//记录头结点
private Node head;
//记录链表的长度
private int N;
/**
* 初始化头节点
*/
public LinkList() {
//头结点不存储具体的数据
this.head = new Node(null, null);
//初始化长度为0
this.N = 0;
}
/**
* 清空链表
*/
public void clear() {
head.next = null;
head.item = null;
this.N = 0;
}
/**
* 获取链表的长度
*
* @return
*/
public int length() {
return N;
}
/**
* 判断链表是否为空
*
* @return
*/
public boolean isEmpty() {
//返回值为布尔类型,所以可以直接返回判断结果
return N == 0;
}
/**
* 获取指定位置i出的元素
*
* @param i
* @return
*/
public T get(int i) {
//判断i是否合法
if (i < 0 || i >= N) {
throw new RuntimeException("当前元素不存在");
}
//定义一个指针,指向头结点
Node n = head.next;
//遍历链表,找到指定位置的元素
for (int index = 0; index < i; index++) {
//让指针指向下一个结点,一直向后,直到指向i的位置;
n = n.next;
}
//返回指定位置的元素
return (T) n.item;
}
/**
* 向链表中插入元素t
*
* @param t
*/
public void insert(T t) {
//判断链表是否为空
if (N == 0) {
//如果链表为空,直接把t放到头结点的下一个结点处
head.next = new Node(t, null);
} else {
//如果链表不为空,找到最后一个结点
Node n = head;
while (n.next != null) {
n = n.next;
}
//把t放到最后一个结点的下一个结点处
n.next = new Node(t, null);
}
//元素个数+1
N++;
}
/**
* 向指定位置i处,添加元素t
* 索引从0开始,(0,1,2,3,4),所以要找到i节点,则要找到i-1索引;
*/
public void insert(int i, T t) {
//判断i是否合法
if (i < 0 || i > N) {
throw new RuntimeException("插入位置不合法");
}
//找到i位置的前一个结点
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//位置i的结点
Node curr = pre.next;
//构建新的结点,让新结点指向位置i的结点
Node newNode = new Node(t, curr);
//让i-1位置的结点指向新结点
pre.next = newNode;
//元素个数+1
N++;
}
/**
* 删除指定位置i处的元素,并返回被删除的元素
* @param i
*/
public T remove(int i) {
//判断i是否合法
if (i < 0 || i > N) {
throw new RuntimeException("插入位置不合法");
}
//找到i位置前一个结点
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//让i的前一个结点直接指向i的下一个结点;
pre.next = pre.next.next;
//元素个数-1
N--;
//返回被删除的元素
return (T) pre.next.item;
}
/**
* 查找元素t在链表中第一次出现的位置
* @param t
* @return
*/
public int indexOf(T t) {
//定义一个指针,指向头结点
Node n = head.next;
//遍历链表,找到元素t
for (int i = 0; n.next != null; i++) {
if (n.item.equals(t)) {
return i;
}
n = n.next;
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator<T>{
private Node n;
public LIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
n = n.next;
return (T) n.item;
}
}
}
测试代码
//测试代码
public class test {
public static void main(String[] args) throws Exception {
//创建顺序表对象
LinkList<String> list = new LinkList<>();
//测试插入
list.insert(0,"张三");
list.insert(1,"李四");
list.insert(2,"王五");
list.insert(3,"赵六");
//测试length方法
for (String s : list) {
System.out.println(s);
}
System.out.println(list.length());
//测试get方法
System.out.println(list.get(2));
System.out.println("------------------------");
//测试remove方法
String remove = list.remove(1);
System.out.println(remove);
System.out.println(list.length());
System.out.println("------------------------");
for (String s : list) {
System.out.println(s);
}
}
}