链表的变形

本文介绍了如何通过在单链表基础上增加尾节点引用和双向链接,提升尾端插入效率,并扩展到循环链表,以便高效实现首尾两端的插入和删除操作。

简单单链表的实现有一个缺点:尾端插入元素操作的效率很低。可以考虑给表对象增加一个尾结点引用域:

通过继承之前的单链表并修改增删操作,简单实现一个双引用域的单链表:

class SingleLinkedListWithTail extends SingleLinkedList {
    Node tail;
    private void checkRange(int i) {
        if(i<0 || i>=size) {
            throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
        }
    }
    public void prepend(int elem) {
        Node newNode = new Node(elem);
        size++;
        if(head==null) {
            head = newNode;
            tail = newNode;
            return;
        }
        newNode.next = head;
        head = newNode;
    }
    public void append(int elem) {
        Node newNode = new Node(elem);
        size++;
        if(head==null) {
            head = newNode;
            tail = newNode;
            return;
        }
        tail.next = newNode;
        tail = newNode;
    }
    public void insert(int elem, int i) {
        if(i==size) {
            append(elem);
            return;
        }
        super.insert(elem, i);
    }
    public void deleteFirst() {
        super.deleteFirst();
        if(head==null) {
            tail = null;
        }
    }
    public void deleteLast() {
        delete(size-1);
    }
    public void delete(int i) {
        checkRange(i);
        if(i==0) {
            deleteFirst();
            return;
        }
        Node currNode = head;
        while(i!=1) {
            currNode = currNode.next;
            i--;
        }
        if(i==size-1) {
            tail = currNode;
        }
        currNode.next = currNode.next.next;
        size--;
    }

}

单链表的另一类常见变形是循环单链表,最后一个结点的引用域不为空(null),而是指向首结点,表对象只需要一个数据域rear来引用尾结点,从而高效实现首尾两端的插入/删除操作

简单实现一个循环单链表,不多赘述

class CircularSingleLinkedList {
    Node rear;
    int size;
    public CircularSingleLinkedList() { }
    public boolean isEmpty() {
        return rear==null;
    }
    public void prepend(int elem) {
        size++;
        Node newNode = new Node(elem);
        if(rear==null) {
            rear = newNode;
            rear.next = rear;
            return;
        }
        newNode.next = rear.next;
        rear.next = newNode;
    }
    public void append(int elem) {
        prepend(elem);
        rear = rear.next;
    }
    public int pop() {
        if(rear==null) {
            throw new IndexOutOfBoundsException("the list is empty");
        }
        size--;
        Node head = rear.next;
        if(head == rear) {
            rear = null;
            return head.elem;
        }
        rear.next = head.next;
        return head.elem;
    }
}

上述变形只能改善尾端插入的效率,如果希望两端的插入和删除都能高效完成,就必须修改结点的设计,增加另一方向的链接:

利用继承关系,简单实现双向链接表结点:

class DoubleLinkedNode extends Node{
    DoubleLinkedNode prev;
    DoubleLinkedNode next;
    public DoubleLinkedNode(int elem) {
        super(elem);
    }
    public DoubleLinkedNode(int elem, DoubleLinkedNode prev, DoubleLinkedNode next) {
        super(elem);
        this.prev = prev;
        this.next = next;
    }
}

为了支持两端的高效操作,可以使用双引用域 

 

继承单链表类,简单实现一个双链表:

class DoubleLinkedList extends SingleLinkedList{
    DoubleLinkedNode head;
    DoubleLinkedNode tail;
    private void insertFromBack(int elem, int i) {
        if(i==0) {
            append(elem);
            return;
        }
        DoubleLinkedNode currNode = tail;
        while(i!=0) {
            currNode = currNode.prev;
            i--;
        }
        DoubleLinkedNode newNode = new DoubleLinkedNode(elem, currNode, currNode.next);
        currNode.next.prev = newNode;
        currNode.next = newNode;
        size++;
    }
    private void insertFromFront(int elem, int i) {
        if(i==0) {
            prepend(elem);
            return;
        }
        DoubleLinkedNode currNode = head;
        while(i!=0) {
            currNode = currNode.next;
            i--;
        }
        DoubleLinkedNode newNode = new DoubleLinkedNode(elem, currNode.prev, currNode);
        currNode.prev.next = newNode;
        currNode.prev = newNode;
        size++;
    }
    private void deleteFromFront(int i) {
        if(i==0) {
            deleteFirst();
            return;
        }
        DoubleLinkedNode currNode = head;
        while(i!=0) {
            currNode = currNode.next;
            i--;
        }
        currNode.prev.next = currNode.next;
        currNode.next.prev = currNode.prev;
        size--;
    }
    private void deleteFromBack(int i) {
        if(i==0) {
            deleteLast();
            return;
        }
        DoubleLinkedNode currNode = tail;
        while(i!=0) {
            currNode = currNode.prev;
            i--;
        }
        currNode.prev.next = currNode.next;
        currNode.next.prev = currNode.prev;
        size--;
    }
    public void prepend(int elem) {
        DoubleLinkedNode newNode = new DoubleLinkedNode(elem, null, head);
        if(head==null) {
            tail = newNode;
        } else {
            head.prev = newNode;
        }
        head = newNode;
        size++;
    }
    public void append(int elem) {
        DoubleLinkedNode newNode = new DoubleLinkedNode(elem, tail, null);
        if(head==null) {
            head = newNode;
        } else {
            tail.next = newNode;
        }
        tail = newNode;
        size++;
    }
    public void insert(int elem, int i) {
        if(i<0 || i>size) {
            throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
        }
        int mid = size>>1;
        if(i<mid) {
            insertFromFront(elem, i);
            return;
        }
        insertFromBack(elem, size-i);
    }

    public void deleteFirst() {
        if(head == null) {
            throw new IndexOutOfBoundsException("the list is empty");
        }
        size--;
        head = head.next;
        if(head!=null) {
            head.prev = null;
            return;
        }
        tail = null;
    }
    public void deleteLast() {
        if(tail==null) {
            throw new IndexOutOfBoundsException("the list is empty");
        }
        size--;
        tail = tail.prev;
        if(tail!=null) {
            tail.next = tail;
            return;
        }
        head = null;
    }
    public void delete(int i) {
        if(i<0 || i>=size) {
            throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
        }
        int mid = size>>1;
        if(i<mid) {
            deleteFromFront(i);
            return;
        }
        deleteFromBack(size-i-1);
    }
    public int get(int i) {
        if(i<0 || i>=size) {
            throw new IndexOutOfBoundsException("Index:"+i+" Size:"+size);
        }
        DoubleLinkedNode currNode = head;
        while(i!=0) {
            currNode = currNode.next;
            i--;
        }
        return currNode.elem;
    }
    public int find(int elem) {
        DoubleLinkedNode currNode = head;
        int i = 0;
        while(currNode!=null) {
            if(currNode.elem==elem) {
                return i;
            }
            i++;
            currNode = currNode.next;
        }
        return -1;
    }
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        DoubleLinkedNode currNode = head;
        while(currNode!=null) {
            sb.append(currNode.elem);
            currNode = currNode.next;
            if(currNode!=null) {
                sb.append(',');
            }
        }
        sb.append(']');
        return sb.toString();
    }
}

双链表可以变形为循环双链表,表尾结点next指向首结点,表首结点prev指向尾结点,与循环单链表需要掌握尾结点tail不同的是,循环双链表无论是掌握首结点head还是尾结点tail,都可以高效实现首尾两端的插入/删除操作,不再赘述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值