<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
排序是將一批無序的記錄(資料)重新排列成按關鍵字有序的記錄序列的過程。
排序分為內部排序和外部排序
內部排序的過程是一個逐步擴大記錄的有序序列長度的過程
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序演演算法是穩定的;否則稱為不穩定的。
總結起來就是,如果一個資料在排序過程中發生了跳躍行為,則為不穩定的排序;反之,則是穩定的排序。
直接插入排序的基本操作是講一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄數增1的有序表。
程式碼:
public static void insertSort(int[] array) { for (int i = 1; i < array.length; i++) { int tmp = array[i]; int j = i-1; for (; j >= 0 ; j--) { if(array[j] > tmp) { array[j+1] = array[j]; }else { break; } } //j回退到了小於0的地方 array[j+1] = tmp; } }
流程圖解:
時間和複雜度分析: 我們在實現的這個排序演演算法的時候,只借助了一個輔助的記錄空間。
所以最好的情況就是我們原來需要的排序的元素之間就是有序的,比如說{1,2,3,4,5,6},那麼我們需要比較的次數,其實就是臨近兩個元素的比較,因此沒有移動的記錄,時間複雜度為O(n);
最壞的情況就是元素都是逆序的,如{6,5,4,3,2,1},所以都需要比較,移動次數達到O(n^2).
穩定性:穩定
插入排序,初始資料越接近有序,時間效率越高
希爾排序法又稱縮小增量法。希爾排序法的基本思想是:先選定一個整數,把待排序檔案中所有記錄分成個組,所有距離為的記錄分在同一組內,並對每一組內的記錄進行排序。然後,重複上述分組和排序的工作。當到達=1時,所有記錄在統一組內排好序。
希爾對記錄的分組,不是簡單地“逐段分割”,而是將相隔某個“增量”的記錄分成一組。
在嚴薇敏的《資料結構》裡面是這樣說的:
上面的話可能有點難理解,下面我們通過畫圖來了解一下希爾排序的本質。
希爾排序跟直接插入排序有點相似,可以說是直接插入排序的升級版。不同在於,希爾排序的比較方式變成了跳躍式的。比如說下面這組資料的第一次排序。
最終排序完成了。
我們來看一下希爾排序的程式碼:
public static void shell(int[] array,int gap) { for (int i = gap; i < array.length; i++ ) { int tmp = array[i]; int j = i-gap; for (; j >= 0 ; j -= gap) { if(array[j] > tmp) { array[j+gap] = array[j]; }else { break; } } array[j+gap] = tmp; } } //按照5、3、1分組 public static void shellSort1(int[] array) { int[] drr = {5,3,1}; for (int i = 0; i < drr.length; i++) { shell(array,drr[i]); } }
是不是跟直接插入排序很像?所以我們才說,希爾排序是直接插入排序的升級版。它不是隨便分組後各自排序,而是將相隔某個增量gap的記錄組成一個子序列,實現跳躍式移動,使得排序的效率提高了。
所以,選取“增量”是非常關鍵的一步,值得一提的是,選取什麼樣的“增量”,目前為止尚沒有一個沒完美的增量序列。
複雜度分析:
穩定性:不穩定的。
簡單選擇排序:通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第 i(1 ≤ i ≤n) 個記錄交換
public static void selectSort1(int[] array) { for (int i = 0; i < array.length; i++) { for (int j = i+1; j < array.length; j++) { //1 2 3 4 5 6 if(array[j] < array[i]) { swap(array,i,j); } } } } public static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp;
有時候,我們可能不需要回圈那麼多次,迴圈一兩次就有序了,如果在有序的序列中繼續迴圈,則會造成時間的浪費。為避免這種情況,所以我們可以對程式碼進行稍稍改進:
public static void selectSort(int[] array) { for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i+1; j < array.length; j++) { //找到最小值下標 if(array[j] < array[minIndex]) { minIndex = j; } } swap(array,i,minIndex); } }
複雜度分析:
穩定性:穩定
堆排序的基本原理也是選擇排序,只是不在使用遍歷的方式查詢無序區間的最大的數,而是通過堆來選擇無序區間的最大的數。
我上一篇文章有詳細介紹,這裡就不再說了,大家感興趣可以去了解一下。 Java資料結構之優先順序佇列(堆)圖文詳解
這裡也給一下程式碼:
public static void heapSort(int[] array) { //1、建堆 O(N) createHeap(array); int end = array.length-1; //2、交換然後調整 O(N * log N) while (end > 0) { swap(array,0,end); shiftDown(array,0,end); end--; } } public static void createHeap(int[] array) { for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) { shiftDown(array,parent,array.length); } } public static void shiftDown(int[] array,int parent,int len) { int child = 2*parent+1;//左孩子下標 while (child < len) { if(child+1 < len && array[child] < array[child+1]) { child++; } //child下標 就是左右孩子最大值的下標 if(array[child] > array[parent]) { swap(array,child,parent); parent = child; child = 2*parent+1; }else { break; } } }
複雜度分析:
穩定性:不穩定
【注意】
氣泡排序是一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。
程式碼:
public static void bubbleSort(int[] array) { for (int i = 0; i < array.length-1; i++) { for (int j = 0; j < array.length-1; j++) { if(array[j] > array[j+1]) { swap(array,j+1,j); } } } } public static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp;
複雜度分析:
穩定性:穩定。
快速排序是氣泡排序的升級版,它們都屬於交換排序類,即它也是通過不斷比較和移動交換來實現排序,不同的是,快排的實現增大的了記錄的比較和移動的距離。
快排將關鍵字較大的資料從前面直接移到後面,關鍵字較小的記錄從後面直接移到前面,從而減少了總的比較次數和移動交換次數。
快排的基本思想:
通過一趟排序將待排序的資料分割成獨立的兩部分,其中一部分比另一部小,然後再分別對這兩部分記錄並再次進行排序,以達到整個序列有序。
我們翻譯翻譯一下上面的那段話:
可能大家看到這裡也還是有點迷惑,我們直接上程式碼。
public static void quickSort(int[] array) { quick(array,0,array.length-1); } public static void quick(int[] array,int left,int right) { if(left >= right) { return; } int pivot = partition(array,left,right);//基準 quick(array,left,pivot-1); quick(array,pivot+1,right); }
上面的程式碼是不是有點像二元樹的遍歷?
這二者確實有相似之處,我們後面再講。
上面還有一個partition函數,這個函數是我們快速排序最關鍵的地方。
/** * 找基準 * @param array 待排序陣列物件 * @param start左邊界 * @param end右邊界 * @return 基準值下標 */ private static int partition(int[] array,int start,int end) { int tmp = array[start]; while (start < end) { while (start < end && array[end] >= tmp) { end--; } //end下標就遇到了 < tmp的值 array[start] = array[end]; while (start < end && array[start] <= tmp) { start++; } //start下標就遇到了 > tmp的值 array[end] = array[start]; } array[start] = tmp; return start; }
我們下面通過圖解模擬一下函數的執行過程:
可以看到,當第一輪走完之後,資料由基準值分成了兩部分。
之後,我們再次對左右兩部分完成同樣的操作,如下:
一直遞迴下來,跟二元樹的遍歷類似。
複雜度分析:
時間複雜度:
空間複雜度:O(logn)
穩定性:不穩定
不知大家看上面的圖解的時候有沒有一點困惑,就是我們這基準選得不好,導致第一趟遞迴下來的效果不好,變成了如下圖:
如果我們有一種辦法,先找到相對居中的那個數位,那麼整個排序的時間複雜度是不是大大減小了。
於是,有人提出了隨機選取一個值來作為基準,稱為隨機選取法。
這種做法,得看運氣,因為假如選的好,剛剛選取中間值,那麼,效能大大提高;如果隨機得不好,比如隨機到最小值或者最大值,那麼效能則變成最差了。
所以有人提出一個新的方法,三數取中:
取三個關鍵字先進行排序,將中間數作為基準,一般取左端,右端和中間三個數。
如果運用三數取中這種方法的話,第一次比較的結果如下:
可以看到,11基本上與中間的數位相差不遠了,效能大大提高。
所以,這裡我們再寫一個找基準的程式碼:
/** * 找基準 三數取中法 * @param array 待排序陣列物件 * @param left 左邊界 * @param right 右邊界 * @return 基準值下標 */ private static int findMidValIndex(int[] array,int start,int end) { int mid = start + ((end-start) >>> 1); if(array[start] < array[end]) { if(array[mid] < array[start]) { return start; }else if(array[mid] > array[end]) { return end; }else { return mid; } }else { if(array[mid] > array[start]) { return start; }else if(array[mid] < array[end]) { return end; }else { return mid; } } }
前面quick函數改動一下,如下:
public static void quick(int[] array,int left,int right) { if(left >= right) { return; } //採用三數取中法找基準值 int midValIndex = findMidValIndex(array,left,right); swap(array,midValIndex,left); int pivot = partition(array,left,right);//基準 quick(array,left,pivot-1); quick(array,pivot+1,right); }
其實,優化到這裡已經很棒了 。但是,我們還能優化。
這裡先插播一個知識點:
直接插入是簡單排序中效能最好的。 所以如果要我們要排序的陣列非常小,直接插入排序會更好。 原因在於,快速排序用到了遞迴操作,在大量的資料排序時,這點效能影響相對它的整體演演算法優勢而言是可以忽略的。但是如果陣列只有不多的資料需要排序,就有點大材小用了。
因此,我們在這裡的優化是:
增加一個判斷,當 right-left+1 不大於某個常數時,就用直接插入排序,這個常數是具體情況而定。這樣我們就能保證最大化地利用兩種排序的優勢來完成排序工作了。
/** * 優化,加入插入排序 * @param array 待排序陣列物件 * @param left 左邊界 * @param right 右邊界 * @return 基準值下標 */ public static void quick(int[] array,int left,int right) { if(left >= right) { return; } //加一個判斷,如果區間內的資料,在排序的過程當中,小於某個範圍了,可以使用直接插入排序 if(right-left+1 <= 1400) { //使用直接插入排序 insertSort2(array,left,right); return; } //1、找基準之前,我們找到中間大小的值-使用三數取中法 int midValIndex = findMidValIndex(array,left,right); swap(array,midValIndex,left); int pivot = partition(array,left,right);//基準 quick(array,left,pivot-1); quick(array,pivot+1,right); } //插入排序 public static void insertSort(int[] array,int start,int end) { for (int i = 1; i <= end; i++) { int tmp = array[i]; int j = i-1; for (; j >= start ; j--) { if(array[j] > tmp) { array[j+1] = array[j]; }else { break; } } array[j+1] = tmp; } }
我們都知道,遞迴對效能是有一定影響的。所以我們也可以採用非遞迴的方式來實現快速排序
/** * 快速排序非遞迴實現 * @param array 待排序陣列 */ public static void quickSort(int[] array) { Stack<Integer> stack = new Stack<>(); int left = 0; int right = array.length-1; int pivot = partition(array,left,right); if(pivot > left+1) { stack.push(left); stack.push(pivot-1); } if(pivot < right-1) { stack.push(pivot+1); stack.push(right); } while (!stack.isEmpty()) { right = stack.pop(); left = stack.pop(); pivot = partition(array,left,right); if(pivot > left+1) { //左邊有2個元素 stack.push(left); stack.push(pivot-1); } if(pivot < right-1) { //右邊有2個元素 stack.push(pivot+1); stack.push(right); } } }
非遞迴複雜度分析:
時間複雜度:
空間複雜度:
穩定性:不穩定
歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演演算法,該演演算法是採用分治法(Divide an Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併
直接上程式碼:
//呼叫了mergeSortInternal函數 public static void mergeSort1(int[] array) { mergeSortInternal(array,0,array.length-1); } private static void mergeSortInternal(int[] array,int low,int high) { if(low>=high) { return; } int mid = low + ((high-low) >>> 1);//>>>無符號右移1位。就是除以2,找中間值 //左邊遞迴 mergeSortInternal(array,low,mid); //右邊遞迴 mergeSortInternal(array,mid+1,high); //合併兩邊陣列 merge(array,low,mid,high); }
mergeSortInternal函數的圖解:
其實就是對半分開陣列
這裡這個merge函數是歸併排序裡面的關鍵,無論是採用遞迴還是非遞迴都必須採用到這部分的函數。
而其本質,其實就是合併兩個陣列,並使其有序起來。
merge函數的程式碼:
private static void merge(int[] array,int low,int mid,int high) { int[] tmp = new int[high-low+1]; int k = 0;// int s1 = low; int e1 = mid; int s2 = mid+1; int e2 = high; while (s1 <= e1 && s2 <= e2) { if(array[s1] <= array[s2]) { tmp[k++] = array[s1++]; }else { tmp[k++] = array[s2++]; } } while (s1 <= e1) { tmp[k++] = array[s1++]; } while (s2 <= e2) { tmp[k++] = array[s2++]; } //拷貝tmp陣列的元素 放入原來的陣列array當中 for (int i = 0; i < k; i++) { array[i+low] = tmp[i]; } }
歸併排序除了用遞迴的方式來實現,也可以用非遞迴的方式來實現。
public static void mergeSort(int[] array) { int nums = 1;//每組的資料個數 while (nums < array.length) { //陣列每次都要進行遍歷,確定要歸併的區間 for (int i = 0; i < array.length; i += nums*2) { int left = i; int mid = left+nums-1; //防止越界 if(mid >= array.length) { mid = array.length-1; } int right = mid+nums; //防止越界 if(right >= array.length) { right = array.length-1; } //小標確定之後,進行合併 merge(array,left,mid,right); } nums *= 2;陣列合併後,以1-2-4-8-16-進行迴圈 } }
圖解如下:
這次常見排序的文章,來來回回寫了兩天左右,在寫的過程,也是學習的過程,特別是裡面畫圖的時候,得理清楚整個排序的思想,才能很好的作出整個圖解出來。各位看客老爺們,希望看到能給個三連,感謝!
到此這篇關於Java資料結構常見幾大排序梳理的文章就介紹到這了,更多相關Java 排序內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援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