2.1线性表的定义
2.1.1什么是线性表
线性表定义是具有相同特性的数据元素的一个有限序列。特征一:所有数据元素类型相同;特征二:线性表由有限个数据元素构成;特征三:线性表中的数据与位置相关,即每个元素有唯一的序号。
2.2线性表的顺序存储结构
顺序存储是线性表最常用的存储方式,它直接将线性表的逻辑结构映射到存储结构上。
2.2.1顺序表
线性表的顺序存储结构是把线性表中的所有元素按照其逻辑顺序依次存储到从计算机存储器中指定存储位置开始的一块连续的存储空间中
2.2.2线性表的基本运算算法在顺序表中的实现
//线性表的基本运算算法
public void CreateList(E[] a) //由a整体建立顺序表
{
int i,k=0;
for (i=0;i<a.length;i++) {
if (size==capacity) //出现上溢出时
updatecapacity(2*size); //扩大容量
data[k]=a[i];
k++; //添加的元素个数增加1
}
size=k; //设置长度
}
public void Add(E e) //在线性表的末尾添加一个元素e
{
if (size==capacity) //顺序表空间满时倍增容量
updatecapacity(2*size);
data[size]=e;
size++; //长度增1
}
public int size() //求线性表长度
{
return size;
}
public void Setsize(int nlen) //设置线性表的长度
{
if (nlen<0 || nlen>size)
throw new IllegalArgumentException("设置长度:n不在有效范围内");
size=nlen;
}
public E GetElem(int i) //返回线性表中序号为i的元素
{
if (i<0 || i>size-1)
throw new IllegalArgumentException("查找:位置i不在有效范围内");
return (E)data[i];
}
public void SetElem(int i,E e) //设置序号i的元素为e
{
if (i<0 || i>size-1)
throw new IllegalArgumentException("设置:位置i不在有效范围内");
data[i]=e;
}
public int GetNo(E e) //查找第一个为e的元素的序号
{
int i=0;
while (i<size && !data[i].equals(e))
i++; //查找元素e
if (i>=size) //未找到时返回-1
return -1;
else
return i; //找到后返回其序号
}
public void swap(int i,int j) //交换data[i]和data[j]
{
E tmp=data[i];
data[i]=data[j]; data[j]=tmp;
}
public void Insert(int i, E e) //在线性表中序号i位置插入元素e
{
if (i<0 || i>size) //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
if (size==capacity) //满时倍增容量
updatecapacity(2*size);
for (int j=size; j>i; j--) //将data[i]及后面元素后移一个位置
data[j]=data[j-1];
data[i]=e; //插入元素e
size++; //顺序表长度增1
}
public void Delete(int i) { //在线性表中删除序号i位置的元素
if (i<0 || i>size-1) //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
for (int j=i; j<size-1;j++) //将data[i]之后的元素前移一个位置
data[j]=data[j+1];
size--; //顺序表长度减1
if (size>initcapacity && size==capacity/4) //满足要求容量减半
updatecapacity(capacity/2);
}
public String toString() { //将线性表转换为字符串
String ans="";
for (int i=0;i<size;i++)
ans+=data[i].toString()+" ";
return ans;
}
2.3线性表的链式存储结构
线性表中的每个元素最多只有一个前驱元素和一个后继元素,因此可以采用链式存储结构存储。
2.3.1链表
线性表的链式存储结构称为链表。在链表中每个结点不仅包含有元素本身的信息(称为数据成员),而且包含有元素之间逻辑关系的信息,即一个结点中包含有后继结点的地址信息或者前驱结点的地址信息,称为指针成员,这样可以通过一个结点的指针成员方便地找到后继结点或者前驱结点。
如果每个结点只设置一个指向其后继结点的指针成员,这样的链表称为线性单向链表,称为单链表;如果每个结点中设置两个指针成员,分别用于指向其前驱结点和后继结点,这样的链表称为线性双向链接表,称为双链表。无前驱结点或者后继结点的相应指针成员用常量null表示。
2.3.2单链表
public class LinkListClass<E> //单链表泛型类
{
LinkNode<E> head; //存放头结点
public LinkListClass() //构造方法
{
head=new LinkNode<E>(); //创建头结点
head.next=null;
}
private LinkNode<E> geti(int i) //返回序号为i的结点
{
LinkNode<E> p=head;
int j=-1;
while (j<i)
{
j++;
p=p.next;
}
return p;
}
//线性表的基本运算算法
public void CreateListF(E[] a) //头插法:由数组a整体建立单链表
{
LinkNode<E> s;
for (int i=0;i<a.length;i++) //循环建立数据结点s
{
s=new LinkNode<E>(a[i]); //新建存放a[i]元素的结点s
s.next=head.next; //将s结点插入到开始结点之前,头结点之后
head.next=s;
}
}
public void CreateListR(E[] a) //尾插法:由数组a整体建立单链表
{
LinkNode<E> s,t;
t=head; //t始终指向尾结点,开始时指向头结点
for (int i=0;i<a.length;i++) { //循环建立数据结点s
s=new LinkNode<E>(a[i]); //新建存放a[i]元素的结点s
t.next=s; //将s结点插入t结点之后
t=s;
}
t.next=null; //将尾结点的next字段置为null
}
public void Add(E e) //在线性表的末尾添加一个元素e
{
LinkNode<E> s=new LinkNode<E>(e); //新建结点s
LinkNode<E> p=head;
while (p.next!=null) //查找尾结点p
p=p.next;
p.next=s; //在尾结点之后插入结点s
}
public int size() //求线性表长度
{
LinkNode<E> p=head;
int cnt=0;
while (p.next!=null) //找到尾结点为止
{
cnt++;
p=p.next;
}
return cnt;
}
public void Setsize(int nlen) //设置线性表的长度
{
int len=size();
if (nlen<0 || nlen>len)
throw new IllegalArgumentException("设置长度:n不在有效范围内");
if (nlen==len) return;
LinkNode<E> p=geti(nlen-1); //找到序号为nlen-1的结点p
p.next=null; //将结点p置为尾结点
}
public E GetElem(int i) //返回线性表中序号为i的元素
{
int len=size();
if (i<0 || i>len-1)
throw new IllegalArgumentException("查找:位置i不在有效范围内");
LinkNode<E> p=geti(i); //找到序号为i的结点p
return (E)p.data;
}
public void SetElem(int i,E e) //设置序号i的元素为e
{
if (i<0 || i>size()-1)
throw new IllegalArgumentException("设置:位置i不在有效范围内");
LinkNode<E> p=geti(i); //找到序号为i的结点p
p.data=e;
}
public int GetNo(E e) //查找第一个为e的元素的序号
{
int j=0;
LinkNode<E> p=head.next;
while (p!=null && !p.data.equals(e))
{
j++; //查找元素e
p=p.next;
}
if (p==null) //未找到时返回-1
return -1;
else
return j; //找到后返回其序号
}
public void swap(int i,int j) //交换序号i和序号j的元素
{
LinkNode<E> p=geti(i);
LinkNode<E> q=geti(j);
E tmp=p.data;
p.data=q.data;
q.data=tmp;
}
public void Insert(int i, E e) //在线性表中序号i位置插入元素e
{
if (i<0 || i>size()) //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
LinkNode<E> s=new LinkNode<E>(e); //建立新结点s
LinkNode<E> p=head=geti(i-1); //找到序号为i-1的结点p
s.next=p.next; //在p结点后面插入s结点
p.next=s;
}
public void Delete(int i) //在线性表中删除序号i位置的元素
{
if (i<0 || i>size()-1) //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
LinkNode<E> p=geti(i-1); //找到序号为i-1的结点p
p.next=p.next.next; //删除p结点的后继结点
}
public String toString() //将线性表转换为字符串
{
String ans="";
LinkNode<E> p=head.next;
while (p!=null)
{
ans+=p.data+" ";
p=p.next;
}
return ans;
}
}
2.3.3双链表
由于双链表中每个结点有两个指针成员,一个指向其后继结点,另一个指向其前驱结点,与单链表相比,双链表访问结点前后相邻结点更加方便。
public class DLinkListClass<E> //双链表泛型类
{
DLinkNode<E> dhead; //存放头结点
public DLinkListClass() //构造方法
{
dhead=new DLinkNode<E>(); //创建头结点
dhead.prior=null;
dhead.next=null;
}
private DLinkNode<E> geti(int i) //返回序号为i的结点
{
DLinkNode<E> p=dhead;
int j=-1;
while (j<i)
{
j++;
p=p.next;
}
return p;
}
//线性表的基本运算算法
public void CreateListF(E[] a) //头插法:由数组a整体建立双链表
{
DLinkNode<E> s;
for (int i=0;i<a.length;i++) //循环建立数据结点s
{
s=new DLinkNode<E>(a[i]); //新建存放a[i]元素的结点s,将其插入到表头
s.next=dhead.next; //修改s结点的next字段
if (dhead.next!=null) //修改头结点的非空后继结点的prior字段
dhead.next.prior=s;
dhead.next=s; //修改头结点的next字段
s.prior=dhead; //修改s结点的prior字段
}
}
public void CreateListR(E[] a) //尾插法:由数组a整体建立双链表
{
DLinkNode<E> s,t;
t=dhead; //t始终指向尾结点,开始时指向头结点
for (int i=0;i<a.length;i++) //循环建立数据结点s
{ s=new DLinkNode<E>(a[i]); //新建存放a[i]元素的结点s
t.next=s; //将s结点插入t结点之后
s.prior=t; t=s;
}
t.next=null; //将尾结点的next字段置为null
}
public void Add(E e) //在线性表的末尾添加一个元素e
{
DLinkNode<E> s=new DLinkNode<E>(e); //新建结点s
DLinkNode<E> p=dhead;
while (p.next!=null) //查找尾结点p
p=p.next;
p.next=s; //在尾结点之后插入结点s
s.prior=p;
}
public int size() //求线性表长度
{
DLinkNode<E> p=dhead;
int cnt=0;
while (p.next!=null) //找到尾结点为止
{
cnt++;
p=p.next;
}
return cnt;
}
public void Setsize(int nlen) //设置线性表的长度
{
int len=size();
if (nlen<0 || nlen>len)
throw new IllegalArgumentException("设置长度:n不在有效范围内");
if (nlen==len) return;
DLinkNode<E> p=geti(nlen-1); //找到序号为nlen-1的结点p
p.next=null; //将结点p置为尾结点
}
public E GetElem(int i) //返回线性表中序号为i的元素
{
int len=size();
if (i<0 || i>len-1)
throw new IllegalArgumentException("查找:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p
return (E)p.data;
}
public void SetElem(int i,E e) //设置序号i的元素为e
{
if (i<0 || i>size()-1)
throw new IllegalArgumentException("设置:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p
p.data=e;
}
public int GetNo(E e) //查找第一个为e的元素的序号
{
int j=0;
DLinkNode<E> p=dhead.next;
while (p!=null && !p.data.equals(e))
{
j++; //查找元素e
p=p.next;
}
if (p==null) //未找到时返回-1
return -1;
else
return j; //找到后返回其序号
}
public void Insert(int i, E e) //在线性表中序号i位置插入元素e
{
if (i<0 || i>size()) //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
DLinkNode<E> s=new DLinkNode<E>(e); //建立新结点s
DLinkNode<E> p=dhead=geti(i-1); //找到序号为i-1的结点p,其后插入s结点
s.next=p.next; //修改s结点的next字段
if (p.next!=null) //修改p结点的非空后继结点的prior字段
p.next.prior=s;
p.next=s; //修改p结点的next字段
s.prior=p; //修改s结点的prior字段
}
public void Delete(int i) //在线性表中删除序号i位置的元素
{
if (i<0 || i>size()-1) //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p,删除该结点
p.prior.next=p.next; //修改p结点的前驱结点的next字段
if (p.next!=null) //修改p结点非空后继结点的prior字段
p.next.prior=p.prior;
}
public String toString() //将线性表转换为字符串
{
String ans="";
DLinkNode<E> p=dhead.next;
while (p!=null)
{
ans+=p.data+" ";
p=p.next;
}
return ans;
}
}
2.3.4循环链表
1.循环单链表
循环单链表的特点是从表中任一结点出发都可找到其他结点,与单链表相比,无需增加存储空间。
循环单链表的插入和删除结点操作与非循环单链表相同,所有许多算法基本相似,区别于
(1)初始只有头结点head,在循环单链表的结构方法中需要通过head.next=head语句置为空表。
(2)循环单链表中涉及查找操作时需要修改表尾判断的条件。
public class CLinkListClass<E> //循环单链表泛型类
{
LinkNode<E> head; //存放头结点
public CLinkListClass() //构造方法
{
head=new LinkNode<E>(); //创建头结点
head.next=head; //置为空的循环单链表
}
private LinkNode<E> geti(int i) //返回序号为i的结点
{
LinkNode<E> p=head;
int j=-1;
while (j<i)
{
j++;
p=p.next;
}
return p;
}
//线性表的基本运算算法
public void CreateListF(E[] a) //头插法:由数组a整体建立循环单链表
{
LinkNode<E> s;
for (int i=0;i<a.length;i++) //循环建立数据结点s
{
s=new LinkNode<E>(a[i]); //新建存放a[i]元素的结点s
s.next=head.next; //将s结点插入到开始结点之前,头结点之后
head.next=s;
}
}
public void CreateListR(E[] a) //尾插法:由数组a整体建立循环单链表
{
LinkNode<E> s,t;
t=head; //t始终指向尾结点,开始时指向头结点
for (int i=0;i<a.length;i++) //循环建立数据结点s
{
s=new LinkNode<E>(a[i]); //新建存放a[i]元素的结点s
t.next=s; //将s结点插入t结点之后
t=s;
}
t.next=head; //将尾结点的next字段置为head
}
public void Add(E e) //在线性表的末尾添加一个元素e
{
LinkNode<E> s=new LinkNode<E>(e); //新建结点s
LinkNode<E> p=head;
while (p.next!=head) //查找尾结点p
p=p.next;
p.next=s; //在尾结点之后插入结点s
s.next=head;
}
public int size() //求线性表长度
{
LinkNode<E> p=head;
int cnt=0;
while (p.next!=head) //找到尾结点为止
{
cnt++;
p=p.next;
}
return cnt;
}
public void Setsize(int nlen) //设置线性表的长度
{
int len=size();
if (nlen<0 || nlen>len)
throw new IllegalArgumentException("设置长度:n不在有效范围内");
if (nlen==len) return;
LinkNode<E> p=geti(nlen-1); //找到序号为nlen-1的结点p
p.next=head; //将结点p置为尾结点
}
public E GetElem(int i) //返回线性表中序号为i的元素
{
int len=size();
if (i<0 || i>len-1)
throw new IllegalArgumentException("查找:位置i不在有效范围内");
LinkNode<E> p=geti(i); //找到序号为i的结点p
return (E)p.data;
}
public void SetElem(int i,E e) //设置序号i的元素为e
{
if (i<0 || i>size()-1)
throw new IllegalArgumentException("设置:位置i不在有效范围内");
LinkNode<E> p=geti(i); //找到序号为i的结点p
p.data=e;
}
public int GetNo(E e) //查找第一个为e的元素的序号
{
int j=0;
LinkNode<E> p=head.next;
while (p!=head && !p.data.equals(e))
{
j++; //查找元素e
p=p.next;
}
if (p==head) //未找到时返回-1
return -1;
else
return j; //找到后返回其序号
}
public void Insert(int i, E e) //在线性表中序号i位置插入元素e
{
if (i<0 || i>size()) //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
LinkNode<E> s=new LinkNode<E>(e); //建立新结点s
LinkNode<E> p=head=geti(i-1); //找到序号为i-1的结点p
s.next=p.next; //在p结点后面插入s结点
p.next=s;
}
public void Delete(int i) //在线性表中删除序号i位置的元素
{
if (i<0 || i>size()-1) //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
LinkNode<E> p=geti(i-1); //找到序号为i-1的结点p
p.next=p.next.next; //删除p结点的后继结点
}
public String toString() //将线性表转换为字符串
{
String ans="";
LinkNode<E> p=head.next;
while (p!=head)
{
ans+=p.data+" ";
p=p.next;
}
return ans;
}
}
2.循环双链表
循环双链表特点是整个链表形成两个环,由此从表中任意一个结点出发均可找到其他结点。
public class CDLinkListClass<E> //循环双链表泛型类
{
DLinkNode<E> dhead; //存放头结点
public CDLinkListClass() //构造方法
{
dhead=new DLinkNode<E>(); //创建头结点
dhead.prior=dhead; //构成空的循环双链表
dhead.next=dhead;
}
private DLinkNode<E> geti(int i) //返回序号为i的结点
{
DLinkNode<E> p=dhead;
int j=-1;
while (j<i)
{
j++;
p=p.next;
}
return p;
}
//线性表的基本运算算法
public void CreateListF(E[] a) //头插法:由数组a整体建立循环双链表
{
DLinkNode<E> s;
for (int i=0;i<a.length;i++) //循环建立数据结点s
{
s=new DLinkNode<E>(a[i]); //新建存放a[i]元素的结点s
s.next=dhead.next; //修改s结点的next字段
dhead.next.prior=s;
dhead.next=s; //修改头结点的next字段
s.prior=dhead; //修改s结点的prior字段
}
}
public void CreateListR(E[] a) //尾插法:由数组a整体建立循环双链表
{
DLinkNode<E> s,t;
t=dhead; //t始终指向尾结点,开始时指向头结点
for (int i=0;i<a.length;i++) //循环建立数据结点s
{ s=new DLinkNode<E>(a[i]); //新建存放a[i]元素的结点s
t.next=s; //将s结点插入t结点之后
s.prior=t; t=s;
}
t.next=dhead; //将尾结点的next字段置为head
dhead.prior=t; //将头结点的prior字段置为t
}
public void Add(E e) //在线性表的末尾添加一个元素e
{
DLinkNode<E> s=new DLinkNode<E>(e); //新建结点s
DLinkNode<E> p=dhead;
while (p.next!=dhead) //查找尾结点p
p=p.next;
p.next=s; //在尾结点p之后插入结点s
s.prior=p;
s.next=dhead;
dhead.prior=s;
}
public int size() //求线性表长度
{
DLinkNode<E> p=dhead;
int cnt=0;
while (p.next!=dhead) //找到尾结点为止
{
cnt++;
p=p.next;
}
return cnt;
}
public void Setsize(int nlen) //设置线性表的长度
{
int len=size();
if (nlen<0 || nlen>len)
throw new IllegalArgumentException("设置长度:n不在有效范围内");
if (nlen==len) return;
DLinkNode<E> p=geti(nlen-1); //找到序号为nlen-1的结点p
p.next=dhead; //将结点p置为尾结点
dhead.prior=p;
}
public E GetElem(int i) //返回线性表中序号为i的元素
{
int len=size();
if (i<0 || i>len-1)
throw new IllegalArgumentException("查找:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p
return (E)p.data;
}
public void SetElem(int i,E e) //设置序号i的元素为e
{
if (i<0 || i>size()-1)
throw new IllegalArgumentException("设置:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p
p.data=e;
}
public int GetNo(E e) //查找第一个为e的元素的序号
{
int j=0;
DLinkNode<E> p=dhead.next;
while (p!=dhead && !p.data.equals(e))
{
j++; //查找元素e
p=p.next;
}
if (p==dhead) //未找到时返回-1
return -1;
else
return j; //找到后返回其序号
}
public void Insert(int i, E e) //在线性表中序号i位置插入元素e
{
if (i<0 || i>size()) //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
DLinkNode<E> s=new DLinkNode<E>(e); //建立新结点s
DLinkNode<E> p=dhead=geti(i-1); //找到序号为i-1的结点p,其后插入s结点
s.next=p.next; //修改s结点的next字段
p.next.prior=s;
p.next=s; //修改p结点的next字段
s.prior=p; //修改s结点的prior字段
}
public void Delete(int i) //在线性表中删除序号i位置的元素
{
if (i<0 || i>size()-1) //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
DLinkNode<E> p=geti(i); //找到序号为i的结点p,删除该结点
p.prior.next=p.next; //修改p结点的前驱结点的next字段
p.next.prior=p.prior;
}
public String toString() //将线性表转换为字符串
{
String ans="";
DLinkNode<E> p=dhead.next;
while (p!=dhead)
{
ans+=p.data+" ";
p=p.next;
}
return ans;
}
}