自定义数组及简单时间复杂度分析

前言:

作为java的一种容器,数组的优缺点同样明显

优点:使用简单 ,查询效率高,内存为连续的区域 

缺点:大小固定,不适合动态存储,不方便动态添加

一、自定义实现数组

1、Java中定义数组的三种形式

    // 第一种:数组格式 类型[] 数组名 = new 类型[数组长度]
        int[] arr = new int[10];

    // 第二种:定义数组,直接赋值
        int[] arr = new int[]{11, 22, 33, 44};

    // 第三种:定义数组,直接赋值
        int[] arr = {11, 22, 33, 44};

提示:第三种定义方式最常用

2、自定义数组

为了加深对数组的理解,实现自定义数组,定义数组的动态扩减容,且自动维护数组的大小size,从而节省了内存空间资源。

 自定义数组的完整代码

package com.theone.arrays;

/**
 * @Description TODO 自定义数组
 * @Author Pureman
 * @Date 2018/9/1 13:08
 **/
public class MyArray<E> {

    // 定义数组类型
    private E[] data;
    // 特别提示一点,这里的数组容量默认是10,因此使用data.length不是数组真实的长度
    // 定义数组大小,实现动态管理数组长度
    private int size;

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

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

    // 获取数组大小
    public int getSize() {
        return this.size;
    }

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

    // 判断数组是否为空
    public Boolean isEmpty() {
        return this.size == 0;
    }

    // 定义方法,向数组的最后添加元素
    public void addLast(E e) {
        add(size, e);
    }

    // 定义方法,向数组的0索引位置添加元素
    public void addFirst(E e) {
        add(0, e);
    }

    // 定义方法向数组中的任意位置添加元素
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index is not expected");
        }
        if (size == data.length) {
            this.resize(2 * data.length);
        }
        // 遍历数据,将原来数据从index向后移动一位
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        // 覆盖原下标的元素
        data[index] = e;
        // 数组大小加一
        size++;
    }

    // 获取数组中指定下标的元素
    public E get(int index) {
        if (index == size) {
            throw new IllegalArgumentException("Get failed ,because required index is not legal");
        }
        return data[index];
    }

    // 更新数组中指定角标的元素
    public void setIndex(int index, E e) {
        if (index == size) {
            throw new IllegalArgumentException("update failed ,because required index is not legal");
        }
        data[index] = e;
    }

    // 判断数组中是否包含指定元素
    public Boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            // ==比较的是对象的引用,equeals比较内容
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    // 获取元素的索引
    public int search(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

    // 指定索引,删除数组中的元素,并返回被删除的元素值
    public E remove(int index) {
        if (0 > index || size <= index) {
            throw new IllegalArgumentException("Remove failed.Index is illegal");
        }
        // 获取待被待删除元素
        E ret = data[index];
        // 不需要关注data[size-1]的元素是否还有值,数组封装对外部不暴露,因此调用时并不知道该数组下标为size-1的位置还有元素
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        // loitering object(游荡的对象) != memory leak(内存泄漏)
        data[size] = null;
        // 判断数组长度是否是容量的1/4,如果是,实现动态减容
        if (size == data.length / 4 && data.length / 2 != 0) {
            this.resize(data.length / 2);
        }
        // 返回删除的元素
        return ret;
    }

    // 移除数组中的第一个元素
    public E removeFirst() {
        return this.remove(0);
    }

    //移除数组中的最后一个元素
    public E removeLast() {
        return this.remove(this.getSize() - 1);
    }

    // 移除数组中指定元素
    public void removeElement(E e) {
        int index = this.search(e);
        if (1 != index) {
            this.remove(index);
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("Array:size=%d,capacity=%d\n", size, data.length));
        sb.append("[");
        for (int i = 0; i < size; i++) {
            sb.append(data[i]);
            if (i != size - 1) {
                sb.append(" ,");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    // 动态拓展数组容量
    private void resize(int capacity) {
        E[] newArr = (E[]) new Object[capacity];
        for (int i = 0; i < size; i++) {
            newArr[i] = data[i];
        }
        data = newArr;
    }
}

二、简单介绍时间复杂度

简单时间复杂度分为
O(1)、O(n)、O(lgn)、O(nlogn)、O(n^2)
注意:大O描述的是算法的运行时间和输入数据之间的关系

public static int sum(int[] nums){
		int sum = 0 ;
		for(int num : nums){
		sum += num ;
		}
		return sum ;
    }

sum(int[] nums)方法的时间复杂度为O(n),实际复杂度公式为:T = c1*n + c2

参数解释:

  • n: nums中元素的个数
  • c2:指sum初始化的时间,及返回值sum返回的时间
  • c1:for循环内遍历num,与sum累加的时间

为什么要使用大O,叫做O(n)?    

在时间复杂度分析过程中,都会假设n趋势于无穷,此时c2所消耗的时间远远小于c1*n的时间,因此分析时忽略常数,此时算法和n呈线性关系

提示:计算时间复杂度都是趋向于最坏情况

另外两个简单的时间复杂度概念:均摊复杂度分析、防止复杂度震荡

均摊复杂度分析(amortized time complexity):resize()

当size == data.length时,自动调用方法resize(2 * data.length)实现数组的动态扩容,扩容后数组的容量是原来的二倍,因此不是每次添加元素都会调用resize()方法进行扩容,所以当自定义数组中的方法addLast()执行所消耗的时间,因由数组arr中的每个元素均应分摊。例如:数组arr的大小为9时,其每个元素所执行的基本操作为15/9次操作

   public void test02() {
        // 定义数组容量为6
        MyArray<Integer> arr = new MyArray<Integer>(6);
        // 向数组添加8个元素
        for (int i = 0; i < 8; i++) {
            arr.addLast(i);
        }
        System.out.println("arr = " + arr);
        // 向数组的最后碎银添加元素100,此时数组大小为9
        arr.addFirst(100);
        System.out.println("arr = " + arr);
    }

防止复杂度震荡:在resize()扩容后,立刻删除数组中的元素,触发数组自动减容

下面代码演示了复杂度震荡,数组动态扩容后,立即删除元素,数组动态减容,这样会造成时间复杂度增加

   public void test03() {
        // 定义数组容量为6
        MyArray<Integer> arr = new MyArray<Integer>(5);
        for (int i = 0; i < 5; i++) {
            arr.addLast(i);
        }
        // 添加元素,此时数组需要动态扩容
        arr.addLast(100);
        // 动态扩容后,立即删除元素,数组需要动态减容
        arr.removeLast();
    }

为了防止复杂度震荡,避免删除元素后,造成复杂度震荡,采用延迟动态减容,代码如下

   public E remove(int index) {
        if (0 > index || size <= index) {
            throw new IllegalArgumentException("Remove failed.Index is illegal");
        }
        // 不需要关注data[size-1]的元素是否还有值,数组封装对外部不暴露,因此调用时并不知道该数组下标为size-1的位置还有元素
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        // loitering object(游荡的对象) != memory leak(内存泄漏)
        data[size] = null;
        // 判断数组长度是否是容量的1/4,如果是,实现动态减容
        if (size == data.length / 4 && data.length / 2 != 0) {
            this.resize(data.length / 2);
        }
        return data[index];
    }

猜你喜欢

转载自blog.csdn.net/jinYwuM/article/details/82393098
今日推荐