排序(上)

如何分析一個排序算法的效率:

  1. 最好情況,最壞情況,平均情況時間複雜度

  2. 時間複雜度的係數,常數,低階

  3. 比較次數和交換(移動)次數

排序算法的穩定性

如果待排序的序列中存在值相等的元素,經過排序之後,相等的元素之間原有的先後順序不變,則爲穩定的排序算法,反之則爲不穩定的排序算法

排序算法的內存消耗

原地排序(Sorted in place) 原地排序算法特指空間複雜度O(1)的排序算法;

冒泡排序(Bubble Sort)

冒泡排序只會操作相鄰的兩個數據, 每次冒泡操作都會對相鄰的兩個元素進行比較,若不滿足大小關係要求則互換,一次冒泡至少會讓一個元素移動到它合適的位置,重複n次,就完成了n個數據的排序工作

例如: 一組數據: 4,5,6,3,2,1,從小到大進行排序,第一次冒泡 操作的詳細過程:

 

要想完成所有數據的排序,只需要進行6次這樣的冒泡操作即可

 

代碼實現

// 冒泡排序,a 表示數組,n 表示數組大小
public void bubbleSort(int[] a, int n) {
  if (n <= 1) return;
 
 for (int i = 0; i < n; ++i) {
    // 提前退出冒泡循環的標誌位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j) {
      if (a[j] > a[j+1]) { // 交換
        int tmp = a[j];
        a[j] = a[j+1];
        a[j+1] = tmp;
        flag = true;  // 表示有數據交換      
      }
    }
    if (!flag) break;  // 沒有數據交換,提前退出
  }
}
​

//冒泡排序的優化: 在每一輪排序記錄最後一次元素交換的位置,作爲下次比較的邊界,對於邊界外的元素,元素在下次循環中無需比較
 public static void bubbleSort2(int[] a, int n) {
        if (n <= 1)
            return;
        int lastExchange = 0;
        int sortedBoard = n - 1;
        for (int i = 0; i < n; i++) {
            //無元素交換時跳出循環
            boolean flag = false;
            for (int j = 0; j < sortedBoard; ++j) {
                if (a[j] > a[j + 1]) {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = true;
                    //記錄最後一次交換的位置
                    lastExchange = j;
                }
            }
            if (!flag)
                break;
            sortedBoard = lastExchange;
​
        }
    }
​
冒泡排序的過程只涉及相鄰數據的交換操作,只需要常量級的臨時空間,空間複雜度爲O(1),是一個原地排序算法冒泡排序是一個穩定排序算法,相等的相鄰元素大小相等的時候,不做交換

插入排序

將數組中的數據分爲已排序區間和未排序區間,初始已排序區間只有一個元素,就是數組的第一個元素,插入算法的核心是取未排序區間的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序,重複這個過程,直到未排序區間中元素爲空;

 

實現代碼:

// 插入排序,a 表示數組,n 表示數組大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;
​
  for (int i = 1; i < n; ++i) {
    int value = a[i];
    int j = i - 1;
    // 查找插入的位置
    for (; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 數據移動
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入數據
  }
}

插入空間複雜度是O(1),是一個原地排序算法;插入排序對於值相同的元素,插入到前面出現的元素的後面,可以保持原有的 前後順序不變,所以是穩定的排序算法,時間複雜度: 最好的情況下爲O(n);最壞的情況下是O(n2 )

選擇排序(Selection Sort)

選擇排序算法思路類似插入排序,也分已排序區間和未排序區間,但是選擇排序每次都會從未排序區間中找到最小的 元素,將其放入到已排序區間的末尾

 

選擇排序是原地排序算法,空間複雜度爲O(1),不是穩定的排序算法,每次排序都要找剩餘未排序元素中的最小值,並和前面的元素交換位置,這樣破壞了穩定性

代碼實現:

package com.zach.algorithm;
​
import java.util.Arrays;
​
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {6, 5, 4, 3, 2, 1};
        selectionSort(arr,6);
        System.out.println(Arrays.toString(arr));
    }
​
    public static void selectionSort(int[] a, int n) {
        if (n <= 1)
            return;
        for (int i = 0; i < n - 1; ++i) {
            //查找最小值
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (a[j] < a[minIndex]) {
                    minIndex = j;
                }
            }
            //交換
            int tmp = a[i];
            a[i] = a[minIndex];
            a[minIndex] = tmp;
        }
    }
}


 

思考題:

冒泡排序和插入排序的時間複雜度都是O(n2 ),都是原地排序算法,爲什麼插入排序要比冒泡排序更受歡迎呢?

從代碼實現上來看,冒泡排序數據交換比插入排序的數據移動要複雜,冒泡排序需要3個賦值操作,而插入排序只需要1個

冒泡排序中數據的交換操作:
if (a[j] > a[j+1]) { // 交換
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}
​
插入排序中數據的移動操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 數據移動
} else {
  break;
}
​

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