【数据结构】实现顺序表以及简单的时间复杂度分析

最近在学数据结构,接下来一段时间我将用java来实现所学的各种数据结构,以加深自己的印象。

线性表包括顺序表和链表,其实顺序表就是动态数组,下面我将二次封装实现属于自己的动态数组。


数组类:Array

数组名:data

数组容量:capacity

已存放元素个数:size

方法:

①获取数组中元素个数(getSize

②获取数组容量(getCapacity

③返回数组是否为空(isEmpty

④向数组末尾添加元素(addLast

⑤向指定索引位置添加元素(add

⑥向所有元素前添加一个新元素(addFirst

⑦得到指定索引位置的元素(get

⑧修改指定索引位置的元素(set

⑨查找数组中是否有指定元素(contains

⑩查找数组中指定元素的索引(find

⑪删除指定索引位置的元素(remove

⑫删除数组中第一个元素(removeFirst

⑬删除数组最后一个元素(removeLast

⑭查找指定元素并删除(removeElement

⑮实现扩容和缩容(resize

当然感兴趣的可以继续添加自己想要的功能,比如删除全部元素等等。


public class Array {
	private E[] data;
	private int size;// 数组中元素个数

	// 构造函数,传入数组容量capacity构造Array
	public Array(int capacity) {
		data = (E[]) new Object[capacity];
		size = 0;
	}

	// 无参数构造函数,默认数组容量为10
	public Array() {
		this(10);
	}

	// 获取数组容量
	public int getCapacity() {
		return data.length;
	}

	// 获取数组中元素个数
	public int getSize() {
		return size;
	}

	// 返回数组是否为空
	public boolean isEmpty() {
		return size == 0;
	}

	// 向数组所有元素前添加元素
	public void addFirst(E e) {
		add(0, e);
	}

	// 向数组末尾添加元素
	public void addLast(E e) {
		add(size, e);
	}

	// 向数组index位置插入元素e
	public void add(int index, E e) {
		if (index < 0 || index > size)
			throw new IllegalArgumentException("操作失败,需要满足 index<0 || index>size");
		if (size == data.length)
			resize(data.length << 1);// 实现两倍扩容
		// 使元素往后挪一个位置
		for (int i = size - 1; i >= index; i--)
			data[i + 1] = data[i];
		data[index] = e;
		size++;
	}

	// 得到指定索引上的数组元素
	public E query(int index) {
		if (index < 0 || index >= size)
			throw new IllegalArgumentException("操作失败,需要满足 index < 0 || index >= size");
		return data[index];
	}

	// 获得第一个元素
	public E getFirst() {
		return query(0);
	}

	// 获得最后一个元素
	public E getLast() {
		return query(size - 1);
	}

	// 将指定索引上的元素修改成e
	public void modify(int index, E e) {
		data[index] = e;
	}

	// 查找数组中是否有元素e
	public boolean contains(int e) {
		for (int i = 0; i < size; i++)
			if (data[i].equals(e))
				return true;
		return false;
	}

	// 查找数组中指定元素的一个索引
	public int find(E e) {
		for (int i = 0; i < size; i++)
			if (data[i].equals(e))
				return i;
		return -1;
	}

	// 删除指定索引上的元素,返回删除元素
	public E remove(int index) {
		if (index < 0 || index >= size)
			throw new IllegalArgumentException("操作失败,需要满足 index < 0 || index >= size");
		E temp = data[index];
		for (int i = index + 1; i < size; i++)
			data[i - 1] = data[i];
		size--;
		data[size] = null;// loitering objects
		if (size == data.length >> 2 && data.length >> 1 != 0)// 防止复杂度震荡,当元素个数为容量的1/4时才执行缩容
			resize(data.length >> 1);// 实现对半缩容
		return temp;
	}

	// 删除数组第一个元素,返回删除元素
	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);
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append(String.format("元素个数:%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();
	}

	// 定义数组扩容方法
	private void resize(int newCapacity) {
		E[] newData = (E[]) new Object[newCapacity];
		for (int i = 0; i < size; i++)
			newData[i] = data[i];
		data = newData;// 将新数组的地址指向旧数组
	}
}

简单时间复杂度分析:

①添加操作(删除操作等同)

addLast(e)           可能触发resize扩容操作,最坏时间复杂的为O(n)

addFirst(e)           O(n)

add(index,e)      O(n/2)=O(n)

②修改操作

set(index,e)       O(1)

③查找操作

get(index)             O(1)

contains(e)           O(n)

find(e)                   O(n)

总结:查询快速,添加删除贼慢


均摊时间复杂度分析:

我们知道在调用addLast方法时,有可能会触发resize扩容,因此最坏情况的时间复杂度是O(n),但是这样其实是不合理的,因为每次addLast操作不一定都触发resize

假设capacity=n,每次都使用addLast作为添加操作,当n+1次addLast操作会触发resize扩容,将前面n个元素复制到新数组中,因此总共执行了2n+1次操作,平均每次addLast约等于进行了两次基本操作;

这样均摊计算,addLast操作的时间复杂度是O(1),在这个例子里均摊计算比计算最坏情况更有意义;

同理,removeLast操作的均摊复杂度也是O(1)。


复杂度震荡:

当数组已满时执行addLast时扩容,又马上执行removeLast导致缩容,会造成复杂度震荡,这样来回操作,每一个操作的时间复杂度为O(n)。

解决方法:

只需当size=capacity/4时,才将capacity减半实现缩容。

 

 

你可能感兴趣的:(java笔记,数据结构,顺序表(动态数组)实现,均摊时间复杂度,复杂度震荡)