数据结构中的线性存储结构分为两大类:顺序存储和链式存储,顺序存储对应的是顺序表,链式存储对应的是链表。这篇文章主要介绍如何使用Java实现一个顺序表。
顺序表: 把线性表的元素按照一定逻辑顺序依次放在一组地址连续的存储单元里,用这种方式存储的线性表简称为 顺序表。而链表中的元素是没有放在一组地址连续的存储单元里,它们之间的链接是靠指针串一起的。
优点:随机存取表中元素。随机存取的意思是对所有元素序号来讲都是一样的计算时间,也就是说,访问任何一个元素的时间都是相同的。由于每个元素在计算机中占有的存储单元是相同的(假设占有的空间是c),则可知a[i]所在的存储地址是: Loc(ai)=Loc(a0)+i*c,访问不同存储位置的元素时间都是相同的,因此顺序标是随机存取。
缺点:插入和删除比较慢,不可以增加长度,如想增加长度,则需另开内存将原数组复制到新空间。
先看看代码的API,有助于理解代码(虽说代码中的注释很完整。。。):
/*
* created on April 15 8:20 2019
*
* @author:lhy
*/
package DS;
import java.util.Arrays;
import java.util.Iterator;
import org.w3c.dom.html.HTMLIsIndexElement;
interface MyList<T>{
//接口中每一个方法都是隐式抽象的,接口中所有方法会被隐式指定为public abstract类型(且只能是public abstract)
//接口中的变量会被隐式指定为public static final类型变量
//接口中的函数不能在接口中实现,只能由实现接口的类来实现接口中的方法
boolean isEmpty();//判断线性表是否为空
int length();//获得List的元素个数
T get(int i);//取得第i个位置上的数据元素,返回数据元素的值
boolean set(int i,T x);//将第i个位置上的数据元素设置为值x
void add(int i,T x);//位置i及其后的元素依次后移一个位置,然后将x插入到第i个位置
T remove(int i);//删除第i个位置上的数据元素,位置i+1及其后的元素依次前移一个位置
int indexOf(T x);//获取数据元素x在表中的下标位置
}
abstract class AbsList<T> implements Iterable<T>,MyList<T>{
//必须实现接口MyList中所描述的所有方法,否则必须声明为抽象类
protected int length;//length指的是这个顺序表已经容纳的元素个数
abstract public T get(int i);//返回第i个元素
abstract public boolean set(int i,T x);//设置第i个元素的值为x
abstract public int indexOf(int begin,int end,T o);//在begin和end之间寻找值为o的元素的下标
abstract public void add(int i,T x);//插入x作为第i个元素
abstract public void clear();//清空列表
abstract public T remove(int i);//删除第i个元素并返回被删除对象
abstract public Iterator<T> iterator();//返回一个迭代器
//判断线性表是否为空
public boolean isEmpty() {
return length==0;
}
//获取线性表长度
public int length(){
return length;
}
//在线性表最后插入x元素
public void add(T x) {
add(length,x);
}
//与add一样
public void append(T x) {
add(length,x);
}
//在整个线性表中查找元素o的下标值
public int indexOf(T o) {
return indexOf(0,length,o);
}
//在下标为begin到末尾的线性表中查找元素o的下标值
public int indexOf(int begin,T o) {
return indexOf(begin,length,o);
}
//删除第i个元素并返回删除对象
public T remove(T o) {
return remove(indexOf(o));
}
}
/*
* SeqList API介绍
* setIncr(int inc): 设置顺序表每次增加时增量的大小
* setCapacity(int newSize): 设置新的最大容量
* getCapacity(): 获取数组的最大容量
* size(): 获取顺序表已经存放数据的数量,同length()
* get(int i): 获取顺序表下标为i的元素值
* set(int i,T x): 修改下标值为i的元素值,即为修改data[i]
* indexOf(int begin,int end,T value): 在begin和end之间查找值为value的元素下标值,使用顺序查找法
* indexOf(T o): 在整个线性表中查找元素o的下标值
* indexOf(int begin,T o): 在下标为begin到末尾的线性表中查找元素o的下标值
* add(int i,T x): 在i位置插入数据元素x
* add(T x): 在表尾添加数据元素x
* append(T x): 在表尾添加数据元素x
* addSort(T x): 以有序方式向顺序表中插入数据元素x
* sort(): 对顺序表从小到大排序
* remove(int i): 删除下标为i的元素,并返回被删除的元素
* remove(T value): 删除值为value的元素,并返回被删除的元素
* clear(): 清除整个顺序表
* toString(): 将顺序表转化成字符串,便于直接输出顺序表
* toArray(): 将顺序表转化成Object数组,并返回
* toArray(T[] a): 将顺序表转换为类型为E的数组
* iterator(): 返回一个迭代对象
* length(): 获取线性表长度
* isEmpty(): 判断线性表是否为空
*/
public class SeqList<T> extends AbsList<T> implements Iterable<T> {
//必须实现抽象类AbsList中的所有抽象方法
private int incrementSize;//顺序表每次增量的长度
protected Object[] data;//保存顺序表数据的数组
//默认构造函数
public SeqList() {
this(16);
}
//构造函数,设定初始容量为initLen
public SeqList(int initLen) {
if(initLen<=0) {
initLen=16;
}
length=0;
incrementSize=16;
data=new Object[initLen];
}
//构造函数,用elem数组初始化顺序表
public SeqList(T[] elem) {
length=elem.length;
incrementSize=16;
data=Arrays.copyOf(elem, length);
}
//设置顺序表每次容量增加时增量的大小,默认是16
public void setIncr(int inc) {
incrementSize=inc;
}
//设置新的数组容量
public void setCapacity(int newSize) {
data=Arrays.copyOf(data, newSize);
}
//获得data数组的最大容量
public int getCapacity() {
return data.length;
}
//获取顺序表已经存放数据的数量,同length()
public int size() {
return length;
}
//取得顺序表下标为i的元素值,即data[i]
public T get(int i) {
if(i<0 || i>length-1)
return null;
return (T)data[i];
}
//修改下标值为i的元素值,即修改data[i]
public boolean set(int i,T x) {
if(i<0 || i>length-1)
return false;
else{
data[i]=x;
return true;
}
}
//内部使用,提供数据元素的比较方法
private int compare(T a,T b) {
if(a instanceof Comparable && b instanceof Comparable) {
return ((Comparable) a).compareTo((Comparable) b);
}
else {
return ((String) a).compareTo((String) b);
}
}
//在begin和end之间查找值为value的数据元素下标,使用顺序查找法
public int indexOf(int begin,int end,T value) {
//先查找null值
if(value==null) {
for(int i=0;i<length;i++) {
if(data[i]==null) {
return i;
}
}
}
//查找有效值
else {
for(int i=0;i<length;i++) {
if(compare((T)data[i],value)==0) {
return i;
}
}
}
return -1;
}
//内部使用,自动增加顺序表容量
private void grow() {
int newSize=data.length+incrementSize;
data=Arrays.copyOf(data, newSize);
}
//在i位置插入数据元素x
public void add(int i, T x) {
//顺序表存满的时候自动增加顺序表容量
if(length==data.length)
grow();
if(i<0) i=0;
if(i>length) {
i=length;
}
for(int j=length-1;j>=i;j--) {
data[j+1]=data[j];
}
data[i]=x;
length++;
}
//向表尾添加数据元素x
public void add(T x) {
if(length==data.length) {
grow();
}
data[length]=x;
length++;
}
//向表尾添加数据元素x,功能同上
public void append(T x) {
add(x);
}
//内部使用,以有序方式插入数据元素x
private void insertOrder(int end,T x) {
if(length==data.length) {
grow();
}
int k;
for(k=end-1;k>=0;k--) {
//当x小于data[k]时
if(compare(x,(T)data[k])<0) {
data[k+1]=data[k];
}
else
break;
}
data[k+1]=x;
}
//以有序方式向顺序表中插入数据元素x
public void addSort(T x) {
insertOrder(length,x);
length++;
}
//对顺序表排序
public void sort() {
for(int i=1;i<=length-1;i++) {
//调用插入函数insertOrder完成具体插入过程
insertOrder(i, (T)data[i]);
}
}
//删除下标为i的元素,并返回被删除的元素
public T remove(int i) {
if(i<0 || i>length-1)
throw new IndexOutOfBoundsException("下标越界 i="+i);
T olddata=(T)data[i];
for(int j=i;j<length-1;j++) {
data[j]=data[j+1];
}
length--;
data[length]=null;
return olddata;
}
//删除值为value的元素
public T remove(T value) {
int i=indexOf(value);
return remove(i);
}
//清除整个列表
public void clear() {
for(int i=0;i<length;i++)
//将元素指向空指针,使用Java虚拟机的辣鸡回收机制自动处理
data[i]=null;
length=0;
}
//将顺序表转化成字符串,便于直接输出顺序表
public String toString() {
StringBuilder strbui=new StringBuilder();
strbui=strbui.append("(");
for(int i=0;i<length-1;i++) {
strbui.append(data[i].toString()+",");
}
strbui=strbui.append(data[length-1].toString()+")");
String string=new String(strbui);
strbui=null;
return string;
}
//将顺序表转化成Object数组,并返回
public Object[] toArray() {
return Arrays.copyOf(this.data, this.length);
}
//将顺序表转换为类型为T的数组(因为T是泛型,理论上可以代表任何变量类型)
public T[] toArray(T[] a) {
//当传入的数组a的长度小于顺序表的长度时,创建一个与数组a的运行时类型相同的数组,a的长度可以是0
if(a.length<length) {
return (T[]) Arrays.copyOf(this.data, this.length,a.getClass());
}
//将data里面的以第1个元素复制到a中第1个元素,以此往后加一,一共加到this.length-1的位置
System.arraycopy(this.data, 0, a, 0, this.length);
if(a.length>this.length) {
a[length]=null;
}
return a;
}
//创建一个迭代器类
class MyIterator implements Iterator<T>{
private int index=0;
public boolean hasNext() {
return index!=length();
}
public T next() {
//用索引来获取SeqList中下标为index的项
return get(index++);
}
public void remove() {
//未实现此方法
}
}
//返回一个迭代器对象
public Iterator<T> iterator(){
return new MyIterator();
}
}
创建一个顺序表并对以上类中函数进行实现检验,代码如下:
/*
* created on April 16 21:33 2019
*
* @author:lhy
*/
package DS;
import java.util.Iterator;
import DS.SeqList;
/*
* SeqList API介绍
* setIncr(int inc): 设置顺序表每次增加时增量的大小
* setCapacity(int newSize): 设置新的最大容量
* getCapacity(): 获取数组的最大容量
* size(): 获取顺序表已经存放数据的数量,同length()
* get(int i): 获取顺序表下标为i的元素值
* set(int i,T x): 修改下标值为i的元素值,即为修改data[i]
* indexOf(int begin,int end,T value): 在begin和end之间查找值为value的元素下标值,使用顺序查找法
* indexOf(T o): 在整个线性表中查找元素o的下标值
* indexOf(int begin,T o): 在下标为begin到末尾的线性表中查找元素o的下标值
* add(int i,T x): 在i位置插入数据元素x
* add(T x): 在表尾添加数据元素x
* append(T x): 在表尾添加数据元素x
* addSort(T x): 以有序方式向顺序表中插入数据元素x
* sort(): 对顺序表从小到大排序
* remove(int i): 删除下标为i的元素,并返回被删除的元素
* remove(T value): 删除值为value的元素,并返回被删除的元素
* clear(): 清除整个顺序表
* toString(): 将顺序表转化成字符串,便于直接输出顺序表
* toArray(): 将顺序表转化成Object数组,并返回
* toArray(T[] a): 将顺序表转换为类型为E的数组
* iterator(): 返回一个迭代对象
* length(): 获取线性表长度
* isEmpty(): 判断线性表是否为空
*/
public class Test_SeqList {
public static void main(String[] args) {
//new一个顺序表对象
SeqList<Integer> seqList= new SeqList<Integer>(16);
System.out.println("顺序表是否为空:"+seqList.isEmpty());
//随机初始化顺序表
for(int i=0;i<6;i++)
seqList.add((int)(1+Math.random()*(1000)));
//创建一个顺序表迭代器,并输出顺序表
Iterator<Integer> seq_iterator=seqList.iterator();
System.out.print("使用迭代器输出整个顺序表:");
for(int i=0;i<6;i++)
System.out.print(seq_iterator.next()+" ");
System.out.print("\n");
System.out.println("初始化后,顺序表是否为空:"+seqList.isEmpty());
System.out.println("获取顺序表容量:"+seqList.getCapacity());
System.out.println("删除下标为2的元素:"+seqList.remove(2));
System.out.println("删除下标为2的元素之后顺序表为:"+seqList.toString());
seqList.append(78);
System.out.println("顺序表末尾加入数据78之后顺序表为:"+seqList.toString());
seqList.sort();
System.out.println("将顺序表从大到小排序后顺序表为:"+seqList.toString());
seqList.add(1,233);
System.out.println("在下标为1的位置插入元素233之后,顺序表为:"+seqList.toString());
System.out.println("获取下标为length-1(最后一个元素)的位置的元素为:"+seqList.get(seqList.length()-1));
seqList.add(998);
System.out.println("顺序表末尾加入数据998之后顺序表为:"+seqList.toString());
seqList.sort();
seqList.addSort(555);
System.out.println("对顺序表排序后,有序插入数据555顺序表为:"+seqList.toString());
System.out.println("查找排序后的元素555所在的位置:"+seqList.indexOf(0,seqList.length(),555));
seqList.setCapacity(32);
System.out.println("将顺序表最大容量变成32之后,最大容量为"+seqList.getCapacity());
Integer num=new Integer(555);
System.out.println("删除元素555:"+seqList.remove(num));
System.out.println("删除元素555之后顺序表为:"+seqList.toString());
//将seqList顺序表转化成Integer[]数组,使用toArray(),这个函数返回的是Object[]类型的数组
Integer[] nums=new Integer[seqList.length()];
//TIPS:因为Integer[]不是Object[]的子类,所以不能直接强制类型转换(Integer是Object的子类)
Object[] nums_object=seqList.toArray();
for(int i=0;i<nums.length;i++)
nums[i]=(Integer)nums_object[i];
System.out.print("输出转化后的数组num:");
for(int j=0;j<nums.length;j++) {
System.out.print(nums[j]+" ");
}
System.out.print("\n");
//将seqList顺序表转化成Integer[]数组,使用toArray(T[] a)),这个函数返回的是T类型的数组
Integer[] nums2=new Integer[seqList.length()];
nums2=seqList.toArray(nums2);
System.out.print("输出转化后的数组num2:");
for(int j=0;j<nums2.length;j++) {
System.out.print(nums2[j]+" ");
}
System.out.print("\n");
System.out.println("使用clear函数清空整个顺序表");
seqList.clear();
System.out.println("顺序表现在是否为空:"+seqList.isEmpty());
}
}
输出为:
顺序表是否为空:true
使用迭代器输出整个顺序表:700 889 70 218 510 319
初始化后,顺序表是否为空:false
获取顺序表容量:16
删除下标为2的元素:70
删除下标为2的元素之后顺序表为:(700,889,218,510,319)
顺序表末尾加入数据78之后顺序表为:(700,889,218,510,319,78)
将顺序表从大到小排序后顺序表为:(78,218,319,510,700,889)
在下标为1的位置插入元素233之后,顺序表为:(78,233,218,319,510,700,889)
获取下标为length-1(最后一个元素)的位置的元素为:889
顺序表末尾加入数据998之后顺序表为:(78,233,218,319,510,700,889,998)
对顺序表排序后,有序插入数据555顺序表为:(78,218,233,319,510,555,700,889,998)
查找排序后的元素555所在的位置:5
将顺序表最大容量变成32之后,最大容量为32
删除元素555:555
删除元素555之后顺序表为:(78,218,233,319,510,700,889,998)
输出转化后的数组num:78 218 233 319 510 700 889 998
输出转化后的数组num2:78 218 233 319 510 700 889 998
使用clear函数清空整个顺序表
顺序表现在是否为空:true