数据结构与算法学习总结-线性表的链式存储与实现

  实现线性表的另一种方法时链式存储,即用指针将存储线性表中数据元素的那些单元依次串联在一起。这种方法避免了再数组中用练习的单元存储元素的缺点,因而在执行插入或删除运算时,不再需要移动元素来腾出空间或填补空缺。相应的,我们需要再每个单元中设置指针来表示表中元素之间的逻辑关系,因而增加了额外的存储空间的开销。

1.单链表

  链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个域是指向其他单元的指针,这里具有一个数据域和多个指针域的促进处单元通常称为节点(node)。

数据结构与算法学习总结-线性表的链式存储与实现_第1张图片
单链表结点结构

  在节点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的节点。
  在Java中没有显式的指针类型,然而实际上对象的访问就是使用指针来实现的,即在Java中是使用对象的引用来替代指针的。因此在使用Java实现该节点结构时,一个节点本身就是一个对象。节点的数据域data可以使用一个Object类型的对象来实现,用于存储任何类型的数据元素,并通过对象的引用指向该元素;而指针域next可以通过节点对象的引用来实现。
   节点接口:

public interface Node{
    //获取节点数据域
    public Object getData();
    //设置节点数据域
    public void setData(Object obj);
}

  单链表节点定义:

public class SLNode implements Node{
    private Object element;
    private SLNode next;
    public SLNode(){
        this(null,null);
    }
    public SLNode(Object ele,SLNode next){
        this.element = ele;
        this.netx = next;
    }
    public SLNode getNext(){
        return next;
    }
    public void setNext(SLNode next){
        this.next = next;
    }
    //节点方法
    public Object getData(){
        return element;
    }
    public void setData(Object obj){
        element = obj;
    }
}

2.双链表

  单链表的一个优点是结构简单,但是它也有一个缺点,即在单链表中只能通过一个节点的引用访问其后续节点,而无法直接访问其前驱节点,要在单链表中找到某个节点的前驱节点,必须从链表的首节点触发依次向后寻找。为此我们可以扩展单链表的节点结构,使得通过一个节点的引用,不但能够访问其后续节点,也可以方便的访问其前驱节点。

数据结构与算法学习总结-线性表的链式存储与实现_第2张图片
双链表节点结构

   双向链表节点定义:

public class DLNode implements Node{
    private Object element;
    private DLNode pre;
    private DLNode next;
    public DLNode(){
        this(null,null,null);
    }
    public DLNode(Object ele,DLNode pre,DLNode next){
        this.element = ele;
        this.pre = pre;
        this.next = next;
    }
    public DLNode getNext(){
        return next;
    }
    public void setNext(DLNode next){
        this.next = next
    }
    public DLNode getPre(){
        return pre;
    }
    public void setPre(DLNode pre){
        this.pre = pre;
    }
    //结点方法
    public Object getData(){
        return element;
    }
    public void setData(Objcet obj){
        element = obj;
    }
}

3.具有头节点和尾节点的双链表

  由于链表结构的特殊性,因此在表的终端创建一个额外的节点来表示终端标记是有意义的。更进一步,我们还能够在表前端创建一个额外的节点,逻辑上代表开始的标记。这些额外的结点有时候就叫做标记节点;特别的,在前端的节点有时候也叫做头节点(header node),而在末端的节点有时候也叫做尾节点(tail node)。
  使用这些额外节点的优点在于,通过排除许多特殊情况极大的简化了编码。例如,如果我们不是用头节点,那么删除第1个节点就变成了一种特殊的情况,因为在删除期间我们必须重新调整链表的到第1个节点,还因为删除算法一般还要访问被删除结点前面的那个节点(而若无头节点,则第1个节点前面没有结点)。

数据结构与算法学习总结-线性表的链式存储与实现_第3张图片
具有头节点和尾节点的双链表
数据结构与算法学习总结-线性表的链式存储与实现_第4张图片
具有头节点和尾节点的空链表

   线性表的单链表实现:

public class ListSLinked implements list{
    private Strategy stategy;   //数据元素比较策略
    private SLNode head;        //单链表首节点引用
    private int size;           //线性表中数据元素的个数
    public ListSLinked(){
        this(new DefaultStrategy());
    }
    public ListSLinked(){
        this.strategy = strategy;
        head = new SLNode();
        size = 0;
    }
    //辅助方法:获取数据元素e所在节点的前驱节点
    private SLNode getPreNode(Object e){
        SLNode p = head;
        while(p.getNext() != null){
            if(strategy.equal(p.getNext().getData(),e)){
                return p;
            }
            return null;
        }
    }
    //辅助方法:获取序号为0<=i
    private SLNode getPreNode(int i){
        SLNode p = head;
        for(;i > 0;i--){
            p = p.getNext()
        }
        return p;
    }
    //获取序号为0<=i
    private SLNode getNode(int i){
        SLNode p = head.getNext();
        for(;i > 0;i--){
            p = p.getNext;
        }
        return p;
    }
    //返回线性表的大小,即数据元素的个数
    public int getSize(){
        return size;
    }
    //如线性表为空返回true,否则返回false
    public boolean isEmpty(){
        return size == 0;
    }
    //判断线性表是否包含数据元素e
    public boolean contains(Object e){
        SLNode p = head.getNext();
        while(p != null){
            if(strategy.equal(p.getData(),e)){
                return true;
            } else {
                p = p.getNext();
            }
            return false;
        }
    }
    //返回数据元素e在线性表中的序号
    public int indexOf(Object e){
        SLNode p = head.getNext();
        int index = 0;
        while(p != null){
            if(strategy.equal(p.getData(),e)){
            return index;
            } else {
                index++;
                p = p.getNext
            }
            return -1;
        }
    }
    //将数据元素e插入到线性表中i号位置
    public void insert(int i,Object e)throws OutOfBoundaryException{
        if(i < 0||i >= size){
            throw new OutOfBoundaryException("错误,指定的插入序号越界");
        }
        SLNode p = getPreNode(i);
        SLNode q = new SLNode(e,p.getNext());
        p.setNext(q);
        size++;
        return;
    }
    //将数据元素e插入到元素obj之前
    public boolean insertBefore(Object obj,Object e){
        SLNode p = getPreNode(obj);
        if(p != null){
            SLNode q = new SLNode(e,p.getNext());
            p.setNext(q);
            size++;
            return true;
        }
        return false;
    }
    //将数据元素e插入到元素obj之后
    public boolean insertAfter(Object obj,Object e){
        SLNode p = head.getNext();
        while(p != null){
            if(strategy.equal(p.getData,obj)){
                SLNode q = new SLNode(e,p.getNext());
                p.setNext(q);
                size++;
                return true;
            } else {
                p = p.getNext();
            }
            return false;
        }
    }
    //删除线性表中序号为i的元素,并返回之
    public Object remove(int i) throws OutOfBoundaryException{
        if(i < 0||i >= size){
            throw new OutOfBoundaryException("错误,指定的删除序号越界。");
        }
        SLNode p = getPreNext();
        Object obj = p.getNext().getData();
        p.setNext(p.getNext().getNext());
        size--;
        return obj;
    }
    //删除线性表中第一个与e相同的元素
    public boolean remove(Object e){
        SLNode p = getPreNode(e);
        if(p != null){
            p.setNext(p.setNext().getNext());
            size--;
            return true;
        }
        return false;
    }
    //替换线性表中序号为i的数据元素为e,返回原数据元素
    public Object replace(int i,Object e)throws OutOfBoundaryException{
        if(i < 0||i >= size){
            throw new OutOfBoundaryException("错误,指定的序号越界。");
        }
        SLNode p = getNode(i);
        Object obj = p.getData();
        p.setData(e);
        return obj;
    }
    //返回线性表中序号为i的数据元素
    public Object get(int i) throws OutOfBoundaryException{
        if(i < 0||i >=size){
            throw new OutOfBoundaryException("错误,指定的序号越界。");
        }
        SLNode p = getNode(i);
        return p.getData();
    }
}

  说明:在LinkedList类中共有3个成员变量,其中size用于表明线性表中数据元素的个数;head是带头节点的单链表的首节点引用;而strategy是用来完成线性表中数据元素的比较操作的策略。

你可能感兴趣的:(数据结构)