文章目錄
- 排序的分類:
-
- 冒泡排序
- 選擇排序
- 插入排序
- 希爾排序
- 快速排序
- 歸并排序
- 基數排序
- 堆排序
- 常用排序算法總結和對比
排序的分類:
- 内部排序:
指将需要處理的所有資料都加載到内部存儲器中進行排序。
- 外部排序法:
資料量過大,無法全部加載到記憶體中,需要借助外部存儲進行排序。
在這裡插入圖檔描述

冒泡排序
冒泡排序(Bubble Sorting)的基本思想是:通過對待排序序列從前向後(從下标較小的元素開始),依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向後部,就象水底下的氣泡一樣逐漸向上冒。
因為排序的過程中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換,就說明序列有序,是以要在排序過程中設定一個标志flag判斷元素是否進行過交換。進而減少不必要的比較。(這裡說的優化,可以在冒泡排序寫好後,在進行)
// // 将前面額冒泡排序算法,封裝成一個方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的時間複雜度 O(n^2), 自己寫出
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]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//System.out.println("第" + (i + 1) + "趟排序後的數組");
//System.out.println(Arrays.toString(arr));
if (!flag) { // 在一趟排序中,一次交換都沒有發生過
break;
} else {
flag = false; // 重置flag!!!, 進行下次判斷
}
}
}
選擇排序
選擇式排序也屬于内部排序法,是從欲排序的資料中,按指定的規則選出某一進制素,再依規定交換位置後達到排序的目的。
選擇排序(select sorting)也是一種簡單的排序方法。它的基本思想是:第一次從arr[0]–arr[n-1]中選取最小值,與arr[0]交換,第二次從arr[1]–arr[n-1]中選取最小值,與arr[1]交換,第三次從arr[2]–arr[n-1]中選取最小值,與arr[2]交換,…,第i次從arr[i-1]–arr[n-1]中選取最小值,與arr[i-1]交換,…,
第n-1次從arr[n-2]~arr[n-1]中選取最小值,與arr[n-2]交換,總共通過n-1次,得到一個按排序碼從小到大排列的有序序列。
//選擇排序
public static void selectSort(int[] arr) {
//在推導的過程,我們發現了規律,是以,可以使用for來解決
//選擇排序時間複雜度是 O(n^2)
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) { // 說明假定的最小值,并不是最小
min = arr[j]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 将最小值,放在arr[0], 即交換
if (minIndex != i) { //如果minIndex = i 即i=j時 此時剛好不用交換
arr[minIndex] = arr[i];
arr[i] = min;
}
//System.out.println("第"+(i+1)+"輪後~~");
//System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
}
}
插入排序
插入式排序屬于内部排序法,是對于欲排序的元素以插入的方式找尋該元素的适當位置,以達到排序的目的。
插入排序(Insertion
Sorting)的基本思想是:把n個待排序的元素看成為一個有序表和一個無序表,開始時有序表中隻包含一個元素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,将它插入到有序表中的适當位置,使之成為新的有序表。
//插入排序
public static void insertSort(int[] arr) {
int insertVal = 0;
int insertIndex = 0;
//使用for循環來把代碼簡化
for(int i = 1; i < arr.length; i++) {
//定義待插入的數
insertVal = arr[i];
insertIndex = i - 1; // 即arr[1]的前面這個數的下标
// 給insertVal 找到插入的位置
// 說明
// 1. insertIndex >= 0 保證在給insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的數,還沒有找到插入位置
// 3. 就需要将 arr[insertIndex] 後移
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
// 當退出while循環時,說明插入的位置找到, insertIndex + 1
// 舉例:了解不了,我們一會 debug
//這裡我們判斷是否需要指派
if(insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
//System.out.println("第"+i+"輪插入");
//System.out.println(Arrays.toString(arr));
}
}
希爾排序
簡單插入排序存在的問題
我們看簡單的插入排序可能存在的問題.
數組 arr = {2,3,4,5,6,1} 這時需要插入的數 1(最小), 這樣的過程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
結論: 當需要插入的數是較小的數時,後移的次數明顯增多,對效率有影響.
希爾排序法介紹
希爾排序是希爾(Donald Shell)于1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱為縮小增量排序。
希爾排序法基本思想
希爾排序是把記錄按下标的一定增量分組,對每組使用直接插入排序算法排序;随着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,算法便終止
希爾排序時, 對有序序列在插入時采用移動法(效率比交換法高)
//對交換式的希爾排序進行優化->移位法
public static void shellSort2(int[] arr) {
// 增量gap, 并逐漸的縮小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 從第gap個元素,逐個對其所在的組進行直接插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) { //這裡if語句可以不加 但是可以減少進入while循環的次數
while (j - gap >= 0 && temp < arr[j - gap]) {
//移動
arr[j] = arr[j-gap];
j -= gap; //循環與前面的相比
}
//當退出while後,就給temp找到插入的位置
arr[j] = temp;
}
}
}
}
// 使用逐漸推導的方式來編寫希爾排序
// 希爾排序時, 對有序序列在插入時采用交換法,
// 思路(算法) ===> 代碼
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
// 根據前面的逐漸分析,使用循環處理
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 周遊各組中所有的元素(共gap組,每組有個元素), 步長gap
for (int j = i - gap; j >= 0; j -= gap) {
// 如果目前元素大于加上步長後的那個元素,說明交換
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
//System.out.println("希爾排序第" + (++count) + "輪 =" + Arrays.toString(arr));
}
}
快速排序
快速排序(Quicksort)是對冒泡排序的一種改進。
基本思想是:通過一趟排序将要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分别進行快速排序,整個排序過程可以遞歸進行,以此達到整個資料變成有序序列
以第一個數為基準
//方法1:便于了解
public static void quickSort2(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;//起始位
j=high;
//temp就是基準位
temp = arr[low];
while (i<j) {
//先看右邊,依次往左遞減
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左邊,依次往右遞增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果滿足條件則交換
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最後将基準為與i和j相等位置的數字交換
arr[low] = arr[i];
arr[i] = temp;
//遞歸調用左半數組
quickSort(arr, low, j-1);
//遞歸調用右半數組
quickSort(arr, j+1, high);
}
public static void quickSort(int[] arr,int left, int right) {
int l = left; //左下标
int r = right; //右下标
//pivot 中軸值
int pivot = arr[(left + right) / 2];
int temp = 0; //臨時變量,作為交換時使用
//while循環的目的是讓比pivot 值小放到左邊
//比pivot 值大放到右邊
while( l < r) {
//在pivot的左邊一直找,找到大于等于pivot值,才退出
while( arr[l] < pivot) {
l += 1;
}
//在pivot的右邊一直找,找到小于等于pivot值,才退出
while(arr[r] > pivot) {
r -= 1;
}
//如果l >= r說明pivot 的左右兩的值,已經按照左邊全部是
//小于等于pivot值,右邊全部是大于等于pivot值
if( l >= r) {
break;
}
//交換
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交換完後,發現這個arr[l] == pivot值 相等 r--, 前移
if(arr[l] == pivot) {
r -= 1;
}
//如果交換完後,發現這個arr[r] == pivot值 相等 l++, 後移
if(arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必須l++, r--, 否則為出現棧溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左遞歸
if(left < r) {
quickSort(arr, left, r);
}
//向右遞歸
if(right > l) {
quickSort(arr, l, right);
}
}
歸并排序
歸并排序(MERGE-SORT)是利用歸并的思想實作的排序方法,該算法采用經典的分治(divide-and-conquer)政策(分治法将問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則将分的階段得到的各答案"修補"在一起,即分而治之)。
//分+合方法
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) / 2; //中間索引
//向左遞歸進行分解
mergeSort(arr, left, mid, temp);
//向右遞歸進行分解
mergeSort(arr, mid + 1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始數組
* @param left 左邊有序序列的初始索引
* @param mid 中間索引
* @param right 右邊索引
* @param temp 做中轉的數組
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 初始化i, 左邊有序序列的初始索引
int j = mid + 1; //初始化j, 右邊有序序列的初始索引
int t = 0; // 指向temp數組的目前索引
//(一)
//先把左右兩邊(有序)的資料按照規則填充到temp數組
//直到左右兩邊的有序序列,有一邊處理完畢為止
while (i <= mid && j <= right) {//繼續
//如果左邊的有序序列的目前元素,小于等于右邊有序序列的目前元素
//即将左邊的目前元素,填充到 temp數組
//然後 t++, i++
if(arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else { //反之,将右邊有序序列的目前元素,填充到temp數組
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩餘資料的一邊的資料依次全部填充到temp
while( i <= mid) { //左邊的有序序列還有剩餘的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while( j <= right) { //右邊的有序序列還有剩餘的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp數組的元素拷貝到arr
//注意,并不是每次都拷貝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最後一次 tempLeft = 0 right = 7
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
基數排序
基數排序(桶排序)介紹
1)基數排序(radix sort)屬于“配置設定式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是通過鍵值的各個位的值,将要排序的元素配置設定至某些“桶”中,達到排序的作用
2)基數排序法是屬于穩定性的排序,基數排序法的是效率高的穩定性排序法
3)基數排序(Radix Sort)是桶排序的擴充
4)基數排序是1887年赫爾曼·何樂禮發明的。它是這樣實作的:将整數按位數切割成不同的數字,然後按每個位數分别比較。
基數排序基本思想
将所有待比較數值統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,
數列就變成一個有序序列。
//基數排序方法
public static void radixSort(int[] arr) {
//根據前面的推導過程,我們可以得到最終的基數排序代碼
//1. 得到數組中最大的數的位數
int max = arr[0]; //假設第一數就是最大數
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大數是幾位數
int maxLength = (max + "").length();
//定義一個二維數組,表示10個桶, 每個桶就是一個一維數組
//說明
//1. 二維數組包含10個一維數組
//2. 為了防止在放入數的時候,資料溢出,則每個一維數組(桶),大小定為arr.length
//3. 名明确,基數排序是使用空間換時間的經典算法
int[][] bucket = new int[10][arr.length];
//為了記錄每個桶中,實際存放了多少個資料,我們定義一個一維數組來記錄各個桶的每次放入的資料個數
//可以這裡了解
//比如:bucketElementCounts[0] , 記錄的就是 bucket[0] 桶的放入資料個數
int[] bucketElementCounts = new int[10];
//這裡我們使用循環将代碼處理
for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
//(針對每個元素的對應位進行排序處理), 第一次是個位,第二次是十位,第三次是百位..
for(int j = 0; j < arr.length; j++) {
//取出每個元素的對應位的值
int digitOfElement = arr[j] / n % 10;
//放入到對應的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照這個桶的順序(一維數組的下标依次取出資料,放入原來數組)
int index = 0;
//周遊每一桶,并将桶中是資料,放入到原數組
for(int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有資料,我們才放入到原數組
if(bucketElementCounts[k] != 0) {
//循環該桶即第k個桶(即第k個一維數組), 放入
for(int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第i+1輪處理後,需要将每個 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
//System.out.println("第"+(i+1)+"輪,對個位的排序處理 arr =" + Arrays.toString(arr));
}
}
基數排序是經典的空間換時間的排序方法,當資料量過大時,容易造成記憶體溢出
堆排序
堆排序基本介紹
1)堆排序是利用堆這種資料結構而設計的一種排序算法,堆排序是一種**選擇排序,**它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。
2)堆是具有以下性質的完全二叉樹:每個結點的值都大于或等于其左右孩子結點的值,稱為大頂堆, 注意 :
沒有要求結點的左孩子的值和右孩子的值的大小關系。
3)每個結點的值都小于或等于其左右孩子結點的值,稱為小頂堆
4)大頂堆舉例說明
堆排序基本思想
堆排序的基本思想是:
1)将待排序序列構造成一個大頂堆
2)此時,整個序列的最大值就是堆頂的根節點。
3)将其與末尾元素進行交換,此時末尾就為最大值。
4)然後将剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反複執行,便能得到一個有序序列了。
可以看到在建構大頂堆的過程中,元素的個數逐漸減少,最後就得到一個有序序列了.
代碼
package com.atguigu.tree;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class HeapSort {
public static void main(String[] args) {
//要求将數組進行升序排序
int arr[] = {4, 6, 8, 5, 9};
// 建立要給80000個的随機的數組
// int[] arr = new int[8000000];
// for (int i = 0; i < 8000000; i++) {
// arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
// }
//
// System.out.println("排序前");
// Date data1 = new Date();
// SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String date1Str = simpleDateFormat.format(data1);
// long time1 = System.currentTimeMillis();
// System.out.println("排序前的時間是=" + date1Str);
//
heapSort(arr);
//
// Date data2 = new Date();
// String date2Str = simpleDateFormat.format(data2);
// System.out.println("排序前的時間是=" + date2Str);
// long time2 = System.currentTimeMillis();
// System.out.println(time2-time1);
//System.out.println("排序後=" + Arrays.toString(arr));
}
//編寫一個堆排序的方法
public static void heapSort(int arr[]) {
int temp = 0;
System.out.println("堆排序!!");
// //分步完成
// adjustHeap(arr, 1, arr.length);
// System.out.println("第一次" + Arrays.toString(arr)); // 4, 9, 8, 5, 6
//
// adjustHeap(arr, 0, arr.length);
// System.out.println("第2次" + Arrays.toString(arr)); // 9,6,8,5,4
//完成我們最終代碼
//将無序序列建構成一個堆,根據升序降序需求選擇大頂堆或小頂堆
for(int i = arr.length / 2 -1; i >=0; i--) {
adjustHeap(arr, i, arr.length);
}
/*
* 2).将堆頂元素與末尾元素交換,将最大元素"沉"到數組末端;
3).重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與目前末尾元素,反複執行調整+交換步驟,直到整個序列有序。
*/
for(int j = arr.length-1;j >0; j--) {
//交換
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
System.out.println("數組=" + Arrays.toString(arr));
}
//将一個數組(二叉樹), 調整成一個大頂堆
/**
* 功能: 完成 将 以 i 對應的非葉子結點的樹調整成大頂堆
* 舉例 int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6} i=length/2 - 1;
* 如果我們再次調用 adjustHeap 傳入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4}
* @param arr 待調整的數組
* @param i 表示非葉子結點在數組中索引
* @param lenght 表示對多少個元素繼續調整, length 是在逐漸的減少
*/
public static void adjustHeap(int arr[], int i, int lenght) {
int temp = arr[i];//先取出目前元素的值,儲存在臨時變量
//開始調整
//說明
//1. k = i * 2 + 1 k 是 i結點的左子結點
for(int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
if(k+1 < lenght && arr[k] < arr[k+1]) { //說明左子結點的值小于右子結點的值
k++; // k 指向右子結點
}
if(arr[k] > temp) { //如果子結點大于父結點
arr[i] = arr[k]; //把較大的值賦給目前結點
i = k; //!!! i 指向 k,繼續循環比較
} else {
break;//!
}
}
//當for 循環結束後,我們已經将以i 為父結點的樹的最大值,放在了 最頂(局部)
arr[i] = temp;//将temp值放到調整後的位置
}
}
常用排序算法總結和對比
相關術語解釋:
1)穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;
2)不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;
3)内排序:所有排序操作都在記憶體中完成;
4)外排序:由于資料太大,是以把資料放在磁盤中,而排序通過磁盤和記憶體的資料傳輸才能進行;
5)時間複雜度: 一個算法執行所耗費的時間。
6)空間複雜度:運作完一個程式所需記憶體的大小。
7)n**: 資料規模
8)k**: “桶“的個數
9)In-place**: 不占用額外記憶體
10)Out-place**:占用額外記憶體