面試常問的排序算法分析及代碼實現

1. 選擇排序

選擇排序就是在遍歷數組的過程中,每次都選出從當前位置到數組末尾的最小值,並把選出來的最小值和當前位置交換。

	//選擇排序
    public static void select_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        for(int i = 0; i < n; i++){
            int minIndex = i;
            for(int j = i + 1; j < n; j++){
                min = arr[minIndex] > arr[j] ? j : minIndex;
            }
            if(minIndex != i) swap(arr, i, minIndex);
        }
    }
  • 時間複雜度:O(n的平方)
  • 空間複雜度:O(1)
  • 可以做到穩定

2. 冒泡排序

冒泡排序就是在遍歷數組的時候,讓當前位置元素arr[j] 和後一個元素arr[j+1] 進行比較,將大的元素放在後面,最後遍歷完,最後一個元素就是最大值,下一次遍歷,只需到上一次最大值的位置(不包括這個位置),這樣,每次找到的都是每一段元素中的最大值。

 	//冒泡排序
    public static void bubble_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        for(int i = n - 1; i >= 0; i--){
        	//注意遍歷到i,不包括i,因爲i位置是最大值
            for(int j = 0; j < i; j++){
                if(arr[j] > arr[j + 1]) swap(arr, j, j + 1);
            }
        }
    }
  • 時間複雜度:O(n的平方)
  • 空間複雜度:O(1)
  • 可以做到穩定

3. 插入排序

插入排序就是遍歷數組的時候,假設當前位置之前的數組都是有序的,如果當前位置的數小於前一個數就交換,如果不小於,就結束插入,繼續遍歷數組中下一個位置

 	//插入排序
    public static void insert_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        //注意這裏是從1開始的
        for(int i = 1; i < n; i++){
        	//若果arr[j] >= arr[j-1],這個循環就結束了
            for(int j = i; j > 0 && arr[j] < arr[j - 1]; j--){
               swap(arr, j, j - 1);
            }
        }
    }
  • 時間複雜度:O(n的平方)
  • 空間複雜度:O(1)
  • 可以做到穩定

4. 歸併排序

歸併排序就是利用分治思想,先將數組切成兩半,然後合併。利用遞歸,將數組切成兩半,再將每一半切成兩半,…,最後分成每一半隻有一個元素,然後,一半一半(這兩半是同一次拆分成的)的合併,合併的過程中排序。

//歸併排序
    public static void merge_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        merge_sort(arr, 0, arr.length - 1);
    }
    private static void merge_sort(int[] arr, int left, int right){
        //切到只有一個元素
        if(left >= right) return;
        int mid = left + (right - left) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr,mid + 1, right);
        merge(arr, left, mid, right);
    }
	//合併,left爲左半部分開始的索引,right爲右半部分結束的索引
    private static void merge(int[] arr, int left, int mid, int right){
        //臨時數組,暫時存放數據
        int[] help = new int[right - left + 1];
        int count = 0;
       	//i爲左半部分的開始,j爲右半部分的開始
        int i = left, j = mid + 1;
        //將左半部分和右半部分進行比較,較小的數先放到數組
        while(i <= mid && j <= right){
            help[count++] = arr[i] > arr[j] ? arr[j++]:arr[i++];
        }
        //可能有左半部分或者右半部分的數沒有存到help數組
        //判斷左半部分是否都已存到help數組,沒有的話,就存到數組
        while(i <= mid){
            help[count++] = arr[i++];
        }
        //判斷右半部分是否都已存到數組,沒有的話,就存到數組
        while(j <= right){
            help[count++] = arr[j++];
        }
        //將排好序的數組放到原數組中
        for(int k = 0; k < help.length; k++){
            arr[left++] = help[k];
        }
    }
  • 時間複雜度:O(nlogn)
  • 空間複雜度:O(n)
  • 可以做到穩定

5. 快速排序

快速排序就是找出一個參照物,每次將大於參照物的放在右邊,小於參照物的放在左邊,之後,再分別從參照爲的兩邊找參照物,重複上述過程,整個過程也是遞歸的。

	//快速排序
    public static void quick_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        quick_sort(arr,0, arr.length - 1);
    }
    private static void quick_sort(int[] arr, int left, int right){
        if(left >= right) return;
        //快速排序的效率跟選的參照物有很大的關係,所以,這裏,隨機選一個,和數組
        //首元素交換
        swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
        int mid = partition(arr, left, right);
        quick_sort(arr, left, mid - 1);
        quick_sort(arr, mid + 1, right);
    }

    private static int partition(int[] arr, int left, int right){
        int model = arr[left];
        while(left < right){
            //當arr[right]>model的時候,讓right一直左移
            //注意這裏要判斷left<right,有可能在移動的時候,left = right
            while(left < right && arr[right] > model){
                right--;
            }
            //上面循環結束,可能是nums[right]<model,這樣的話,就交換left和right的值
            //也可能是left == right,所以判斷一下
            if(left < right){
                arr[left++] = arr[right];
            }
            while(left < right && arr[left] < model){
                left++;
            }
            if(left < right) {
                arr[right--] = arr[left];
            }
        }
            //當right=left的時候,這個位置是空,把model放進去
            arr[left] = model;
            return left;
    }
  • 時間複雜度:O(nlogn)
  • 空間複雜度:O(logn)
  • 常規實現不穩定

快速排序的時間複雜度主要與選取的參照物有關,可以隨機選出一個值作爲參照物,下面是快速排序的另一種寫法,這種寫法和上面哪一種寫法的partition過程不一樣。

 //快速排序
    public static void quick_sort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        quick_sort(arr,0, arr.length - 1);
    }
    private static void quick_sort(int[] arr, int left, int right){
        if(left >= right) return;
        //快速排序的效率跟選的參照物有很大的關係,所以,這裏,隨機選一個,和數組
        //首元素交換
        //swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
        //隨機選一個數,和數組最後一個元素交換,作爲參照物
        swap(arr, right, left + (int)(Math.random() * (right - left + 1)));
        //int mid = partition(arr, left, right);
        int mid = partition2(arr, left, right);
        quick_sort(arr, left, mid - 1);
        quick_sort(arr, mid + 1, right);
    }

    private static int partition2(int[] arr, int left, int right) {
        //小於等於target的邊界
        int small = left - 1;
        //大於target的邊界,注意最後一個數是target,最後交換
        int big = right;
        while(left < big){
            if(arr[left] < arr[right]){
                //注意left++
                swap(arr, ++small, left++);
            }else if(arr[left] > arr[right]){
                swap(arr, --big, left);
            }else{
                left++;
            }

        }
        //最後,將arr[big]和比較的值arr[right]交換
        swap(arr, big, right);
        //最後返回big的索引
        return big;
    }

6. 堆排序

數據結構中的堆是一個完全二叉樹,一個數組,可以看成是一個完全二叉樹,按層次遍歷的順序存放,一個元素(索引爲i)的父節點是 (i- 1)/ 2, 左孩子的索引爲(2 * i + 1)。如果堆中,每一個根節點(子樹中)都所在樹中的最大值,那麼這個堆就是最大堆,堆排序就是利用最大堆進行排序的。大致過程是,將數組構建成一個最大堆,將數組最後一個元素(n -1)和第一個元素交換,再將除最後一個元素之外的數組調整爲最大堆,重複上述過程。

	//堆排序
    public static void heap_sort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        //構造最大堆
        for(int i = 0; i < arr.length; i++){
            heap_insert(arr, i);
        }

        int size = arr.length;
        //將第一個元素和最後一個元素交換
        swap(arr, 0, --size);
        while(size > 0){
            //調整堆爲最大堆
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }
    //使用插入的方式構建最大堆
    private static void heap_insert(int[] arr, int index){
        //當孩子結點大於父節點的時候交換
        while(arr[index] > arr[(index - 1) / 2]){
            swap(arr, index, (index - 1) / 2);
            //父節點和子節點交換完之後,將父節點的索引賦給index,
            //再去判斷父節點和父節點的父節點的大小,所以這裏用的是while
            index = (index - 1) / 2;
        }
    }
    //調整堆,注意size是數組個數,不是索引
    private static void heapify(int[] arr, int index,int size){
        int left = index * 2 + 1;
        while(left < size){
            int largest = left;
            //找出左右孩子中最大的一個
            if(left + 1 < size && arr[left + 1] > arr[left]){
                largest++;
            }
            //如果根節點大於左右孩子,那麼說明已經是最大堆了,
            //因爲左右孩子是左右子樹的根節點,是子樹中的最大值
            if(arr[index] > arr[largest]){
                break;
            }
            swap(arr, index, largest);
            //將左右子樹中最大值的索引賦給index,調整子樹爲最大堆
            index = largest;
            left = index * 2 + 1;
        }
    }
  • 時間複雜度:O(nlogn)
  • 空間複雜度:O(1)
  • 不穩定

7. 計數排序

計數排序其實是一種特殊的桶排序,就是先求出數組中的最大值max和最小值min,然後,建max-min +1個桶,假入是數組,數組長度就是max-min+1,bucket數組中存的就是原數組中第i(i爲桶的索引,數組元素-min得到)小的數的個數,統計完之後,根據桶(bucket)中的個數,將min+i放到原數組中,對應的桶中記錄的有幾個就放幾個。計數排序和前面六中排序不同的是,計數排序不是基於比較的排序。

//計數排序
    public static void count_sort(int [] arr){
        if(arr == null || arr.length < 2) return;
        int min = arr[0];
        int max =0;
        //找出數組中的最大值和最小值
        for(int i = 0; i < arr.length; i++){
            max = Math.max(max, arr[i]);
            min = Math.min(min, arr[i]);
        }
        //bucket數組中存放的是第i小的個數
        int[] bucket = new int[max - min + 1];
        //統計元素出現的次數
        for(int i = 0; i < arr.length; i++){
            bucket[arr[i] - min]++;
        }
        int j = 0;
        //將bucket中的數寫回原數組
        for(int i = 0; i < bucket.length; i++){
            while(bucket[i]-- > 0){
                arr[j++] = min + i;
            }
        }
    }

8. java中的Arrays.sort()

java1.8中,當數組長度小於47的時候,使用的是冒泡排序,當數組長度大於47,小於286的時候使用雙軸快速排序,並且當快速排序遞歸時,如果長度小於47,直接使用插入排序,大於286的時候,使用的是歸併排序,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章