排序總結——Java語言描述
各種排序方法Java源代碼鏈接:各種排序方法Java源代碼鏈接
一 排序概述
1.1 排序的定義
排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整爲“有序”的記錄序列。
1.2 排序的分類
排序分爲內部排序
和外部排序
內部排序
:若整個排序過程不需要訪問外存便能完成(如軟盤、硬盤),則稱此類排序問題爲內部排序
;
外部排序
:若參加排序的記錄數量很大,整個序列的排序過程**不可能在內存中
完成**,則稱此類排序問題爲外部排序
。
二 內部排序
2.1 內部排序概述
內部排序的過程是一個逐步擴大記錄的有序序列長度的過程。基於不同的“擴大” 有序序列長度的方法,內部排序方法大致可分下列幾種類型:
1**插入類**:將無序子序列中的一個或幾個記錄插入
到有序序列中,從而增加記錄的有序子序列的長度。
1)直接插入排序(基於順序查找)
2) 折半插入排序(基於折半查找)
3) 希爾插入排序(基於逐趟縮小增量)
2 交換類:通過“交換”無序序列中的記錄從而得到其中關鍵字最小或最大的記錄,並將它加入到有序子序列中,以此方法增加記錄的有序子序列的長度
1) 冒泡排序
2) 快速排序
3 選擇類:從記錄的無序子序列中“選擇”關鍵字最小或最大的記錄,並將它加入到有序子序列中,以此方法增加記錄的有序子序列的長度。
1) 簡單選擇排序
2) 樹形選擇排序
3) 堆排序
4**歸併類**:通過“歸併”兩個或兩個以上的記錄有序子序列,逐步增加記錄有序序列的長度。
1) 歸併排序
5 其他類
2.2 各種排序方法詳解
插入類排序
2.2.1 直接插入排序法
a) 思想:利用 “順序查找”實現“在R[1..i-1]中查找R[i]的插入位置”
b) Java語言描述的關鍵代碼
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description 直接插入排序(基於順序查找)
* @author zhiman in 2017年10月7日 下午5:26:45 [email protected]
* @version V1.0
*
*/
public class SimpleInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("請輸入待排數據:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法調用
System.out.println("排序完成後的數據:");
SimpleInsertionSort.<Integer>insertionSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 直接插入排序核心代碼.
* @author zhiman in 2017年10月7日 下午5:28:56
*/
public static <AnyType extends Comparable<? super AnyType>>
void insertionSort(AnyType[] data) {
//暫存帶排序的元素
int i;
for(int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
for (i = pointer; i > 0 && temp.compareTo(data[i - 1]) < 0; i--) {
//if (temp.compareTo(data[i-1]) < 0)
data[i] = data[i - 1];
}
data[i] = temp;
}
}
}
c) 算法分析
直接插入排序法 是穩定的排序排序算法
,時間複雜度爲
2.2.2 折半插入排序法
a) 思想:利用 “折半查找”實現“在R[1..i-1]中查找R[i]的插入位置”
b) Java語言描述的關鍵代碼
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description TODO 折半插入排序法
* @author zhiman in 2017年10月7日 下午9:49:48 mail:[email protected]
* @version V1.0
*
*/
public class BinaryInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("折半插入排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("折半插入排序——請輸入待排數據:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法調用
System.out.println("折半插入排序——排序完成後的數據:");
new BinaryInsertionSort().<Integer>binaryInsert(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
public <AnyType extends Comparable<? super AnyType>>
void binaryInsert(AnyType[] data) {
int low,high,mid;
for (int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
low = 0;
high = pointer - 1;
//查找元素插入位置
while (low <= high) {
mid = (low + high) / 2;
if (temp.compareTo(data[mid]) < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
//移動元素
for (int j = pointer; j > high + 1; j--) {
data[j] = data[j - 1];
}
//將待排元素放到合適位置,即high+1的位置
data[high + 1] = temp;
}
}
}
c) 算法分析
折半插入排序法 是穩定的排序排序算法
,時間複雜度爲
2.2.3 希爾排序法
a) 算法思想:先將整個待排記錄序列分割成若干子序列分別進行直接插入排序,待整個序列中的記錄基本有序時,再對全體記錄進行一次直接插入排序。
b)算法描述
1、選擇一個步長序列
2、 按步長序列個數k,對序列進行k趟排序;
3、每趟排序,根據對應的步長
c)關鍵代碼
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
public class ShellInsertionSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("希爾插入排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("希爾插入排序——請輸入待排數據:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法調用
System.out.println("希爾插入排序——排序完成後的數據:");
new ShellInsertionSort().<Integer>shellInsert(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 希爾排序關鍵代碼
* @param data 待排數據
* @author zhiman in 2017年10月8日 上午11:15:54
*/
public <AnyType extends Comparable<? super AnyType>>
void shellInsert( AnyType[] data ) {
//初始增量選擇data.length/2,後面增量依次選擇上次增量的0.5倍
//這種增量叫做希爾增量
for ( int gap = data.length/2; gap > 0; gap /= 2 ) {
for ( int i = gap; i < data.length; i++ ) {
AnyType temp = data[i];
int j;
for ( j = i;j >= gap/*&& temp.compareTo( data[ j - gap ] ) < 0*/; j -= gap ) {
if ( temp.compareTo( data[ j - gap ] ) < 0 ) {
data[ j ] = data[ j - gap ];
} else
break;
}
data[ j ] = temp;
}
}
}
}
d) 算法分析
1.希爾排序方法是一個不穩定的排序方法
2.關鍵字的比較次數與記錄移動次數依賴於步長因子序列的選取
3.使用希爾增量時,希爾排序最差時間複雜度爲$O(n^2)$
交換類排序
2.2.4 冒泡排序法
a) 思想:它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。
b) Java語言描述的關鍵代碼
package exchangeCategorySort;
import java.util.Scanner;
public class BubbleSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("冒泡排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("冒泡排序——請輸入待排數據:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法調用
System.out.println("冒泡排序——排序完成後的數據:");
new BubbleSort().<Integer>bubbleSortMethod(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 冒泡排序核心代碼
* @param data 待排數據
* @author zhiman in 2017年10月8日 下午10:14:11
*/
public <AnyType extends Comparable<? super AnyType>>
void bubbleSortMethod(AnyType[] data) {
//改進算法,設置標誌位
boolean flag = true;
for (int i = 0; i < data.length - 1 && flag ; i++) {
flag = false;
for (int j = 0; j < data.length - i - 1; j++) {
if (data[j].compareTo(data[j+1]) > 0) {
AnyType temp = data[j+1];
data[j+1] = data[j];
data[j] = temp;
//如果某次循環,沒有交換,則排序完成
flag = true;
}
}
}
}
}
b) 算法分析
1. 上述改進後的冒泡排序算法,當待排數據順序有序時,有最好的時間複雜
2. 冒泡排序是穩定的排序算法
2.2.4 快速排序法
a) 思想:快速排序(quick sort)是對起泡排序的一種改進。通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,然後分別對這兩部分記錄繼續進行排序,直到整個序列有序。
b) Java語言描述的關鍵代碼
javapackage exchangeCategorySort;
import java.util.*;
public class QuickSort {
public static void main(String[] args) {
List<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("快速排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("快速排序——請輸入待排數據:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
arrs.add(temp);
}
scanner.close();
//arrs.toArray(arr);
//泛型方法調用
System.out.println("快速排序——排序完成後的數據:");
QuickSort qs = new QuickSort();
//調用快排算法一
qs.<Integer>quickSortOne(arr,0,arr.length - 1);
for(Integer in:arr) {
System.out.println(in);
}
//調用快排算法二
qs.quickSortTWo(arrs);
for(Integer in:arrs) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 第一種快速排序方法
* @param data 待排數據
* @param low 待排數據低下標
* @param high 待排數據高下標
* @author zhiman in 2017年10月9日 下午4:17:23
*/
public <AnyType extends Comparable<? super AnyType>>
void quickSortOne(AnyType[] data , int low , int high) {
int keyElementPosition;
//此處必須是if判斷,否則無限循環
if (low < high) {
keyElementPosition = doPartition(data,low,high);
//對一次劃分的左邊元素快速排序
quickSortOne(data,low,keyElementPosition - 1);
//對一次劃分的右邊元素快速排序
quickSortOne(data,keyElementPosition + 1,high);
}
}
/**
* TODO(方法功能描述) 一次劃分
* @param data 待排數據
* @param low 待排數據低下標
* @param high 待排數據高下標
* @return 樞紐元素插入位置
* @author zhiman in 2017年10月9日 下午3:43:00
*/
private <AnyType extends Comparable<? super AnyType>>
int doPartition(AnyType[] data , int low , int high) {
//獲取low high 和(low + high)/2中間元素,並將其置於low所指位置
getMidian(data,low,high);
AnyType key = data[low];//選取數組下標爲low元素作爲樞紐
//直到low = high 時循環結束
while (low < high) {
//循環直到找到一個比樞軸元素小的元素
while ( low < high && key.compareTo( data[high] ) < 0 ) {
high--;
}
data[low] = data[high];
//循環直到找到一個比樞軸元素大的元素
while ( low < high && key.compareTo( data[low] ) > 0 ) {
low++;
}
data[high] = data[low];
}
data[low] = key;
return low;
}
/**
* TODO(方法功能描述) 一種簡單實現快排的方法
* @param items List集合
* @author zhiman in 2017年10月9日 下午8:19:43
*/
public void quickSortTWo(List<Integer> items) {
if (items.size() > 1) {
List<Integer> smaller = new ArrayList<Integer>();
List<Integer> same = new ArrayList<Integer>();
List<Integer> larger = new ArrayList<Integer>();
//得到樞軸元素
Integer keyItem = items.get(items.size() / 2);
for (Integer it:items) {
if (it < keyItem) {
smaller.add(it);
} else if (it > keyItem) {
larger.add(it);
} else {
same.add(it);
}
}
quickSortTWo(smaller);
quickSortTWo(larger);
items.clear();
items.addAll(smaller);
items.addAll(same);
items.addAll(larger);
}
}
/**
* TODO(方法功能描述) 獲取中值,並將其放到left所指位置
* @param a 待排數據
* @param left 待排數據低索引
* @param right 待排數據高索引
* @return 樞軸元素
* @author zhiman in 2017年10月10日 下午3:40:51
*/
private <AnyType extends Comparable<? super AnyType>>
void getMidian(AnyType[] a, int left, int right){
int mid = (left + right) / 2;
if (a[mid].compareTo(a[left]) < 0)
swap(a,left,mid);
if (a[right].compareTo(a[left]) < 0)
swap(a,left,right);
if (a[right].compareTo(a[mid]) < 0)
swap(a,mid,right);
//讓中間值位於下標爲right - 1的位置
swap(a , mid, left);
}
private <AnyType extends Comparable<? super AnyType>>
void swap(AnyType[] a, int left, int right) {
AnyType temp = a[left];
a[left] = a[right];
a[right] = temp;
}
}
c) 算法分析
1. 快速排序是不穩定的排序方法
2. 快速排序平均時間複雜度爲O(nlogn) —犧牲穩定性換來效率提升
3. 當待排序列逆序有序時,快速排序退化爲冒泡排序,有最差時間複雜度O(n2)
選擇類排序
2.2.5 簡單選擇排序法
a) 思想:
第 1 趟選擇: 從 1—n 個記錄中選擇關鍵字最小的記錄,並和第 1 個記錄交換。
第 2 趟選擇:從 2—n 個記錄中選擇關鍵字最小的記錄,並和第 2 個記錄交換。
*
*
*
第 n 趟選擇:從 (n-1)—n 個記錄中選擇關鍵字最小的記錄,並和第 n-1 個記錄交換。
b) Java語言描述的關鍵代碼
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description 直接插入排序(基於順序查找)
* @author zhiman in 2017年10月7日 下午5:26:45 [email protected]
* @version V1.0
*
*/
public class SimpleInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("請輸入待排數據:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法調用
System.out.println("排序完成後的數據:");
new SimpleInsertionSort().<Integer>insertionSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 直接插入排序核心代碼.
* @author zhiman in 2017年10月7日 下午5:28:56
*/
public <AnyType extends Comparable<? super AnyType>>
void insertionSort(AnyType[] data) {
//暫存帶排序的元素
int i;
for(int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
for (i = pointer; i > 0 /*&& temp.compareTo(data[i - 1]) < 0*/; i--) {
if (temp.compareTo(data[i-1]) < 0)
data[i] = data[i - 1];
else
break;
}
data[i] = temp;
}
}
}
c) 算法分析: 時間複雜度:O(n2) ,且是不穩定的排序法。
2.2.6 堆排序
a) 思想:在大頂堆中,將堆頂和最後一個記錄交換,即得第一趟的結果;再使剩餘n-1個元素的序列重又建成一個堆,則得到n個元素的次大值。如此反覆執行,便能得到一個有序序列,這個過程稱爲堆排序。
b) Java語言描述的關鍵代碼
package selectCategorySort;
import java.util.Scanner;
/**
* @Description 對用數組存儲的堆進行排序
* @author zhiman in 2017年10月10日 下午9:42:08 [email protected]
* @version V1.0
*
*/
public class HeapSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("堆排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("堆排序——請輸入待排數據:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法調用
System.out.println("堆排序——排序完成後的數據:");
new HeapSort().<Integer>heapSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 堆排序關鍵代碼
* @param data 待排數據
* @author zhiman in 2017年10月10日 下午9:25:07
*/
public <AnyType extends Comparable<? super AnyType>>
void heapSort(AnyType[] data) {
//建堆,對於有n個元素的堆,從n/2處開始調整堆,數組從0下標開始,所以此處需再減一
for (int i = data.length / 2 - 1; i >= 0; i--) {
percDown(data,i,data.length);
}
//刪除堆頂最大元素
for (int i = data.length - 1; i > 0; i--) {
//swap(data,0,i)中i代表元素下標
swap(data,0,i);
//percDown(data,0,i)中i代表堆中元素個數
percDown(data,0,i);
}
}
/**
* TODO(方法功能描述) 刪除堆頂元素並重建堆
* @param a 待排數據
* @param i
* @param n 堆元素個數
* @author zhiman in 2017年10月10日 下午9:31:13
*/
public <AnyType extends Comparable<? super AnyType>>
void percDown(AnyType[] a,int i, int n) {
int child;
AnyType temp;
for (temp = a[i]; getLeftChild(i) < n; i = child) {
child = getLeftChild(i);
//n-1爲數組表示的堆得最後一個元素的下標,讓child指向左右孩子中較大的元素下標
if ( child != n-1 && a[child].compareTo( a[child + 1] ) < 0 ) {
child++;
}
//將結點的值與該節點左右孩子中較大的值比較,並調整堆
if( temp.compareTo( a[child] ) < 0) {
a[i] = a[child];
} else
break;
}
a[i] = temp;
}
/**
* TODO(方法功能描述) 得到元素i左孩子下標
* @param i
* @return 元素i左孩子下標
* @author zhiman in 2017年10月10日 下午9:27:24
*/
private int getLeftChild (int i) {
//用數組來存儲堆,堆頂元素下標爲0,所以左孩子下標爲2*i+1;
return 2 * i + 1;
}
/**
* TODO(方法功能描述) 交換數組元素
* @param a
* @param left
* @param right
* @author zhiman in 2017年10月10日 下午9:48:59
*/
private <AnyType extends Comparable<? super AnyType>>
void swap(AnyType[] a, int left, int right) {
AnyType temp = a[left];
a[left] = a[right];
a[right] = temp;
}
}
c) 算法分析
1. 堆排序最壞情況下,時間複雜度也爲O(nlogn2) 。
2. 堆排序是不穩定的排序算法。
3. 堆排序相對簡單選擇排序而言犧牲了空間換取效率的提升。
歸併類排序
2.2.7 歸併排序法
a) 思想: 將兩個或兩個以上的有序子序列 “歸併” 爲一個有序序列。
b) Java語言描述的關鍵代碼
package mergeCategorySort;
import java.util.Scanner;
public class MergeSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("歸併排序——請輸入待排序數的個數:");
int num = scanner.nextInt();
System.out.println("歸併排序——請輸入待排數據:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法調用
System.out.println("歸併排序——排序完成後的數據:");
new MergeSort().<Integer>mergeSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 重載方法
* @param a 待排數據
* @author zhiman in 2017年10月10日 下午11:18:18
*/
public <AnyType extends Comparable<? super AnyType>>
void mergeSort(AnyType[] a) {
@SuppressWarnings("unchecked")
AnyType[] tempArray = (AnyType[])new Comparable[a.length];
mergeSort(a, tempArray, 0, a.length - 1);
}
/**
* TODO(方法功能描述) 歸併排序關鍵代碼
* @param a 待排數據
* @param tempArray 暫存數組
* @param left 下標
* @param right 下標
* @author zhiman in 2017年10月10日 下午11:11:45
*/
public <AnyType extends Comparable<? super AnyType>>
void mergeSort(AnyType[] a, AnyType[] tempArray, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(a, tempArray, left, center);
mergeSort(a, tempArray, center + 1, right);
merge(a, tempArray, left, center + 1, right);
}
}
/**
* TODO(方法功能描述) 歸併元素
* @param a 待排數據
* @param tempArray 暫存數組
* @param leftPos 左表起始位置
* @param rightPos 右表起始位置
* @param rightEnd 右表終止位置
* @author zhiman in 2017年10月10日 下午11:23:01
*/
private <AnyType extends Comparable<? super AnyType>>
void merge(AnyType[] a, AnyType[] tempArray, int leftPos, int rightPos, int rightEnd) {
//左表終止位置
int leftEnd = rightPos - 1;
int tempPos = leftPos;
//元素總個數
int numElements = rightEnd - leftPos + 1;
//循環歸併直到兩個表中一個表元素全部歸併到新表
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if ( a[leftPos].compareTo( a[rightPos] ) <= 0 ) {
tempArray[tempPos++] = a[leftPos++];
} else {
tempArray[tempPos++] = a[rightPos++];
}
}
//若右表歸併完,則複製左表剩餘元素到tempArray
while ( leftPos <= leftEnd ) {
tempArray[tempPos++] = a[leftPos++];
}
//若左表歸併完,則複製右表剩餘元素到tempArray
while ( rightPos <= rightEnd ) {
tempArray[tempPos++] = a[rightPos++];
}
//將tempArray複製到a
for (int i = 0; i < numElements; i++ , rightEnd--) {
//why
a[rightEnd] = tempArray[rightEnd];
}
}
}