一:數組的概述
什麼是數組?
- 數組(Array),是多個相同類型資料按一定順序排列的集合,并使用一個名字命名,并通過編号的方式對這些資料進行統一管理。
數組常見概念:數組名,下标(或索引),元素,數組的長度(元素的個數)。
數組的特點:
- 建立數組對象會在記憶體中開辟一整塊連續的空間(在堆空間中),而數組名中引用的是這塊連續空間的首位址。
- 有序排列的(下标是有序的),
- 使用下标通路指定位置上的元素。(數組的角标(或索引)是從0開始的,到 數組的長度-1 結束)。
- 數組的長度一旦确定,就不能修改。
- 數組本身是引用資料類型,而數組中的元素可以是任何資料類型。
- 在資料結構中屬于順序表結構。
數組的分類:
- 按照次元:一維數組、二維數組、三維數組 …
- 按元素的資料類型分:元素為基本資料類型的數組、元素為引用資料類型的數組(即對象數組)
數組是一個類嗎?
- 數組是有對應的類,這個類是在JVM運作時建立的,是以沒有對應的class 檔案。
- 數組的類名是:
開頭的,和普通類的不一樣。[
- 數組類中不包含任何成員和變量(可以通過getClass拿到 Class 對象來檢視),數組的長度length是通過JVM的指令 arraylength 直接得到的。
- 數組的類和一般類在JVM中是區分對待的,JVM會對數組類做一些特殊的操作,比如數組類的對象建立是通過JVM指令直接執行的,比如
建立一個數組對象,newarray
建立多個數組對象。multianewarray
- 數組類并不是隻有一個類,而是會有很多個。數組類的類型是由數組的内容和次元同時決定的。比如:int[] 的類名是:
;int[][] 的類名是:[I
(其中的[[I
是 int 類型的在虛拟機指令中資料類型)。這是兩個不同的類。I
二:數組的使用
一維數組的使用
- 建立數組:建立數組對象,賦給一個數組變量。
//數組類型變量的聲明:資料類型[] int[] nums;//常用 int nums[]; //效果一樣:變量名都是nums //初始化:也叫建立數組對象 new //靜态初始化:數組的初始化和數組元素的指派操作同時進行 nums = new int[]{1001,1002,1003,1004}; //自定義初始化資料,[]裡不能有值,長度根據{}裡元素的個數。 int[] arr = {1,2,3}; //類型推斷 int[] a; a = {1,3,2};//編譯不通過。類型推斷不能這樣用,類型推斷必須和聲明一起用。 //動态初始化:數組的初始化和數組元素的指派操作分開進行 String[] names = new String[5];//有預設的初始化值。[]裡的數一定要給的,表示長度,不然編譯不通過。 /* 總結:數組一旦初始化完成後,其長度就确定了。 */
- 數組元素的預設初始化值:這是Java對于各個資料類型預設值的規範。Java語言中其他地方也這麼用。
整型數組:0 浮點型:0.0 字元型:0 或 '\u0000' ,而非'0' 布爾型:false 引用資料類型:null
- 調用數組中的元素:根據索引取值,也可以根據索引指派。
String[] names = new String[5]; //通過角标的方式調用 //數組的角标(或索引)是從0開始的,到 數組的長度-1 結束 String name = name[0];//取值 names[0] = “你好”;//指派 names[6] = "n";//超出數組的長度,編譯通過,但運作會報數組角标越界異常。
- 擷取數組的長度:數組的屬性:length
int[] names = new int[]{1001,1002,1003,1004}; //屬性:length int l = names.length;
- 周遊數組:
int[] names = new int[]{1001,1002,1003,1004}; for(int i = 0; i < names.length; i++) { System.out.println(names[i]); }
- 數組的記憶體解析 數組中的資料最終都是(指向)方法區中的。
多元數組的使用
說明:
- Java 語言裡提供了支援多元數組的文法
- 從數組底層的運作機制來看,其實沒有多元數組
- 以二維數組為例:對于二維數組的了解,可以看成是一維數組 array1 又作為另一個一維數組 array2 的元素而存在。多元數組就是以這種方式衍生出來。其實,從數組底層的運作機制來看,其實沒有多元數組。
使用(以二維數組為例):
- 聲明和初始化的方式。(原理和一維數組無異,是由一維數組衍生出來的)
//聲明 int[][] a1; int a2[][]; int[] a3[]; //初始化 //靜态初始化 int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; int[][] arr5 = {{1,2,3},{4,5},{6,7,8}};//類型推斷,注意類型推斷須和聲明一起使用。 //動态初始化1 String[][] arr2 = new String[3][2];//使用此方式時各個次元的長度都已确定。 //動态初始化2 String[][] arr3 = new String[3][];//這裡第二個[]不用指定長度是因為其作為數組中的元素有預設值null //String[][] arr3 = new String[][];//是錯的,編譯不通過
- 數組的預設初始化值
//規定,二維數組分外層元素和内層元素 //外層元素:arr[0],arr[1]等 //内層元素:arr[0][0],arr[1][2]等 針對初始化方式一:如:int[][] arr = new int[4][3]; 》外層元素:位址值 》内層元素:和一維數組一樣 針對初始化方式二:如:int[][] arr = new int[4][]; 》外層元素:null 》内層元素:不能調用,否則空指針異常
int[][] arr = new int[4][3]; System.out.println(arr[0]);//[[email protected] (位址值:指向堆空間的數組對象) System.out.println(arr[0][0]);//0 (數組的預設初始化值) System.out.println(arr);//[[[email protected] (位址值)
- 數組的預設初始化值
- 調用數組的指定位置的元素,根據索引取值,也可以根據索引指派。
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; String[][] arr2 = new String[3][2]; String[][] arr3 = new String[3][]; //取值 arr1[0][1]; //2 arr2[1][1]; //null,前面講過的預設初始化值 arr3[1][0]; //空指針,arr3[1]是null,null的[0]:null沒有索引,是以空指針。 //将其聲明為一個數組類型,就不會報空指針: //arr3[1] = new String[4];//指派 //arr3[1][0]; //null
- 擷取數組長度
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; //使用數組的屬性:length arr1.length; //3 arr1[1].length; //2
- 周遊
for(int i = 0; i < arr4.length; i++) { for(int j = 0; j < arr4[i].length; j++) { System.out.pritnln(arr4[i][j]); } }
- 數組的記憶體解析
例題
1、從鍵盤讀入學生成績,找出最高分,并輸出學生成績等級。
成績>=最高分-10 等級為’A’
成績>=最高分-20 等級為’B’
成績>=最高分-30 等級為’C’
其餘 等級為’D’
提示:先讀入學生人數,根據人數建立int數組,存放學生成績
import java.util.Scanner; public class Test { public static void main(String[] args) throws Exception { //1.使用Scanner,讀取學生個數 Scanner scanner = new Scanner(System.in); System.out.print("請輸入學生人數:"); int number = scanner.nextInt(); //2.建立數組,存儲學生成績:動态初始化 int[] scores = new int[number]; //3.給數組中的元素指派 //4.擷取數組中的元素的最大值:最高分 int maxScore = 0; System.out.println("請輸入" + number + "個學生成績:"); for (int i = 0; i < scores.length; i++) { scores[i] = scanner.nextInt(); if (maxScore < scores[i]) maxScore = scores[i]; } System.out.println("學生成績的最高分是:" + maxScore); //5.根據每個學生成績與最高分的內插補點,得到每個學生的等級,并輸出等級和成績 char level; for (int i = 0; i < scores.length; i++) { if (maxScore - scores[i] <= 10) { level = 'A'; } else if (maxScore - scores[i] <= 20) { level = 'B'; } else if (maxScore - scores[i] <= 30) { level = 'C'; } else { level = 'D'; } System.out.println("student" + i + "'s score is" + scores[i] + "and grade is " + level); } //關閉資源 scanner.close(); } }
//運作結果 請輸入學生人數:5 請輸入5個學生成績: 62 30 92 85 78 學生成績的最高分是:92 student0's score is62and grade is C student1's score is30and grade is D student2's score is92and grade is A student3's score is85and grade is A student4's score is78and grade is B
2、聲明:int[] x,y[]; 在給x,y變量指派以後,以下選項允許通過編譯的是:
**a ) x[0] = y; ** no
**b) y[0] = x; **yes
**c) y[0][0] = x; **no。了解:y[0][0]是int型的,x是int[]型的
**d) x[0][0] = y; **no
**e) y[0][0] = x[0]; **yes
**f) x = y; **no
提示:
一維數組:int[] x 或者int x[]
二維數組:int[][] y 或者 int[] y[] 或者 int y[][]
三:數組涉及的常見算法
- 數組元素的指派(楊輝三角、回形數等)
- 求數值型數組中元素的最大值、最小值、平均數、總和等
- 數組的複制、反轉、查找(線性查找、二分法查找)
- 複制
int[] array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; //實作複制 int[] array3 = new int[array1.length]; for (int i = 0; i < array3.length; i++) { array3[i] = array1[i]; }
- 反轉
String[] arr = new String[]{"jj", "aa", "bb", "cc"}; //數組的反轉 for (int i = 0; i < arr.length / 2; i++) { String temp = arr[i]; arr[i] = arr[arr.length - i - 1]; arr[arr.length - i - 1] = temp; }
- 查找
- 線性查找:就簡單的,同時效率也是最差的。
String[] arr = new String[]{"jj", "aa", "bb", "cc"}; //線性查找 String test = "bb"; for (int i = 0; i < arr.length / 2; i++) { if (test.equals(arr[i])) { System.out.println("找到了"); break; } }
- 常用的有:二分法查找等
- 線性查找:就簡單的,同時效率也是最差的。
- 複制
- 數組元素的排序算法
例題
1、使用二維數組列印一個 10 行楊輝三角。
【提示】
1. 第一行有 1 個元素, 第 n 行有 n 個元素
2. 每一行的第一個元素和最後一個元素都是 1
3. 從第三行開始, 對于非第一個元素和最後一個元
素的元素。即:
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-IApjrAf4-1625911129420)(C:\Users\17812\Desktop\筆記(work)]\typora_images\image-20210331223910731.png)
import java.util.Scanner; public class Test { public static void main(String[] args) throws Exception { //1.聲明并初始化二維數組 int[][] yangHui = new int[10][]; //2.給數組的元素指派,并輸出 for (int i = 0; i < yangHui.length; i++) { yangHui[i] = new int[i + 1]; //給首末元素指派 yangHui[i][0] = yangHui[i][i] = 1; //給每行的非首末元素指派 for (int j = 1; j < yangHui[i].length - 1; j++) { yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j]; } //輸出 for (int a = 0; a < yangHui[i].length; a++) { System.out.print(yangHui[i][a] + "\t"); } System.out.println(); } } }
2、定義一個int型的一維數組,包含10個元素,分别賦一些随機整數,
然後求出所有元素的最大值,最小值,和值,平均值,并輸出出來。
要求:所有随機數都是兩位數。
提示;
[0,1) * 90 -> [0,90) -> 10 -> [10,100) -> [10,99]
(int)(Math.random() * 90 + 10)
import java.math.*; public class Test { public static void main(String[] args) throws Exception { int[] arr = new int[10]; //最大、最小值 int maxValue = 0; int minValue = 0; //總和 int sum = 0; //平均數 double avgValue; for (int i = 0; i < arr.length; i++) { arr[i] = (int) (Math.random() * 90 + 10); //求數組元素的最大值、最小值 if (i == 0) { maxValue = arr[0];//更完美,int maxValue = 0; => 不适合有全為負數的情況 minValue = arr[0]; } else { if (maxValue < arr[i]) maxValue = arr[i]; if (minValue > arr[i]) minValue = arr[i]; } sum += arr[i];//求總和 System.out.print(arr[i] + "\t"); } System.out.println(); avgValue = (double) sum / arr.length;//求平均數 System.out.println("最大值為:" + maxValue); System.out.println("最小值為:" + minValue); System.out.println("總和為:" + sum); System.out.println("平均數為:" + avgValue); } }
3、使用簡單數組
(1)建立一個名為ArrayTest的類,在main()方法中聲明array1和array2兩個變量,
他們是int[]類型的數組。
(2)使用大括号{},把array1初始化為8個素數:2,3,5,7,11,13,17,19。
(3)顯示array1的内容。
(4)指派array2變量等于array1,修改array2中的偶索引元素,使其等于索引值
(如array[0]=0,array[2]=2)。列印出array1。
思考:array1和array2是什麼關系?
拓展:修改題目,實作array2對array1數組的複制
import java.math.*; public class Test { public static void main(String[] args) throws Exception { int[] array1, array2, array3; array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); array2 = array1; for (int i = 0; i < array2.length; i++) { if (i % 2 == 0) array2[i] = i; } for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); /** * 結果: * 2 3 5 7 11 13 17 19 * 0 3 2 7 4 13 6 19 * * 思考: * 》array1和array2的位址相同,指向堆空間中同一個數組實體。這不能稱作數組的複制。 */ array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); array2[i] = array1[i]; } System.out.println(); //實作複制 array3 = new int[array1.length]; for (int i = 0; i < array3.length; i++) { array3[i] = array1[i]; } for (int i = 0; i < array3.length; i++) { if (i % 2 == 0) array3[i] = i; } for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); /** * 結果: * 2 3 5 7 11 13 17 19 2 3 5 7 11 13 17 19 思考: 》此時,修改array3并不會改變array1的值,兩者互不影響,在記憶體中各有獨立的空間。實作了數組的複制。 */ } }
算法了解
算法的5大特征:
輸入( Input) | 有0個或多個輸入資料,這些輸入必須有清楚的描述和定義 |
---|---|
輸出(Output) | 至少有1個或多個輸出結果,不可以沒有輸出結果 |
有窮性(有限性,Finiteness) | 算法在有限的步驟之後會自動結束而不會無限循環,并且每一個步驟可以在可接受的時間内完成 |
确定性(明确性,Definiteness) | 算法中的每一步都有确定的含義,不會出現二義性 |
可行性(有效性,Effectiveness) | 算法的每一步都是清楚且可行的,能讓使用者用紙筆計算而求出答案 |
說明:滿足确定性的算法也稱為:确定性算法。現在人們也關注更廣泛的概念,例如考慮各種非确定性的算法,如并行算法、機率算法等。另外,人們也關注并不要求終止的計算描述,這種描述有時被稱為過程(procedure)。
查找算法
二分法查找
前提:所要查找的數組必須有序
思路:
算法思想為:從數組中間元素開始,如果中間元素正好是要查找的元素,則搜素過程結束;如果某一特定元素大于或者小于中間元素,則在數組大于或小于中間元素的那一半中查找,而且跟開始一樣從中間元素開始比較。如果在某一步驟數組已經為空,則表示找不到指定的元素。二分法查找每次是使查找範圍縮小一半,其時間複雜度是O(log(n))
int[] arr = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int dest = -34;//想找的元素
int head = 0;//初始的首索引
int end = arr.length -1; //初始的末索引
boolean isFlag =true;
while(head <= end) {
int middle = (head + end)/2;
if(dest == arr[middle]) {
System.out.println("找到了指定元素,位置為:"+ middle);
isFlag = false;
break;
} else if(arr[middle] > dest) {
end = middle - 1;
} else {
head = middle + 1;
}
}
if(isFlag) {
System.out.println("很抱歉,沒有找到")
}
排序算法
目的是快速查找
總結使用排序算法的優缺點:
- 優點:
- 性能上的優越。(運作速度快)
- 缺點:
- 有些會存在排序的不穩定。
- 需要大量輔助空間。
- 不适于少量資料排序。
衡量一個排序算法的優劣的要素:
- 時間複雜度:分析關鍵字的比較次數和記錄的移動次數
- 空間複雜度:分析排序算法中需要多少輔助記憶體
- 穩定性:若兩個記錄A和B的關鍵字值相等,但排序後A、B的先後次序保持不變,則稱這種排序算法是穩定的。
排序算法分類:内部排序和外部排序。
- 内部排序:整個排序過程不需要借助于外部存儲器(如磁盤等),所有排序操作都在記憶體中完成。
- 外部排序:參與排序的資料非常多,資料量非常大,計算機無法把整個排序過程放在記憶體中完成,必須借助于外部存儲器(如磁盤)(内部排序完,存儲在外部裝置中,為記憶體騰出排序空間)。外部排序最常見的是多路歸并排序。可以認為外部排序是由多次内部排序組成。
十大内部排序算法:
- 選擇排序
- 直接選擇排序、堆排序
- 交換排序
- 冒泡排序、快速排序
- 插入排序
- 直接插入排序、折半插入排序、Shell排序(希爾排序)
- 歸并排序
- 桶式排序
- 基數排序
各種内部排序方法性能比較
- 從平均時間而言:快速排序最佳。但在最壞情況下時間性能不如堆排序和歸并排序。
- 從算法簡單性看:由于直接選擇排序、直接插入排序和冒泡排序的算法比較簡單,将其認為是簡單算法。對于Shell排序、堆排序、快速排序和歸并排序算法,其算法比較複雜,認為是複雜排序。
- 從穩定性看:直接插入排序、冒泡排序和歸并排序時穩定的;而直接選擇排序、快速排序、 Shell排序和堆排序是不穩定排序
- 從待排序的記錄數n的大小看,n較小時,宜采用簡單排序;而n較大時宜采用改進排序。
性能對比:
排序算法的選擇(n:待排序的記錄數)
- 若n較小(如n≤50),可采用直接插入或直接選擇排序。
- 當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少于直接插入,應選直接選擇排序為宜。
- 若檔案初始狀态基本有序(指正序),則應選用直接插入、冒泡或随機的快速排序為宜;
- 若n較大,則應采用時間複雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。
冒泡排序
- 介紹:
- 冒泡排序的原理非常簡單,它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。
- 排序思想:
- 比較相鄰的元素。如果第一個比第二個大(升序),就交換他們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較為止。
實作:
public class Test {
public static void main(String[] args) throws Exception {
int[] arr = new int[]{43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
//冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
//周遊輸出
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
快速排序
- 介紹:
- 快速排序通常明顯比同為O(nlogn)的其他算法更快,是以常被采用,而且快排采用了分治法的思想,是以在很多筆試面試中能經常看到快排的影子。可見掌握快排的重要性。
- 快速排序(Quick Sort)由圖靈獎獲得者Tony Hoare發明,被列為20世紀十大算法之一,是迄今為止所有内排序算法中速度最快的一種。冒泡排序的更新版,交換排序的一種。快速排序的時間複雜度為O(nlog(n))
- 排序思想:
- 從數列中挑出一個元素,稱為"基準"(pivot),
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處于數列的中間位置。這個稱為分區(partition)操作。
- 遞歸地(recursive)把小于基準值元素的子數列和大于基準值元素的子數列排序。
- 遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,但是這個算法總會結束,因為在每次的疊代(iteration)中,它至少會把一個元素擺到它最後的位置去。
- 示範:
實作:(不考慮穩定性的,注意:好的排序算法是要考慮穩定性的)
package com.atguigu.array.sort;
/**
* 快速排序
* 通過一趟排序将待排序記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分關鍵字小,
* 則分别對這兩部分繼續進行排序,直到整個序列有序。
* @author shkstart
* 2018-12-17
*/
public class QuickSort {
private static void swap(int[] data, int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
private static void subSort(int[] data, int start, int end) {
if (start < end) {
int base = data[start];
int low = start;
int high = end + 1;
while (true) {
while (low < end && data[++low] - base <= 0)
;
while (high > start && data[--high] - base >= 0)
;
if (low < high) {
swap(data, low, high);
} else {
break;
}
}
swap(data, start, high);
subSort(data, start, high - 1);//遞歸調用
subSort(data, high + 1, end);
}
}
public static void quickSort(int[] data){
subSort(data,0,data.length-1);
}
public static void main(String[] args) {
int[] data = { 9, -16, 30, 23, -30, -49, 25, 21, 30 };
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
quickSort(data);
System.out.println("排序之後:\n" + java.util.Arrays.toString(data));
}
}
四:Arrays 工具類
- 介紹:
類即為操作數組的工具類java.util.Arrays
- 包含了用來操作數組(比如排序和搜尋)的各種方法。 如:
- 有許多重載方法用于不同資料類型的使用,結構都一樣,下面隻展示一種。
- 下面展示的源碼都是jdk1.8的
- equals(…):
//源碼 public static boolean equals(int[] a, int[] a2) { if (a==a2) return true; if (a==null || a2==null) return false; int length = a.length; if (a2.length != length) return false; for (int i=0; i<length; i++) if (a[i] != a2[i]) return false; return true; }
- toString(…):
//源碼 public static String toString(int[] a) { if (a == null) return "null"; int iMax = a.length - 1; if (iMax == -1) return "[]"; StringBuilder b = new StringBuilder(); b.append('['); for (int i = 0; ; i++) { b.append(a[i]); if (i == iMax) return b.append(']').toString(); b.append(", "); } }
- fill(…):
//源碼 public static void fill(long[] a, long val) { for (int i = 0, len = a.length; i < len; i++) a[i] = val; }
- sort(…):
//源碼 public static void sort(int[] a) { DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); /** DualPivotQuicksort:雙基點快排。Java将其封裝成一個類供使用。 結論: 》底層用的是快排。 */ }
- binarySearch(…)
//源碼 public static int binarySearch(int[] a, int key) { return binarySearch0(a, 0, a.length, key); } private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) { int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >>> 1; int midVal = a[mid]; if (midVal < key) low = mid + 1; else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. }
- 包含了用來操作數組(比如排序和搜尋)的各種方法。 如: