点击上方“Java专栏”,选择“置顶或者星标”
第一时间阅读精彩文章!
1、☞ 程序员进阶必备资源免费送「21种技术方向!」 点击查看☜2、☞ 《Java面试手册》.PDF 点击查看
线性表抽象数据类型概述
首先来说明一下什么是抽象数据类型,我们都知道java在默认情况下,所有的基本数据类型(int,float,boolean等)都支持基本运算,如加减法,这是因为系统已帮我们实现了这些基本数据类型的的基本运算。而对于自定义的数据类型(如类)也需要定义相应的运算,但在实际使用这些自定义的数据类型的运算时需要自己实现相关的运算,也就是说用户自定义的数据类型的运算需要我们自己利用系统提供的基本运算来定义和实现。这些自定义了数据结构(如自定义类)和包含相关运算组合实现的数据类型就称其为抽象数据类型(ADT,Abstract Data Type),因此一个ADT会包含数据声明和运算声明。常用的ADT包含链表、栈、队列、优先队列、二叉树、散列表、图等,所以接下来我们要分析的顺序表和链表也属于ADT范畴。下面引用自java数据结构一书对线性表的定义:
线性表是由n(n>=0)个类型相同的数据元素a0,a1,…,an-1组成的有限的序列,在数学中记作(a0,a1,…,an-1),其中ai的数据类型可以是基本数据类型(int,float等)、字符或类。n代表线性表的元素个数,也称其为长度(Length)。若n=0,则为空表;若n > 0,则ai(0 < i < n-1)有且仅有一个前驱(Predecessor)元素ai-1和一个后继(Successor)元素ai+1,a0没有前驱元素,ai没有后继元素。
以上便是对线性表抽象数据类型概述,下面我们开始分别针对顺序表和链表进行深入分析。
2.线性表的顺序存储设计与实现(顺序表)
2.1 顺序存储结构的设计原理概要
顺序存储结构底层是利用数组来实现的,而数组可以存储具有相同数据类型的元素集合,如int,float或者自定义类型等,当我们创建一个数组时,计算机操作系统会为该数组分配一块连续的内存块,这也就意味着数组中的每个存储单元的地址都是连续的,因此只要知道了数组的起始内存地址就可以通过简单的乘法和加法计算出数组中第n-1个存储单元的内存地址,就如下图所示:
通过上图可以发现为了访问一个数组元素,该元素的内存地址需要计算其距离数组基地址的偏移量,即用一个乘法计算偏移量然后加上基地址,就可以获得数组中某个元素的内存地址。其中c代表的是元素数据类型的存储空间大小,而序号则为数组的下标索引。整个过程需要一次乘法和一次加法运算,因为这两个操作的执行时间是常数时间,所以我们可以认为数组访问操作能再常数时间内完成,即时间复杂度为O(1),这种存取任何一个元素的时间复杂度为O(1)的数据结构称之为随机存取结构。而顺序表的存储原理正如上图所示,因此顺序表的定义如下(引用):
线性表的顺序存储结构称之为顺序表(Sequential List),它使用一维数组依次存放从a0到an-1的数据元素(a0,a1,…,an-1),将ai(0< i <> n-1)存放在数组的第i个元素,使得ai与其前驱ai-1及后继ai+1的存储位置相邻,因此数据元素在内存的物理存储次序反映了线性表数据元素之间的逻辑次序。
2.2 顺序存储结构的实现分析
接着我们来分析一下顺序表的实现,先声明一个顺序表接口类ISeqList,然后实现该接口并实现接口方法的代码,ISeqList接口代码如下:
package com.zejian.structures.LinkedList;
/**
* Created by zejian on 2016/10/30.
* 顺序表顶级接口
*/
publicinterfaceISeqList {
/**
* 判断链表是否为空
* @return
*/
boolean isEmpty();
/**
* 链表长度
* @return
*/
int length();
/**
* 获取元素
* @param index
* @return
*/
T get(int index);
/**
* 设置某个元素的值
* @param index
* @param data
* @return
*/
T set(int index, T data);
/**
* 根据index添加元素
* @param index
* @param data
* @return
*/
boolean add(int index, T data);
/**
* 添加元素
* @param data
* @return
*/
boolean add(T data);
/**
* 根据index移除元素
* @param index
* @return
*/
T remove(int index);
/**
* 根据data移除元素
* @param data
* @return
*/
boolean remove(T data);
/**
* 根据data移除元素
* @param data
* @return
*/
boolean removeAll(T data);
/**
* 清空链表
*/
void clear();
/**
* 是否包含data元素
* @param data
* @return
*/
boolean contains(T data);
/**
* 根据值查询下标
* @param data
* @return
*/
int indexOf(T data);
/**
* 根据data值查询最后一个出现在顺序表中的下标
* @param data
* @return
*/
int lastIndexOf(T data);
/**
* 输出格式
* @return
*/
String toString();
}
}
代码中声明了一个Object数组,初始化数组大小默认为64,存储的元素类型为泛型T,length则为顺序表的长度,部分方法实现比较简单,这里不过多分析,我们主要分析get(int index)、set(int index, T data)、add(int index, T data)、remove(int index)、removeAll(T data)、indexof(T data)等方法的实现。
get(int index) 实现分析
从顺序表中获取值是一种相当简单的操作并且效率很高,这是由于顺序表内部采用了数组作为存储数据的容器。因此只要根据传递的索引值,然后直接获取数组中相对应下标的值即可,代码实现如下:
public T get(int index){
if(index>=0&& index<this.length)
return(T) this.table[index];
returnnull;
}
set(int index, T data) 实现分析 在顺序表中替换值也是非常高效和简单的,只要根据传递的索引值index找到需要替换的元素,然后把对应元素值替换成传递的data值即可,代码如下:
public T set(int index, T data){
if(index>=0&& index<this.length&& data!=null)
{