學(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 個元素, 第 n 行有 n 個元素
每一行的第一個元素和最后一個元素都是 1
-
從第三行開始, 對于非第一個元素和最后一個元素的元素。即:
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ù)列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。
排序思想:
比較相鄰的元素。如果第一個比第二個大(升序),就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結(jié)尾的最后一對。這步做完后,最后的元素會是最大的數(shù)。
針對所有的元素重復(fù)以上的步驟,除了最后一個。
持續(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))。
排序思想:
從數(shù)列中挑出一個元素,稱為"基準"(pivot),
重新排序數(shù)列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的后面(相同的數(shù)可以到任一邊)。在這個分區(qū)結(jié)束之后,該基準就處于數(shù)列的中間位置。這個稱為分區(qū)(partition)操作。
遞歸地(recursive)把小于基準值元素的子數(shù)列和大于基準值元素的子數(shù)列排序。
遞歸的最底部情形,是數(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)的排序方法:快速排序、堆排序或歸并排序。
- 若n較小(如n≤50),可采用直接插入或直接選擇排序。
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());