1. 程式人生 > >排序——冒泡、歸併、快速、選擇、插入、堆

排序——冒泡、歸併、快速、選擇、插入、堆

氣泡排序,O(n²)

原理:遍歷集合多次,比較相鄰的兩個元素,將較大或者較小的元素向後移動,類似於“氣泡”一樣向上浮動。

    /**
     * 
     * <p>Title: 基礎原理</p>
     * <p>author : xukai</p>
     * <p>date : 2017年5月16日 下午2:51:22</p>
     * @param array
     */
    public static void bubbleSort1(int[] array) {
        // 1.外層迴圈,次數為(length-1)
for (int i = 1; i < array.length; i++) { // 2.遍歷集合,比較相鄰元素大小 for (int j = 0; j < array.length - i; j++) { if (array[j] > array[j + 1]) { swap(array, j, j + 1); } } System.out.print("第" + i + "次迴圈執行之後:"
); print(array); } }

優化:假如在外層迴圈中,某次迴圈一次都沒有執行swap操作,說明集合已經排序完畢,無需再遍歷

    /**
     * 
     * <p>Title: 某次遍歷沒有swap(排序完成),那麼下一次也不需要遍歷</p>
     * <p>author : xukai</p>
     * <p>date : 2017年5月16日 下午3:10:03</p>
     * @param array
     */
    private
static void bubbleSort2(int[] array) { boolean needNextPass = true; for (int i = 1; i < array.length && needNextPass; i++) { // 1.假設集合排序完畢 needNextPass = false; for (int j = 0; j < array.length - i; j++) { if (array[j] > array[j + 1]) { swap(array, j, j + 1); // 2.假如未被執行,集合排序完畢,外層迴圈結束 needNextPass = true; } } System.out.print("第" + i + "次迴圈執行之後:"); print(array); } }

歸併排序,O(nlog(n))

原理:1.將集合平均拆分為兩個子集合,對子集合進行歸併排序。2.直到子集合中有且僅有一個元素,對兩個子集合排序。
這裡寫圖片描述

public class MergeSort {

    public static void main(String[] args) {
        int[] array = { 2, 9, 5, 4, 1, 6, 7, 6 };
        mergeSort(array);
    }

    /**
     * 
     * <p>Title: 1.遞迴拆除陣列</p>
     * <p>author : xukai</p>
     * <p>date : 2017年5月17日 下午5:51:16</p>
     * @param array
     */
    public static void mergeSort(int[] array) {
        if (array.length > 1) {
            // 1.左側陣列
            int[] arrayLeft = new int[array.length / 2];
            System.arraycopy(array, 0, arrayLeft, 0, array.length / 2);
            mergeSort(arrayLeft);

            // 2.右側陣列
            int arrayRightLength = array.length - arrayLeft.length;
            int[] arrayRight = new int[arrayRightLength];
            System.arraycopy(array, array.length / 2, arrayRight, 0, arrayRightLength);
            mergeSort(arrayRight);

            // 3.左右側數組合並
            int[] temp = merge(arrayLeft, arrayRight);
            print(temp);

            // 4.返回已經排序完畢的子陣列
            System.arraycopy(temp, 0, array, 0, temp.length);
        } else {
            return;
        }
    }

    /**
     * 
     * <p>Title: 合併兩個陣列</p>
     * <p>author : xukai</p>
     * <p>date : 2017年5月17日 下午5:51:33</p>
     * @param arrayLeft
     * @param arrayRight
     * @return
     */
    private static int[] merge(int[] arrayLeft, int[] arrayRight) {
        int[] temp = new int[arrayLeft.length + arrayRight.length];
        int indexOfLeft = 0;
        int indexOfRight = 0;
        int indexOfTemp = 0;

        while (indexOfLeft < arrayLeft.length && indexOfRight < arrayRight.length) {
            if (arrayLeft[indexOfLeft] < arrayRight[indexOfRight]) {
                temp[indexOfTemp++] = arrayLeft[indexOfLeft++];
            } else {
                temp[indexOfTemp++] = arrayRight[indexOfRight++];
            }
        }

        while (indexOfLeft < arrayLeft.length) {
            // 左側未遍歷完
            temp[indexOfTemp++] = arrayLeft[indexOfLeft++];
        }

        while (indexOfRight < arrayRight.length) {
            // 右側未遍歷完
            temp[indexOfTemp++] = arrayRight[indexOfRight++];
        }

        return temp;
    }

    private static void print(int[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.print("\n");
    }
}

這裡需要注意一點程式碼中的第4步,temp陣列為已經排好序的陣列,一定要複製給原陣列
這裡寫圖片描述

快速排序,O(nlog(n))

原理:任意選擇主元元素(預設index=0),將陣列分為兩部分,左側元素小於或等於主元,右側元素大於主元。對左右兩部分遞迴此操作。
這裡寫圖片描述

/**
 * @moudle: QuickSort
 * @version:v1.0
 * @Description: 快速排序:任意選擇主元元素(預設index=0),將陣列分為兩部分,左側元素小於或等於主元,右側元素大於主元。遞迴此規則。
 * @author: xukai
 * @date: 2017年5月17日 下午6:21:08
 *
 */
public class QuickSort {

    public static void main(String[] args) {
        int[] array = { 5, 2, 9, 3, 8, 5, 0, 1, 6, 7 };
        quickSort(array);
        print(array);
    }

    public static void quickSort(int[] array) {
        quickSort(array, 0, array.length - 1);
    }

    private static void quickSort(int[] array, int firstIndex, int lastIndex) {
        if (firstIndex < lastIndex) {
            int pivotIndex = partition(array, firstIndex, lastIndex);
            quickSort(array, 0, pivotIndex - 1);
            quickSort(array, pivotIndex + 1, lastIndex);
        }
    }

    /**
     * 
     * <p>Title: partition</p>
     * <p>author : xukai</p>
     * <p>date : 2017年5月20日 上午11:39:50</p>
     * @param array
     * @param firstIndex
     * @param lastIndex
     * @return
     */
    private static int partition(int[] array, int firstIndex, int lastIndex) {
        int pivot = array[firstIndex]; // 主元
        int low = firstIndex + 1; // 向前下標
        int high = lastIndex; // 向後下標

        while (low < high) {
            // 當前元素小於等於主元,next
            while (low <= high && array[low] <= pivot)
                low++;

            // 當前元素大於主元,prenext
            while (high >= low && array[high] > pivot)
                high--;

            // low,high未移動,array[low]>pivot && array[high] <= pivot
            if (low < high)
                swap(array, low, high);
        }

        // TODO
        /**
         * case:
         * 1.(array[high]==pivot)=true,next
         * 2.lastIndex - firstIndex == 1
         */
        while (high > firstIndex && array[high] >= pivot)
            high--; 

        if (array[high] < pivot) {
            // pivot放在中間
            array[firstIndex] = array[high];
            array[high] = pivot;
            return high; // 返回主元新下標
        } else {
            return firstIndex; // 主元下標
        }
    }

    private static void print(int[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.print("\n");
    }

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

我一直沒看懂程式碼中TODO標籤下的while迴圈有什麼作用,為什麼不刪除掉….

選擇排序,O(n²)

原理:遍歷陣列,將最小的數放在最前面。遍歷剩餘元素,持續此操作。

/**
 * @moudle: SelectSort
 * @version:v1.0
 * @Description: 選擇排序:遍歷陣列,將最小的數放在最前面。遍歷剩餘元素,持續此操作。
 * @author: xukai
 * @date: 2017年5月21日 下午2:51:31
 *
 */
public class SelectSort {

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

    public static void selectSort(int[] array) {
        // 遍歷次數為length - 1;
        for (int i = 0; i < array.length - 1; i++) {
            // 1.遍歷陣列找到min元素
            int minIndex = i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            // 2.swap(array[i],array[index])
            if (minIndex != i) {
                int temp = array[minIndex];
                array[minIndex] = array[i];
                array[i] = temp;
            }
        }
    }

}

這裡寫圖片描述

插入排序,O(n²)

原理:將新元素重複插入已排序好的子數列中

    public static void insertSort(int[] array) {
        // 遍歷未排序陣列下標
        for (int i = 1; i < array.length; i++) {
            // 遍歷已排序完畢的子陣列下標
            for (int j = 0; j < i; j++) {
                if (array[j] > array[i]) {
                    // j為插入位置
                    int insert = array[i];
                    for (int k = i; k > j; k--) {
                        array[k] = array[k - 1];
                    }
                    array[j] = insert;
                }
            }
            System.out.print("第" + i + "次迴圈執行之後:");
            System.out.println(Arrays.toString(array));
        }
    }

這裡寫圖片描述

堆排序,O(nlog(n))

堆特性:

  1. 一顆完整的二叉樹(除了最後一層未滿且葉子偏左,或每層都是滿的)
  2. 每個結點大於或者等於它的子節點

新增結點原理:
1. 將新元素放置進內部List集合
2. 將新元素下標作為遊標,開始遍歷其父結點
3. 如果大於父結點,swap(子節點,父結點),執行2和3,直到遍歷樹完畢。

刪除結點原理:

  1. 判斷是否為空樹,if(true) return null
  2. 將最後的元素放置在index=0(根),並移除原先元素,temp=root
  3. 遍歷樹,遊標從根開始,找到左右子樹中最大值的下標maxIndex
  4. 比較遊標和maxIndex的值大小,如果list(cursor)
import java.util.ArrayList;

/**
 * @moudle: 堆類:1.每個結點大於它的所有子結點。2.完全二叉樹
 * @version:v1.0
 * @Description: TODO
 * @author: xukai
 * @date: 2017年5月21日 下午1:58:16
 *
 * @param <E>
 */
public class Heap<E extends Comparable<E>> {

    private ArrayList<E> list = new ArrayList<>();

    public Heap() {
    }

    public Heap(E[] objects) {
        for (int i = 0; i < objects.length; i++) {
            add(objects[i]);
        }
    }

    public void add(E object) {
        list.add(object);
        // 1.新元素下標
        int currentIndex = list.size() - 1;

        while (currentIndex > 0) {
            int parentIndex = (currentIndex - 1) / 2; // 當前結點的父結點

            // 2.新元素大於其父結點,swap
            if (list.get(currentIndex).compareTo(list.get(parentIndex)) > 0) {
                E temp = list.get(currentIndex);
                list.set(currentIndex, list.get(parentIndex));
                list.set(parentIndex, temp);
            } else {
                break;
            }

            // 3.向上遍歷樹
            currentIndex = parentIndex;
        }
    }

    public E remove() {
        // 1.判斷是否為空樹,if(true) return null
        if (list.size() == 0)
            return null;

        // 2.將最後的元素放置在index=0(根),並移除原先元素
        E removeObject = list.get(0);
        list.set(0, list.get(list.size() - 1));
        list.remove(list.size() - 1);

        // 3.從頭遍歷樹
        int currentIndex = 0;
        while (currentIndex < list.size()) {
            // 3.1 maxIndex(left,right)
            int leftChildIndex = 2 * currentIndex + 1;
            int rightChildIndex = leftChildIndex + 1;
            if (leftChildIndex >= list.size())
                break; // 超出範圍
            int maxIndex = leftChildIndex; // 預設為左子結點,右子節點可能為空
            if (rightChildIndex < list.size())
                if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0) 
                    maxIndex = rightChildIndex;

            // 3.2 if(current<maxIndex) swap(current, maxIndex)
            if (list.get(currentIndex).compareTo(list.get(maxIndex)) < 0) {
                E temp = list.get(maxIndex);
                list.set(maxIndex, list.get(currentIndex));
                list.set(currentIndex, temp);
                currentIndex = maxIndex;
            } else {
                break;
            }
        }
        // 4.返回被刪除元素
        return removeObject;
    }

    public int getSize() {
        return list.size();
    }
}

排序原理:執行堆的刪除操作,即獲得堆中最大值。

/**
 * @moudle: HeapSort 
 * @version:v1.0
 * @Description: 堆排序
 * @author: xukai
 * @date: 2017年5月21日 下午2:38:36
 *
 */ 
public class HeapSort {

    public static void main(String[] args) {
        Integer[] array = {5, 2, 9, 3, 8, 5, 0, 1, 6, 7};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static Integer[] heapSort(Integer[] array) {
        // 1.建立一個堆物件,並初始化完畢
        Heap<Integer> heap = new Heap<>(array);
        // 2.反向遍歷陣列,從頭開始刪除
        for (int i = array.length - 1; i >= 0; i--) {
            array[i] = heap.remove();
        }
        return array;
    }
}

總結

JDK中有很多的排序方法實現,例如:Arrays裡面的sort方法。
檢視程式碼
動畫演示