列表是常用的数据结构, 也是队列和栈的基础。本文将介绍两种列表的简单实现:数组列表和链表。
列表接口列表接口定义了常用的列表方法。列表接口Github源代码
public interface IList<T> {
/**
* 插入
* @param i 位置
* @param data 要插入的数据
*/
void insert(int i, T data);
/**
* 获取
* @param i 位置
* @return i 位置上的元素
*/
T get(int i);
/**
* 删除
* @param i 位置
* @return 被删除的元素
*/
T delete(int i);
/**
* 列表内元素的数量
* @return 数量
*/
int size();
/**
* 列表是否为空
* @return
*/
boolean isEmpty();
}
数组列表
数组列表代码如下。需要注意以下几点:
- 需要先进行下标检查及数组是否已空检查
- 使用System.arraycopy,提高性能。
- 未实现自动扩容,列表可能会满,构造函数需要传入列表长度最大值。
数组列表Github源代码
数组列表测试Github源代码(测试用例不完全覆盖)
public class MyArrayList<T> implements IList<T> {
private T[] datas;
private int count = 0;
private int capacity;
public MyArrayList(int capacity) {
datas = (T[]) new Object[capacity];
this.capacity = capacity;
}
@Override
public void insert(int i, T data) {
if (count == capacity)
throw new RuntimeException("列表已满");
if (i < 0 || i > count)
throw new RuntimeException("下标非法");
if (i == count)
datas[count++] = data;
else {
System.arraycopy(datas, i, datas, i + 1, count - i);
datas[i] = data;
count++;
}
}
@Override
public T get(int i) {
if (i < 0 || i >= count)
throw new RuntimeException("下标非法");
return datas[i];
}
@Override
public T delete(int i) {
if (i < 0 || i >= count)
throw new RuntimeException("下标非法");
T tmp = datas[i];
System.arraycopy(datas, i + 1, datas, i, count - i);
count--;
return tmp;
}
@Override
public int size() {
return this.count;
}
@Override
public boolean isEmpty() {
return this.size() == 0;
}
}
链表
链表使用了一个私有的内部类Node。仍然要进行必要的下标检查。使用了一个私有的getNode
方法辅助。
链表Github源代码
链表测试类Github源代码(用例不完全覆盖)
public class MyLinkedList<T> implements IList<T> {
private int count = 0;
private Node head = new Node();
private class Node{
T data;
Node next;
public Node() {
}
Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
@Override
public void insert(int i, T data) {
if (i < 0 || i > this.count)
throw new RuntimeException("下标非法");
Node prev = getNode(i - 1);
prev.next = new Node(data, prev.next);
count++;
}
@Override
public T get(int i) {
if (i < 0 || i >= count)
throw new RuntimeException("下标非法");
return this.getNode(i).data;
}
@Override
public T delete(int i) {
if (i < 0 || i >= count)
throw new RuntimeException("下标非法");
Node prev = getNode(i - 1);
Node node = prev.next;
prev.next = node.next;
count--;
return node.data;
}
@Override
public int size() {
return this.count;
}
@Override
public boolean isEmpty() {
return this.size() == 0;
}
/**
* 得到第i个位置的元素
* 注意k的初始条件和while循环递增
* @param i 若i为-1,返回head
* @return
*/
private Node getNode(int i) {
int k = - 1;
Node res = head;
while (k++ < i)
res = res.next;
return res;
}
}
总结
数组列表的插入、删除的时间复杂度是O(n),查询的时间复杂度是O(1);
链表的插入、删除的时间复杂度是O(1),查询的时间复杂度是O(n);
因此,读多写少用数组列表,写多读少用链表。