前言
为了面试,时隔一年不得不重新复习数据结构与算法,这次重新复习很有收获,以前在上课总有不清晰的知识,通过这次手写一遍和敲一遍代码,以前不清晰的知识都变得清晰了,果然学编程还是得多敲代码,多敲代码,多敲代码,也要多总结,多总结,多总结。
学习资源
视频 黑马程序员 数据结构与算法
顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
1.实现
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
//顺序表
public class SequenceList<T> implements Iterable<T>{
//存储元素的数组
private T[] eles;
//记录当前顺序表中的元素个数
private int N;
//传入数组容量 capacity
public SequenceList(int capacity){
//初始化数组
this.eles = (T[]) new Object[capacity];
//初始化长度
this.N = 0;
}
//将一个线性表置为空表
public void clear(){
this.N = 0;
}
//获取线性表的长度
public int length(){
return N;
}
//获取指定位置的元素
public T get(int i){
return eles[i];
}
//向线型表中添加元素t
public void insert(T t){
//如果元素个数等于数组长度 对数组进行扩容 把原数组的长度变为原来的 2 倍
if(N==eles.length){
resize(2*eles.length);
}
eles[N++] = t;
}
//在i元素处插入元素t 12 4 索引2处插入3 1234
public void insert(int i,T t){
//如果元素个数等于数组长度 对数组进行扩容 把原数组的长度变为原来的 2 倍
if(N==eles.length){
resize(2*eles.length);
}
//先把i索引处的元素及其后面的元素依次向后移动一位 4后移一位
for (int index = N;index>i;index--){
eles[index] = eles[index-1]; //eles[3] = eles[2]
}
//再把t元素放到i索引处即可
eles[i]=t; //else[2] = 3
//元素个数+1
N++;
}
//删除指定位置i处的元素,并返回该元素 1234 删除索引 2 的元素 3
public T remove(int i){
//记录索引i处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for(int index=i;index<N-1;index++){
eles[index]=eles[index+1];
}
//元素个数-1
N--;
//如果元素个数是数组长度的 1/4 对数组进行缩容 把原数组的长度变为原来的 1/2
if(N<eles.length/4){
resize(eles.length/2);
}
//返回删除值
return current;
}
//查找t元素第一次出现的位置
public int indexOf(T t){
//从前往后遍历,找到和元素相等的值,返回下标
for(int index=0;index<N;index++){
if(t.equals(eles[index])){
return index;
}
}
//没有该元素
return -1;
}
//根据参数newSize,重置eles的大小
private void resize(int newSize){
//定义一个临时数组
T[] temp = eles;
//创建一个新数组
eles=(T[])new Object[newSize];
//把原数组的数据拷贝到新数组即可
for(int i= 0;i<N;i++){
eles[i]=temp[i];
}
}
//重写迭代器遍历方法
//实现Iterable接口,重写接口的iterator方法;
@Override
public Iterator<T> iterator() {
return new SIterator();
}
//实现Iterator接口,重写hasNext方法和next方法;
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
this.cusor=0;
}
@Override
public boolean hasNext() {
return cusor<N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
//测试
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> sl = new SequenceList<String>(23);
//测试插入
sl.insert("张三");
sl.insert("王五");
sl.insert("赵六");
sl.insert(1,"李四");
for (String s : sl) {
System.out.println(s);
}
System.out.println("------------------------------------------");
//测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
//测试删除
String removeResult = sl.remove(0);
System.out.println("删除索引0处的元素是:"+removeResult);
//测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:"+sl.length());
}
}
2.顺序表的遍历
一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式。
在java中,遍历集合的方式一般都是用的是foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:
- 让SequenceList实现Iterable接口,重写iterator方法;
- 在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
//重写迭代器遍历方法
//实现Iterable接口,重写接口的iterator方法;
@Override
public Iterator<T> iterator() {
return new SIterator();
}
//实现Iterator接口,重写hasNext方法和next方法;
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
this.cusor=0;
}
@Override
public boolean hasNext() {
return cusor<N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
3.顺序表的容量可变
- 添加元素时:
添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,我们这里创建一个是原数组两倍容量的新数组存储元素。
//如果元素个数等于数组长度 对数组进行扩容 把原数组的长度变为原来的 2 倍
if(N==eles.length){
resize(2*eles.length);
}
- 移除元素时:
移除元素时,应该检查当前数组的大小是否太大,比如正在用100个容量的数组存储10个元素,这样就会造成内存空间的浪费。
应该创建一个容量更小的数组存储元素。如果我们发现数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素。
//如果元素个数是数组长度的 1/4 对数组进行缩容 把原数组的长度变为原来的 1/2
if(N<eles.length/4){
resize(eles.length/2);
}
4.顺序表的时间复杂度
get(i): 不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,时间复杂为O(n);
remove(int i):每一次删除,都需要把i位置后面的元素移动一次,时间复杂度为O(n);
结论: 顺序表查询快,增删慢;
5.ArrayList实现
java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
1.是用数组实现;
2. 有扩容操作;
3. 有提供遍历方式;