day08_陣列入門

java_pedestrian發表於2020-12-22

陣列的概述

陣列(Array),是多個相同型別資料按一定順序排列 的集合,並使用一個名字命名,並通過編號的方式 對這些資料進行統一管理。陣列是一種容器,可以同時存放多個資料值。

特點

  • 陣列本身是引用資料型別,而陣列中的元素可以是任何資料型別,包括 基本資料型別和引用資料型別。但同一個陣列只能是同一種型別
  • 陣列在記憶體方面儲存的時候,陣列中的元素記憶體地址(儲存的每一個元素都是有規則的挨著排列的)是連續的。記憶體地址連續。這是陣列儲存元素的特點(特色)。陣列實際上是一種簡單的資料結構。
  • 所有的陣列都是拿“第一個小方框的記憶體地址”作為整個陣列物件的記憶體地址。(陣列中首元素的記憶體地址作為整個陣列物件的記憶體地址。)
  • 陣列的長度一旦確定,就不能修改。
  • 我們可以直接通過下標(或索引)的方式呼叫指定位置的元素,速度很快。
  • 所有的陣列物件都有length屬性(java自帶的),用來獲取陣列中元素的個數。
  • 陣列元素是有下標(index)的,下標從 0 開始,也就是第一個元素的下標為 0,依次類推最後一個元素的下標為length - 1,我們可以通過陣列的下標來訪問陣列的元素。
  • 陣列當中如果儲存的是“java物件”的話,實際上儲存的是物件的“引用(記憶體地址)”,陣列中不能直接儲存java物件。

陣列的分類:

  • 按照維度:一維陣列、二維陣列、三維陣列、…(一維陣列較多,二維陣列偶爾使用!)
  • 按照元素的資料型別分:基本資料型別元素的陣列、引用資料型別元素的陣列(即對 象陣列)

陣列這種資料結構的優點和缺點是什麼?

優點查詢/查詢/檢索某個下標上的元素時效率極高。可以說是查詢效率最高的一個資料結構。為什麼檢索效率高?

  • 每一個元素的記憶體地址在空間儲存上是連續的。
  • 每一個元素型別相同,所以佔用空間大小一樣。
  • 知道第一個元素記憶體地址,知道每一個元素佔用空間的大小,又知道下標,所以通過一個數學表示式就可以計算出某個下標上元素的記憶體地址。直接通過記憶體地址定位元素,所以陣列的檢索效率是最高的。陣列中儲存100個元素,或者儲存100萬個元素,在元素查詢/檢索方面,效率是相同的,因為陣列中元素查詢的時候不會一個一個找,是通過數學表示式計算出來的。(算出一個記憶體地址,直接定位的。)

缺點:陣列元素的增刪效率低,陣列不能儲存大資料量。

  • 由於為了保證陣列中每個元素的記憶體地址連續,所以在陣列上隨機刪除或者增加元素的時候,效率較低,因為隨機增刪元素會涉及到後面元素統一向前或者向後位移的操作。對於陣列中最後一個元素的修改,是沒有效率影響的。
  • 因為很難在記憶體空間上找到一塊特別大的連續的記憶體空間。

一維陣列的宣告格式有以下兩種:

  • 陣列元素的型別[] 變數名稱
  • 陣列元素的型別 變數名稱[]
/陣列的宣告
int [] a;
Student[] stu
 
// 在一行中也可以宣告多個陣列,例如:
int[] a, b, c

陣列的初始化:在記憶體當中建立一個陣列,並且向其中賦予一些預設值。

  • 動態初始化(指定長度):在建立陣列的時候,直接指定陣列當中的資料元素個數。
  • 靜態初始化(指定內容)在建立陣列的時候,不直接指定資料個數多少,而是直接將具體的資料內容進行指定。

靜態初始化基本格式:

  • 資料型別[] 陣列名稱 = new 資料型別[] { 元素1, 元素2, ... };

示例程式碼

// 直接建立一個陣列,裡面裝的全都是int數字,具體為:5、15、25
 int[] arrayA = new int[] { 5, 15, 25, 40 };
 
// 建立一個陣列,用來裝字串:"Hello"、"World"、"Java"
String[] arrayB = new String[] { "Hello", "World", "Java" };

靜態初始化省略格式:

  • 資料型別[] 陣列名稱 = { 元素1, 元素2, ... };

示例程式碼

// 省略格式的靜態初始化
int[] arrayA = { 10, 20, 30 };

動態初始化陣列的格式:

  • 資料型別[] 陣列名稱 = new 資料型別[陣列長度];

解析含義:

  • 左側資料型別:也就是陣列當中儲存的資料,全都是統一的什麼型別
  • 左側的中括號:代表我是一個陣列
  • 左側陣列名稱:給陣列取一個名字
  • 右側的new:代表建立陣列的動作
  • 右側資料型別:必須和左邊的資料型別保持一致
  • 右側中括號的長度:也就是陣列當中,到底可以儲存多少個資料,是一個int數字

示例程式碼

package com.bjpowernode.javase.array;

/*
關於每個型別的預設值還有印象嗎?
    資料型別            預設值
    ----------------------------
    byte                0
    short               0
    int                 0
    long                0L
    float               0.0F
    double              0.0
    boolean             false
    char                \u0000
    引用資料型別          null

 什麼時候採用靜態初始化方式,什麼時候使用動態初始化方式呢?
    當你建立陣列的時候,確定陣列中儲存哪些具體的元素時,採用靜態初始化方式。
    當你建立陣列的時候,不確定將來陣列中儲存哪些資料,你可以採用動態初始化的方式,預先分配記憶體空間。
 */
public class ArrayTest02 {
    public static void main(String[] args) {
        // 宣告/定義一個陣列,採用動態初始化的方式建立
        int[] a = new int[4]; // 建立長度為4的int陣列,陣列中每個元素的預設值是0
        // 遍歷陣列
        for (int i = 0; i < a.length; i++) {
            System.out.println("陣列中下標為" + i + "的元素是:" + a[i]);
        }

        // 後期賦值
        a[0] = 1;
        a[1] = 100;
        a[2] = 111;
        a[3] = 222; // 注意下標別越界。

        for (int i = 0; i < a.length; i++) {
            System.out.println("陣列中下標為" + i + "的元素是:" + a[i]);
        }

        // 初始化一個Object型別的陣列,採用動態初始化方式
        Object[] objs = new Object[3]; // 3個長度,動態初始化,所以每個元素預設值是null
        for (int i = 0; i < objs.length; i++) {
            System.out.println(objs[i]);
        }

        System.out.println("===============================");

        String[] strs = new String[3];
        for (int i = 0; i < strs.length; i++) {
            System.out.println(strs[i]);
        }

        // 採用靜態初始化的方式
        String[] strs2 = {"abc", "def", "xyz"};
        for (int i = 0; i < strs2.length; i++) {
            System.out.println(strs2[i]);
        }

        // 儲存Object,採用靜態初始化呢?
        /*Object o1 = new Object();
        Object o2 = new Object();
        Object o3 = new Object();

        Object[] objects = {o1, o2, o3};*/

        Object[] objects = {new Object(), new Object(), new Object()};

        for (int i = 0; i < objects.length; i++) {
            /*Object o = objects[i];
            System.out.println(o);*/
            System.out.println(objects[i]);
        }
    }
}

陣列是引用型別,它的元素相當於類的成員變數,因此陣列一經 分配空間,其中的每個元素也被按照成員變數同樣的方式被隱式初始化。不同資料型別的陣列,預設值不同,具體如下所示

陣列遍歷: 就是將陣列中的每個元素分別獲取出來,就是遍歷。遍歷也是陣列操作中的基石

  • 陣列名 [ 索引 ]= 數值, 為陣列中的元素賦值

  • 變數= 陣列名 [ 索引 ] , 獲取出陣列中的元素

程式碼示例

package demo01;

public class ArrayTest01 {

    public static void main(String[] args) {
        // 宣告一個int型別的陣列,使用靜態初始化的方式
        int[] a = {1, 100, 10, 20, 55, 689};
        // 這是C++風格,不建議java中使用。
        //int a[] = {1, 100, 10, 20, 55, 689};

        // 所有的陣列物件都有length屬性
        System.out.println("陣列中元素的個數" + a.length);

        // 陣列中每一個元素都有下標
        // 通過下標對陣列中的元素進行存和取。
        // 取(讀)
        System.out.println("第一個元素 = " + a[0]);
        System.out.println("最後一個元素 = " + a[5]);
        System.out.println("最後一個元素 = " + a[a.length - 1]);

        // 存(改)
        // 把第一個元素修改為111
        a[0] = 111;
        // 把最後一個元素修改為0
        a[a.length - 1] = 0;

        System.out.println("第一個元素 = " + a[0]);
        System.out.println("最後一個元素 = " + a[5]);

        // 一維陣列怎麼遍歷呢?
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]); // i是從0到5,是下標
        }

        // 下標為6表示第7個元素,第7個元素沒有,下標越界了。會出現什麼異常呢?
        //System.out.println(a[6]); //ArrayIndexOutOfBoundsException(比較著名的異常。)

        // 從最後一個元素遍歷到第1個元素
        for (int i = a.length - 1; i >= 0; i--) {
            System.out.println("顛倒順序輸出-->" + a[i]);
        }
    }
}

陣列越界異常

我們訪問了陣列中不存在的索引,程式運 行後,將會丟擲 ArrayIndexOutOfBoundsException 陣列越界異常。在開發中,陣列的越界異常是不能出現的,一 旦出現了,就必須要修改我們編寫的程式碼。

/*
陣列的索引編號從0開始,一直到“陣列的長度-1”為止。
如果訪問陣列元素的時候,索引編號並不存在,那麼將會發生
陣列索引越界異常
ArrayIndexOutOfBoundsException
原因:索引編號寫錯了。
解決:修改成為存在的正確索引編號。
 */
        public class Demo01ArrayIndex {
 
            public static void main(String[] args) {
                int[] array = { 15, 25, 35 };
 
                System.out.println(array[0]); //15
        System.out.println(array[1]); // 25
        System.out.println(array[2]); // 35
 
        // 錯誤寫法
        // 並不存在3號元素,所以發生異常
        System.out.println(array[3]);
    }
 
}

陣列空指標異常

陣列變數名沒有儲存陣列的記憶體地址,也就不允許再運算元組了,因此執行的時候 會丟擲 NullPointerException 空指標異常。在開發中,陣列的越界異常是不能出現的,一旦出現了,就必須要修 改我們編寫的程式碼。

/*
所有的引用型別變數,都可以賦值為一個null值。但是代表其中什麼都沒有。
陣列必須進行new初始化才能使用其中的元素。
如果只是賦值了一個null,沒有進行new建立,
那麼將會發生:
空指標異常 NullPointerException
原因:忘了new
解決:補上new
 */
public class Demo02ArrayNull {
 
    public static void main(String[] args) {
        int[] array = null;
//        array = new int[3];
        System.out.println(array[0]);
    }
 
}

方法的引數型別是陣列的時候

package demo01;


// 當一個方法上,引數的型別是一個陣列的時候。
public class ArrayTest03 {
    // main方法的編寫方式,還可以採用C++的語法格式哦!
    public static void main(String args[]) {

        // 呼叫方法時傳一個陣列
        int[] x = {1,2,3,4};
        printArray(x);

        // 建立String陣列
        String[] stringArray = {"abc", "def", "hehe", "haha"};
        printArray(stringArray);


        printArray( new String[10]); // 10個null

        printArray(new int[4]);

    }

    public static void printArray(int[] array){
        for(int i = 0; i < array.length; i++){
            System.out.println(array[i]);
        }
    }

    public static void printArray(String[] args){
        for(int i = 0; i < args.length; i++){
            System.out.println("String陣列中的元素:" + args[i]);
        }
    }

}

main方法上面的“String[] args”有什麼用?

JVM呼叫main方法的時候,會自動傳一個String陣列過來。JVM呼叫的時候一定會傳一個String陣列過來。預設情況,JVM傳遞過來的陣列長度都是0,

 // 以下這一行程式碼表示的含義:陣列物件建立了,但是陣列中沒有任何資料。
String[] strs = new String[0];
String[] strs = {}; // 靜態初始化陣列,裡面沒東西。

其實這個陣列是留給使用者的,使用者可以在控制檯上輸入引數,這個引數自動會被轉換為“String[] args”。其實這個陣列是留給使用者的,使用者可以在控制檯上輸入引數,這個引數自動會被轉換為“String[] args”。例如這樣執行程式:java ArrayTest05 abc def xyz,那麼這個時候JVM會自動將“abc def xyz”通過空格的方式進行分離,分離完成之後,自動放到“String[] args”陣列當中。所以main方法上面的String[] args陣列主要是用來接收使用者輸入引數的。把abc def xyz 轉換成字串陣列:{"abc","def","xyz"}

程式碼示例

package com.bjpowernode.javase.array;

/*
模擬一個系統,假設這個系統要使用,必須輸入使用者名稱和密碼。
 */
public class ArrayTest06 {
    // 使用者名稱和密碼輸入到String[] args陣列當中。
    public static void main(String[] args) {
        if(args.length != 2){
            System.out.println("請輸入程式引數,引數中包括使用者名稱和密碼資訊,例如:zhangsan 123");
            return;
        }

        // 程式執行到此處說明使用者確實提供了使用者名稱和密碼。
        // 接下來你應該判斷使用者名稱和密碼是否正確。
        // 取出使用者名稱
        String username = args[0];
        // 取出密碼
        String password = args[1];

        // 假設使用者名稱是admin,密碼是123的時候表示登入成功。其它一律失敗。
        // 判斷兩個字串是否相等,需要使用equals方法。
        //if(username.equals("admin") && password.equals("123")){
        // 這樣編寫是不是可以避免空指標異常。
        // 採用以下編碼風格,及時username和password都是null,也不會出現空指標異常。(一條程式設計經驗。)
        if("admin".equals(username) && "123".equals(password)){
            System.out.println("登入成功,歡迎[" + username + "]回來");
            System.out.println("您可以繼續使用該系統....");
        }else{
            System.out.println("驗證失敗,使用者名稱不存在或者密碼錯誤!");
        }
    }
}

陣列引用型別深入理解

對於陣列來說,實際上只能儲存java物件的“記憶體地址”。陣列中儲存的每個元素是“引用”。陣列是父型別的時候,可以儲存子型別的物件引用。

package demo01;

/**
 * 一維陣列的深入,陣列中儲存的型別為:引用資料型別
 * 對於陣列來說,實際上只能儲存java物件的“記憶體地址”。陣列中儲存的每個元素是“引用”。
 */
public class ArrayTest {
    public static void main(String[] args) {

        // 建立一個Animal型別的陣列,陣列當中儲存Cat和Bird
        Cat c = new Cat();
        Bird b = new Bird();
        Animal[] anis = {c, b};

        //Animal[] anis = {new Cat(), new Bird()}; // 該陣列中儲存了兩個物件的記憶體地址。
        for (int i = 0; i < anis.length; i++) {
            // 這個取出來的可能是Cat,也可能是Bird,不過肯定是一個Animal
            // 如果呼叫的方法是父類中存在的方法不需要向下轉型。直接使用父型別引用呼叫即可。
            Animal an = anis[i];
            an.move();

            //Animal中沒有sing()方法。
            //anis[i].sing();

            // 呼叫子物件特有方法的話,需要向下轉型!!!
            if (anis[i] instanceof Cat) {
                Cat cat = (Cat) anis[i];
                cat.catchMouse();
            } else if (anis[i] instanceof Bird) {
                Bird bird = (Bird) anis[i];
                bird.sing();
            }
        }

    }
}

class Animal {
    public void move() {
        System.out.println("Animal move...");
    }
}

// 商品類
class Product {

}

// Cat是子類
class Cat extends Animal {
    public void move() {
        System.out.println("貓在走貓步!");
    }

    // 特有方法
    public void catchMouse() {
        System.out.println("貓抓老鼠!");
    }
}

// Bird子類
class Bird extends Animal {
    public void move() {
        System.out.println("Bird Fly!!!");
    }

    // 特有的方法
    public void sing() {
        System.out.println("鳥兒在歌唱!!!");
    }
}

一維陣列的擴容

陣列擴容要使用System類中的arraycopy方法

  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) :將陣列中指定的資料拷貝到另一個陣列中。陣列的拷貝動作是系統級的,效能很高。System.arraycopy方法具有5個引數,含義分別為:

程式碼示例

package demo01;

/**
 * 關於一維陣列的擴容。
 * 在java開發中,陣列長度一旦確定不可變,那麼陣列滿了怎麼辦?
 * 陣列滿了,需要擴容。
 * java中對陣列的擴容是:
 * 先新建一個大容量的陣列,然後將小容量陣列中的資料一個一個拷貝到大陣列當中。
 * <p>
 * 結論:陣列擴容效率較低。因為涉及到拷貝的問題。所以在以後的開發中請注意:儘可能少的進行陣列的拷貝。
 * 可以在建立陣列物件的時候預估計以下多長合適,最好預估準確,這樣可以減少陣列的擴容次數。提高效率。
 */
public class ArrayTest {
    public static void main(String[] args) {
        // java中的陣列是怎麼進行拷貝的呢?
        //System.arraycopy(5個引數);

        // 拷貝源(從這個陣列中拷貝)
        int[] src = {1, 11, 22, 3, 4};

        // 拷貝目標(拷貝到這個目標陣列上)
        int[] dest = new int[20]; // 動態初始化一個長度為20的陣列,每一個元素預設值0

        // 呼叫JDK System類中的arraycopy方法,來完成陣列的拷貝
        System.arraycopy(src, 0, dest, 0, src.length);
        for (int i = 0; i < dest.length; i++) {
            System.out.println(dest[i]);
        }

        // 陣列中如果儲存的元素是引用,可以拷貝嗎?當然可以。
        // 拷貝的時候是拷貝物件,還是拷貝物件的地址。(地址。)
        String[] strs = {"hello", "world!", "study", "java", "oracle", "mysql", "jdbc"};
        String[] newStrs = new String[20];
        System.arraycopy(strs, 0, newStrs, 0, strs.length);
        for (int i = 0; i < newStrs.length; i++) {
            System.out.println(newStrs[i]);
        }

    }
}

二維陣列

二維陣列的理解,我們可以看成是一維陣列 array1又作為另一個一維陣列array2的元素而存 在。其實,從陣列底層的執行機制來看,其實沒 有多維陣列。當陣列元素的型別是陣列時就成了多維陣列。二維陣列的初始化格式如下:

格式1:

  • 資料型別[][] 陣列名 = new 資料型別[二維陣列的長度/包含的一維陣列的個數][每個一維陣列的長度];

示例程式碼

class Demo01ArrayUse {
    public static void main(String[] args) {
        //定義了一個整型的二維陣列,其中包含3個一維陣列,每個一維陣列可以儲存5個整數
        int[][] arr = new int[3][5];
        //下標為0的位置上的一維陣列
        System.out.println(arr[0]);//[I@4554617c
       //如果要獲取具體的元素需要兩個下標
        System.out.println(arr[1][3]); //0
    }
 
}

格式2:

  • 資料型別[][] 陣列名 = new 資料型別[二維陣列的長度/包含的一維陣列的個數][];

示例程式碼

class Demo01ArrayUse {
    public static void main(String[] args) {
        // 二維陣列中有3個一維陣列。每個一維陣列都是預設初始化值null
        int[][] arr = new int[3][];
        //可以對這個三個一維陣列分別進行初始化
        arr[0] = new int[3];
        arr[1] = new int[1];
        arr[2] = new int[2];
        //非法  int[][]arr = new int[][3];
        //訪問第一個一維陣列的第2個元素
        System.out.println(arr[0][1]);//0
        //給此位置賦值
        arr[0][1] = 52;
        System.out.println(arr[0][1]);//52
    }
 
}

格式3:

  • 資料型別[][] 陣列名 = {{元素},{元素1, 元素2},……};

示例程式碼

class Demo01ArrayUse {
    public static void main(String[] args) {
        int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};
        /*
        定義一個名稱為arr的二維陣列,二維陣列中有三個一維陣列
        每一個一維陣列中具體元素也都已初始化
        第一個一維陣列 arr[0] = {3,8,2};
        第二個一維陣列 arr[1] = {2,7};
        第三個一維陣列 arr[2] = {9,0,1,6};
        第三個一維陣列的長度表示方式:arr[2].length;
         */
        
    }
 
}

二維陣列中的length屬性

        // 二維陣列
        // 以下程式碼當中:裡面的是4個一維陣列。
        int[][] a = {
                {100, 200, 300},
                {30, 20, 40, 50, 60},
                {6, 7, 9, 1},
                {0}
        };
        //獲取二維陣列中一維陣列的個數
        System.out.println(a.length); // 4
        //獲取二維陣列中index為a[0]中一維陣列中元素的個數
        System.out.println(a[0].length); // 3
        System.out.println(a[1].length); // 5
        System.out.println(a[2].length); // 4
        System.out.println(a[3].length); // 1

二維陣列中元素的:讀和改。

/*
關於二維陣列中元素的:讀和改。

    a[二維陣列中的一維陣列的下標][一維陣列的下標]

    a[0][0]:表示第1個一維陣列中的第1個元素。

    a[3][100]:表示第4個一維陣列中的第101個元素。

    注意:對於a[3][100]來說,其中 a[3] 是一個整體。[100]是前面a[3]執行結束的結果然後再下標100.
 */
public class ArrayTest10 {
    public static void main(String[] args) {
        // 二維陣列
        int[][] a = {
                {34,4,65},
                {100,200,3900,111},
                {0}
        };

        // 請取出以上二位數中的第1個一維陣列的第一個元素。
        System.out.println(a[0][0]);

        // 取出第2個一維陣列當中第3個元素
        System.out.println("第二個一維陣列中第三個元素:" + a[1][2]);

        // 取出第3個一維陣列當中第1個元素
        System.out.println("第3個一維陣列中第1個元素:" + a[2][0]);

        // 改
        a[2][0] = 11111;
        System.out.println(a[2][0]);

        // 注意別越界。
        //java.lang.ArrayIndexOutOfBoundsException
        //System.out.println(a[2][1]);
    }
}

二維陣列的遍歷

package com.bjpowernode.javase.array;

/*
二維陣列的遍歷
 */
public class ArrayTest11 {
    public static void main(String[] args) {

        // 二維陣列
        String[][] array = {
                {"java", "oracle", "c++", "python", "c#"},
                {"張三", "李四", "王五"},
                {"lucy", "jack", "rose"}
        };

        // 遍歷二維陣列,取出二維陣列中有幾個一維陣列
        for(int i = 0; i < array.length; i++){ // 外層迴圈3次。(負責縱向。)
            // 負責遍歷一維陣列,取出每個一維陣列中的具體元素
            for(int j = 0; j < array[i].length; j++){
                System.out.print(array[i][j] + " ");
            }
             // 輸出換行符
            System.out.println();
        }
    }
}

Arrays工具類

java.util.Arrays類即為運算元組的工具類,包含了用來運算元組(比 如排序和搜尋)的各種方法。

陣列中常用的演算法

氣泡排序

package com.bjpowernode.javase.array;

/*
氣泡排序演算法
    1、每一次迴圈結束之後,都要找出最大的資料,放到參與比較的這堆資料的最右邊。(冒出最大的那個氣泡。)
    2、核心:
        拿著左邊的數字和右邊的數字比對,當左邊 > 右邊的時候,交換位置。

原始資料:
3, 2, 7, 6, 8
第1次迴圈:(最大的跑到最右邊。)
2, 3, 7, 6, 8 (3和2比較,2 < 3,所以2和3交換位置)
2, 3, 7, 6, 8 (雖然不需要交換位置:但是3和7還是需要比較一次。)
2, 3, 6, 7, 8 (7和6交換位置)
2, 3, 6, 7, 8 (雖然不需要交換位置:但是3和7還是需要比較一次。)

經過第1次迴圈,此時剩下參與比較的資料:2, 3, 6, 7
第2次迴圈:
2, 3, 6, 7 (2和3比較,不需要交換位置)
2, 3, 6, 7 (3和6比較,不需要交換位置)
2, 3, 6, 7 (6和7比較,不需要交換位置)

經過第2次迴圈,此時剩下參與比較的資料:2, 3, 6
第3次迴圈:
2, 3, 6 (2和3比較,不需要交換位置)
2, 3, 6 (3和6比較,不需要交換位置)

經過第3次迴圈,此時剩下參與比較的資料:2, 3
第4次迴圈:
2, 3 (2和3比較,不需要交換位置)

 */
public class BubbleSort {
    public static void main(String[] args) {

        // 這是int型別的陣列物件
        //int[] arr = {3, 2, 7, 6, 8};
        int[] arr = {9, 8, 10, 7, 6, 0, 11};

        // 經過氣泡排序演算法對以上陣列中元素進行排序
        // 氣泡排序演算法的核心是什麼?

        // 7條資料,迴圈6次。以下的程式碼可以迴圈6次。(氣泡排序的外層迴圈採用這種方式)
        //int count = 0;
        int count2 = 0;
        for(int i = arr.length-1; i > 0; i--){
            for(int j = 0; j < i; j++){
                // 不管是否需要交換位置,總之是要比較一次的。
                //count++;
                if(arr[j] > arr[j+1]){
                    // 交換位置。
                    // arr[j] 和 arr[j+1] 交換
                    int temp;
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    count2++;
                }
            }
        }

        //System.out.println("比較次數:" + count);
        System.out.println("交換位置的次數:" + count2); //13
        // 輸出結果
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

選擇排序演算法

package com.bjpowernode.javase.array;
/*
選擇排序:
    每一次從這堆“參與比較的資料當中”找出最小值,
    拿著這個最小值和“參與比較的這堆最前面的元素”交換位置。

    選擇排序比氣泡排序好在:每一次的交換位置都是有意義的。

    關鍵點:選擇排序中的關鍵在於,你怎麼找出一堆資料中最小的。
        3 2 6 1 5
        假設:
            第一個3是最小的。
            3和2比較,發現2更小,所以此時最小的是2.

            繼續拿著2往下比對,2和6比較,2仍然是最小的。
            繼續拿著2往下比對,2和1比對,發現1更小,所以此時最小的是1.
            繼續拿著1往下比對,1和5比對,發現1還是小的,所以1就是最小的。

            拿著1和最左邊的3交換位置。
       2 6 3 5
       假設:
        第一個2是最小的。
        ...

      6 3 5
        假設6是最小的:
        6和3比對,發現3更小,所以此時最小的是3.
        ...
 */
public class SelectSort {
    public static void main(String[] args) {

        //int[] arr = {3, 1, 6, 2, 5};
        int[] arr = {9, 8, 10, 7, 6, 0, 11};

        int count = 0;
        int count2 = 0;

        // 選擇排序
        // 5條資料迴圈4次。(外層迴圈4次。)
        for(int i = 0; i < arr.length - 1; i++){
            // i的值是0 1 2 3
            // i正好是“參加比較的這堆資料中”最左邊那個元素的下標。
            //System.out.println(i);
            // i是一個參與比較的這堆資料中的起點下標。
            // 假設起點i下標位置上的元素是最小的。
            int min = i;
            for(int j = i+1; j < arr.length; j++){
                count++;
                //System.out.println("===>" + j);
                if(arr[j] < arr[min]){
                    min = j; //最小值的元素下標是j
                }
            }

            // 當i和min相等時,表示最初猜測是對的。
            // 當i和min不相等時,表示最初猜測是錯的,有比這個元素更小的元素,
            // 需要拿著這個更小的元素和最左邊的元素交換位置。
            if(min != i){
                // 表示存在更小的資料
                // arr[min] 最小的資料
                // arr[i] 最前面的資料
                int temp;
                temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
                count2++;
            }
        }

        // 氣泡排序和選擇排序實際上比較的次數沒變。
        // 交換位置的次數減少了。
        System.out.println("比較次數" + count); // 21
        System.out.println("交換次數:" + count2); // 5

        // 排序之後遍歷
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

//1 2 3 4 5
//假設1是最小的,結果1確實是最小的,就不需要交換位置。

二分法排序

package com.bjpowernode.javase.array;

/*
1、陣列工具類:自己寫的。不是SUN的。

2、關於查詢演算法中的:二分法查詢。
    10(下標0) 11 12 13 14 15 16 17 18 19 20(下標10)   arr陣列。

    通過二分法查詢,找出18這個元素的下標:
        (0 + 10) / 2 --> 中間元素的下標: 5

    拿著中間這個元素和目標要查詢的元素進行對比:
        中間元素是:arr[5] --> 15
        15 < 18(被查詢的元素)
        被查詢的元素18在目前中間元素15的右邊。
        所以開始元素的下標從0變成 5 + 1.

    再重新計算一箇中間元素的下標:
        開始下標是:5 + 1
        結束下標是:10
        (6 + 10) / 2 --> 8

    8下標對應的元素arr[8]是18
        找到的中間元素正好和被找的的元素18相等,表示找到了:下標為8

    二分法查詢的終止條件:一直折半,直到中間的那個元素恰好是被查詢的元素。

3、二分法查詢演算法是基於排序的基礎之上。(沒有排序的資料是無法查詢的。)

 */
public class ArrayUtil {
    public static void main(String[] args) {

        int[] arr = {100,200,230,235,600,1000,2000,9999};

        // 找出arr這個陣列中200所在的下標。
        // 呼叫方法
        int index = binarySearch(arr, 230);
        System.out.println(index == -1 ? "該元素不存在!" : "該元素下標" + index);
    }

    /**
     * 從陣列中查詢目標元素的下標。
     * @param arr 被查詢的陣列(這個必須是已經排序的。)
     * @param dest 目標元素
     * @return -1表示該元素不存在,其它表示返回該元素的下標。
     */
    public static int binarySearch(int[] arr, int dest) {
        // 開始下標
        int begin = 0;
        // 結束下標
        int end = arr.length - 1;
        // 開始元素的下標只要在結束元素下標的左邊,就有機會繼續迴圈。
        while(begin <= end) {
            // 中間元素下標
            int mid = (begin + end) / 2;
            if (arr[mid] == dest) {
                return mid;
            } else if (arr[mid] < dest) {
                // 目標在“中間”的右邊
                // 開始元素下標需要發生變化(開始元素的下標需要重新賦值)
                begin = mid + 1; // 一直增
            } else {
                // arr[mid] > dest
                // 目標在“中間”的左邊
                // 修改結束元素的下標
                end = mid - 1; // 一直減
            }
        }
        return -1;
    }

}

 

相關文章