算法穩定性:
假定在待排序的記錄序列中,存在多個具有相同關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變。即在原序列中,ri=rj,且ri在rj之前,而在排序后,ri仍在rj之前,則稱這種排序算法是穩定的。
- 算法穩定性的重要性
在實際的應用中,我們交換的不一定只是一個數,而可能是一個很大的對象,交換元素存在一定的開銷。
排序分類:
插入排序
插入排序由N-1趟排序組成。對于i=1到N-1趟,插入排序保證從位置0到位置i上的元素已經處于排過序的狀態。
如下圖:
具體實現:
public class InsertSort {
public static void main(String[] args){
int[] arr = {34,8,64,51,32,21};
System.out.println("Before sort:");
printArray(arr);
System.out.println("After insert sort:");
insertionSort(arr);
printArray(arr);
}
public static void printArray(int[] a){
for (int i = 0; i < a.length; i ++){
System.out.print(a[i] + " ");
}
System.out.println();
}
/**
* 實現思路:從第二個位置開始遍歷,保證目標數左邊是排序好的,目標數不斷
* 往右邊找,直到找到合適的位置放入。
* @param a
*/
public static void insertionSort(int[] a){
int j;
//從第二個位置開始
for (int i = 1; i < a.length; i ++){
//設置臨時變量
int tmp = a[i];
//將該位置不斷往左移,直到當前的數不大于該數
for (j = i; j > 0 && tmp < a[j-1] ; j--){
//將數往右移,為目標數騰一個位置
a[j] = a[j - 1];
}
//找到位置,將目標數放入
a[j] = tmp;
}
}
}
輸出結果:
Before sort:
34 8 64 51 32 21
After insert sort:
8 21 32 34 51 64
評價:
插入排序的復雜度為O(N2),但算法是穩定的。
希爾排序:
先將序列按Gap劃分為元素個數相同的若干數組,使用直接插入排序法進行排序,然后不斷縮小Gap直至1,最后使用插入排序完成排序。希爾排序實際上是插入排序的增強版。
如下圖:
實現過程:
public static void shellSort(int[] a){
int j;
//設置gap,定義gap/2變化
for (int gap = a.length / 2; gap > 0; gap /= 2){
System.out.println("第" + gap + "趟排序:");
//每gap間進行插入排序
for (int i = gap; i < a.length; i ++){
int tmp = a[i];
for (j = i; j >= gap && tmp < a[j-gap]; j -= gap){
a[j] = a[j-gap];
}
a[j] = tmp;
}
printArray(a);
}
}
輸出結果:以第一個位置進行比較
評價:
- 希爾排序的時間復雜度與增量(即,步長gap)的選取有關。例如,當增量為1時,希爾排序退化成了直接插入排序,此時的時間復雜度為O(N2),而Hibbard增量的希爾排序的時間復雜度為O(N3/2)。
- 希爾排序是不穩定的算法。
冒泡排序
它重復地走訪過要排序的數列,一次比較兩個元素,如果它們的順序錯誤就把它們交換過來。走訪數列的工作就是重復地進行直到沒有再需要交換,也就是說數列已經排序完成。
如下圖:
具體實現:
public static void bubbleSort(int[] a){
int temp;
//遍歷N-1趟
for (int i = 0; i < a.length - 1; i ++){
//每一躺保證該趟的最后一個元素為最大
for (int j = 0; j < a.length - i - 1; j ++){
if (a[j] > a[j+1]){
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
評價:
- 冒泡排序與插入排序擁有相等的運行時間,但是兩種算法在需要的交換次數卻很大地不同。在最好的情況,冒泡排序需要
O(n^{2})次交換,而插入排序只要最多O(n)。 - 冒泡排序是穩定的。
快速排序
使用分治法策略來把一個序列分為兩個子序列。
- 從數列中挑出一個元素,稱為“基準”。
- 重新排序數列,所有比基準小的元素放在基準的左邊,所有比基準大的元素放在基準的右邊。
- 對基準的左邊和右邊兩個子集,不斷重復前面兩個步驟,直到所有子集只剩下一個元素。
如下圖:
具體實現:
public class QuickSort {
private int[] arr;
public QuickSort(int[] arr){
this.arr = arr;
}
//打印數組
public void printArray(){
for (int i = 0; i < arr.length; i ++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
public void quick_sort(){
quick_sort_recursive(0, arr.length - 1);
}
private void quick_sort_recursive(int start, int end){
//只有一個元素的情況,直接返回
if (start >= end) return;
//取最后一個元素作為基準
int mid = arr[end];
//設置左右邊
int left = start;
int right = end - 1;
//進行比較交換
while (left < right){
//左邊的數往右找,直到找到大于基準的數
while (arr[left] <= mid && left < right)
left ++;
//右邊的數往左找,直到找到小于基準的數
while (arr[right] >= mid && left < right)
right --;
//交換兩個數
Swap(left,right);
}
//若中間元素大于基準,則進行交換
if (arr[left] > arr[end])
Swap(left,end);
//否則left++,目的是排除掉中間元素
else
left ++;
//遞歸左右兩邊
quick_sort_recursive(start,left-1);
quick_sort_recursive(left+1, end);
}
//交換元素
private void Swap(int x, int y){
int temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}
評價:
快速排序的時間復雜度為O(nlgn),最壞情況為O(n2),其空間復雜度為O(lgn)。
快速排序的算法是不穩定的。
選擇排序
首先在未排序序列中找到最大(小)元素,存放到排序序列中的起始位置,然后,再從剩余未排序元素中繼續尋找最大(小)元素,然后放到已排序序列的末尾。以此類推,直到所有元素排序完畢。
實現如下:
private static void selection_sort(int[] arr){
int i,j,min,temp,len = arr.length;
for (i = 0; i < len - 1; i ++){
//記錄當前元素
min = i;
//獲取到未排序的序列中找到最小元素的位置
for (j = i + 1; j < len; j ++)
if (arr[min] > arr[j])
min = j;
//將最小的元素放在起始位置
temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
評價:
- 選擇排序的時間復雜度為O(n2)
- 選擇排序是穩定的。
堆排序
利用堆這種數據結構設計的一種排序算法。堆積是一個近似的完全二叉樹的結構,并同時滿足堆積的性質:即子結點的鍵值或索引總是小于(或大于)它的父節點。
堆節點的訪問:
a. 父親節點i 的左子節點的位置(2i+1)
b. 父節點i的右子節點的位置(2i+2)
c. 子節點的父節點位置floor((i-1)/2)堆的操作:
a. 創建最大堆:將堆所有數據重新排序
b. 最大堆調整:將堆的末端子節點作調整,使得子節點永遠小于父節點
c. 堆排序:移除位于第一個數據的根節點,并做出最大堆調整的遞歸運算具體實現過程:
http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
- 具體實現方法:
public class HeapSort {
private int[] arr;
public HeapSort(int[] arr){
this.arr = arr;
}
//進行排序
public void sort(){
//建立最大堆
buildMaxHeap();
//移除頂點,并調整堆順序
for (int i = arr.length - 1; i > 0; i --){
//將頂點與最后的元素(最小值)交換
swap(i,0);
//然后調整堆順序
maxHeapify(0,i);
}
}
private void swap(int i , int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//建立最大堆,遞歸實現
private void maxHeapify(int index, int len){
//默認父節點為最大值
int max = index;
//獲取左右孩子節點位置
int left = (index * 2) + 1;
int right = left + 1;
//若左孩子大于父親,則賦值左孩子
if (left < len && arr[max] < arr[left])
max = left;
//若右孩子大于父親,則賦值右孩子
if (right < len && arr[max] < arr[right])
max = right;
//若需要更新父節點,則交換,并調整
if (max != index){
swap(index, max);
maxHeapify(max,len);
}
}
//建立最大堆
private void buildMaxHeap(){
//設置根節點位置
int parent = arr.length/2 - 1;
//從根開始創建堆
for (int i = parent; i >= 0; i --){
maxHeapify(i,arr.length);
}
}
private void printArray(int[] a){
for (int i = 0; i < a.length; i ++){
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args){
int[] arr = {81,94,11,96,12,35,17,95,28,58,41,75,15};
System.out.println("Before sort:");
HeapSort hs = new HeapSort(arr);
hs.printArray(arr);
hs.sort();
System.out.println("After heapSort sort:");
hs.printArray(arr);
}
}
輸出結果:
Before sort:
81 94 11 96 12 35 17 95 28 58 41 75 15
After heapSort sort:
11 12 15 17 28 35 41 58 75 81 94 95 96
歸并排序
將數組劃分為若干個部分進行排序,再將這些排序好的部分合并
實現過程:
實現方法:
/**
* 實現歸并排序
* @param arr 原始數組
* @param rs 結果數組
* @param start 開始位置
* @param end 結束位置
*/
private static void merge_sort_recursive(int[] arr, int[] rs, int start, int end){
if (start >= end) return;
//劃分為兩段,并分別設置這兩段的始末位置
int len = end - start;
int mid = len / 2 + start;
int s1 = start, e1 = mid;
int s2 = mid + 1, e2 = end;
//對這兩段分別進行遞歸操作,劃分數組
merge_sort_recursive(arr,rs,s1,e1);
merge_sort_recursive(arr,rs,s2,e2);
//進行合并數組
int k = start;
//從兩個劃分的數組中選取較小的數放入結果數組中
while (s1 <= e1 && s2 <= e2)
rs[k++] = arr[s1] < arr[s2] ? arr[s1++] : arr[s2++];
//對于未排完的,直接放入結果數組中
while (s1 <= e1)
rs[k++] = arr[s1++];
while (s2 <= e2)
rs[k++] = arr[s2++];
//賦值給原始數組
for (k= start; k <=end; k++)
arr[k] = rs[k];
}