第04章_數(shù)組

學(xué)習(xí)目標

  • 理解容器的概念
  • 掌握一維的聲明和初始化
  • 使用索引訪問數(shù)組的元素
  • 掌握數(shù)組的遍歷
  • 了解數(shù)組的內(nèi)存圖解
  • 熟悉空指針和數(shù)組角標越界異常
  • 掌握數(shù)組基礎(chǔ)算法
    • 掌握數(shù)組元素的統(tǒng)計分析
    • 掌握數(shù)組最大值的獲取
    • 掌握數(shù)組元素的查找
    • 掌握數(shù)組元素的反轉(zhuǎn)
    • 掌握數(shù)組的排序

1. 數(shù)組的概述

1.1 容器概述

需求分析1:

現(xiàn)在需要統(tǒng)計某公司50個員工的工資情況,例如計算平均工資、找到最高工資等。用前面所學(xué)的知識,首先需要聲明50個變量來分別記住每位員工的工資,這樣做會顯得很麻煩。因此我們可以使用容器進行操作,將所有的數(shù)據(jù)全部存儲到一個容器中,統(tǒng)一操作。

容器概念:

  • 生活中的容器:水杯(裝水等液體),衣柜(裝衣服等物品),教室(裝學(xué)生等人員)。
  • 程序中的容器:是將多個數(shù)據(jù)存儲到一起,每個數(shù)據(jù)稱為該容器的元素。

1.2 數(shù)組的概念

  • 數(shù)組(Array),是多個相同類型數(shù)據(jù)按一定順序排列的集合,并使用一個名字命名,并通過編號的方式對這些數(shù)據(jù)進行統(tǒng)一管理。

  • 數(shù)組中的概念

    • 數(shù)組名
    • 下標(或索引)
    • 元素
    • 數(shù)組的長度

數(shù)組的特點:

  • 數(shù)組本身是引用數(shù)據(jù)類型,而數(shù)組中的元素可以是任何數(shù)據(jù)類型,包括基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。

  • 創(chuàng)建數(shù)組對象會在內(nèi)存中開辟一整塊連續(xù)的空間,而數(shù)組名中引用的是這塊連續(xù)空間的首地址。

  • 數(shù)組的長度一旦確定,就不能修改

  • 我們可以直接通過下標(或索引)的方式調(diào)用指定位置的元素,速度很快。

1.3 數(shù)組的分類

1、按照維度分:

  • 一維數(shù)組:存儲一組數(shù)據(jù)
  • 二維數(shù)組:存儲多組數(shù)據(jù),相當于二維表,一行代表一組數(shù)據(jù),只是這里的二維表每一行長度不要求一樣。

2、按照元素類型分:

  • 基本數(shù)據(jù)類型的元素:存儲基本數(shù)據(jù)類型的值
  • 引用數(shù)據(jù)類型的元素:存儲對象(本質(zhì)上存儲對象的首地址)(這個在面向?qū)ο蟛糠种v解)

2. 一維數(shù)組的使用

2.1 一維數(shù)組的聲明

  • 一維數(shù)組的聲明/定義格式
//推薦
元素的數(shù)據(jù)類型[] 數(shù)組的名稱;

//不推薦
元素的數(shù)據(jù)類型  數(shù)組名[];

舉例:

int a[];
int[] a1;
double  b[];
String[] c;  //引用類型變量數(shù)組
  • 數(shù)組的聲明,就是要確定:

(1)數(shù)組的維度:在Java中數(shù)組的標點符號是[],[]表示一維,[][]表示二維

(2)數(shù)組的元素類型:即創(chuàng)建的數(shù)組容器可以存儲什么數(shù)據(jù)類型的數(shù)據(jù)。可以是基本數(shù)據(jù)類型,也可以是引用數(shù)據(jù)類型。例如:int, String, Student等

(3)數(shù)組名:就是代表某個數(shù)組的標識符,數(shù)組名其實也是變量名,按照變量的命名規(guī)范來命名。數(shù)組名是個引用數(shù)據(jù)類型的變量,因為它代表一組數(shù)據(jù)。

  • 示例
public class Test01ArrayDeclare {
    public static void main(String[] args) {
        //比如,要存儲一個小組的成績
        int[] scores;
        int grades[];
//        System.out.println(scores);//未初始化不能使用

        //比如,要存儲一組字母
        char[] letters;

        //比如,要存儲一組姓名
        String[] names;

        //比如,要存儲一組價格
        double[] prices;

    }
}

注意:Java語言中聲明數(shù)組時不能指定其長度(數(shù)組中元素的數(shù))。 例如: int a[5]; //非法

2.2 一維數(shù)組的初始化

2.2.1 靜態(tài)初始化

  • 什么是靜態(tài)初始化?

  • 靜態(tài)初始化就是用靜態(tài)數(shù)據(jù)(編譯時已知)為數(shù)組初始化。此時數(shù)組的長度由靜態(tài)數(shù)據(jù)的個數(shù)決定。

  • 一維數(shù)組靜態(tài)初始化格式1:

    數(shù)據(jù)類型[] 數(shù)組名 = new 數(shù)據(jù)類型[]{元素1,元素2,元素3...};
    
    或
        
    數(shù)據(jù)類型[] 數(shù)組名;
    數(shù)組名 = new 數(shù)據(jù)類型[]{元素1,元素2,元素3...};
    
    • new:關(guān)鍵字,創(chuàng)建數(shù)組使用的關(guān)鍵字。因為數(shù)組本身是引用數(shù)據(jù)類型,所以要用new創(chuàng)建數(shù)組對象。

    例如,定義存儲1,2,3,4,5整數(shù)的數(shù)組容器。

    int[] arr = new int[]{1,2,3,4,5};//正確
    //或
    int[] arr;
    arr = new int[]{1,2,3,4,5};//正確
    
  • 一維數(shù)組靜態(tài)初始化格式2:

    數(shù)據(jù)類型[] 數(shù)組名 = {元素1,元素2,元素3...};//必須在一個語句中完成,不能分開兩個語句寫
    

例如,定義存儲1,2,3,4,5整數(shù)的數(shù)組容器

int[] arr = {1,2,3,4,5};//正確

int[] arr;
arr = {1,2,3,4,5};//錯誤
  • 舉例
public class Test02ArrayInitialize {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};//右邊不需要寫new int[]

        int[] nums;
        nums = new int[]{10,20,30,40}; //聲明和初始化在兩個語句完成,就不能使用new int[]

        char[] word = {'h','e','l','l','o'};

        String[] heros = {"袁隆平","鄧稼先","錢學(xué)森"};

        System.out.println("arr數(shù)組:" + arr);//arr數(shù)組:[I@1b6d3586
        System.out.println("nums數(shù)組:" + nums);//nums數(shù)組:[I@4554617c
        System.out.println("word數(shù)組:" + word);//word數(shù)組:[C@74a14482
        System.out.println("heros數(shù)組:" + heros);//heros數(shù)組:[Ljava.lang.String;@1540e19d
    }
}

2.2.2 動態(tài)初始化

  • 什么是動態(tài)初始化?

動態(tài)初始化就是先確定元素的個數(shù)(即數(shù)組的長度),而元素值此時只是默認值,還并未真正附自己期望的值。真正期望的數(shù)據(jù)需要后續(xù)單獨一個一個賦值。

  • 格式:
數(shù)組存儲的元素的數(shù)據(jù)類型[] 數(shù)組名字 = new 數(shù)組存儲的元素的數(shù)據(jù)類型[長度];

或

數(shù)組存儲的數(shù)據(jù)類型[] 數(shù)組名字; 或 數(shù)據(jù)類型 數(shù)組名[]; (括號在數(shù)組名后也可以)
數(shù)組名字 = new 數(shù)組存儲的數(shù)據(jù)類型[長度];
  • [長度]:數(shù)組的長度,表示數(shù)組容器中可以最多存儲多少個元素。

  • 注意:數(shù)組有定長特性,長度一旦指定,不可更改。和水杯道理相同,買了一個2升的水杯,總?cè)萘烤褪?升是固定的。

  • 舉例1

int[] arr = new int[5];

int[] arr;
arr = new int[5];

int[] arr = new int[5]{1,2,3,4,5};//錯誤的,后面有{}指定元素列表,就不需要在[]中指定元素個數(shù)了。
  • 錯誤情況:

    //double[] arr = new double[2]{12.3,34.5};
    //double[2] arr1 = new double[2];
    

2.3 一維數(shù)組的使用

2.3.1 數(shù)組的長度

  • 數(shù)組的元素總個數(shù),即數(shù)組的長度
  • 每個數(shù)組都有一個屬性length指明它的長度,例如:a.length 指明數(shù)組a的長度(元素個數(shù))
  • 每個數(shù)組都具有長度,而且一旦初始化,其長度是不可變的

2.3.2 數(shù)組元素的引用

  • 如何表示數(shù)組中的一個元素?

每一個存儲到數(shù)組的元素,都會自動的擁有一個編號,從0開始,這個自動編號稱為數(shù)組索引(index)或下標,可以通過數(shù)組的索引/下標訪問到數(shù)組中的元素。

數(shù)組名[索引/下標]
  • 數(shù)組的下標范圍?

    Java中數(shù)組的下標從[0]開始,下標范圍是[0, 數(shù)組的長度-1],即[0, 數(shù)組名.length-1]

  • 數(shù)組元素下標可以是整型常量或整型表達式。如a[3] , b[i] , c[6*i];

  • 舉例

public class Test03ArrayUse {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};

        System.out.println("arr數(shù)組的長度:" + arr.length);
        System.out.println("arr數(shù)組的第1個元素:" + arr[0]);//下標從0開始  1
        System.out.println("arr數(shù)組的第2個元素:" + arr[1]); //2
        System.out.println("arr數(shù)組的第3個元素:" + arr[2]); //3
        System.out.println("arr數(shù)組的第4個元素:" + arr[3]); //4
        System.out.println("arr數(shù)組的第5個元素:" + arr[4]); //5

        //修改第1個元素的值
        //此處arr[0]相當于一個int類型的變量
        arr[0] = 100;
        System.out.println("arr數(shù)組的第1個元素:" + arr[0]); //100
    }
}

2.4 一維數(shù)組的遍歷

數(shù)組遍歷: 就是將數(shù)組中的每個元素分別獲取出來,就是遍歷。遍歷也是數(shù)組操作中的基石。for循環(huán)與數(shù)組的遍歷是絕配。

  • 舉例1
public class Test05ArrayIterate {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        //打印數(shù)組的屬性,輸出結(jié)果是5
        System.out.println("數(shù)組的長度:" + arr.length); //5

        //遍歷輸出數(shù)組中的元素
        System.out.println("數(shù)組的元素有:");
        for(int i=0; i<arr.length; i++){
            System.out.println(arr[i]);    
        }
    }
}

//1
//2
//3
//4
//5
  • 舉例2
public class Test06ArrayInitialize {
    public static void main(String[] args) {
        int[] arr = new int[5];

        System.out.println("arr數(shù)組的長度:" + arr.length); //5
        System.out.print("存儲數(shù)據(jù)到arr數(shù)組之前:[");
        for (int i = 0; i < arr.length; i++) {   //存儲數(shù)據(jù)到arr數(shù)組之前:[0,0,0,0,0]
            if(i==0){
                System.out.print(arr[i]);
            }else{
                System.out.print("," + arr[i]);
            }
        }
        System.out.println("]");

        //初始化
        arr[0] = 2;
        arr[1] = 4;
        arr[2] = 6;
        arr[3] = 8;
        arr[4] = 10;

        for (int i = 0; i < arr.length; i++) {  //arr = {2,4,6,8,10}
            arr[i] = (i+1) * 2;
        }

        System.out.print("存儲數(shù)據(jù)到arr數(shù)組之后:["); 
        for (int i = 0; i < arr.length; i++) { //存儲數(shù)據(jù)到arr數(shù)組之后:[2,4,6,8,10]
            if(i==0){
                System.out.print(arr[i]);
            }else{
                System.out.print("," + arr[i]);
            }
        }
        System.out.println("]");
    }
}

2.5 數(shù)組元素的默認值

數(shù)組是引用類型,當我們使用動態(tài)初始化方式創(chuàng)建數(shù)組時,元素值只是默認值。例如:

public class Test {
    public static void main(String argv[]){
        int a[]= new int[5]; 
        System.out.println(a[3]); //a[3]的默認值為0
    }
} 

對于基本數(shù)據(jù)類型而言,默認初始化值各有不同。

對于引用數(shù)據(jù)類型而言,默認初始化值為null(注意與0不同!)

public class Test07ArrayElementDefaultValue {
    public static void main(String[] args) {
        //存儲26個字母
        char[] letters = new char[26];
        System.out.println("letters數(shù)組的長度:" + letters.length);  //letters數(shù)組的長度:26
        System.out.print("存儲字母到letters數(shù)組之前:[");
        for (int i = 0; i < letters.length; i++) { //存儲字母到letters數(shù)組之前:[0,0,0......0] 26個
            if(i==0){
                System.out.print(letters[i]);
            }else{
                System.out.print("," + letters[i]);
            }
        }
        System.out.println("]");

       //存儲5個姓名
        String[] names = new String[5];
        System.out.println("names數(shù)組的長度:" + names.length); //names數(shù)組的長度:5
        System.out.print("存儲姓名到names數(shù)組之前:[");
        for (int i = 0; i < names.length; i++) { //存儲姓名到names數(shù)組之前:[null,null,null,null,null]
            if(i==0){
                System.out.print(names[i]);
            }else{
                System.out.print("," + names[i]);
            }
        }
        System.out.println("]");
    }
}

3. 一維數(shù)組內(nèi)存分析

3.1 內(nèi)存概述

內(nèi)存是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。其作用是用于暫時存放CPU中的運算數(shù)據(jù),以及與硬盤等外部存儲器交換的數(shù)據(jù)。只要計算機在運行中,CPU就會把需要運算的數(shù)據(jù)調(diào)到內(nèi)存中進行運算,當運算完成后CPU再將結(jié)果傳送出來。我們編寫的程序是存放在硬盤中的,在硬盤中的程序是不會運行的,必須放進內(nèi)存中才能運行,運行完畢后會清空內(nèi)存。

Java虛擬機要運行程序,必須要對內(nèi)存進行空間的分配和管理。

3.2 Java虛擬機的內(nèi)存劃分

為了提高運算效率,就對空間進行了不同區(qū)域的劃分,因為每一片區(qū)域都有特定的處理數(shù)據(jù)方式和內(nèi)存管理方式。

區(qū)域名稱 作用
程序計數(shù)器 程序計數(shù)器是CPU中的寄存器,它包含每一個線程下一條要執(zhí)行的指令的地址
本地方法棧 當程序中調(diào)用了native的本地方法時,本地方法執(zhí)行期間的內(nèi)存區(qū)域
方法區(qū) 存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
堆內(nèi)存 存儲對象(包括數(shù)組對象),new來創(chuàng)建的,都存儲在堆內(nèi)存。
虛擬機棧 用于存儲正在執(zhí)行的每個Java方法的局部變量表等。局部變量表存放了編譯期可知長度的各種基本數(shù)據(jù)類型、對象引用,方法執(zhí)行完,自動釋放。

3.3 一維數(shù)組在內(nèi)存中的存儲

1、一個一維數(shù)組內(nèi)存圖

public static void main(String[] args) {
    int[] arr = new int[3];
    System.out.println(arr);//[I@5f150435
}

思考:打印arr為什么是[I@5f150435,它是數(shù)組的地址嗎?

答:它不是數(shù)組的地址。

問?不是說arr中存儲的是數(shù)組對象的首地址嗎?

答:arr中存儲的是數(shù)組的首地址,但是因為數(shù)組是引用數(shù)據(jù)類型,打印arr時,會自動調(diào)用arr數(shù)組對象的toString()方法,該方法默認實現(xiàn)的是對象類型名@該對象的hashCode()值的十六進制值。

問?對象的hashCode值是否就是對象內(nèi)存地址?

答:不一定,因為這個和不同品牌的JVM產(chǎn)品的具體實現(xiàn)有關(guān)。例如:Oracle的OpenJDK中給出了5種實現(xiàn),其中有一種是直接返回對象的內(nèi)存地址,但是OpenJDK默認沒有選擇這種方式。

2、數(shù)組下標為什么是0開始

因為第一個元素距離數(shù)組首地址間隔0個單元格。

3、兩個一維數(shù)組內(nèi)存圖

兩個數(shù)組獨立

public static void main(String[] args) {
    int[] arr = new int[3];
    int[] arr2 = new int[2];
    System.out.println(arr);
    System.out.println(arr2);
}

4、兩個變量指向一個一維數(shù)組

  • 兩個數(shù)組變量本質(zhì)上代表同一個數(shù)組首地址值相同
public static void main(String[] args) {
    // 定義數(shù)組,存儲3個元素
    int[] arr = new int[3];
    //數(shù)組索引進行賦值
    arr[0] = 5;
    arr[1] = 6;
    arr[2] = 7;
    //輸出3個索引上的元素值
    System.out.println(arr[0]); //5
    System.out.println(arr[1]); //6
    System.out.println(arr[2]); //7
    //定義數(shù)組變量arr2,將arr的地址賦值給arr2
    int[] arr2 = arr;
    arr2[1] = 9; 
    System.out.println(arr[1]); //9 
}

4. 一維數(shù)組的應(yīng)用

4.1 案例1

升景坊單間短期出租4個月,550元/月(水電煤公攤,網(wǎng)費35元/月),空調(diào)、衛(wèi)生間、廚房齊全。屋內(nèi)均是IT行業(yè)人士,喜歡安靜。所以要求來租者最好是同行或者剛畢業(yè)的年輕人,愛干凈、安靜。

public class ArrayTest {
      public static void main(String[] args) {
      int[] arr = new int[]{8,2,1,0,3}; //號碼中包含的數(shù)
      int[] index = new int[]{2,0,3,2,4,0,1,3,2,3,3}; //對應(yīng)數(shù)的位置
      String tel = "";
      for(int i = 0;i < index.length;i++){
            tel += arr[index[i]];
      }
      System.out.println("聯(lián)系方式:" + tel); //18013820100
      }
}

4.2 案例2:

從鍵盤讀入學(xué)生成績,找出最高分,并輸出學(xué)生成績等級。

  • 成績>=最高分-10 等級為’A’

  • 成績>=最高分-20 等級為’B’

  • 成績>=最高分-30 等級為’C’

  • 其余 等級為’D’

提示:先讀入學(xué)生人數(shù),根據(jù)人數(shù)創(chuàng)建int數(shù)組,存放學(xué)生成績。

        //1. 實例化Scanner,獲取學(xué)生人數(shù)
        Scanner scan = new Scanner(System.in);
        System.out.println("請輸入學(xué)生人數(shù):");
        int num = scan.nextInt();

        //2.根據(jù)人數(shù),創(chuàng)建指定長度的數(shù)組 (使用動態(tài)初始化)
        int[] scores = new int[num];

        //3. 遍歷數(shù)組,通過控制臺的方式給每個數(shù)組元素賦值
        System.out.println("請輸入" + num + "個成績:");
        for(int i = 0;i < scores.length;i++){
            int score = scan.nextInt();
            scores[i] = score;
        }

        //4. 獲取數(shù)組中元素的最大值
        int maxScore = scores[0];
        for(int i = 1;i < scores.length;i++){
            if(maxScore < scores[i]){
                maxScore = scores[i];
            }
        }

        System.out.println("最高分為:" + maxScore);

        //5. 遍歷數(shù)組,判斷每個學(xué)生成績與最高分的差值,根據(jù)差值,決定每個學(xué)生的等級,并輸出
        for(int i = 0;i < scores.length;i++){
            char grade;
            if(scores[i] >= maxScore - 10){
                grade = 'A';
            }else if(scores[i] >= maxScore - 20){
                grade = 'B';
            }else if(scores[i] >= maxScore - 30){
                grade = 'C';
            }else{
                grade = 'D';
            }

            System.out.println("student " + i + " score is " + scores[i] + " grade is " + grade);
        }

5. 多維數(shù)組的使用

5.1 概述

  • Java 語言里提供了支持多維數(shù)組的語法。
  • 如果說可以把一維數(shù)組當成幾何中的線性圖形,那么二維數(shù)組就相當于是一個表格,像右圖Excel中的表格一樣。
  • 對于二維數(shù)組的理解,我們可以看成是一維數(shù)組array1又作為另一個一維數(shù)組array2的元素而存在。其實,從數(shù)組底層的運行機制來看,其實沒有多維數(shù)組。

5.2 聲明與初始化

5.2.1 聲明

二維數(shù)組聲明的語法格式:

//推薦
元素的數(shù)據(jù)類型[][] 二維數(shù)組的名稱;

//不推薦
元素的數(shù)據(jù)類型  二維數(shù)組名[][];
//不推薦
元素的數(shù)據(jù)類型[]  二維數(shù)組名[];

例如:

public class Test20TwoDimensionalArrayDefine {
    public static void main(String[] args) {
        //存儲多組成績
        int[][] grades;

        //存儲多組姓名
        String[][] names;
    }
}

面試:

int[] x, y[];
//x是一維數(shù)組,y是二維數(shù)組

5.2.2 靜態(tài)初始化

格式:

int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};

定義一個名稱為arr的二維數(shù)組,二維數(shù)組中有三個一維數(shù)組

  • 每一個一維數(shù)組中具體元素也都已初始化
    • 第一個一維數(shù)組 arr[0] = {3,8,2};
    • 第二個一維數(shù)組 arr[1] = {2,7};
    • 第三個一維數(shù)組 arr[2] = {9,0,1,6};
  • 第三個一維數(shù)組的長度表示方式:arr[2].length;
  • 注意特殊寫法情況:int[] x,y[]; x是一維數(shù)組,y是二維數(shù)組。
  • 示例:

舉例1:

int[][] arr = {{1,2,3},{4,5,6},{7,8,9,10}};//聲明與初始化必須在一句完成

int[][] arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};

int[][] arr;
arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};

arr = new int[3][3]{{1,2,3},{4,5,6},{7,8,9,10}};//錯誤,靜態(tài)初始化右邊new 數(shù)據(jù)類型[][]中不能寫數(shù)字

舉例2:

public class TwoDimensionalArrayInitialize {
    public static void main(String[] args) {
        //存儲多組成績
        int[][] grades = {
                    {89,75,99,100},
                    {88,96,78,63,100,86},
                    {56,63,58},
                    {99,66,77,88}
                };

        //存儲多組姓名
        String[][] names = {
            {"張三","李四", "王五", "趙六"},
            {"劉備","關(guān)羽","張飛","諸葛亮","趙云","馬超"},
            {"曹丕","曹植","曹沖"},
            {"孫權(quán)","周瑜","魯肅","黃蓋"}
        };
    }
}

5.2.3 動態(tài)初始化

如果二維數(shù)組的每一個數(shù)據(jù),甚至是每一行的列數(shù),需要后期單獨確定,那么就只能使用動態(tài)初始化方式了。動態(tài)初始化方式分為兩種格式:

格式1:規(guī)則二維表:每一行的列數(shù)是相同的

//(1)確定行數(shù)和列數(shù)
元素的數(shù)據(jù)類型[][] 二維數(shù)組名 = new 元素的數(shù)據(jù)類型[m][n];
    //其中,m:表示這個二維數(shù)組有多少個一維數(shù)組。或者說一共二維表有幾行
    //其中,n:表示每一個一維數(shù)組的元素有多少個。或者說每一行共有一個單元格

//此時創(chuàng)建完數(shù)組,行數(shù)、列數(shù)確定,而且元素也都有默認值

//(2)再為元素賦新值
二維數(shù)組名[行下標][列下標] = 值;

舉例:

int[][] arr = new int[3][2];
  • 定義了名稱為arr的二維數(shù)組

  • 二維數(shù)組中有3個一維數(shù)組

  • 每一個一維數(shù)組中有2個元素

  • 一維數(shù)組的名稱分別為arr[0], arr[1], arr[2]

  • 給第一個一維數(shù)組1腳標位賦值為78寫法是:arr[0][1] = 78;

格式2:不規(guī)則:每一行的列數(shù)不一樣

//(1)先確定總行數(shù)
元素的數(shù)據(jù)類型[][] 二維數(shù)組名 = new 元素的數(shù)據(jù)類型[總行數(shù)][];

//此時只是確定了總行數(shù),每一行里面現(xiàn)在是null

//(2)再確定每一行的列數(shù),創(chuàng)建每一行的一維數(shù)組
二維數(shù)組名[行下標] = new 元素的數(shù)據(jù)類型[該行的總列數(shù)];

//此時已經(jīng)new完的行的元素就有默認值了,沒有new的行還是null

//(3)再為元素賦值
二維數(shù)組名[行下標][列下標] = 值;

舉例:

int[][] arr = new int[3][];
  • 二維數(shù)組中有3個一維數(shù)組。
  • 每個一維數(shù)組都是默認初始化值null (注意:區(qū)別于格式1)
  • 可以對這個三個一維數(shù)組分別進行初始化:arr[0] = new int[3]; arr[1] = new int[1]; arr[2] = new int[2];
  • 注:int[][]arr = new int[][3]; //非法

練習(xí):

/*
 1
 2 2
 3 3 3
 4 4 4 4
 5 5 5 5 5
 */
public class Test25DifferentElementCount {
    public static void main(String[] args){
        //1、聲明一個二維數(shù)組,并且確定行數(shù)
        //因為每一行的列數(shù)不同,這里無法直接確定列數(shù)
        int[][]  arr = new int[5][];

        //2、確定每一行的列數(shù)
        for(int i=0; i<arr.length; i++){
            /*
            arr[0] 的列數(shù)是1
            arr[1] 的列數(shù)是2
            arr[2] 的列數(shù)是3
            arr[3] 的列數(shù)是4
            arr[4] 的列數(shù)是5
            */
            arr[i] = new int[i+1];
        }

        //3、確定元素的值
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                arr[i][j] = i+1;
            }
        }

        //4、遍歷顯示
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }

    }
}

小結(jié):

Java中多維數(shù)組不必都是規(guī)則矩陣形式

5.3 使用說明

因為二維數(shù)組是用來存儲多組數(shù)據(jù)的,因此要比一維數(shù)組麻煩一些,需要我們搞清楚如下幾個概念:

  • 二維數(shù)組的長度/行數(shù):二維數(shù)組名.length
  • 二維數(shù)組的某一行:二維數(shù)組名[行下標],此時相當于獲取其中一組數(shù)據(jù)。它本質(zhì)上是一個一維數(shù)組。行下標的范圍:[0, 二維數(shù)組名.length-1]。此時把二維數(shù)組看成一維數(shù)組的話,元素是行對象。
  • 某一行的列數(shù):二維數(shù)組名[行下標].length,因為二維數(shù)組的每一行是一個一維數(shù)組。
  • 某一個元素:二維數(shù)組名[行下標][列下標],即先確定行/組,再確定列。
public class Test22TwoDimensionalArrayUse {
    public static void main(String[] args){
        //存儲3個小組的學(xué)員的成績,分開存儲,使用二維數(shù)組。
        /*
        int[][] scores1;
        int scores2[][];
        int[] scores3[];*/

        int[][] scores = {
                {85,96,85,75},
                {99,96,74,72,75},
                {52,42,56,75}
        };

        System.out.println(scores);//[[I@15db9742
        System.out.println("一共有" + scores.length +"組成績.");

        //[[:代表二維數(shù)組,I代表元素類型是int
        System.out.println(scores[0]);//[I@6d06d69c
        //[:代表一維數(shù)組,I代表元素類型是int
        System.out.println(scores[1]);//[I@7852e922
        System.out.println(scores[2]);//[I@4e25154f
        //System.out.println(scores[3]);//ArrayIndexOutOfBoundsException: 3

        System.out.println("第1組有" + scores[0].length +"個學(xué)員.");
        System.out.println("第2組有" + scores[1].length +"個學(xué)員.");
        System.out.println("第3組有" + scores[2].length +"個學(xué)員.");

        System.out.println("第1組的每一個學(xué)員成績?nèi)缦拢?);
        //第一行的元素
        System.out.println(scores[0][0]);//85
        System.out.println(scores[0][1]);//96
        System.out.println(scores[0][2]);//85
        System.out.println(scores[0][3]);//75
        //System.out.println(scores[0][4]);//java.lang.ArrayIndexOutOfBoundsException: 4
    }
}

二維數(shù)組的遍歷:

  • 格式:
for(int i=0; i<二維數(shù)組名.length; i++){ //二維數(shù)組對象.length
    for(int j=0; j<二維數(shù)組名[i].length; j++){//二維數(shù)組行對象.length
        System.out.print(二維數(shù)組名[i][j]);
    }
    System.out.println();
}
  • 舉例:
public class Test23TwoDimensionalArrayIterate {
    public static void main(String[] args) {
        //存儲3個小組的學(xué)員的成績,分開存儲,使用二維數(shù)組。
        int[][] scores = {
                {85,96,85,75},
                {99,96,74,72,75},
                {52,42,56,75}
        };

        System.out.println("一共有" + scores.length +"組成績.");
        for (int i = 0; i < scores.length; i++) {
            System.out.print("第" + (i+1) +"組有" + scores[i].length + "個學(xué)員,成績?nèi)缦拢?);
            for (int j = 0; j < scores[i].length; j++) {
                System.out.print(scores[i][j]+"\t");
            }
            System.out.println();
        }
    }
}

5.4 內(nèi)存解析

二維數(shù)組本質(zhì)上是元素類型是一維數(shù)組的一維數(shù)組。

        int[][] arr = {
            {1},
            {2,2},
            {3,3,3},
            {4,4,4,4},
            {5,5,5,5,5}
        };
        //1、聲明二維數(shù)組,并確定行數(shù)和列數(shù)
        int[][] arr = new int[4][5];
        
        //2、確定元素的值
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                arr[i][j] = i + 1;
            }
        }
        //1、聲明一個二維數(shù)組,并且確定行數(shù)
        //因為每一行的列數(shù)不同,這里無法直接確定列數(shù)
        int[][]  arr = new int[5][];
        
        //2、確定每一行的列數(shù)
        for(int i=0; i<arr.length; i++){
            /*
            arr[0] 的列數(shù)是1
            arr[1] 的列數(shù)是2
            arr[2] 的列數(shù)是3
            arr[3] 的列數(shù)是4
            arr[4] 的列數(shù)是5
            */
            arr[i] = new int[i+1];
        }
        
        //3、確定元素的值
        for(int i=0; i<arr.length; i++){
            for(int j=0; j<arr[i].length; j++){
                arr[i][j] = i+1;
            }
        }

5.6 應(yīng)用舉例

案例1:獲取arr數(shù)組中所有元素的和。

提示:使用for的嵌套循環(huán)即可。

int[][] arr = {{3,5,8},{12,9},{7,0,6,4}};
int sum = 0;
for(int i = 0;i < arr.length ;i++){
    for(int j = 0; j < arr[i].length;j++){
        sum+=arr[i][j];
    }
}
System.out.println("所有元素的和:"+sum);

案例2:聲明:int[] x,y[]; 在給x,y變量賦值以后,以下選項允許通過編譯的是:

聲明:int[] x,y[]; 在給x,y變量賦值以后,以下選項允許通過編譯的是: 
a)    x[0] = y;         //no
b)    y[0] = x;         //yes
c)    y[0][0] = x;      //no
d)    x[0][0] = y;      //no
e)    y[0][0] = x[0];   //yes
f)    x = y;            //no

提示:
一維數(shù)組:int[] x  或者int x[]   
二維數(shù)組:int[][] y 或者  int[] y[]  或者 int  y[][]

案例3:使用二維數(shù)組打印一個 10 行楊輝三角。

提示:

  1. 第一行有 1 個元素, 第 n 行有 n 個元素

  2. 每一行的第一個元素和最后一個元素都是 1

  3. 從第三行開始, 對于非第一個元素和最后一個元素的元素。即:

    yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
    
        //1.二維數(shù)組的聲明和初始化(動態(tài)初始化)
        int[][] yangHui = new int[10][];
        //2.通過for循環(huán)給外層數(shù)組元素yangHui[i]初始化
        for(int i = 0;i <yangHui.length;i++){
            yangHui[i] = new int[i+1];
            //3.賦值:每一行的第一個元素和最后一個元素都是 1
            yangHui[i][0] = 1;
            yangHui[i][i] = 1;
            //4.賦值:從第三行開始,對于非第一個元素和最后一個元素的元素的賦值
            for (int j = 1; j < i ;j++){
                yangHui[i][j] = yangHui[i-1][j-1] + yangHui[i-1][j];
            }
        }
        for (int i = 0; i < yangHui.length; i++) {
            //System.out.println(Arrays.toString(yangHui[i]));
            for(int j = 0; j < yangHui[i].length;j++){
                System.out.print(yangHui[i][j] + " ");
            }
            System.out.println();
        }

6. 數(shù)組的常見算法

6.1 數(shù)值型數(shù)組特征值統(tǒng)計

  • 這里的特征值涉及到:平均值、最大值、最小值、總和等

舉例1:數(shù)組統(tǒng)計:求總和、均值

public class TestArrayElementSum {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9};
        //求總和、均值
        int sum = 0;//因為0加上任何數(shù)都不影響結(jié)果
        for(int i=0; i<arr.length; i++){
            sum += arr[i];
        }
        double avg = (double)sum/arr.length;

        System.out.println("sum = " + sum);
        System.out.println("avg = " + avg);
    }
}

舉例2:求數(shù)組元素的總乘積

public class TestArrayElementMul {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9};

        //求總乘積
        long result = 1;//因為1乘以任何數(shù)都不影響結(jié)果
        for(int i=0; i<arr.length; i++){
            result *= arr[i];
        }

        System.out.println("result = " + result);
    }
}

舉例3:求數(shù)組元素中偶數(shù)的個數(shù)

public class TestArrayElementEvenCount {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9};
        //統(tǒng)計偶數(shù)個數(shù)
        int evenCount = 0;
        for(int i=0; i<arr.length; i++){
            if(arr[i]%2==0){
                evenCount++;
            }
        }

        System.out.println("evenCount = " + evenCount);
    }
}

舉例4:求數(shù)組元素的最大值

public class TestArrayMax {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9};
        //找最大值
        int max = arr[0];
        for(int i=1; i<arr.length; i++){//此處i從1開始,是max不需要與arr[0]再比較一次了
            if(arr[i] > max){
                max = arr[i];
            }
        }

        System.out.println("max = " + max);
    }
}

舉例5:找最值及其第一次出現(xiàn)的下標:

public class TestMaxIndex {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9};
        //找最大值以及第一個最大值下標
        int max = arr[0];
        int index = 0;
        for(int i=1; i<arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
                index = i;
            }
        }

        System.out.println("max = " + max);
        System.out.println("index = " + index);
    }
}

舉例6:找最值及其所有最值的下標

public class Test13AllMaxIndex {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9,9,3};
        //找最大值
        int max = arr[0];
        for(int i=1; i<arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }
        System.out.println("最大值是:" + max);
        System.out.print("最大值的下標有:");

        //遍歷數(shù)組,看哪些元素和最大值是一樣的
        for(int i=0; i<arr.length; i++){
            if(max == arr[i]){
                System.out.print(i+"\t");
            }
        }
        System.out.println();
    }
}

優(yōu)化

public class Test13AllMaxIndex2 {
    public static void main(String[] args) {
        int[] arr = {4,5,6,1,9,9,3};
        //找最大值
        int max = arr[0];
        String index = "0";
        for(int i=1; i<arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
                index = i + "";
            }else if(arr[i] == max){
                index += "," + i;
            }
        }

        System.out.println("最大值是" + max);
        System.out.println("最大值的下標是[" + index+"]");
    }
}

舉例7:輸入一個整形數(shù)組,數(shù)組里有正數(shù)也有負數(shù)。數(shù)組中連續(xù)的一個或多個整數(shù)組成一個子數(shù)組,每個子數(shù)組都有一個和。求所有子數(shù)組的和的最大值。要求時間復(fù)雜度為O(n)。
例如:輸入的數(shù)組為1, -2, 3, -10, -4, 7, 2, -5,和最大的子數(shù)組為3, 10, -4, 7, 2,因此輸出為該子數(shù)組的和18。

public class Test5 {
    public static void main(String[] args) {
        int[] arr = new int[]{1, -2, 3, 10, -4, 7, 2, -5};
        int i = getGreatestSum(arr);
        System.out.println(i);
    }
    
    public static int getGreatestSum(int[] arr){
        int greatestSum = 0;
        if(arr == null || arr.length == 0){ //當arr為空時返回null
            return 0;
        }
        int temp = greatestSum;  //將子數(shù)組最大值賦給temp
        for(int i = 0;i < arr.length;i++){ //遍歷arr數(shù)組
            temp += arr[i];    //將arr元素的值加到temp上
            
            if(temp < 0){     //如果temp小于0,將temp重新復(fù)制成0
                temp = 0;
            }
            
            if(temp > greatestSum){ //temp總和值 大于 子數(shù)組的值greatestSum ,將temp 賦給 greatestSum
                greatestSum = temp;
            }
        }
        if(greatestSum == 0){   //如果子集合的值為0 (如果arr中都為負數(shù),選擇最大值返回)
            greatestSum = arr[0];   //將arr數(shù)組的第一個值賦給 greatestSum
            for(int i = 1;i < arr.length;i++){ //遍歷arr數(shù)組
                if(greatestSum < arr[i]){   //將arr中最大值賦給greatestSum
                    greatestSum = arr[i];
                }
            }
        }
        return greatestSum;
    }
}

6.2 數(shù)組元素的賦值

舉例1:楊輝三角(前面已講過)

舉例2:使用簡單數(shù)組

(1)創(chuàng)建一個名為ArrayTest的類,在main()方法中聲明array1和array2兩個變量,他們是int[]類型的數(shù)組。

(2)使用大括號{},把array1初始化為8個素數(shù):2,3,5,7,11,13,17,19。

(3)顯示array1的內(nèi)容。

(4)賦值array2變量等于array1,修改array2中的偶索引元素,使其等于索引值(如array[0]=0,array[2]=2)。打印出array1。 array2 = array1;

思考:array1和array2是什么關(guān)系?

  array1 和 array2 是兩個變量,共同指向了堆空間中的同一個數(shù)組,則二者的地址值相同

拓展:修改題目,實現(xiàn)array2對array1數(shù)組的復(fù)制

舉例3:一個數(shù)組,讓數(shù)組的每個元素去除第一個元素,得到的商作為被除數(shù)所在位置的新值。

public class Test3 {
    public static void main(String[] args) {
        int[] arr = new int[]{12,43,65,3,-8,64,2};
        
//      for(int i = 0;i < arr.length;i++){   沒有考慮第一個元素與第一個之間的結(jié)果為1,后面的值都是去除以1的。
//          arr[i] = arr[i] / arr[0];
//      }
        for(int i = arr.length -1;i >= 0;i--){ //逆序 或者 將第一個元素賦值給一個變量,然后用數(shù)組的元素去除以變量
            arr[i] = arr[i] / arr[0];
        }
        //遍歷arr
        for(int i = 0;i < arr.length;i++){
            System.out.print(arr[i] + " ");
        }
    }
}

舉例4:創(chuàng)建一個長度為6的int型數(shù)組,要求數(shù)組元素的值都在1-30之間,且是隨機賦值。同時,要求元素的值各不相同。

public class Test4 {
    // 5-67 Math.random() * 63 + 5;
    @Test
    public void test1() {
        int[] arr = new int[6];
        for (int i = 0; i < arr.length; i++) {// [0,1) [0,30) [1,31)
            arr[i] = (int) (Math.random() * 30) + 1;

            boolean flag = false;
            while (true) {
                for (int j = 0; j < i; j++) {
                    if (arr[i] == arr[j]) {
                        flag = true;
                        break;
                    }
                }
                if (flag) {
                    arr[i] = (int) (Math.random() * 30) + 1;
                    flag = false;
                    continue;
                }
                break;
            }
        }

        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
    //更優(yōu)的方法
    @Test
    public void test2(){
        int[] arr = new int[6];
        for (int i = 0; i < arr.length; i++) {// [0,1) [0,30) [1,31)
            arr[i] = (int) (Math.random() * 30) + 1;
            
                for (int j = 0; j < i; j++) {
                    if (arr[i] == arr[j]) {
                        i--;
                        break;
                    }
                }
            }

        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

舉例5:回形數(shù)

從鍵盤輸入一個整數(shù)(1~20) ,則以該數(shù)字為矩陣的大小,把1,2,3…n*n 的數(shù)字按照順時針螺旋的形式填入其中。

例如: 輸入數(shù)字2,則程序輸出:
1 2
4 3

輸入數(shù)字3,則程序輸出:
1 2 3
8 9 4
7 6 5
輸入數(shù)字4, 則程序輸出:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7

//方式1
public class RectangleTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("輸入一個數(shù)字");
        int len = scanner.nextInt();
        int[][] arr = new int[len][len];
        
        int s = len * len;
        /*
         * k = 1:向右
         * k = 2:向下
         * k = 3:向左
         * k = 4:向上
         */
        int k = 1;
        int i = 0,j = 0;
        for(int m = 1;m <= s;m++){
            if(k == 1){
                if(j < len && arr[i][j] == 0){
                    arr[i][j++] = m;
                }else{
                    k = 2;
                    i++;  
                    j--;
                    m--;
                }
            }else if(k == 2){
                if(i < len && arr[i][j] == 0){
                    arr[i++][j] = m;
                }else{
                    k = 3;
                    i--;
                    j--;
                    m--;
                }
            }else if(k == 3){
                if(j >= 0 && arr[i][j] == 0){
                    arr[i][j--] = m;
                }else{
                    k = 4;
                    i--;
                    j++;
                    m--;
                }
            }else if(k == 4){
                if(i >= 0 && arr[i][j] == 0){
                    arr[i--][j] = m;
                }else{
                    k = 1;
                    i++;
                    j++;
                    m--;
                }
            }
        }
        
        //遍歷
        for(int m = 0;m < arr.length;m++){
            for(int n = 0;n < arr[m].length;n++){
                System.out.print(arr[m][n] + "\t");
            }
            System.out.println();
        }
    }
}
//方式2
/*
    01 02 03 04 05 06 07 
    24 25 26 27 28 29 08 
    23 40 41 42 43 30 09 
    22 39 48 49 44 31 10 
    21 38 47 46 45 32 11 
    20 37 36 35 34 33 12 
    19 18 17 16 15 14 13 
 */
public class RectangleTest1 {

    public static void main(String[] args) {
        int n = 7;
        int[][] arr = new int[n][n];
        
        int count = 0; //要顯示的數(shù)據(jù)
        int maxX = n-1; //x軸的最大下標
        int maxY = n-1; //Y軸的最大下標
        int minX = 0; //x軸的最小下標
        int minY = 0; //Y軸的最小下標
        while(minX<=maxX) {
            for(int x=minX;x<=maxX;x++) {
                arr[minY][x] = ++count;
            }
            minY++;
            for(int y=minY;y<=maxY;y++) {
                arr[y][maxX] = ++count;
            }
            maxX--;
            for(int x=maxX;x>=minX;x--) {
                arr[maxY][x] = ++count;
            }
            maxY--;
            for(int y=maxY;y>=minY;y--) {
                arr[y][minX] = ++count;
            }
            minX++;
        }
        
        
        for(int i=0;i<arr.length;i++) {
            for(int j=0;j<arr.length;j++) {
                String space = (arr[i][j]+"").length()==1 ? "0":"";
                System.out.print(space+arr[i][j]+" ");
            }
            System.out.println();
        }
    }
}

6.3 數(shù)組的元素查找

1、順序查找

順序查找:挨個查看

要求:對數(shù)組元素的順序沒要求

public class TestArrayOrderSearch {
    //查找value第一次在數(shù)組中出現(xiàn)的index
    public static void main(String[] args){
        int[] arr = {4,5,6,1,9};
        int value = 1;
        int index = -1;

        for(int i=0; i<arr.length; i++){
            if(arr[i] == value){
                index = i;
                break;
            }
        }

        if(index==-1){
            System.out.println(value + "不存在");
        }else{
            System.out.println(value + "的下標是" + index);
        }
    }
}

2、二分查找

//二分法查找:要求此數(shù)組必須是有序的。
int[] arr3 = new int[]{-99,-54,-2,0,2,33,43,256,999};
boolean isFlag = true;
int number = 256;
//int number = 25;
int head = 0;//首索引位置
int end = arr3.length - 1;//尾索引位置
while(head <= end){
    int middle = (head + end) / 2;
    if(arr3[middle] == number){
        System.out.println("找到指定的元素,索引為:" + middle);
        isFlag = false;
        break;
    }else if(arr3[middle] > number){
        end = middle - 1;
    }else{//arr3[middle] < number
        head = middle + 1;
    }
}

if(isFlag){
    System.out.println("未找打指定的元素");
}

6.4 數(shù)組元素的反轉(zhuǎn)

實現(xiàn)思想:數(shù)組對稱位置的元素互換。

public class TestArrayReverse1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        System.out.println("反轉(zhuǎn)之前:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //反轉(zhuǎn)
         /*
        思路:首尾對應(yīng)位置的元素交換
        (1)確定交換幾次
           次數(shù) = 數(shù)組.length / 2
        (2)誰和誰交換
        for(int i=0; i<次數(shù); i++){
             int temp = arr[i];
             arr[i] = arr[arr.length-1-i];
             arr[arr.length-1-i] = temp;
        }
         */
        for(int i=0; i<arr.length/2; i++){
            int temp = arr[i];
            arr[i] = arr[arr.length-1-i];
            arr[arr.length-1-i] = temp;
        }

        System.out.println("反轉(zhuǎn)之后:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

}

public class TestArrayReverse2 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        System.out.println("反轉(zhuǎn)之前:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //反轉(zhuǎn)
        //左右對稱位置交換
        for(int left=0,right=arr.length-1; left<right; left++,right--){
            //首  與  尾交換
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
        }

        System.out.println("反轉(zhuǎn)之后:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

6.5 數(shù)組元素排序

6.5.1 算法概述

  • 定義
    • 排序:假設(shè)含有n個記錄的序列為{R1,R2,...,Rn},其相應(yīng)的關(guān)鍵字序列為{K1,K2,...,Kn}。將這些記錄重新排序為{Ri1,Ri2,...,Rin},使得相應(yīng)的關(guān)鍵字值滿足條Ki1<=Ki2<=...<=Kin,這樣的一種操作稱為排序。
    • 通常來說,排序的目的是快速查找。
  • 算法的5大特征:

說明:滿足確定性的算法也稱為:確定性算法。現(xiàn)在人們也關(guān)注更廣泛的概念,例如考慮各種非確定性的算法,如并行算法、概率算法等。另外,人們也關(guān)注并不要求終止的計算描述,這種描述有時被稱為過程(procedure)。

  • 衡量排序算法的優(yōu)劣:

    • 時間復(fù)雜度:分析關(guān)鍵字的比較次數(shù)和記錄的移動次數(shù)

      • 常見的算法時間復(fù)雜度由小到大依次為:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)
    一個算法執(zhí)行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但我們不可能也沒有必要對每個算法都上機測試,只需知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。
    
    并且一個算法花費的時間與算法中語句的執(zhí)行次數(shù)成正比例,哪個算法中語句執(zhí)行次數(shù)多,它花費時間就多。一個算法中的語句執(zhí)行次數(shù)稱為語句頻度或時間頻度。記為T(n)。n稱為問題的規(guī)模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時我們想知道它變化時呈現(xiàn)什么規(guī)律。為此,我們引入時間復(fù)雜度概念。 
    
    一般情況下,算法中基本操作重復(fù)執(zhí)行的次數(shù)是問題規(guī)模n的某個函數(shù),用T(n)表示,若有某個輔助函數(shù)f(n),使得當n趨近于無窮大時,T(n)/f(n)的極限值為不等于零的常數(shù),則稱f(n)是T(n)的同數(shù)量級函數(shù)。記作T(n)=O(f(n)),稱O(f(n)) 為算法的漸進時間復(fù)雜度,簡稱時間復(fù)雜度。
  • 空間復(fù)雜度:分析排序算法中需要多少輔助內(nèi)存

    類似于時間復(fù)雜度的討論,一個算法的空間復(fù)雜度(Space Complexity)S(n)定義為該算法所耗費的存儲空間,它也是問題規(guī)模n的函數(shù)。
    
  • 穩(wěn)定性:若兩個記錄A和B的關(guān)鍵字值相等,但排序后A、B的先后次序保持不變,則稱這種排序算法是穩(wěn)定的。

6.5.2 排序算法概述

  • 排序算法分類:內(nèi)部排序和外部排序

    • 內(nèi)部排序:整個排序過程不需要借助于外部存儲器(如磁盤等),所有排序操作都在內(nèi)存中完成。
    • 外部排序:參與排序的數(shù)據(jù)非常多,數(shù)據(jù)量非常大,計算機無法把整個排序過程放在內(nèi)存中完成,必須借助于外部存儲器(如磁盤)。外部排序最常見的是多路歸并排序。可以認為外部排序是由多次內(nèi)部排序組成。
  • 十大內(nèi)部排序算法

數(shù)組的排序算法很多,實現(xiàn)方式各不相同,時間復(fù)雜度、空間復(fù)雜度、穩(wěn)定性也各不相同:

6.5.3 冒泡排序

介紹:

Java中的經(jīng)典算法之冒泡排序(Bubble Sort)。冒泡排序的原理非常簡單,它重復(fù)地走訪過要排序的數(shù)列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。

排序思想:

  1. 比較相鄰的元素。如果第一個比第二個大(升序),就交換他們兩個。

  2. 對每一對相鄰元素作同樣的工作,從開始第一對到結(jié)尾的最后一對。這步做完后,最后的元素會是最大的數(shù)。

  3. 針對所有的元素重復(fù)以上的步驟,除了最后一個。

  4. 持續(xù)每次對越來越少的元素重復(fù)上面的步驟,直到?jīng)]有任何一對數(shù)字需要比較為止。

動態(tài)演示:https://visualgo.net/zh/sorting

/*
1、冒泡排序(最經(jīng)典)
思想:每一次比較“相鄰(位置相鄰)”元素,如果它們不符合目標順序(例如:從小到大),
     就交換它們,經(jīng)過多輪比較,最終實現(xiàn)排序。
     (例如:從小到大)   每一輪可以把最大的沉底,或最小的冒頂。
     
過程:arr{6,9,2,9,1}  目標:從小到大

第一輪:
    第1次,arr[0]與arr[1],6>9不成立,滿足目標要求,不交換
    第2次,arr[1]與arr[2],9>2成立,不滿足目標要求,交換arr[1]與arr[2] {6,2,9,9,1}
    第3次,arr[2]與arr[3],9>9不成立,滿足目標要求,不交換
    第4次,arr[3]與arr[4],9>1成立,不滿足目標要求,交換arr[3]與arr[4] {6,2,9,1,9}
    第一輪所有元素{6,9,2,9,1}已經(jīng)都參與了比較,結(jié)束。
    第一輪的結(jié)果:第“一”最大值9沉底(本次是后面的9沉底),即到{6,2,9,1,9}元素的最右邊

第二輪:
    第1次,arr[0]與arr[1],6>2成立,不滿足目標要求,交換arr[0]與arr[1] {2,6,9,1,9}
    第2次,arr[1]與arr[2],6>9不成立,滿足目標要求,不交換
    第3次:arr[2]與arr[3],9>1成立,不滿足目標要求,交換arr[2]與arr[3] {2,6,1,9,9}
    第二輪未排序的所有元素 {6,2,9,1}已經(jīng)都參與了比較,結(jié)束。
    第二輪的結(jié)果:第“二”最大值9沉底(本次是前面的9沉底),即到{2,6,1,9}元素的最右邊
第三輪:
    第1次,arr[0]與arr[1],2>6不成立,滿足目標要求,不交換
    第2次,arr[1]與arr[2],6>1成立,不滿足目標要求,交換arr[1]與arr[2] {2,1,6,9,9}
    第三輪未排序的所有元素{2,6,1}已經(jīng)都參與了比較,結(jié)束。
    第三輪的結(jié)果:第三最大值6沉底,即到 {2,1,6}元素的最右邊
第四輪:
    第1次,arr[0]與arr[1],2>1成立,不滿足目標要求,交換arr[0]與arr[1] {1,2,6,9,9}
    第四輪未排序的所有元素{2,1}已經(jīng)都參與了比較,結(jié)束。
    第四輪的結(jié)果:第四最大值2沉底,即到{1,2}元素的最右邊

*/
public class Test19BubbleSort{
    public static void main(String[] args){
        int[] arr = {6,9,2,9,1};

        //目標:從小到大
        //冒泡排序的輪數(shù) = 元素的總個數(shù) - 1
        //輪數(shù)是多輪,每一輪比較的次數(shù)是多次,需要用到雙重循環(huán),即循環(huán)嵌套
        //外循環(huán)控制 輪數(shù),內(nèi)循環(huán)控制每一輪的比較次數(shù)和過程
        for(int i=1; i<arr.length; i++){ //循環(huán)次數(shù)是arr.length-1次/輪
            /*
            假設(shè)arr.length=5
            i=1,第1輪,比較4次
                arr[0]與arr[1]
                arr[1]與arr[2]
                arr[2]與arr[3]
                arr[3]與arr[4]
                
                arr[j]與arr[j+1],int j=0;j<4; j++
                
            i=2,第2輪,比較3次
                arr[0]與arr[1]
                arr[1]與arr[2]
                arr[2]與arr[3]
                
                arr[j]與arr[j+1],int j=0;j<3; j++
                
            i=3,第3輪,比較2次
                arr[0]與arr[1]
                arr[1]與arr[2]
                
                arr[j]與arr[j+1],int j=0;j<2; j++
            i=4,第4輪,比較1次
                arr[0]與arr[1]
            
                arr[j]與arr[j+1],int j=0;j<1; j++
                
                int j=0; j<arr.length-i; j++
            */
            for(int j=0; j<arr.length-i; j++){
                //希望的是arr[j] < arr[j+1]
                if(arr[j] > arr[j+1]){
                    //交換arr[j]與arr[j+1]
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }

        //完成排序,遍歷結(jié)果
        for(int i=0; i<arr.length; i++){
            System.out.print(arr[i]+"  ");
        }
    }
}

冒泡排序優(yōu)化

/*
思考:冒泡排序是否可以優(yōu)化
*/
class Test19BubbleSort2{
    public static void main(String[] args){
        int[] arr = {1,3,5,7,9};
        
        //從小到大排序
        //int lun = 0;//聲明lun變量,統(tǒng)計比較幾輪
        //int count = 0;//聲明count變量,統(tǒng)計比較的次數(shù)
        for(int i=1; i<arr.length; i++){ 
            //lun++;
            boolean flag = true;//假設(shè)數(shù)組已經(jīng)是有序的
            for(int j=0; j<arr.length-i; j++){
                //count++;
                //希望的是arr[j] < arr[j+1]
                if(arr[j] > arr[j+1]){
                    //交換arr[j]與arr[j+1]
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    
                    flag = false;//如果元素發(fā)生了交換,那么說明數(shù)組還沒有排好序
                }
            }
            if(flag){
                break;
            }
        }
        
        //System.out.println("一共比較了" + lun +"輪");
        //System.out.println("一共比較了" + count +"次");
        
        //完成排序,遍歷結(jié)果
        for(int i=0; i<arr.length; i++){
            System.out.print(arr[i]+"  ");
        }
    }
}

6.5.4 快速排序

介紹:快速排序通常明顯比同為O(nlogn)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多筆試面試中能經(jīng)常看到快排的影子。可見掌握快排的重要性。

快速排序(Quick Sort)由圖靈獎獲得者Tony Hoare發(fā)明,被列為20世紀十大算法之一,是迄今為止所有內(nèi)排序算法中速度最快的一種。冒泡排序的升級版,交換排序的一種。快速排序的時間復(fù)雜度為O(nlog(n))。

排序思想:

  1. 從數(shù)列中挑出一個元素,稱為"基準"(pivot),

  2. 重新排序數(shù)列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的后面(相同的數(shù)可以到任一邊)。在這個分區(qū)結(jié)束之后,該基準就處于數(shù)列的中間位置。這個稱為分區(qū)(partition)操作。

  3. 遞歸地(recursive)把小于基準值元素的子數(shù)列和大于基準值元素的子數(shù)列排序。

  4. 遞歸的最底部情形,是數(shù)列的大小是零或一,也就是永遠都已經(jīng)被排序好了。雖然一直遞歸下去,但是這個算法總會結(jié)束,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。

動態(tài)演示:https://visualgo.net/zh/sorting

圖示1:

圖示2:

public class quickSort {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-567,70};
        quickSort(arr,0,arr.length-1);
        System.out.println("arr="+ Arrays.toString(arr));
    }
    public static void quickSort(int[] arr,int left,int right){
        int l = left; //左下標
        int r = right; //右下標
        int temp = 0; //臨時變量,交換時使用
        //pivot 中軸值
        int pivot = arr[(left + right)/2];
        //while 循環(huán)的目的,是讓比pivot 值小的放到左邊
        //比 pivot 值放到右邊
        while(l < r){ //
            //在pivot 的左邊一直找,找到大于等于 pivot 值,才退出
            while( arr[l] < pivot){
                l += 1;
            }
            //在pivot 的右邊一直找,找到小于等于 pivot 值,才退出
            while(arr[r] > pivot){
                r -= 1;
            }
            //如果l >= r 說明 pivot 的左右兩邊的值,已經(jīng)按照
            // 左邊全部是小于等于pivot 的值,右邊全部大于等于pivot的值
            if(l >= r){
                break;
            }
            //交換
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交換完后,發(fā)現(xiàn)這個 arr[l] == pivot 值相等 r--, 前移
            if(arr[l] == pivot){
                r -= 1;
            }
            //如果交換完后,發(fā)現(xiàn)這個 arr[r] == pivot 值相等 l++, 后移
            if(arr[l] == pivot){
                l += 1;
            }
        }

        //如果 l == r,必須 l++,r--,否則出現(xiàn)棧溢出
        if(l == r){
            l += 1;
            r -= 1;
        }
        //向左遞歸
        if(left < r){
            quickSort(arr,left,r);
        }
        //向右遞歸
        if(right > l){
            quickSort(arr,l,right);
        }
    }

}

6.5.5 內(nèi)部排序性能比較與選擇

  • 性能比較

    • 從平均時間而言:快速排序最佳。但在最壞情況下時間性能不如堆排序和歸并排序。
    • 從算法簡單性看:由于直接選擇排序、直接插入排序和冒泡排序的算法比較簡單,將其認為是簡單算法。對于Shell排序、堆排序、快速排序和歸并排序算法,其算法比較復(fù)雜,認為是復(fù)雜排序。
    • 從穩(wěn)定性看:直接插入排序、冒泡排序和歸并排序時穩(wěn)定的;而直接選擇排序、快速排序、 Shell排序和堆排序是不穩(wěn)定排序
    • 從待排序的記錄數(shù)n的大小看,n較小時,宜采用簡單排序;而n較大時宜采用改進排序。
  • 選擇

    • 若n較小(如n≤50),可采用直接插入或直接選擇排序。
      當記錄規(guī)模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數(shù)少于直接插入,應(yīng)選直接選擇排序為宜。
    • 若文件初始狀態(tài)基本有序(指正序),則應(yīng)選用直接插入、冒泡或隨機的快速排序為宜;
    • 若n較大,則應(yīng)采用時間復(fù)雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。

5. Arrays工具類的使用

java.util.Arrays類即為操作數(shù)組的工具類,包含了用來操作數(shù)組(比如排序和搜索)的各種方法。

舉例:java.util.Arrays類的sort()方法提供了數(shù)組元素排序功能:

import java.util.Arrays;
public class SortTest {
    public static void main(String[] args) {
        int [] numbers = {5,900,1,5,77,30,64,700};
        Arrays.sort(numbers);
        for(int i = 0; i < numbers.length; i++){
            System.out.println(numbers[i]);
        }
    }
}

6. 數(shù)組中的常見異常

6.1 數(shù)組角標越界異常

當訪問數(shù)組元素時,下標指定超出[0, 數(shù)組名.length-1]的范圍時,就會報數(shù)組下標越界異常:ArrayIndexOutOfBoundsException。

public class TestArrayIndexOutOfBoundsException {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
       // System.out.println("最后一個元素:" + arr[3]);//錯誤,下標越界
      //  System.out.println("最后一個元素:" + arr[arr.length]);//錯誤,下標越界
        System.out.println("最后一個元素:" + arr[arr.length-1]);//對
    }
}

創(chuàng)建數(shù)組,賦值3個元素,數(shù)組的索引就是0,1,2,沒有3索引,因此我們不能訪問數(shù)組中不存在的索引,程序運行后,將會拋出 ArrayIndexOutOfBoundsException 數(shù)組越界異常。在開發(fā)中,數(shù)組的越界異常是不能出現(xiàn)的,一旦出現(xiàn)了,就必須要修改我們編寫的代碼。

6.2 空指針異常

觀察一下代碼,運行后會出現(xiàn)什么結(jié)果。

public class TestNullPointerException {
    public static void main(String[] args) {
        //定義數(shù)組
        int[][] arr = new int[3][];

        System.out.println(arr[0][0]);//NullPointerException
    }
}

因為此時數(shù)組的每一行還未分配具體存儲元素的空間,此時arr[0]是null,此時訪問arr[0][0]會拋出NullPointerException 空指針異常。

空指針異常在內(nèi)存圖中的表現(xiàn)

小結(jié):空指針異常情況

        //舉例一:
//      int[] arr1 = new int[10];
//      arr1 = null;
//      System.out.println(arr1[9]);
        
        //舉例二:
//      int[][] arr2 = new int[5][];
//      //arr2[3] = new int[10];
//      System.out.println(arr2[3][3]);
        
        //舉例三:
        String[] arr3 = new String[10];
        System.out.println(arr3[2].toString());
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容