简介
线性表是 n 个数据元素的有限序列: (a1,a2,…an) ;
在线性表(a0,a1,…,an-1)中,a0为开始结点,没有前驱元素, an-1为终端结点,没有后继结点。除开始结点和终端结点外,每个数据元素ai都有一且仅有一个前驱元素和后继元素。
线性表的抽象数据Java接口:
public interface IList {
public void clear();//将线性表置成空表
public boolean isEmpty();//判断线性表是否为空表
public int length();//返回线性表的长度
public Object get(int i) throws Exception;//读取并返回线性表中第i个数据元素
public void insert(int i,Object x) throws Exception;//插入x作为第i个元素
public void remove(int i) throws Exception; //删除第i个元素
public int indexOf(Object x);//返回元素x首次出现的位序号
public void display();//输出线性表中各个数据元素的值
}
线性表的顺序存储
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。
顺序表用一组连续的内存单元依次存放数据元素,元素在内存中的物理存储次序和它们在线性表中的逻辑次序一致;
线性表的第i个数据元素ai的存储位置为 :
特点:
- 在线性表中逻辑上相邻的元素在物理位置上也同样相邻;
- 可按照数据元素的位序号进行随机存取;
- 进行插入、删除操作需要移动大量的数据元素;
- 需要进行存储空间的预先分配,可能会造成空间浪费,但存储密度较高;
顺序表类的Java语言描述:
import java.util.Scanner;
public class SqList implements IList{
private Object[] listItem; //顺序表存储空间
private int curLen; //顺序表当前长度
private int maxSize;
//构造一个存储空间为maxsize的顺序表
public SqList(int maxsize) {
curLen=0;
maxSize=maxsize;
listItem=new Object[maxSize];
}
//顺序表置为空表
public void clear() {
curLen=0;
}
//判断顺序表是否为空表,若空,返回true,否则,返回false
public boolean isEmpty() {
return curLen==0;
}
//返会顺序表的长度
public int length() {
return curLen;
}
//读取并返回第i个数据元素
public Object get(int i) throws Exception {
if(i<0||i>curLen-1)
{
throw new Exception("第"+i+"个数据元素不存在");
}
return listItem[i];
}
//插入x作为第i个元素
public void insert(int i, Object x) throws Exception{
if(curLen==maxSize) //判断顺序表的存储空间是否已满
{
throw new Exception("顺序表满");
}
if(i<0||i>curLen) //判断参数的值是否满足
{
throw new Exception("插入位置非法");
}
for(int j=curLen;j>i;j--)//将插入位置及其之后的所有的数据元素后移一个存储位置
{
listItem[j]=listItem[j-1];
}
listItem[i]=x;//在位置处插入新的数据元素
curLen++;//表长加1
}
//删除第i个元素
public void remove(int i) throws Exception{
if(i<0||i>curLen-1)
{
throw new Exception("删除位置非法");
}
for(int j=i;i<curLen-1;i++)
{
listItem[j]=listItem[j+1];
}
curLen--;
}
//返回元素x首次出现的位序号
public int indexOf(Object x) {
for(int i=0;i<=curLen-1;i++)
{
if(listItem[i].equals(x))
{
return i;
}
}
return -1;
}
//输出顺序表中的元素
public void display() {
for(int i=0;i<curLen-1;i++)
{
System.out.print(listItem[i]+" ");
}
}
}
顺序表的基本操作实现
1:插入操作
步骤如下:
- 判断顺序表的存储空间是否已满,若已满则抛出异常;
- 判断参数 i 的值是否满足 0<= i <= curLen,若不满足则抛出异常;
- 将插入位置及其之后的所有数据元素后移一个存储位置;
- 在位置 i 处插入新的数据元素 x ;
- 表长加 1 ;
public void insert(int i, Object x) throws Exception{
if(curLen==maxSize) //判断顺序表的存储空间是否已满
{
throw new Exception("顺序表满");
}
if(i<0||i>curLen) //判断参数的值是否满足
{
throw new Exception("插入位置非法");
}
for(int j=curLen;j>i;j--)//将插入位置及其之后的所有的数据元素后移一个存储位置
{
listItem[j]=listItem[j-1];
}
listItem[i]=x;//在位置处插入新的数据元素
curLen++;//表长加1
}
算法的时间复杂度为 O(n)。
2:删除操作
步骤如下:
- 判断参数 i 是否满足 0<= i <= curLen-1,若不满足则抛出异常;
- 将第 i 个数据元素之后的数据元素都向前移动一个存储单元;
- 表长减1;
public void remove(int i) throws Exception{
if(i<0||i>curLen-1)
{
throw new Exception("删除位置非法");
}
for(int j=i;i<curLen-1;i++)
{
listItem[j]=listItem[j+1];
}
curLen--;
}
算法的时间复杂度为O(n)。
3:查找操作
步骤如下:将 x 与顺序表中的每一个数据元素的进行比较,若相等,则返回该数据元素的位置;若比较结束未找到等值的数据元素,返回 -1。
public int indexOf(Object x) {
for(int i=0;i<=curLen-1;i++)
{
if(listItem[i].equals(x))
{
return i;
}
}
return -1;
}
算法的时间复杂度为O(n)。
注意:顺序表的插入和删除操作的效率很低,每插入或删除一个数据元素,元素的移动次数较多;并且数组容量不可更改,存在因容量小造成数据溢出或者因容量大造成内存资源浪费的问题;
线性表的链式存储和实现
采用链式存储方式的线性表称为链表,链表是用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,必须采用附加信息表示数据元素之间的逻辑关系。因此链表的每一个结点包含数据域和指针域;
1:单链表
单链表是指结点中只包含一个指针域(双向链表具有两个指针域)的链表,指针域中存储着指向后继结点的指针。单链表的头指针是线性表的起始地址,是线性表中第一个数据元素的存储地址。单链表的尾结点没有后继结点,所以其指针域值为null。
单链表的结点的存储空间是在插入和删除过程中动态申请和释放的,不需要预先分配。
带头结点的单链表的基本形态有:
- 单链表空:L->next == 0 ;
- 单链表不空:L->next != 0 ;
结点类描述:
public class Node {
public Object data;//存放结点数据值
public Node next;//存放后继结点
//无参构造函数
public Node(){
this(null,null);
}
//只有结点值的构造函数
public Node(Object data){
this(data,null);
}
//带有节点值和后继结点的构造函数
public Node(Object data,Node next){
this.data=data;
this.next=next;
}
}
单链表类描述:
import java.util.Scanner;
public class LinkList implements IList{
public Node head;//单链表的头指针
//构造函数初始化头结点
public LinkList(){
head=new Node();
}
//构造函数构造长度为n的单链表
public LinkList(int n,boolean Order) throws Exception{
this();
if(Order)
{
create1(n);
}
else
{
create2(n);
}
}
//尾插法顺序建立单链表
public void create1(int n) throws Exception{
Scanner sc=new Scanner(System.in);
for(int i=0;i<n;i++){
insert(0,sc.next());
}
}
//头插法逆序建立单链表
public void create2(int n) throws Exception{
Scanner sc=new Scanner(System.in);
for(int i=0;i<n;i++){
insert(length(),sc.next());
}
}
//将链表置空
public void clear(){
head.data=null;
head.next=null;
}
//判断链表是否为空
public boolean isEmpty(){
return head.next==null;
}
//返回链表长度
public int length(){
Node p=head.next;
int length=0;
while(p!=null){
p=p.next;
length++;
}
return length;
}
//读取并返回第i个位置的数据元素
public Object get(int i) throws Exception {
Node p=head.next;
int j;
//从首结点开始向后查找,直到p指向第i个结点或者p为null
for(j=0;j<i&&p!=null;j++){
p=p.next;
}
if(j>i||p==null)//i不合法时抛出异常
{
throw new Exception("第"+i+"个数据元素不存在");
}
return p.data;
}
//带头结点的插入操作,插入x作为第i个元素
public void insert(int i, Object x) throws Exception{
Node p=head;
int j=-1;
//寻找第i个结点的前驱
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p==null)//i不合法时抛出异常
{
throw new Exception("插入位置不合法");
}
Node s=new Node(x);
s.next=p.next;
p.next=s;
}
//删除第i个元素
public void remove(int i) throws Exception{
Node p=head;
int j=-1;
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p.next==null)
{
throw new Exception("删除位置不合法");
}
p.next=p.next.next;
}
//返回元素x首次出现的位序号
public int indexOf(Object x) {
Node p=head.next;
int j=0;
while(p!=null&&!p.equals(x)){
p=p.next;
j++;
}
if(p!=null)
return j;
else
return -1;
}
public void display(){
Node p=head.next;
while(p!=null){
System.out.print(p.data+" ");
p=p.next;
}
}
}
单链表类的基本操作实现
1:查找操作
步骤:单链表的存储空间不连续,必须从头结点开始沿着后继节点依次进行查找;
//读取并返回第i个位置的数据元素
public Object get(int i) throws Exception {
Node p=head.next;
int j;
//从首结点开始向后查找,直到p指向第i个结点或者p为null
for(j=0;j<i&&p!=null;j++){
p=p.next;
}
if(j>i||p==null)//i不合法时抛出异常
{
throw new Exception("第"+i+"个数据元素不存在");
}
return p.data;
}
2:按值查找
步骤:将 x 与单链表中的每一个数据元素的数据域进行比较,若相等,则返回该数据元素在单链表中的位置;若比较结束未找到等值的数据元素,返回-1。
//返回元素x首次出现的位序号
public int indexOf(Object x) {
Node p=head.next;
int j=0;
while(p!=null&&!p.equals(x)){
p=p.next;
j++;
}
if(p!=null)
return j;
else
return -1;
}
3:插入操作
insert(i,x)
步骤:
- 查找到插入位置的前驱结点,即第 i-1 个结点;
- 创建数据域值为 x 的新结点;
- 修改前驱结点的指针域为指向新结点的指针,新结点的指针域为指向原第 i 个结点的指针;
带头结点的单链表的插入操作:
//插入x作为第i个元素
public void insert(int i, Object x) throws Exception{
Node p=head;
int j=-1;
//寻找第i个结点的前驱
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p==null)//i不合法时抛出异常
{
throw new Exception("插入位置不合法");
}
Node s=new Node(x);
s.next=p.next; //1
p.next=s; //2
}
注意:完成插入的步骤:①②。技巧:先修改新结点的指针域。
不带头结点的插入操作:
//不带头结点的插入操作
public void insert(int i, Object x) throws Exception{
Node p=head;
int j=-1;
//寻找第i个结点的前驱
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p==null)//i不合法时抛出异常
throw new Exception("插入位置不合法");
Node s=new Node(x);
if(i==0){ //如图
s.next=head;
head=s;
}
else{
s.next=p.next;
p.next=s;
}
}
在带头结点的单链表上进行插入操作时,无论插入位置是表头、表尾还是表中,操作语句都是一致的;
但是在不带头结点的单链表上进行插入操作时,在表头插入和在其他位置插入的语句是不同的;
4:删除操作
步骤:
- 判断单链表是否为空;
- 查找待删除结点的前驱结点;
- 修改前驱结点的指针域为待删除结点的指针域;
带头结点的删除操作:
//删除第i个元素
public void remove(int i) throws Exception{
Node p=head;
int j=-1;
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p.next==null)
{
throw new Exception("删除位置不合法");
}
p.next=p.next.next; //2
}
不带头结点的删除操作:
//删除第i个元素
public void remove(int i) throws Exception{
Node p=head;
int j=-1;
while(p!=null&&j<i-1){
p=p.next;
j++;
}
if(j>i-1||p.next==null)
{
throw new Exception("删除位置不合法");
}
if(i == 0) //删除第一个结点时,会改变头指针的值
{
p = p.next;
}else
{
p.next=p.next.next;
}
}
其他链表
1:循环链表
循环链表与单链表的结构相似,只是将链表的首尾相连,即尾结点的指针域指向头结点的指针,从而形成了一个环状的链表;
循环链表与单链表的操作算法基本一致,判定循环链表中的某个结点是否为尾结点的条件不是它的后继结点为空,而是它的后继结点是否为头结点;
2:双向链表
双向链表具有两个指针域:一个指向前驱结点,一个指向后继结点。
使得查找某个结点的前驱结点不再需要从表头开始顺着链表依次进行查找,减小时间复杂度;
public class DuLNode {
public Object data; //数据域
public DuLNode prior; //存放指向前驱结点的指针
public DuLNode next; //存放指向后继结点的指针
public DuLNode(){
this(null);
}
public DuLNode(Object data){
this.data=data;
this.prior=null;
this.next=null;
}
}
双向链表与单链表的不同之处在于进行插入和删除操作时每个结点需要修改两个指针域;
插入操作
public void insert(int i, Object x) throws Exception {
DuLNode p=head;
int j=-1;
//寻找插入位置i
while(p!=null&&j<i){
p=p.next;
j++;
}
if(j>i||p==null)//i不合法时抛出异常
throw new Exception("插入位置不合法");
DuLNode s=new DuLNode(x);
p.prior.next=s;
s.next=p;
p.prior=s;
}
删除操作
public void remove(int i) throws Exception {
DuLNode p=head;
int j=-1;
while(p!=null&&j<i){
p=p.next;
j++;
}
if(j>i||p==null)//i不合法时抛出异常
throw new Exception("删除位置不合法");
p.prior.next=p.next;
p.next.prior=p.prior;
}
顺序表与链表的比较
顺序表优缺点:
- 可进行高效随机存取;
- 存储密度高,空间开销小;
- 实现简单,便于使用;
- 需要预先分配存储空间;
- 不便于进行插入和删除操作;
链表的优缺点:
- 灵活,可进行存储空间的动态分配;
- 插入、删除效率高;
- 存储密度低;
- 不可按照序号随机存取;
以上作为学习数据结构的笔记,供日后复习巩固;
在萌新的道路上慢慢探索吧