比較排序:
冒泡、選擇、插入、希爾、堆、歸併、快排
非比較排序:
基數、計數、桶
穩定排序:
冒泡、插入、歸併、基數、計數、桶
不穩定排序:
選擇、希爾、堆、快排
分治思想:
歸併、快排
冒泡排序
- 算法步驟:
1)比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
3)針對所有的元素重複以上的步驟,除了最後一個。
4)持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。 - 優化策略:設立一個 flag,當在一趟序列遍歷中元素沒有發生交換,則證明該序列已經有序。
- 動圖演示:
- 適用場景:
少量數據 - 代碼實現:
public class BubbleSort {
public int[] sort(int[] arr){
for (int i = 1; i < arr.length; i++) {
//標記,若爲true,就表明此次循環沒有進行交換,表明已經排好序,就跳出循環
boolean flag = true;
for (int j = 0; j < arr.length-i; j++) {
if (arr[j] > arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
//交換了,就設爲false
flag =false;
}
}
if(flag) break;
}
return arr;
}
}
選擇排序
- 算法步驟:
1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2)再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
3)重複第二步,直到所有元素均排序完畢。 - 動圖演示
- 適用場景:
少量數據 - 代碼實現:
public class SelectSort{
public int[] sort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
int min = i;
for (int j = i+1; j < arr.length; j++) {
if (arr[j] < arr[min]){
//記錄最小元素的下標
min = j;
}
}
//將找到的最小值和i位置所在的值進行交換
if (i != min){
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
return arr;
}
}
插入排序
- 算法步驟:
1)將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
2)從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。) - 動圖演示:
- 適用場景:
小規模、基本有序的時候十分有效。在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率。 - 代碼實現:
public class InsertSort{
public int[] sort(int[] arr){
//0爲已排好序的,從1開始往有序序列裏面插入
for (int i = 1; i < arr.length; i++) {
//要插入的數據
int tmp = arr[i];
//j是要插入的數據位置,它和j前已排好序的序列j-1 ~ 0比較,
int j = i;
while (j>0 && tmp < arr[j-1]){
//找到就將數據往後移一位
arr[j] = arr[j-1];
j--;
}
//將數據插入
if (j != i){
arr[j] = tmp;
}
}
return arr;
}
}
希爾排序
- 算法步驟:
1)選擇一個增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
2)按增量序列個數 k,對序列進行 k 趟排序;
3)每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲 1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度 - 動圖演示:
- 適用場景:
對插入排序的一種改進,中等大小規模表現良好,對規模非常大的數據排序不是最優選擇。 - 代碼實現
public class ShellSort{
public int[] sort(int[] arr){
//間隔計算
int gap = 1;
while(gap <= arr.length / 3){
gap = gap * 3 + 1;
}
while (gap > 0){
//相當於間隔爲gap的插入排序,如果gap爲1就和插入排序一樣
for (int i = gap; i < arr.length; i++) {
int tmp = arr[i];
int j = i;
while (j >= gap && arr[j-gap] > tmp){
arr[j] = arr[j-gap];
j -= gap;
}
arr[j] = tmp;
}
//減小間隔
gap = gap / 3;
}
return arr;
}
}
堆排序
- 算法步驟
1)創建初始堆;(大頂堆和小頂堆)
2)把堆頂和堆尾互換;
3)把堆的尺寸縮小 1,並重新調整堆;
4)重複步驟 2,直到堆的尺寸爲 1。 - 動圖演示:
- 適用場景:
數據量大,適用面較廣,可以適用比快排更大量的數,也適用於多核並行處理。堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。 - 代碼實現:
public class HeapSort{
public int[] sort(int[] arr){
int len = arr.length;
//先建大頂堆,用堆調整的方法建立
buildMaxHeap(arr, len);
for (int i = len -1 ; i > 0; i--) {
//交換堆頂和堆尾
swap(arr, 0 ,i);
len --; //堆大小減1
//調整堆
adjustHeap(arr, 0, len);
}
return arr;
}
//建大頂堆
private void buildMaxHeap(int[] arr, int len){
for (int i = len/2; i >= 0 ; i--) {
adjustHeap(arr, i, len);
}
}
//調整堆
private void adjustHeap(int[] arr, int index, int len){
int left = 2 * index + 1; //左子節點索引
int right = left + 1; //右子節點索引
int largest = index; //最大節點索引,默認值是當前節點(父節點)
//判斷左子節點比父節點的大小,如果有節點,肯定必有左子節點
if (left < len && arr[left] > arr[largest]) {
largest=left;
}
//先判斷是否有右子節點,再判斷左右節點,哪個較大。
if (right < len && arr[right] > arr[largest]){
largest = right;
}
//當前位置的索引不是最大索引就交換,然後再重新調整,如果相等就不動。
if (largest != index){
swap(arr, index, largest);
adjustHeap(arr, largest, len);
}
}
//交換數據
private void swap(int[] arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
歸併排序
- 算法步驟:
1)申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列;
2)設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置;
3)比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;
4)重複步驟 3 直到某一指針達到序列尾;
5)將另一序列剩下的所有元素直接複製到合併序列尾 - 動圖演示:
- 適用場景:
數據量大,且要求穩定的時候,可以考慮歸併排序。 - 代碼實現:
這裏運用了Arrays.copyOfRange(T[ ] original,int from,int to)方法,將一個原始的數組original,從下標from開始複製,複製到上標to,生成一個新的數組。注意這裏包括下標from,不包括上標to。
public class MergeSort{
public int[] sort(int[] arr){
if (arr.length < 2){
return arr;
}
int middle = arr.length / 2;
//將當前數組分爲兩部分(分治)
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
//遞歸
return mergeSort(sort(left), sort(right));
}
private int[] mergeSort(int[] left, int[] right){
//合併數組
int[] result = new int[left.length + right.length];
//判斷左數組和右數組 中第一個數值的大小,將小的放入數組,並把小的那個數組的該項去掉
int i = 0;
while (left.length > 0 && right.length > 0){
if (left[0] <= right[0]){
result[i++] = left[0];
left = Arrays.copyOfRange(left,1, left.length);
}else{
result[i++] = right[0];
right = Arrays.copyOfRange(right,1, right.length);
}
}
//左邊數組有剩餘,加入數組
while (left.length > 0){
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
//右邊數組有剩餘,加入數組
while (right.length > 0){
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
}
快速排序
-
算法步驟:
1)首先取數組的第一個元素作爲基準元素pivot。
2)重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作;
3)遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序; -
動圖演示:
-
適用場景:
數據量大,快速排序是目前基於比較的排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短; -
代碼實現:
public class QuickSort{
public int[] sort(int[] arr){
return quickSort(arr, 0, arr.length-1);
}
private int[] quickSort(int[] arr, int left, int right){
if (left < right){
int partition = partition(arr, left, right);
//分治
quickSort(arr, left, partition - 1);
quickSort(arr, partition + 1, right);
}
return arr;
}
private int partition(int[] arr, int left, int right){
int pivot = left;
int index = pivot + 1; //始終爲第一個大於pivot值的位置
for (int i = index; i <= right; i++) {
if (arr[i] < arr[pivot]){
swap(arr, i, index);
index++;
}
}
//將pivot位置的值和最後一個小於pivot的值交換
swap(arr, pivot, index-1);
//交換後 index-1 就是 pivot 的位置
return index-1;
}
private void swap(int[] arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
計數排序
- 算法步驟:
1)根據待排序集合中最大元素和最小元素的差值範圍,申請額外空間;
2)遍歷待排序集合,將每一個元素出現的次數記錄到元素值對應的額外空間內;
3)對額外空間內數據進行計算,得出每一個元素的正確位置;
4)將待排序集合每一個元素移動到計算得出的正確位置上。 - 動圖演示:
- 適用場景:
每個桶只存儲單一鍵值;它的優勢在於在對一定範圍內的整數排序時,它的複雜度爲Ο(n+k)(其中k是整數的範圍),快於任何比較排序算法。當然這是一種犧牲空間換取時間的做法。 - 代碼實現:
public class CountingSort{
public int[] sort(int[] arr){
int maxValue = getMaxValue(arr);
return countingSort(arr, maxValue);
}
private int[] countingSort(int[] arr, int maxValue){
int bucketLen = maxValue + 1;
int[] bucket = new int[bucketLen];
//按照數組中的值(對應桶中的key),將桶中對應值的加1
// 一個爲1,兩個就爲2,...,依次增加,數組中value相同的情況
for (int value : arr){
bucket[value]++;
}
int sortedIndex = 0;
for (int i = 0; i < bucketLen; i++) {
//依次把桶中有數值的,將key取出來
while (bucket[i] > 0){
arr[sortedIndex++] = i;
//可能有相同的值
bucket[i]--;
}
}
return arr;
}
//取最大值,確定桶的範圍
private int getMaxValue(int[] arr){
int maxValue = arr[0];
for(int value : arr){
if (maxValue < value){
maxValue = value;
}
}
return maxValue;
}
}
桶排序
- 算法步驟:
1)首先規定好桶的數量
2)其次計算好每個桶的數據範圍 (max-min+1)/bucketCount
3)把數據放在對應的桶裏,桶內是排好序的
4)遍歷每個桶,得到最後排好序的序列 - 動圖演示:
- 適用場景:
數據量大,求中位數。每個桶存儲一定範圍的數值,桶排序是計數排序的升級版。 - 代碼實現:
public class BucketSort{
public int[] sort(int[] arr){
//7爲桶數,自行設置
return bucketSort(arr,7);
}
private int[] bucketSort(int[] arr, int bucketLen){
if (arr.length == 0){
return arr;
}
//求數組的最大值和最小值
int minValue = arr[0];
int maxValue = arr[0];
for (int value: arr) {
if (value < minValue){
minValue = value;
}
if (value > maxValue){
maxValue = value;
}
}
//每個桶的大小
int bucketCount = (maxValue - minValue) / bucketLen + 1;
int[][] buckets = new int[bucketCount][0];
//利用映射函數將數據分配到各個桶中
for (int i = 0; i < arr.length; i++) {
int index = (arr[i] - minValue) / bucketLen;
//擴容桶,並添加數據
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
//取有數據的桶,進行排序
if(bucket.length > 0) {
//對每個桶進行排序,這裏用的插入排序,數據量小用插入,數據量大用快速
InsertSort insertSort = new InsertSort();
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
}
return arr;
}
//擴容保存數據
private int[] arrAppend(int[] arr, int value){
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value; //新數組最後一位添加數據
return arr;
}
}
基數排序
- 算法步驟:
1)將所有待比較數值統一爲同樣的數位長度,數位較短的數前面補零。
2)從最低位開始,依次進行一次排序。
3)這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。
基數排序的方式可以採用 LSD(Least significant digital)或 MSD(Most significant digital),LSD 的排序方式由鍵值的最右邊開始,而 MSD 則相反,由鍵值的最左邊開始。 - 動圖演示:
- 適用場景:
基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。 - 代碼實現:
public class RadixSort{
public int[] sort(int[] arr) {
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
//獲取最高位數
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
//獲取最大值
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
//獲取最高位,個位爲1,十位爲2,百位爲3,...
private int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
private int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考慮負數的情況,這裏擴展一倍隊列數,其中 [0-9]對應負數,[10-19]對應正數 (bucket + 10)
int[][] buckets = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
//+mod 正好把正數和負數區分出來,[0-9]對應負數,[10-19]對應正數
int index = ((arr[j] % mod) / dev) + mod;
buckets[index] = arrayAppend(buckets[index], arr[j]);
}
//按照該位的順序,把元素都放入到數組中。
int pos = 0;
for (int[] bucket : buckets) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
return arr;
}
//擴容保存數據
private int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value; //新數組最後一位添加數據
return arr;
}
}
代碼下載連接:本文的可運行代碼
參考連接:
https://www.toutiao.com/i6704815171367338508/
https://www.jianshu.com/u/47a4e21999fc