Java数据结构与算法之顺序表的实现

一、前言

上一篇中内容中,我们从存储结构、性能以及试用场景几个方面分析了数组这种最基本的数据结构。顺序表的也是由数组实现的。在实际的开发过程中,我们也经常使用一个与数组类似的顺集合容器—ArrayList。ArrayList的底层实现就是数组,但是它把数组的特性进行了封装,调用起来更方便,同时优化了数组的痛点(不支持自动扩容)。下面我们就利用上一篇学到的知识,试着完成一个简单的顺序表。如图:

                             

先简单回顾下数组的特点:数组是一种线性表数据结构,是一组连续的内存空间(这也是随机访问的重要切入点),存储的数据类型相同。(https://blog.csdn.net/anyway8090/article/details/89428804)

下面来看一个接口MyList,接口中包含了容器类的大部分方法,包括增删改查的方法也就是我们自己手写的手写的MyArrayList的顶级接口。

package cn.com.arrayList;

/**
 * 顺序表顶级接口
 * @author Administrator
 */
public interface MyList<T> {

	/**
	 * 判断顺序表是否为空
	 */
	boolean isEmpty();
	/**
	 * 顺序表大小
	 */
	int size();
	/**
	 * 获取元素
	 * @param index
	 * @return
	 */
	T get(int index);
	/**
	 * 设置某个元素的值
	 * @param index
	 * @param data
	 * @return
	 */
	T set(int index,T data);
	/**
	 *添加元素
	 * @param data
	 * @return
	 */
	boolean add(T data);
	/**
	 * 在指定位置添加元素
	 * @param index
	 * @param data
	 * @return
	 */
	boolean add(int index,T data);
	/**
	 * 根据index移除元素
	 * @param index
	 * @return
	 */
	T remove(int index);
	/**
	 * 根据data移除元素
	 * @param data
	 * @return
	 */
	boolean remove(T data);
	/**
	 * 清空链表
	 */
	void clear();
	/**
	 * 是否包含data元素
	 * @param data
	 * @return
	 */
	boolean contains(T data);
	/**
	 * 根据值查询下标
	 * @param data
	 * @return
	 */
	int indexOf(T data);
	/**
	 * 根据data值查询最后一个出现在顺序表中下标
	 * @param data
	 * @return
	 */
	int lastIndexOf(T data);
	/**
	 * 输出格式
	 * @return
	 */
	String toString();
}

首先实现接口先实现接口MyList,从写接口中的方法,声明当前数组,当前数组大小及默认数组的长度大小为10。

	//当前数组
	private Object[] table;
	//当前数组长度
	private int size;
	//默认数组长度
	public final static int DEFULT_LENTHT=10;

此处声明两个构造方法,一个指定数据的长度,一个默认无参构造方法,而无参构造方法调指定默认数组长度大小为10;

	/**
	 *指定容器大小 
	 * @param capacity
	 */
	public MyArrayList(int capacity){
		this.table=new Object[capacity];
	}
	/**
	 * 无参构造方法,设置默认大小10
	 */
	public MyArrayList(){
		this(DEFULT_LENTHT);
	}

 来先看下几个简单的方法

判断是容器是否为空,只需判断当前数组的长度是否为0;

	/**
	 * 判断是否为空,判断当前容器大小是否为0
	 */
	@Override 
	public boolean isEmpty() {
 		return this.size==0;
	}

获得元素在容器的中的位置:由于容器本身是个数组,所有元素的位置也就是元素在数组中的下标,而确定元素的下标需要遍历所有元素从而找到元素的下标位置。

	/**
	 * 根据下标寻址,默认返回-1,表示该元素在容器中不存在
	 */
	@Override
	public int indexOf(T data) {
		if (data != null) {
			for (int i = 0; i < table.length; i++) {
				if (this.table[i].equals(data)) {
					return i;
				}
			}
		}
		return -1;
	}

判断元素最后一次出现的位置:与indexOf的方法相似,只是遍历的顺序会和indexOf相反(代码就省略了)

容器指定位置添加元素:因为数组的连续性,在未指定条件的情况,新加入顺序表的元素都会放入的数组的末端。当数组长度无法满足当前添加元素时,触发容器的动态扩容机制。

    @Override
    public void add(int index, T data) {
       //边界校验,插入元素失败
	    if (index < 0 || index > size()) {
		    return false;
	     }  
	    //动态扩容
        ensureCapacity(index);
	    // 插入新值
	    this.table[index] = data;
	    // 长度加1
	     size++;
	     return true;
    }
        /**
	 * 判断数据是否需要扩容,当容器大小与数组的大于等于数组大小时,
	 * 将原来的数据扩容到之前的2倍,并且搬移数组到新数组中
	 * @param index
	 */
	public void ensureCapacity(int index){
		if (size() == table.length) {
			//将当前数组赋值给临时数组
			Object[] temp = this.table;
			// 将原来的数组扩容到原来的两倍
			this.table = new Object[size() *2];
			// 把原来的数组赋值复制到新的数组中
			for (int i = 0; i < size(); i++) {
				this.table[i] = temp[i];
			}
		}
        //index后面的元素向后移动一位
		for (int j = size; j > index; j--) {
			this.table[j] = this.table[j-1];
		}
	}

指定位置添加元素时,默认在容器末尾添加元素:

	/**
	 * 在尾部插入元素
	 */
	@Override
	public boolean add(T data) {
		return this.add(size(),data);
	}
	

获取指定位置的元素:由于顺序表的底层是由数组实现,所以根据数组下标获取元素值,如果边界不合法返回null

	/**
	 * 获取元素(根据数组下标获取元素)
	 */
	@Override
	public T get(int index) {
		if(index> 0 && index<this.size){
			return (T) this.table[index];
		}
		return null;
	}

删除指定位置的元素:由于数组中的数据具有连续性,在删除数据组时,需要进行数据搬移才能保证数据的连续性,在大规模的数据搬移过程中,此时的性能消耗是不可忽略的。

	/**
	 * 删除指定位置的元素
	 */
	@Override
	public T remove(int index) {
		if(index > 0 && index<size){
			//记录删除的元素并返回
			T old=(T) this.table[index];
			//从被删除的元素开始,后面的元素依次上前移动一位
			for (int j = index; j < size-1; j++) {
				this.table[j]=this.table[j+1];
			}
			//将末尾元素设置为null
			this.table[size-1]=null;
			//顺序表长度-1
			--size;
			return old;
		}
		return null;
	}

删除指定元素:删除不为null的元素,首先找到在数组中找到指定元素的位置,然后将该元素后的元素进行搬移,将删除的元素赋值为null;

	public boolean remove(T data) {
		if (data != null) {
			for (int index = size; index > 0; index--) {
				if (data.equals(table[index])) {
					// 从被删除的元素开始,后面的元素依次上前移动一位
					for (int j = index; j < size - 1; j++) {
						this.table[j] = this.table[j - 1];
					}
					// 末尾的元素设置为null
					this.table[size - 1] = null;
					// 顺序表长度-1
					--size;
					return true;
				}

			}

		}
		return false;
	}

修改某个元素的值:校验边界合法性,根据下标获取该元素,用新元素替换旧元素

        /**
	 * 设置某个节点的值返回旧的节点
	 */
	@Override
	public T set(int index, T data) {
		if(index>0 && index < size  ){
			T olddata = (T) this.table[index];
			this.table[index]=data;
			return  olddata;
		}
		return null;
	}

 

以上就是顺序表最基本的增删改查方法,从上面的方法中,可以看出,顺序表结构在增删情况下由于要涉及数据搬移效率要明显低于读写,不过在已经数据较少同时对于性能不敏感的情况下,顺序表基本能满足需求,譬如ArrayList在我们实际系统中应用是比较广泛的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法: 算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值