【Java資料結構與演算法】簡單排序、二分查詢和異或運算

gonghr 發表於 2021-07-20
Java 演算法 資料結構

簡單排序

選擇排序

概念

image
首先,找到陣列中最小的那個元素,其次,把它和陣列的第一個元素交換位置(如果第一個元素就是最小的元素那麼它就和自己交換)。再次,在剩下的元素中找到最小的元素,將它與陣列的第二個元素交換位置。如此往復,直到將整個陣列排序。這種方法叫做選擇排序,因為它在不斷地選擇剩餘元素中地最小者。

程式碼實現

   public static void SelectionSort(int[] arr){
        if(arr==null||arr.length<2) return;     //去除多餘情況
        int N = arr.length;
        for (int i = 0; i < N-1; i++) {
            int minIndex = i;
            for (int j = i+1; j < N; j++){
                if(arr[j] < arr[minIndex]) minIndex = j;  //更新每一輪最小元素的下標
            }
            swap(arr,i,minIndex);
        }
    }
    public static void swap(int[] arr, int i, int j){     //交換元素
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

複雜度分析

選擇排序過程中,0~N-1 上任意位置i都要進行一次交換和N-1-i次比較。因此總共有N次交換和(N-1)+(N-2)+……+1=N(N-1)/2~N^2/2次比較。
也就是O(N^2)

氣泡排序

概念

image
從第一個元素開始遍歷陣列每兩個元素一組比較,如果不滿足從小到大的排序規則則進行交換,這樣最後可以得到最大元素在陣列最後的位置排好。如此往復,直到整個陣列排好。

程式碼實現

    public static void sort(int[] arr){
        if(arr==null||arr.length<2) return;      //去除多餘情況
        for(int i = arr.length - 1; i >= 0;i--){ //外層反向遍歷
            for(int j = 0;j < i; j++){
                if(arr[j] > arr[j+1])
                    swap(arr,j,j+1);
            }
        }
    }
    public static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

複雜度分析

總的比較次數是(N-1)+(N-2)+……+1=N(N-1)/2~N^2/2
也就是O(N^2)

插入排序

概念

image
從左向右遍歷,每次把遍歷到的元素放到前面已經排好順序的陣列的合適位置。如此往復,直到整個陣列排好。

程式碼實現

    public static void sort(int[] arr){
        if(arr==null||arr.length<2) return;     //去除無效情況
        for(int i = 1; i < arr.length; i++){    //i從1開始,認為i為1時已經排好
            for(int j = i-1; j >= 0 && arr[j] > arr[j+1]; j--)
                swap(arr,j,j+1);
        }
    }
    public static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

複雜度分析

總的比較和交換次數都是(N-1)+(N-2)+……+1=N(N-1)/2~N^2/2
也就是O(N^2)

二分查詢

左側邊界二分查詢

在arr上,找滿足>=value的最左位置

   public static int nearestIndex(int[] arr, int value) {
        int L = 0;
        int R = arr.length - 1;
        int index = -1;
        while (L <= R) {      //注意等號
            int mid = L + ((R - L) >> 1);
            if (arr[mid] >= value) {  //注意
                index = mid;
                R = mid - 1;
            } else {
                L = mid + 1;
            }
        }
        if(index == -1) return  -1;     //index可能為-1,防ArrayOutOfIndexException
        else if(arr[index] == value)
            return index;
        else return -1;
    }

右側邊界二分查詢

在arr上,找滿足>=value的最右位置

    public static int nearestIndex(int[] arr, int value) {
        int L = 0;
        int R = arr.length - 1;
        int index = -1;
        while (L <= R) {  
            int mid = L + ((R - L) >> 1);
            if (arr[mid] <= value) {  //注意
                index = mid;
                L = mid + 1;
            } else {
                R = mid - 1;
            }
        }
        if(index == -1) return  -1;     //index可能為-1,防止ArrayOutOfIndexException
        else if(arr[index] == value)
            return index;
        else return -1;
    }

總結

  • 左側邊界二分查詢
    先寫if (arr[mid] >= value)
  • 右側邊界二分查詢
    先寫if (arr[mid] <= value)
  • 別忘了最後對index為-1做特殊處理,防止陣列越界訪問

異或運算

公式

底層表現在二進位制上,異或運算使得相同數字得0,不同數字得1
1 ^ 1 = 0
0 ^ 0 = 0
1 ^ 0 = 1

1 1 0 1 0 1 1 0
^ 0 1 0 0 1 1 0 0
= 1 0 0 1 1 0 1 0

0^N=N
N^N=0
滿足交換律和結合律

應用

交換

a=a^b
b=a^b
a=a^b
實現了交換a,b的值
注意:前提是a,b有不同的記憶體空間。如果a,b是陣列下標,那麼當a=b的時候不能使用該方法,因為a,b代表陣列一塊相同的空間。
優點:位運算速度快,效率高

常考面試題

第一題

在一組數中,只有一種數出現了奇數次,其他型別的數出現了偶數次,問這個出現奇數次的數是什麼,時間複雜度小於O(N)

解析:藉助異或運算性質,N^N=0,只需要讓所有數連續異或,最後的結果就是答案

    public static void printOddTimesNum1(int[] arr) {
		int eO = 0;
		for (int cur : arr) {
			eO ^= cur;
		}
		System.out.println(eO);
	}

第二題

在一組數中,有兩種數出現了奇數次,其他型別的數出現了偶數次,問這兩個出現奇數次的數是什麼,時間複雜度小於O(N)

解析:設這兩個數是a和b,所以所有數字第一次連續異或可以得到a^b。下面我們只需要想辦法搞出a或者b。
舉個例子,我們成功搞出a來,那麼異或運算aba就可以得到b。注意到a和b前提是肯定不相同,那麼在二進位制的層面看這兩個數字,它們肯定至少有一位數字是不一樣的,一個是0,一個是1
a:…………0001…………
b:…………0000…………
下面要做的就是把所有出現了兩次的數字根據ab這一位不同的數字進行分組。
這一位數字為0的數字為一組,這一位數字為1的數字為一組
image
讓其中一組進行連續異或,比如這一位為1的這一組數字連續異或,出現偶數次的數字異或為0,最後只剩下a,我們就成功得到了a。
最後進行a ^ b ^ a就可以得到b
本題預設尋找a,b從右到左第一次出現的不同二進位制位的位置,這也是本題的關鍵,等價於求ab異或後從右向左第一次出現1的位置。
記住一個公式:原碼和補碼做與運算可以得到二進位制數從右到左第一次出現1的位置為1,其他位置為0的數

1010111100
& 0101000100
= 0000000100
    public static void printOddTimesNum2(int[] arr) {
		int eO = 0, eOhasOne = 0;
		for (int curNum : arr) {
			eO ^= curNum;
		}
		int rightOne = eO & (~eO + 1);  原碼和補碼做與運算
		for (int cur : arr) {
			if ((cur & rightOne) == 1) { 該位是1的為一組
				eOhasOne ^= cur;
			}
		}
		System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
	}