数据结构(Data Structures)
顾名思义
数组
数组实现采用“基于下标访问”(Index-based access)的模式。
链表
链表实现则是基于“节点”(Node)
或“位置”(Position)
的概念。
所谓链表(Linkedlist),就是按线性次序排列的一组数据节点。
每个节点的 next引用都相当于一个链接或指针,指向另一节点。
借助于这些 next引用,我们可以从一个节点移动至其它节点。
链表的第一个和最后一个节点,分别称作链表的首节点(Head)和末节点(Tail)。
public interface Position {
/**
* 返回存放于该位置的元素
*
* @return 存放于该位置的元素
*/
Object getElem();
/**
* 将给定元素存放至该位置,返回此前存放的元素
*
* @param e
* @return 此前存放的元素
*/
Object setElem(Object e);
}
/**
* <b>Create Date:</b> 2018/6/27<br>
* <b>Email:</b> tongsonloo@gmail.com<br>
* <b>Description:</b>
* 每个节点的 next 引用都相当于一个链接或指针,指向另一节点。借助于这些 next 引用,我们可以从一个节点移动至其它节点。
* 链表的第一个和最后一个节点,分别称作链表的首节点(Head)和末节点(Tail)。
* 末节点的特征是,其 next 引用为空。如此定义的链表,称作单链表(Singly linkedlist)。
* 与数组类似,单链表中的元素也具有一个线性次序⎯⎯若 P 的 next 引用指向 S,则 P 就是 S的直接前驱,而 S 是 P 的直接后继。
* 与数组不同的是,单链表的长度不再固定,而是可以根据实际需要不断变化。
* 如此一来,包含 n 个元素的单链表只需占用 O(n)空间⎯⎯这要比定长数组更为灵活。 <br>
* <p>
* 单链表节点类
*
* @author tongs
*/
public class Node implements Position {
/**
* 数据对象
*/
private Object element;
/**
* 指向后继节点
*/
private Node next;
/**************************** 构造函数 ****************************/
/**
* 指向数据对象、后继节点的引用都置空
*/
public Node() {
this(null, null);
}
/**
* 指定数据对象及后继节点
*
* @param e
* @param n
*/
public Node(Object e, Node n) {
element = e;
next = n;
}
/**************************** Position接口方法 ****************************/
/**
* 返回存放于该位置的元素
*
* @return
*/
@Override
public Object getElem() {
return element;
}
/**
* 将给定元素存放至该位置,返回此前存放的元素
*
* @param e
* @return
*/
@Override
public Object setElem(Object e) {
Object oldElem = element;
element = e;
return oldElem;
}
/**************************** 单链表节点方法 ****************************/
/**
* 取当前节点的后继节点
*
* @return
*/
public Node getNext() {
return next;
}
/**
* 修改当前节点的后继节点
*
* @param newNext
*/
public void setNext(Node newNext) {
next = newNext;
}
}
复制代码
节点的插入与删除
写方法呗。
栈与队列
最简单、最基本、最重要的。
栈
遵循后进先出(Last-in-first-out,LIFO)的原则
public interface Stack {
/**
* @return 返回栈中元素数目
*/
int getSize();
/**
* 判断栈是否为空
*
* @return
*/
boolean isEmpty();
/**
* 取栈顶元素(但不删除)
*
* @return
* @throws ExceptionStackEmpty
*/
Object top() throws ExceptionStackEmpty;
/**
* 入栈
*
* @param ele
*/
void push(Object ele);
/**
* 出栈
*
* @return
* @throws ExceptionStackEmpty
*/
Object pop() throws ExceptionStackEmpty;
}
复制代码
数组实现
public class StackArray implements Stack {
/**
* 数组的默认容量
*/
public static final int CAPACITY = 1024;
/**
* 数组的实际容量
*/
protected int capacity;
/**
* 对象数组
*/
protected Object[] mStack;
/**
* 栈顶元素的位置
*/
protected int top = -1;
/**
* 按默认容量创建栈对象
*/
public StackArray() {
this(CAPACITY);
}
/**
* 按指定容量创建栈对象
*
* @param cap
*/
public StackArray(int cap) {
capacity = cap;
mStack = new Object[capacity];
}
/**
* 获取栈当前的规模
*
* @return
*/
@Override
public int getSize() {
return (top + 1);
}
/**
* 测试栈是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return (top < 0);
}
/**
* 入栈
*
* @param obj
* @throws ExceptionStackFull
*/
@Override
public void push(Object obj) throws ExceptionStackFull {
if (getSize() == capacity) {
throw new ExceptionStackFull("意外:栈溢出");
}
mStack[++top] = obj;
}
/**
* 取栈顶元素
*
* @return
* @throws ExceptionStackEmpty
*/
@Override
public Object top() throws ExceptionStackEmpty {
if (isEmpty()) {
throw new ExceptionStackEmpty("意外:栈空");
}
return mStack[top];
}
/**
* 出栈
*
* @return
* @throws ExceptionStackEmpty
*/
@Override
public Object pop() throws ExceptionStackEmpty {
Object elem;
if (isEmpty()) {
throw new ExceptionStackEmpty("意外:栈空");
}
elem = mStack[top];
mStack[top--] = null;
return elem;
}
}
复制代码
从代码看出来,只是基于数据做一些简单的方法。
链表实现
public class StackList {
/**
* 指向栈顶元素
*/
protected Node top;
/**
* 栈中元素的数目
*/
protected int size;
/**
* 构造方法(空栈)
*/
public StackList() {
top = null;
size = 0;
}
/**
* 查询当前栈的规模
*
* @return
*/
public int getSize() {
return size;
}
/**
* 判断是否栈空
*
* @return
*/
public boolean isEmpty() {
return (top == null) ? true : false;
}
/**
* 压栈
*
* @param elem
*/
public void push(Object elem) {
//创建一个新节点,将其作为首节点插入
Node v = new Node(elem, top);
//更新首节点引用
top = v;
//更新规模记录
size++;
}
/**
* 读取(但不删除)栈顶
*
* @return
* @throws ExceptionStackEmpty
*/
public Object top() throws ExceptionStackEmpty {
if (isEmpty()) {
throw new ExceptionStackEmpty("意外:栈空");
}
return top.getElem();
}
/**
* 弹出栈顶
*
* @return
* @throws ExceptionStackEmpty
*/
public Object pop() throws ExceptionStackEmpty {
if (isEmpty()) {
throw new ExceptionStackEmpty("意外:栈空");
}
Object temp = top.getElem();
//更新首节点引用
top = top.getNext();
//更新规模记录
size--;
return temp;
}
}
复制代码
数组与链表的区别与分析
时间复杂度是一样的。 数组初始化的时候决定了MaxSize,而链表则解决了这个问题。
应用
Java虚拟机中的栈、表达式中的括号匹配、HTML文件中的标志匹配等。
队列
遵循“先进先出”(First-In-First-Out, FIFO)的原则
public interface Queue {
/**
* 返回队列中元素数目
*
* @return
*/
int getSize();
/**
* 判断队列是否为空
*
* @return
*/
boolean isEmpty();
/**
* 取队首元素(但不删除)
*
* @return
* @throws ExceptionQueueEmpty
*/
Object front() throws ExceptionQueueEmpty;
/**
* 入队
*
* @param obj
* @throws ExceptionQueueFull
*/
void enqueue(Object obj) throws ExceptionQueueFull;
/**
* 出队
*
* @return
* @throws ExceptionQueueEmpty
*/
Object dequeue() throws ExceptionQueueEmpty;
/**
* 遍历
*/
void Traversal();
}
复制代码
数组实现
public class QueueArray implements Queue {
/**
* 数组的默认容量
*/
public static final int CAPACITY = 1000;
/**
* 数组的实际容量
*/
protected int capacity;
/**
* 对象数组
*/
protected Object[] mQueue;
/**
* 队首元素的位置
*/
protected int f = 0;
/**
* 队尾元素的位置
*/
protected int r = 0;
/**
* 构造方法(空队列)
*/
public QueueArray() {
this(CAPACITY);
}
/**
* 按指定容量创建对象
*
* @param cap
*/
public QueueArray(int cap) {
capacity = cap;
mQueue = new Object[capacity];
}
@Override
public int getSize() {
return (capacity - f + r) % capacity;
}
@Override
public boolean isEmpty() {
return (f == r);
}
@Override
public Object front() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:队列空");
}
return mQueue[f];
}
@Override
public void enqueue(Object obj) throws ExceptionQueueFull {
if (getSize() == capacity - 1) {
throw new ExceptionQueueFull("Queue overflow.");
}
mQueue[r] = obj;
r = (r + 1) % capacity;
}
@Override
public Object dequeue() throws ExceptionQueueEmpty {
Object elem;
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:队列空");
}
elem = mQueue[f];
mQueue[f] = null;
f = (f + 1) % capacity;
return elem;
}
@Override
public void Traversal() {
for (int i = f; i < r; i++) {
System.out.print(mQueue[i] + " ");
}
System.out.println();
}
}
复制代码
链表实现
public class QueueList {
/**
* 指向表首元素
*/
protected Node head;
/**
* 指向表末元素
*/
protected Node tail;
/**
* 队列中元素的数目
*/
protected int size;
/**
* 构造方法(空队列)
*/
public QueueList() {
head = tail = null;
size = 0;
}
/**
* 查询当前队列的规模
*
* @return
*/
public int getSize() {
return size;
}
/**
* 判断队列是否为空
*
* @return
*/
public boolean isEmpty() {
return (0 == size) ? true : false;
}
/**
* 入队
*
* @param obj
*/
public void enqueue(Object obj) {
Node node = new Node();
node.setElem(obj);
//新节点作为末节点插入
node.setNext(null);
if (0 == size) {
//若此前队列为空,则直接插入
head = node;
} else {
//否则,将新节点接至队列末端
tail.setNext(node);
}
//更新指向末节点引用
tail = node;
//更新规模
size++;
}
/**
* 出队
*
* @return
* @throws ExceptionQueueEmpty
*/
public Object dequeue() throws ExceptionQueueEmpty {
if (0 == size) {
throw new ExceptionQueueEmpty("意外:队列空");
}
Object obj = head.getElem();
head = head.getNext();
size--;
if (0 == size) {
//若队列已空,须将末节点引用置空
tail = null;
}
return obj;
}
/**
* 取(并不删除)队首元素
*
* @return
* @throws ExceptionQueueEmpty
*/
public Object front() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:队列空");
}
return head.getElem();
}
/**
* 遍历(不属于ADT)
*/
public void Traversal() {
Node p = head;
while (null != p) {
System.out.print(p.getElem() + " ");
p = p.getNext();
}
}
}
复制代码
数组与链表的区别与分析
时间复杂度是一样的。 数组初始的时候决定化MaxSize,而链表则解决了这个问题。
应用
循环分配器
双端队列
双端队列(Double-ended queue),简称为Deque。顾名思义,也就是前端与后端都支持插入和删除操作的队列。
public interface Deque {
/**
* 返回队列中元素数目
*
* @return
*/
int getSize();
/**
* 判断队列是否为空
*
* @return
*/
boolean isEmpty();
/**
* 取首元素
* (但不删除)
*
* @return
* @throws ExceptionQueueEmpty
*/
Object first() throws ExceptionQueueEmpty;
/**
* 取末元素
* (但不删除)
*
* @return
* @throws ExceptionQueueEmpty
*/
Object last() throws ExceptionQueueEmpty;
/**
* 将新元素作为首元素插入
*
* @param obj
*/
void insertFirst(Object obj);
/**
* 将新元素作为末元素插入
*
* @param obj
*/
void insertLast(Object obj);
/**
* 删除首元素
*
* @return
* @throws ExceptionQueueEmpty
*/
Object removeFirst() throws ExceptionQueueEmpty;
/**
* 删除末元素
*
* @return
* @throws ExceptionQueueEmpty
*/
Object removeLast() throws ExceptionQueueEmpty;
/**
* 遍历
*/
void Traversal();
}
public class DLNode implements Position {
/**
* 数据对象
*/
private Object element;
/**
* 指向前驱节点
*/
private DLNode prev;
/**
* 指向后继节点
*/
private DLNode next;
/**************************** 构造函数 ****************************/
public DLNode() {
this(null, null, null);
}
/**
* 注意三个参数的次序:数据对象、前驱节点、后继节点
*
* @param e 数据对象
* @param p 前驱节点
* @param n 后继节点
*/
public DLNode(Object e, DLNode p, DLNode n) {
element = e;
prev = p;
next = n;
}
/**************************** Position接口方法 ****************************/
/**
* 返回存放于该位置的元素
*
* @return
*/
@Override
public Object getElem() {
return element;
}
/**
* 将给定元素存放至该位置,返回此前存放的元素
*
* @param e
* @return
*/
@Override
public Object setElem(Object e) {
Object oldElem = element;
element = e;
return oldElem;
}
/**************************** 双向链表节点方法 ****************************/
/**
* 找到后继位置
*
* @return
*/
public DLNode getNext() {
return next;
}
/**
* 找到前驱位置
*
* @return
*/
public DLNode getPrev() {
return prev;
}
/**
* 修改后继位置
*
* @param newNext
*/
public void setNext(DLNode newNext) {
next = newNext;
}
/**
* 修改前驱位置
*
* @param newPrev
*/
public void setPrev(DLNode newPrev) {
prev = newPrev;
}
}
public class DequeDLNode implements Deque {
/**
* 指向头节点(哨兵)
*/
protected DLNode header;
/**
* 指向尾节点(哨兵)
*/
protected DLNode trailer;
/**
* 队列中元素的数目
*/
protected int size;
/**
* 构造函数
*/
public DequeDLNode() {
header = new DLNode();
trailer = new DLNode();
header.setNext(trailer);
trailer.setPrev(header);
size = 0;
}
/**
* 返回队列中元素数目
*
* @return
*/
@Override
public int getSize() {
return size;
}
/**
* 判断队列是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return (0 == size) ? true : false;
}
/**
* 取首元素(但不删除)
*
* @return
* @throws ExceptionQueueEmpty
*/
@Override
public Object first() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:双端队列为空");
}
return header.getNext().getElem();
}
/**
* 取末元素(但不删除)
*
* @return
* @throws ExceptionQueueEmpty
*/
@Override
public Object last() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:双端队列为空");
}
return trailer.getPrev().getElem();
}
/**
* 在队列前端插入新节点
*
* @param obj
*/
@Override
public void insertFirst(Object obj) {
DLNode second = header.getNext();
DLNode first = new DLNode(obj, header, second);
second.setPrev(first);
header.setNext(first);
size++;
}
/**
* 在队列后端插入新节点
*
* @param obj
*/
@Override
public void insertLast(Object obj) {
DLNode second = trailer.getPrev();
DLNode first = new DLNode(obj, second, trailer);
second.setNext(first);
trailer.setPrev(first);
size++;
}
/**
* 删除首节点
*
* @return
* @throws ExceptionQueueEmpty
*/
@Override
public Object removeFirst() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:双端队列为空");
}
DLNode first = header.getNext();
DLNode second = first.getNext();
Object obj = first.getElem();
header.setNext(second);
second.setPrev(header);
size--;
return (obj);
}
/**
* 删除末节点
*
* @return
* @throws ExceptionQueueEmpty
*/
@Override
public Object removeLast() throws ExceptionQueueEmpty {
if (isEmpty()) {
throw new ExceptionQueueEmpty("意外:双端队列为空");
}
DLNode first = trailer.getPrev();
DLNode second = first.getPrev();
Object obj = first.getElem();
trailer.setPrev(second);
second.setNext(trailer);
size--;
return (obj);
}
/**
* 遍历
*/
@Override
public void Traversal() {
DLNode p = header.getNext();
while (p != trailer) {
System.out.print(p.getElem() + " ");
p = p.getNext();
}
System.out.println();
}
}
复制代码
向量
对数组结构进行抽象与扩展之后,就可以得到向量结构,因此向量也称作数组列表(Array list)。
向量提供一些访问方法,使得我们可以通过下标直接访问序列中的元素,也可以将指定下标处的元素删除,或将新元素插入至指定下标。
为了与通常数组结构的下标(Index)概念区分开来,我们通 常将序列的下标称为秩(Rank)。
public interface Vector {
/**
* 返回向量中元素数目
*
* @return
*/
int getSize();
/**
* 判断向量是否为空
*
* @return
*/
boolean isEmpty();
/**
* 取秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
Object getAtRank(int r) throws ExceptionBoundaryViolation;
/**
* 将秩为r的元素替换为obj
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation;
/**
* 插入obj,作为秩为r的元素;返回该元素
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation;
/**
* 删除秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
Object removeAtRank(int r) throws ExceptionBoundaryViolation;
}
复制代码
基于数组的简单实现
public class VectorArray implements Vector {
/**
* 数组的容量
*/
private final int N = 1024;
/**
* 向量的实际规模
*/
private int n = 0;
/**
* 对象数组
*/
private Object[] A;
/**
* 构造函数
*/
public VectorArray() {
A = new Object[N];
n = 0;
}
/**
* 返回向量中元素数目
*
* @return
*/
@Override
public int getSize() {
return n;
}
/**
* 判断向量是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return (0 == n) ? true : false;
}
/**
* 取秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object getAtRank(int r) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
return A[r];
}
/**
* 将秩为r的元素替换为obj
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
Object bak = A[r];
A[r] = obj;
return bak;
}
/**
* 插入obj,作为秩为r的元素;返回该元素
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
if (0 > r || r > n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
if (n >= N) {
throw new ExceptionBoundaryViolation("意外:数组溢出");
}
for (int i = n; i > r; i--) {
//后续元素顺次后移
A[i] = A[i - 1];
}
//插入
A[r] = obj;
//更新当前规模
n++;
return obj;
}
/**
* 删除秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
Object bak = A[r];
//后续元素顺次前移
for (int i = r; i < n; i++) {
A[i] = A[i + 1];
}
//更新当前规模
n--;
return bak;
}
}
复制代码
基于可扩充数组的实现
public class VectorExtArray implements Vector {
/**
* 数组的容量,可不断增加
*/
private int N = 8;
/**
* 向量的实际规模
*/
private int n;
/**
* 对象数组
*/
private Object A[];
/**
* 构造函数
*/
public VectorExtArray() {
A = new Object[N];
n = 0;
}
/**
* 返回向量中元素数目
*
* @return
*/
@Override
public int getSize() {
return n;
}
/**
* 判断向量是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return (0 == n) ? true : false;
}
/**
* 取秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object getAtRank(int r) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
return A[r];
}
/**
* 将秩为r的元素替换为obj
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
Object bak = A[r];
A[r] = obj;
return bak;
}
/**
* 插入obj,作为秩为r的元素;并返回该元素
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
if (0 > r || r > n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
if (N <= n) {
//空间溢出的处理
N *= 2;
//开辟一个容量加倍的数组
Object B[] = new Object[N];
for (int i = 0; i < n; i++) {
//A[]中内容复制至B[]
B[i] = A[i];
}
//用B替换A(原A[]将被自动回收)
A = B;
}
for (int i = n; i > r; i--) {
//后续元素顺次后移
A[i] = A[i - 1];
}
//插入
A[r] = obj;
//更新当前规模
n++;
return obj;
}
/**
* 删除秩为r的元素
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
if (0 > r || r >= n) {
throw new ExceptionBoundaryViolation("意外:秩越界");
}
Object bak = A[r];
for (int i = r; i < n - 1; i++) {
//后续元素顺次前移
A[i] = A[i + 1];
}
//更新当前规模
n--;
return bak;
}
}
复制代码
性能分析
insertAtRank()、removeAtRank()方法都需要耗费O(n)时间。其它那些基于位置的操作则只需要 O(1)的时间。
应用
java.util.ArrayList 类和 java.util.Vector 类
列表
与向量相对应地,列表ADT则是对链表结构的抽象。列表提供的访问、更新方法,按照面向对象的规范对列表的节点对象进行了封装,称作位置(Position)。
public interface List {
/**
* 查询列表当前的规模
*
* @return
*/
int getSize();
/**
* 判断列表是否为空
*
* @return
*/
boolean isEmpty();
/**
* 返回第一个元素(的位置)
*
* @return
*/
Position first();
/**
* 返回最后一个元素(的位置)
*
* @return
*/
Position last();
/**
* 返回紧接给定位置之后的元素(的位置)
*
* @param p
* @return
* @throws ExceptionPositionInvalid
* @throws ExceptionBoundaryViolation
*/
Position getNext(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation;
/**
* 返回紧靠给定位置之前的元素(的位置)
*
* @param p
* @return
* @throws ExceptionPositionInvalid
* @throws ExceptionBoundaryViolation
*/
Position getPrev(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation;
/**
* 将e作为第一个元素插入列表
*
* @param e
* @return
*/
Position insertFirst(Object e);
/**
* 将e作为最后一个元素插入列表
*
* @param e
* @return
*/
Position insertLast(Object e);
/**
* 将e插入至紧接给定位置之后的位置
*
* @param p
* @param e
* @return
* @throws ExceptionPositionInvalid
*/
Position insertAfter(Position p, Object e) throws ExceptionPositionInvalid;
/**
* 将e插入至紧靠给定位置之前的位置
*
* @param p
* @param e
* @return
* @throws ExceptionPositionInvalid
*/
Position insertBefore(Position p, Object e) throws ExceptionPositionInvalid;
/**
* 删除给定位置处的元素,并返回之
*
* @param p
* @return
* @throws ExceptionPositionInvalid
*/
Object remove(Position p) throws ExceptionPositionInvalid;
/**
* 删除首元素,并返回之
*
* @return
*/
Object removeFirst();
/**
* 删除末元素,并返回之
*
* @return
*/
Object removeLast();
/**
* 将处于给定位置的元素替换为新元素,并返回被替换的元素
*
* @param p
* @param e
* @return
* @throws ExceptionPositionInvalid
*/
Object replace(Position p, Object e) throws ExceptionPositionInvalid;
/**
* 位置迭代器
*
* @return
*/
Iterator positions();
/**
* 元素迭代器
*
* @return
*/
Iterator elements();
}
复制代码
基于双向链表实现的列表
public class ListDLNode implements List {
/**
* 列表的实际规模
*/
protected int numElem;
/**
* 哨兵:首节点+末节点
*/
protected DLNode header, trailer;
/**
* 构造函数
*/
public ListDLNode() {
//空表
numElem = 0;
//首节点
header = new DLNode(null, null, null);
//末节点
trailer = new DLNode(null, header, null);
//首、末节点相互链接
header.setNext(trailer);
}
/**************************** 辅助方法 ****************************/
/**
* 检查给定位置在列表中是否合法,若是,则将其转换为*DLNode
*
* @param p
* @return
* @throws ExceptionPositionInvalid
*/
protected DLNode checkPosition(Position p) throws ExceptionPositionInvalid {
if (null == p) {
throw new ExceptionPositionInvalid("意外:传递给List_DLNode的位置是null");
}
if (header == p) {
throw new ExceptionPositionInvalid("意外:头节点哨兵位置非法");
}
if (trailer == p) {
throw new ExceptionPositionInvalid("意外:尾结点哨兵位置非法");
}
DLNode temp = (DLNode) p;
return temp;
}
/**************************** ADT方法 ****************************/
/**
* 查询列表当前的规模
*/
@Override
public int getSize() {
return numElem;
}
/**
* 判断列表是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return (numElem == 0);
}
/**
* 返回第一个元素(的位置)
*
* @return
* @throws ExceptionListEmpty
*/
@Override
public Position first() throws ExceptionListEmpty {
if (isEmpty()) {
throw new ExceptionListEmpty("意外:列表空");
}
return header.getNext();
}
/**
* 返回最后一个元素(的位置)
*
* @return
* @throws ExceptionListEmpty
*/
@Override
public Position last() throws ExceptionListEmpty {
if (isEmpty()) {
throw new ExceptionListEmpty("意外:列表空");
}
return trailer.getPrev();
}
/**
* 返回紧靠给定位置之前的元素(的位置)
*
* @param p
* @return
* @throws ExceptionPositionInvalid
* @throws ExceptionBoundaryViolation
*/
@Override
public Position getPrev(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation {
DLNode v = checkPosition(p);
DLNode prev = v.getPrev();
if (prev == header) {
throw new ExceptionBoundaryViolation("意外:企图越过列表前端");
}
return prev;
}
/**
* 返回紧接给定位置之后的元素(的位置)
*
* @param p
* @return
* @throws ExceptionPositionInvalid
* @throws ExceptionBoundaryViolation
*/
@Override
public Position getNext(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation {
DLNode v = checkPosition(p);
DLNode next = v.getNext();
if (next == trailer) {
throw new ExceptionBoundaryViolation("意外:企图越过列表后端");
}
return next;
}
/**
* 将e插入至紧靠给定位置之前的位置
*
* @param p
* @param element
* @return
* @throws ExceptionPositionInvalid
*/
@Override
public Position insertBefore(Position p, Object element) throws ExceptionPositionInvalid {
DLNode v = checkPosition(p);
numElem++;
DLNode newNode = new DLNode(element, v.getPrev(), v);
v.getPrev().setNext(newNode);
v.setPrev(newNode);
return newNode;
}
/**
* 将e插入至紧接给定位置之后的位置
*
* @param p
* @param element
* @return
* @throws ExceptionPositionInvalid
*/
@Override
public Position insertAfter(Position p, Object element) throws ExceptionPositionInvalid {
DLNode v = checkPosition(p);
numElem++;
DLNode newNode = new DLNode(element, v, v.getNext());
v.getNext().setPrev(newNode);
v.setNext(newNode);
return newNode;
}
/**
* 将e作为第一个元素插入列表
*
* @param e
* @return
*/
@Override
public Position insertFirst(Object e) {
numElem++;
DLNode newNode = new DLNode(e, header, header.getNext());
header.getNext().setPrev(newNode);
header.setNext(newNode);
return newNode;
}
/**
* 将e作为最后一个元素插入列表
*
* @param e
* @return
*/
@Override
public Position insertLast(Object e) {
numElem++;
DLNode newNode = new DLNode(e, trailer.getPrev(), trailer);
if (null == trailer.getPrev()) {
System.out.println("!!!Prev of trailer is NULL!!!");
}
trailer.getPrev().setNext(newNode);
trailer.setPrev(newNode);
return newNode;
}
/**
* 删除给定位置处的元素,并返回之
*
* @param p
* @return
* @throws ExceptionPositionInvalid
*/
@Override
public Object remove(Position p) throws ExceptionPositionInvalid {
DLNode v = checkPosition(p);
numElem--;
DLNode vPrev = v.getPrev();
DLNode vNext = v.getNext();
vPrev.setNext(vNext);
vNext.setPrev(vPrev);
Object vElem = v.getElem();
//将该位置(节点)从列表中摘出,以便系统回收其占用的空间
v.setNext(null);
v.setPrev(null);
return vElem;
}
/**
* 删除首元素,并返回之
*
* @return
*/
@Override
public Object removeFirst() {
return remove(header.getNext());
}
/**
* 删除末元素,并返回之
*
* @return
*/
@Override
public Object removeLast() {
return remove(trailer.getPrev());
}
/**
* 将处于给定位置的元素替换为新元素,并返回被替换的元素
*
* @param p
* @param obj
* @return
* @throws ExceptionPositionInvalid
*/
@Override
public Object replace(Position p, Object obj) throws ExceptionPositionInvalid {
DLNode v = checkPosition(p);
Object oldElem = v.getElem();
v.setElem(obj);
return oldElem;
}
/**
* 位置迭代器
*
* @return
*/
@Override
public Iterator positions() {
return new IteratorPosition(this);
}
/**
* 元素迭代器
*
* @return
*/
@Override
public Iterator elements() {
return new IteratorElement(this);
}
}
复制代码
性能分析
从方法可以看出O(1)
应用
序列
public interface Sequence extends Vector, List {
/**
* 若0 <= r < getSize(),则返回秩为r的元素所在的位置;否则,报错
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
Position rank2Pos(int r) throws ExceptionBoundaryViolation;
/**
* 若p是序列中的合法位置,则返回存放于p处的元素的秩;否则,报错
*
* @param p
* @return
* @throws ExceptionPositionInvalid
*/
int pos2Rank(Position p) throws ExceptionPositionInvalid;
}
复制代码
基于双向链表实现序列
实现序列最自然、最直接的方式,就是利用双向链表。
public class SequenceDLNode extends ListDLNode implements Sequence {
/**
* 检查秩r是否在[0, n)之间
*/
protected void checkRank(int r, int n) throws ExceptionBoundaryViolation {
if (r < 0 || r >= n) {
throw new ExceptionBoundaryViolation("意外:非法的秩" + r + ",应该属于[0, " + n + ")");
}
}
/**
* 若0 <= r < getSize(),则返回秩为r的元素所在的位置;否则,报错--O(n)
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Position rank2Pos(int r) throws ExceptionBoundaryViolation {
DLNode node;
checkRank(r, getSize());
if (r <= getSize() / 2) {
//若秩较小,则
//从前端开始
node = header.getNext();
for (int i = 0; i < r; i++) {
//逐一扫描
node = node.getNext();
}
} else {
//若秩较大,则
//从后端开始
node = trailer.getPrev();
for (int i = 1; i < getSize() - r; i++) {
//逐一扫描
node = node.getPrev();
}
}
return node;
}
/**
* 若p是序列中的合法位置,则返回存放于p处的元素的秩;否则,报错--O(n)
*
* @param p
* @return
* @throws ExceptionPositionInvalid
*/
@Override
public int pos2Rank(Position p) throws ExceptionPositionInvalid {
DLNode node = header.getNext();
int r = 0;
while (node != trailer) {
if (node == p) {
return (r);
}
node = node.getNext();
r++;
}
throw new ExceptionPositionInvalid("意外:作为参数的位置不属于序列");
}
/**
* 取秩为r的元素--O(n)
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object getAtRank(int r) throws ExceptionBoundaryViolation {
checkRank(r, getSize());
return rank2Pos(r).getElem();
}
/**
* 将秩为r的元素替换为obj--O(n)
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
checkRank(r, getSize());
return replace(rank2Pos(r), obj);
}
/**
* 插入obj,作为秩为r的元素--O(n);返回该元素
*
* @param r
* @param obj
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
checkRank(r, getSize() + 1);
if (getSize() == r) {
insertLast(obj);
} else {
insertBefore(rank2Pos(r), obj);
}
return obj;
}
/**
* 删除秩为r的元素--O(n)
*
* @param r
* @return
* @throws ExceptionBoundaryViolation
*/
@Override
public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
checkRank(r, getSize());
return remove(rank2Pos(r));
}
}
复制代码
基于数组实现序列
采用这种数据结构,在插入或删除操作之后,我们只需扫描一遍数组,即可找到需要修正秩的那些位置,并将其秩加一。
跟Vector的数组实现类似吧。不写了哦。
性能分析
insertFirst()、insertBefore()、insertAfter()和 remove()方法都需要耗费O(n)时间。其它那些基于位置的操作则只需要 O(1)的时间。
应用
迭代器
在对向量、列表和序列进行处理(比如,查找某一特定的元素)时,一种典型的操作就是依次访问或修改其中的各个元素。迭代器是软件设计的一种模式,它是对“逐一访问所有元素”这类操作的抽象。
实现
public interface Iterator {
/**
* 检查迭代器中是否还有剩余的元素
*
* @return
*/
boolean hasNext();
/**
* 返回迭代器中的下一元素
*
* @return
*/
Object getNext();
}
复制代码
基于列表实现的位置迭代器
public class IteratorPosition implements Iterator {
/**
* 列表
*/
private List list;
/**
* 当前(下一个)位置
*/
private Position nextPosition;
/**
* 默认构造方法
*/
public IteratorPosition() {
list = null;
}
/**
* 构造方法
*
* @param L
*/
public IteratorPosition(List L) {
list = L;
if (list.isEmpty()) {
//若列表为空,则//当前位置置空
nextPosition = null;
} else {
//否则//从第一个位置开始
nextPosition = list.first();
}
}
/**
* 检查迭代器中是否还有剩余的位置
*
* @return
*/
@Override
public boolean hasNext() {
return (nextPosition != null);
}
/**
* 返回迭代器中的下一位置
*
* @return
* @throws ExceptionNoSuchElement
*/
@Override
public Object getNext() throws ExceptionNoSuchElement {
if (!hasNext()) {
throw new ExceptionNoSuchElement("意外:没有下一位置");
}
Position currentPosition = nextPosition;
if (currentPosition == list.last()) {
//若已到达尾位置,则
//不再有下一个位置
nextPosition = null;
} else {
//否则
//转向下一位置
nextPosition = list.getNext(currentPosition);
}
return currentPosition;
}
}
复制代码
基于列表实现的元素迭代器
public class IteratorElement implements Iterator {
/**
* 列表
*/
private List list;
/**
* 当前(下一个)元素的位置
*/
private Position nextPosition;
/**
* 默认构造方法
*/
public IteratorElement() {
list = null;
}
/**
* 构造方法
*
* @param L
*/
public IteratorElement(List L) {
list = L;
//若列表为空,则
if (list.isEmpty()) {
//当前元素置空
nextPosition = null;
} else {//否则
//从第一个元素开始
nextPosition = list.first();
}
}
/**
* 检查迭代器中是否还有剩余的元素
*
* @return
*/
@Override
public boolean hasNext() {
return (null != nextPosition);
}
/**
* 返回迭代器中的下一元素
*
* @return
* @throws ExceptionNoSuchElement
*/
@Override
public Object getNext() throws ExceptionNoSuchElement {
if (!hasNext()) {
throw new ExceptionNoSuchElement("意外:没有下一元素");
}
Position currentPosition = nextPosition;
//若已到达尾元素,则
if (currentPosition == list.last()) {
//不再有下一元素
nextPosition = null;
} else {//否则
//转向下一元素
nextPosition = list.getNext(currentPosition);
}
return currentPosition.getElem();
}
}
复制代码
应用
迭代器有什么优点呢? java.util.Iterator的大多数实现都提供了故障快速修复(Fail-fast)的机制 ⎯⎯⎯⎯⎯在利用迭代器遍历某一容器的过程中,一旦发现该容器的内容有所改变,迭代器就会抛出ConcurrentModificationException意外错并立刻退出。
在 Java 中,可以通过多个迭代器同时对同一链表进行遍历。不过,正如上面所提到的,一旦其中某个迭代器修改了链表的内容,所有的迭代器都会成为非法的。
总结
这文章算是数据结构的入门吧。
很基础很简单。
java.util.List 接口所提供的功能, java.util 中的 ArrayList 类
和 Vector 类
都是基于数组
实现的,而LinkedList类
则是基于链表
实现的。
该基于数组
实现还是基于链表
实现的,这两种实现各有利弊,在解决实际问题时,我们需要在二者之间做一权衡。
计算机的数据结构差不多都是这样子的,无论什么语言。
下一篇打算加深一下,讲树、串、图。