线性表
线性表是 n 个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有顺序表、链表、栈和队列等。
线性表在逻辑上是一条连续的直线,但在物理结构上不一定连续,线性表在物理上存储时,通常以数组和链式结构的形式存储。分别如下图所示:
顺序表(顺序结构):
线性表(链式结构):
顺序表
顺序表是用一段物理地址连续的存储单元,依次存储数据元素的线性结构,一般情况下采用数组存储。接下来将在数组上完成数据的增删查改操作。
首先定义一个顺序表类:
public class ArrayList {
public int[] elem;
public int usedSize; //数组中数据的个数
public ArrayList() {
this.elem = new int[5]; //初始化数组容量为5
}
public ArrayList(int capacity) {
this.elem = new int[capacity]; //也支持传递参数来初始化数组容量
}
}
为了方便查看顺序表中的数据,先实现一个遍历顺序表的方法:
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i]+" ");
}
System.out.println();
}
获取顺序表的长度,可以直接将 usedSize 返回:
public int size() {
return this.usedSize;
}
运行结果:
判断顺序表中是否包含某一元素:
public boolean contains(int num){
for (int i = 0; i < this.usedSize; i++) {
//这里的数据是基本数据类型,所以用了==
//如果顺序表中的数据是其他类型,需要视情况更改
if (num == this.elem[i]){
return true;
}
}
return false;
}
运行结果:
返回某个元素对应的下标,这个和上面的类似,只需将返回值更改即可:
public int index(int num){
for (int i = 0; i < this.usedSize; i++) {
if (num == this.elem[i]){
return i;
}
}
return -1;
}
运行结果:
在实现增加元素的方法前,我们需要先实现一个判断数组是否还能添加数据,也就是判断数组满了没有:
//返回true,说明数组中的数据个数和数组的长度相等,也就意味着数组满了
public boolean isFull() {
return this.usedSize == this.elem.length;
}
数组满了,就需要扩充数组:
//扩充数组大小
public void resize() {
//每次扩大一倍
this.elem = Arrays.copyOf(this.elem, this.elem.length * 2);
}
扩充后:
接下来就可以实现增加元素的方法了,默认在数组最后添加:
public void add(int num) {
//添加数据时,需要先判断数组中是否还有位置能够添加数据
if (isFull()) {
resize();
}
this.elem[this.usedSize] = num;
this.usedSize++;
}
运行效果:
在指定位置添加数据时,需要判断位置是否合理:
public boolean checkIndex(int pos) {
if (pos < 0 || pos > this.usedSize) {
throw new IndexOutOfException("位置不合法");
}
return true;
}
这里我们使用了一个自定义异常:
public class IndexOutOfException extends RuntimeException {
public IndexOutOfException() {
}
public IndexOutOfException(String mes) {
super(mes);
}
}
现在就可以在指定位置添加元素了:
public void add(int pos, int data) {
checkIndex(pos);
if (isFull()) {
resize();
}
for (int i = this.usedSize; i > pos; i--) {
this.elem[i] = this.elem[i - 1];
}
this.elem[pos] = data;
this.usedSize++;
}
运行效果:
更改指定位置元素的值:
public void set(int pos,int data){
checkIndex(pos); //同样需要检查位置
this.elem[pos]=data;
}
运行效果:
删除第一次出现的关键字 key:
public boolean remove(int toRemove) {
//先检查是否有这个数
int index = index(toRemove);
if (-1 == index) {
System.out.println("没有这个数据");
return false;
} else {
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
this.elem[usedSize] = 0;//这里是基本数据类型,置为 0就行,如果为引用类型,需要置为空
return true;
}
}
运行结果:
ArrayList
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它没有固定大小的限制,我们可以添加或删除元素。
ArrayList 是以泛型方式实现的,使用时需要先实例化。
ArrayList 实现了 RandomAccess 接口,所以 ArrayList 支持随机访问。
ArrayList 实现了 Cloneable 接口,所以 ArrayList 可以克隆。
ArrayList 实现了 Serializable 接口,所以 ArrayList 支持序列化。
ArrayList 底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。
ArrayList的构造
无参构造:
指定初始容量构造:
利用其它列表构造:
注意: 如果在省略了类型,那么什么类型的数据都可以存放,这就可能会错误存取一些数据。
ArrayList常见操作
ArrayList 是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
添加元素 add():
访问元素 get():
修改元素 set():
删除元素 remove():
计算大小 size():
遍历列表:
截取部分 list:
List<E> subList(int fromIndex, int toIndex)
除了上面常用的操作方法,ArrayList 还提供了很多其他的操作方法,需要时可以到下面的网址查询:ArrayList 操作方法。
ArrayList案例(杨辉三角)
先给出题目链接,有兴趣的可以一试:LeetCode–118. 杨辉三角
题目描述:
题目分析:
根据分析我们可以得出下面这段代码:
public static void main(String[] args) {
int[][] arr = new int[5][5];
for (int i = 0; i < 5; i++) {
for (int j = 0; j <= i; j++) {
if (i == j || j == 0) {
arr[i][j] = 1;
} else {
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
}
}
}
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j <= i; j++) {
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
运行结果:
但是,我们来看题目,并不是用数组随便做出来就行,有固定要求的:
方法都大同小异,只不过写法上不同,先来看解析:
代码:
public static List<List<Integer>> generate(int numRows) {
//生成一个 lists,lists里存放list,每个list里的数据类型是Integer
List<List<Integer>> lists = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
//这里并不知道杨辉三角有几层,所以每次for循环就从新new一个
List<Integer> list = new ArrayList<>();
for (int j = 0; j <= i; j++) {
if (j == 0 || j == i) {
//和数组一样,只不过使用add方法把 1 加入到每个单独的list里
list.add(1);
} else {
//其他值获取到也是使用add加入到list
list.add(lists.get(i - 1).get(j) + lists.get(i - 1).get(j - 1));
}
}
//每个list完成后添加到lists
lists.add(list);
}
//返回lists即可
return lists;
}
运行效果:
Arraylist的问题
- ArrayList 使用连续的空间,这使得查询很快,只需给出下标即可查询某一元素,但也正因为是连续的空间,所以在任意位置插入和删除元素时,都会涉及其他元素的移动。
- 扩容时申请新的空间,拷贝数据,再释放旧的空间,会有不少的消耗。
- 扩容的时候,不是添加一个数据就只加一个数据的空间,而是按倍数增加空间,有时候只增加一个数据,剩下的空间就会浪费。
面对以上的问题,我们就需要使用其他的数据结构 ------ 链表。