一.概述
顺序表是在计算机内存中以数组(内存地址是连续的)的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中在逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
Java中常见的ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
二.代码实现
首先创造一个sequence类,在sequence类的构造函数中初始化数组e和记录元素个数的N。
实现Iterable接口是为了使顺序表能够用增强for循环来遍历。
public class sequence<T> implements Iterable<T>{
private T[] e; //初始化一个T类型的数组
private int N; //N用于记录当前顺序表中元素的个数
public sequence(int n){
e=(T[])new Object[n];//构造一个长度为n的空数组
N=0;}
清空数组,这里我令当前数组e的每个元素为null
如果使用重开一个数组,应该会增大开销。
//清空数组,重置N值
public void clear(){
//e=(T[]) new Object[e.length]; //重开一个数组
for(int i=0;i<e.length;i++){
e[i]=null;
}
N=0;}
返回顺序表的长度:
public int length(){
return N;}
是否为空:
public boolean isEmpty(){
return N==0;}
获取指定索引的元素:
public T get(int i) {
if (i < 0 || i >= N) {
throw new RuntimeException("不存在");//运行时异常
}
return e[i];
}
扩容/缩容的方法resize(),参数为数组需要扩/缩到的指定的容量大小newSize,所以不管是扩容还是缩容都调用此方法来改变数组长度。
//顺序表的扩容和缩容
public void resize(int newSize){
T[] temp=e; //创建一个临时数组temp记录原数组e
e=(T[])new Object[newSize]; //用原数组的引用e创建新容量的空数组
for(int i=0;i<N;i++){//将temp中原数组的元素遍历到新的空数组中,原数组共N个,遍历N次
e[i]=temp[i];
}
}
向顺序表的末尾添加元素:
先判断数组容量是否已经满了,若是则先调用上面的resize进行扩容,扩容规则:扩至原数组的2倍大小
原数组最后一个元素的索引为N-1,则新添加的元素索引为N
public void add(T t){//向顺序表末尾添加元素
if(N==e.length){
resize(e.length*2);
}
e[N++]=t; //将t放到索引N处
}
在指定的位置 i 插入新元素 t :
同样先判断数组是否满了,若是则同样以2倍进行扩容 。
扩的过程中,数组原本最后一位元素的索引为N-1,插入新的元素后,最后一位元素的索引应该为N,此时需要将索引 i 到索引 N-1 的元素,依次向后移动一位至索引 i+1 到索引N 处。
注:向后移动元素的时候必须采用倒序遍历的方法。
然后将新元素 t 放到索引 i 处,并将N+1。
public void insert(int i,T t){
if (i < 0 || i > N) {
throw new RuntimeException("插入位置非法");}
if(N==e.length){ //若数组满了则进行扩容
resize(e.length*2);}
//扩完容之后,再插入元素t
for(int index=N;index>i;index--){ //索引i后面的元素后移一位,倒序遍历
e[index]=e[index-1];
}
e[i]=t;
N++;
}
在指定位置删除元素:
删除 i 位置的元素后,需要将索引 i+1 到N-1的元素依次前移一位至 索引i到N-2处。前移则必须正序遍历。
缩容规则:若元素个数小于总长度的1/4则将数组容量调整至原长度的一半。
这里是先删元素再进行缩容,我也不知道为什么。。有没有大佬指点一下??
public T remove(int i){
T result=e[i]; //先记录下要删除的元素
if(i<0||i>N-1){ //索引N-1为最后一个元素
throw new RuntimeException("索引非法");
}
for(int index=i;index<=N-2;index++){
e[index]=e[index+1];// 索引i处元素删掉,i后面的前移,正序遍历
}
N--;
//删完之后再判断是否需要缩容
if(N>0&&N<e.length/4){
resize(e.length/2);
}
return result; //返回已被删除的元素
}
查找指定元素第一次出现的索引,for循环的,每个 i 正好对应索引,所以返回 i 即可。
public int index(T t){ //查找元素第一次出现的索引,
if(t==null){
throw new RuntimeException("查找的元素不能为空");
}
for(int i=0;i<N;i++){ //返回的是索引,则循环 i 次,i 正好对应索引
if(e[i].equals(t)){ //假设元素的equals已重写
return i;
}
}
return -1;
}
顺序表的遍历:
此处的sequence顺序表类实现了iterable接口,需要重写iterator方法
iterator方法固定返回一个Iterator,而Iterator还是一个接口,接口不能实例化对象,所以需要自己创建一个MyIterator类实现Iterator接口,并用MyIterator新建一个对象并return。
自己创建的MyIterator需要实现hasNext()和next()两个抽象方法。
//顺序表的遍历
public Iterator iterator(){ //需要返回一个Iterator接口的实现类
return new MyIterator();
}
private class MyIterator implements Iterator{ //成员内部类
private int cur=0; //顺序表底层是数组,则使用指针来遍历
public MyIterator(){
this.cur=0; //初始化遍历的指针
}
@Override
public boolean hasNext() {//判别当前数组是否还有元素可以遍历
return cur<=N-1; //最后一位元素的索引为N-1,当指针<=N-1时即存在下一个元素
}
@Override
public Object next() {//返回迭代的下一个元素
return e[cur++]; //指针++,即可得到下一个元素
}
}
=======================================================================================
完整代码及main函数中测试:
public class sequence<T> implements Iterable<T>{
private T[] e; //初始化一个T类型的数组
private int N; //N用于记录当前顺序表中元素的个数
public sequence(int n){
e=(T[])new Object[n];//构造一个长度为n的空数组
N=0;}
//清空数组,重置N值
public void clear(){
//e=(T[]) new Object[e.length];
for(int i=0;i<e.length;i++){
e[i]=null;
}
N=0;}
public int length(){
return N;}
public boolean isEmpty(){
return N==0;}
//获取指定索引的元素
public T get(int i) {
if (i < 0 || i >= N) {
throw new RuntimeException("不存在");//运行时异常
}
return e[i];
}
public void add(T t){//向顺序表末尾添加元素
if(N==e.length){
resize(e.length*2);
}
e[N++]=t;
}
//顺序表的扩容和缩容
public void resize(int newSize){
T[] temp=e; //创建一个临时数组temp记录原数组e
e=(T[])new Object[newSize]; //用原数组的引用e创建新容量的空数组
for(int i=0;i<N;i++){//将temp中原数组的元素遍历到新的空数组中,遍历N次
e[i]=temp[i];
}
}
//插入元素
public void insert(int i,T t){
if (i < 0 || i > N) {
throw new RuntimeException("插入位置非法");}
if(N==e.length){ //若数组满了则进行扩容
resize(e.length*2);}
//扩完容之后,再插入元素t
for(int index=N;index>i;index--){ //索引i后面的元素后移一位,倒序遍历
e[index]=e[index-1];
}
e[i]=t;
N++;
}
//删除指定位置的元素
public T remove(int i){
T result=e[i]; //先记录下要删除的元素
if(i<0||i>N-1){ //索引N-1为最后一个元素
throw new RuntimeException("索引非法");
}
for(int index=i;index<N-1;index++){
e[index]=e[index+1];// 索引i处元素删掉,i后面的前移,正序遍历
}
N--;
//删完之后再判断是否需要缩容
if(N>0&&N<e.length/4){
resize(e.length/2);
}
return result; //返回已被删除的元素
}
//查找元素第一次出现的位置
public int index(T t){
if(t==null){
throw new RuntimeException("查找的元素不能为空");
}
for(int i=0;i<N;i++){
if(e[i].equals(t)){ //假设元素的equals已重写
return i;
}
}
return -1;//没找到则返回-1
}
//顺序表的遍历
public Iterator iterator(){ //需要返回一个Iterator接口的实现类
return new MyIterator();
}
private class MyIterator implements Iterator{ //成员内部类
private int cur=0; //使用指针来遍历
public MyIterator(){
this.cur=0; //初始化指针为0
}
@Override
public boolean hasNext() {
return cur<=N-1;} //指针所在位置小于等于 最后一位元素的索引(N-1)即存在下一个元素
@Override
public Object next() {
return e[cur++]; //指针++即下一个元素
}
}
public static void main(String[] args) {
sequence<String> list=new sequence<>(3);
list.add("中国");
list.add("美国");
list.add("俄罗斯");
list.add("法国");
System.out.println(list.length()); //输出4
for (String s : list) {
System.out.println(s);
} //输出:中国 美国 俄罗斯 法国
list.remove(0); //删去 "中国"
System.out.println("------------");
System.out.println(list.get(2)); //输出:法国
System.out.println("------------");
list.clear(); //清空顺序表
list.add("俄罗斯");
System.out.println(list.length()); //输出:1
}
}
需要注意的地方:
索引N-1处为最后一个元素