数据结构与算法之数组

房东的猫 提交于 2019-12-08 15:17:56

本章重点:

1.Java中数组的基础知识;

创建数组:

在许多的编程语言中(甚至有些面向对象语言,如c++),数组也是基本类型,但是在Java中把他们当做对象来对待,因此在创建数组时必须使用new操作符:

int[] intArray;

intArray = new int[100];   []操作符对于编译器来说是一个标志,它说明正在命名的是数组对象而不是普通的变量。

或:

int[] intArray = new int[100];

int intArray[] = new int[100];

由于数组是一个对象,所以它的名字intArray是数组的一个引用,而不是数组本身。数组存储在内存中的其他地址中,而intArray仅仅保存着这个地址。正如大多数编程语言一样,数组一旦被创建,数组大小便不可改变。

访问数组数据项:

数组数据项通过使用方括号中的下标数来访问:

temp = intArray[3];

注意:无论是在c、c++,还是java中,第一个数据项的下标都是0,所以一个有10个数据项的数组下标是从0到9.强撸会报Array Index Out Of Bounds数组越界错误。

初始化:

当创建整型数组之后,如果不另行指定,那么整型数组会自动初始化为空。与C++不同的是,即使通过方法来定义数组也是这样的。创建一个对象数组如下:

autoData[] carArray = new autoData[4000];

除非将特定的值赋给数组的数据项,否则他们一直都是特殊的null对象。如果尝试访问一个含有null的数组数据项,程序会出现Null Pointer Assignment 的运行时错误。这主要是为了保证读取某个数据项之前要先对其赋值。

使用下面语法可以对一个基本类型的数组初始化,它同时取代了引用声明和使用new来创建数组。在大括号中的数据被称为初始化列表。数组大小由列表中数据项的个数决定,赋非空值:

int[] intArray = {0,1,2,23,36,44,33,42,54};

数组例子:

class ArrayApp{
    public static void main(String[] args){
        long[] arr;
        arr = new long[100];
        int nElems =0;
        int j;
        long searchkey;
//------------------------insert three items
        arr[0] = 77;
        arr[1] = 78;
        arr[2] = 79;
        nElems = 3;
//------------------------foreach
        for(j=0;j<nElems;j++){
            System.out.println(arr[j]+" ");
        System.out.println("*");
        }
//------------------------searchkey
        searchKey = 77;
        for(j=0;j<nElems;j++){
            if(arr[j]==searchKey)    
                break;
        if(j==nElems){
            System.out.println("can not find"+searchKey);
        else
            System.out.println("Found"+searchKey);
//------------------------delete
        searchKey = 77;
        for(j=0;j<nElems;j++)
        if(arr[j]==searchKey)
            break;
        for(int k=j;k<nElems;k++)
            arr[k] = arr[k+1];
        nElems--;
        }
        }
    }
}

 

2.将程序划分成类;

将程序划分成类之后,可以得到许多好处。数据存储结构本身就是类,程序中使用这个结构的部分也是类。通过将程序划分成两个类,可以使程序的功能清晰,使之跟更加容易设计和理解

3.类接口;

类接口的好处是:类的用户通过类接口的方式与类相连。由于类的字段经常都是私有的,所以当我们讨论接口时,经常指类的方法,它们是用来干什么的和它们的参数是什么。通过调用这些方法,类用户与类对象进行交互。面向对象编程最重要的优点之一是类的接口可以设计得尽可能方便且高效。

下面一个名为HighArray的程序给出了数据结构类的一个改进的接口。类用户(HighArrayApp类)使用这个接口就不再考虑下标了。它取消了setElem()和getElem()方法,取而代之的是insert(),find(),delete()。因为由类来负责处理下标问题,所以这些方法不再需要将下标当做参数。类(HighArrayApp)用户可以集中精力于做什么而不是怎么做:什么要被插入、删除和访问,而不是如何执行这些操作。

class HighArray{
    private long[] a;
    private int nElems;
    //------------------------------------------------------
    public HighArray(int max){    //constructor
        a = new long[max];        //create the array
        nElems = 0;               //no items yet
    }
    //------------------------------------------------------
    public boolean find(long searchKey){
        int j;  
        for(j=0;j<nElems;j++){
            if(a[j]==searchKey)
            break;
        }
        if(j==nElems){
            return false;        //not found it
        }else{
            return true;        //found it
        }
    }
    //------------------------------------------------------
    public void insert(long value)        //insert elems
        {
        a[nElems] = value;
        nElems++;
    }
    //------------------------------------------------------
    public boolean delete(long value){
        int j;
        for(j=0;j<nElems;j++){
           if(value==a[j])
                break;
        }
        if(j==nElems){
           return false;        //cant not found it
        }else{
            for(int k=j;k<nElems;k++){
                a[k]=a[k+1]
            }
            nElems--;            //delete ok
            return true;
        }
    }
    //------------------------------------------------------
    public void display(){
        for(int j=0;j<nElems;j++){
            System.out.println(a[j]+" ");
        }
        System.out.println("*");
    }
}

class HighArrayApp{
    public static void main(String[] args){
        int maxSize = 100;
        HighArray arr;
        arr = new HighArray(maxSize);

        arr.insert(77);
        arr.insert(78);
        arr.insert(79);
        arr.insert(80);
        arr.insert(81);

        arr.display();

        int searchKey = 35;
        if(arr.find(searchKey))
            System.out.println("found"+searchKey);
        else{
            System.out.println("not found"+searchKey);
        }

        arr.delete(80);

        arr.display
    }
}

 

4.有序数组的java代码;

下面讨论一下有序数组的java代码,它使用OrdArray类来封装数组和它的算法。类的核心是find()方法(前提数组是有序的),通过它可以用二分查找来定位一个特定的数据项。在给出整个程序之前先详细研究一下这个方法。

//二分查找中的find()方法
public int find(long searchKey){
    int lowerBound = 0;
    int upperBound = nElems-1;
    int curIn;
    while(true){
        curIn = (lowerBound+upperBound)/2
        if(a[curIn]==searchKey)
            return curIn;        //found it
        else if(lowerBound>upperBound)
            return nElems;       //can not find it
        else{
            if(a[curIn]<searchKey)
                lowerBound = curIn+1;    //it is in upper half
            else
                upperBound = curIn-1;    //it is in lower half
        }
    }
}

有序数组的优点:

使用有序数组会给我们带来什么好处?最主要的好处是查找的速度比无序数组快多了。不好的方面是在插入操作中由于所有靠后的数据都需移动腾开空间,所以速度较慢。有序数组和无序数组中的删除操作都很慢,这是因为数据项必须向前移动来填补已删除数据项的洞。

有序数组在查找频繁的情况下十分有用,但若是插入与删除较为频繁时,则无法高效工作。例如,有序数组适合于公司雇员的数据库。雇员和解雇雇员同读取一个已存在雇员的有关信息或更改薪水、地址等信息相比,前两者是不经常发生的。

另一方面,零售商店的存活清单不适合用有序数组来实现,这是由于与频繁的进货和出货相应的插入与删除操作都会执行得很慢。

 

5.对数;

从上图可以看出以2为底r的对数是为了得到r重复乘2的次数,在上图中,第一列显示的数字s便等于log2(r)。

6.存储对象;

目前为止在以上的例子中,数据结构中只存储long类型的简单变量。接下来的例子就会出现以person对象类似的例子,为方便理解,直接上代码。

class Person{
    private String lastName;
    private String firstName;
    private int age;
//-------------------------------------------------
    public Person(String last,String first,int a){
        //constructor
        lastName  = last;
        firstName = first;
        age       = a;
    }
    public void displayPerson(){
        System.out.print("last name:"+lastName);
        System.out.print("first name:"+firstName);
        System.out.print("Age:"+age);
    }
    public String getLast(){        //get last name
        return lastName;
    }    //end class Person
}

在上面类中只有三个变量,一个人的姓,名和年龄。当然大多数应用程序会包括许多另外的字段。

构造函数创建一个新的Person对象并将它的各个字段初始化。displayPerson()方法显示了一个Person对象的数据,getLast()方法返回Person的姓:这是用于搜索所需的关键字字段。

使用Person类的程序与存储long类型数据项的highArray.java程序类似。只需要几个变化就可以适应处理Person对象。下面是一些比较大的变化:

           数组类型改为Person;

           关键字现在是一个String类对象,因此作比较时需要使用equals方法而不是==运算符。Person中的getLast方法可以得到一个Person对象的姓,equals通过下面代码进行比较:if(a[j].getLast().equals(searchName))   //found item?

           insert方法创建一个新的Person对象,并把它插入到数组中,而不是插入一个long类型的值。

为避免混淆,给出一个完整的例子,如下代码classDataArray.java程序:

class ClassDataArray{
    private Person[] a;        //reference to array
    private int nElems;        //number of data items

    public ClassDataArray(int max){        //constructor
        a = new Person[max];               //create the array
        nElems = 0;                        //no items yet
    }
    public Person find(String searchName){
        int j;
        for(j=0;j<nElems;j++){
            if(a[j].getLast().equals(searchName))
                break;
        if(j==nElems)
            return null;
        else
            return a[j];
        }
    }
    public void insert(String last,String first ,int age){
        a[nElems] = new Person(last,first,age);
        nElems++;            //increment size
    }
    public boolean delete(String searchName){
        int j;
        for(j=0;j<nElems;j++)
            if(a[j].getLast().equals(searchName))
                break;
        if(j==nElems)
            return false;
        else{
            for(int k=j;k<nElems;k++)
                a[k]=a[k+1];
            nElems--;
            return true;
        }
    }
    public void displayA(){
        for(int j=0;j<nElems;j++)
            a[j].displayPerson();
    }
}

class ClassDataApp{
    public static void main(String[] args){
        int maxSize = 100;
        ClassDataArray arr;
        arr = new ClassDataArray(maxSize);

        //insert
        arr.insert("Evans","Patty",24)
        arr.insert("Evanss","Pattys",25)
        
        //display
        arr.displayA()

        //search
        String searchKey = "Stimson";    
        Person found;
        found = arr.find(searchKey);

        //delete
        arr.delete("Evans")
    }
}

classDataArray.java程序显示了数据存储结构可以通过与简单类型同样的方法来处理类对象。

7.大O表示法;

汽车按尺寸被分为若干类:微型,小型,中型等等。在不提及具体尺寸的情况下,这些分类可以为我们所涉及到车的大小提供一个大致概念。我们同样也需要一种快捷的方法来评价计算机算法的效率。在计算机科学中,这种粗略的度量方法被称作“大O”表示法。而我们所需要的是一个可以描述算法的速度是如何与数据项的个数相联系的比较。下面是我们目前所见过的算法的大O表示。

无序数组的插入:常数

无序数组的插入式我们到现在为止所见过的算法中唯一一个与数组中的数据项个数无关的算法。新数据项总是被放在下一个有空的地方,a[nElems],然后nElems增加。无论数组中的数据项个数N有多大,一次插入总是用相同的时间。我们可以说向一个无序数组中插入一个数据项的时间T是一个常数K:

T=K

在现实情况下,插入所需的实际时间与一下这些因素有关:微处理器,编译程序生成程序代码的效率,等等。上面等式中的常数K包含了所有这些因素。在现实情况中要得到K的值,需要测量一次插入所花费的时间。(软件就是为了这个目的而存在的。)K就等于这个时间。

线性查找:与N成正比

在数组数据项的线性查找中,我们已经发现寻找特定数据项所需的比较次数平均为数据项总数的一半。因此设N为数据项总数,搜索时间T与N的一半成正比:

T = K*N/2

同插入一样,若要得到方程中K值,首先需要对某个N值(有可能很大)的查找进行计时,然后用T来计算K。当得到K后便可对任意N的值计算T。

这个方程说明平均线性查找时间与数组的大小成正比。即如果一个数组增大两倍,则所花费的查找时间也会相应第地增长两倍。

二分查找:与log(N)成正比

同样,我们可以为二分查找制定出一个与T和N有关的公式:

T = K*log2(N)

正如前面所提到的,时间T与以2为底N的对数成正比。实际上,由于所有的对数都和其他对数成比例,我们可以将这个常数的底数也并入K。由此不必指定底数:

T = K*log(N)

不要常数

大O表示法同上面的公式比较类似,但它省去了常数K。当比较算法时,并不在乎具体的微处理器芯片或编译器:真正需要比较的是对应不同的N值,T是如何变化的,而不是具体的数字。因此不需要常数。

大O表示法使用大写字母O,可以认为其含义是“order of”(大约是)。我们可以使用大O表示法描述线性查找使用了O(N)级时间,二分查找使用了O(logN)级时间。向一个无序数组中的插入使用了O(1),或常数级时间。(小括号中是数字1.)

非常主观的认为:O(1)是优秀,O(logN)是良好,O(N)是还可以,O(N2)则差一些了。O(N2)会在本书后面的冒泡排序和其他某些算法中出现。

8.为什么不用数组表示一切?

仅仅使用数组似乎就可以完成所有工作,为什么不用它们来进行所有的数据存储呢?归根就是效率问题。

简单介绍:数组是应用最广泛的数据存储结构。它被植入到大部分编程语言中。由于数组十分易懂,所以它被用来作为介绍数据结构的起步点,并展示面向对象编程和数据结构之间的相互关系。本篇中将会介绍Java中的数组并展示自制的数组类。

 

 

小结:

Java中数组是对象,由new操作符创建;

无序数组可以提供快速的插入,但查找和删除较慢;

将数组封装到类中可以保护数组不被随意更改;

类的接口由类用户可访问的方法(还有可能有字段)组成;

类的接口被设计成使类用户的操作更加简单;有序数组可以使用二分查找;

以B为底A的对数是在结果小于1之前用B除A的次数;   //暂时不懂

线性查找需要的时间与数组中数据项的个数成正比;

二分查找需要的时间与数组中数据项的个数的对数成正比;

大O表示法为比较算法的速度提供了一种方便的方法;

O(1)级时间的算法是最好的,O(logN)次之,O(N)为一般,O(N2)最差。

 

问题:

1.向一个无序数组中插入一个数据项:不管已有多少数据项都花费同样的时间。

2.判断当从无序数组中删除数据项时,大多数情况下需要移动其他的数据项来填洞。

3.在无序数组中,允许重复会导致在某些情况下查找时间的增加。

4.在无序数组中,查找一个不在数组中的数据项通常要比找到一个数据项快。  错误

5.Java中创建一个数组需要使用关键字 new。

6.如果类A要使用类B,那么类B能做的操作越多越好。

7.当类A使用类B时,可以被类A访问的类B中的方法和字段被称作是类B的接口。

8.与无序数组相比,有序数组查找更快。

9.对数是指数的反函数。

10.以10为底1000的对数是3.

11.在一个含有200的个数据项的数组中完成二分查找所需检查的最大数据项个数是8.

12.以2为底64的对数是6.

13.以2为底100的对数是不是2.

14.大O表示法表示了算法的速度是如何与数据项的个数相关的。

15.O(1)意味着一个操作执行了常量的时间。

16.简单类型变量和对象都可以存入数组中。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!