摘要java
本文系Introduction to Java Programming 10e (Java語言程序設計-進階篇)的學習筆記。涉及如下內容:
插入排序
冒泡排序
歸併排序
快速排序
堆排序
桶排序和基數排序
外部排序
node
一. 插入排序
插入排序的時間複雜度是O(n^2)。
插入排序重複地將新元素插入到一個排好序的子線性表中,直到整個線性表排好序。
算法描述以下:
for(int i=0;i<list.length;i++){
將list[i]插入,將致使已排好序的子線性表後移。
}算法
public static void insertionSort(int[] list) { if(list.length>=2) { for(int i=1;i<list.length;i++) { int currentElement=list[i]; int k; for(k=i-1;k>=0&&list[k]>currentElement;k--) { list[k+1]=list[k]; } list[k+1]=currentElement; } } }
二. 冒泡排序
最差狀況下冒泡的時間複雜度是O(n^2)。
冒泡排序屢次遍歷數組,在每次遍歷中連續比較相鄰的元素,若是沒按順序排列,就交換它們的值。
較小的值像「氣泡」同樣逐漸浮向頂部,而較大的值沉向底部。第一次遍歷後最後一個元素是數組中的最大值,第二次遍歷後,倒數第二個元素是數組中第二大的數。這個過程持續到全部元素都排好序。數組
public static void bubbleSort(int[] list) { boolean needNextPass=true; for(int k=1;k<list.length&&needNextPass;k++) { needNextPass=false; for(int i=0;i<list.length-k;i++) { if (list[i] > list[i + 1]) { int temp = list[i + 1]; list[i + 1] = list[i]; list[i] = temp; needNextPass=true; } } } System.out.println(Arrays.toString(list)); }
三. 歸併排序
歸併排序的時間複雜度是O(nlogn)。
歸併排序將數組分爲2半,對每部分遞歸地應用歸併排序。在每部分排好序後對它們進行歸併。less
public class MergeSort { public static void mergeSort(int[] list) { if(list.length>1) { int[] firstHalf = new int[list.length / 2]; System.arraycopy(list, 0, firstHalf, 0, list.length / 2); mergeSort(firstHalf); int secondHalfLength = list.length - list.length / 2; int[] secondHalf = new int[secondHalfLength]; System.arraycopy(list, list.length / 2, secondHalf, 0, secondHalfLength); mergeSort(secondHalf); merge(firstHalf, secondHalf, list); } } public static void merge(int[] list1,int[] list2,int[] temp) { int current1=0; int current2=0; int current3=0; while (current1<list1.length&¤t2<list2.length) { if(list1[current1]<list2[current2]) temp[current3++]=list1[current1++]; else temp[current3++]=list2[current2++]; } while (current1<list1.length) { temp[current3++]=list1[current1++]; } while (current2<list2.length) { temp[current3++]=list2[current2++]; } } }
四. 快速排序
快速排序的平均時間爲O(nlogn) –各類精確的平均狀況分析超出本筆記目標。快排的空間效率高於歸併排序。
快速排序的工做機制:該算法在數組中選擇一個元素稱爲主元(pivot),並將數組分爲兩部分,使得前半部的元素都小於或等於主元,後半部的元素都大於主元。對第一部分遞歸地應用快速排序算法,而後對第二部分遞歸地應用快速排序算法。
快速排序由C.A.R.Hoare於1962年開發。主元的選擇會影響算法的性能。性能
public class QuickSort { public static void quickSort(int[] list) { quickSort(list,0,list.length-1); } public static void quickSort(int[] list,int first,int last) { if(last>first) { int pivotIndex = partition(list, first, last); quickSort(list, 0, pivotIndex - 1); quickSort(list, pivotIndex + 1, last); } } public static int partition(int[] list,int first,int last) { int pivot=list[first]; int low=first+1; int high=last; while(high>low) { while(low<=high&&list[low]<=pivot) low++; while(high>=low&&list[high]>=pivot) high--; if(high>low) { int temp=list[high]; list[high]=list[low]; list[low]=temp; } } while(high>first&&list[high]>=pivot) high--; if(pivot>list[high]) { list[first]=list[high]; list[high]=pivot; return high; } else { return first; } } }
五. 堆排序
堆排序的時間複雜度爲O(nlogn),與歸併排序相同,但堆排序的空間效率高於歸併排序。
堆排序使用的是二叉堆。它首先將全部的元素添加到一個堆上,而後不斷移除最大的元素以得到一個排好序的線性表。學習
二叉堆(binary heap) 是一棵具備如下屬性的二叉樹:
1).它是一棵徹底二叉樹。
2).每一個結點大於或等於它的任意一個孩子。ui
上圖中,只有a是一個堆。b的根(39)小於它的右孩子(42),c和d的二叉樹都不是徹底的。spa
1.堆的存儲
若是堆的大小事先知道,那麼能夠將堆存儲到一個數組。數根在位置0處,它的兩個孩子在位置1和位置2處。
對於位置i的結點,它的左子結點在位置2i+1處,它的右子結點在位置2i+2處,而它父結點在位置(i-1)/2處。設計
2.添加一個新結點
爲了給堆添加一個新結點,首先將它添加到堆的末尾,而後按以下方式重建這棵樹:
將最後一個結點做爲當前結點;
while(當前結點大於它的父結點){
將當前結點與它的父結點交換;
如今當前結點往上面進了一個層次;
}
3.刪除根結點
常常須要刪除推中最大元素,也就是堆中的根結點。在刪除根結點後,必需要重建這棵樹以保持堆的屬性:
用最後一個結點替換根結點;
讓根結點成爲當前結點;
while(當前結點具備子結點而且當前結點小於它的子結點){
將當前結點與它的較大子結點交換;
如今當前結點往下面退了一個層次;
}
上圖將原根刪除後
public class Head<E extends Comparable<E>> { private java.util.ArrayList<E> list=new java.util.ArrayList<>(); public Heap() { } public Head(E[] objects){ for(int i=0;i<objects.length;i++) add(objects[i]); } public void add(E newObject){ list.add(newObject); int currentIndex=list.size()-1; while (currentIndex>0){ int parentIndex=(currentIndex-1)/2; if(list.get(currentIndex).compareTo(list.get(parentIndex))>0){ E temp=list.get(currentIndex); list.set(currentIndex,list.get(parentIndex)); list.set(parentIndex,temp); } else
break; currentIndex=parentIndex; } } public E remove(){ if(list.size()==0)return null; E removedObject=list.get(0); list.set(0,list.get(list.size()-1)); list.remove(list.size()-1); int currentIndex=0; while (currentIndex<list.size()){ int leftChildIndex=2*currentIndex+1; int rightChildIndex=2*currentIndex+2; if(leftChildIndex>=list.size()) break;//the tree is a heap
int maxIndex=leftChildIndex; if(rightChildIndex<list.size()){ if(list.get(maxIndex).compareTo(list.get(rightChildIndex))<0){ maxIndex=rightChildIndex; } } //swap if the current node is less than the maximum
if(list.get(currentIndex).compareTo(list.get(maxIndex))<0){ E temp=list.get(maxIndex); list.set(maxIndex,list.get(currentIndex)); list.set(currentIndex,temp); currentIndex=maxIndex; } else
break; } return removedObject; } public int getSize(){ return list.size(); } }
六. 桶排序和基數排序
一般基數排序須要耗費O(dn)的時間對帶整數鍵的n個元素排序,其中d是全部鍵值中基數位數目的最大值。
桶排序是穩定的(stable),這意味着原始線性表中的兩個元素有相同的鍵值,那麼它們在有序線性表中的順序是不變的。
import java.util.LinkedList; //注意這裏只實現了簡單的1位處理
public class BucketSort { public static void bucketSort(int[] list) { LinkedList[] bucket=new LinkedList[10]; for(int i=0;i<list.length;i++) { int key=calKey(list[i]); if(bucket[key]==null) bucket[key]=new LinkedList(); System.out.println(list[i]); bucket[key].add(list[i]); } int k=0; for(int i=0;i<bucket.length;i++) { if(bucket[i]!=null) { for(int j=0;j<bucket[i].size();j++) { list[k++]=(int)bucket[i].get(j); } } } } private static int calKey(int value) { return value%10; } }
七. 外部排序
外部排序的I/O複雜度爲O(n),合併步驟的總數爲log(n/c),總數爲O(n)*log(n/c),所以外部排序的複雜度是O(nlogn)。
外部排序一般用來對大容量數據進行排序,一種歸併排序的變體分兩步:
階段I:重複將數據從文件讀入數組,並使用內部排序算法對數組排序,而後將數據從數組輸出到一個臨時文件,臨時文件中保存的是S1,S2,…Sk的有序分段,分段S的大小依賴於分配的內存大小,最後一個分段Sk的數值可能會較少(不滿)。階段II:將每對有序分段(好比S1和S2,S3和S4,…)歸併到一個大一些的有序分段中,並將新分段存儲到新的臨時文件中。繼續一樣的過程直到獲得僅僅一個有序分段。(代碼略)