資料結構(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類
則是基於連結串列
實現的。
該基於陣列
實現還是基於連結串列
實現的,這兩種實現各有利弊,在解決實際問題時,我們需要在二者之間做一權衡。
計算機的資料結構差不多都是這樣子的,無論什麼語言。
下一篇打算加深一下,講樹、串、圖。