10分鐘學會七大排序(2)

五、冒泡排序

public void bubbleSort(int[] array){
        for (int bound = 0; bound < array.length; bound++) {
            for (int cur = array.length - 1; cur > bound ; cur--) {
                if(array[cur - 1] > array[cur]){
                    swap(array,cur - 1,cur);
                }
            }
        }
    }

性能分析

1. 時間複雜度
最好:O(N)   數據有序
最壞:O(N^2) 數組逆序
平均:O(N^2)。

2.空間複雜度:O(1)
3.穩定性:不穩定


六、快速排序(重要)

原理

  1. 從待排序區間選擇一個數,作爲基準值(pivot);
  2. Partition: 遍歷整個待排序區間,將比基準值小的(可以包含相等的)放到基準值的左邊,將比基準值大的(可以包含相等的)放到基準值的右邊;
  3. 採用分治思想,對左右兩個小區間按照同樣的方式處理,直到小區間的長度 == 1,代表已經有序,或者小區間的長度 == 0,代表沒有數據。

從前往後,找第一個比基準值大的元素
從後往前,找第一個比基準值小的元素
交換這兩個位置的元素。

	public void quickSort(int[] array){
        //參數的含義表示針對數組中的那段區間進行排序
        //由於有基準值,採用前閉後閉區間
        quickSortHelper(array,0,array.length - 1);
    }
    public void quickSortHelper(int[] array,int left,int right){
        if(left >= right){
            //逼近條件很重要
            //閉區間,有一個元素或者沒有元素
            return;
        }
        //區間整理的方法:選取基準值,小於基準值的放在左側,大於基準值的放在右側
        int index = partition(array,left,right);//整理完畢後基準值的下標
        quickSortHelper(array,left,index - 1);
        quickSortHelper(array,index + 1,right);
    }
    public int partition(int[] array,int left,int right){
        //3w2s
        int baseIndex = right;
        int baseValue = array[baseIndex];//數組最右側元素,因爲是前閉後閉的
        while (left < right){//聯想到swap
            while (left < right && array[left] <= baseValue){
                left++;
            }
            //走到這步,left指向的元素是從左往右走,第一個大於基準值的數
            while (left < right && array[right] >= baseValue){
                right--;
            }
            //走到這步,right指向的位置,就是第一個小於基準值的數

            swap(array,left,right);
        }
        //循環結束,兩者重合
        swap(array,left,baseIndex);
        return left;
    }

如何證明這裏的left對應的元素是比基準值大的

swap(array,left,baseIndex);

循環結束有兩種情況

  1. 由於right–導致的循環結束,此時就是right沒有找到比基準值小的元素,但是和left撞上了,並且left的元素是比基準值大的,此時跳出大循環,left指向的元素比基準值大
  2. 由於left++導致的循環結束,此時就是left沒有找到比基準值大的元素,但是和right撞上了,爲什麼此時的right還是比基準值大呢,因爲上一層循環left和right交換了。

如果要取左側的元素爲基準值,1.從後往前找比基準值小的2.從前往後找比基準值大的
(取右側的元素爲基準值,1.從前往後找比基準值大的2.從後往前找比基準值小的。

public int partition(int[] array,int left,int right){
        //3w2s
        int baseIndex = left;
        int baseValue = array[baseIndex];//數組最右側元素,因爲是前閉後閉的
        while (left < right){
            while (left < right && array[right] >= baseValue){
                right--;
            }
            //走到這步,right指向的位置,就是第一個小於基準值的數
            while (left < right && array[left] <= baseValue){
                left++;
            }
            //走到這步,left指向的元素是從左往右走,第一個大於基準值的數


            swap(array,left,right);
        }
        //循環結束,兩者重合
        swap(array,right,baseIndex);
        return right;
    }

性能分析

1. 時間複雜度:最壞O(N^2)(逆序,沒法達到遞歸二分的效果)
			  最好:O(N*log(N))
			  平均:O(N*log(N))
			  
2.空間複雜度:由於進行了遞歸,需要調用棧保存層次的關係,需要佔據空間的
				最好:O(N)
				最壞:O(log(N))
				平均:O(log(N))

穩定性:不穩定


快速排序的優化

  1. 如果當前逆序的話,用上面的快速排序效率低
  2. 如果基準值選的不好,也會影響性能
  3. 元素數量多,遞歸深度大,棧就存不下了

優化手段
1.優化取基準值:三元素取中(第一個元素,最後一個元素,中間位置的元素)
2.如果遞歸深度達到一定層次,不再遞歸,對當前的待排序區間使用其他排序算法(堆排序)
3.如果當前待排序區間比較小(left,right),使用插排


非遞歸實現快速排序

用棧的思想來實現遞歸的操作
代碼如下:

public void quickSortByLoop(int[] array){
        //1.先創建一個棧,棧裏面存的是待處理區間的下標
        Stack<Integer> stack = new Stack<>();
        stack.push(array.length - 1);
        stack.push(0);//此時根節點入棧
        
        while (!stack.isEmpty()){
            //3.取棧頂元素,棧頂元素就是我們要處理的區間
            int left = stack.pop();//左側的區間
            int right = stack.pop();
            if(left >= right){
                //一個元素或空,不符合要求,
                continue;
            }
            int index = partition(array,left,right);
            //接下來將左右兩個區間入棧
            stack.push(index-1);
            stack.push(left);

            stack.push(right);
            stack.push(index + 1);
        }
    }

用棧來模擬遞歸的過程


七、歸併排序

這和我之前寫過的一道題很像,叫做歸併兩個有序鏈表變成一個有序鏈表

	public void mergeSort(int[] array){
		//輔助方法,有三個參數,是區間(前閉後開)
        mergeSortHelper(array,0,array.length);
    }
    public void mergeSortHelper(int[] array,int left,int right){
        //類似於後序
        if(left >= right || right - left == 1){
            //劃分的數組爲空或着只有一個元素
            return;
        }
        int mid = (left + right)/2;
        //[left,mid)
        //[mid,right)
        mergeSortHelper(array,left,mid);//遞歸左區間數組
        mergeSortHelper(array,mid,right);//遞歸右區間數組
        merge(array,left,mid,right);//合併
    }
    public void merge(int[] array,int left,int mid ,int right){
        //[left,mid)
        //[mid,right)
        int length = right - left;//創建數組的長度
        int[] output = new int[length];
        int outputIndex = 0;
        int i = left;//想當於鏈表的cur1,cur2,記錄下標的位置
        int j = mid;
        while (i < mid && j < right){//類似cur1和cur2 都不爲空
            if(array[i] <= array[j]){
                output[outputIndex++] = array[i++];
            }else {
                //否則將後面的元素添入
                output[outputIndex++] = array[j++];
            }
        }
        while (i < mid ){
            output[outputIndex++] = array[i++];
        }
        while (j < right){
            output[outputIndex++] = array[j++];
        }
        for (int k = 0; k < length; k++) {
            array[left + k] = output[k];
        }
    }

    public static void main(String[] args) {
        SortDemo sortDemo = new SortDemo();
        int[] array ={9,5,2,7,6,5,1,8};
        sortDemo.mergeSort(array);
        System.out.println(Arrays.toString(array));
    }

性能分析

1. 時間複雜度:O(n * log(n))              數據不敏感
2. 空間複雜度:O(N)                          數據不敏感
3. 穩定性:穩定(目前只有插入排序,冒泡排序和這個歸併排序是穩定的)

優化總結
在排序過程中重複利用兩個數組,減少元素的複製過程

歸併排序

 1. 數據允許在外存中,外部排序,核心思路就是歸併排序
 2. 歸併排序也是一種高效的給鏈表進行排序的算法
 3. 也是各種標準庫中穩定排序算法的主要實現方式

非遞歸版本(瞭解即可)
藉助下標,對整個數組進行分組。

	public void mergeSortByloop(int[] array){
        //藉助下標及相關規律進行分組
        //初始情況下,讓每個元素單獨作爲一組
        //[0][1]     [2][3]       [4][5]
        //[0,1]  和 [2,3] 合併           [4,5]和[6,7]合併
        for (int gap = 1;gap < array.length;gap *= 2){
            for (int i = 0; i < array.length; i += 2*gap) {
                int beg = i;
                int mid = i + gap;
                int end = i + 2*gap;
                if(mid > array.length){
                    mid = array.length;
                }
                if(end > array.length){
                    end = array.length;
                }
                merge(array,beg,mid,end);
            }
        }
    }

在這裏插入圖片描述

排序方法 最好 平均 最壞 空間複雜度 穩定性
冒泡排序 O(N^2) O(N^2) O(N^2) O(1) 不穩定
插入排序 O(N) O(N^2) O(N^2) O(1) 不穩定
選擇排序 O(N^2) O(N^2) O(N^2) O(1) 穩定
希爾排序 O(N) O(N^1.3) O(N^2) O(1) 不穩定
堆排序 O(N*log(N)) O(N*log(N)) O(N*log(N)) O(1) 不穩定
快速排序 O(N*log(N)) O(N*log(N)) O(N^2):逆序 O(log(N))~O(N) 不穩定
歸併排序 O(N*log(N)) O(N*log(N)) O(N*log(N)) O(N) 穩定
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章