数据结构与算法学习总结-线性表基础

  表、栈和队列是最简单和最基本的三种数据结构。实际上,每一个有意义的程序都将显式地至少使用一种这样的数据结构,而栈则在程序中总是要被间接地用到,不管我们在程序中是否做了声明。

1.抽象数据类型

  抽象数据类型(abstract data type,ADT):是带有一组操作的一些对象的集合。抽象数据类型是数学的抽象;在ADT的定义中没有的地方提到关于这组操作是如何实现的任何解释。如表、集合、图以及与它们各自的操作一起形成的这些对象都可以被看做是抽象数据类型,就像整数、实数、布尔数都是数据类型一样。整数、实数和布尔数各自都有与之相关的操作,而抽象数据类型也是如此。
  对于集合ADT,可以有像添加(add)、删除(remove)以及包含(contain)这样一些操作。当然,也可以只要两种操作并(union)和查找(find),这两种操作又在这个集合上定义了一种不同的ADT。
  Java中也考虑到ADT的实现,不过适当地隐藏了实现的细节。这样,程序中需要对ADT实施操作的任何其他部分可以通过调用适当的方法来进行。如果由于某种原因需要改变实现的细节,那么通过仅仅改变执行这些ADT操作的例程应该是很容易做到的。这种改变对于程序的其余部分是完全透明的。
  对于每种ADT并不存在什么法则来告诉我们必须要有哪些操作。当它们的方法被正确地实现之后,使用它们的程序并没有必要知道它们是如何实现的。

2.线性表

  线性表(linear list)是n个类型相同数据元素的有限序列,通常记作(a0,a1,…,ai-1,ai,ai+1,…,an-1)。
  线性表是最简单,也是最常用的数据结构之一。
  其线性结构的特点是:在数据元素的有限集中,除第一个元素无直接前驱,最后一个元素无直接后续以外,每个数据元素有且仅有一个直接前驱元素和一个直接后续元素。

2.1线性表的简单数组实现

  对表的所有操作都可以通过使用数组来实现。虽然数组是由固定容量创建的,但在需要的时候可以用双倍的容量创建一个不同的数组。它解决由于使用数组而产生最严重的问题,即从历史上看为了使用一个数组,需要对表的大小进行估计。而这种估计在Java都是不必要的。
  假设有一个初始长度为10的数组arr,在必要的时候对其长度进行扩展的方式如下:

int[] arr = new int[10];
//对数组arr进行扩容
int[] newArr = new int[arr.length * 2];
for(int i = 0;i < arr.length;i++){
    newArr[i] = arr[i];
}
arr = newArr;

  数组的实现可以使printList以线性时间被执行,而findKth操作则花费常数时间,这正式我们所能预期的。不过插入和删除的花费却隐藏着巨大的浪费。假如我们在index=0的位置插入一个元素,则我们需要将整个数组后移一个位置用来腾出空间,而在该位置删除一个元素则需要将表中的所有元素前移一个位置,这两种操作所需的时间均为O(N)。最好的情况,自然是在index=n-1的位置插入、删除元素,无需对表中的其他元素进行移动。
  在一些情形下,对表的插入操作均在高端进行,其后只进行对数组的访问操作,这种情形下,数组是表的一种恰当的实现。当需要对表进行一些插入、删除操作,特别是在表的前端进行,这时,使用链表将会是一种更好的选择。

2.2线性表的简单链表实现

  当表单中的元素不连续存储时,我们就可以有效的避免插入和删除的线性开销。如图所示:
数据结构与算法学习总结-线性表基础_第1张图片

一个链表

  链表由一系列节点构成,这些节点不必在内存中相连。每一个节点均含有表元素和到包含该元素后继元的节点的链(link)。称为next链。最后一个单元的next链引用null。
  为了执行printListhuo find(x),只要从表的第一个节点开始然后用一些后继的next链遍历该表即可。这种操作显然是线性时间的,和在数组实现一样,不过其中的常数可能会比数组实现时要大。findKth操作不如数组实现时的效率高。
  Remove方法可以通过修改一个next引用来实现。以删除第三个元素为例,实现方式如图所示:
数据结构与算法学习总结-线性表基础_第2张图片
从链表中删除

  Insert方法需要使用new操作符从系统取得一个新节点,此后执行两次引用的调整。如图所示:
数据结构与算法学习总结-线性表基础_第3张图片
向链表中插入

  可以看出,在实现中如果知道变动将要发生的地方,那么像链表插入、删除一项元素的操作不需要移动很多项,而只涉及常数个节点链的改变。
  像这样只能通过一个结点的引用访问其后续节点,而无妨直接访问其前驱结点的链表,称为单链表。要再单链表中找到某个节点的前驱结点,必须从链表的首结点触发依次向后寻找,但是需要O(n)时间。为此我们可以扩展单链表的节点结构,使得通过一个结点的引用,不但能访问其后续结点,也可以方便的访问其前驱结点。做法为在单链表结点结构中新增一个域,该域作用于指向结点的直接前驱结点。扩展后的链表,即为双链表,如图所示:
数据结构与算法学习总结-线性表基础_第4张图片
双向链表

3.List接口

  抽象数据类型可以对应到Java中的类,抽象数据类型的数据对象与数据之间的关系可以通过类的成员变量来存储和表示,抽象数据类型的操作则使用类的方法来实现。
  先不考虑如何完成数据对象以及数据之间关系的存储和表示,我们考虑如何将抽象数据类型所提供的操作使用Java语言给出明确的定义。事实上对抽象数据类型提供的操作使用高级语言进行定义,就是给出其应用程序接口,在Java中我们可以使用一个接口来进行定义。如此在使用类来完成抽象数据类型的具体体现时,我们只要实现相应接口就实现了对抽象数据类型定义的操作的实现。
  List接口:

public interface List{
    //返回线性表的大小,即数据元素的个数;
    public int getSize(); 
    //如果线性表为空返回true,否则返回false; 
    public boolean isEmpty(); 
    //判断线性表是否包含数据元素e;
    public boolean contains(Object e); 
    //返回数据元素e在线性表中i号位置;
    public int indexOf(Object e); 
    //将数据元素e插入到线性表中i号位置; 
    public void insert(int i,Object e) throws OutOfBoundaryException; 
    //将数据元素e插入到obj之前; 
    public boolean insertBefore(Object obj,Object e); 
    //将数据元素e插入到obj之后; 
    public boolean insertAfter(Object obj,Object e); 
    //删除线性表中序号为i的元素,并返回之; 
    public Object remove(int i) throws OutOfBoundaryException; 
    //删除线性表示第一个与e相同的元素; 
    public boolean remove(Object e); 
    //替换线性表中序号为i的数据元素为e,返回原数据元素; 
    public Object replace(int i,Object e)throws OutOfBoundaryException; 
    //返回线性表中序号为i的数据元素 
    public Object get(int i) throws OutOfBoundaryException; 
}

  OutOfBoundaryException异常:

//线性表中出现序号越界是抛出该异常 
public class OutOfBoundaryException extends RuntimeException{ 
    public OutOfBoundaryException(String err){ 
        super(err); 
    } 
}

4.Strategy接口

public interface Strategy{
    //判断两个数据元素是否相等
    public boolean equal(Object obj1,Object obj2);
    /**
     *比较两个数据元素的大小
     *如果obj1 < obj2 返回-1
     *如果obj1 = obj2 返回0
     *如果obj1 > obj2 返回1
     */
    public int compare(Object obj1,Object obj2);
}

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