一、线性表的定义
线性表是同一类型数据的一个有限序列,数据与数据之间在逻辑上存在着线性关系,即一对一的联系。线性表中元素的个数称为线性表的长度。线性表的一般表示为:(a1,a2,a3,a4.。。。an)。即用一对圆括号把构成线性表的所有数据元素括起来,元素之间的先后排列次序蕴涵着其线性关系。线性表中的第一个元素a1称为表头元素,an称为表尾元素,线性表长度n>=0。
若需给一个线性表命名,则相应的标识符通常采用大写。例如,可把上面线性表用A表示,即A=(a1,a2,a3,a4.。。。an)。
线性表中的元素是在逻辑上线性有序的,用二元组表示为: linear_list=(A,R) 其中:A={ ai | 1<=i<=n ,n>=0 } R={ <ai ,ai+1> | 1<=i<n-1 } 。
二、线性表与集合的区别
线性表同集合在操作上的本质区别:线性表中的元素通常是变化的,但元素之间的逻辑关系应保持不变,当从一个位置上删除一个元素后,其后的所有元素都要依次前移一个位置,同样,当向一个位置插入一个元素时,该位置的原有元素及后面的所有元素都要依次后移一个位置。
从集合中删除一个元素后,不需要移动元素,只需要简单地用最后一个元素来填补空出的位置;向集合中插入元素时不需要考虑插入位置,因为不需要移动任何元素,只简单地放到表头或者表尾即可。
线性表与集合在元素的重复值上也有区别,集合中的元素不允许重复,而线性表中的元素值允许重复。反映到插入算法上:当向线性表插入元素时,不需要首先从线性表中查找,看是否存在被插入的元素,可进行直接插入。
线性表中的元素类型同集合一样,可以为任何一种数据类型。为了能够进行线性表中元素大小的比较,实际的元素类型还应该同时继承系统提供的java.lang.Comparable接口并实现其中的comparaTo成员方法,这样才能够利用comparaTo方法来比较元素的大小,以便能够对线性表进行按值插入或排序等运算。在系统提供的类中,如各种简单类型的包装类和字符串等,都实现了Comparable接口,都带有comparaTo方法的定义。若传递给线性表中元素对象的类型为用户自己定义的类,则定义该类时要实现Comparable接口,并在定义体中给出comparaTo方法的定义。
public class Student implements Comparable {
private String numb; //学号
private String name; //姓名
private int grade; //成绩
public Student(String numb, String name, int grade) {
this.numb = numb;
this.name = name;
this.grade = grade;
}
public boolean equals(Object obj) { //定义比较两学生相同的方法
Student s=(Student)obj;
return numb.equals(obj); //假定通过对学号的比较来判断相等
}
public String toString() { //定义用于输出的学生记录的字符串形式
return numb+" "+name+" "+grade;
}
public int compareTo(Object o) { //定义比较两个学生大小的方法
Student s=(Student)o;
return grade-s.grade; //假定通过对成绩的比较来判断大小
}
}
假定以Student为元素类型的一个线性表list所含元素如下表所示:
学生成绩表
Number(学号) | Name(姓名) | Grade(成绩) |
201 | xuxk | 73 |
202 | weir | 84 |
203 | xucong | 65 |
204 | tongy | 90 |
205 | chat | 82 |
下面首先给出具有Student类的x,y,z引用对象的定义并创建:
Student x=new Studnet("206","baojun",80); //给出一条学生新记录
Student y=new Studnet("203","",0); //只给出学号用于查找
Student z=new Studnet("210","ningchen",88); //给出另外一条学生新记录
接着给出对线性表list进行的一组操作:
(1)把x插入到线性表list的第三个元素位置
list.add(x,3); //list变为(201,202,206,203,204,205)
(2)从表中删除第二个元素,返回该元素值
Student a1=(Student)list.remove(2); //list变为(201,206,203,204,205)
(3)从表头开始顺序查找等于y值的第1个元素
int b1=list.find(y); //返回的元素序号为3,被赋给b1
(4)用z的值重写第5个元素的值
list.modify(z,5); //list变为(201,206,203,204,210)
(5)正向遍历输出list表中的所有元素:
list.forward(); //输出结果
numb(学号) | name(姓名) | grade(成绩) |
201 | xuxk | 73 |
206 | baojuan | 80 |
203 | xucong | 65 |
204 | tongy | 90 |
210 | ningchen | 88 |
(6)对list中的所有记录按照学生成绩进行排序
List list1=list.sort(); //将排序结果所形成的线性表返回给list1
(7)反向遍历输出list1表中的所有元素
list.backward(); //输出结果
namb(学号) | name(姓名) | grade(成绩) |
204 | tongy | 90 |
210 | ningchen | 88 |
206 | baojuan | 80 |
201 | xuxk | 73 |
203 | xucong | 65 |
三、线性表的顺序存储结构和操作实现
3.1 线性表接口List的定义如下:
public interface List {
Object value(int pos); //返回线性表中第pos个元素的值
boolean add(Object obj,int pos); //向第pos个元素位置插入一个元素obj
Object remove(int pos); //删除第pos个元素并返回,若不存在则返回空
int find(Object obj); //从头查找等于obj值的第一个元素,返回序号
boolean modify(Object obj,int pos); //用obj修改线性表中给定序号pos的元素值
boolean isEmpty(); //判断线性表是否为空
int size(); //返回线性表长度
void forward(); //正向遍历线性表中的所有元素
void backward(); //反向遍历线性表中的所有元素
void clear(); //清除线性表中的所有元素,使之变为空表
List sort(); //根据当前线性表排序生成新的有序表并返回
}
3.2 顺序线性表类SequenceList的具体定义如下:
public class SequenceList implements List {
final int minSize=10; //数组初始长度
private Object[] listArray; //数组声明,元素类型为Object
private int len; //线性表的当前长度
//无参构造函数定义
public SequenceList() {
len=0; //线性表初始为空,即长度为0
listArray=new Object[minSize]; //数组初始长度为minSize的值10
}
//带初始长度的构造函数定义
public SequenceList(int n) {
if(n<minSize)
{
n=minSize; //条件成立时将n修改为固定值minSize
}
len=0; //线性表初始长度为0
listArray=new Object[n]; //数组初始长度为n的值
}
//返回线性表中第pos个元素的值
public Object value(int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法得到元素!");
return null; //返回空值,表示参数无效
}
return listArray[pos-1]; //返回线性表中第pos个元素的值
}
//向第pos个元素位置插入一个元素obj
public boolean add(Object obj, int pos) {
if(pos<1||pos>len+1) //检查插入位置pos是否有效
{
System.out.println("参数pos的值不合法,无法插入元素!");
return false;
}
if(len==listArray.length) //对数组空间用完情况进行再分配
{
Object[] p=new Object[len*2];
for(int i=0;i<len;i++)
{
p[i]=listArray[i]; //复制原线性表
listArray=p; //使listArray指向新数组空间
}
}
for(int i=len-1;i>=pos-1;i--) //为空出位置而移动元素
{
listArray[i+1]=listArray[i]; //从表尾向前依次后移元素
}
listArray[pos-1]=obj; //把obj赋给已空出的元素位置
len++; //线性表长度增1
return true; //返回真,表示插入成功
}
//删除第pos个元素并返回,若不存在则返回空
public Object remove(int pos) {
if(pos<1||pos>len+1)
{
System.out.println("参数pos的值不合法,无法删除元素");
return null;
}
Object x=listArray[pos-1]; //暂存第pos位置的元素到x
for(int i=pos; i<len-1;i++) //依次前移第pos位置以后的元素
{
listArray[i]=listArray[i+1];
}
len--; //长度减1
return x; //返回删除的元素
}
//从头查找等于obj值的第一个元素,返回序号
public int find(Object obj) {
for(int i=0;i<len;i++) //遍历所有元素,从表头开始向后扫描
{
if(listArray[i].equals(obj)) //查找成功返回序号
{
return i+1;
}
}
return -1;
}
//用obj修改线性表中给定序号pos的元素值
public boolean modify(Object obj, int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法修改元素!");
return false;
}
listArray[pos-1]=obj; //用obj的值更新第pos个元素的值
return true; //返回真,表示修改成功
}
//判断线性表是否为空
public boolean isEmpty() {
return len==0;
}
//返回线性表长度
public int size() {
return len;
}
//正向遍历线性表中的所有元素
public void forward() {
for(int i=0;i<len;i++)
{
System.out.println(listArray[i].toString());
}
}
//反向遍历线性表中的所有元素
public void backward() {
for(int i=len-1;i>=0;i--)
{
System.out.println(listArray[i].toString());
}
}
//清除线性表中的所有元素,使之变为空表
public void clear() {
len=0; //置线性表的长度为0
}
//根据当前线性表排序生成新的有序表并返回
public List sort() {
//使用插入排序法,进行排序
SequenceList list=new SequenceList(len); //创建线性表list
list.len=len; //新线性表长度与当前表相同
for(int i=0;i<len;i++) //把当前表复制到新线性表中
{
list.listArray[i]=listArray[i];
}
int i,j; //定义内外循环控制变量
for(i=1;i<list.len;i++) //外循环变量i取值len-1次
{
Object x=list.listArray[i]; //暂存本次待插入的元素到x中
for(j=i-1;j>=0;j--) //在有序表中寻找插入位置
{
Object y=list.listArray[j]; //待比较元素暂存y中
if(((Comparable)x).compareTo(y)<0) //若条件成立则继续向前查找位置
{
list.listArray[j+1]=list.listArray[j];//向后移动元素
}
else
break; //已找到插入位置,它是已空出的j+1位置
}
list.listArray[j+1]=x; //将x的值(即本次待排序的元素)写入空位置
}
return list; //整个排序完成后,返回新的有序线性表
}
}
四、有序线性表的定义和实现
1、有序线性表接口的定义
有序线性表是指线性表中的元素是按照值或关键字的大小先后有序(升序或降序)排列的。对有序线性表进行运算除了继承一般线性表的所有运算外,还有按值或关键字进行插入、删除和查找元素等特殊运算。下面首先给出有序线性表的接口定义:
public interface SortedList extends List {
//有序线性表接口也要继承一般线性表接口
void insert(Object obj); //向有序表按值插入一个元素,运算后仍有序
Object delect(Object obj); //按值删除一个元素并返回,运算后仍有序
int check(Object obj); //按值查找元素,返回元素序号或-1
}
2、顺序存储的有序线性表类的定义
继承一般的线性表类,并实现有序线性表接口中新方法的定义,以及必要的构造方法的定义。
插入、删除和查找方法的定义。
2.1 插入元素方法
向有序表中按值插入一个元素,应让待插入的元素值obj从有序表的第一个元素开始向后依次同每个元素值value(i)进行比较,一旦发现obj小于value(i)时,则第i个元素的位置就是待插入元素obj的位置,接着调用add(obj,i)方法,把obj的值插入到第i个元素的位置,此位置之前的元素都小于等于该元素,之后的元素都大于该元素,所有插入操作后,有序表仍然保持其有序性。
//向有序表按值插入一个元素,运算后仍有序
public void insert(Object obj) {
int i;
for(i=1;i<=this.size();i++) //循环寻找插入位置i
{
if(((Comparable)obj).compareTo(this.value(i))<0)
{
break;
}
this.add(obj, i); //把obj的值插入到第i个位置
}
}
2.2 删除元素方法
从有序表中删除一个元素,通常是删除与给定的对象值或关键字相同的,从表头开始向后顺序查找到的第一个元素,此删除方法也是利用一个循环。从表头第一个元素开始,让待查找的给定元素值obj同他进行比较,若obj较小,则应返回空值,表示删除失败,因为后面的元素值则更大,没有继续比较和查找的必要;若obj等于比较到的当前元素值,则调用remove()方法从有序表中删除该元素并返回原有的元素值;否则继续循环,同有序表中的下一个元素进行比较和处理。党比较完所有元素后,正常结束循环查找过程,表明有序表中不存在给定值为obj的元素,应返回空值,结束算法。
//按值删除一个元素并返回,运算后仍有序
public Object delect(Object obj) {
//删除有序表中其值等于obj的元素并返回,若不存在则返回空
for(int i=1;i<=this.size();i++) //从表头开始顺序向后查找
{
Object x=this.value(i); //取出第i个元素
if(((Comparable)obj).compareTo(x)<0) //查找失败返回空值
{
return null;
}
if(((Comparable)obj).compareTo(x)==0)//查找成功删除元素
{
return this.remove(i);
}
}
return null; //比较完所有元素后没有找到,返回空值
}
2.3 查找元素方法
在有序表上按值查找元素的方法有两种,一种是顺序查找方法,一种是二分查找方法。此处用顺序查找方法。
//按值查找元素,返回元素序号或-1
public int check(Object obj) {
//从有序表中按值顺序查找元素,返回元素序号,若查找失败则返回-1
for(int i=1;i<=this.size();i++)
{
Object x=this.value(i); //取出第i个元素
if(((Comparable)obj).compareTo(x)<0)
{
return -1; //查找失败返回-1
}
if(((Comparable)obj).compareTo(x)==0)
{
return 1; //查找成功返回序号
}
}
return -1; //查找失败返回-1
}
完整定义和实现类:
public class SequenceSortedList extends SequenceList implements SortedList {
//无参构造方法定义
public SequenceSortedList() {
super();
}
//带初始长度的构造方法定义
public SequenceSortedList(int n) {
super(n);
}
//参数为线性表接口的构造方法定义
public SequenceSortedList(List list) { //参数为线性表接口的构造方法定义
super(list.size()); //建立空的有序表
for(int i=1;i<=list.size();i++) //循环次数为参数表中元素的个数
{
this.insert(list.value(i)); //把每个元素按值插入到有序表中
}
}
//向有序表按值插入一个元素,运算后仍有序
public void insert(Object obj) {
int i;
for(i=1;i<=this.size();i++) //循环寻找插入位置i
{
if(((Comparable)obj).compareTo(this.value(i))<0)
{
break;
}
this.add(obj, i); //把obj的值插入到第i个位置
}
}
//按值删除一个元素并返回,运算后仍有序
public Object delect(Object obj) {
//删除有序表中其值等于obj的元素并返回,若不存在则返回空
for(int i=1;i<=this.size();i++) //从表头开始顺序向后查找
{
Object x=this.value(i); //取出第i个元素
if(((Comparable)obj).compareTo(x)<0) //查找失败返回空值
{
return null;
}
if(((Comparable)obj).compareTo(x)==0)//查找成功删除元素
{
return this.remove(i);
}
}
return null; //比较完所有元素后没有找到,返回空值
}
//按值查找元素,返回元素序号或-1
public int check(Object obj) {
//从有序表中按值顺序查找元素,返回元素序号,若查找失败则返回-1
for(int i=1;i<=this.size();i++)
{
Object x=this.value(i); //取出第i个元素
if(((Comparable)obj).compareTo(x)<0)
{
return -1; //查找失败返回-1
}
if(((Comparable)obj).compareTo(x)==0)
{
return 1; //查找成功返回序号
}
}
return -1; //查找失败返回-1
}
}
五、线性表的链接存储结构和操作实现
与集合的存储结构相同,线性表既可以采用顺序存储也可以采用链接存储,当采用链接存储时,由于相邻元素所对应的存储结点可以不连续安排,它是通过链接指针反映结点之间的先后逻辑关系的,所以在链接表的任何位置插入一个新结点时,只需要链接好前后结点的指针即可,不需要像顺序存储那样移动其后面的每个元素值,从而能够有效的节省运算的时间。链接存储的另一个优点就是:不存在存储空间已满并进行重新分配空间和复制数据的情况。
线性表的链接存储结构可以采用单链接,也可以采用双链接。下面采用单链接实现:
1、向线性表中给定位置上插入一个元素可分为以下几步:
第1步:对参数pos值进行有效性判断,若pos值无效,则返回假表示插入失败。
第2步:扫描得到单链表中第pos个结点位置,此扫描需要设置前后两个指针,一个指向待扫描结点,另一个指向其前驱结点,因为插入一个结点时,需要修改其前驱结点的指针域的值。
第3步:根据插入元素创建一个新结点,并被链接到单链表中,即给单链表中第pos-1个位置结点的指针域赋予新插入结点的引用,同时新插入结点的指针域赋予原来第pos个位置结点的引用。
第4步:使线性表长度域len的值增1,然后返回真表示插入成功。
算法如下:
//向第pos个元素位置插入一个元素obj
public boolean add(Object obj, int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法插入元素,返回假!");
return false;
}
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head; //p指向前驱结点
Node q=head.next; //q初始指向第1个结点
while(num<pos)
{
p=q;
q=q.next; //p,q依次指向下一个结点
num++; //num的值增1
}
p.next=new Node(obj,q); //在第pos个位置(即p和q结点之间)插入新结点
len++; //线性表长度增1
return true; //返回真,表示插入成功
}
2、从线性表中删除给定位置上的元素
从链接存储的线性表中删除元素,可以按照以下步骤进行:
第1步:检查所给的位置参数pos是否有效,若无效则给出提示信息并返回空值。
第2步:从表头开始,顺序扫描到第pos个结点,需要保存该结点的前驱结点的位置,以便修改它的指针域。
第3步:从单链表中删除第pos个结点,即将它的前驱结点的指针域的值修改为它的后继结点的引用。
第4步:使线性表长度域的值减1。
第5步:返回被删除结点的值。
算法如下:
//删除第pos个元素并返回,若不存在则返回空
public Object remove(int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法删除元素,返回空值!");
return null;
}
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head,q=head.next; //p指向其前驱,q初始指向表头结点
while(num<pos) //循环扫描到第pos个结点为止
{
num++;
p=q;
q=q.next;
}
p.next=q.next; //删除第pos个元素的结点(即q结点)
len--; //线性表长度减1
return q.element; //删除成功返回被删除元素
}
3、从线性表中查找具有给定值obj的第1个元素并返回所在位置
从带附加表头结点的循环单链接表示的线性表中查找给定值元素的步骤如下:
第1步:从表头开始顺序查找值为obj的元素结点,并记录下扫描过的结点数。
第2步:若扫描完整个单链表仍找不到给定值元素的结点,则返回-1表示查找失败。
第3步:返回已统计出的从表头到已查找到结点的结点数。
//从头查找等于obj值的第一个元素,返回序号
public int find(Object obj) {
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head.next; //p初始指向单链表的表头结点
while(p!=head&&p.element.equals(obj)==false)
{ //从表头向后顺序查找值为obj的结点并记数
num++;
p=p.next;
}
if(p==head)
{
return -1; //查找完整个链表后返回-1,表示查找失败
}
return num; //查找成功返回元素结点的位置序号
}
4、反向遍历输出线性表中的所有元素
4.1 非递归方法
//反向遍历线性表中的所有元素
public void backward() {
Object [] a=new Object[len]; //保存扫描得到的结点值
int i=0;
Node p=head.next; //p初始指向单链表的表头结点
while(p!=head)
{
a[i]=p.element; //所有元素值都放入a中
p=p.next;
}
for(i=len-1;i>=0;i--) //按反序输出每个元素值
{
System.out.println(a[i].toString());
}
}
4.2 递归方法
包括一个递归方法和一个非递归方法,非递归方法用来起到驱动另一个递归方法执行和外部调用接口的作用;递归方法带有一个结点引用参数,起到反向遍历输出单链表的作用。非递归的驱动方法把指向表头结点的引用作为实际参数去调用递归方法。
非递归方法定义为公用的,以方便外部对象调用:
public void backward() //反向遍历输出单链表中每个元素的外部驱动方法
{
backward(head.next); //利用指向表头结点的指针去调用内部私用的递归方法
}
递归方法定义为私有的,只被限定在类中调用。
private void backward(Node p) //反向遍历输出线性表中的每个元素的递归方法
{
if(p==head)
{
return; //扫描到表尾返回
}
else
{
backward(p.next); //用指向后继结点的指针递归调用该方法
}
System.out.println(); //每次递归调用返回后输出结点值
}
5、各个方法的具体定义和实现如下:
//链接线性表类的定义
public class LinkList implements List {
private Node head; //表头指针
private int len; //线性表(单链表)的当前长度
//无参构造函数定义
public LinkList() {
//初始化线性表为空
len=0; //线性表的初始长度为0
head=new Node(null); //初始建立由head指向的附加表头结点
head.next=head; //构成单链接循环空表
}
//返回线性表中第pos个元素的值
public Object value(int pos) {
// 返回线性表中第pos个元素的值,若pos值无效则返回空值
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法得到元素,返回空值!");
return null;
}
int num=1;
Node p=head.next; //用num保存将要扫描到的结点数,初值为1
while(num<pos) //循环扫描单链表,直到第pos个结点为止
{
num++;
p=p.next;
}
return p.element; //返回第pos个结点保存的元素值
}
//向第pos个元素位置插入一个元素obj
public boolean add(Object obj, int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法插入元素,返回假!");
return false;
}
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head; //p指向前驱结点
Node q=head.next; //q初始指向第1个结点
while(num<pos)
{
p=q;
q=q.next; //p,q依次指向下一个结点
num++; //num的值增1
}
p.next=new Node(obj,q); //在第pos个位置(即p和q结点之间)插入新结点
len++; //线性表长度增1
return true; //返回真,表示插入成功
}
//删除第pos个元素并返回,若不存在则返回空
public Object remove(int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法删除元素,返回空值!");
return null;
}
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head,q=head.next; //p指向其前驱,q初始指向表头结点
while(num<pos) //循环扫描到第pos个结点为止
{
num++;
p=q;
q=q.next;
}
p.next=q.next; //删除第pos个元素的结点(即q结点)
len--; //线性表长度减1
return q.element; //删除成功返回被删除元素
}
//从头查找等于obj值的第一个元素,返回序号
public int find(Object obj) {
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head.next; //p初始指向单链表的表头结点
while(p!=head&&p.element.equals(obj)==false)
{ //从表头向后顺序查找值为obj的结点并记数
num++;
p=p.next;
}
if(p==head)
{
return -1; //查找完整个链表后返回-1,表示查找失败
}
return num; //查找成功返回元素结点的位置序号
}
//用obj修改线性表中给定位置的元素值
public boolean modify(Object obj, int pos) {
if(pos<1||pos>len)
{
System.out.println("参数pos的值不合法,无法修改元素,返回假");
return false;
}
int num=1; //用num保存将要扫描到的结点数,初值为1
Node p=head.next; //p初始指向单链表的表头结点
while(num<pos) //扫描到pos位置为止
{
num++;
p=p.next;
}
p.element=obj; //修改第pos个结点的值
return true; //返回真表示结点修改成功
}
//判断线性表是否为空
public boolean isEmpty() {
return len==0;
}
//返回线性表长度
public int size() {
return len;
}
//正向遍历线性表中的所有元素
public void forward() {
Node p=head.next; //p初始指向单链表的第一个元素结点
while(p!=head) //访问每个结点时输出它的值
{
System.out.println(p.element.toString());
p=p.next;
}
}
//反向遍历线性表中的所有元素
public void backward() {
Object [] a=new Object[len]; //保存扫描得到的结点值
int i=0;
Node p=head.next; //p初始指向单链表的表头结点
while(p!=head)
{
a[i]=p.element; //所有元素值都放入a中
p=p.next;
}
for(i=len-1;i>=0;i--) //按反序输出每个元素值
{
System.out.println(a[i].toString());
}
}
//清除线性表中的所有元素,使之变为空表
public void clear() {
len=0;
head.next=head;
}
//根据当前线性表排序生成新的有序表并返回
public List sort() {
LinkList list=new LinkList(); //创建list为一个链接类型的空表
Node r=head.next; //定义结点引用r,让它初始指向当前表头结点
while(r!=head) //扫描当前单链表中的每个结点,直到表尾止
{
Object x=r.element; //取出一个节点值到x中
Node p=list.head,q=p.next; //q初始指向list的表头结点,p为前驱
while(q!=list.head) //从list表中为有序插入x顺序查找位置
{
Object y=q.element; //顺序取出list表中一个结点值到y中
if(((Comparable)x).compareTo(y)<0)
{ //若x<y则x的插入位置在p和q之间
break;
}
p=q;q=q.next; //为继续查找插入位置而向下扫描
}
p.next=new Node(x,q); //把新结点插入到p和q结点之间
list.len++; //线性表长度增1
r=r.next; //用r扫描到当前线性表的下一个结点
}
return list; //返回链接存储的有序表
}
}
六、有序线性表的链接存储结构和操作实现
链接存储的有序线性表类需要继承链接线性表类LinkList和实现有序线性表接口SortedList。类的具体定义和各方法实现如下:
public class LinkSortedList extends LinkList implements SortedList{
//链接存储的有序表类继承链接线性表类并实现有序线性表接口
//无参构造函数
public LinkSortedList() {
super();
}
//参数为线性表的构造函数
public LinkSortedList(List list) {
super(); //建立一个空的有序连接表
for(int i=1;i<=list.size();i++) //循环次数为参数表中元素的个数
{
this.insert(list.value(i)); //把每个元素按值插入到有序表中
}
}
//向有序表按值插入一个元素
public void insert(Object obj) {
int i;
for(i=1;i<=size();i++) //依次扫描查找插入位置
{
if(((Comparable)obj).compareTo(value(i))<0)
{
break;
}
}
add(obj,i); //把obj插入到第i个元素位置
}
//按值删除一个元素并返回
public Object delect(Object obj) {
for(int i=1;i<=size();i++) //依次扫描参数表中的每个元素
{
if(((Comparable)obj).compareTo(value(i))<0)
{
return null;
}
if(((Comparable)obj).compareTo(value(i))==0)
{
return remove(i);
}
}
return null;
}
//按值查找一个元素并返回元素序号
public int check(Object obj) {
for(int i=1;i<=size();i++) //从表头开始依次向后查找
{
if(((Comparable)obj).compareTo(value(i))<0)
{
return -1;
}
if(((Comparable)obj).compareTo(value(i))==0)
{
return i;
}
}
return -1; //查找失败返回-1
}
}
七、线性表应用举例——多项式计算
1、多项式表示与求值
1.1 只保存多项式中每项系数的线性表表示与求值
static double polyValue(List poly,double x)
{
//poly为线性表接口List的引用,对应的实参可以是顺序表或者链接表
//x为一个双精度参数,此方法返回当x为一个具体值时的多项式的值
//用sum计算多项式的值,初始为x的n次方的系数a0的值
double sum=Double.parseDouble(poly.value(1).toString());
//循环嵌套计算多项式的值
for(int i=1;i<poly.size();i++)
{
//循环n次,n为x的最高次幂
//计算出一层括号内的值
sum=sum*x+Double.parseDouble(poly.value(i+1).toString());
}
//返回求出的多项式的值
return sum;
}
假定一个多项式为2x^5-6x^3+3x^2+5,对应的线性表为(2,0,-6,3,0,5),若采用顺序方式存储。
public static void main(String[] args)
{
List std=new SequenceList(); //定义并创建空的顺序表
double []a={2,0,-6,3,0,5}; //线性表元素被保存在数组中
for(int i=0;i<a.length;i++)
{
Object aa =new Double(a[i]); //将double类型转换为Object类型
std.add(aa,i+1); //建立多项式线性表
}
std.forward(); //输出所有元素
System.out.println("线性表长度:"+std.size());
double y=polyValue(std,2); //用x等于2计算出的多项式的值赋给y
System.out.println("x值为2时的多项式的值:"+y); //输出结果
}
控制台输出的结果如下:
2.0
0.0
-6.0
3.0
0.0
5.0
线性表长度:6
x值为2时的多项式的值:33.0
1.2 保存多项式中每项系数和指数的线性表表示与求值
在一个多项式中往往会出现许多缺项。例如:P(x)=7*x^60-3x^12+6x^5+1,其中只有4项,缺少57项,或者说57个因数项的系数均为0。若仍采用上述定义方式的线性表,则将浪费存储空间和运算时间,是不可取的。为此,通常采用另外一种形式的线性表表示,该线性表中的每个元素对应多项式中的一个非零项,每个元素包含两个域:系数域和指数域,用它们分别表示对应项中的系数ai和x的指数,并且线性表中的元素应安装指数的升序或降序排列,它是按指数有序的一个有序表。
上述P(x)多项式的这种线性表表示为:({ 1,0 },{ 6,5 },{ -3,12 },{ 7,60 })
若把线性表中的元素类型定义为Item类,则如下所示:
public class Item implements Comparable {
double coef; //系数
int exp; //指数
Item(double coef,int exp)
{
this.coef=coef;
this.exp=exp;
}
public String toString() { //返回对象的字符串表示,其中用字符^表示幂
return String.valueOf(coef)+"x^"+exp;
}
public int compareTo(Object obj) { //定义比较两因数项的大小
Item s=(Item)obj;
return exp-s.exp; //假定通过对指数的比较来判断大小
}
}
下面给出利用这种线性表进行多项式求值的算法。
static double polyValue(List poly,double x)
{
double sum=0; //给作为累加变量的sum赋初值为0
for(int i=0;i<poly.size();i++) //累加计算多项式的值。每次循环累加一个单项式的值
{
Item y=(Item)poly.value(i+1); //从线性表中取出一个因数项
sum+=y.coef*Math.pow(x, y.exp); //把新项的值累加到sum中
}
return sum; //返回所求结果
}
此算法中调用了在系统类Math中定义的静态方法pow(x1,x2),它能够计算出x1的x2次幂的值并返回。
假定要利用这个算法计算多项式2x^5-6x^3+3x^2+5和7x^60-3x^12+6x^5+1的值。具体描述如下:
public static void main(String[] args)
{
//定义并创建两个线性表,它们可以为任何存储结构,不妨一个为顺序,一个为链接
List std1=new SequenceList(); //定义并创建空的顺序表
List std2=new LinkList(); //定义并创建空的链接表
int [][]r1={{5,0},{3,2},{-6,3},{2,5}}; //分别用两个二维数组来表示两个多项式数据
int [][]r2={{1,0},{6,5},{-3,12},{7,60}};
for(int i=0;i<r1.length;i++)
{
std1.add(new Item(r1[i][0],r1[i][1]), i+1);
}
for(int i=0;i<r2.length;i++)
{
std2.add(new Item(r2[i][0],r2[i][1]), i+1);
}
double y1=polyValue(std1,2); //计算第一个多项式的值
double y2=polyValue(std2,2); //计算第二个多项式的值
System.out.println("x值为2时的两个多项式的值为:"+y1+","+y2);
}
控制板输出:
x值为2时的两个多项式的值为:33.0,8.0704505322479165E18
注意:注释掉LinkList类add(Object obj, int pos)方法的if...判断语句,否则会无法插入。
2、两个多项式相加
2.1 两个多项式相加的算法分析
两个多项式相加就是使对应项相加,若另一个多项式中没有对应项(即指数相同的项)则把它直接复制到结果中。例如: p1(x)=2x^5-6x^3+3x^2+5
p2(x)=9x^6-2x^5+3x^3-2x^2+4x+3
相加结果为: p3(x)=9x^6-3x^3+x^2+4x+8
设计此题的算法,需要带有两个表示多项式的线性表参数,作为两个加数项,假定分别用b1和b2表示,在算法体中还要定义和创建一个表示多项式的线性表参数,假定用b3表示,把计算b1和b2相加的结果放入b3中,当计算结束后返回b3。b1、b2和b3均可以采用顺序或链接的任一种存储方式,这里就选用此方法进行多项式的线性表表示。
进行两个多项式b1和b2相加得到b3的解题思路是:同时从各自表头开始顺序扫描两个线性表b1和b2,对应比较两个表中当前元素的指数域的大小,若b1元素的指数值较小,则把该元素插入到b3的表尾(b3初始为一个空表),若b2元素的指数值较小,则把b2的当前元素插入到b3的表尾,否则他们的指数值必然相等,把这两个元素的系数相加,若不为0则向b3的表尾插入一个新元素,该元素的系数为两个元素系数相加的结果,指数为任一个元素中的指数。当b1和b2中的一个元素被插入到b3后,接着将扫描相应的下一个元素,再继续进行比较和插入,直到出现任一个表中的所有元素被扫描完为止。最后当出现任一个线性表未处理完所有元素时,就把剩下的所有元素复制到b3的表尾。
根据分析编写出算法为:
static List polyAdd(List b1,List b2)
{
//实现两个多项式线性表的加法操作,返回相加结果
//定义表示结果多项式的线性表b3并初始化为空
List b3=new SequenceList();
//定义整型变量i1和i2,初始分别为b1和b2的第一个元素的序号
int i1=1,i2=1;
//当两个表同时不空时的处理过程
while(i1<=b1.size()&&i2<=b2.size())
{
//分别取出各自表中的当前元素的值到x1和x2中
Item x1=(Item)b1.value(i1),
x2=(Item)b2.value(i2);
//将b1中当前元素的值插入到b3的表尾
if(x1.exp<x2.exp)
{
b3.add(x1, b3.size()+1);
i1++;//将循环扫描b1的下一个元素
}
//将b2中当前元素的值插入到b3的表尾
else if(x1.exp>x2.exp)
{
b3.add(x2, b3.size()+1);
i2++;//将循环扫描b2的下一个元素
}
//将b1和b2的当前元素合并,若系数不为0则插入新元素到b3的表尾
else
{
double y=x1.coef+x2.coef;
if(Math.abs(y)>1.0E-6)//判断0条件为避免出现误差而采用
{
b3.add(new Item(y,x1.exp),b3.size()+1);
}
i1++;i2++;//两元素合并后,i1和i2均需后移
}
}
//将b1多项式中的剩余元素依次复制到b3的表尾
while(i1<=b1.size())
{
b3.add(b1.value(i1),b3.size()+1);
i1++;
}
//将b2多项式中的剩余元素依次复制到b3的表尾
while(i2<=b2.size())
{
b3.add(b2.value(i2),b3.size()+1);
i2++;
}
//返回结果多项式的线性表表示
return b3;
}
2.2 两个多项式相加的应用举例
假定要计算多项式2x^5-6x^3+3x^2+5和9x^6-2x^5+3x^3-2x^2+4x+3的和,他们对应的线性表(假定按指数从低到高排序)分别为:
{{ 5,0},{3,2},{-6,3},{2,5}}
{{3,0},{4,1},{-2,2},{3,3},{-2,5},{9,6}}
下面的程序采用顺序存储结构的线性表来计算这两个多项式的值,以及他们的相加结果的多项式。
public class Example3_7 {
public static void main(String [] args)
{
//定义并创建两个空表std1和std2
List std1=new SequenceList(); //定义并创建空的顺序表
List std2=new SequenceList();
double [][]r1={{5,0},{3,2},{-6,3},{2,5}}; //分别用两个二维数组来表示两个多项式数据
double [][]r2={{3,0},{4,1},{-2,2},{3,3},{-2,5},{9,6}};
//依次建立多项式所对应的线性表std1和std2
for(int i=0;i<r1.length;i++)
{
//依次向std1的表尾插入元素
std1.add(new Item(r1[i][0],(int)r1[i][1]), i+1);
}
for(int i=0;i<r2.length;i++)
{
//依次向std2的表尾插入元素
std2.add(new Item(r2[i][0],(int)r2[i][1]), i+1);
}
//用x的值2计算并输出每个多项式的值
double y1=polyValue(std1,2);
double y2=polyValue(std2,2);
System.out.println("x值为2时的两个多项式的值为:"+y1+","+y2);
//计算并输出两个多项式的和
List std3; //定义顺序存储的std3线性表
std3=polyAdd(std1,std2); //把返回多项式赋给std3
std3.forward(); //遍历输出std3中每个单项式数据
}
//下面给出利用这种线性表进行多项式求值的算法
static double polyValue(List poly,double x)
{
double sum=0; //给作为累加变量的sum赋初值为0
for(int i=0;i<poly.size();i++) //累加计算多项式的值。每次循环累加一个单项式的值
{
Item y=(Item)poly.value(i+1); //从线性表中取出一个因数项
sum+=y.coef*Math.pow(x, y.exp); //把新项的值累加到sum中
}
return sum; //返回所求结果
}
static List polyAdd(List b1,List b2)
{
//实现两个多项式线性表的加法操作,返回相加结果
//定义表示结果多项式的线性表b3并初始化为空
List b3=new SequenceList();
//定义整型变量i1和i2,初始分别为b1和b2的第一个元素的序号
int i1=1,i2=1;
//当两个表同时不空时的处理过程
while(i1<=b1.size()&&i2<=b2.size())
{
//分别取出各自表中的当前元素的值到x1和x2中
Item x1=(Item)b1.value(i1),
x2=(Item)b2.value(i2);
//将b1中当前元素的值插入到b3的表尾
if(x1.exp<x2.exp)
{
b3.add(x1, b3.size()+1);
i1++;//将循环扫描b1的下一个元素
}
//将b2中当前元素的值插入到b3的表尾
else if(x1.exp>x2.exp)
{
b3.add(x2, b3.size()+1);
i2++;//将循环扫描b2的下一个元素
}
//将b1和b2的当前元素合并,若系数不为0则插入新元素到b3的表尾
else
{
double y=x1.coef+x2.coef;
if(Math.abs(y)>1.0E-6)//判断0条件为避免出现误差而采用
{
b3.add(new Item(y,x1.exp),b3.size()+1);
}
i1++;i2++;//两元素合并后,i1和i2均需后移
}
}
//将b1多项式中的剩余元素依次复制到b3的表尾
while(i1<=b1.size())
{
b3.add(b1.value(i1),b3.size()+1);
i1++;
}
//将b2多项式中的剩余元素依次复制到b3的表尾
while(i2<=b2.size())
{
b3.add(b2.value(i2),b3.size()+1);
i2++;
}
//返回结果多项式的线性表表示
return b3;
}
}
控制板输出为:
x值为2时的两个多项式的值为:33.0,539.0
8.0x^0
4.0x^1
1.0x^2
-3.0x^3
9.0x^6
由输出结果可以知道,所求得的两个多项式的和为:9x^6-3x^3+x^2+8