JAVA数据结构——线性表

本文深入探讨了JAVA中线性表的数据结构,包括数组线性表和链表的实现细节。通过对MyList接口及其实现类的分析,展示了线性表的各种操作方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA数据结构(1)——线性表

1.引言

线性表是典型的数据结构之一,Java API支持多种数据结构,利用集合框架进行具体实现。本文将剖析这些数据结构是如何实现的。
线性表是按顺序储存数据时常用的一种数据结构。线性表的主要操作包括提取、插入、删除、查找以及判断线性表是否为空等功能。实现线性表的方式有两种,一种是数组储存线性表,另一种是使用链式结构。这两种类具备了相同的操作,但都具有不同的实现。

2.线性表的一般特性

为了具体分析线性表的内在功能,我们定义了MyList接口,接口内包含了以下函数。

函数名称具体功能
void add(E e)在线性表末尾添加新元素
E get(int index)获得指定下标的元素
boolean isEmpty()判断线性表是否为空
boolean remove(E e)移除指定下标的元素
int size()返回线性表大小

接口定义如下

public interface MyList<E>{
  public void add(E e);
  public int get(int index);
  pubblic boolean isEmpty();
  public boolean remove(E e);
  public int size();
}

MyList接口内所包含的方法在MyAbstractList抽象类中实现,抽象类主要用来进行类型隐藏。构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但本质上相同的具体概念的抽象。MyAbstrackList类如下:

public abstract class MyAbstractList<E> implements MyList<E>{
  protected int size=0; //线性表大小
  protected MyAbstractList(){} //构造函数
  protected MyAbstractList(E[] objects){
    for(int i=0;i<objects.length;i++)
      add(objects[i]);
  }
  public void add(E e){}//线性表末尾添加元素
  public boolean delete(int index){}//删除指定下标元素
  public int size(){
    return size;
  }//返回线性表大小
}
  • 上述代码我们发现在抽象类MyAbstractList中我们将size定义为了protected,这样做的目的在于MyAbstractList的子类可以访问size,但是在不同包中的MyAbstractList的非子类不能访问它。
  • MyAbstractList抽象类中,我们并没有实现add()以及delete()方法,因为在不同类型的线性表中上述两种方法具有不同的实现。
3.数组线性表

数组是一种大小固定的数据结构。数组一旦创建之后,其大小就无法改变。尽管如此,但我们仍然可以用数组来实现动态的数据结构。解决方法的基本思想是,当数组不能再存储线性表中的新元素时,创建一个更大的新数组来替换当前数组。
数组线性表包含了一个类型为E[]的数组data,向数组中插入一个新元素时,首先确认数组是否有足够的空间。若数组的空间不够,则创建大小为当前数组两倍的新数组,然后将当前数组的元素复制到新的数组中。这样,新数组就变成了当前数组。在指定下标处插入一个新元素前,必须将指定下标后面所有的元素向右移动一个位置并将该线性表的大小增加1。具体实现方法如下所示。

class MyArrayList<E>{
  public static final int CAPACITY=16;
  public int size=0;
  private E[] data=(E[])new Object[CAPACITY];
  public MyArrayList(){}
  public MyArrayList(E[] objects){
    for(int i=0;i<objects.length;i++)
      add(objects[i]);
  }

  public void add(E e){
    add(size,e);
  }

  public void add(int index,E e){
    ensureCapacity();
    if(index<size){
      for(int i=size-1;i>=index;i--)
        data[i+1]=data[i];

      data[index]=e;
    }else data[index]=e;
    size++;
  }

  private void ensureCapacity(){
    if(size>=data.length){
      E[] newData=(E[])(new Object[size*2+1]);
      System.arraycopy(data,0,newData,0,size);
      data=newData;
    }
  }
}

上述数组线性表中的各方法如下:

方法名方法
MyArrayList()构造函数
MyArrayList(E[] objects)根据数组建立数组线性表
void add(int index,E e)在指定下标位置添加元素
void ensureCapacity()判断数组是否已满
4.链表

数组线性表中包含的get(int index)和set(int index, Object o)方法都是典型的通过下标访问和修改元素的方法,这些方法都是高效可行的。但是在数组线性表中,在非尾端添加或删除元素的方法效率很低,因为一旦在数组线性表中间添加删除元素,这样的操作需要移动潜在的大量元素。为了提高在表中任意位置添加删除元素的效率,采用链式结构存储数据的方法应运而生。

4.1 节点

链表中的每个元素都包含了一个称为节点(Node)的结构。当向链表中加入新的元素时,就会产生一个包含它的新的节点。每个节点都和他的相邻节点相链接。
节点可以单招如下方式定义为一个类:

class Node<E>{
  E element;
  Node<E> next;
  public Node(E e){
    element=e;
  }
}

一个链表中包含两个变量head以及tail,head指向链表的第一个节点,tail指向最后一个节点。如果链表为空,则head及tail均为null。我们将利用head和tail来进行链表的创建,创建链表主要包括以下四个步骤。

  • 第一步:声明head和tail
 Node<E> head=null;
 Node<E> tail=null;//此时链表为空
  • 第二步:创建第一个节点并将它追加到链表中
head=new Node<E>();
tail=head;//此时链表存在一个节点,并且head和tail均指向该节点
  • 第三步:创建第二个节点并将它追加到链表中
tail.next=new Node<E>();
tail=tail.next;//此时链表存在两个节点,head仅指向头节点,以后均利用tail在链表末尾添加节点
  • 第四步:循环第二三步

每个节点都包含了元素和一个名为next的数据域,next指向下一个元素。单向链表tail.next所包含的值为null,双向链表tail.next指向头节点head,针对单向链表我们可以利用如下代码循环遍历链表中的所有节点。

Node current=head;
while(current!=null){
  System.out.println(current.element);
  current=current.next;
}
4.2LinkedList类

LinkedList类是使用链式结构实现的动态线性表,它扩展了AbstractList类,此外他它还提供了addFirst、addLassr、emoveFirst、removeLast、getFirst和getLast方法。下表展示了MyLinkedList类中所包含的方法和变量。

方法名方法
MyLinkedList()构造函数
MyLinkedList(Objects[] E)通过对象数组构造链表
void addFirst(E e)在表头添加一个对象
void addLast(E e)在表尾添加一个对象
E getFirst()获取表头元素
E getFirst()获取表尾元素
E removeFirst()删除表头元素并返回
E removeLastt()删除表尾元素并返回

本文不再对上述方法的实现一一赘述,仅展示如何添加创建链表的算法以及删除算法。具体实现如下所示。

class Node<E>{
  E element;
  Node next=null;
  public Node(E e){
    element=e;
  }
}

class MyLinkedList<E>{
  Node head=null;//初始化头节点
  Node tail=null;//初始化尾节点
/*构造函数*/
  public MyLinkedList(){}
  public MyLinkedList(E[] objects){
    for(int i=0;i<objects.length;i++)
      add(objects[i]);
  }
/*添加节点构造链表*/
  public void add(E e){
    if(head==null){
      Node current=new Node(E);
      head=current;
      tail=current;
  }else{
    tail.next=new Node(e);
    tail=tail.next;
  }
 }

上述构造的链表是最基础也是最简单的链表形式,这种链表被称为单链表,除了这种形式的链表外,还存在着其他集中链表的变体。具体形式如下:

  • 循环单链表:除了链表中的最后一个节点的指针指向头节点外,其他都很像单链表。需要注意的是,循环单链表不需要tail。
  • 双向链表:这种形式的链表的Node类型中包含了两个指针的节点,一个指向下一个节点,而另一个指向前一个节点。为了方便起见,一般对上述两个指正命名为前向指针(next)以及后向指针(previous),本文在此给出双向链表实现示例。
class Node<E>{
  E element;
  Node next=null;
  Node previous=null;
  public Node(E e){
    element=e;
  }
}

class DoubleLinkedList<E>{
  Node head=null;
  Node tail=null;
  public DoubleLinkedList(){}
  public DoubleLinkedList(E[] objects){
    for(int i=0;i<objects.length;i++)
      add(objects[i]);
  }
 /*双向链表添加节点方法*/
  public void add(E e){
    if(head==null){
       Node newNode=new Node(e);
       head=newNode;
       tail=head;  
    }else{
       tail.next=new Node(e);
       tail.next.previous=tail;
       tail=tail.next;
    }
  }
}
  • 循环双向链表:除了链表中最后一个节点的前向指针(next)指向第一个节点,且第一个节点的前向指针(previous)指向最后一个节点外,其他都与双向链表保持一致。
5.小结

本文主要介绍了数据结构中的线性表,以及线性表中的数组线性表、链表、以及链表的变体。定义一个数据结构其本质上就是定义一个类。数据结构的类应该使用数据域存储数据,并提供对这些数据进行操作的方法,例如线性表中的插入和删除等。创建一个数据结构其本质就是实例化一个数据结构类,可以应用各种数据结构类中的方法去处理存储在数据域中的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值