Java:算法 - 手动实现双向链表中的增插删改

1. 前言:

先来复习下链表的概念:

物理结构

计算机的物理存储结构有两种:{顺序,链式}

物理存储结构定义存取速率插入删除速率(给定地址)插入删除速率(给定值)空间占用空间利用率
链式数据存储在地址随机的物理存储单元
顺序数据存储在地址连续的物理存储单元

逻辑结构

链表的最大优势就是给定数据地址时,插入删除的速度为O(1), 最大缺点是随机读取和存储速率感人。指针也增加了额外的内外存空间。双向链表对于单向链表添加了pre前驱指针,如果是链表的值是顺序的,大量的随机访问平均只需遍历表的一半。

2. 正题:

手动实现一个双向链表,实现CRUD基本功能
API按照JDK-11里的LinkedList:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedList.html

类图:

在这里插入图片描述

代码:

MyLinkedList的内部成员:

public class MyLinkedList<E>{
	private class Node<E>{
        E item;
        Node<E> pre;
        Node<E> next;

        public Node(E item, Node<E> pre, Node<E> next) {
            this.item = item;
            this.pre = pre;
            this.next = next;
        }
        
    private int size;
    private Node<E> head;
    private Node<E> tail;
}

MyLinkedList构造方法:

	public MyLinkedList(){}
	
	/*
     * construct a new MyLinkedList that contains an element of E
     * @param element
     * the first element as well as the head node and tail node of the list
     */
	public MyLinkedList(E element){
        this.size = 1;
        this.head = new Node<E>(element,null,null);
        this.tail = head;
    }

add(E element):boolean接口实现:

	/*
     * append an element of E to the last of the list
     * @param element
     * the element to be added to the list
     */
	  public boolean add(E element){
        if(size==0){
            this.size = 1;
            this.head = new Node<E>(element,null,null);
            this.tail = head;
            return true;
        }else{
            //generate a new node with pre -> the current tail of the list and next -> null
            Node<E> node = new Node<E>(element, tail,null);
            //the next of current tail -> the new node
            this.tail.next=node;
            //the list set tail as the new node
            this.tail = node;
            this.size++;
            return true;
        }
    }

PS:这里是尾部插入哦!

重写toString:

   @Override
    public String toString() {
        StringBuffer str = new StringBuffer();

        if(this.size>0){
            //append all available nodes to str,starting from the head
            Node<E> iterator = this.head;
            str.append("[ ");
            str.append(iterator.item+" ");
            //append nodes to str until tail being found
            while(iterator.next!=null){
                iterator = iterator.next;
                str.append(iterator.item+" ");
            }
            str.append("]");
            return str.toString();
        }else{
            return "[]";
        }
    }

说明: 这里用StringBuffer将所有Node中的数值一个个存入字符串缓冲区,以便日后显示我们的链表元素

先来测试下add功能

  public static void main(String[] args) {
		var list1 = new MyLinkedList<Integer>();
        System.out.println(list1);
        list1.add(1);
        list1.add(2);
        System.out.println(list1);
        
        var list2 = new MyLinkedList<Integer>(1);
        list2.add(2);
        list2.add(3);
        list2.add(4);
        System.out.println(list2);
    }

输出:

[]
[ 1 2 ]
[ 1 2 3 4 ]

嗯,add成功(好吧其实途中改了几个bug才成功的…)

add(int index, E element):boolean接口实现

这里双链表的优势来了,给定了位置,那么可以判断从头还是从尾部找,这里分了2步,一步定位,一步插入,

/*
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any)
     * and any subsequent elements to the right (adds one to their indices).
     * @index
     * the position of the list to be inserted
     * @element
     * the element node of E to be inserted
     */
    public boolean add(int index, E element){
        //add to the last of the list?
        if((this.size==0 && index==0)||index==this.size){
            add(element);
            return true;
        }
        //index out of boundary
        if(index > this.size || index < 0){
            return false;
        }
        //index within the boundary
        else{
            if(index+1 > this.size/2){
                //starting from the tail
                int count = this.size-1;
                Node<E> iterator = this.tail;
                while(index != count){
                    iterator = iterator.pre;
                    count--;
                }
                //insert before this node
                insertNode(iterator,element);
                return true;

            }else{
                //staring from the head
                int count = 0;
                Node<E> iterator = this.head;
                while(index != count) {
                    iterator = iterator.next;
                    count++;
                }
                //insert before this node
                insertNode(iterator,element);
                return true;
            }
        }
    }
    /*
     * insert an element before the node of which the iterator referenced
     * @iterator
     * a reference to the node to be unlinked from its prev node
     * @element
     * a new node of which prev -> iterator.prev & next -> iterator
     */
    private void insertNode(Node iterator, E element){

        Node<E> node = new Node<E>(element,iterator.pre,iterator);

        // is the new node the head?
        if(iterator == this.head){

            this.head = node;
        }
        if(iterator.pre != null){
            iterator.pre.next = node;
        }
        iterator.pre = node;
        this.size++;

    }

测试:

	 public static void main(String[] args) {

        var list3 = new MyLinkedList<Integer>();
        //out of boundary
        list3.add(1,0);
        System.out.println(list3);

        //add to the last
        list3.add(0,0);
        System.out.println(list3);
        for(int i=1;i<10;i++){
            list3.add(i);
        }
        System.out.println(list3);
        list3.add(10,10);
        System.out.println(list3);

        //index is before the first half elements
        list3.add(5,11);
        System.out.println(list3);
        //index is not before the first half elements
        list3.add(6,12);
        System.out.println(list3);
		//index is before the first half and is the head
        list3.add(0,255);
        System.out.println(list3);

    }

输出

[]
[ 0 ]
[ 0 1 2 3 4 5 6 7 8 9 ]
[ 0 1 2 3 4 5 6 7 8 9 10 ]
[ 0 1 2 3 4 11 5 6 7 8 9 10 ]
[ 0 1 2 3 4 11 12 5 6 7 8 9 10 ]
[ 255 0 1 2 3 4 11 12 5 6 7 8 9 10 ]

嗯,看起来add实现成功了。
插入的api代码调试中遇到的bug:
主要是移动的几种情况没考虑清楚,插入头节点,只移动右节点;插入非头非尾,移动双边节点;插入尾节点,移动左节点。

  • 没有考虑插入时,如果是头节点的情况(头节点前没有节点,因而遇到runtime NullPointerException)
  • 没有考虑插入时,是尾节点的情况,就应该直接add。

remove(E element):boolean接口实现

删除一个节点,如果删除头尾,只移动一边;如果删除非头非尾节点,移动两边节点
这里按照LinkedList中接口定义,从头向下找到第一个element删除
这边手动写发现unlink和relink过程可能需要重构,先写了后看LinkedList源码再改。

  /*
     * Removes the first occurrence of the specified element from this list, if it is present.
     * If this list does not contain the element, it is unchanged.
     * @param element
     * the element to be removed,if present
     */
    public boolean remove(E element){
        Node<E> iterator = this.head;

        if(iterator == null){
            return false;
        }else{
            //if the head contains the target element
            if( iterator.item.equals(element) ){
                //if the head has a node linked to it
                if(iterator.next!=null){
                    //unlink reference to the head,set next node as the head
                    iterator.next.pre=null;
                    this.head = iterator.next;
                    //else the head is the only node in the list
                }else{
                    //just set the head as null
                    this.head = null;
                }
                //remove all links of the original head
                iterator.next = null;

                iterator.item = null;
                this.size--;
                return true;
                //head is not the target, search from its next, if presents
            }else{
                while(iterator.next != null){
                    iterator = iterator.next;
                    if( iterator.item.equals(element) ){

                        //found the target, if it has a node next to it
                        if(iterator.next!=null){
                            //re-link left and right nodes
                            iterator.next.pre = iterator.pre;
                            iterator.pre.next = iterator.next;
                            //remove target links
                            iterator.next = null;
                            iterator.pre = null;
                        //then the target is the tail
                        }else{
                            iterator.pre.next=null;
                            this.tail = iterator.pre;
                            iterator.pre = null;
                        }

                        iterator.item = null;
                        this.size--;
                        return true;
                    }
                }
            }

        }
        return false;
    }

测试:

 public static void main(String[] args) {

        var list4 = new MyLinkedList(1);
        for(int i=2;i<10;i++){
            list4.add(i);
        }
        System.out.println("Original list: " + list4);
        list4.remove(1);
        System.out.println("head deleted: " + list4);
        list4.remove(9);
        System.out.println("tail deleted: " + list4);
        list4.remove(5);
        System.out.println("target deleted in the middle: " + list4);
        list4.remove(5);
        System.out.println("non-existing target: "+ list4);
    }

结果:

Original list: [ 1 2 3 4 5 6 7 8 9 ]
head deleted: [ 2 3 4 5 6 7 8 9 ]
tail deleted: [ 2 3 4 5 6 7 8 ]
target deleted in the middle: [ 2 3 4 6 7 8 ]
non-existing target: [ 2 3 4 6 7 8 ]

看起来测试通过了,证明链表的节点值和指向都没错,但这里拆掉的节点是否被垃圾回收无法验证,可能有内存泄露。所以要检查代码是否清空目标节点的成员,具体运行如何测试内存泄露以后再看。

set(int index, E element):E 接口实现

从头结点(0位)开始,替换第index位的值,返回替换前的值。
要检查set功能,当然先get咯,我们需要先实现get(int index,E element): E 接口

    /*
     * Returns the element at the specified position in this list.
     * @param index
     * the position of the target element
     */
    public E get(int index){
        if(index < 0 || index >= this.size){
            return null;
        }else{
            int count = 0;
            Node<E> iterator = this.head;
            while(iterator != null){
                if(count == index){
                    return iterator.item;
                }
                count++;
                iterator = iterator.next;
            }
        }
        return null;
    }

然后是set, 几乎和get一样…

    /*
     *Replaces the element at the specified position in this list with the specified element.
     * @param index
     * the position of which the target element to be replaced
     * @param element
     * the element to replace the target
     */
    public E set(int index, E element){
        if(index < 0 || index >= this.size){
            return null;
        }else{
            int count = 0;
            Node<E> iterator = this.head;
            while(iterator != null){
                if(count == index){
                    E oldElement = iterator.item;
                    iterator.item = element;
                    return oldElement;
                }
                count++;
                iterator = iterator.next;
            }
        }
        return null;
    }

测试:

    public static void main(String[] args) {

        var list5 = new MyLinkedList();
        for(int i=0;i<11;i++){
            list5.add(i);
        }
        System.out.println(list5);
        list5.set(0,-1);
        System.out.println(list5.get(0));
        System.out.println(list5);
        
    }

结果

[ 0 1 2 3 4 5 6 7 8 9 10 ]
-1
[ -1 1 2 3 4 5 6 7 8 9 10 ]

好了,这里MyLinkedList的equals和hashcode没重写,不过大概手动完成了双向链表的增插删改,接下来就直接看LinkedList源码重构吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值