常見排序方法及其時間複雜度
1. 冒泡排序 n^2
2. 選擇排序 n^2 (尋找局部最小數)
3. 直接插入排序 n^2 (有序數組)
4. 希爾排序 nlogn (二分+直接插入+遞歸)
5. 快速排序 nlogn (最右基準,大放右,小左)
6. 歸併排序 nlogn (拆分+合併)
7. 基數排序 n+r,r爲最大數最高位的位數 (按位數比較)
8. 堆排序 nlogn (平衡二叉樹)
9. 桶排序 n+r,(這裏的r受桶的數量影響) (範圍+拆分比較)
10. 計數排序 n+r,r爲桶的數量 (範圍+個數)
1、2、3、4、5、6、8屬於比較排序,不受數據影響。(即數據可以是小數)
7、9、10屬於非比較排序,對數據規模和數據分佈有一定的要求。
場景分析
1.當輸入規模比較小,推薦使用直接插入排序
3.當輸入規模比較大時
對性能要求嚴苛:快速排序
對空間有要求:堆排序
對穩定性有要求(有大量重複的數):歸併排序
如果數據都是範圍類整形:計數排序
穩定,指的是一個序列中的相同值,它排序後,它的相同值的順序不會改變。
常見排序的java代碼實現
import java.util.ArrayList;
import java.util.ListIterator;
public class SortedUtils {
/**
* 001冒泡排序
* 1000萬的數據排序需要55小時
* 時間複雜度爲 n^2
* @param arr
* @return
*/
public static int[] bubbleSort(int[] arr){
int temp = 0;
boolean flag = false; //表示是否已經按順序排列好了
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if (!flag){
break;
}else {
flag = false;
}
}
return arr;
}
/**
* 002選擇排序
* 1000萬的數據排序需要12個小時
* 時間複雜度爲 n^2
* 原數組: 4 1 2 5 9 3
* 第一步:將最小的數放在位置1
* 第二步:將剩下最小的放位置2
* 依次類推
*
* @param arr
* @return
*/
public static int[] selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
int minIndex = i; // 初始最小值下表(假定每一輪的第一個值爲最小值)
int min = arr[i]; // 初始最小值
for (int j = i; j < arr.length-1; j++) {
if (min > arr[j+1]){
minIndex = j+1; // 改變最小值索引
min = arr[j+1];
}
}
if (minIndex != i){ // 最小值已經改變,將最小值與第一個位置進行交換
arr[minIndex] = arr[i];
arr[i] = min;
}
}
return arr;
}
/***
* 003直接插入排序
* 1000萬的數據排序需要3.2個小時
* 時間複雜度爲 n^2
*
* 通過一個有序數組來存放 4 1 2 5 9 3
* 第一步:4
* 第二步:1 4
* 第三步:1 2 4
* ....
* 第六步:1 2 3 3 5 9
* @param arr
* @return
*/
public static int[] insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) { // 從一開始,前面的第一個數爲有序數,後面的數與前面的有序數進行比較
int insertValue = arr[i]; // 待插入的數
int insertIndex = i - 1; // 待插入數前面一個數的索引
while (insertIndex >= 0 && insertValue < arr[insertIndex]){
arr[insertIndex+1] = arr[insertIndex]; // 將比要插入數大的向後移動
insertIndex--; // 索引向前移動
}
arr[insertIndex+1] = insertValue; // 將要插入的數放在找到的位置
}
return arr;
}
/***
* 004 希爾排序 (是對直接插入排序的一種改進)
* 1000萬的數據排序需要3.067秒
* 時間複雜度爲 nlogn (涉及二分法)
* 第一步:將數據一分二 (0 2 4 一組 1 3 5 一組 間隔),分別對其進行直接排序 6/2=3
* 第二步:再次一分爲二,在分別直接排序 3/2=1
* 第三步:進行微調
*
* @param arr
* @return
*/
public int[] shellSort2(int[] arr){
for (int gap = arr.length/2; gap >0 ; gap /= 2) { // 每次步長減半
for (int i = gap; i < arr.length; i++) { // 從第gap個元素,逐個對其所在的組進行直接插入排序
int insertIndex = i;
int insertValue = arr[insertIndex];
while (insertIndex - gap >= 0 && insertValue < arr[insertIndex - gap]){
arr[insertIndex] = arr[insertIndex - gap];
insertIndex -= gap;
}
arr[insertIndex] = insertValue;
}
}
return arr;
}
/***
* 005 快速排序
* 1000萬的數據排序需要1.55秒
* 時間複雜度爲 nlogn (涉及二分法)
* 第一步:選擇最右的數 作爲基準 比它的放右邊,比它小的放左邊
* 第二步:左右兩組數據 ,再次選擇最右數爲基準,進行比較
* 依次類推,知道左右兩邊的數組長度都爲1
*
* @param arr
* @param left
* @param right
*/
public void quickSort(int[] arr, int left, int right){
if (left >= right){
return ;
}
int l = left;
int r = right;
int temp = arr[right];
int t = 0;// 作爲交換變量
while (l < r){
while (arr[l] <= temp && l < r){ // 從左邊向右尋找大於等於temp的數
l++;
}
while (arr[r] >= temp && l < r){ // 從右向左尋找小於等於temp的數
r--;
}
if (l < r){ //交換
t = arr[l];
arr[l] = arr[r];
arr[r] = t;
}
}
arr[right] = arr[l];
arr[l] = temp;
quickSort(arr, left, r-1);//向左遞歸
quickSort(arr, l+1, right);// 向右遞歸
}
/***
* 006 歸併排序
* 1000萬的數據排序需要1.75秒
* 時間複雜度爲 nlogn (涉及二分法)
* 第一步:將數組不斷拆分,知道每個數組大小都爲2,比較大小
* 第二步:將拆分後的數組兩兩合併並比較大小
* 依次類推,直到所有的數組都合併
*
* @param arr
* @param left
* @param right
*/
public void mergeSort(int[] arr, int[] temp, int left, int right){
if (left < right){
int mid = (left + right) / 2;
// 向左遞歸分解
mergeSort(arr, temp, left, mid);
// 向右遞歸分解
mergeSort(arr, temp, mid + 1, right);
merge(arr, temp, left, right, mid);
}
}
public void merge(int[] arr, int[] temp, int left, int right, int mid){
int i = left;
int j = mid + 1;
int t = 0;
// 1.比較兩個兩部分每一個的大小,知道有一部分數據全部加入temp
while (i <= mid && j <= right){
if (arr[i] < arr[j]){
temp[t] = arr[i];
i++;
}else {
temp[t] = arr[j];
j++;
}
t++;
}
// 2.將剩餘的那一部分的全部加入temp
while (i <= mid){ // 若前面的部分剩餘,將前面那部分剩餘的全部加入
temp[t] = arr[i];
i++;
t++;
}
while (j <= right){ // 若後面的部分剩餘,則將後面的的部分全部加入
temp[t] = arr[j];
j++;
t++;
}
// 3.將temp的數全部拷貝到arr中
t = 0;
int tempLeft = left;
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
/**
* 007 基數排序
* 1000萬的數據排序需要0.695秒
* 時間複雜度爲 n+r (r爲是最高位的位數)
* 第一步:根據個位排序
* 第二步:根據十位排序
* 依次類推,直到根據最大位進行排序
*
* @param arr
*/
public void radixSort(int[] arr){
int max = arr[0];
for (int i = 0; i < arr.length; i++) { // 找到最大值,根據最大值位數確定排序的次數
if (arr[i] > max){
max = arr[i];
}
}
int maxLength = (max+"").length();//最大值的長度
// 初始化一個二維數據,二維數組的沒一個數組都是一個桶,因爲無法確定每一個桶會裝多少個數據,所以設置爲arr.lenth
int[][] bucket = new int[10][arr.length];
// 初始化一個一維數組用於保存桶保存的數的個數
int [] bucketElementCounts = new int[10];
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) { // i控制循環次數,n控制每次取得的位數
for (int j = 0; j < arr.length; j++) { // 循環每個數,按照規則放入桶內
int digitalOfElement = arr[j] / n % 10; // 每次取模得到相應位數的值
bucket[digitalOfElement][bucketElementCounts[digitalOfElement]] = arr[j];
bucketElementCounts[digitalOfElement]++;
}
int index = 0;//存放數據的索引
for (int k = 0; k < bucket.length; k++) { // 循環所有桶
if (bucketElementCounts[k] != 0){ // 不爲空的桶
for (int m = 0; m < bucketElementCounts[k]; m++) { // 將桶的元素放入數組
arr[index++] = bucket[k][m];
}
}
bucketElementCounts[k] = 0;//清空桶
}
}
}
/**
* 008 堆排序
* 1000萬的數據排序需要2.695秒
* 時間複雜度爲 nlogn
*
* @param arr
*/
public void heapSort(int[] arr){
int temp = 0;
// 第一次調整:將最大是數調整爲根節點
// arr.length/2 - 1 :爲第一個非葉子節點
for (int i = arr.length/2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
for (int i = arr.length-1 ; i > 0; i--) {
temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
// 爲什麼這裏的的第二個參數寫死是0 ?
// 因爲經過第一次調整之後,再經過交換,就只有很根節點不是一個大頂堆,
// 所以只需要進行簡單的與根節點交換就好,而不需要在進行整體的一個大頂堆的構造
adjustHeap(arr, 0, i);
}
}
public void adjustHeap(int[] arr, int i, int length){
int temp = arr[i];
// k = i * 2 + 1:表示i這個節點的左子樹
for (int k = i * 2 + 1; k < arr.length; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k+1]){ // 比較左右子節點的大小,將k指向較大的拿一個節點
k++;
}
if (arr[k] > temp){ // 子節點大於父節點
arr[i] = arr[k];//交換數據第一步:子節點的值給父節點
i = k ;//將i指向調整後的子節點,用於循環結束後交換數據
}else {
break;
}
}
arr[i] = temp;//交換數據第二步
}
/**
* 009 桶排序
* 1000萬的數據排序需要16小時
* 時間複雜度爲 O(N+N*logN-N*logM) 桶範圍越大時間複雜度越大
* 假設數據是均勻分佈的 把0—100放在數組A 把 100-200放數組B 分別對其排序
* @param arr
*/
public void bucketSort(int[] arr){
int min = arr[0];
int max = arr[0];
for (int i = 0; i < arr.length; i++) { // 找出最大值與最小值
min = Math.min(min,arr[i]);
max = Math.max(max,arr[i]);
}
int bucketNumber = (max - min)/arr.length + 1; // 桶的數量
ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(); // 存儲每一個桶
ArrayList<Integer> bucket = null;
for (int i = 0; i < bucketNumber; i++) { // 將沒一個桶放入buckets中
bucket = new ArrayList<Integer>();
buckets.add(bucket);
}
for (int item : arr) { // 循環數據插入到桶
ArrayList<Integer> bc = buckets.get((item - min) / arr.length); // (item - min) / arr.length 表示桶的位置
insert(bc, item); // 插入桶(桶內排序)
}
int index = 0; // 索引數組
for (ArrayList<Integer> bc : buckets) { // 循環每一個桶
for (Integer item : bc) { // 循環桶內的數據
arr[index++] = item; // 依次將桶內的數據取出
}
}
}
public void insert(ArrayList<Integer> bc, int item){
ListIterator<Integer> itl = bc.listIterator();
boolean flag = true;
while (itl.hasNext()){
if (item <= itl.next()){
itl.previous(); // 將迭代器的位置偏移到上一位
itl.add(item); // 將數據插入到迭代器當前的位置上
flag = false;
break;
}
}
if (flag){ // 否在九八數據插插入到末端
bc.add(item);
}
}
/**
* 010 計數排序 (在確定都是整數的情況下 一個數一個桶 )
* 1000萬的數據排序需要0.241秒
* 時間複雜度爲 n+k(k爲桶的個數)
* 假設數據是均勻分佈的 把0—100放在數組A 把 100-200放數組B 分別對其排序
* @param arr
*/
public void countSort(int[] arr){
int min = arr[0];
int max = arr[0];
for (int i = 0; i < arr.length; i++) { // 找出最大值與最小值
if (arr[i] > max){
max = arr[i];
}else if (arr[i] < min){
min = arr[i];
}
}
int[] counts = new int[max - min + 1];// 計數空間
for (int i = 0; i < arr.length; i++) { // 將每一個數減去最小值統計到臨時數組中
counts[arr[i] - min] += 1; // arr[i]-min對應計數空間的下表
}
for (int i = 0,index =0; i < counts.length; i++) {
int item = counts[i];//每一個數對應的統計量
while (item-- != 0){ // 直到每一個數的統計量歸零
arr[index++] = i + min; // 將原來的數展開放到原數組中,展開就是按照大小排列
}
}
}
}