Java数据结构与排序算法 (一)

本文介绍了数据结构的基本概念,包括数组、链表、栈、队列、双端队列、向量和列表等,详细阐述了它们的特点和应用场景。

数据结构(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类则是基于链表实现的。

该基于数组实现还是基于链表实现的,这两种实现各有利弊,在解决实际问题时,我们需要在二者之间做一权衡。

计算机的数据结构差不多都是这样子的,无论什么语言。

下一篇打算加深一下,讲树、串、图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值