Java數據結構和算法總結-冒泡排序、選擇排序、插入排序算法分析
前言:排序在算法中的地位自然不必多說,在許多工作中都用到了排序,就像學生成績統計名次、商城商品銷量排名、新聞的搜索熱度排名等等。也正因為排序的應用範圍如此之廣,引起了許多人深入研究它的興趣,直至今天,排序算法已經出現了很多種。本篇博文主要介紹常見的八種排序算法,總得來說,不同的排序算法在不同的場景下都有著自己獨特的優點,例如一下簡單的冒泡排序、選擇排序、插入排序不僅思路簡單,有利於我們理解,而且在小規模的數據量的處理中並不遜色。接下來我們就一一分析一下各算法的優缺點以及時間復雜度。
本篇博文的所有代碼已上傳 github ,對應工程的 exercise 模塊下的 cn.codingblock.sort 包,下載地址:https://github.com/lgliuwei/DataStructureStudy,項目工程為 IntelliJ IDEA 環境,童鞋不妨下載下來,參照著代碼看博文豈不是效果更好~(額~由於本工程為練習工程,結構比較隨意而且還有重復代碼,參考時還請多找找:D)
一、冒泡排序
冒泡排序非常簡單,也很易於理解,其邏輯思路就像水裏面冒氣泡一樣,越往上氣泡越大,也因此而得名冒泡排序,其具體規則如下:
1、從一組的數據的起始位置開始依次向後比較相鄰兩個數,
2、如果前一個數比後一個數大則交換位置,
3、一直比較到最後一個數。
此時,這組數的最後一個數一定是最大的了,然後進行下一輪如此比較到倒數第二個數,再下一輪比較到倒數第三個,如此循環,直到有一輪比較到第二個數結束,最終這一組數就是有序的了。
上代碼讓日誌演示一下過程,具體代碼如下:
1 /** 2 * 冒泡排序 3 * @param nums 4*/ 5 public void bubbleSort(int[] nums){ 6 int count = 0;// 日誌輔助代碼 7 for (int out = nums.length - 1; out > 1; out--){ 8 for (int in = 0; in < out; in++) { 9 Logger.print("比較 " + nums[in] + " , " + nums[in + 1]); 10 if (nums[in] > nums[in + 1]) {11 int temp = nums[in+1]; 12 nums[in+1] = nums[in]; 13 nums[in] = temp; 14 Logger.println(": 交換: ");// 日誌輔助代碼 15 display(nums, in, in + 1);// 日誌輔助代碼 16 } else { 17 Logger.println(": 不交換");// 日誌輔助代碼 18 } 19 } 20 Logger.print(">>第" + (++count) + "趟冒泡:");// 日誌輔助代碼 21 display(nums);// 日誌輔助代碼 22 } 23 }
日誌如下:
排序前:23, 52, 81, 69, 78, 32, 67, 70, 36, 73, 比較 23 , 52: 不交換 比較 52 , 81: 不交換 比較 81 , 69: 交換: 23, 52, [69], [81], 78, 32, 67, 70, 36, 73, 比較 81 , 78: 交換: 23, 52, 69, [78], [81], 32, 67, 70, 36, 73, 比較 81 , 32: 交換: 23, 52, 69, 78, [32], [81], 67, 70, 36, 73, 比較 81 , 67: 交換: 23, 52, 69, 78, 32, [67], [81], 70, 36, 73, 比較 81 , 70: 交換: 23, 52, 69, 78, 32, 67, [70], [81], 36, 73, 比較 81 , 36: 交換: 23, 52, 69, 78, 32, 67, 70, [36], [81], 73, 比較 81 , 73: 交換: 23, 52, 69, 78, 32, 67, 70, 36, [73], [81], >>第1趟冒泡:23, 52, 69, 78, 32, 67, 70, 36, 73, 81, 比較 23 , 52: 不交換 比較 52 , 69: 不交換 比較 69 , 78: 不交換 比較 78 , 32: 交換: 23, 52, 69, [32], [78], 67, 70, 36, 73, 81, 比較 78 , 67: 交換: 23, 52, 69, 32, [67], [78], 70, 36, 73, 81, 比較 78 , 70: 交換: 23, 52, 69, 32, 67, [70], [78], 36, 73, 81, 比較 78 , 36: 交換: 23, 52, 69, 32, 67, 70, [36], [78], 73, 81, 比較 78 , 73: 交換: 23, 52, 69, 32, 67, 70, 36, [73], [78], 81, >>第2趟冒泡:23, 52, 69, 32, 67, 70, 36, 73, 78, 81, 比較 23 , 52: 不交換 比較 52 , 69: 不交換 比較 69 , 32: 交換: 23, 52, [32], [69], 67, 70, 36, 73, 78, 81, 比較 69 , 67: 交換: 23, 52, 32, [67], [69], 70, 36, 73, 78, 81, 比較 69 , 70: 不交換 比較 70 , 36: 交換: 23, 52, 32, 67, 69, [36], [70], 73, 78, 81, 比較 70 , 73: 不交換 >>第3趟冒泡:23, 52, 32, 67, 69, 36, 70, 73, 78, 81, 比較 23 , 52: 不交換 比較 52 , 32: 交換: 23, [32], [52], 67, 69, 36, 70, 73, 78, 81, 比較 52 , 67: 不交換 比較 67 , 69: 不交換 比較 69 , 36: 交換: 23, 32, 52, 67, [36], [69], 70, 73, 78, 81, 比較 69 , 70: 不交換 >>第4趟冒泡:23, 32, 52, 67, 36, 69, 70, 73, 78, 81, 比較 23 , 32: 不交換 比較 32 , 52: 不交換 比較 52 , 67: 不交換 比較 67 , 36: 交換: 23, 32, 52, [36], [67], 69, 70, 73, 78, 81, 比較 67 , 69: 不交換 >>第5趟冒泡:23, 32, 52, 36, 67, 69, 70, 73, 78, 81, 比較 23 , 32: 不交換 比較 32 , 52: 不交換 比較 52 , 36: 交換: 23, 32, [36], [52], 67, 69, 70, 73, 78, 81, 比較 52 , 67: 不交換 >>第6趟冒泡:23, 32, 36, 52, 67, 69, 70, 73, 78, 81, 比較 23 , 32: 不交換 比較 32 , 36: 不交換 比較 36 , 52: 不交換 >>第7趟冒泡:23, 32, 36, 52, 67, 69, 70, 73, 78, 81, 比較 23 , 32: 不交換 比較 32 , 36: 不交換 >>第8趟冒泡:23, 32, 36, 52, 67, 69, 70, 73, 78, 81, 排序後:23, 32, 36, 52, 67, 69, 70, 73, 78, 81,
冒泡排序的時間復雜度:O(n^2)。
通過分析我們可以得出,如果對 N 個數據項進行排序,一般情況下第一趟排序比較了 N-1 次,第二趟排序比較了 N-2 次,依次類推。所以冒泡排序對N個數據項排序時需要比較的次數為:
(N-1)+(N-2)+(N-3)+....+ = N*(N-1)/2 方便起見,近似看做比較了 (N^2)/2 次數。
用大 O 表示法忽略常數,所以其時間復雜度為:O(n^2)。
二、選擇排序
選擇排序是在冒泡排序的基礎上做了一些改進,雖然比較次數仍然是O(n^2),但它將必要的交換次數從O(n^2)將到了O(n)次,其排序規則如下:
1、從數組的0下標開始標記為最小,
2、從1下標開始與標記下標的值比較,如果小於標記下標的值則更新將標記賦值為1下標,依次往後類推直到最後遍歷完數組,最後將0下標與最小標記下標的值交換位置。
3、然後再從數組1小標開始比較為最小,類比第二步最終交換1下標與最小標記下標的位置。依次類推。。。
選擇排序的一趟排序過程示意圖(非排序全程):
選擇詳細代碼如下:
1 /** 2 * 選擇排序 3 * @param nums 4 */ 5 public void selectionSort(int[] nums) { 6 int min; 7 for (int out = 0; out < nums.length; out++) { 8 min = out; 9 for (int in = out + 1; in <nums.length; in++) { 10 if (nums[in] < nums[min]) { 11 min = in; 12 } 13 } 14 int temp = nums[min]; 15 nums[min] = nums[out]; 16 nums[out] = temp; 17 } 18 }
選擇排序的時間復雜度為:O(N^2)。
相比冒泡而言,選擇排序雖然大大減少了交換次數,但是也比較了和冒泡相同的次數,所以其時間復雜度也為:O(N^2)。
三、插入排序
插入排序是根據有序插入的思想來的,可以對照上篇博文中的有序插入來理解插入排序,其大概規則如下:
1、最數組的1下標開始賦值給臨時變量,看成待插入元素,
2、從數組的1下標向前遍歷,凡是大於臨時變量的元素均後移一位,直到遇到不大於臨時變量的值時將臨時變量插入到它後一位。
3、然後在從數組的2下標開始賦值給臨時變量,並從2下標向前遍歷依照第二步遇到不大於臨時變量時將臨時變量插入到它後一位,依次類推...
插入排序的詳細代碼如下:
1 /** 2 * 插入排序 3 * @param nums 4 */ 5 public void insertionSort1(int[] nums) { 6 int temp; 7 int in; 8 for(int out = 1; out < nums.length; out++) { 9 temp = nums[out]; 10 in = out; 11 while (in > 0 && nums[in - 1] > temp) { 12 nums[in] = nums[in - 1]; 13 in--; 14 } 15 nums[in] = temp; 16 } 17 }
插入排序的時間復雜度:O(N^2)。
對於隨機數據,插入排序速度是冒泡排序的二倍,比選擇排序也要快一點,而對於基本有序的數據來說插入排序表現則要出色的多,因為基本有序的數據排序時,while 循環的條件大部分情況下都不成立,這樣基本就相當於只執行了外層的一層循環,某些理想情況下插入排序的時間復雜度可以達到 O(N),但通常情況下,插入平均時間復雜度為:O(N^2)。
四、小結
除了上面介紹的三種簡單排序之外,還有歸並排序、希爾排序、快速排序、基數排序、堆排序等等,後面這幾種都是高級排序,稍微復雜一些,而且用到了遞歸、樹等知識,所以這些排序將會在以後的博文中介紹,本篇中的三種排序對比如下:
Java數據結構和算法總結-冒泡排序、選擇排序、插入排序算法分析