【知識總結】排序

不具有穩定性的排序:選擇排序、快速排序、堆排序java

具有穩定性的排序:冒泡排序、插入排序、歸併排序(nlogn)api

選擇排序

每次遍歷找到數組中最小的元素的索引,依次交換。數組

public int[] sortArray (int[] nums) {   
    for(int i=0; i<nums.length; i++) {
        int minIndex = i;
        for(int j=i+1; j<nums.length; j++) {
            minIndex = nums[j] < nums[minIndex] ? j : minIndex; 
        }
        swap(nums, i, minIndex);
    }
    return nums;
}

快速排序

  • 快速排序是由上到下的,先分區,而後再處理子問題。

基本思路:ui

  1. 先從數組中找一個基準數
  2. 讓其餘比它大的元素移動到數組一邊,比他小的元素移動到數組另外一邊。從而把數組拆解成兩部分。
  3. 再對左右區間重複第二部,直到各區間只有一個數。

low 指針找到大於 pivot 的元素,hight 指針找到小於 pivot 的元素,而後兩個元素交換位置,最後再將基準數歸位。spa

  • 填坑法
public void quickSort (int[] nums, int low, int high) {
    if(low < high) {
        int index = partition(nums, low, high);
        quickSort(nums, low, index-1);
        quickSort(nums, index+1, high);
    }
}

public int partition(int[] nums, int low, int high) {

    int pivot = nums[low];

    while(low < high) {
        while(low<high && nums[high] >= pivot) {
            high--;
        }
        nums[low] = nums[high];
        while(low<high && nums[low] <= pivot) {
            low++;
        }
        nums[high] = nums[low];
    }
    nums[low] = pivot;
    return low;
}
  • 交換法

其實這種方法,算是對上面方法的挖坑填坑步驟進行合併,low 指針找到大於 pivot 的元素,hight 指針找到小於 pivot 的元素,而後兩個元素交換位置,最後再將基準數歸位。指針

public void quickSort (int[] nums, int low, int high) {
    if(low < high) {
        int index = partition(nums, low, high);
        quickSort(nums, low, index-1);
        quickSort(nums, index+1, high);
    }
}

public int partition(int[] nums, int low, int high) {
    int pivot = nums[low];
    int start = low;    //記錄low指針

    while(low < high) {
        while(low < high && nums[high] >= pivot)  high--;
        while(low < high && nums[low] <= pivot)    low++;

        if(low >= high) {
            break;
        }
              
        swap(nums, low, high); 
    }
    //基準值歸位
    swap(nums, start, low);
    return low;
}

    public void swap(int[] nums, int i, int j) {
        if(i == j) return;
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }

冒泡排序

兩兩比較相鄰記錄的關鍵字,若是是反序則交換,直到沒有反序爲止。code

public int[] sortArray(int[] nums) {
        
    for(int i=0; i<nums.length; i++) {
        for(int j=0; j<nums.length-i-1; j++) {
            if(nums[j] > nums[j+1]) {
                swap(nums, j, j+1);
            }
        }
    }
    return nums;
}


private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

冒泡排序能夠用 標識flag 改進,若是交換則標識位發生變化,若是標識爲沒有變化,說明已經排序好了!排序

插入排序

不斷地與前面的數字比較,若是前面的數字比它大,它就和前面的數字交換位置。遞歸

public int[] sortArray(int[] nums) {
    for(int i=1; i<nums.length; i++) {
        int j = i;  //記錄當前數字下標

        //當前數字比前一個數字小,則交換
        while(j >= 1 && nums[j] < nums[j-1]) {
            swap(nums, j, j-1);

            j--;  //繼續向前一個元素比較
        }
    }
    return nums;
}

public void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

歸併排序

  • 歸併排序採用的是 分治法 思想。
  • 總體就是簡單的遞歸,左邊排好序,右邊排好序,再讓其總體有序。怎麼總體有序呢?準備一個輔助空間,看看兩邊排好序的左邊,比較誰小就誰先進入這個輔助空間(外排序方法)
  • 歸併排序中的比較次數是全部排序中最少的,它一開始是不斷地劃分,比較只發生在合併各個有序的子數組時。

public int[] sortArray (int[] nums) {   
    int[] temp = new int[nums.length];   
    mergeSort(nums, 0, nums.length-1, temp);
    return nums;     
}

public void mergeSort(int[] nums, int low, int high, int[] temp) {
    int mid = (low + high) / 2;
    if(low < high) {
        //對左右進行拆分
        mergeSort(nums, low, mid, temp);
        mergeSort(nums, mid+1, high, temp);
        //合併
        merg(nums, low, high, mid, temp);
    }
}

public void merg(int[] nums, int low, int high, int mid, int[] temp) {
    int index = 0;
    int i = low;        //左邊序列起始索引
    int j = mid + 1;    //右邊序列起始索引
        
    while(i <= mid && j <= high) {
        if(nums[i] <= nums[j]) {            //這裏最好用 <=
            temp[index++] = nums[i++];
        } else {
            temp[index++] = nums[j++];
        }
    }

    //若左邊序列還有剩餘
    while(i <= mid) {
        temp[index++] = nums[i++];
    }

    //若右邊序列還有剩餘
    while(j <= high) {
        temp[index++] = nums[j++];
    }

    //把temp數組的賦值給原數組nums
    for(int t=0; t<index; t++) {
        nums[low + t] = temp[t];
    }
}

時間複雜度爲:O(NlogN)索引

額外空間複雜度爲:O(N)

堆排序

  • 二叉堆,必須是徹底二叉樹,並且二叉堆中的每個節點,都必須大於等於(或小於等於)其子樹中每一個節點的值。
  • 由於堆是徹底二叉樹,因此咱們徹底能夠用數組存儲。將根節點的下標視爲 0,則徹底二叉樹有以下性質:

    • 它的左子節點下標:2i + 1
    • 它的右子節點下標:2i + 2
    • 對於有 n 個元素的徹底二叉樹(n >2),它的最後一個非葉子結點的下標:n/2 - 1

構建大頂堆:將整個數列的初始狀態視做一棵徹底二叉樹,自底向上調整樹的結構,使其知足大頂堆的要求。

變量 heapSize 用來記錄還剩下多少個數字沒有排序完成,每當交換了一個堆頂的數字,heapSize 就會減 1。在 maxHeapify 方法中,使用 heapSize 來限制剩下的選手,不要和已經躺在數組最後,當過冠軍的人比較,省得被暴揍。

  1. 構建初始大頂堆,從最後一個非葉子節點開始堆化。
  2. 進入循環,將最大值放到數組的最後,而且堆化調整位置。循環最大索引次,排序完畢。
public int[] sortArray(int[] nums) {
    //構建初始大頂堆
    buildMaxHeap(nums);

    for(int i=nums.length - 1; i >= 0; i--) {
        swap(nums, 0, i);   //將最大值放到數組的最後
        maxHeapify(nums, 0, i); //調整剩餘數組,使其知足大頂堆
    }
    return nums;
}


public void buildMaxHeap(int[] nums) {
    // 從最後一個非葉子結點開始調整大頂堆,最後一個非葉子結點的下標就是 arr.length / 2-1
    for(int i = nums.length / 2 - 1; i>=0; i--) {
        maxHeapify(nums, i, nums.length);
    }
}

//調整大頂堆(第三個參數表示剩餘未排序的數字的數量,也就是剩餘堆的大小)
public void maxHeapify(int[] nums, int i, int heapSize) {
    int l = 2 * i + 1;      // 左子結點下標
    int r = l + 1;          // 右子結點下標

    int largest = i;        //記錄根結點和兩個兒子之間的最大值

    if(l < heapSize && nums[l] > nums[largest])  largest = l;
    if(r < heapSize && nums[r] > nums[largest])  largest = r;

    //若是有子結點大於根結點,則交換,並用 largest 去再次調整大頂堆。
    if(largest != i) {
        swap(nums, i, largest);
        maxHeapify(nums, largest, heapSize);
    }
}

public void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
相關文章
相關標籤/搜索