排序

/** 快速排序前端

  • 原理:基於分治的思想,在數組或鏈表中找到基準元素,小於基準元素的放在左邊,大於或等於基準元素的放在右邊,而後對基準元素兩邊再次進行排序。
  • 時間複雜度:
  • 最壞O(N^2)		說明:當待排序的序列在排序前是有序(順序或逆序)的,此時的時間複雜度最差(這種狀況下,快排退化成了冒泡排序)。
  • 平均O(NlogN)
  • 最好O(NlogN)
  • 說明:快速排序是對冒泡排序的一種改進。

*/ public class QuickSort {node

// ------- 1.數組的快速排序 -------

public int[] quickSort(int[] array, int left, int right) {
    if (left < right) {
        int pivotPosition = partition(array, left, right);  // 獲取基準元素的下標
        quickSort(array, left, pivotPosition - 1);          // 對基準元素的左邊進行排序
        quickSort(array, pivotPosition + 1, right);         // 對基準元素的右邊進行排序
    }
    return array;
}

public int partition(int[] array, int left, int right) {

    int pivot = array[left];    // 選取數組的頭元素做爲基準元素
    int pivotPosition = left;   // 基準元素的索引

    /**
     * 循環結束後:
     *      1)pivotPosition後面的元素都大於等於基準元素pivot
     *      2)pivotPosition前面的元素(包括pivotPosition指向的元素):除了array[left]外,其它的元素都小於基準元素pivot
	 *			注:循環結束後,咱們還要將array[left]與array[pivotPosition]進行交換,以實現pivotposition前面的元素(不包括pivotPosition指向的元素)都小於基準元素
     */
    for (int i = pivotPosition + 1; i <= right; i++) { // i表示當前元素的索引
        if (array[i] < pivot) {

            pivotPosition++;
            if (pivotPosition != i) {
                swap(array, pivotPosition, i);
            }
        }
    }

    /**
     * 目的:
     *      將pivotPosition指向基準元素pivot,而且返回pivotPosition,以肯定遞歸函數quickSort()中left參數與right參數的值
     * 實現:
     *      交換基準元素與pivotPosition當前指向元素的位置。
     */
    if (pivotPosition != left) {
        array[left] = array[pivotPosition];
        array[pivotPosition] = pivot;
    }

    return pivotPosition;
}
// -------


// ------- 2.鏈表的快速排序 -------

public void quickSort(Node left, Node right) {

    if (left == null || right == null || left == right) return;

    Node pivotPosition = partition(left, right);    // 獲取基準元素的下標
    quickSort(left, pivotPosition);                 // 對基準元素的左邊進行排序
    quickSort(pivotPosition.next, right);           // 對基準元素的右邊進行排序
}

public Node partition(Node left, Node right) {

    int pivot = left.value;     // 選取鏈表的頭元素做爲基準元素
    Node pivotPosition = left;  // 基準元素的引用

    for (Node i = pivotPosition.next; i != right.next; i = i.next) { // i表示當前元素的引用
        if (i.value < pivot) {

            pivotPosition = pivotPosition.next;
            if (pivotPosition != i) {
                swap(pivotPosition, i);
            }
        }
    }

    if (pivotPosition != left) {
        left.value = pivotPosition.value;
        pivotPosition.value = pivot;
    }

    return pivotPosition;
}
// -------

/**
 * 數組中,交換兩個元素的位置
 */
private void swap(int[] array, int pivotPosition, int i) {
    int temp = array[pivotPosition];
    array[pivotPosition] = array[i];
    array[i] = temp;
}

/**
 * 鏈表中,交換兩個元素的值
 */
private void swap(Node pivotPosition, Node i) {
    int temp = pivotPosition.value;
    pivotPosition.value = i.value;
    i.value = temp;
}

public static void print(int[] array) {
    for (int i = 0; i < array.length; i++) {
        System.out.print(array[i] + ",");
    }
    System.out.println();
}

public static void print(Node head) {

    ArrayList<Object> list = new ArrayList<>();
    while (head.next != null) {
        list.add(head.value);
        head = head.next;
    }
    list.add(head.value);
    System.out.println(list.toString());
}

public static void main(String[] args) {
    QuickSort qs = new QuickSort();

    // test array quicksort
    int[] array = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    int[] sortArray = qs.quickSort(array, 0, array.length - 1);
    print(sortArray);

    // test linkedlist quicksort
    Node head = new Node(5);
    Node node1 = new Node(3);
    Node node2 = new Node(2);
    Node node3 = new Node(6);
    Node node4 = new Node(7);
    Node node5 = new Node(17);
    Node node6 = new Node(9);

    head.next = node1;
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = node6;

    qs.quickSort(head, node6);
    print(head);
}

}算法

============================================================================數組

/**函數

  • 冒泡排序
  • 原理:每一趟排序中,(從後往前)比較相鄰的兩個元素,大數下沉,小數上升,就像冒泡同樣,故稱爲冒泡排序。
  • 時間複雜度:
  • 最壞O(N^2)	說明:當待排序的序列在排序前是逆序的,此時的時間複雜度最差
  • 平均O(N^2)
  • 最好O(N)	說明:當待排序的序列在排序前是順序的,此時的時間複雜度最好

*/ public class BubbleSort {測試

public static void sort(int[] array) {

    // 總共冒泡array.length-1次,每次冒泡都會將未排序序列中的最小元素放到未排序序列的最前端。
    for (int i = 0; i < array.length-1; i++) {

        // 一趟冒泡(排序):從後往前比較相鄰的元素,較小的元素往前冒泡,最終將最小的元素放到未排序序列的最前端。
        for (int j = array.length - 1; j > 0; j--) {
            if (array[j] < array[j - 1]) {
                swap(array, j, j - 1);
            }
        }
    }
}


/**
 * 數組中,交換兩個元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {

    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));

}

}優化

============================================================================ui

/**this

  • 選擇排序
  • 原理:首先從n個元素中找到最小的元素並與第一個元素進行交換,而後再從剩下的n-1個元素中找到最小的元素並與第二個元素進行交換,以此類推,直到剩下1個元素,排序完成。
  • 時間複雜度:
  • 最壞O(N^2)
  • 平均O(N^2)
  • 最好O(N^2)

*/ public class SelectSort {.net

public static void sort(int[] array) {

    // 將array[i]和array[i+1 ~ a.length]中的最小元素進行交換
    for (int i = 0; i < array.length - 1; i++) {

        // 記錄本次循環中最小元素的下標,初始值爲第一個元素的下標
        int minIndex = i;

        // 將本次循環中最小值的下標賦值給minIndex
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < array[minIndex]) {
                minIndex = j;
            }
        }

        if (minIndex != i) {
            // 將本次(第i次)循環中的最小值a[minIndex]與a[i]進行交換
            swap(array, minIndex, i);
        }
    }
}

/**
 * 數組中,交換兩個元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 插入排序
  • 原理:首先構建有序序列(將第一個元素做爲有序序列),而後從後向前遍歷已排序的序列,遍歷的過程當中,將未排序的元素插入到合適的位置。
  • 時間複雜度:
  • 最壞O(N^2)
  • 平均O(N^2)
  • 最好O(N)

*/ public class InsertSort {

public static void sort(int[] array) {

    int insertElement;  // 要插入的元素的值

    int j;              // 有序序列的下標

    /**
     * 經過遍歷前面的有序序列,將下標爲i的元素插入到不大於本身的元素的後面。
     */
    for (int i = 1; i < array.length; i++) { // 將array[i]做爲要插入的元素,從數組的第二個元素開始遍歷。

        insertElement = array[i];   // 要插入的元素

        j = i - 1;                  // 有序序列最後一個元素的下標 (注:要插入的元素 前面的序列是有序序列,從後向前遍歷前面的有序序列)。

        while (j >= 0 && array[j] > insertElement) { // 1.若是下標爲j的元素大於要插入的元素,則將下標爲j的元素向後移動一位。
            array[j + 1] = array[j];
            j--;
        }
        array[j + 1] = insertElement;               // 2.直到下標爲j的元素不大於要插入的元素,則將要插入的元素插入到下標爲j+1的位置。
    }
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 希爾排序(最小增量排序)
  • 原理:
  • 1)先將要排序的序列按照某個增量(increment)分紅若干個子序列,每一個子序列中元素的下標相差increment(即:第1個元素和第1+increment個元素組成了第一個子序列),而後對每一個子序列中的所有元素進行插入排序;
  • 2)再次將要排序的序列按照某個增量(increment/2)分紅若干個子序列,每一個子序列中元素的下標相差increment/2,而後對每一個子序列中的所有元素進行插入排序;
  • 3)當增量小於1時,排序完成。
  • 說明:
  • 1)通常使用待排序序列長度的一半做爲初始增量,且 下一次的增量=上一次的增量/2 即:increment1=length/2 increment2=increment1/2
  • 2)希爾排序是對插入排序的一個優化。
  • 時間複雜度:
  • 平均O(N^1.3)

*/ public class ShellSort {

public static void sort(int[] array) {

    if (array == null || array.length <= 1) {
        return;
    }

    int increment = array.length / 2;   // 增量

    while (increment > 0) {

        for (int i = 0; i < array.length; i++) {                            // 遍歷待排序序列

            for (int j = i; j+increment < array.length; j += increment) {   // 對子序列進行插入排序
                if (array[j] > array[j+increment]) {
                    swap(array,j,j+increment);
                }
            }
        }

        increment = increment / 2;                                          // 設置新的增量,進行下一輪的遍歷。
    }
}

/**
 * 數組中,交換兩個元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 歸併排序
  • <p>

  • 原理:
  • 1)首先把待排序的序列分割成兩個子序列,而後將子序列繼續分割,直到子序列中只有一個元素爲止(遞歸分割)。
  • 2)將這些子序列兩兩合併爲一個有序的序列,最終將全部的子序列合併爲一個大的有序序列,排序完成。
  • 時間複雜度:
  • 最壞O(NlogN)
  • 平均O(NlogN)
  • 最好O(NlogN)

*/ public class MergeSort {

public static int[] sort(int[] array, int low, int high) {

    if (low < high) {                   // 當low=high時,說明已經分解到單個元素了
        int mid = (low + high) / 2;
        sort(array, low, mid);          // 遞歸地對左半部分進行分解
        sort(array, mid + 1, high);     // 遞歸地對右半部分進行分解

        merge(array, low, mid, high);   // 分解已完成,將左子序列和右子序列進行合併
    }
    return array;
}

/**
 * 將左子序列和右子序列合併爲一個有序序列。
 *
 * [@param](https://my.oschina.net/u/2303379) array
 * [@param](https://my.oschina.net/u/2303379) low
 * [@param](https://my.oschina.net/u/2303379) mid
 * [@param](https://my.oschina.net/u/2303379) high
 */
public static void merge(int[] array, int low, int mid, int high) {

    int[] temp = new int[high - low + 1];   // 臨時數組,長度與原數組長度一致

    int i = low;            // 左邊序列的第一個元素的下標
    int j = mid + 1;        // 右邊序列的第一個元素的下標

    int k = 0;              // 臨時數組temp的下標


    // eg: 左邊的序列爲:6 9 13  右邊的序列爲:4 7

    /**
     * 把較小的數先copy到臨時數組temp中:                                       ==> 依次將 四、六、7 copy到臨時數組temp中。
     */
    while (i <= mid && j <= high) {
        if (array[i] < array[j]) {
            temp[k++] = array[i++];
        } else {
            temp[k++] = array[j++];
        }
    }

    // 若是左邊的序列中還有未copy過的元素,則把左邊剩餘的元素copy臨時數組temp中。      ==> 依次將 九、13 copy到臨時數組temp中。
    while (i <= mid) {
        temp[k++] = array[i++];
    }

    // 若是右邊的序列中還有未copy過的元素,則把右邊剩餘的元素copy臨時數組temp中。
    while (j <= high) {
        temp[k++] = array[j++];
    }

    // 用排好序的臨時數組temp覆蓋原數組
    for (int t = 0; t < temp.length; t++) {
        array[t + low] = temp[t];
    }
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data,0,data.length-1);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 【堆】
  • 堆的概念:堆是一棵順序存儲的徹底二叉樹。
  • 堆分爲大根堆和小根堆:
  • 大根堆:每一個節點的值不小於等於其左、右孩子的值
  • 小根堆:每一個節點的值不大於等於其左、右孩子的值
  • 【堆排序】
  • 1)概念:指利用堆的特性將待排序的序列進行排序。
  • 2)過程:
  • 1)將一個待排序的序列構形成一個堆:從最後一個非葉子結點向上遍歷,直到全部的非葉子節點都遍歷完畢(篩選法)。
  • 2)移走堆頂元素後,將剩餘的元素再次構形成一個新的堆。
  • 3)大根堆的排序:
  • 1)將待排序的n個元素構形成一個大根堆。
  • 2)移走堆頂元素(即:將堆頂元素與堆數組的末尾元素進行交換,此時末尾元素爲最大值)。
  • 3)將剩餘的n-1個元素從新構形成一個大根堆,重複上面的步驟,直到剩餘的元素只有一個時,排序完成。
  • 4)說明:將數組構形成初始堆時,若想升序排列構造大根堆,若想降序排列則構造小根堆。
  • 將一個徹底二叉樹按層序排號依次存入數組:
  • 8
  • /   \
  • 3	   9		---按層序排號存入數組---> [8,3,9,5,7,4,2]
  • / \   / \
  • 5   7 4   2
  • 5)在數組中,很容易獲得下面的結論:
  • 1)下標爲i的節點,其父節點的下標爲(i-1)/2
  • 2)下標爲i的節點,其左子節點的下標爲2*i+1,右子節點的下標爲2*i+2
  • 6)使用場景:創建堆和調整堆的過程當中會產生比較大的開銷,故堆排序適用於排序元素較多的時候。eg:
  • 問題:top K
  • 方案:
  • 第1步)分治:先將全部的數據按照hash方法分解成多個較小數據集。
  • 第2步)使用堆排序分別找出這幾個較小數據集中的topK。
  • 第3步)將第2步中各數據集的topk放在一個集合裏,而後求出最終的topK。
  • 7)複雜度:
  • 1)時間複雜度:最好、最壞、平均 的複雜度都是 O(nlogn),故堆排序堆輸入的數據不敏感。
  • 建堆:O(n)
  • 調整:O(log n)
  • 2)空間複雜度:堆排序是就地排序,故其空間複雜度爲O(1)。
  • 8)穩定性:排序先後相同元素間的相對位置可能會發生改變,故堆排序是一種不穩定的排序算法。

*/ public class HeapSort {

public static int[] array;

public static int adjustTime = 0;
public static int whileTime = 0;

public HeapSort(int[] array) {
    this.array = array;
}

/**
 * 根據子節點的索引來獲取父節點的索引
 *
 * [@param](https://my.oschina.net/u/2303379) child
 * @return
 */
public static int parentIndex(int child) {
    return (child - 1) / 2;
}

/**
 * 根據父節點的索引來獲取左子節點的索引
 *
 * @param parent
 * @return
 */
public static int leftChildIndex(int parent) {
    return parent * 2 + 1;
}

/**
 * 第一步:將待排序的n個元素構形成一個大根堆。
 *
 * 將數組初始化爲大根堆:從下(最後一個非葉子節點)往上(堆的根節點)循環遍歷。
 *
 *  要點:遍歷存在左子節點的父節點,從最後一個非葉子結點開始遍歷。
 *  說明:
 *      1)徹底二叉樹是按層序排號存入數組的,故二叉樹的最後一個節點(即:數組中索引值最大的元素)必定是葉子節點,故最後一個節點必定有父節點,且最後一個節點的父節點就是 堆最後一個非葉子節點。
 *      2)二叉樹的最後一個節點的索引爲array.length-1,則其父節點(即:最後一個非葉子節點)的索引爲(array.length-1-1)/2,故咱們從array.length/2-1開始遍歷!
 */
public static void initHeap() {

    // 從下往上的循環
    for (int parentIndex = parentIndex(array.length-1); parentIndex >= 0; parentIndex--) {
        adjustHeap(array, parentIndex, array.length - 1);
    }
}

/**
 * 對堆進行排序
 */
public static void sortHeap() {

    // array.length-1次 調整完成排序
    for (int i = array.length - 1; i > 0; i--) {

        // 第二步:將堆頂元素(數組中第一個元素)和當前未排序子序列中的最後一個元素交換
        swap(array, 0, i);

        // 第三步:交換後,將剩餘的n-1個元素從新構形成一個大根堆
        adjustHeap(array, 0, i-1);
    }
}

/**
 * 調整堆:從上往下循環遍歷,即 沿父節點的較大子節點向下調整
 *
 * @param array
 * @param parentIndex
 * @param maxIndex
 */
public static void adjustHeap(int[] array, int parentIndex, int maxIndex) { adjustTime++;

    int temp = array[parentIndex];              // 父節點的值
    int child = leftChildIndex(parentIndex);    // 左子節點的索引

    // 從上往下循環:沿父節點的較大子節點向下調整
    while (child <= maxIndex) {   // 左子節點必須在未排序子序列中

        whileTime++;    // 記錄循環的次數,測試用。

        // 若當前節點(即:父節點)存在右子節點(且右子節點在未排序的子序列中),而且右子節點的值大於左子節點時,將右子節點的索引賦值給child
        if (child + 1 <= maxIndex && array[child + 1] > array[child]) {
            child++;   // 將左子節點轉換爲右子節點
        }

        // 此時,child表示 子節點中值最大的那個節點 的索引

        // 若當前節點(即:父節點)的值大於子節點的值時,直接退出。
        if (temp > array[child]) {
            break;
        } else {
            array[parentIndex] = array[child]; // 將子節點的值賦值給父節點

            // 選取子節點的左子節點繼續向下調整(執行wile循環)
            parentIndex = child;
            child = leftChildIndex(parentIndex);
        }
    }
    // 若發生了交換,則parentIndex表示子節點的索引;若沒有發生交換,則parentIndex仍舊錶示父節點的索引。
    array[parentIndex] = temp;
}


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

public static void main(String[] args) {

    int[] array = {2, 13, 5, 7, 14, 6, 10, 8, 11, 4, 3, 9, 1, 12, 0};
    HeapSort heapSort = new HeapSort(array);
    heapSort.initHeap();
    heapSort.sortHeap();
    System.out.println("排序後數組" + Arrays.toString(heapSort.array) + " 調整次數" + adjustTime + " while循環次數" + whileTime);
}

}

相關文章
相關標籤/搜索