顺序表(数组)实现
1.1 基本介绍
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
数组是最常见的顺序表,数组(Array)
是有限个相同类型的变量所组成的有序集合,数组中的每一个变量被称为元素。数组是最为简单、最为常用的数据结构。
1.2 相关原理分析
1、基本概念
数组用一组连续的内存空间来存储一组具有相同类型的数据。
(模拟内存存储)灰色格子:被使用的内存,黄色格子:空闲的内存,蓝色格子:数组占用的内存
2、顺序表API设计
类名 | SqList |
---|---|
构造方法 | SqList(int capacity) :创建容量为capacity 的SqList 对象 |
成员方法 | 1、public void clear() :清空所有元素。2、 public boolean isEmpty() :判断线性表是否为空,是返回true ,否返回false 。3、 public int size() :获取线性表中元素的个数。4、 public E get(int index) : 返回index位置对应的元素。5、 public void add(int index,E data) :在线性表的第i 个元素之前插入一个值为t 的数据元素。6、 public void add(E data) :向线性表中添加一个元素t 。7、 public E remove(int index) : 删除index位置对应的元素。8、 public int indexOf(E e) : 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1 。9、 boolean contains(E data) :是否包含某个元素。10、 public E set(int index, E data) :设置index 位置的元素 |
成员变量 | 1、private E[] data :存储元素的数组。2、 private int size :当前线性表的长度。 |
1.3 添加元素
1、尾部插入
在数据的实际元素数量小于数组长度的情况下:直接把插入的元素放在数组尾部的空闲位置即可,等同于更新元素的操作。
直接在索引(index = 6)的位置上添加元素10
2、通过数组下标插入
在数据的实际元素数量小于数组长度的情况下:
由于数组的每一个元素都有其固定下标,所以首先把插入位置及后面的元素向后移动腾出地方,再把要插入的元素放到对应的数组位置上。
3、代码实现
顺序表:SqList
package cn.liner.demo01;
public class SqList{
// 顺序表的元素
private int[] data;
// 顺序表元素的数量
private int size;
// 构造函数,传入数组的容量capacity构造SqList
public SqList(int capacity){
data = new int[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public SqList(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
// 向所有元素后添加一个新元素
public void addLast(int e){
add(size, e);
}
// 在所有元素前添加一个新元素
public void addFirst(int e){
add(0, e);
}
// 在index索引的位置插入一个新元素e
public void add(int index, int e){
if(size == data.length){
throw new IllegalArgumentException("数组已满,添加失败!!");
}
if(index < 0 || index > size){
throw new IllegalArgumentException("添加失败,索引值不符合添加范围!!");
}
for(int i = size - 1; i >= index ; i --){
data[i + 1] = data[i];
}
data[index] = e;
size ++;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("顺序表(SqList)长度:%d, 容器:%d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}
测试类:SqListDemo01
package cn.liner.demo01;
/**
* 顺序表测试类
*/
public class SqListDemo01 {
public static void main(String[] args) {
//1.1 创建顺序表对象
SqList list = new SqList(20);
for (int i = 0; i < 10; i++)
list.addLast(i);
System.out.println(list);
System.out.println("=====");
list.add(1, 100);
System.out.println(list);
System.out.println("=====");
list.addFirst(-1);
System.out.println(list);
}
}
3、执行结果
1.4 查询和修改元素
1、代码实现
顺序表:SqList
package cn.liner.demo01;
public class SqList{
// 顺序表的元素
private int[] data;
// 顺序表元素的数量
private int size;
// 构造函数,传入数组的容量capacity构造SqList
public SqList(int capacity){
data = new int[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public SqList(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取index索引位置的元素
public int get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("获取失败!!该索引值不存在!!");
return data[index];
}
// 修改index索引位置的元素为e
public void set(int index, int e){
if(index < 0 || index >= size){
throw new IllegalArgumentException("设置失败,该索引值不存在");
}
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(int e){
for(int i = 0 ; i < size ; i ++){
if(data[i] == e)
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(int e){
for(int i = 0 ; i < size ; i ++){
if(data[i] == e)
return i;
}
return -1;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("顺序表(SqList)长度:%d, 容器:%d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}
测试类:SqListDemo01
package cn.liner.demo01;
/**
* 顺序表测试类
*/
public class SqListDemo01 {
public static void main(String[] args) {
//1.1 创建顺序表对象
SqList list = new SqList(20);
System.out.println("===顺序表添加===");
for (int i = 0; i < 10; i++){
list.addLast(i);
}
System.out.println(list);
System.out.println("===顺序表(获取索引位置的元素)===");
int index = list.get(2);
System.out.println("查找到的元素是:" +index);
System.out.println("===顺序表(修改索引位置的元素)===");
list.set(3,100);
System.out.println(list);
System.out.println("===顺序表(查找数组中是否有该元素)===");
boolean flag = list.contains(4);
System.out.println("是否存在:" + flag);
System.out.println("===顺序表(查找数组中元素e所在的索引)===");
int i = list.find(100);
System.out.println(i);
}
}
2、执行结果
1.5 删除元素
1、代码实现
顺序表:SqList
package cn.liner.demo01;
public class SqList{
// 顺序表的元素
private int[] data;
// 顺序表元素的数量
private int size;
// 构造函数,传入数组的容量capacity构造SqList
public SqList(int capacity){
data = new int[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public SqList(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 从数组中删除index位置的元素, 返回删除的元素
public int remove(int index){
if(index < 0 || index >= size){
throw new IllegalArgumentException("移除失败,索引值不存在!!!");
}
int ret = data[index];
for(int i = index + 1 ; i < size ; i ++){
data[i - 1] = data[i];
}
size --;
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public int removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public int removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(int e){
int index = find(e);
if(index != -1) {
remove(index);
}
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("顺序表(SqList)长度:%d, 容器:%d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}
测试类:SqListDemo01
package cn.liner.demo01;
/**
* 顺序表测试类
*/
public class SqListDemo01 {
public static void main(String[] args) {
//1.1 创建顺序表对象
SqList list = new SqList(20);
System.out.println("===顺序表添加===");
for (int i = 0; i < 10; i++){
list.addLast(i);
}
System.out.println(list);
System.out.println("===删除元素操作====");
// 从数组中删除index位置的元素
list.remove(2);
System.out.println(list);
// 从数组中删除元素e
list.removeElement(4);
System.out.println(list);
// 删除第一个元素
list.removeFirst();
System.out.println(list);
// 从数组中删除最后一个元素
list.removeLast();
System.out.println(list);
}
}
3、执行结果
1.6 泛型技术
使用泛型技术可以让数组更加通用,可以存放任何数据类型。
1、代码实现
顺序表:SqList
package cn.liner.demo01;
import java.util.Iterator;
public class SqList<E> implements Iterable<E>{
// 顺序表的元素
private E[] data;
// 顺序表元素的数量
private int size;
// 定义常量
private static final int ELEMENT_NOT_FOUND = -1;
// 构造函数,传入数组的容量capacity构造SqList
public SqList(int capacity){
data = (E[]) new Object[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public SqList(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
// 向所有元素后添加一个新元素
public void addLast(E e){
add(size, e);
}
// 在所有元素前添加一个新元素
public void addFirst(E e){
add(0, e);
}
// 在index索引的位置插入一个新元素e
public void add(int index, E e){
if(size == data.length){
throw new IllegalArgumentException("数组已满,添加失败!!");
}
for(int i = size - 1; i >= index ; i --){
data[i + 1] = data[i];
}
data[index] = e;
size ++;
}
// 获取index索引位置的元素
public E get(int index){
rangeCheck(index);
return data[index];
}
// 查看元素的索引
public int indexOf(E e){
if (e == null) {
for (int i = 0; i < size; i++) {
if (data[i] == null) return i;
}
} else {
for (int i = 0; i < size; i++) {
if (e.equals(data[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
// 修改index索引位置的元素为e
public void set(int index, E e){
rangeCheck(index);
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(E e){
return indexOf(e) != ELEMENT_NOT_FOUND;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return i;
}
return -1;
}
// 从数组中删除index位置的元素, 返回删除的元素
public E remove(int index){
rangeCheck(index);
E ret = data[index];
for(int i = index + 1 ; i < size ; i ++){
data[i - 1] = data[i];
}
size --;
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(E e){
int index = find(e);
if(index != -1) {
remove(index);
}
}
// 数组索引越界处理
private void outOfBounds(int index){
throw new IndexOutOfBoundsException("index:" + index + ", Size:" + size);
}
// 索引值检查范围方法
private void rangeCheck(int index){
if(index < 0 || index >=size){
// 调用越界处理方法
outOfBounds(index);
}
}
// 添加方法索引检查范围
private void rangeCheckAdd(int index){
if(index < 0 || index >size){
// 调用越界处理方法
outOfBounds(index);
}
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("顺序表(SqList)长度:%d, 容器:%d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
// 遍历方法
@Override
public Iterator<E> iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
// 定义一个指针变量
private int cur;
public SIterator(){
this.cur=0;
}
@Override
public boolean hasNext() {
return cur< size;
}
@Override
public E next() {
return data[cur++];
}
}
}
测试类: Student
package cn.liner.demo01;
public class Student implements Comparable<Student>{
// 成员变量
private String username;
private int userage;
// 构造方法
public Student(String username, int userage) {
this.username = username;
this.userage = userage;
}
@Override
public int compareTo(Student o) {
return o.userage - this.userage;
}
@Override
public boolean equals(Object student) {
if(this == student)
return true;
if(student == null)
return false;
if(this.getClass() != student.getClass())
return false;
Student o = (Student)student;
return this.userage == o.userage;
}
@Override
public String toString() {
return String.format("Student[姓名:%s, 年龄:%d]", username, userage);
}
public static void main(String[] args) {
// 创建list对象
SqList<Student> list = new SqList<>();
// 往数组中添加元素
list.addLast(new Student("guardwhy", 10));
list.addLast(new Student("curry", 12));
list.addLast(new Student("james", 38));
// 遍历数组
for(Student s : list){
System.out.println(s);
}
}
}
2、执行结果
1.7 动态数组
1、数组的扩容操作
当使用SqList
时,先new SqList(5)
创建一个对象,创建对象时就需要指定容器的大小,初始化指定大小的数组来存储元素,当插入元素时,如果已经插入了5
个元素,还要继续插入数据,则会报错,就不能插入了。
这种设计不符合容器的设计理念,因此在设计顺序表时,应该考虑它的容量的伸缩性。考虑容器的容量伸缩性,其实就是改变存储数据元素的数组的大小,需要考虑什么时候需要改变数组的大小?
添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,这里创建一个是原数组1.5倍容量的新数组存储元素。
2、数组的缩容操作
移除元素时,应该检查当前数组的大小是否太大,比如正在用100
个容量的数组存储10
个元素,这样就会造成内存空间的浪费,应该创建一个容量更小的数组存储元素。如果发现数据元素的数量不足数组容量的1/4
,则创建一个是原数组容量的1/2
的新数组存储元素。
3、代码示例
顺序表:SqList
package cn.liner.demo01;
import java.util.Iterator;
public class SqList<E> implements Iterable<E>{
// 顺序表的元素
private E[] data;
// 顺序表元素的数量
private int size;
// 定义常量
private static final int ELEMENT_NOT_FOUND = -1;
private static final int DEFAULT_CAPACITY = 6;
// 元素的数量
public int size() {
return size;
}
//清除所有元素
public void clear() {
for (int i = 0; i < size; i++) {
data[i] = null;
}
size = 0;
}
// 构造函数,传入数组的容量capacity构造SqList
public SqList(int capacity){
data = (E[]) new Object[capacity];
capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
}
// 无参数的构造函数,默认数组的容量capacity=10
public SqList(){
this(DEFAULT_CAPACITY);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
// 向所有元素后添加一个新元素
public void addLast(E e){
add(size, e);
}
// 在所有元素前添加一个新元素
public void addFirst(E e){
add(0, e);
}
// 在index索引的位置插入一个新元素e
public void add(int index, E e){
// 扩容操作
if(size == data.length){
resize(2 * data.length);
}
for(int i = size - 1; i >= index ; i --){
data[i + 1] = data[i];
}
data[index] = e;
size ++;
}
// 获取index索引位置的元素
public E get(int index){
rangeCheck(index);
return data[index];
}
// 查看元素的索引
public int indexOf(E e){
if (e == null) {
for (int i = 0; i < size; i++) {
if (data[i] == null) return i;
}
} else {
for (int i = 0; i < size; i++) {
if (e.equals(data[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
// 修改index索引位置的元素为e
public void set(int index, E e){
rangeCheck(index);
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(E e){
return indexOf(e) != ELEMENT_NOT_FOUND;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return i;
}
return -1;
}
// 从数组中删除index位置的元素, 返回删除的元素
public E remove(int index){
rangeCheck(index);
E ret = data[index];
for(int i = index + 1 ; i < size ; i ++){
data[i - 1] = data[i];
}
// 置空
data[--size] = null;
// 缩容操作
if(size == data.length / 4){
resize(data.length / 2);
}
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(E e){
int index = find(e);
if(index != -1) {
remove(index);
}
}
// 数组索引越界处理
private void outOfBounds(int index){
throw new IndexOutOfBoundsException("index:" + index + ", Size:" + size);
}
// 索引值检查范围方法
private void rangeCheck(int index){
if(index < 0 || index >=size){
// 调用越界处理方法
outOfBounds(index);
}
}
// 添加方法索引检查范围
private void rangeCheckAdd(int index){
if(index < 0 || index >size){
// 调用越界处理方法
outOfBounds(index);
}
}
// 扩容capacity方法
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for(int i = 0 ; i < size ; i ++){
newData[i] = data[i];
}
data = newData;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("顺序表(SqList)长度:%d, 容器:%d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
// 遍历方法
@Override
public Iterator<E> iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
// 定义一个指针变量
private int cur;
public SIterator(){
this.cur=0;
}
@Override
public boolean hasNext() {
return cur< size;
}
@Override
public E next() {
return data[cur++];
}
}
}
测试类:SqListDemo01
package cn.liner.demo01;
/**
* 顺序表测试类
*/
public class SqListDemo01 {
public static void main(String[] args) {
//1.1 创建顺序表对象
SqList<Integer> list = new SqList();
System.out.println("===顺序表添加===");
for (int i = 0; i < 10; i++){
list.addLast(i);
}
System.out.println(list);
System.out.println("===顺序表(修改索引位置的元素)===");
list.set(3,100);
System.out.println(list);
}
}
4、执行结果
1.8 时间复杂度分析
读取和更新都是随机访问,所以是O(1)
,插入数组扩容的时间复杂度是O(n)
,插入并移动元素的时间复杂度也是O(n)
,综合起来插入操作的时间
复杂度是O(n)
。删除操作,只涉及元素的移动,时间复杂度也是O(n)
顺序表优点
- 数组拥有非常高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素。
顺序表缺点
- 插入和删除元素方面。由于数组元素连续紧密地存储在内存中,插入、删除元素都会导致大量元素被迫移动,影响效率。
(ArrayList LinkedList )
。 - 申请的空间必须是连续的,也就是说即使有空间也可能因为没有足够的连续空间而创建失败。
- 如果超出范围,需要重新申请内存进行存储,原空间就浪费了。