概念:线性表的顺序存储结构称为顺序表,它使用一维数组依次存放线性表的数据元素。顺序表是一种随机存取结构。
下面我们来看一下顺序表类的设计与实现。
首先声明顺序类SeqList的泛型如下,有两个具有保护权限的成员变量element和n,element数组存放数据元素,元素类型为T,n表示顺序表元素个数,-1<n<element.length。
顺序表的实现代码如下:
package dataStructure;
//顺序表类,实现ADTList<T>声明的方法,T表示数据元素
public class SeqList<T> extends Object{
protected Object[] element; //对象数组存储顺序表的数据元素,保护成员
protected int n; //顺序表元素个数
//构造容量为null的空表
public SeqList(int length) {
this.element=new Object[length];
this.n=0;
}
//创建容量默认的空表,构造方法重载
public SeqList() {
this(64); //调用上一个构造方法
}
//构造顺序表
public SeqList(T[] values) {
this(values.length); //创建容量为values.length的空表
for(int i=0;i<values.length;i++)
this.element[i]=values[i];
this.n=element.length;
}
//判断顺序表是否为空,为空返回true
public boolean isEmpty() {
return this.n==0;
}
//返回顺序表元素个数
public int size() {
return this.n;
}
//返回第i个元素,越界返回null
public T get(int i) {
if(i>=0&&i<this.n)
return (T)this.element[i];
return null;
}
//设置第i个元素的值为x
public void set(int i,T x) {
if(x==null)
throw new NullPointerException("x==null");
if(i>=0&&i<this.n)
this.element[i]=x;
else throw new java.lang.IndexOutOfBoundsException(i+"");
}
//返回顺序表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法
public String toString() {
String str=this.getClass().getName()+"(";//返回类名
if(this.n>0)
str+=this.element[0].toString();
for(int i=1;i<this.n;i++)
str+=","+this.element[i].toString();
return str+")";
}
public int insert(int i,T x) {
if(x==null)
throw new NullPointerException("x==null"); //抛出空对象异常
if(i<0)
i=0;
if(i>this.n)
i=this.n; //插入在最后
Object [] source=this.element;
if(this.n==element.length) {
this.element=new Object[source.length*2];
//复制当前数组前i-1个元素
for(int j=0;j<i;j++)
this.element[j]=source[j];
}
//从i开始至表尾的元素向后移动,次序从后向前
for(int j=this.n-1;j>=i;j--)
this.element[j+1]=source[j];
this.element[i]=x;
this.n++;
return i; //返回x序号
}
public int insert(T x) {
return this.insert(this.n ,x);
}
public T remove(int i) {
if(i>=0&&i<this.n&&this.n>0) {
T old=(T)this.element[i];
for(int j=i;j<this.n-1;j++)
{
this.element[j]=this.element[j+1];
}
this.element[this.n-1]=null;
this.n--;
return old;
}else {
return null;}
}
public void clear() {
for(int i=0;i<this.n;i++) {
this.element[i]=null;
}
}
//顺序查找首次出现与key相等元素,返回元素符号
public int search(T key) {
for(int i=0;i<this.n;i++) {
if(key.equals(this.element[i]))
return i;
}
return -1; //空表或者未找到
}
//判断是否包含关键字为key元素
public boolean contains(T key) {
return this.search(key)!=-1;
}
}
顺序表的效率分析:
判空isEmpty(),求长度size()以及获取元素例如get()以及set()等方法,时间复杂度都为O(1),而插入和删除以及toString()方法的时间复杂度都为O(n),其中插入和删除的时候平均移动表的一半,当n很大的时候,表的效率很低。顺序表适合用于查找,但如果需要频繁的插入以及删除,这需要使用到单链表。
求解Josephus环问题
本题目的:
(1)声明顺序表对象存储数据元素集合,以便插入、从操作并分析操作效率,删除操作需要返回被删除元素。
(2)线性表的泛型参数T的实际参数不能是基本数据类型,必须是对象,此处是String。
(3)环形的逻辑结构设计。按循环方式遍历顺序表。
约瑟夫环问题:古代某法官要判决number个犯人的死刑,他有一条荒唐的法律,将犯人站成一个圆圈,从第start个人开始数起,每数到第distance个犯人,就拉去处决,然后将从下一个人开始数distance个,数到的人再拉出去处决,直到剩下最后一个犯人予以赦免。当number=5,start=0,distance=2时,约瑟夫环问题执行过程如下所示:
使用顺序表求解这个问题,算法描述如下:
(1)创建一个拥有number个元素的顺序表对象list。
(2)从第start个元素开始,依次计数,每数到distance,就将该元素删除。
(3)重复计数并删除元素,直到剩下一个元素
代码如下:
package dataStructure;
import dataStructure.*;
public class Josephus {
//创建Josephus环并求解,参数指定环长度,起始位置,计数
public Josephus(int number,int start,int distance) {
System.out.print("Josephus("+number+","+start+","+distance+"),");
//创建顺序表实例,构造方法参数指定顺序表容量
SeqList<String>list=new SeqList<String>(number);
for(int i=0;i<number;i++)
list.insert((char)('A'+i)+"");
System.out.println(list.toString());
int i=start;
while(list.size()>1)
{
i=(i+distance-1) % list.size();
System.out.print("删除"+list.remove(i)+",");
System.out.println(list.toString());
}
System.out.println("被赦免者是"+list.get(0).toString());
}
public static void main(String [] args) {
new Josephus(5,0,2);
}
}
结果截图:
顺序表的浅拷贝与深拷贝
(1)顺序表的浅拷贝
分析:
1.当成员变量的数据类型是基本数据类型时,浅拷贝能够实现对象复制功能。
2.当成员变量的数据类型是引用数据类型时,浅拷贝复制的只是数组引用或者对象引用,并没有实现对象复制功能。此时需要深拷贝。
(2)顺序表的深拷贝
当一个类包含数组或者对象引用等引用类型的成员变量时,该类声明的拷贝构造方法,不仅要复制对象的所有基本类型成员变量值,还要引用类型变量申请存储空间,并复制其中所有元素/对象,这种就叫做深拷贝。
两者的区别:
简单来说就是浅拷贝能复制基本类型的属性,引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。
而深拷贝复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。