Java数据结构(一)双端链表

一、介绍

双端链表需要链表中的每个节点持有一个前置节点和一个后置节点的引用及本身的值,链表中包含一个头节点和一个尾节点,这两个节点仅作标识使用,本身只含有对后置节点或前置节点的引用,不含值。如图所示
这里写图片描述
图中pre代表前置节点,next代表后置节点,val则是本身的值。

插入操作分析

如图所示:
这里写图片描述
做插入操作时,需要把当前节点前置节点指向原节点的前置节点,当前节点的后置节点指向原节点,另外别忘了还要将原节点的前置节点指向当前节点,原节点的前置节点的后置节点指向当前节点(比较绕,多看看图,要做四步操作)

二、代码示例

双端链表中需要一个节点类及一个迭代器,本次两个类都会作为内部类存在。
节点类:

private class Node{
        private Node pre;
        private Node next;
        private T val;
        private Node(T val,Node pre,Node next) {
            this.pre = pre;
            this.next = next;
            this.val = val;
        }
    }

迭代器:

private class MyIterator implements Iterator<T>{
        //当前节点
        private Node current = beginNode;
        //当前节点索引
        private int currentIdx = -1;
        //迭代器总操作次数(后面介绍)
        private int mod = modCount;
        //是否可做删除操作(下面介绍)
        private boolean canRemove = false;
        @Override
        public boolean hasNext() {
            return current.next!=endNode;
        }
        @Override
        public T next() {
            canRemove = true;
            if(!hasNext()||mod!=modCount) {
                throw new RuntimeException();
            }
            current = current.next;
            return current.val;
        }
        public void remove() {
            if(!canRemove) {
                throw new RuntimeException();
            }
            //该类是内部类,MyLinkedList是它的外部类(内部类要用这种方式调用外部类的方法)
            MyLinkedList.this.remove(currentIdx);
            canRemove = false;
            mod++;
        } 

迭代器中有两个标志,一个是是否可删除,一个是总操作次数mod(他的初始值等于创建迭代器时的链表的总操作数)。
删除标志是为了判断当前是否有可删除值,如果当前是头节点或者尾节点或者已经删除过一次,则都会抛出异常。
至于操作次数,是为了防止在迭代过程中用了迭代器以外的方式删除了链表元素或新增链表元素,这会导致数据异常,报错了还可以及时发现,如果没有报错可能会导致想删除当前元素却删除了前一个或后一个元素,错误难以排查。
链表:

public class MyLinkedList<T> {
    private Node beginNode;
    private Node endNode;
    //链表长度,不含头节点和尾节点
    private int size = 0;
    //对链表的add、remove次数
    private int modCount = 0;
    ...此处是属性部分

私有方法:
这里使用私有方法是因为这两个方法返回值是Node,外部并不能获取到,而我们也应该刻意隐藏Node的实现,使用户只需要操作数据,而不关心内部实现。

    //获取指定节点
    private Node getNode(int idx,int low,int upper) {
        if(idx<low||idx>upper) {
            throw new RuntimeException();
        }
        //索引位于节点前半段,从头节点开始遍历
        if(idx<size()/2) {
            Node n = beginNode.next;
            for(int i=0;i<idx;i++) {
                n = n.next;
            }
            return n;
        }else {//索引位于后半段,从尾节点开始遍历
            Node n = endNode;
            for(int i=size();i>idx;i--) {
                n = n.pre;
            }
            return n;
        }
    }
    //在指定节点前插入节点
    private Node addBefore(T val,Node node) {
        Node n = new Node(val, null, null);
        //n的前置节点指向原节点的前置节点(这里就是前文提到的四步操作)
        n.pre = node.pre;
        //n的后置节点指向原节点
        n.next = node;
        //将原节点的前置节点的后置节点指向n
        node.pre.next = n;
        //原节点的前置节点指向n
        node.pre = n;
        size++;
        modCount++;
        return n;
    }

公有方法:

    //向指定位置添加节点
    public T add(int idx,T val) {
        Node node = getNode(idx,0,size());
        return addBefore(val, node).val;
    }
    //移除指定位置节点
    public T remove(int idx) {
        if(idx<0||idx>size()-1) {
            throw new RuntimeException();
        }
        Node node = getNode(idx,0,size()-1);
        //前置节点的后置节点指向原节点的后置节点
        node.pre.next = node.next;
        //后置节点的前置节点指向原节点的前置节点
        node.next.pre = node.pre;
        size--;
        modCount++;
        return node.val;
    }
    //获取指定位置的值
    public T get(int idx) {
        return getNode(idx,0,size()-1).val;
    }
    //设置指定位置值
    public void set(int idx,T val) {
        getNode(idx,0,size()-1).val = val;
    }
    //返回元素个数
    public int size() {
        return size;
    }
    //返回迭代器
    public Iterator<T> iterator(){
        return new MyIterator();
    }
    //构造方法
    public MyLinkedList() {
        beginNode = new Node(null,null,endNode);
        endNode = new Node(null,beginNode,null);
    }
    //节点类
    private class Node{
        ...
    }
    //迭代器
    private class MyIterator implements Iterator<T>{ 
        .....
    }
}

到此就是全部代码啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于双向链表实现双端队列结构 */ package dsa; public class Deque_DLNode implements Deque { protected DLNode header;//指向头节点(哨兵) protected DLNode trailer;//指向尾节点(哨兵) protected int size;//队列中元素的数目 //构造函数 public Deque_DLNode() { header = new DLNode(); trailer = new DLNode(); header.setNext(trailer); trailer.setPrev(header); size = 0; } //返回队列中元素数目 public int getSize() { return size; } //判断队列是否为空 public boolean isEmpty() { return (0 == size) ? true : false; } //取首元素(但不删除) public Object first() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return header.getNext().getElem(); } //取末元素(但不删除) public Object last() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return trailer.getPrev().getElem(); } //在队列前端插入新节点 public void insertFirst(Object obj) { DLNode second = header.getNext(); DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = trailer.getPrev(); DLNode first = new DLNode(obj, second, trailer); second.setNext(first); trailer.setPrev(first); size++; } //删除首节点 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); } //删除末节点 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); } //遍历 public void Traversal() { DLNode p = header.getNext(); while (p != trailer) { System.out.print(p.getElem()+" "); p = p.getNex

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值