Java数据结构和算法( 二 ) ## 数组

目录

  • 数组综述
  • Java中的数组
    • 创建数组
    • 访问数组
    • 初始化数组
    • 有序数组
    • 线性查找
    • 二分查找
    • 有序数组的优缺点
  • 大O表示法(order of)
  • 为什么不用数据解决一切

- 小结

数组综述

    数组是最广泛的数据存储结构,其中还有一种特殊数组(有序数组),下面讲解下插入、查询、删除相关的注意事项

  • 插入: 新数据总是插在数组第一个空位上,由于已有数据项个数已知,所以空位的具体位置很容易得知,然而查询和删除却没有那么快。当不允许插入重复值的模式下,还需要将算法计算是否存在数据项。
  • 查找: 若是无重复项模式,必须平均搜索一半的数据项来查找特定数据项。找数组头部的数据项快,找数组尾部的数据项慢。若重复模式,必须从头查找到尾。
  • 删除: 只有找到特定数据项后才能进行删除操作(要么该数据项被替换,要么变为空)。删除算法中假设不允许有洞(一个或多个空单元后面存在非空数据项),如果删除算法允许有洞,则其他算法变得很复杂,因为要判断非空降低效率。因此非空数据项必须连续,不能有洞。在无重复项模式下查找平均N/2个数据项+移动剩下的N/2个数据项来补洞。总共是N步。
  • 重复值问题:
    • 重复项模式下查找算法: 重复项使查找算法复杂,匹配上一个后,还得考虑是否继续寻找可能的匹配,直到最后一个数据项(所有蓝眼睛的人和第一个蓝眼睛的人的区别)。通常操作需要N步。
    • 重复项模式下插入算法: 和不重复项模式一样,只需要一步。
    • 重复项模式下删除算法:若是删除第一个特定的数据项,操作步骤平均是N/2次查找+N/2移动,若是删除所有特定的数据项,需要检查N个数据项+移动多余N/2个数据项,操作的平均时间取决于重复项的个数和分布。

表格区别重复与无重复模式的区别

操作不允许重复允许重复
查找 N/2次比较 N次比较
插入 无比较、一次移动 一次移动
删除 N/2次比较、N/2次移动 N次比较、多于N/2次移动

Java中的数组

创建数组

int[] intArray = new int[]{};
int[] intArray = new int[2];

访问数组

数组数据项通过下标访问,下标从0开始,止于长度减1。若下标不在这个范围访问数组,则抛出Array Index Out of Bounds的运行时错误。

初始化数组

int[] intArray = {1,2,3,4,5};
int[] intArray = new int[]{1,2,3,4,5};

代码示例

github 地址

public class HightArray {
    /** 声明一个数组 */ private long[] a;

    /** 声明变量记录数据项个数 */ private int nElemts;

    /** 构造方法用于初始化数组和数据项总数 */
    public HightArray(int max){ a = new long[max]; nElemts = 0; }

    public void insert(long value){ a[nElemts] = value; nElemts ++ ; }

    public boolean find(long searchKey){
        int j;

        for (j=0;j<nElemts;j++){ if(a[j]==searchKey){ break; } }

        return j == nElemts;
    }

    public boolean delete(long value){
        int j;

        for (j=0; j<nElemts; j++){ if(a[j]==value){ break; } }

        if(j == nElemts) { return false; }
        else {
            for (int k=j; k<nElemts; k++){ a[k] = a[k+1]; }
            nElemts--;

            return true;
        }

    }

    public void display(){                   System.out.println(Arrays.stream(a).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
    }
}

测试类>>>

public class HightArrayTest {

    @Test public void test(){
        int maxSize = 100;

        HightArray hightArray = new HightArray(maxSize){{
            insert(77); insert(99); insert(44); insert(55); insert(66);
            insert(22); insert(88); insert(11); insert(00); insert(33);
        }};

        hightArray.display();

        int searchKey = 35;
        System.out.println(hightArray.find(searchKey)?"Found ":"Can't find " + searchKey);

        hightArray.delete(00); hightArray.delete(55); hightArray.delete(99);

        hightArray.display();

    }
}
有序数组

    数组中的数据项按照关键字升序排列。当向此数组中插入数据项时,需要为插入操作找到正确位置,在稍小位置和稍大位置之间,然后将稍大位置至末尾的数据项往后移动一位腾出位置。
    这中顺序排列的好处就是可以通过二分查找显著提高查询速度,但是降低了插入的速度,毕竟要腾出坑位。

线性查找

    线性查找就是依次向后,寻找匹配。而在有序数组中当匹配到一个合适的数据项就退出查找。

二分查找

    二分查找就是一半一半的找,这种查询比线性查询快很多,尤其对大数组来说。

有序数组代码示例

github 地址

public class OrderArray {
    private long[] a;

    private int nElems;

    public OrderArray(int maxSize){ a = new long[maxSize]; nElems = 0; }

    public int size(){ return nElems; }

    public void insert(long value){
        int j ;
        for (j = 0; j < nElems; j++) { if(a[j] > value) { break; } }

        for(int k=nElems; k>j; k--){ a[k] = a[k-1]; }

        a[j] = value;

        nElems++;
    }

    public int find(long searchKey){
        int lowerBound = 0, upperBound = nElems -1, curIndex;

        while (true){
            if(lowerBound > upperBound){ return nElems; }

            curIndex = (lowerBound + upperBound)/2;
            if(a[curIndex] == searchKey) { return curIndex; }
            else if (a[curIndex] < searchKey){ lowerBound = curIndex + 1; }
            else { upperBound = curIndex - 1; }
        }
    }

    public boolean delete(long value){
        int j = find(value);
        if(j == nElems) { return false; }

        for (int k = j; k < nElems; k++){ a[k] = a[k+1]; }
        nElems--;
        return true;
    }

    public void display(){
        System.out.println(Arrays.stream(a).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
    }
}

测试类>>>

public class OrderArrayTest {
    @Test public void test(){
        int maxSize = 100;
        OrderArray orderArray = new OrderArray(maxSize){{
            insert(77); insert(99); insert(44); insert(55); insert(22);
            insert(88); insert(11); insert(00); insert(66); insert(33);
        }};

        int searchKey = 55;
        System.out.println(orderArray.find(searchKey)!=orderArray.size()?"Found":"Can't find" + searchKey);

        orderArray.display();

        orderArray.delete(00); orderArray.delete(55); orderArray.delete(99);

        orderArray.display();
    }
}
有序数组的优缺点
  • 优点: 二分查找比无序数据快。
  • 缺点: 插入需要查找,查找后需要将靠后的数据项整体往后移动。删除操作都需要补洞。

大O表示法(order of)

算法运行时间
线性查找(从头到尾) O(N)
二分查找(一半一半) O(LogN)
无序数组插入(仅一次) O(1)
有序数组插入(查询+移动) O(N)
无序数组删除(查询+补洞) O(N)
有序数组删除(查询+补洞) O(N)

为什么不用数据解决一切

    一个无序数组插入(O(1)时间),查询却花费(O(N)时间)。一个有序数组查询花费(O(LogN)时间),但是插入缺花费(O(N)时间)。而删除都需要花费(O(N)时间)。所以需要一种数据结构插入、查询、删除都很快,理论上(O(1)或者O(LogN)时间),而且数组一旦创建,其大小被固定,扩容不便,若空间大了又会浪费。

小结

  • 无序数组可以快速插入,但查询和删除比较慢。
  • 有序数组可以使用二分法查找。
  • 线性查找需要的时间和数据项个数成正比。
  • 二分法查找需要的时间和数据项个数的对数成正比。
  • O(1)算法最好、O(LogN)次之、O(N)一般、O(N*N)最差。
posted @ 2018-02-06 16:42  vision_0802  阅读(134)  评论(0编辑  收藏  举报