为了账号安全,请及时绑定邮箱和手机立即绑定

2-玩转数据结构-不要小瞧数组

标签:
数据结构

我们要学习的第一个数据结构就是数组,数组中很多值得挖掘。

数组基础

把数据码成一排进行存放

5c1346910001fe4d08930301.jpg

数组中索引从0开始,Java语法中要求数组存放同一类型的元素,可以通过中括号下标的方式取到元素。

5c134691000149ed03010097.jpg

这样可以看到Main中有的方法。

5c134691000119cb02990077.jpg

package cn.mtianyan;public class Main {    public static void main(String[] args) {        // 必须传入长度
        int[] arr = new int[10];        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }

        System.out.println("=========");        int[] scores = new int[]{100,99,96};        for (int i = 0; i < scores.length; i++) {
            System.out.println(scores[i]);
        }

        System.out.println("=========");        for (int score : scores) {
            System.out.println(score);
        }

        System.out.println("=========");
        scores[0] = 92;        for (int score : scores) {
            System.out.println(score);
        }
    }
}

运行结果:

5c1346910001a32d03610276.jpg

数组可以这样遍历,是因为它实现了可遍历接口。

Java为我们提供的数组,其中非常重要的一部分就是它的索引。索引可以有语义,也可以没有语义,2可以代表学号,但也可以将其视为没有语义。

数组最大的优点: 快速查询,scores[2]直接插到2号索引的。因此数组最好应用于"索引有语义"的情况,查学号为2的学生成绩。

但并非所有有语意的索弓都适用于数组,如身份证号: 1101031985121 66666;依次为索引,这得开辟很大的内存空间,空间浪费。

数组也可以处理“索引没有语意”的情况。我们在这一章,主要处理“索引没有语意”的情况数组的使用。

5c1346920001231a06960164.jpg

索引没有语意,如何表示没有元素? capacity 和 size的区别: 数组size是3,capacity是8。

如何添加元素(超过size如何处理)?如何删除元素(挪位)? Java中的数组并没有这些方法,我们需要基于java的数组,二 次封装属于我们自己的数组类。

我们自己做的是动态数组(内部依然使用java数组实现),Java的是静态数组。

5c1346920001393206530455.jpg

增删改查,有的数据结构不一定四个齐全。capacity是数组最多可以装多少元素,和实际能装多少元素是没有关系的。

5c134692000117a208400188.jpg

package cn.mtianyan;public class Array {    private int[] data;    private int size;    /**
     * 带容量参数构造函数
     *
     * @param capacity 数组容量
     */
    public Array(int capacity) {
        data = new int[capacity];
        size = 0;
    }    /**
     * 默认构造函数
     */
    public Array() {        this(10);
    }    /**
     * 静态数组入参构造函数
     *
     * @param data 传入静态数组
     */
    public Array(int[] data) {        this.data = data;
    }    /**
     * 获取数组元素个数
     *
     * @return size 数组元素个数
     */
    public int getSize() {        return size;
    }    /**
     * 获取数组的容量
     *
     * @return capacity 获取容量
     */
    public int getCapacity(){        return data.length;
    }    /**
     * 判断数组是否为空
     *
     * @return 是否为空
     */
    public boolean isEmpty(){        return size == 0;
    }
}

向数组中添加元素

向数组末尾添加元素

5c1346920001bda308350190.jpg

size指向data中第一个为空位置。因此添加元素时只需要添加到arr[size],并size++即可。(注意添加元素判满)

    /**
     * 向所有元素末尾添加一个新元素。
     *
     * @param e 添加的元素
     */
    public void addLast(int e){//        if (isFull()){//            throw new IllegalArgumentException("AddLast failed. Array is Full");//        }else {//            data[size] =e; // data[size++] =e;//            size++;//        }
        add(size,e);
    }    /**
     * 向索引0号位置添加元素
     *
     * @param e 添加的元素
     */
    public void addFirst(int e){
        add(0,e);
    }    /**
     * 在index位置插入一个新元素e
     *
     * @param index 插入位置索引
     * @param e 插入元素
     */
    public void add(int index,int e){        if (isFull())            throw new IllegalArgumentException("AddLast failed. Array is Full");        // 大于size就不是紧密排列了
        if (index<0 || index>size){            throw new IllegalArgumentException("AddLast failed. Required index<0 or index>size ");
        }        else {            // 从哪开始挪呢: 从size-1这个元素(size本身是指向空的),挪到哪个呢,index位置的这个元素也是要挪的。
            for (int i=size-1; i>=index; i--){
                data[i+1] = data[i]; // 后一个等于前一个,从数组最后一个元素开始。
                // 极端值验证: size 1 index 0;(i=0;i>=0;i--)会执行一次data[1]=data[0].正确。
            }
            data[index] = e;
            size++;
        }
    }

5c1346920001a90a08120361.jpg

77插入,后面的元素都要向后挪动。

5c1346920001b21208730265.jpg

这里注意必须从100这里,向后挪,否则会造成值覆盖。

在数组中查询元素和修改元素

    /**
     * 打印数组信息及遍历元素。
     *
     * @return 数组信息和元素遍历结果
     */
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d, capacity = %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();
    }
package cn.mtianyan;public class ArrayTest {    public static void main(String[] args) {
        Array array = new Array(20);        for (int i = 0; i < 10; i++) {
            array.addLast(i);
        }
        System.out.println(array);
    }
}

运行结果:

5c1346930001d5ad03490091.jpg

        array.add(1,100);
        System.out.println(array);

运行结果:

5c1346930001d31c04110124.jpg

        array.addFirst(-1);
        System.out.println(array);

运行结果:

5c1346930001ea1504110113.jpg

取出某一个元素。

    /**
     * 传入索引,获取该位置元素
     *
     * @param index 要获取的元素索引
     * @return 返回该位置数组元素
     */
    public int get(int index){        if (index<0 || index>=size){            throw new IllegalArgumentException("Get failed. Required index<0 or index>=size ");
        }else {            return data[index];
        }
    }

通过get方法用户无法使用到数组后半截没有使用到的空间,通过封装保证。

System.out.println(array.get(array.getSize()-1));

运行结果:

5c13469300016d0004020103.jpg

    /**
     * 传入索引和元素值,将该位置元素设置为传入值
     * @param index 索引
     * @param e 传入值
     */
    public void set(int index,int e){        if (index<0 || index>=size){            throw new IllegalArgumentException("Set failed. Required index<0 or index>=size ");
        }else {
            data[index] = e;
        }
    }
        array.set(0,-99);
        System.out.println(array);

运行结果:

5c13469400012ef304340145.jpg

数组中的包含,搜索和删除元素

5c1346940001a1a808220353.jpg

如果要删除索引为1的元素,从88开始,后面都要往前挪,挪size-index次。只需要size--,对于最后一个位置的元素不用做其他操作,因为用户也访问不到。

/**
     * 查找数组中是否有元素e
     *
     * @param e
     * @return 包含 true; 不包含 false
     */
    public boolean contains(int e){        for (int i = 0; i < size; i++) {            if (data[i] == e){                return true;
            }
        }        return false;
    }    /**
     * 查找数组中元素,返回其索引(第一个遇到)
     *
     * @param e 元素
     * @return 不存在 -1; 存在 i
     */
    public int find(int e){        for (int i = 0; i < size; i++) {            if (data[i] == e){                return i;
            }
        }        return -1;
    }    public int[] findAll(int e){        int[] tempArray=new int[size];        int index = 0;        for (int i = 0; i < size; i++) {            if (data[i] == e){
                tempArray[index] = i;
                index++;
            }
        }        int[] indexArray=new int[index];        for (int i = 0; i < index; i++) {
            indexArray[i] = tempArray[i];
        }        return indexArray;
    }

注意findAll中使用tempArray临时对象的作用: 同时将int数组,与它的size一起传递了出来。

        System.out.println("===============加入重复元素后数组如下===================");
        array.add(3,3);
        array.add(array.getSize()-1,9);
        System.out.println(array);
        System.out.println("================包含 寻找测试===========================");
        System.out.println(array.contains(-99));
        System.out.println(array.contains(-100));
        System.out.println("3的index: "+array.find(3));        int [] tmpArr = array.findAll(3);        for (int i : tmpArr) {
            System.out.print(i+" ");
        }
        System.out.println();

运行结果:

5c134694000174f805700156.jpg

 /**
     * 删除数组元素,返回删除的元素值
     *
     * @param index 索引
     * @return 该索引位置元素值
     */
    public int remove(int index){        // 判空,空数组 removeFirst index=0,size=0,index>=size异常。空数组 removeLast index=-1 index<0异常;
        if (index<0 || index>=size){            throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size ");
        }else {            int ret = data[index];            for (int i=index+1;i < size;i++){                // 从哪个元素开始挪,从index位置的后一个元素开始,挪到哪个元素结束,挪到size-1(因此没等号)
                data[i-1] = data[i]; // 前一个等于后一个
            }
            size--;            return ret;
        }
    }    /**
     * 删除第一个(索引0)元素
     *
     * @return 删除的元素值
     */
    public int removeFirst(){        return  remove(0);
    }    /**
     * 删除最后一个(索引size-1)元素
     *
     * @return 删除的元素值
     */
    public int removeLast(){        return remove(size-1);
    }    /**
     * 删除数组中某一个元素值(删除数组中第一个找到的)
     *
     * @param e 元素值
     * @return 删除是否成功
     */
    public boolean removeElement(int e){        int index = find(e);        if (index != -1){
            remove(index);            return true;
        }        return false;
    }    /**
     * 删除数组中包含的所有该元素值
     * @param e 元素值
     * @return 删除成功与否
     */
    public boolean removeAllElement(int e){        int[] indexArray = findAll(e);        if (indexArray.length != 0){            for (int i = 0; i < indexArray.length; i++) {
                remove(indexArray[i]-i); // 此处注意-i的巧妙
            }            return true;
        }        return false;
    }

重难点代码在于remove(indexArray[i]-i);此处很可能会被忽略,造成异常。因为数组每删除一个,原本获取到的临近后一个元素的index值应该-1;临近后两个则应该-2;以此类推。

        System.out.println("================开始删除测试========================");
        System.out.println(array);
        array.remove(3); // 删除一个3
        System.out.println(array);
        array.removeElement(1); // 删除1
        System.out.println(array);
        System.out.println("=====删除index3 后 删除元素1如上====");

运行结果:

5c13469400013ec405310149.jpg

        array.removeAllElement(9);
        System.out.println(array);
        System.out.println("=====删除所有9=====");
        array.addFirst(-99);
        array.removeAllElement(-99);
        System.out.println(array);
        System.out.println("=====首位添加-99,后删除所有-99 结果如上=====");

5c13469400012fdf04650159.jpg

        array.add(4,99);
        array.add(5,99);
        array.addFirst(99);
        array.addLast(99);
        array.add(7,99);
        System.out.println(array);
        System.out.println("=====上面为删除99前,下面为删除99后=====");
        array.removeAllElement(99);
        System.out.println(array);

5c13469400012dd805370105.jpg

使用泛型

我们上面实现的数组目前只能存放int类型,但是我们需要的是可以承载多种类型,甚至自定义对象的容器。Java泛型可以解决这个问题。

使用泛型: 让我们的数据结构可以放置“任何”数据类型,不可以是基本数据类型,只能是类对象。

java中的八种基本类型: boolean , byte, char , short , int , long , float , double;

每个基本数据类型都有对应的包装类: Boolean , Byte , Char , Short, Int , Long , Float , Double。自动的拆包,解包。

public class Array<Element>public class Array<E>

为类名后面添加泛型类型标识,此处的类型标识可以自定义。

    private E[] data;

20180809045932_uFryvK_Screenshot.jpeg

这里无法直接通过E实例化。只能通过间接的Object再转换。

data = (E[]) new Object[capacity];
    /**
     * 静态数组入参构造函数
     *
     * @param data 传入静态数组
     */
    public Array(E[] data) {        this.data = data;
    }
public void add(int index,E e){public void addLast(E e){public void addFirst(E e){
 public boolean contains(int E){        for (int i = 0; i < size; i++) {            if (data[i].equals(E)){                return true;
            }
        }        return false;
    }

这里注意,两个对象之间的比较要使用equals方法,将所有的与成员数组类型相关的都改了。

    public E remove(int index){        // 判空,空数组 removeFirst index=0,size=0,index>=size异常。空数组 removeLast index=-1 index<0异常;
        if (index<0 || index>=size){            throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size ");
        }else {
            E ret = data[index];            for (int i=index+1;i < size;i++){                // 从哪个元素开始挪,从index位置的后一个元素开始,挪到哪个元素结束,挪到size-1(因此没等号)
                data[i-1] = data[i]; // 前一个等于后一个
            }
            size--;
            data[size] = null;            return ret;
        }
    }

data[size]还指着一个值不过用户访问不到而已,新的元素添加会自动覆盖。使用泛型后,data数组中存放的是类对象的引用, size-- 后data[size]依然指向一个引用,引用就涉及到空间释放的问题,Java有自动垃圾回收机制,但如果 data[size]仍存放引用,就不会被自动垃圾回收。data[size] = null;(不必须,可以被新对象覆盖)

如果不置为null,它会被称为loitering Objects,没有用但垃圾回收机制不回收。 loitering Objects != memory leak 为了程序优化,手动去除更好。

Main中改动

Array<Integer> array = new Array(20);
Array<Integer> array = new Array<>(20);

即使不改,也是可以正常运行的。但是我们最好加上更清楚的看出类型,jdk1.7之后不用再后面再重复一次。

package cn.mtianyan;public class Student {    private String name;    private int score;    public Student(String studentName, int studentScore){
        name = studentName;
        score = studentScore;
    }    @Override
    public String toString(){        return String.format("Student(name: %s, score: %d)", name, score);
    }    public static void main(String[] args) {

        Array<Student> arr = new Array();
        arr.addLast(new Student("mtianyan1", 100));
        arr.addLast(new Student("mtianyan2", 66));
        arr.addLast(new Student("mtianyan3", 88));
        System.out.println(arr);
    }
}

运行结果:

20180809052431_h9iZjO_Screenshot.jpeg

    @Override
    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof Student)) return false;
        Student student = (Student) o;        return score == student.score &&
                (name.equals(student.name));
    }    @Override
    public int hashCode() {        return Objects.hash(name,score);
    }



作者:天涯明月笙
链接:https://www.jianshu.com/p/291d5c9e2591


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消