第二章三用类实现动态数组curd

在这里插入图片描述

  戳我跳到个人主页

  本人码云(建议pc端打开,手机可能会出现字体过小问题。)
  本人会不定期在码云上更新一些算法或数据结构的基础程序,后期也会学习进阶程序,也会更新大厂的面试题。
  如果您有好的建议和想法,欢迎在本人码云相应代码评论区评论哦。希望大家都能在编程这条路上越走越远。也祝看到这篇博客的人,能真正搞懂这方面知识,当然,若有错误,请提出改正,谢谢大家啦。

  本人后续会在这里更新操作系统和计组的笔记,敬请期待!
  有赞必回,评论必回,顺着网线回访!

什么是线性表

1.线性表( linear list )是 n 个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
2. 线性表在逻辑上是线性结构(逻辑上连续),也就是说连续的一条直线。但是在物理结构上并不一定是连续的。
3. 线性表在物理上存储时,通常以数组和链式结构的形式存储。

什么是逻辑上连续?
在这里插入图片描述逻辑上连续,物理上不连续,代表就是链表
在这里插入图片描述

对于线性表物理上到底是不是挨着的、我不关注

数组就是最常见的物理上连续线性表在这里插入图片描述

什么是顺序表

顺序表是线性表的一种
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增 / 删 / 改 / 查
顺序表一般可以分为两种:

  • 静态顺序表:使用定长数组存储。
    这就是一个在堆上存储的一个静态的整型数组在这里插入图片描述
  • 动态顺序表:使用动态开辟的数组存储。

他们有什么不同呢?
静态顺序表适用于确定知道需要存多少数据的场景。
静态顺序表的定长数组导致 N 如果定义大了空间开多了浪费,开少了不够用。
相比之下动态顺序表更灵活,根据需要动态的开辟数组。

动态数组

就是在普通数组上、增加了一个可以根据元素的个数动态调整数组大小的数组。(用类实现)
我们之前用的数组最大的问题就在于数组长度定长,一旦一个数组在定义时确定长度之后,使用过程中无法改变长度

Java 中提供的数组都是静态数组 int[] 、char []、long []…(定义之后无法改变长度)
=> 需要我们自己定义一个类,拓展基础数组的功能。此时、对外部来说,他已经不是一个数组了,而是一个类,创建这个“变长数组”相当于创建对象

思想
先创建一个类MyArray、在这个类中,定义一个private修饰的整型全局数组 private int [] data(private是为了让外界不可见)、再定义一个私有的整型全局变量private int size(表示这个私有数组已经存有数据的长度、另外、因为是全局变量、size有初始值 0 )。

为什么:size 表示私有数组已经存有数据的长度?
因为当我们存储时、data.length 不一定会存满、而是通过我们自己定义的一个变量,size来表示我们下一个期待存入数据的位置。size-1 索引代表的是最后一个元素
所以、size 的值 即代表 数组的有效长度

接着当我们创建这个类的一个对象时候、肯定要给动态数组分配一个初始空间吧、所以我们要定义一个有参的构造方法public MyArray( int num )、传入一个整型变量 num ,传入的参数作为初始的数组的长度。即this.data = new int [num];。当然,也可以再定义一个无参的构造方法public MyArray()、让这个无参的方法去调用这个有参的方法、只需要在调用时传入一个固定值即可、因为是无参调用有参、即this( 10 );、(这代表当我们使用无参构造方法创建一个MyArray类的对象时,默认开辟的整型数组大小为 10)。

public class MyArray {

// 核心仍然还是一个整型数组
private int[] data;
// 这个变量、表示已经存有数据元素的大小
// 因为data.length不一定存满
private int size;

public MyArray()
{
    // 假设通过无参的构造方法创建的数组大小为 10
    // 即正常创建对象时,初始开辟长度为 10

    this(10 );
    // 通过调用有参的构造方法实现
}

public MyArray( int num )
{
    this.data = new int [ num ];
    // this.data 代表当前对象数组
    // 设置初始数组长度为 num(通过参数传入)
}

做好准备之后我们需要考虑数组的基本操作、增删改查、如何进行。

正常的增加:

假设、当我们通过无参的构造方法创建了一个MyArray的对象 liveArray 之后,这个对象中的整型数组的大小为 10还没有存储任何数据。此时、肯定不能用普通的数组赋值来对这个动态数组的元素赋值、我们必须通过一个方法、来进行数据的增加,假设为 public void add ( int val )、当我们传入一个参数 val 时,就将它加到数组的末端、因为size有初值0(全局变量),所以立即让 data[size] = val;这表示增加了第一个元素data[0],此时,数组下一次希望添加的元素是 data[1]、如果依然想使用add方法来增加的话、就必须要在让 size = 1,即 size ++ 。得出结论,每进行一次add方法之后,始终让size指向下一个期待存入的元素的位置 、即size++

注意:这有可能会产生一种情况、那就是数组的空间不够用的情况、此时、需要扩容,而这正是动态数组的精髓所在。通过size和data.length的大小比较、我们就可以很明显的感受什么时候需要扩容。
因为size总是指向下一个期待传入的元素的位置,那么当size = data.length-1时,表示期待传入数组尾元素(即最后一个元素)、数组还剩有一个空间;当size = data 时,此时表明期待传入索引为 data.length 的元素、但是 数组的最后一个元素下标就是data.length-1、此时、显然已经不够用,需要扩容。定义一个方法 grow 来实现扩容、并在add方法中调用它。
在这里插入图片描述

public void add ( int val )
{
    // size 为 全局变量、初始时有初值 0
    // 首先让传入的参数存储到 data[0] 这个位置
    data[size] = val;

    // 让 size 加一、方便下次添加元素、
    // 也可以保证每一个 add 后,有效元素之后一定还有空间
    // 每增加完一个元素、总是让 size 指向当前元素后一个位置
    size ++;
    
    //扩容
    if ( size == data.length )
    	grow();


}

grow 方法(Arrays.copyOf ) 实现

//对于外部使用者、不知道MyArray类中有一个 int[]
// 数组的扩容对于外界也应该是不可见的
private void grow()
{
    // 扩容为原来的长度加一
    // 实际上 这是先创建了一个新的数组空间、然后让我们的数组引用指向它
    // (旧的数组引用存储新的数组对象地址)
    this.data = Arrays.copyOf( data, data.length + 1 );
    
    // this.data 表明当前对象数组
    
}

在 index 索引位置、插入元素:
先把 index 这个位置的元素 和 它后面的所有的元素 向后移动一个数组单位
然后将 val 元素存入 index 位置
在这里插入图片描述

public void add( int index, int val )  //可以定义为重载方法
{
    // 判断边界问题
    // 当 index = 0 时、表示在数组头部插入
    // 当 index = size 时、表示在数组尾部插入
    // 所以这是两个边界
    if( index < 0 || index > size )
    {
        System.out.println("边界不合法!无法插入!");
        // 因为此时无法插入、没有意义、直接结束方法的调用
        return ;
    }

    // 思考正常插入的情况
    // 需要从最后一个开始,到 index 这个位置
    // 把这些元素 全部向后移一个单位
    // 但是这样难免会出现数组长度不够的问题
    for ( int i = size - 1; i >= index; i -- )
    {
        if (size == data.length)
        {
            grow();
        }
        data[i+1] = data[i];

    }

    // 当 位于索引为 index 及它后面的元素全部移动成功之后
    // index 位置空出来
    data[index] = val;

    size ++;
    // 为什么要执行一次 size ++ ?
    // 为下一次添加数组元素做准备

}

查询当前动态数组中第一个值为val的元素对应的索引

public int getByValue(int val) 
{
	// 遍历当前的动态数组
	for ( int i = 0; i <= size -1 ; i ++ ) 
	{
    	if ( data [i] == val ) 
    	{
        	return i;
    	}
	}
	// 还没找到val,不存在
	return -1;
}

判断当前动态数组中是否包含值为val的元素

public boolean contains( int val ) 
{
	int index = getByValue ( val ) ;
	// 调用方法、通过值查找的方法

	return index != -1;
	// 如果 index 等于 -1、则表示 (-1)  != (-1) 为假
	// 表明没有找到
	// 如果 index 不等于 -1、则找到了 
}

查询当前动态数组中索引index的元素值

public int get ( int index ) 
{
    // 判断边界问题
    // size-1对应最后一个有效元素
    if ( index < 0 || index >= size ) 
    {
        System.out.println("索引不合法!");
        return -1;
    }
    return data[ index ];
}

修改当前动态数组中索引为index位置的元素值为newVal返回修改前的值oldVal

public int set(int index,int newVal) 
{
	if (index < 0 || index >= size) 
	{
    	System.out.println("索引非法");
    	return -1;
	}
	
	int oldVal = data[index];
	
	data[index] = newVal;
	
	return oldVal;
}

将动态数组中第一个值为oldVal的元素修改为newVal

public boolean setValue( int oldVal, int newVal ) 
{
	// 调用方法、通过值查找的方法
	int index = getByValue(oldVal);

	if (index != -1) 
	// 不等于-1、表示找到了
	{
    	data[index] = newVal;
    	return true;
	}
	
	System.out.println("值不存在、无法修改!");
	return false;
}

public int removeIndex( int index ):删除索引为index的元素、并返回那个被删除的元素;
public int removeFirst():删除数组头元素、并返回首元素;
public int removeLast():删除数组尾元素、并返回尾元素;
public boolean removeByValueOnce( int val ):删除第一个值为 val 的元素,返回是否删除成功。

先判断、边界问题:

如果 index = 0 表示删除首元素、如果 index = size-1 表示删除尾元素
那么 index < 0 或者 index >= size 时,代表没有意义,此时应该输出索引不合理、并立即退出方法

下面思考正常情况:

先将所有下标为 index +1 的元素及后面的所有元素向左移一格。 即将数组中所有元素从下标为 index 的元素开始、i = indexdata[ i ] = data[ i + 1 ]
(这会覆盖掉下标为 index 的元素、但是我们需要返回这个元素、所以需要提前把它存储起来 int hasRemovedElement = data[ index ] );
最后一个元素的下标为多少? 答:size-1

循环执行结束时、i+1 = size-1、所以 i 只能到 size-2

为什么最后的元素到 size-1、那是因为size的意义表示下一次期待存入元素的位置、所以下标为size-1时、即为最后一个有效元素

for ( int i = index; i <= size-2; i ++ )
{
	data[i] = data[i+1];
	// 这个方法并不会造成数组溢出的情况、所以不用考虑
}

再另 size 的值 减减、(因为size代表的是数组的有效长度、减一即实现删除了一个元素

size --;

最后返回那个被删除的元素

return hasRemovedElement;

整体代码:

public int removeIndex ( int index )
{
    if( index < 0 || index >= size )
    {
        System.out.println("索引不合法!");
        return -1;
    }
    int hasRemovedElement = data[index];

    for( int i = index; i <= size-2; i ++ )
    {
        data[i] = data[i+1];
    }

    size --;
    return hasRemovedElement;
}

下面两个方法、都是通过调用 removeIndex( int index ) 来进行。

// 删除首元素
// 调用删除索引元素那个方法
public int removeFirst()
{
    return removeIndex( 0 );
}


// 删除尾元素
public int removeLast()
{
    return removeIndex( size -1 );

}

删除当前动态数组中第一个值为val的元素,返回是否删除成功

public boolean removeByValueOnce(int val) 
{
    for ( int i = 0; i <= size - 1 ; i ++ ) 
    {
        if ( data[i] == val) 
        {
            // 此时i就是第一个值为val的元素
            removeIndex(i);
            return true;
        }
    }
    return false;
}

如何在数组中删除所有值为val的数据?

  • 16
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值