本人码云(建议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 = index
、data[ 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的数据?