<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文章主要是講解我個人在學習Java開發環境的排序演演算法時做的一些準備,以及個人的心得體會,彙整合本篇文章,作為自己對排序演演算法理解的總結與筆記。
內容主要是關於十大經典排序演演算法的簡介、原理、動靜態圖解和原始碼實現的分析。
對於一名程式設計師來講,我們都知道資料結構與演演算法起初是用於C語言居多,然而在Java語言下使用演演算法的案例卻很少,因此,特別整理了在Java開發環境的排序演演算法,供大家一起學習探討。
所謂排序演演算法,即通過特定的演演算法因式將一組或多組資料按照既定模式進行重新排序。這種新序列遵循著一定的規則,體現出一定的規律,因此,經處理後的資料便於篩選和計算,大大提高了計算效率。對於排序,我們首先要求其具有一定的穩定性,即當兩個相同的元素同時出現於某個序列之中,則經過一定的排序演演算法之後,兩者在排序前後的相對位置不發生變化。換言之,即便是兩個完全相同的元素,它們在排序過程中也是各有區別的,不允許混淆不清。
常見的排序演演算法有:插入排序、希爾排序、選擇排序、氣泡排序、歸併排序、快速排序、堆排序、基數排序等。
演演算法圖解(菜鳥教學借圖):
圖解分析:
時間複雜度
平方階 (O(n2)) 排序 各類簡單排序:直接插入、直接選擇和氣泡排序。
線性對數階 (O(nlog2n)) 排序 快速排序、堆排序和歸併排序;
O(n1+§)) 排序,§ 是介於 0 和 1 之間的常數。 希爾排序
線性階 (O(n)) 排序 基數排序,此外還有桶、箱排序。
關於穩定性
穩定的排序演演算法:氣泡排序、插入排序、歸併排序和基數排序。
不是穩定的排序演演算法:選擇排序、快速排序、希爾排序、堆排序。
名詞解釋:
平均時間複雜度是指所有可能的輸入範例均以等概率的出現情況下得到演演算法的執行時間
最壞時間複雜度,一般討論的時間複雜度均是最壞情況下的時間複雜度,這樣做的原因是最壞情況下的時間複雜度是演演算法在任何輸入範例上執行的界限,這就保證了演演算法的執行時間不會比最壞情況更長。
平均時間複雜度和最壞時間複雜度是否一樣,這就需要根據演演算法不同而不同了。
PS:案例均以陣列{15,63,97,12,235,66}排序為例
氣泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演演算法。
它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果順序(如從大到小、首字母從Z到A)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素列已經排序完成。
這個演演算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端一樣,故名“氣泡排序”。
1.1實現原理
1.2動圖演示
1.3範例展示
import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { int[] arrays =new int[]{15,63,97,12,235,66}; for (int i=0;i<arrays.length-1;i++){ // 控制比較次數,三者交換,實現排序 for(int j=0;j<arrays.length-1-i;j++){ if (arrays[j] > arrays[j+1]){ int temp = 0;//類似空桶 temp = arrays[j]; //A桶中水倒入空桶C中 arrays[j] = arrays[j+1];//把B桶的水倒入A桶中 arrays[j+1] = temp;//把C桶的水倒入B桶中 } } } System.out.println(Arrays.toString(arrays)); } }
排序結果展示:
快速排序(Quicksort),電腦科學詞彙,適用領域Pascal,c++等語言,是對氣泡排序演演算法的一種改進。
2.1實現原理
快速排序是對氣泡排序的一種改進。通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另一部分所有的資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列
2.2 動圖演示
2.3範例展示
import java.util.Arrays; public class QuickSort { public static void main(String[] args) { int[] arrays = new int[]{15,63,97,12,235,66}; sort(arrays,0,arrays.length-1); System.out.println(Arrays.toString(arrays)); } public static void sort(int[] arrays,int left,int right){ int l = left; int r = right; int pivot = arrays[(left+right)/2]; int temp = 0; while (l<r){ //在左邊查詢小於中間值的 while(arrays[l]<pivot){ l++; } //查詢右邊小於中間值 while (arrays[r]>pivot){ r--; } if (l>=r){ break; } temp = arrays[l]; arrays[l] = arrays[r]; arrays[r] = temp; // 交換完資料arrays[l] = pivot if (arrays[l]==pivot){ r--; } if (arrays[r]==pivot){ l++; } if (r==l){ l++; r--; } if (left<r){ sort(arrays,left,r); } if (right>l){ sort(arrays,l,right); } } } }
排序結果展示:
基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法是屬於穩定性的排序,其時間複雜度為O (nlog(r)m),其中r為所採取的基數,而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。
基數排序是1887年赫爾曼、何樂禮發明的。思想是講整數按位元數切割成不同的數位,然後按每個位數分別比較。
3.1實現原理
講所有的待比較數值統一設定為同樣的數位長度,位數比較短的數前面補零,然後從最地位開始依次進行一次排序,這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。
3.2 動圖演示
3.3範例展示
import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; public class BasicSort { public static void main(String[] args) { int[] arrays = new int[]{15,63,97,12,235,66}; SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-mm-dd HH:MM:ss:SS"); System.out.println("開始排序前:"+simpleDateFormat.format(new Date())); sort(arrays); System.out.println("排序結束:"+simpleDateFormat.format(new Date())); } // 1.獲取原序列的最大位多少 // @param arrays public static void sort(int[] arrays){ // 獲取最大位數 int max = 0; for(int i=0;i<arrays.length;i++){ if (arrays[i]>max){ max = arrays[i]; } } // 獲取字串長度,所以把int型別轉字串型別 int maxLength = (max+"").length(); // 定義二維陣列,大小10,表示10個桶,每一個桶則是一個陣列 // [[],[],[],[],[]...] int[][] bucket = new int[10][arrays.length]; // 輔助陣列 int[] bucketElementCount = new int[10]; // 迴圈獲取無序數列 for (int j=0;j<arrays.length;j++){ int locationElement = arrays[j]%10; // 放入桶中 bucket[locationElement][bucketElementCount[locationElement]] = arrays[j] ; bucketElementCount[locationElement]++; } // 遍歷每一個桶,講元素存放原陣列中 int index = 0; for (int k = 0;k<bucketElementCount.length;k++){ if (bucketElementCount[k] !=0){ for (int l = 0;l<bucketElementCount[k];l++){ arrays[index++] = bucket[k][l]; } } bucketElementCount[k] = 0; } System.out.println(Arrays.toString(arrays)); // 第一輪針對個位數進行比較 for (int j = 0;j<arrays.length;j++){ int locationElement = arrays[j]/1%10; bucket[locationElement][bucketElementCount[locationElement]] = arrays[j]; bucketElementCount[locationElement]++; } // 取出來按照桶的順序放回原陣列中 int indx = 0; for (int k = 0;k<bucketElementCount.length;k++){ if (bucketElementCount[k] !=0){ for (int l=0;l<bucketElementCount[k];l++){ arrays[indx++] = bucket[k][l]; } } bucketElementCount[k] = 0; } // 判斷十位數 for (int j = 0;j<arrays.length;j++){ int locationElement = arrays[j]/10%10; bucket[locationElement][bucketElementCount[locationElement]] = arrays[j]; bucketElementCount[locationElement]++; } // 取出來按照桶的順序放回原陣列中 indx = 0; for (int k = 0;k<bucketElementCount.length;k++){ if (bucketElementCount[k] !=0){ for (int l=0;l<bucketElementCount[k];l++){ arrays[indx++] = bucket[k][l]; } } bucketElementCount[k] = 0; } // 獲取百位數比較 for (int j = 0;j<arrays.length;j++){ int locationElement = arrays[j]/100%10; bucket[locationElement][bucketElementCount[locationElement]] = arrays[j]; bucketElementCount[locationElement]++; } // 取出來按照桶的順序放回原陣列中 indx = 0; for (int k = 0;k<bucketElementCount.length;k++){ if (bucketElementCount[k] !=0){ for (int l=0;l<bucketElementCount[k];l++){ arrays[indx++] = bucket[k][l]; } } bucketElementCount[k] = 0; } System.out.println("基數排序後的順序:"+Arrays.toString(arrays)); } }
排序結果展示:
插入排序,一般也被稱為直接插入排序。對於少量元素的排序,它是一個有效的演演算法 。插入排序是一種最簡單的排序方法,它的基本思想是將一個記錄插入到已經排好序的有序表中,從而一個新的、記錄數增1的有序表。在其實現過程使用雙層迴圈,外層迴圈對除了第一個元素之外的所有元素,內層迴圈對當前元素前面有序表進行待插入位置查詢,並進行移動 。
4.1實現原理
插入排序的工作方式像許多人排序一手撲克牌。開始時,我們的左手為空並且桌子上的牌面向下。然後,我們每次從桌子上拿走一張牌並將它插入左手中正確的位置。為了找到一張牌的正確位置,我們從右到左將它與已在手中的每張牌進行比較。拿在左手上的牌總是排序好的,原來這些牌是桌子上牌堆中頂部的牌。
插入排序是指在待排序的元素中,假設前面n-1(其中n>=2)個數已經是排好順序的,現將第n個數插到前面已經排好的序列中,然後找到合適自己的位置,使得插入第n個數的這個序列也是排好順序的。按照此法對所有元素進行插入,直到整個序列排為有序的過程,稱為插入排序。
4.2 動圖演示
4.3範例展示
public class InsertSort { public static void main(String[] args) { int[] array = new int[]{15,63,97,12,235,66}; //控制拿去每一個元素 for(int i=1;i<array.length;i++){ //比較次數 for (int j=i;j>=1;j--){ //是否小於前面的元素 if (array[j]<array[j-1]){ int temp = 0; temp = array[j]; array[j] = array[j-1]; array[j-1] = temp; }else { //continue 與 break break; } } } System.out.println("排序後的結果:"+ Arrays.toString(array)); } }
排序結果展示:
選擇排序(Selection sort)是一種簡單直觀的排序演演算法。它的工作原理是:第一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾。以此類推,直到全部待排序的資料元素的個數為零。選擇排序是不穩定的排序方法。
5.1實現原理
第一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾。以此類推,直到全部待排序的資料元素的個數為零。選擇排序是不穩定的排序方法。
5.2 動圖演示
5.3範例展示
import java.util.Arrays; public class SelectSort { public static void main(String[] args) { int[] arr = new int[]{15,63,97,12,235,66}; for (int i=0;i<arr.length;i++){ for (int j=arr.length-1;j>i;j--){ if (arr[j]<arr[i]){ int temp = 0; temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } } System.out.println("選擇排序後的結果:"+ Arrays.toString(arr)); } }
排序結果展示:
希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是插入排序演演算法的一種更高效的改進版本。希爾排序是非穩定排序演演算法。該方法因 D.L.Shell 於 1959 年提出而得名。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至 1 時,整個檔案恰被分成一組,演演算法便終止。
6.1實現原理
先取一個小於n的整數d1作為第一個增量,把檔案的全部記錄分組。所有距離為d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2
般的初次取序列的一半為增量,以後每次減半,直到增量為1。
6.2 動圖演示
6.3範例展示
import java.util.Arrays; public class ShellSort { public static void main(String[] args) { int[] array = new int[]{15,63,97,12,235,66}; // 實現增量變化 for (int gap = array.length/2;gap>0;gap/=2){ for (int i=gap;i<array.length;i++){ for (int j=i-gap;j>=0;j-=gap){ if (array[j]>array[j+gap]){ int temp = 0; temp = array[j]; array[j] = array[j+gap]; array[j+gap] = temp; } } } } System.out.println(Arrays.toString(array)); } }
排序結果展示:
歸併排序(Merge Sort)是建立在歸併操作上的一種有效,穩定的排序演演算法,該演演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併。
7.1實現原理
我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖最後一次合併,將[2,4,5,6]和[1,3,7,8]已經有序的子序列合併最終序列[1,2,3,4,5,6,7,8]
7.2 動圖演示
7.3範例展示
import java.util.Arrays; public class MSort { public static void main(String[] args) { int[] array = new int[]{15,63,97,12,235,66}; //臨時陣列 int[] temp = new int[array.length]; sort(array,0,array.length-1,temp); System.out.println(Arrays.toString(array)); } public static void sort(int[] array,int left,int right,int[] temp){ if (left<right){ // 求出中間值 int mid = (left+right)/2; // 向左邊分解 sort(array,left,mid,temp); // 向右邊分解 sort(array,mid+1,right,temp); // 合併資料 sum(array,left,right,mid,temp); } } /** * 合併元素 * @param array * @param left * @param right * @param mid * @param temp */ public static void sum(int[] array,int left,int right,int mid,int[] temp){ int i = left; int j = mid+1; // 指向臨時陣列下標 int t = 0; // 開始迴圈比較左右兩遍陣列元素比較 while (i<=mid && j<=right){ if (array[i]<=array[j]){ temp[t] = array[i]; t++; i++; }else { temp[t] = array[j]; t++; j++; } } // 把剩餘的元素直接存放在臨時陣列中 while(i<=mid){ temp[t] = array[i]; t++; i++; } while (j<=right){ temp[t] = array[j]; t++; j++; } // 臨時陣列中的元素拷貝至原陣列中 int tempIndex = left; int k = 0; while (tempIndex<=right){ array[tempIndex] = temp[k]; k++; tempIndex++; } } 75 }
排序結果展示:
計數排序是一個非基於比較的排序演演算法,該演演算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定範圍內的整數排序時,它的複雜度為Ο(n+k)(其中k是整數的範圍),快於任何比較排序演演算法。 當然這是一種犧牲空間換取時間的做法,而且當O(k)>O(n*log(n))的時候其效率反而不如基於比較的排序(基於比較的排序的時間複雜度在理論上的下限是O(n*log(n)), 如歸併排序,堆排序)
8.1實現原理
假設輸入的線性表L的長度為n,L=L1,L2,..,Ln;線性表的元素屬於有限偏序集S,|S|=k且k=O(n),S={S1,S2,..Sk};則計數排序可以描述如下:
8.2 動圖演示
8.3範例展示
public class CountSort { public static void main(String[]args){ //排序的陣列 int a[]={15,63,97,12,235,66}; int b[]=countSort(a); for(int i:b){ System.out.print( i+","); } System.out.println(); } public static int[] countSort(int[]a){ int b[] = new int[a.length]; int max = a[0],min = a[0]; for(int i:a){ if(i>max){ max=i; } if(i<min){ min=i; } }//這裡k的大小是要排序的陣列中,元素大小的極值差+1 int k=max-min+1; int c[]=new int[k]; for(int i=0;i<a.length;++i){ c[a[i]-min]+=1;//優化過的地方,減小了陣列c的大小 } for(int i=1;i<c.length;++i){ c[i]=c[i]+c[i-1]; } for(int i=a.length-1;i>=0;--i){ b[--c[a[i]-min]]=a[i];//按存取的方式取出c的元素 } return b; } }
排序結果展示:
堆排序(英語:Heapsort)是指利用堆這種資料結構所設計的一種排序演演算法。堆是一個近似完全二元樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
9.1實現原理
9.2 動圖演示
9.3範例展示
public static int[] heapSort(int[] array) { //這裡元素的索引是從0開始的,所以最後一個非葉子結點array.length/2 - 1 for (int i = array.length / 2 - 1; i >= 0; i--) { adjustHeap(array, i, array.length); //調整堆 } // 上述邏輯,建堆結束 // 下面,開始排序邏輯 for (int j = array.length - 1; j > 0; j--) { // 元素交換,作用是去掉大頂堆 // 把大頂堆的根元素,放到陣列的最後;換句話說,就是每一次的堆調整之後,都會有一個元素到達自己的最終位置 swap(array, 0, j); // 元素交換之後,毫無疑問,最後一個元素無需再考慮排序問題了。 // 接下來我們需要排序的,就是已經去掉了部分元素的堆了,這也是為什麼此方法放在迴圈裡的原因 // 而這裡,實質上是自上而下,自左向右進行調整的 adjustHeap(array, 0, j); } return array; } /** * 整個堆排序最關鍵的地方 * @param array 待組堆 * @param i 起始結點 * @param length 堆的長度 */ public static void adjustHeap(int[] array, int i, int length) { // 先把當前元素取出來,因為當前元素可能要一直移動 int temp = array[i]; for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1為左子樹i的左子樹(因為i是從0開始的),2*k+1為k的左子樹 // 讓k先指向子節點中最大的節點 if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子樹,並且右子樹大於左子樹 k++; } //如果發現結點(左右子結點)大於根結點,則進行值的交換 if (array[k] > temp) { swap(array, i, k); // 如果子節點更換了,那麼,以子節點為根的子樹會受到影響,所以,迴圈對子節點所在的樹繼續進行判斷 i = k; } else { //不用交換,直接終止迴圈 break; } } } /** * 交換元素 * @param arr * @param a 元素的下標 * @param b 元素的下標 */ public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }
排序結果展示:
桶排序 (Bucket sort)或所謂的箱排序,是一個排序演演算法,工作的原理是將陣列分到有限數量的桶子裡。每個桶子再個別排序(有可能再使用別的排序演演算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。
10.1實現原理
假定:輸入是由一個隨機過程產生的[0, 1)區間上均勻分佈的實數。將區間[0, 1)劃分為n個大小相等的子區間(桶),每桶大小1/n:[0, 1/n), [1/n, 2/n), [2/n, 3/n),…,[k/n, (k+1)/n ),…將n個輸入元素分配到這些桶中,對桶中元素進行排序,然後依次連線桶輸入0 ≤A[1..n] <1輔助陣列B[0..n-1]是一指標陣列,指向桶(連結串列)。
10.2 動圖演示
然後,元素在每個桶中排序:
10.3範例展示
public static void basket(int data[])//data為待排序陣列 { int n=data.length; int bask[][]=new int[10][n]; int index[]=new int[10]; int max=Integer.MIN_VALUE; for(int i=0;i<n;i++) { max=max>(Integer.toString(data[i]).length())?max:(Integer.toString(data[i]).length()); } String str; for(int i=max-1;i>=0;i--) { for(int j=0;j<n;j++) { str=""; if(Integer.toString(data[j]).length()<max) { for(int k=0;k<max-Integer.toString(data[j]).length();k++) str+="0"; } str+=Integer.toString(data[j]); bask[str.charAt(i)-'0'][index[str.charAt(i)-'0']++]=data[j]; } int pos=0; for(int j=0;j<10;j++) { for(int k=0;k<index[j];k++) { data[pos++]=bask[j][k]; } } for(intx=0;x<10;x++)index[x]=0; } }
排序結果展示:
以上就是Java十大經典排序演演算法的實現圖解的詳細內容,更多關於Java排序演演算法的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45