数据结构专题——线性表之单链表及其Java实现

来源:互联网 发布:app store付费软件 编辑:程序博客网 时间:2024/06/05 18:45

在上一篇博客中,博主和同学们一起认识了本系列中的第一个数据结构——线性表。在提供了抽象数据类型(ADT List)和Java版的MyList之后,我们又基于Java语言实现了在内存中顺序存储的线性表(也称为顺序表)。

尽管我们在遇到频繁访问列表中元素时,ArrayList是一个很好的选择;但当我们需要频繁添加、删除列表中元素,尤其是在表的前端进行增删操作时,ArrayList就不是一个特别好的选择了。

为了解决添加、删除元素时需要大量其它元素移动的问题,我们今天一起来接触在内存中链式存储的线性表(也称为链表)。

链式存储的线性表同样需要一组存储单元去存储数据元素,但是这组存储单元可以是内存上连续的,也可以是内存上不连续的

对于顺序存储的线性表来说,每个存储单元只需要存储数据元素即可,因为它们在内存上是连续的,第一个数据元素所占的内存空间之后,一定是下一个数据元素。

但是对于链式存储的线性表来说,除了要存数据元素之外,还需要存它的后继元素的存储地址,因为下一个数据元素很可能在内存中任意位置。

因此,对于每一个数据元素ai来说,除了存储其本身的数据信息之外,还需要存储一个指示其直接后继元素的内存位置。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为指针域。这两部分信息组成数据元素ai的存储映像,称为节点(Node)。n个节点连接成一个链表(a1,a2, … ,an),因为这个链表的每个节点中只包含一个指针域(指向它的直接后继元素),所以这种链表也成为单链表。

  • 下面我们就来一起用Java实现一个单链表MySinglyLinkedList
    public class MySinglyLinkedList<E> implements MyList<E> {    //单链表中的首节点,此节点不存放元素,只用来指向单链表中的第一个节点    private Node firstNode;    //单链表的元素数量    private int size;    public MySinglyLinkedList() {        firstNode = new Node(null, null);        this.size = 0;    }    //内部类Node,用来表示单链表中每一个数据元素    class Node{        //数据域        E data;        //后继指针域,指向下一个数据元素        Node nextNode;        //提供一个构造函数        Node(E data, Node nextNode){            this.data = data;            this.nextNode = nextNode;        }    }
  • 接下来我们实现获取元素个数和查看是否为空的方法,很简单单行代码即可实现
    @Override    public int size() {        // TODO Auto-generated method stub        return this.size;    }    @Override    public boolean isEmpty() {        // TODO Auto-generated method stub        return size()==0;    }
  • 然后是两种不同的插入方法
    @Override    public boolean add(E e) {        // TODO Auto-generated method stub        //在单链表的结尾插入一个元素,就相当于在index为size的位置处插入元素        add(size(),e);        return true;    }    @Override    public void add(int index, E element) {        // TODO Auto-generated method stub        //一旦要插入元素的位置为负或大于目前的元素数量就抛出异常                //此处允许index等于size,相当于在单链表末尾插入元素        if(index<0 || index>size())            throw new IndexOutOfBoundsException();        //需要插入的元素        Node newNode = new Node(element, null);        //由于单链表只能从前往后遍历,因此要想在某个位置插入元素,必须先获得它前一个位置上的元素节点        //私有方法getPrevNodeByIndex可以用来获取该元素节点        Node posNode = getPrevNodeByIndex(index);        //需要被插入元素的后继元素指向索引位置的原节点        newNode.nextNode = posNode.nextNode;        //前一个位置的节点的后继元素指向被插入的节点        posNode.nextNode = newNode;        this.size++;    }    //私有方法getPrevNodeByIndex获取index前一个位置的节点    private Node getPrevNodeByIndex(int index) {        Node posNode = firstNode;        for(int i=0; i<index;i++) {            posNode = posNode.nextNode;        }        return posNode;    }
  • 紧接着是两个不同的删除方法
    @Override    public boolean remove(Object o) {        // TODO Auto-generated method stub        if(isEmpty())            return false;        //如果需要被删除的元素为null        if(o == null) {            for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) {                if(n.nextNode.data == null) {                    Node targetNode = n.nextNode;                    n.nextNode = targetNode.nextNode;                    //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作                    targetNode.data = null;                    targetNode.nextNode = null;                    targetNode = null;                    this.size--;                    return true;                }            }            return false;        }else {            for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) {                if(o.equals(n.nextNode.data)) {                    Node targetNode = n.nextNode;                    n.nextNode = targetNode.nextNode;                    //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作                    targetNode.data = null;                    targetNode.nextNode = null;                    targetNode = null;                    this.size--;                    return true;                }            }            return false;        }    }    @Override    public E remove(int index) {        // TODO Auto-generated method stub        //一旦要插入元素的位置为负或大于目前的元素数量就抛出异常                //此处不允许index等于size        checkIndexValidation(index);        //获取要被删除节点的前驱节点        Node prevNode = getPrevNodeByIndex(index);        //获取要被删除的节点        Node targetNode = prevNode.nextNode;        //取出要被删除节点的数据域        E data = targetNode.data;        //要被删除节点的前驱节点的后继节点指向要被删除节点的后继节点        prevNode.nextNode = targetNode.nextNode;        //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作        targetNode.data = null;        targetNode.nextNode = null;        targetNode = null;        this.size--;        return data;    }
  • 之后是获取,修改,查看是否包含,以及清空整个列表这几个简单的方法了:
    @Override    public E get(int index) {        // TODO Auto-generated method stub        checkIndexValidation(index);        return getPrevNodeByIndex(index).nextNode.data;    }    @Override    public E set(int index, E element) {        // TODO Auto-generated method stub        checkIndexValidation(index);        Node targetNode = getPrevNodeByIndex(index).nextNode;        E oldData = targetNode.data;        targetNode.data = element;        return oldData;    }    @Override    public boolean contains(Object o) {        // TODO Auto-generated method stub        if(isEmpty())            return false;        if(o == null) {            for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) {                if(n.nextNode.data == null) {                    return true;                }            }            return false;        }else {            for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) {                if(o.equals(n.nextNode.data)) {                    return true;                }            }            return false;        }    }    @Override    public void clear() {        // TODO Auto-generated method stub        for(Node n = firstNode.nextNode; n !=null;) {            Node next = n.nextNode;            n.data = null;            n.nextNode = null;            n = next;        }        firstNode.nextNode = null;        this.size = 0;    }

我们在这里也分析一下MyLinkedList的特点:

我们要读取或修改某一个位置的节点需要花费线性时间 O(n)去遍历列表;
我们要增加或删除某一个(已知位置的)节点最坏情况下要花费常数时间 O(1)

因此,当我们在遇到频繁访问列表中元素时,ArrayList是一个很好的选择;但当我们需要频繁在已知位置添加、删除列表中元素时,LinkedList有更好的性能。

本节博客的源码全部放在这里,欢迎大家下载,下载后记得修改源码中的package名字哦。

原创粉丝点击