目录
6.获取pos位置的元素&&给pos位置的元素值设为value
一.线性表
1.1线性表的概念
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列。
线性表在逻辑上是线性结构,也就是连续的一条直线。但是在物理结构上不一定是连续的,线性表在物理上存储时,通常以数组和链式结构存储。
本章我们主要讲的是顺序表。
二.顺序表
2.1顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存放数据元素的线性结构,一般采用数组存储。在数组上完成数据的增删查改。
2.2顺序表的实现
1.顺序表的接口
有了接口,可以实现代码的复用。
package MyArraylistto;
public interface IList {
// 新增元素,默认在数组最后新增
public void add(int data);
// 在 pos 位置新增元素
public void add(int pos, int data);
// 判定是否包含某个元素
public boolean contains(int toFind);
// 查找某个元素对应的位置
public int indexOf(int toFind);
// 获取 pos 位置的元素
public int get(int pos);
// 给 pos 位置的元素设为 value
public void set(int pos, int value);
//删除第一次出现的关键字key
public void remove(int toRemove);
// 获取顺序表长度
public int size() ;
// 清空顺序表
public void clear();
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display();
}
1.2顺序表的功能实现
首先,我们需要创建一个类并且实现接口中的方法。
MyArraylistf.java
1.顺序表初始化
在此之前,我们需要为顺序表开辟一定的空间内存。
private int[] elems; // 用于存储数组元素的内部数组
private int size; // 表示当前数组中元素的数量
public MyArraylistf()
{
elems = new int[10]; // 初始化内部数组 elems,容量为10
size = 0; // 将数组元素数量初始化为0
}
2.新增元素功能:
我们在插入元素之前需要先判断我们所提供的内存是否已经用完了,如果用完了,我们可以使用Arrays.copyOf(原数组,开辟的空间大小).进行扩容。在插入数据元素后,我们需要让size+1。如下
public void add(int data)
{
if(size== elems.length){
elems= Arrays.copyOf(elems,2*elems.length);
}
elems[size]=data;
this.size++;
}
3.清空顺序表是否为空&&获取顺序表长度&&打印顺序表:
1.清空顺序表长度:我们直接让size置为空。
2.获取顺序表长度:我们直接返回size的大小即可。
public void clear(){//清空顺序表
this.size=0;
}
public int size(){//返回顺序表元素个数
return this.size;
}
public void display(){
for(int i=0;i<this.size;i++){
System.out.print(elems[i]+" ");
}
System.out.println();
}
4.判断是否包含某个元素&&查找某个元素的对应位置
判断是否包含某个元素,只需要遍历一遍顺序表即可,如果存在返回true,反之,返回false.
查找元素的位置,也是遍历一遍顺序表,找到返回对应下标即可。
// 判定是否包含某个元素
public boolean contains(int toFind){
for(int i=0;i<this.size;i++){
if(elems[i]==toFind){
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind){
for(int i=0;i<this.size;i++){
if(elems[i]==toFind){
return i;
}
}
return -1;
}
5.在指定位置新增元素
我们在指定位置新增元素时,需要考虑:
1.插入的位置是否合法,如果小于0或者大于顺序表的最大长度,则抛出异常。
2.位置没有问题,那就需要判断顺序表是否已满,如果满了,进行扩容。
3.我们需要将pos位置以后的数据全部后移以为,才能进行插入。
/* 检查给定的位置是否合法。
* 如果位置小于0或大于当前数组大小,则抛出PosNotlegalException异常。
* @param pos 要检查的位置
* @throws PosNotlegalException 如果位置不合法,则抛出此异常
*/
private void checkPos(int pos)throws PosNotlegalException{
if(pos<0||pos>size){
throw new PosNotlegalException("位置不合法");
}
}
/**
* 在指定的位置插入一个元素。
* 首先会检查位置是否合法,然后将元素插入到指定位置,后续元素依次后移。
* 如果数组已满,则会自动扩展数组大小。
* @param pos 插入元素的位置
* @param data 要插入的元素值
*/
public void add(int pos, int data){
// 判断位置是否合法
try{
checkPos(pos);
}catch (PosNotlegalException e){
e.printStackTrace();
}
// 检查顺序表是否已满,若满则扩展一倍容量
if(size== elems.length){
elems= Arrays.copyOf(elems,2*elems.length);
}
// 将插入位置之后的元素依次后移
for(int i=size;i>pos;i--){
elems[i]=elems[i-1];
}
// 在指定位置插入元素
elems[pos]=data;
// 更新数组大小
size++;
}
6.获取pos位置的元素&&给pos位置的元素值设为value
1.获取pos位置的元素:我们严谨一点,需要考虑pos是否合法,不合法抛出异常。
2.给pos位置的值设为value:我们与上相同,判断完,直接让pos位置的值改为value。
// 获取 pos 位置的元素
public int get(int pos){
try {
checkPos(pos);
}catch(PosNotlegalException e){
e.printStackTrace();
}
return elems[pos];
}
// 给 pos 位置的元素设为 value
public void set(int pos, int value){
try {
checkPos(pos);
}catch(PosNotlegalException e){
e.printStackTrace();
}
elems[pos]=value;
}
7.删除第一次出现关键字key
我们这里可以采用双层遍历的方式进行删除。
在外循环遍历的时候,如果数组中的元素与key相同,则进入内循环,将i位置的数据进行覆盖,我们需要遍历size-1次。
扩展:如果要删除所有出现key的元素,我们将return去掉即可
/**
* 删除数组中第一次出现的指定元素。
* @param toRemove 需要删除的元素。
*/
public void remove(int toRemove){
// 遍历数组,寻找第一个匹配的元素
for(int i=0;i<this.size;i++){
if(elems[i]==toRemove){
// 将找到元素后的元素依次向前移动
for(int j=i;j<this.size-1;j++){
elems[j]=elems[j+1];
}
// 调整数组大小,表示已删除一个元素
size--;
return;
}
}
}
//删除数组中出现toRemove的全部元素
public void removeAll(int toRemove){
for(int i=0;i<this.size;i++){
if(elems[i]==toRemove){
for(int j=i;j<this.size-1;j++){
elems[j]=elems[j+1];
}
size--;
}
}
}
至此,我们把接口中的方法全部进行了实现。我们可以在main函数中进行测试一下。
2.3顺序表完整代码
测试类:TTest.java
package MyArraylistto;
public class TTest {
public static void main(String[] args) {
MyArraylistf list = new MyArraylistf();
list.add(1);
list.add(1);
list.add(1);
list.add(1);
list.add(2);
list.add(2,2);
list.display();
System.out.println("===========");
list.removeAll(2);
list.display();
}
}
顺序功能实现:MyArraylistf.java
package MyArraylistto;
import java.util.Arrays;
public class MyArraylistf implements IList{
/**
* MyArraylistf 类用于实现一个简单的数组列表。
* 该类内部使用整型数组存储元素,并且能够动态调整数组大小。
*/
private int[] elems; // 用于存储数组元素的内部数组
private int size; // 表示当前数组中元素的数量
/**
* MyArraylistf 构造函数。
* 初始化内部数组 elems 为长度为 10 的整型数组,并将 size 设置为 0。
*/
public MyArraylistf()
{
elems = new int[10]; // 初始化内部数组 elems,容量为10
size = 0; // 将数组元素数量初始化为0
}
public void add(int data)
{
if(size== elems.length){
elems= Arrays.copyOf(elems,2*elems.length);
}
elems[size]=data;
this.size++;
}
/**
* 检查给定的位置是否合法。
* 如果位置小于0或大于当前数组大小,则抛出PosNotlegalException异常。
* @param pos 要检查的位置
* @throws PosNotlegalException 如果位置不合法,则抛出此异常
*/
private void checkPos(int pos)throws PosNotlegalException{
if(pos<0||pos>size){
throw new PosNotlegalException("位置不合法");
}
}
/**
* 在指定的位置插入一个元素。
* 首先会检查位置是否合法,然后将元素插入到指定位置,后续元素依次后移。
* 如果数组已满,则会自动扩展数组大小。
* @param pos 插入元素的位置
* @param data 要插入的元素值
*/
public void add(int pos, int data){
// 判断位置是否合法
try{
checkPos(pos);
}catch (PosNotlegalException e){
e.printStackTrace();
}
// 检查顺序表是否已满,若满则扩展一倍容量
if(size== elems.length){
elems= Arrays.copyOf(elems,2*elems.length);
}
// 将插入位置之后的元素依次后移
for(int i=size;i>pos;i--){
elems[i]=elems[i-1];
}
// 在指定位置插入元素
elems[pos]=data;
// 更新数组大小
size++;
}
// 获取 pos 位置的元素
public int get(int pos){
try {
checkPos(pos);
}catch(PosNotlegalException e){
e.printStackTrace();
}
return elems[pos];
}
// 给 pos 位置的元素设为 value
public void set(int pos, int value){
try {
checkPos(pos);
}catch(PosNotlegalException e){
e.printStackTrace();
}
elems[pos]=value;
}
//删除第一次出现的关键字key
public void remove(int toRemove){
for(int i=0;i<this.size;i++){
if(elems[i]==toRemove){
for(int j=i;j<this.size-1;j++){
elems[j]=elems[j+1];
}
size--;
return;
}
}
}
public void removeAll(int toRemove){
for(int i=0;i<this.size;i++){
if(elems[i]==toRemove){
for(int j=i;j<this.size-1;j++){
elems[j]=elems[j+1];
}
size--;
}
}
}
// 判定是否包含某个元素
public boolean contains(int toFind){
for(int i=0;i<this.size;i++){
if(elems[i]==toFind){
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind){
for(int i=0;i<this.size;i++){
if(elems[i]==toFind){
return i;
}
}
return -1;
}
public void clear(){//清空顺序表
this.size=0;
}
public int size(){//返回顺序表元素个数
return this.size;
}
public void display(){
for(int i=0;i<this.size;i++){
System.out.print(elems[i]+" ");
}
System.out.println();
}
}
接口:IList.java
package MyArraylistto;
public interface IList {
// 新增元素,默认在数组最后新增
public void add(int data);
// 在 pos 位置新增元素
public void add(int pos, int data);
// 判定是否包含某个元素
public boolean contains(int toFind);
// 查找某个元素对应的位置
public int indexOf(int toFind);
// 获取 pos 位置的元素
public int get(int pos);
// 给 pos 位置的元素设为 value
public void set(int pos, int value);
//删除第一次出现的关键字key
public void remove(int toRemove);
// 获取顺序表长度
public int size() ;
// 清空顺序表
public void clear();
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display();
}
三.java中的ArrayList
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架如下:
说明
1. ArrayList是以泛型方式实现的,使用时必须要先实例化
2. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
3. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
4. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
5. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者 CopyOnWriteArrayList
6. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
3.1ArrayList的使用
java中所提供的方法在ArrayList (Java 平台 SE 8 ) (oracle.com)可进行查看。
四.顺序表的缺陷
1.ArrayList底层使用连续的空间,任意位置的插入或删除元素时,需要将该位置后序元素整体向前或者向后移动,时间复杂度为O(N)。
2.扩容的时候需要申请新的空间,拷贝数据,释放旧空间,会有不小的损耗。
3.扩容一般是呈2倍的增长,所以一定会有空间的浪费。例如,你开辟了50的空间,等满了以后扩容到100,这时再利用10个空间,但后面的空间没有使用,就浪费了45个数据空间。
但是我们使用链表不会有这样的问题。
链表在插入数据的时候是生成一个个数据空间,通过指针连接起来,这样就不会造成空间的浪费。
同时,链表在插入和删除数据的效率比顺序表快。