2.线性表—顺序表

来源:互联网 发布:打鸟汽枪650淘宝 编辑:程序博客网 时间:2024/05/29 18:35
   

引言(重点):

1.线性表的概述

2.线性表的抽象数据类型描述

3.线性表的实现方式

4.线性表的具体实现

5.每种具体实现的分析



1、什么是线性表?
线性表(Linear List):由同类型元素构成有序序列的线性结构。

特征:
1.表中元素个数称为线性表的长度
2.线性表没有元素时,称为空表
3.表起始位置称表头,表结束位置称为表尾
4.在一个元素的前面的元素叫前驱元素,在一个元素后面的元素叫后继元素。

2、线性表的抽象数据类型描述

List MakeEmpty():初始化一个空线性表L;
ElementType FindKth( int K, List L ):根据位序K,返回相应元素 ;
int Find( ElementType X, List L ):在线性表L中查找X的第一次出现位置;
void Insert( ElementType X, int i, List L):在位序i前插入一个新元素X;
void Delete( int i, List L ):删除指定位序i的元素;
int Length( List L ):返回线性表L的长度n。

注意:由于线性表的数据元素之间具有顺序关系,可以为每个元素约定一个序号,因此线性表提供对指定序号元素进行操作的方法。

线性表接口LList:

package com.clarck.datastructure.linear;

/**
* 线性表接口LList,描述线性表抽象数据类型,泛型参数T表示数据元素的数据类型

* @author clarck
*
*/
public interface LList<T> {
/**
* 判断线性表是否空
* @return
*/
boolean isEmpty();

/**
* 返回线性表长度
* @return
*/
int length();

/**
* 返回第i(i≥0)个元素
* @param i
* @return
*/
T get(int i);

/**
* 设置第i个元素值为x
* @param i
* @param x
*/
void set(int i, T x);

/**
* 插入x作为第i个元素
* @param i
* @param x
*/
void insert(int i, T x);

/**
* 在线性表最后插入x元素
* @param x
*/
void append(T x);

/**
* 删除第i个元素并返回被删除对象
* @param i
* @return
*/
T remove(int i);

/**
* 删除线性表所有元素
*/
void removeAll();

/**
* 查找,返回首次出现的关键字为key元素
* @param key
* @return
*/
T search(T key);
}

 

3、主要实现方式:顺序存储实现和链式存储实现。

 

4.顺序存储实现——顺序表

package com.clarck.datastructure.linear;

/**
* 顺序表(线性表的顺序存储结构)类,实现线性表接口,T是泛型参数,指定任意类

* @author clarck

* @param <T>
*/
public class SeqList<T> implements LList<T> {
/**
* 对象数组,保护成员
*/
protected Object[] element;

/**
* 顺序表长度,记载元素个数
*/
protected int len;

/**
* 默认构造方法,创建默认容量的空表
*/
public SeqList() {
this(64);
}

/**
* 构造方法,创建容量为size的空表

* @param size
*/
public SeqList(int size) {
this.element = new Object[size];
this.len = 0;
}

/**
* 判断顺序表是否空,若空返回true,O(1)
*/
@Override
public boolean isEmpty() {
return this.len == 0;
}

/**
* 返回顺序表长度,O(1)
*/
@Override
public int length() {
return this.len;
}

/**
* 返回第i(≥0)个元素。若i<0或大于表长则返回null,O(1)
*/
@SuppressWarnings("unchecked")
@Override
public T get(int i) {
if (i >= 0 && i < this.len) {
return (T) this.element[i];
}
return null;
}

/**
* 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作
*/
@Override
public void set(int i, T x) {
if (x == null)
return;

if (i >= 0 && i < this.len) {
this.element[i] = x;
} else {
throw new IndexOutOfBoundsException(i + ""); // 抛出序号越界异常
}
}

/**
* 顺序表的插入操作 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素
*/
@Override
public void insert(int i, T x) {
if (x == null)
return;

// 若数组满,则扩充顺序表容量
if (this.len == element.length) {
// temp也引用elements数组
Object[] temp = this.element;
// 重新申请一个容量更大的数组
this.element = new Object[temp.length * 2];
// 复制数组元素,O(n)
for (int j = 0; j < temp.length; j++) {
this.element[j] = temp[j];
}
}

// 下标容错
if (i < 0)
i = 0;

if (i > this.len)
i = this.len;

// 元素后移,平均移动len/2
for (int j = this.len - 1; j >= i; j--) {
this.element[j + 1] = this.element[j];
}
this.element[i] = x;
this.len++;
}

/**
* 在顺序表最后插入x元素
*/
@Override
public void append(T x) {
insert(this.len, x);
}

/**
* 顺序表的删除操作 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。
*/
@SuppressWarnings("unchecked")
@Override
public T remove(int i) {
if (this.len == 0 || i < 0 || i >= this.len) {
return null;
}

T old = (T) this.element[i];
// 元素前移,平均移动len/2
for (int j = i; j < this.len - 1; j++) {
this.element[j] = this.element[j + 1];
}
this.element[this.len - 1] = null;
this.len--;
return old;
}

/**
* 删除线性表所有元素
*/
@Override
public void removeAll() {
this.len = 0;
}

/**
* 查找,返回首次出现的关键字为key元素
*/
@SuppressWarnings("unchecked")
@Override
public T search(T key) {
int find = this.indexOf(key);
return find == -1 ? null : (T) this.element[find];
}

/**
* 顺序表比较相等 比较两个顺序表是否相等 ,覆盖Object类的equals(obj)方法,O(n)
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;

if (obj instanceof SeqList) {
SeqList<T> list = (SeqList<T>) obj;
// 比较实际长度的元素,而非数组容量
for (int i = 0; i < this.length(); i++) {
if (!this.get(i).equals(list.get(i))) {
return false;
}
}
return true;
}
return false;
}

/**
* 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回-1

* @param key
* 可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
* @return
*/
public int indexOf(T key) {
if (key != null) {
for (int i = 0; i < this.len; i++) {
// 对象采用equals()方法比较是否相等
if (this.element.equals(key)) {
return i;
}
}
}
return -1;
}

// 返回顺序表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法
public String toString() {
String str = "(";
if (this.len > 0)
str += this.element[0].toString();
for (int i = 1; i < this.len; i++)
str += ", " + this.element[i].toString();
return str + ") "; // 空表返回()
}
}



复制代码
线性表的顺序表示和实现,测试类:

复制代码
package com.clarck.datastructure.linear;

/**
* 线性表的顺序表示和实现

* @author clarck

*/
public class SeqList_test {
public static void main(String args[]) {
//空表
SeqList<String> lista = new SeqList<String>(4);
System.out.println("lista is empty: "+lista.toString());
//扩容
for (int i = 5; i >= 0; i--) {
lista.insert(i, new String((char)('A' + i) + ""));
}
System.out.println("lista insert finished: "+lista.toString());
lista.set(3, new String((char)(lista.get(3).charAt(0) + 32) + ""));
System.out.println("lista set 3 : "+lista.toString());
lista.remove(0);
System.out.println("lista remove 0 position: "+lista.toString());
lista.remove(3);
System.out.println("lista remove 3 position: "+lista.toString());
}
}


输出结果:

lista is empty: () 
lista insert finished: (A, F, B, E, C, D) 
lista set 3 : (A, F, B, e, C, D) 
lista remove 0 position: (F, B, e, C, D) 
lista remove 3 position: (F, B, e, D)


 

5、线性表的顺序存储实现说明
1).线性表的顺序存储结构:

a1.其数据的存储顺序和逻辑的顺序一样。
(注意:顺序表和数组的关系,一般顺序表需要数组来实现,顺序表的长度需要小于等于数组的长度)

a2.每个元素都有存储地址,设a0的存储地址是Loc(a0),每个元素占用c字节,则ai的存储地址为Loc(ai)=Loc(a0)+i*c
(注意:1、顺序表元素ai的存储地址是它在线性表中位置i的线性函数,与线性表长度无关
2、计算一个元素地址所需的时间是一个常量,与元素位置i无关(即任何一个元素的元素复杂度都为O(l))。

a3.顺序表是一种随机存储结构(就是存储的顺序可以跟物理顺序不一样,虽然他们排列的时候一样)。
(注意:顺序表通常是采用数组存储数据元素的,而数组只能赋值,取值操作,不能直接插入等等)


2).顺序表类(线性表是实在的类,且有几个,我们这边研究的是SepList类,因为其是唯一一个顺序表类。)
假设SepList类表示顺序表,其需要声明实现线性表接口LList,和一般类似于SepList类中有两个私有成员变量element,和len。
elements是一个存放线性表元素的一维数组,元素类型为T,len表示线性表长度,len<=element.length。
(注意:约定线性表中数据元素不能是空对象)



3).顺序表的插入操作
c1.插入操作的原理:在顺序表元素ai位置上插入元素x,首先必须将元素a0,a1,...,a n-1向后移动,空出一个位置,然后将x插入。
(注意:如果数据已满,再插入的话会数据溢出,解决这个问题的方法是申请一个更大容器的数组
并复制全部的数组元素,这样就扩充了顺序表的容量。)

c2:插入和扩充代码例子:
需要用的方法为:insert(int,T):在第i个位置上加元素x
append(T):在顺序表最后插入x对象

public void insert(int i,T x)
{ //插入x作为第i个元素,不能插入null

if(x==null){
return;}//不能添加null

if(this.len==element.length) //如果数组已满,则扩充顺序表容量
{
Object[] a=this.element; //a也引用elements数组,目的就是为了让下面数组增量的时候可以使用其他符号,
//不用this.element.length,从而混淆了我们思路和在后面的步骤中还需要利用原本的信息(包括元素和长度等)。

this.element=new Object[a.length*2] ; //重新申请一个容量更大的数组

for(int j=0;j<a.length;j++){ //复制数组元素
this.element[j]=a[j]; }


if(i<0) {
i=0; 
}//下标过小,把他调成第一位

if(i>this.len){
i=this.len; }//下标过大,把他调到线性表的最后一位

for(int j=this.len-1;j>=i;j--){ //元素后移,平均移动len/2(注意:往里面移动,从小到大,往外面移动,从大到小)
this.element[j+1]=this.element[j];
}

this.element[i]=x; //把对象x放在指定i的位置上

this.len++;//线性表的长度加1.
}

public void append(T x){
insert(this.len,x);
}//在顺序表最后插入x对象

 

4):顺序表的删除操作:
d1.原理:将我们要制定的元素之后一个一个元素往前挪,之后最后一个元素等于null。

d2:删除操作代码例子:
方法:remove(int)
removeAll()//删除所有的元素
public T remove(int i) //删除角标为i的元素,若操作成功返回被删除元素(对象),否则返回null
{
if(this.len==0||i<0||i>=this.len){
return null; }

T old=(T)this.element[i];//保存被删除元素,以便返回

for(int j=i;j<this.len-1;j++){ //元素前移,平均移动len/2(注意:往里面移动,从小到大,往外面移动,从大到小)
this.element[j]=this.element[j+1];
}

this.element[this.len-1]=null; 
this.len--;
return old;
}

public void removeAll{ this.len=0;} //删除顺序表所有元素



5).对于顺序表类说明
a.SepList<T>类声明泛型类,类型形式参数T代表线性表元素的数据类型,且T必须是类,不可以是int等等类型 
b.成员变量必须是私有权限
c.默认构造方法(没有构造函数的时候按照其数据类型系统自动初始化)
d.可自动扩充容量(当插入一个元素时,如果数组elements已满,为了能使插入操作正常运行,insert()方法创建一个容量是原数组
2倍的新数组,并将原数组中的元素复制到新数组中)
e.指定元素序号不正确时的操作处理:
e1:方法返回错误信息
e2:抛出异常
(注意:一个空对象调用方法的时候java会抛出空对样异常NullPointerException。)

6).顺序表操作的效率分析:顺序表的静态特性很好,动态特性很差。
说明:a.顺序表元素的物理存储顺序直接反映线性表元素的逻辑顺序,顺序表是一种随机存取结构。
(顺序表实现了线性表抽象数据类型所要求的基本操作,不仅存取元素ai的时间复杂度是
o(l),而且获得ai的前驱元素和后继元素的时间复杂度也是o(l))
b.插入和删除操作效率低。
(怎么说?b1:每插入或删除一个元素,可能需要移动大量元素,其平均移动次数是顺序表长度的一半。
b2:数组容量不可以更改,存在因容量小造成数据溢出或因容量过大造成内存资源浪费的问题。

7).顺序表的的浅拷贝和深拷贝
a.顺序表的浅拷贝:复制成员变量的值。
注意:浅拷贝构造方法复制数组引用,使得两个对象的elements变量只拥有一个数组,但是在修改,插入,删除
等操作结果不是相互影响的。比如说,a对象删除了第三个数,但b对象他不会知道这个数是被删除的,还
认为它存在,如果输出就会产生错误。
实现:SeqList<String> listb=new SeqList<String>(lista) ; //浅拷贝构造方法

b.顺序表的深拷贝
b1.概念:就是不仅要复制对象的所有基本类型成员变量值,还要重新申请应用类型变量占用的动态存储空间,并复制其中所有对象,这种复制方式称为深拷贝。

b2.代码表现:
public SeqList(SeqList<T> list) //深拷贝方法
{
this.len=list.len;
this.element=new Object[list.element.length]; //申请一个数组
for(int i=0;i<list.element.length;i++) { //复制数组元素,o(n)

this.element[i]=list.element[i]; //对象应用,没有创建新对象;
}
}

b3.关于两者对象的元素改变之间的相互影响:listb.elements申请新的数组空间,使得listb.elements和listb.elements分别引用
一个数组,进行插入,删除等操作,两者不会相互影响。但是由于对象赋值是
引用赋值,导致lista.elements和listb.elements两个数组对应元素引用相同实例,修改其中某个
实例,将同时改变另一个数组元素引用的该实例。
(同一个数组,一个对象改变了,另外对象不知,多个数组,同个实例,其实例改变了,
其余数组的实例也会改变)

8).比较顺序表是否相等的方法
a.相等需要判断的条件:1先长度相等 2里面的元素相等
b.代码例子:
public boolean equals(Object obj)
{
if(this==obj)
return true;

if(obj instanceof Seqlist)
{
SeqList<T> list=(SeqList<T>)obj;
if(this.length()==list.length())
{
for(int i=0;i<this.length();i++) //比较实际长度的元素,而非数组容量
if(!(this.get(i).equals(list.get(i)))){ //运行时多态
return false; }
return true;
}
}
return false;
}


9).线性表的应用————使用顺序表类SeqList求解约瑟夫环问题

约瑟夫环运作如下:
1、一群人围在一起坐成环状(如:N)
2、从某个编号开始报数(如:K)
3、数到某个数(如:M)的时候,此人出列,下一个人重新报数
4、一直循环,直到所有人出列,约瑟夫环结束

下面是使用顺序表类SeqList求解约瑟夫环问题的代码:
[java] view plain copy print?
package com.clarck.datastructure.linear; 

/** 
* 使用顺序表类SeqList求解约瑟夫环问题。 

* @author clarck 

*/ 
public class Josephus { 
/** 
* 创建约瑟夫环并求解 

* @param number 
* 参数指定环长度 
* @param start 
* 起始位置 
* @param distance 
* 计数 
*/ 
public Josephus(int number, int start, int distance) { 
// 这是应用,所以先建立表格,并往里面传数据。 
SeqList<String> list = new SeqList<String>(number); 
for (int i = 0; i < number; i++) { 
list.append((char) ('A' + i) + ""); 

System.out 
.print("约瑟夫环(" + number + "," + start + "," + distance + "),"); 
System.out.println(list.toString());

//之后就是对数据的处理。

int i = start; 
while (list.length() > 1) { 
// 计数按循环规律变化,顺序表可看作是环形结构 
i = (i + distance - 1) % list.length(); 
// 删除指定位置对象 
System.out.print("删除" + list.remove(i).toString() + ","); 
System.out.println(list.toString()); 

System.out.println("被赦免者是" + list.get(0).toString()); 


public static void main(String args[]) { 
new Josephus(5, 0, 2); 


运行结果如下:
[plain] view plain copy print?
约瑟夫环(5,0,2),(A, B, C, D, E) 
删除B,(A, C, D, E) 
删除D,(A, C, E) 
删除A,(C, E) 
删除E,(C) 
被赦免者是C

原创粉丝点击