“陣列就是大型倉庫,讓你存放數據;學會它,你就可以處理大量資料了!” “不過,要操作它也會比較複雜 ” 陣列 Array 陣列 Array 是甚麼? 如果把一個變數想成是一個箱子的話,那麼陣列就是一組箱子,存放許多 個相同型態資料的物件--int, double, String, 以及任何的物件 objects。 存放 50 位學生所有考試科目的成績,變數就必須宣告 50 次,但是陣列只 需要宣告一次。 1 Array 是一種重要常用的 資料結構(Data Structure),各式各樣的程式都會用到陣 列。 依據維度 Dimension 的多寡,陣列有簡單也有複雜: ⚫ 1 維陣列 single dimensional array ⚫ 2 維陣列 two dimensional array ⚫ 多維陣列 multidimensional array 一維陣列 Single dimensional array 1 1 2 存放內容示意圖: 位置 0 1 2 array 10 20 30 int[ ] array = new int[3]; 或 int array[ ] = new int[3]; int 為新陣列內存放資料的型態 array 為新陣列的名稱 [3]代表預留 3 個空間(3 個箱子,箱子編號為 0,1,2) 位置/索引/index 2 陣列的索引值是從 0 開始的,因此的陣列元素為 array [0]、array [1]、array [2]。 陣列元素/陣列內容 (element) array [0]的內容值 ⚫ 先宣告 再配置 再指定初值 int[] array; 宣告方式 1 建議用這種! int array[]; 宣告方式 2 array = new int[3]; 配置陣列大小 array[0]=10; //給值 array[1]=20; 2 印出陣列內的每一個元素 element: System.out.println(array[0]); System.out.println(array[1]); System.out.println(array[2]); ⚫ 一氣呵成:宣告、配置、指定初值 int[] array = {10,20,30}; 或 int array[] = {10,20,30}; 印出陣列內的每一個元素 element: 3 System.out.println(array[0]); System.out.println(array[1]); System.out.println(array[2]); array 名稱 {10,20,30} --陣列被建立之後,陣列大小即不可再更改 array.length 取得陣列大小 --當陣列被建立時,其元素會指定有預設值 如果是數字基本資料型態,會被指定為預設值 0 如果是 boolean 型態,則會被指定為預設值 false 3 3 4 範例:陣列內容操作: int[] array = new int[3]; array[0] = 10; array[1] = 20; array[0] = array[0]+1; array[0] += 1; array[0]++; array[0] *= 5; array[2] = array[0]+ array[1]; System.out.println(array[0]); 4 System.out.println(array[1]); System.out.println(array[2]); 例外錯誤: 陣列 array index out of bounds exception 索引 超出邊界 例外 當陣列的索引不能超出陣列的大小,若超過會有例外訊息出現!! 看到這個錯誤訊息,你就知道該如何排除錯誤了。 範例:使用迴圈印出陣列內容 int[] array = {10,20,30}; for (int i=0; i<array.length; i++) { 4 System.out.print(array[i]+" "); } 新式迴圈寫法:類似 C#的 foreach 或是 Python 的 for e in array: for (int value : array) //新式 foreach 迴圈寫法 { System.out.print( value +" "); } Python 或其他語言也有類似的寫法 for value in array: print(value) 5 讓我們動手做一些容易理解的範例 範例:陣列內容值加總 int[] array={10,20,30}; int total=0; for (int i=0; i<array.length; i++) { total += array[i]; } System.out.println(total ); 新式迴圈寫法: int[] array={10,20,30}; int total=0; for (value in array) { total += value; 5 5 6 } System.out.println(total ); 範例:陣列內容值平均: int[] array={10,20,30}; int total=0; for (int i=0; i<array.length; i++) { total += array[i]; } System.out.println( (double)total/array.length ); 陣列也可當作靜態方法的參數 6 範例:寫一個靜態方法 display(int[] array),可以印出陣列內容 用迴圈印出陣列內容 public static void display(int[] array) { for (int i=0; i<array.length; i++) { System.out.print(array[i]+" "); } System.out.println(); } 用迴圈印出順序逆轉的陣列內容 public static void displayReverse(int[] array) { for (int i =array.length-1 ; i >=0; i--) { System.out.print(array[i] + " "); } System.out.println(); } 6 7 另法:新式 foreach 迴圈寫法 public static void display(int[] array) { for (int value : array) //新式 foreach 迴圈寫法 { System.out.print( value +" "); } System.out.println(); } 如何呼叫? public static void main(String[] args) 7 { int[] array={10,20,30}; display( array ); //沒有回傳值 } 範例:回傳陣列數字之總和 getTotal() public static int getTotal (int[] array) { int total=0; for (int i=0; i<array.length; i++) { total += array[i]; } return total; } 如何呼叫? 7 //回傳 public static void main(String[] args) { System.out.println( getTotal(array) ); 印出回傳值 } 範例: 改寫成靜態方法--回傳陣列數字之平均 public static double getAvg(int[] array) { double avg; int total=0; for (int i=0; i<array.length; i++) { total += array[i]; } 8 avg = (double)total/array.length ; return avg; //回傳平均值 } 範例:回傳陣列數字之平均-呼叫 getTotal() (更佳寫法) public static double getAvg(int[] array) { return (double)getTotal(array)/array.length ; 回傳平均值 } 如何呼叫? public static void main(String[] args) { int[] array = {80,90,70}; System.out.println( getAvg(array) ); 印出回傳值 8 8 9 } 每個人與平均分數差距多少? 列印到小數點下一位 public static void main(String[] args) { int[] array = {80,90,70}; double avg = getAvg(array); for (int i=0; i<array.length; i++) { System.out.printf("%.1f ", array[i] - avg); } } 9 範例:回傳陣列中最大的數字 public static int max(int[] array) { int max=-1; for (int i=0; i<array.length; i++) { if (array[i] > max) max = array[i]; } return max; } 如何呼叫? public static void main(String[] args) { int[] array = {80,90,70}; System.out.println( max(array) ); 印出回傳值 } 9 10 每個人與最高分差距多少? public static void main(String[] args) { int[] array = {80,90,70}; int maxValue = max(array); //最大值都一樣,取得只要呼叫一次 for (int i=0; i<array.length; i++) { System.out.printf("%d ", array[i] - maxValue); } } 這樣寫好嗎?是不是呼叫 max(array)太多次了,最大值都一樣,只要呼叫一次, 這樣才有效率,節能減碳喔! public static void main(String[] args) 10 { int[] array = {80,90,70}; for (int i=0; i<array.length; i++) { System.out.printf("%d ", array[i] - max(array) ); } } OJ 考題練習:每個人的分數與最高分相差多少分 輸入: 第一個數字 3,表示有 3 個人的成績 3 70 90 80 輸出: 與最高分相差多少分 20 0 10 參考程式碼 public static void main(String[] args) 10 { int[] array; Scanner input = new Scanner(System.in); // 讀入分數個數 size,並初始化大小為 size 的 array 陣列 int size = input.nextInt(); array = new int[size]; for (int i=0; i < size; i++) { array[i] = input.nextInt(); } int maxValue = max(array); for (int i=0; i<array.length; i++) { System.out.printf("%d ", maxValue- array[i] ); } } 11 OJ 考題練習:與平均分數相差多少分 輸入: 3 90 70 80 輸出: 與平均分數相差多少分,列印到小數點下一位 10.0 -10.0 0.0 範例:統計陣列內數字出現頻率…開始複雜了! 給產品的評分(評價)等地為 1,2,3,4,5 分 有 10 位客戶給評分 統計各等地的人數(次數)(頻率)為多少 public static void main(String[] args) { int[] grades={2,2,5,1,1,3,4,2,4,5,1}; 11 11 int[] freq = new int[6]; //0,1,2,3,4,5 for (int i = 0; i < grades.length; i++) { freq[ grades[i] ]++; //這個寫法很高竿! grades[i]的內容是評分 1~5 12 } // 印出頻率次數 for (int i=0; i< freq.length; i++) { if (freq[i]!=0) System.out.println(i+":"+freq[i]); } } grades 12 freq 0 1 2 3 4 5 6 7 8 9 2 2 5 1 1 3 4 2 4 5 0 1 2 3 4 5 0 0 0 0 0 0 分解動作: 以下三種寫法結果相同: freq[2] = freq[2] +1 freq[2] += 1 freq[2] ++ 請問 freq[2]的值變為多少? 12 grades[0] → 2 grades[1] → 2 freq[ grades[0] ] → freq[ freq[ grades[0] ]++ → freq[ 2 ] 2 ]++ grades[0]是 2,將 2 做為 freq 的索引 13 延伸問題: 如果 freq 的大小規定為 5,則程式如何修改? int[] freq = new int[5]; //0,1,2,3,4 OJ 考題練習:計算頻率 輸入: 2 2 5 3 -1 輸出: 13 2:2 3:1 5:1 (1)頻率統計利用陣列。 (2)輸入的數字沒必要用陣列存放,讀完統計後即丟。 import java.util.Scanner; public class FrequencyMain { public static void main(String[] args) { int[] freq = new int[6]; //0,1,2,3,4,5 int grade; Scanner input = new Scanner(System.in); grade = input.nextInt(); while(grade != -1) { freq[ grade ]++; 13 grade = input.nextInt(); } // 印出頻率次數 for (int i=0; i< freq.length; i++) { if (freq[i]!=0) System.out.println(i+":"+freq[i]); } } } 計算隨機數的頻率 14 Description 產生 200 個隨機數字,計算 0-9 數字出現的頻率 輸入:200 (產生隨機數的數量) 輸出:統計 0-9 數字出現的頻率 提示: (1) 亂數種子固定為 123: Random rnd = new Random(123); rnd.nextInt(10); (2) 輸出格式:System.out.println( i + " quantity is " + counts[i]); Input 輸入:200 (產生隨機數的數量) Output 0 quantity is 19 1 quantity is 22 14 14 15 2 quantity is 18 3 quantity is 18 4 quantity is 21 5 quantity is 17 6 quantity is 21 7 quantity is 22 8 quantity is 14 9 quantity is 28 OJ 也可能要求格式如下: 200 0:19 1:22 2:18 15 3:18 4:21 5:17 6:21 7:22 8:14 9:28 package chapter07; import java.util.Random; import java.util.Scanner; public class FreqRandomMain { public static void main(String[] args) { Scanner input = new Scanner(System.in); int N = input.nextInt(); //200 Random rnd = new Random(123); 15 16 int[] freq = new int[10]; //0 1 2 .... 9 for (int i = 1; i <= N; i++) { freq[rnd.nextInt(10)]++; //0 1 2 ... 9 } for (int i = 0; i < freq.length; i++) { if (freq[i] != 0) { System.out.println(i + ":" + freq[i]); } } } } 範例:將陣列的順序反轉 16 規定你不能使用額外的陣列,也就是只能用 array 本身調換內容。 寫一個靜態方法 reverseArray(),將陣列的順序反轉 0 1 2 3 4 5 10 20 30 40 50 60 觀念: 後半部換到前面 16 前半部換到後面 17 演算法: 我們要關注在索引… i=0 i=1 i=2 0 1 2 5 4 3 3 索引 i 跟 j 要交換? 要讓以下關係成立的關係式 → 找出這個關係,就成功了 17 i=0 i=1 i=2 j=5 j=4 j=3 i j = array.length - 1 - i; → j = 5-i 如何與新位置的值交換? 兩個人要換座位,規定兩個人的屁股不能同時離開椅子 宣告一個 temp 變數,暫時的椅子,讓人暫時坐一下 temp = array[i]; array[i] = array[j]; array[j] = temp; 先在 main()方法中測試 public static void main(String[] args) { int array[] = {10,20,30,40,50,60}; int j; //取哪個位置的數? int temp; 17 4 5 0 // 將前半部 與 後半部 一一交換! for (int i = 0; i < array.length / 2; i++) { //j 位置是後面的那個數字 j = array.length - 1 - i; // i 與 j 位置的值交換 temp = array[i]; array[i] = array[j]; array[j] = temp; // 把交換的過程列印出來觀察正不正確! System.out.printf("%d %d\n", i, j); } } 寫成方法 18 public static void reverse(int[] array) { int j; //取哪個位置的數? int temp; // 將前半部 與 後半部 一一交換! for (int i = 0; i < array.length / 2; i++) { j = array.length - 1 - i; //與新位置的值交換 temp = array[i]; array[i] = array[j]; array[j] = temp; // 把交換的過程列印出來觀察正不正確! System.out.printf("%d %d\n", i, j); } } 用迴圈印出陣列內容 public static void display(int[] array) 18 18 { for (int i=0; i<array.length; i++) { System.out.print(array[i]+" "); } System.out.println(); } public static void main(String[] args) { int array[] = {10,20,30,40,50,60}; reverse(array); display(array); } } 19 思考問題: 1. 印出呼叫 reverse()前、後的 array 內容,你發現甚麼? 2. array 內容是不是不一樣? 3. 甚麼是傳值呼叫?傳參考呼叫? 範例:將陣列的順序打亂 想法: 針對每一個數字,與新位置(隨機產生新位置)的值交換 規定不能使用額外的陣列。 public static void main(String[] args) { int[] array = {10, 20, 30, 40, 50}; Random rnd = new Random(123); //給定種子 int j; //某個隨機決定的位置 int temp; //用來暫放數字的變數 19 19 for (int i = 0; i < array.length; i++) { //隨機產生新位置 j = rnd.nextInt( array.length ); //0,1,2,3,4 //j = (int) (Math.random() * array.length); //0,1,2,3,4 System.out.println(j); //與新位置的值交換 temp = array[i]; array[i] = array[j]; array[j] = temp; } display(array); } public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } 20 System.out.println(); } 範例:將陣列的順序打亂(改寫成方法呼叫) public static void main(String[] args) { int[] array = {10, 20, 30, 40, 50}; random(array); display(array); } public static void random(int[] array) { int j; int temp; Random rnd = new Random(123); //給定種子 for (int i = 0; i < array.length; i++) { //隨機產生新位置 j = rnd.nextInt( array.length ); //0,1,2,3,4 20 20 21 //與新位置的值交換 temp = array[i]; array[i] = array[j]; array[j] = temp; } } public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } 範例:將陣列的順序打亂,規定數字不能位於原來位置 21 請問以下寫法有何不同? public static void main(String[] args) { int[] array = {10,20,30,40,50}; int j; int temp; Random rnd = new Random(123); //給定種子 for (int i= array.length-1; i > 0; { //隨機產生新位置 //j =(int)(Math.random()*(i)); j = rnd.nextInt( i ); // i=4 時 j 可能值為 0,1,2,3 // i=3 時 j 可能值為 0,1,2 // i=2 時 j 可能值為 0,1 // i=1 時 j 可能值為 0 // i=0 沒必要! //與新位置的值交換 temp = array[i]; array[i] = array[j]; 21 i--) array[j] = temp; } display( array ); 22 } 範例:排列組合 Description 輸入: 陣列大小 N(整數)、各別數值(整數) 輸出: 印出陣列數字的所有組合,陣列中較前位置的數字先印出 22 例如:有一個陣列內容為 int[]numbers = {1, 2, 3},印出以下排列 12 13 23 演算法(步驟): 使用巢狀 for 迴圈,外面的迴圈控制索引 i 印出前面數字 numbers[i],內部的迴 圈控制索引 j 印出後面數字 numbers[j] System.out.println(numbers[i] + " " + numbers[j]); Input 輸入 N 個數字(整數),例如:N=3 22 23 各別數值:1 2 3 Output 12 13 輸入: 4 4231 輸出: 42 43 41 23 23 21 31 參考答案巢狀迴圈 public class CombinationMain { public static void main(String[] args) { int[] array = {10,20,30}; //10, 20 10, 30 20, 30 for (int i=0; i < array.length; i++) { for (int j=i+1; j < array.length; j++) { System.out.printf("%d, %d\n",array[ i], array[j]); } } } } 23 24 字串 String 陣列宣告與 new 使用 ⚫ 先宣告 再配置 再指定初值 String[] seasons= new String[4]; seasons[0] ="春天"; seasons[1] = new String("夏"); 字串初始化較正式的寫法 seasons[2] = new String("秋"); seasons[3] = new String("冬"); 24 ⚫ 一氣呵成:宣告、配置、指定初值 String seasons[] = { "春", "夏", "秋", "冬" }; for (int i=0; i < seasons.length; i++) System.out.println(seasons[i]); ⚫ 字串陣列的列印方法 public static void display(String[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } 24 範例:簡單的樸克牌發牌 亂數函數怎麼使用? 你忘記了嗎? 亂數類別 Random 25 Random rnd = new Random(123); //給定種子 rnd.nextInt( 6 ); // 0 1 2 3 4 5 樸克牌花色 String suits[] = { "Hearts", "Diamonds", "Clubs", "Spades" }; for (int i=0; i < suits.length; i++) System.out.println(suits[i]); 發牌遊戲很複雜,不太適合初學者,講完自訂類別之後,期末若還有時間,再 介紹樸克牌遊戲,使用自訂類別,會比較好寫樸克牌發牌。 25 假設我們的牌有很多付(Deck of cards, 52 張)混和在一起,牌可以重複發。 String suits[] = { "紅心", "鑽石", "梅花", "黑桃" }; int[] points = {1,2,3,4,5,6,7,8,9,10,11,12,13}; int suit; int point; // 發 1 張牌 suit = rnd.nextInt(4); //0,1,2,3 point =rnd.nextInt(13); //0,1,2,3,…,12 System.out.printf("%s%d", suits[suit], points[point]); // 發 3 張牌 並計算點數和 // 請自行完成點數的加總 for(int i=1; i<=3; i++) { suit = rnd.nextInt(4); 25 //0,1,2,3 point =rnd.nextInt(13); //0,1,2,3,…,12 System.out.printf("%s%d", suits[suit], points[point]); } OJ 驗收:樸克牌發牌 隨機發樸克牌 N 張,印出發牌結果,並計算點數和 亂數種子固定為:123 花色與點數陣列已知: String suits[] = { "紅心", "鑽石", "梅花", "黑桃" }; int[] points = {1,2,3,4,5,6,7,8,9,10,11,12,13}; 26 先用亂數決定花色,再用亂數決定點數,按此順序決定某一張牌的內容。 注意: 亂數種子、花色與點數陣列必須與題目給的一樣。 假設:我們的牌有很多付牌混和在一起,牌可以重複發。 輸入: 3 輸出: 梅花 2,黑桃 7,鑽石 8 17 import java.util.Random; public class PokerGame { public static void main(String[] args) { //亂數類別 Random Random rnd = new Random(123); //給定種子 //隨機發樸克牌 3 張算點數和 26 26 String suits[] = {"紅心", "鑽石", "梅花", "黑桃"}; int[] points = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; int suit; //置放花色 int point; //置放點數 int N = 3; //發牌張數 // 發 N 張牌 並計算點數和 int pointSum = 0; //置放點數和 for (int i = 1; i <= N; i++) { suit = rnd.nextInt(4); //0,1,2,3 point = rnd.nextInt(13); //0,1,2,3,…,12 System.out.printf("%s%d,", suits[suit], points[point]); pointSum += points[point]; } System.out.println(); System.out.println(pointSum); } 27 } 方法:可變長度的引數列表 不定數量引數的方法 型別後面的刪節號 (...) 表示要接收此一特定型別不定數量的引數 範例: 計算平均值 public static double average( double… numbers) { //把 numbers 當成是一個陣列去操作即可!! double total = 0.0; for (double num : numbers) { total += num; } 27 27 return total / numbers.length ; } public static void main(String[] args) { System.out.println( average(2,3) ); System.out.println( average(2,3,4) ); } 範例:任意個整數的最大公因數 // 2 個參數的 gcd 輾轉相除法,原理請參考迴圈講義 public static int gcd(int x, int y) { int tmp = 0; 28 while (x % y != 0) { tmp = y; y = x % y; x = tmp; } return y; } // 3 個參數的 gcd public static int gcd(int x, int y, int z) { return gcd(gcd(x, y), z); } 任意參數的 gcd(),你要寫好多的多載?! 不用喔! 額外寫一個即可… public static void main( String[] args ) { 28 28 System.out.println(gcd(25,15)); System.out.println(gcd(25,15, 5)); System.out.println(gcd(25,15, 30, 75)); } // 寫一個兩個參數的 gcd(int x, int y){} // 寫一個任意參數的 gcd(int... public static int gcd(int... numbers){} numbers){ } 29 多個參數的 gcd()寫法 public static int gcd(int... numbers) { int x = ?? ; for ( … ) { … } return x; } gcd(12, 6, 4, 8); 分解步驟 x = 12 29 numbers[0] x = gcd(12, 6) x=6 x = gcd(6, 4) x=2 29 x = gcd(2, 8) x=2 x = gcd(x, number[i]) return x; 多個參數的 gcd()寫法 public static int gcd(int... numbers) { int x = numbers[0] ; for (int i=1; i< numbers.length; i++) { x = gcd(x, numbers[i]); // 神奇的一步驟!! 30 } return x; } 完整的版本 public class GcdMultipleParameters { public static void main(String[] args) { System.out.println(gcd(25, 15)); System.out.println(gcd(25, 15, 5)); System.out.println(gcd(25, 15, 30, 75)); } // 2 個參數的 gcd 輾轉相除法,原理請參考迴圈講義 public static int gcd(int x, int y) { int tmp = 0; while (x % y != 0) { tmp = y; 30 30 y = x % y; x = tmp; } return y; } public static int gcd(int... numbers) { int x = numbers[0]; for (int i = 1; i < numbers.length; i++) { x = gcd(x, numbers[i]); // 神奇的一步驟!! } return x; } } 31 傳“值”呼叫 vs. 傳“參考”呼叫 傳值呼叫(Call by value, pass by value) 傳“值”呼叫 英文 call-by-value 參數是基本型別數值 int double 將引數複製一份副本(copy)給參數用 就是大家所說的“傳值呼叫” 傳“參考”呼叫(call-by-reference, pass by reference) 傳參考呼叫(call-by-reference) 31 ⚫ 在方法中可以直接修改引數內容 (修改陣列內容) ⚫ 不用複製的大量資料(不用拷貝一份),增進效能 31 32 實例:針對一維成績陣列,寫一個方法可以將每一個人的成績加 10 分。 public class GradeBook { public static void main(String[] args) { int[] score = {53,42,63}; plus10( score); 呼叫方法 display(score); } public static void plus10(int[] array) { for (int i=0; i < array.length; i++) { array[i]+=10; 32 } } public static void display(int[] array) { for (int value : array) //新式 foreach 迴圈寫法 { System.out.print( value +" "); } System.out.println(); } } 問題: 方法用陣列當參數,在方法中對該陣列內容操作,會不會影響到呼叫處的陣列 原來內容? 這就是 call-by-reference 傳參考呼叫的特性!! (參數內容可以被修改!!) 32 當參數類型是一個陣列或是物件時,就是傳參考呼叫 33 傳“值”呼叫 vs. 傳“參考”呼叫更詳細的說明 Java 中所有的引數都是透過以下兩種傳值,也就是可以傳遞兩種型態的值 給所呼叫的方法: (1)基本型別數值的副本(copy): int double 也就是大家所說的“傳值呼叫” (2)物件 “參照值” “參考值” 的副本(copy): 就是“傳參考(照)呼叫”,與 C++的 “傳址 address (地址 位址)呼叫”,結果相同,達到相同的目的(不過內部運作方 式不同喔!)。 傳參考呼叫必須注意,原始呼叫處的引數可以被任意修改其內容! 問題: 請問以下參數是提供何種方式呼叫? 參數: “陣列型態” 答案: 陣列當參數,是一種“傳參考呼叫”,例如:前面的實例。 33 問題: 請問以下參數是提供何種方式呼叫? 參數:是“物件型態” 就是用 new 一個類別所產生的物件 (下一個章節才會教到 自訂的類別) 答案: “物件”當參數,是一種“傳參考呼叫” “物件”的參照是一種 “傳參考呼叫” 方法中的參數與呼叫程式傳遞過來的引數,兩者是指向記憶體中相同的物 件。 方法中可以使用物件參照的副本,呼叫此物件的 public 方法,藉此和被參 照的物件互動。 不過,如果方法更改參照型別的參數,讓它指向另一個物件,則只有該則 參數會指到新的物件,存放於呼叫者的變數中的參照,仍然會指向原本的物件 如果你喜歡,“傳參考呼叫” 也可以稱呼為 “傳參考值呼叫” ➔多一個“值”似乎 更貼切於 Java 內部的運作。 方法之回傳值: 陣列 33 參數可以是陣列 34 範例:針對一維成績陣列,寫一個方法可以將每一個人的成績加 10 分, 回傳加分之後的陣列。 public class GradeBook { public static void main(String[] args) { int[] score = {53,42,63}; display( getPlus10(score) ); //呼叫方法並列印 // 有時候需要用一個變數"儲存"回傳的陣列 int[] result; //不必配置記憶體空間,此陣列名稱會指到另一個陣列 result = getPlus10(score); //指定回傳加分後的陣列參考(位置)給 result 34 display(result); //列印 } public static int[] getPlus10(int[] array) { int[] result = new int[array.length]; for (int i=0; i < array.length; i++) { result[i] = array[i]+10; } return result; } public static void plus10(int[] array) { for (int i=0; i < array.length; i++) { array[i]+=10; } } 34 35 public static void display(int[] array) { for (int value : array) //新式 foreach 迴圈寫法 { System.out.print( value +" "); } System.out.println(); } } 範例:將陣列反過來並回傳反轉之後的結果 寫法 1: 容易理解 public static int[] getReverse(int[] array) { 35 int[] result = new int[ array.length ]; int j; //取哪個位置的數? for (int i=0; i < array.length; i++) { j = array.length-1 result[i] = -i ; // index: 4, 3, 2, 1, 0 array[j]; } return result; } public static void main(String[] args) { int array = {10, 20, 30}; int[] result; result = getReverse( array ); display( result ); } 35 //接收回傳的一維陣列 i=0 5-1-0 36 寫法 2: for 迴圈較複雜的寫法 練習一下以下的宣告方式: int a,b; public static int[] getReverse(int[] array) { int[] result = new int[ for ( int i=0, array.length j = array.length-1 ; i < ]; array.length ; i++, j-- ) result[i] = array[j]; return result; } 範例:將陣列的順序打亂回傳,呼叫後,引數陣列內容仍保持原樣,不 36 被修改。 注意:前面已經寫好一個 random 函數可以直接呼叫使用,不必再寫一次。 package chapter7; public class P50_RandomArray { public static void main(String[] args) { int[] array = {10, 20, 30, 40, 50}; display(getRandom(array)); //random(array); //display(array); } public static int[] getRandom(int[] array) { int j; int temp; //用新的陣列來操作,引數陣列不要更動內容 int[] result = new int[array.length]; 36 //複製一份給 result System.arraycopy(array, 0, result, 0, array.length); random(result); //呼叫 random()把順序打亂! return result; } public static void random(int[] array) { int j; int temp; for (int i = 0; i < array.length; i++) { //隨機產生新位置 j = rnd.nextInt(array.length); //與新位置的值交換 temp = array[i]; array[i] = array[j]; array[j] = temp; } } 37 public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } } OJ 範例: 寫一個方法可以判斷兩個陣列是否完全相同 順序與內容都一樣 輸入:兩個陣列 輸出:兩個陣列相同為 true, 不同則為 true 37 37 38 輸入: 3 10 20 30 3 10 20 30 輸出: true 輸入: 3 10 20 30 3 15 20 30 輸出: false 輸入: 4 10 20 30 10 3 10 20 30 輸出: 38 false public class Ex20_ArrayEquals { Scanner input = new Scanner(System.in); public static void main(String[] args) { //讀第 1 個陣列 int N1=input.nextInt(); int[] array1 = new int[N1]; for (int i=0; i < array1.length; i++) { array1[i] = input.nextInt(); } //讀第 2 個陣列 int N2=input.nextInt(); int[] array2 = new int[N2]; for (int i=0; i < array2.length; i++) 38 { array2[i] = input.nextInt(); } //印出比對結果 System.out.println(equals(array1, array2)); } public static boolean equals(int[] array1, int[] array2) { if (array1.length != array2.length) { return false; } for (int i = 0; i < list1.length; i++) { if (array1[i] != array2[i]) { return false; } } 39 return true; } } 另法: 使用 Arrays 提供的靜態方法,一行搞定。 import java.util.Arrays; System.out.println(Arrays.equals(array1, array2)); System.arraycopy 複製陣列內容 System 類別的 arraycopy 方法 將一份陣列的內容複製到另一陣列 System.arraycopy(來源, 起始索引, 目的, 起始索引, 複製長度) 39 39 int [] array1 = {1,2,3,4,5}; int [] array2 = new int[5]; System.arraycopy(array1, 0, array2, 0, array1.length); 以下寫法不是複製陣列: array2 = array1 //參照到同一個陣列物件 範例:自己寫一個 arrayCopy() void arrayCopy(int[] array1, int[] array2),可以將 array1 的內容 copy 給 array2 (用 for 迴圈可輕鬆搞定!) public class ArrayCopy { 40 public static void arrayCopy(int[] array1, int[] array2) { for (int i = 0; i < array1.length; i++) { array2[i] = array1[i]; } } public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } public static void main(String[] args) { int[] array1 = {1, 2, 3, 4, 5}; int[] array2 = new int[array1.length]; //先配置好 array 的長度 arrayCopy(array1, array2); display(array2); } 40 40 41 } 範例:自己寫一個有回傳的 arrayCopy() 自己寫一個 int[] arrayCopy(int[] array),可以將 array 的內容回傳(等同於複製一 份) public class ArrayCopyV2 { public static int[] arrayCopy(int[] array) { int[] result = new int[array.length]; for (int i = 0; i < array.length; i++) { result[i] = array[i]; 41 } return result; } public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } public static void main(String[] args) { int[] array1 = {1, 2, 3, 4, 5}; int[] array2; array2 = arrayCopy(array1); display(array1); display(array2); } 41 42 } Arrays 類別:提供許多好用的陣列操作的靜態方法 Arrays 類別,提供了進行常見陣列操作的 靜態 static 方法 其方法包括 ⚫ sort,用來排序陣列 (以遞增順序排列元素) ⚫ binarySearch,用來在陣列中進行搜尋 ⚫ equals,用來比較陣列內容有沒有相同 ⚫ fill,用來把數值放入陣列中 這些方法都有多個多載,以處理參數為基本型別的陣列以及物件的陣列 42 int[] data = {50, 40, 30, 10, 20}; Arrays.sort(data); 陣列內容會被排序!存放位置也變了! 進階陣列處理(資料結構) 資料結構 Data Structure 是二年級的必修課,也是各校研究所入學考科,也是程式的基礎工具, 它非常重要! Algorithms + Data Structures = Programs 來源 線性搜尋 Linear Search 42 43 在 main()程式 int[] data = {6,4,1,9,7,3,2,8}; System.out.println("Index is " + binarySearch(data, 11)); 參考寫法 public class LinearSearch { 43 /** The method for finding a key in the array */ public static int linearSearch(int[] array, int key) { // 逐一去比對 for (int i = 0; i < array.length; i++){ if (key == array [i]) return i; // return array index } return -1; // // not found } } 另一種寫法也不錯 public static int linearSearch(int[] array, int key) { int index = -1; for (int i = 0; i < array.length; i++) { if (key == array [i]) { index=i; // array index break; //找到就不必再找了 43 } } return index; } } 二分搜尋法 Binary Search 課本實例: 控制 3 個指標(low, mid, high) mid = (low+high)/2 陣列元素有奇數個,剛好整除,mid 是中間的那個元素 陣列元素有偶數個,mid 是小數但取整數 展示找到 key 的步驟 44 key 找不到的情況: low high 位置不正常交錯! 44 44 key is 54 low key > 50 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] list 2 mid 4 7 10 11 45 high 50 59 60 66 69 70 79 low key < 66 list 45 mid high [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] 59 60 66 69 70 79 low mid high [7] [8] key < 59 list 59 60 low high [6] [7] [8] 59 60 在 main()程式 int[] data = {2, 4, 7, 10, 11, 45, 50, 59, 60, 66, 69, 70, 79}; 45 System.out.println("Index is " + binarySearch(data, 11)); public static int binarySearch(int[] array, int key) { int low = 0; int high = array.length - 1; int mid = (low + high) / 2; System.out.printf("low:%d mid:%d high:%d\n", low, mid, high); //印出看看 while ( high >= low ) { //處理三種可能情況 if ( key < array[mid] ) high = mid - 1; //在左半邊 else if ( key == array[mid] ) return mid; //找到了 直接離開 不必再進行後面的迴圈 else if (key > array[mid]) low = mid + 1; //在右半邊 mid = (low + high) / 2; //mid 的值依據新的 low, high 去計算 System.out.printf("low:%d mid:%d high:%d\n", low, mid, high); //印出看看 } 45 return -1; //找不到,就回傳-1 //return -1 - low; //找不到,就回傳 key 該插入之位置 } 疑問: int mid 宣告在 for 迴圈裡面比較好?還是在迴圈外面比較好? • If type of variable is simple (int, double, ...) I prefer inside. Reason: reducing scope of variable. • If type of variable is not simple (some kind of class) I prefer outside. Reason: reducing number of object initialization. 參考: statckoverflow 氣泡排序法(陣列由小排到大 Ascending) 46 *氣泡排序法使用兩個 for 迴圈即可搞定 外部迴圈條件為 for (int i=0; i < array.length ;i++) 內部迴圈條件為 for (int j=0; j < array.length -1 ;j++) *氣泡排序法使用兩個 for 迴圈—少比較幾次,不做白工(請問為甚麼?) 外部迴圈條件為 for (int i=0; i < array.length-1 ;i++) 內部迴圈條件為 for (int j=0; j < array.lenght -1-i ;j++) 第 1 輪(外層迴圈 i):比較 4 次(內層迴圈 j) (不要比第 5 次,陣列索引會出界 array index out of bound exception) 46 5 4 3 2 1 5、4 比較,5>4 故互換 4 5 3 2 1 5、3 比較,5>3 故互換 4 3 5 2 1 5、2 比較,5>2 故互換 4 3 2 5 1 5、1 比較,5>1 故互換➔4 3 2 1 5 5 浮出 46 4 3 2 1 5 5 浮出 第 2 輪:比較 3 次 4 3 2 1 5 4、3 比較,4>3 故互換 3 4 2 1 5 4、2 比較,5>3 故互換 3 2 4 1 5 4、1 比較,5>2 故互換 3 2 1 4 5 4、5 比較,不動(這步做白工 ) 2 1 4 5 4 浮出 4 浮出 3 第 3 輪:比較 2 次 47 3 2 1 4 5 2 3 1 4 5 2 1 3 4 5 不動 2 1 3 4 5 不動 1 3 4 5 3 浮出 3 浮出 2 第 4 輪:比較 1 次 2 1 3 4 5 1 2 3 4 5 不動 1 2 3 4 5 不動 1 2 3 4 5 不動 2 3 4 5 2 浮出 2 浮出 1 47 47 48 第 5 輪:比較 0 次(這輪做白工)(最後一輪不必做,因此,只要做 n-1 輪即可) 1 2 3 4 5 不動 1 2 3 4 5 不動 1 2 3 4 5 不動 1 2 3 4 5 不動 2 3 4 5 1 浮出 1 浮出 1 ⚫ 如何交換陣列內容? temp = data[j]; 48 j 位置的人到 temp 暫坐 data[j] = data[j + 1]; data[j + 1] = temp; ⚫ temp 坐到 j+1 位置 參考答案 public class MyArrays { public static void sort(int[] array) { int temp; for (int i = 0; i < array.length - 1; i++) { for (int j = 0; j < array.length - 1 - i; j++) { if (array [j] > array[j + 1]) { temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } public static void display(int[] array) { 48 for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } 49 System.out.println(); } public static void main(String[] args) { int[] data = {5, 4, 3, 1, 2}; MyArrays.sort(data); display(data); } } 氣泡排序法:印出排序之詳細過程 請改寫前述氣泡排序法,將排序的詳細過程(每一輪 每一次比較之結果)列印在 49 畫面。 氣泡排序法時間複雜度是 O(𝒏𝟐 ) *氣泡排序法之時間複雜度 Big O 是 O(𝑛 2 ),請問為甚麼? 時間複雜度(time complexity): Big O O 是 ‘Order’ 用來表示一個演算法 (algorithm)的執行效率 n 表示陣列長度,陣列的元素越多,排序就越花時間,所以,時間複雜度與 n 的大有有關。 但是,為甚麼是𝑛 2 ? → 兩個迴圈執行次數: n*n 由大排到小 Descending 氣泡排序法 public static void sortDescend (int[] array) { int temp; 49 for (int i = 0; i < array.length - 1; i++) { for (int j = 0; j < array.length - 1 - i; j++) { if (array [j] < array[j + 1]) { temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } 漂亮好用的排序方法:透過參數決定由大或由小兩種排序法 50 活用以前學過的方法多載,開發一個專業有水準的工具程式! 請提供多載方法,讓使用者可以這樣呼叫: 排序:由小到大 MyArrays.sort(data) or MyArrays.sort(data, 0) or MyArrays.sort(data, MyArrays.ASCEND) →這樣寫最佳! or MyArrays.sort(data,"ascend") 排序:由大到小 MyArrays.sort(data, 1) or MyArrays.sort(data, MyArrays.DESCEND) or MyArrays.sort(data,"descend") 50 →這樣寫最佳! 50 51 在 MyArrays 中先定義: 由小到大 ASCEND=0、由大到小 DESCEND=1 與兩種 代號 這樣呼叫: MyArrays.sort(data, MyArrays.ASCEND) 參考答案 public class MyArrays { // 定義類別(靜態)變數,讓使用者方便使用,不必記憶數字代碼 public final static int ASCEND = 0; public final static int DESCEND = 1; 51 // 版本 1 public static void sort(int[] data, int sortedMethod) { switch (sortedMethod) { case ASCEND: // 寫 case 0:也可以,不過較不清楚! sort(data); break; case DESCEND: sortDescend(data); break; default: System.out.println("未執行排序"); break; } } // 版本 2 public static void sort(int[] data, String sortedMethod) { if (sortedMethod.equals("ascend")) { sort(data); } else if ("descend".equals(sortedMethod)) { sortDescend(data); 51 } else { System.out.println("未執行排序"); } } public static void sort(int[] data) { int temp; for (int i = 0; i < data.length - 1; i++) { for (int j = 0; j < data.length - 1 - i; j++) { if (data[j] > data[j + 1]) { temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } } 52 } // 以下這個方法若不公開,可以設定為 private public static void sortDescend(int[] data) { int temp; for (int i = 0; i < data.length - 1; i++) { for (int j = 0; j < data.length - 1 - i; j++) { if (data[j] < data[j + 1]) { temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } } } public static void display(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } 52 52 public static void main(String[] args) { int[] data = {50, 40, 30, 10, 20}; //MyArrays.sort(data); //MyArrays.sort(data, MyArrays.ASCEND); MyArrays.sort(data, "ascend2"); display(data); } } 在另外一個類別程式中呼叫: import static chapter07.MyArrays.display; //靜態方法 import public class MyArraysTest { public static void main(String[] args) { 53 int[] data = {50, 40, 30, 10, 20}; MyArrays.sort(data); //MyArrays.sort(data, MyArrays.ASCEND); //MyArrays.sort(data, "ascend"); //MyArrays.sortDescend(data); //改成 private 方法,這行會報錯嗎? display(data); // MyArrays.display(data); 這樣寫也可 } } OJ 各種考題 ⚫ 課本習題: 7.9 寫一個方法回傳陣列中最大的一個數 7.11 寫一個方法計算陣列的平均數。寫一個方法計算陣列的標準差。 53 53 課本習題 1. 隨機產生骰子的點數 1~6 的整數 60 萬個,統計各數字出現的次數(頻率) 2. 寫一個方法 boolean isSorted(int[] array) ,可以判斷陣列是否由小排到大 3. 寫一個方法 boolean equals(int[] array1, int[] array2),可以判斷兩個陣列是否 內容完全相等 ⚫ 寫一個方法回傳對應的分數 10 個等級陣列 等第的的計算如下: 54 分數 等第 100 10 90 9 80 8 70 7 … 10 以下 0 輸出畫面如下: Grade 1 : 5 Grade 2 : 3 Grade 10: 1 public static void main(String[] args) { int score = {96, 83, 77}; int[] grade; //宣告一個新陣列名稱,不必 new,只用到其名稱 grade = getGrade( score ); 接收回傳的分數等級的一維陣列 display( grade ); printLevel( score ); } 54 54 ⚫ public static int[] getGrade(int[] array) { int[] feq = new int[11]; 55 for (int i = 0; i < array.length; i++) { freq[ array[i] / 10 ]++; //這個寫法很高竿! } return freq; //回傳一個陣列回去 } public static void printGrade(int[] array) { int[] levels = new int[11]; for (int i = 0; i < array.length; i++) { levels[ array[i] / 10 ]++; //這個寫法很高竿! } display( levels ); //列印結果陣列 } 55 public static void display(int[] array) { for (int i=0; i<array.length; i++) { System.out.print(array[i]+" "); } System.out.println(); } ⚫ OJ 考題:氣泡排序 1. 寫氣泡排序法 sort()可以由小到大 排序:由小到大 public static void sort(int[] array) 2. 改寫氣泡排序法 sortDescend()可以由大排到小(這不能多載,必須另取一個 名字為 sortDescend(),為甚麼?) 55 排序: 由大排到小 public static void sortDescend(int[] array) 56 MyArrays.sortDescend(data) 3. 寫一個氣泡排序法 sort()的多載方法,提供第二個參數,決定由大排到小或 是由小到大 public final static int ASCEND=0; public final static int DESCEND=1; public static void sort(int[] array, int sortedMethod) 排序:由小到大 56 MyArrays.sort(data, MyArrays.ASCEND) 排序:由大到小 MyArrays.sort(data, MyArrays.DESCEND) 如何判斷 sortedMethod? if ( sortedMethod == ASCEND ) { 由小到大! 呼叫 sort() } else if ( sortedMethod == DESCEND ) { 由大到小! 呼叫 sortDescend() }else { System.out.println("參數不正確"); return; } 56 57 4. 實作二分搜尋法 binarySearch( int[] array ) ⚫ 請先判斷該陣列是否為已排序(由小到大!),若是未排序,則列印"請先排序 資料,資料未經排序,搜尋結果將會不正確!" public static boolean isSorted(int[] array) 你已經寫過這隻程式!這時可以派上用 場了! ⚫ 可以印出每個回合之 low, high 的值,藉以觀察搜尋的過程。 陣列名稱是一個參考(reference) 57 ⚫ 展示情況 1 int[] array1 = {1,2,3}; int[] array2; array2 = array1; 不是把 array1 拷貝給 array2 的意思!! 是把 array1 的參考指定給 array2!! 也就是 array2 指到(參考到)array1 的位置 array2 的內容現在是甚麼? 當然是 {1,2,3} array1 array2 {1,2,3} 57 對 array2 操作就等於是對 array1 操作 這是你學習陣列不可不知道的事! ⚫ 展示情況 2 int[] array1 = {1,2,3}; int[] array2 = {4,5,6}; array2 array1 {1,2,3} 以下這行的做甚麼? 58 array2 = array1; array2 的內容現在是甚麼? 當然是 {1,2,3} {4,5,6}被它拋棄了! array1 array2 {1,2,3} 以指令的的輸出是甚麼? if ( array2 == array1) System.out.println("兩個陣列的位置(參考值)相等"); 答案會是甚麼? 58 {4,5,6} 58 59 判斷兩個陣列之內容是否相等? int[] array1 = {1,2,3}; int[] array2 = {1,2,3}; if ( array2 == array1) System.out.println("兩個陣列相等??"); 答案會是甚麼? false 兩個陣列內容一樣,為甚麼得到 false? 這樣比對是沒用的! 59 以下兩行的輸出是甚麼? System.out.println(array1); System.out.println(array2); [I@15db9742 [I@6d06d69c ➔這是甚麼東東亂碼? 這就是傳說中的 ‘參考’ ‘reference’ 可以這樣去想: 陣列的地址、位址、address、參考、 reference array2 array1 {1,2,3} if ( array2 == array1)不是 用來判斷陣列內容是否相等! 59 {1,2,3} 是用來判斷兩個陣列的位置(參考值) 是否相等! 60 那麼,你要如何寫一個方法可以判斷兩個陣列是否相等?? 參看前面介紹過的靜 態方法。 ArrayList 的用法 ArrayList 集合 類似陣列 動態調整大小 它們會在執行時自動增加大小,以容納更多元素 當你要存放的個數不固定,或是事先不知道時,可以用 ArrayList 較方便。 60 main()方法之參數—就是一個字串陣列 …終於看懂 String[] args 這個奇怪的東東… arguments: 引數 爭論 爭辯 範例:請在 command line(Console)(Terminal 終端機)環境下執行兩個 數相加 (java 環境設定,請參考講義:"如何在 CommandLine 執行 Java 程式") (1) 使用文書編輯器編輯以下程式碼,存成檔案 Plus.java (不要寫套件比較方便執行) (2) 在檔案目錄之下,進入 terminal 畫面 60 (3) 在命令列輸入: java Plus.java 3 5 會得到相加結果: 8 public class Plus { public static void main( String[] args ) { //檢查是否輸入兩個數? if (args.length != 2) { System.out.println( "請輸入兩個數!"); System.exit(0); } // 運算並列印 int result = Integer.parseInt(args[0]) + Integer.parseInt(args[1]); 61 System.out.println(result); } } 執行方式 2 (1) 先編譯 Plus.java javac Plus.java (2) 執行編譯後的 class 字元碼(byte code) java Plus 2 3 會得到相加結果: 8 61 61 62 若有中文字,則請更換編碼為 UTF-8,否則會有錯誤!(微軟內定編碼系統為 MS950) 62 C:\...>javac -encoding UTF-8 Plus.java 若有定義套件 mymath,則程式碼要放在套件(卷夾)內,執行.java 檔案的方式不 影響,但是執行字元碼的方式不一樣 編譯: javac .\mymath\Plus.java 或在 mymath 目錄中編譯 javac Plus.java 執行時,必須確定當前目錄之下,有一個子目錄 mymath: java mymath.Plus 3 5 62 範例:command line 計算機 public class Calculator { /** Main method */ public static void main(String[] args) { // Check number of strings passed if (args.length != 3) { System.out.println( "Usage: java Calculator operand1 operator operand2"); System.exit(0); } // The result of the operation int result = 0; // Determine the operator int n1 = Integer.parseInt(args[0]); int n2 = Integer.parseInt(args[2]); 63 switch (args[1].charAt(0)) { case '+': result = n1 + n2; break; case '-': result = n1-n2; break; case '*': result = n1 * n2; break; case '/': result = n1 / n2; } // Display result System.out.printf("%s%s%s=%d", args[0],args[1],args[2], result); } } ⚫ 63 設定 Java 路徑的方式: 63 64 64 64 編輯 Path 65 新增 java bin 路徑 65 65