数据结构【一】--数组

 

一.基本实现

public class ArrayDemo {--> Array<E>
    private int size;//指向当前元素
    private int[] dataArr; ->E[]dataArr;//表示元素个数-
    
    public ArrayDemo(int capacity){
        dataArr = new int[capacity];-->(E[])new objcet[capacity]
        size = 0;
    }
    //无参构造
    public ArrayDemo(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数据中的容量
    public int getCapacity(){
        return dataArr.length;
    }
    //判断数据时候为空
    public boolean isEmpty(){
        return size==0;
    }
    /**
     * 添加元素
     */
    //向所有数组后添加一个新元素
    public void addLast(int element){--> E elemnet
        /*if(size == getCapacity()){
            throw new IllegalArgumentException("The array is full");
        }*/
        //dataArr[size++] = element;
        add(size,element);
    }
    public void addFirst(int element){--> E elemnet
        /*if(size == getCapacity()){
            throw new IllegalArgumentException("The array is full");
        }*/
        add(0,element);
    }
    public void resize(int newSize){
        E[] newData = (E[])new Object[newSize];
        for(int i=0;i<size;i++){
            newData[i] = dataArr[i];
        }
        dataArr = newData;    
    }
    //向指定位置添加元素
    public void add(int index,int element){--> E elemnet
        if(size == getCapacity()){
            //throw new IllegalArgumentException("The array is full");
            resize(dataArr.length*2);//java扩容的时候选的是1.5
        }
        if(index >size || index< 0){
            throw new IllegalArgumentException();
        }

   //不能使用for(int i=index;i<size;i++),这样会使得后面的数字和element完全一样
        for(int i=size-1;i>=index;i--){
            dataArr[i+1] = dataArr[i];
        }
     
        size++;
        dataArr[index] = element;
    }
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Arrays: size=%d, capacit= %d\n", size, getCapacity()));
        res.append("[");
        for(int i=0;i< size; i++){
            res.append(dataArr[i]);
            if(i!=size-1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
    //获取Index索引位置的元素
    public int get(int index){--> E get(int index)
        if(index <0 || index >= size){
            throw new IllegalArgumentException("Get failed, the index is illegal");
        }
        return dataArr[index];
    }
    //修改Index索引位置的元素
    void set(int index, int element){--> E element
        if(index <0 || index >= size){
            throw new IllegalArgumentException("Get failed, the index is illegal");
        }
        dataArr[index] = element;
    }    
    /**
     * 包含/搜索/删除
     * */
    //
    //包含
    public boolean contains(int element){-->E element
        for(Integer x : dataArr){-->E x
            if(element == x){-->element.equal(x)
                return true;
            }
        }
        return false;
    }
    //查看某元素的索引
    public int find(int element){-->E element
        for(int i=0;i<size;i++){
            if(element == dataArr[i]){-->element.equal(dataArr[i])
                return i;
            }
        }
        return -1;
    }
    public String findAll(int element){-->E element
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<size;i++){
            if(element == dataArr[i]){-->element.equal(dataArr[i])
                sb.append(i);
            }
        }
        if(sb.toString().isEmpty()){
            return "-1";
        }else{
            return sb.toString();
        }
    }
    //删除某个位置的元素
    public int remove(int index){-->E remove
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Remove failed, the index is illegal");
        }
        int data = dataArr[index];

 //不能使用for(int i=size-1;i>index;i--),数据会被污染,就都会变成arr[index-1]
        for(int i=index+1;i<size;i++){
            dataArr[i-1]= dataArr[i];
        }
        size--;
        if(size == dataArr.length/2){
            resize(dataArr.length/2);
        }
        return data;    
    }
    public int removeFirst() {-->E removeFirst
        return remove(0);    
    }
    public int removeLast(){-->E removeLast
        return remove(size-1);
    }
    public Boolean removeElement(int element){-->E element
        int index = find(element);
        if(index != -1){
            remove(index);
            return true;
        }
        return false;
    }
    public void removeAllElement(int element){-->E element
        for(int i=0;i<size;i++){
            if(dataArr[i]==element){-->element.equal(dataArr[i])
                remove(i);
            }
        }    
    }

}

二.测试类


用自己new的类去测试
public class Student {
    private String name;
    private int score;
    public Student(String name,int score){
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString(){
        return String.format("Stdent(name: %s,score:%d)",name,score);
    }
    public static void main(String[] args) {
        ArrayDemo<Student> stuArr = new ArrayDemo<Student>();
        stuArr.addLast(new Student("廖明",39));
        stuArr.addLast(new Student("小红",78));
        stuArr.addLast(new Student("橙子",99));
        System.err.println(stuArr.toString());

    }
        //打印结果
        Arrays: size=3, capacit= 10
        [Stdent(name: 廖明,score:39), Stdent(name: 小红,score:78), Stdent(name: 橙子,score:99)]

}

三.将实现类中的特定数据结构改为泛型


1. ArrayDemo 改为--> ArrayDemo<E>
    这里的E表示的是“类型”
2. 将所有的要使用的数据类型都改为E
    2.1 private int[] dataArr -> E[]dataArr
    2.2 dataArr = new int[capacity];-->E[capacity]
        但是java本身不支持直接new一个泛型数组,需要做如下变动:
            dataArr = (E[])new Object[capacity];
    2.3 public void add(int index,int element){--> E elemnet
    2.4 public int get(int index){--> E get(int index)
3. 对于remove函数来说,根据我们的Remove逻辑来说,dataArr[size]其实还指着一个值,只不过用户访问不到。但是当使用泛型时,数据中存放的都是对象的引用,所以dataArr[size]还指着一个对象的引用,对于引用就有一个对象释放的问题,在java中有垃圾回收技术。但是这里还存着引用,就不会被java的垃圾回收器回收。
        public int remove(int index){
            size--;
            //写上这个,原来dataArr[size]所指向的对象就已经不再程序中和任何其他对象相关联了。java的回收机制就会回收它。
            //但是可以不写,因为添加新的元素之后,dataArr[size]指向了新的对象,原来的对象就每人指向它了,他就会被垃圾回收器回收。
            对于这种对象叫做loitering objects != memory leak
            dataArr[size] = null;//
            return data;    
        }
4. 使用
    ArrayDemo<Integer> ad = new ArrayDemo<Integer>(20);

四。使用动态数组


1. 思路
    1.1 当数组满了(size=capacity)之后,开辟一个新的数组newdatta.将原来的data数组中的值依次放到新的数组newData中。将新的数组中的capacity的newsize指定为data的size*2.
    1.2 将原来的data数组指向新的有size*2的数组。这时候data和newdata指向同一空间。我们将这个过程写在一个方法中,当这个方法执行完毕,newdata就会失效,但是data是整个类的成员变量,所以data有效,并且指向新的数组。
    1.3 原来的数组由于没有对象指向它,java的垃圾回收器会将他回收。
2. 为什么不让capacity= 2*capacity; 
    因为 java中数组的长度不可以改变。 java中的数组的定义就是相同类型,固定长度的一组数据,一旦被初始化,长度不可更改。
3. 代码
    public void resize(int newSize){
        E[] newData = (E[])new Object[newSize];
        for(int i=0;i<size;i++){
            newData[i] = dataArr[i];
        }
        dataArr = newData;    
    }
    //扩容
    public void add(int index,E element){
        if(size == getCapacity()){
            resize(dataArr.length*2);//java扩容的时候选的是1.5
        }
    }
    //缩容
    public E remove(int index){size--;
        dataArr[index] = null;
        if(size == dataArr.length/2){
            //之所以这里的代码正确,是因为每添加一个index就会+1;例如:数组中只有一个元素的时候,index为1,如果数组capacity是6,添加完6个元素后,size就变成了6.
            //当删除2个元素时候size变成了4,删除3个元素的时候,size=3.这时候,刚刚占用了一半的位置。
            resize(dataArr.length/2);
        }
        return data;    
    }

五。时间复杂度分析


1. 简单的时间复杂度
    (1) O(1),  O(n),  O(nlogn),  O(n^2)
    (2) 大O描述的是算法的时间运行和输入数据之间的关系
        例如:定义n是nums中的元素个数,运行时间的多少和元素个数成线性关系.n越大,也就是nums中的数多,运行时间就越长。这里是O(n)
            public static int sum(int[] nums){
                int sum=0;
                for(int num:nums) sum+=num;
                return sum;
            }
            但是实际时间T=c1*n + c2。但是不同语言的比如说将nums整个数从数组中取出来这件事,不同语言基于不同的实现,时间是不同的。就算转化成机器码,它对应的机器码的指令数也可能是不一样的。就算指令是相同的,根据cpu的不同,执行的操作也是不同的。
            所以即使我们能大概说出c1是几条指令,但是很难准确说出它的值。所以忽略常数,所以就是O(n)

                1. c1*n :我们需要把num从nums的数组中取出来,然后还要将sum取出来,最后我们还要将num和sum加在一起,
                2. c2 : 开始前要开辟一个sum的空间,将0初始化赋给sum。在算法运行结束之后还要将sum来return回去。

    (3) 由于O()是忽略常数的,所以并不代表对于任意输入来说,O(n)已经比O(n^2)越好。
    (4) O() 其实就是[渐进时间复杂度],是对于n趋紧于无穷的情况。
    (5) O(1) [就意味着这个操作所消耗的时间和数据规模无关。]
2. 数组中方法的时间复杂度
    2.1 以下都可以归结为O(n)级别的。因为我们考虑的是最坏情况, 而且考虑到[resize操作】所以这个是 O(n)的。
        addLast(e)      O(1)【数组的规模就是数组中的元素个数,也就是不管数组中有多少元素,addLast都能在常数范围里完成】
        addFirst(e)     O(n)【需要把数组所有元素都向后挪一个单位,所以复杂度是O(n)】
        add(index, e)    平均是O(n/2) = O(n)
            严格计算:由于index可以取从0到size这么多种可能,但是取到每一个值的概率是相等的。这种情况下我们就可以求出它的时间期望是多少。
    2.2 以下都可以归结为O(n)级别的。因为我们考虑的是最坏情况, 而且考虑到[resize操作】所以这个是 O(n)的。
        removeLast(e)      O(1)
        removeFirst(e)     O(n)
        remove(index, e)    O(n/2) = O(n)
    2.3 搜索
        set(index,e)  O(1)
    2.4 查询
        get(index)   O(1)
        contians(e)   O(n)
        find(e)       O(n)

六。 均摊时间复杂度和防止复杂度的震荡


1. resize的复杂度分析--【均摊复杂度-amortized time complexity】
    1.1 假设当前capacity=8,并且每一次添加操作都使用addLast
        (1) 前8次元素的添加都是O(1)的时间复杂度
        (2) 当到第九个元素的时候,resize操作,添加8个数到新数组,再添加这个数,这个花费9次操作
        (3) 所以9次addLast操作,总共进行了17次基本操作.平均,每次addLast操作,进行2次基本操作.
        (4) 假设capacity=n,n+1次addLast,触发resize,总共进行2n+1次基本操作。平均,每次addLast操作,进行2次基本操作.
        (5) 按照这样平摊来算的话,addLast的时间复杂度是O(1)级别的。
        (6) 在这个例子中,这样均摊计算,比计算最坏情况有意义。
2. 复杂度震荡
    2.1 同时进行addLast和removeLast操作。
        (1) 如果capacity是n。并且已经装满了
        (2) 然后我添加1个元素,addLast, 触发resize,容量变成2n, 复杂度为O(n)
        (3) 此时再删除1个元素, removeLast, 触发resize,容量变成n, 复杂度为O(n) 
        (4) 重复(2)和(3) 
    2.2 出现问题的原因是: removeLast和resize过于着急(Eager)
    2.3 解决方案: Lazy
        当数组删除到了数组的1/4的时候,我们缩容1/2.所以还剩1/4的空间,这时候添加元素就不不会立即触发resize.
    2.4 代码
        (1)    if(size == dataArr.length/2){}
                变为
            if(size == dataArr.length/4){}
        (2) 但是此时有一个小bug,因为随着缩容,dataArr.length可能为1.也就是dataArr.length/2会为0.
        (3) 但是又需要resize操作。
                resize(dataArr.length/2);        
            这里的数组的长度不能为0.所以变成如下:
            if(size == dataArr.length/4 && dataArr.length/2!=0){}
 

以下所有内容都是通过"慕课网"听"liuyubobobo"的《玩转数据结构》课程后总结

猜你喜欢

转载自blog.csdn.net/sunshine77_/article/details/87830862