1 簡單排序算法
2 中級的排序算法:歸併排序
package intdata.InsertionSort;
public class Meger {
static int[] a = {2,1,5,34,12,9,64,123,32,432,13};
static int[] workSpace = new int[11];
public static void main(String[] args){
regMegerSort(a,0,10);
for(int i = 0;i< a.length; i++){
System.out.println(a[i]);
}
}
private static void meger(int[] workSpace,int lower,int mid,int upper) {
int j=0;
int lowerBound = lower;
int mider = mid -1;
int n = upper-lower+1;
while(lower <= mider && mid <= upper){
if(a[lower]<a[mid]) {
workSpace[j++]=a[lower++];
}
else {
workSpace[j++]=a[mid++];
}
while(lower <= mid){
workSpace[j++] = a[lower++];
}
while(mid <= upper){
workSpace[j++] = a[mid++];
}
for(j=0;j<n;j++){
a[lowerBound+j] = workSpace[j];
}
}
}
private static void regMegerSort(int[] workSpace, int lowerBound,int upperBound){
if(lowerBound == upperBound)
return;
else {
int mid = (lowerBound + upperBound)/2;
regMegerSort(workSpace,lowerBound,mid);
regMegerSort(workSpace,mid+1,upperBound);
meger(workSpace, lowerBound,mid+1, upperBound);
}
}
}
程序還是比較簡單,易於實現的。2 在歸併排序中,一個大數組的單個數據項的子數組歸井爲兩個數據項的子數組,然後兩個數據項的子數組歸併爲4 個數據項的子數組,如此下去直到所有的數組數據項有序。
4 歸併排序需要一個大小等於原來數組的工作空間。
3 高級排序
這裏主要的兩個高級的排序算法:希爾排序和快速排序。最後還有基數排序,雖然不常用但很有趣的排序算法。
- 希爾排序是基於插入排序,但是增加了一個新的特性,大大地提高了插入排序的執行效率。
- 希爾排序對於多達幾千個數據項的,中等大小規模的數組排序表現良好。
- 希爾排序不像快速排序和其他時間複雜度爲O(N*logN)的排序算法那麼快,因此對非常大的文件排序,它不是最優選擇。
- 但是,希爾排序比選擇排序和插入排序這種時間複雜度爲O(N2 ) 的排序算法還是要快得多,並且它非常容易實現。
- 希爾排序算法的代碼既短又簡單。
<span style="font-size:18px;">public static void insertionSort(int[] a){ //a 爲數字對象的一個引用。引用=地址
int i,j,key,n=a.length; //i爲數組下標;j爲循環次數,與i有聯繫;key爲容器。
for(j=1;j<n;j++){
key=a[j];//將新的數據(牌)給容器
i=j-1;//i爲手中原有的牌數,a[i]爲手中原有的(牌)
while(i>=0 && a[i] > key ){//從小到大
a[i+1]=a[i]; //將原數往後放一位
i--; //循環,直到減爲0。
}
a[i+1]=key;//把新的數據插入正確的標號。
}
}</span>
插入排序的思想是將新的要插入的值和原來數組中的值一個一個做比較,然後在一個一個複製移動,直到插入合適的位置。最壞的情況下,要從頭到尾都複製移動一次。時間複製度要O(N*N),最好的情況下,就是數組基本有序的情況下,只需要移動2個左右的數就可以排行序,這時的時間複雜度爲O(N)。這就是爲什麼說,插入排序對基本有序的數組排序,效率很高的原因。<span style="font-size:18px;">package intdata.InsertionSort;
public class SheelSortApp {
public static void main(String[] args){
long[] a = {5,1,9,3,46,90,19,100,11,65};
shellSort(a);
for(int i = 0; i<a.length; i++){
System.out.print(a[i]+" ");
}
}
private static void shellSort(long[] a) {
int inner,outer;
long temp;
int h=1;
while(h<a.length /3)
h = 3*h+1;
while(h>0){
for(outer = h;outer < a.length; outer++){
temp = a[outer];
inner = outer;
while(inner > h-1 && a[inner -h] > temp){
a[inner] = a[inner-h];
inner -= h;
}
a[inner] = temp;
}//end for
h = (h-1)/3;
}//end while
}//end shellSort
} //end class
</span>
這就是傳說中的希爾排序算法,是不是也很簡單。注意看程序中的變量h,他就是增量排序中的增量,它的取值不唯一。這裏的取值是算法書中推薦的值的取法。
- 1. 快速排序是基於劃分思想的。
- 2.快速排序算法是對自身的遞歸調用。
- 3.要理解快速排序算法必須先理解劃分算法。
package intdata.InsertionSort;
public class ArrayPartitiom {
private long[] theArray;
private int nElems;
public ArrayPartitiom(int max){
theArray = new long[max];
nElems = 0;
}
public void insert(long value){
theArray[nElems++] = value;
}
public int patitionIt(int left, int right,long pivot){
int leftPtr = left - 1; //左指針,在左邊數據的的前一位
int rightPtr = right + 1; //右指針,在右邊數據的的後一位
while(true){
while(leftPtr < right && theArray[++leftPtr] < pivot); //find bigger item
while(rightPtr > left && theArray[--rightPtr] > pivot); //find smaller item
if(leftPtr >= rightPtr)
break;
else
swap(leftPtr,rightPtr);
} //end while
return leftPtr;
} //end patitionIt method
private void swap(int leftPtr, int rightPtr) {
long temp;
temp = theArray[leftPtr];
theArray[leftPtr] = theArray[rightPtr];
theArray[rightPtr] = temp;
} //end swap method
public void display(){
for(int i =0;i< nElems; i++){
System.out.print(theArray[i]+" ");
}
System.out.println();
}// end display method
public int size(){
return nElems;
}//end size method
}
下面是劃分算法的測試程序:package intdata.InsertionSort;
public class PartitionApp {
public static void main(String[] args) {
int maxSize = 16;
ArrayPartitiom arr = new ArrayPartitiom(maxSize);
for(int i = 0;i < maxSize; i++){
long n = (int) (java.lang.Math.random()*199);
arr.insert(n);
}
arr.display();
long pivot = 99;
int size = arr.size();
System.out.println("pivot = "+pivot);
int partDex = arr.patitionIt(0, size-1, pivot);
System.out.println("patitoin is at index "+partDex);
arr.display();
} //end main method
}
程序的運行結果如下:pivot = 99 (這個是中樞)
patitoin is at index 9
48 60 59 16 8 49 97 39 1 119 179 150 142 185 102 151
快速排序算法本質上通過把一個數組劃分爲兩個子數組,然後遞歸地調用自身爲每一個子數組進行快速排序來實現的。但是,對這個基本的設計還需要進行一些加工。算法還必須要選擇樞紐以及對小的劃分區域進行排序。
- 1.把數組或者子數組劃分成左邊(較小的關鍵字)的一組和右邊(較大的關鍵字)的一組。
- 2. 調用自身對左邊的一組進行排序。
- 3. 再次調用自身對右邊的一組進行排序。
package intdata.InsertionSort;
public class QuickSort1 {
private static long[] theArray = new long[16];
public static void main(String[] args) {
for(int i = 0;i < theArray.length; i++){
theArray[i] = (int) (java.lang.Math.random()*99);
}
display();
recQuickSort(0,theArray.length-1);//theArray.length-1 是最後一個數
display();
}
private static void display() {
for(int i =0;i< theArray.length; i++){
System.out.print(theArray[i]+" ");
}
System.out.println();
}//end display method
private static void recQuickSort(int left,int right){
if(right-left <= 0)
return;
else{
long pivot = theArray[right];
int partition = partition(left,right,pivot);
recQuickSort(left, partition-1);
recQuickSort(partition+1, right);
} //end else
}// end recQuickSort
private static int partition(int left, int right,long pivot){
int leftPtr = left - 1; //左指針,在左邊數據的的前一位
int rightPtr = right; //右指針,右邊數據
while(true){
while(theArray[++leftPtr] < pivot); //find bigger item
while(rightPtr > 0 && theArray[--rightPtr] > pivot); //find smaller item
if(leftPtr >= rightPtr)
break;
else
swap(leftPtr,rightPtr);
} //end while
swap(leftPtr,right);
return leftPtr;
} //end patitionIt method
private static void swap(int leftPtr, int rightPtr) {
long temp;
temp = theArray[leftPtr];
theArray[leftPtr] = theArray[rightPtr];
theArray[rightPtr] = temp;
} //end swap method
}
程序結果:
16 23 26 36 37 38 48 55 61 62 69 76 82 92 94 97
劃分方法應該使用什麼樣的樞紐呢?以下是一些相關的思想:
- • 應該選擇具體的一個數據項的關鍵字的值作爲樞紐:稱這個數據項爲pivot ( 樞紐) 。
- • 可以選擇任意一個數據項作爲樞紐。爲了簡便,我們假設總是選擇待劃分的子數組最右端的數據項作爲樞紐。
- • 劃分完成之後,如果樞紐被插入到左右子數組之間的分界處,那麼樞紐就落在排序之後的最終位置上了。
但是上面的樞紐選擇方案,在已經有序的數據進行排序時,時間複雜多會變成O(N*N),爲了解決這個問題,提出了另一種樞紐選擇方案:三項數據取中
package intdata.InsertionSort;
public class QuickSort2 {
/**
* 三項數據取中
* 解決了對已經有序的數組快速排序需要O(N*N)的問題
*/
private static long[] theArray = new long[16];
public static void main(String[] args) {
for(int i = 0;i < theArray.length; i++){
theArray[i] = (int) (java.lang.Math.random()*99);
}
display();
recQuickSort(0,theArray.length-1);//theArray.length-1 是最後一個數
display();
}
private static void display() {
for(int i =0;i< theArray.length; i++){
System.out.print(theArray[i]+" ");
}
System.out.println();
}//end display method
private static void recQuickSort(int left,int right){
int size = right-left+1;
if(size <= 3){ //處理小劃問題,cutoff = 3
manualSort(left,right); //簡單的人工排序
}
else{
long median = medianOf3(left,right);//取三項數據的中間值爲劃分樞紐
int partition = partition(left,right,median);
recQuickSort(left, partition-1);
recQuickSort(partition+1, right);
} //end else
}// end recQuickSort
private static long medianOf3(int left, int right) {
int center = (left+right)/2;
if(theArray[left] > theArray[center])
swap(left,center);
if(theArray[left] > theArray[right])
swap(left,right);
if(theArray[center] > theArray[right])
swap(center,right); //三項數據排序取中完成
swap(center,right-1); //put pivot on right;
return theArray[right-1]; //return median value
}//end medianOf3 method
private static void manualSort(int left, int right) {
int size = right-left+1;
if(size <= 1)
return;
if(size == 2){ //2 sort left and right
if(theArray[left] > theArray[right])
swap(left,right);
return;
}
else { //sort is 3 //right = right -1;
if(theArray[left] > theArray[right -1])
swap(left,right-1);
if(theArray[left] > theArray[right])
swap(left,right);
if(theArray[right -1] > theArray[right])
swap(right-1,right);
}
}//end manual method
private static int partition(int left, int right,long pivot){
int leftPtr = left; //左指針,左邊數據
int rightPtr = right -1; //右指針,右邊數的後一位
while(true){
while(theArray[++leftPtr] < pivot); //find bigger item
while(theArray[--rightPtr] > pivot); //find smaller item
if(leftPtr >= rightPtr)
break;
else
swap(leftPtr,rightPtr);
} //end while
swap(leftPtr,right-1); //restore pivot
return leftPtr; //return pivot location
} //end patitionIt method
private static void swap(int leftPtr, int rightPtr) {
long temp;
temp = theArray[leftPtr];
theArray[leftPtr] = theArray[rightPtr];
theArray[rightPtr] = temp;
} //end swap method
}
0 3 23 23 28 32 36 39 55 57 58 61 85 88 88 95
3 處理小劃分:
如果使用三數據項取中劃分方法,則必須要遵循快速排序算法不能執行三個或者少於三個數據
項的劃分的規則。在這種情況下,數字3 被稱爲切割點(cutoff)。
(cutoff) 。可以把界限定爲10 、20 或者其他任何數。試驗不同切割點的值以找到最好的執行效率。
package intdata.InsertionSort;
public class QuickSort3 {
/**
* 對於小劃分的小於10項的數據,用插入算法排序
*/
private static long[] theArray = new long[16];
public static void main(String[] args) {
for(int i = 0;i < theArray.length; i++){
theArray[i] = (int) (java.lang.Math.random()*99);
}
display();
recQuickSort(0,theArray.length-1);//theArray.length-1 是最後一個數
display();
}
private static void display() {
for(int i =0;i< theArray.length; i++){
System.out.print(theArray[i]+" ");
}
System.out.println();
}//end display method
private static void recQuickSort(int left,int right){
int size = right-left+1;
if(size < 10){ //處理小劃問題,cutoff = 10
insertionSort(left,right); //插入算法
}
else{
long median = medianOf3(left,right);//取三項數據的中間值爲劃分樞紐
int partition = partition(left,right,median);
recQuickSort(left, partition-1);
recQuickSort(partition+1, right);
} //end else
}// end recQuickSort
private static void insertionSort(int left, int right) {
int inner,outer;
long temp;
for(outer = left+1;outer <= right;outer++){
temp = theArray[outer];
inner = outer;
while(inner > left && theArray[inner-1] >= temp){
theArray[inner] = theArray[inner -1];
--inner; //inner--;
}
theArray[inner] = temp;
}
}
private static long medianOf3(int left, int right) {
int center = (left+right)/2;
if(theArray[left] > theArray[center])
swap(left,center);
if(theArray[left] > theArray[right])
swap(left,right);
if(theArray[center] > theArray[right])
swap(center,right); //三項數據排序取中完成
swap(center,right-1); //put pivot on right;
return theArray[right-1]; //return median value
}//end medianOf3 method
private static void manualSort(int left, int right) {
int size = right-left+1;
if(size <= 1)
return;
if(size == 2){ //2 sort left and right
if(theArray[left] > theArray[right])
swap(left,right);
return;
}
else { //sort is 3 //right = right -1;
if(theArray[left] > theArray[right -1])
swap(left,right-1);
if(theArray[left] > theArray[right])
swap(left,right);
if(theArray[right -1] > theArray[right])
swap(right-1,right);
}
}//end manual method
private static int partition(int left, int right,long pivot){
int leftPtr = left; //左指針,左邊數據
int rightPtr = right -1; //右指針,右邊數的後一位
while(true){
while(theArray[++leftPtr] < pivot); //find bigger item
while(theArray[--rightPtr] > pivot); //find smaller item
if(leftPtr >= rightPtr)
break;
else
swap(leftPtr,rightPtr);
} //end while
swap(leftPtr,right-1); //restore pivot
return leftPtr; //return pivot location
} //end patitionIt method
private static void swap(int leftPtr, int rightPtr) {
long temp;
temp = theArray[leftPtr];
theArray[leftPtr] = theArray[rightPtr];
theArray[rightPtr] = temp;
} //end swap method
}
程序結果:12 15 41 44 45 48 51 52 53 62 65 69 81 87 90 98
這裏的基數排序都是普通的以10 爲基數的運算,因爲這樣更易於講解。但是,以2 爲基數實現的基數排序也是非常高效的,這可以利用計算機高速的位運算。這裏只考察基數排序,而不考察與基數排序相似但更復雜一些的基數交換排序。基數這個詞的意思是一個數字系統的基。10 是十進制系統的基數, 2 是二進制系統的基數。
1.根據數據項個位上的值,把所有的數據項分爲10 組。
2. 然後對這10 組數據項重新排列:把所有關鍵字是以0 結尾的數據項排在最前面,然後是關鍵字結尾是1 的數據項,照此順序直到以9 結尾的數據。這個步驟被稱爲第一趟子排序。
3. 在第二趟子排序中,再次把所有的數據項分爲10 組,但是這一次是根據數據項十位上的值來分組的。這次分組不能改變先前的排序順序。也就是說,第二趟排序之後,從每一組數據項的內部來看,數據項的順序保持不變:這趟子排序必須是穩定的。
4. 然後再把10 組數據項重新合併,排在最前面的是十位上爲0 的數據項,然後是10 位爲1 的數據,如此排序直到十位上爲9 的數據。
5. 對剩餘位重複這個過程。
- 希爾排序將增量應用到插入排序,然後逐漸縮小增量。
- n-增量排序表示每隔n 個元素進行排序。
- 被稱爲間隔序列或者間距序列的數列決定了希爾排序的排序間隔。
- 常用的間隔序列是由遞歸表達式h=3*h+1 生成的, h 的初始值爲10
- 一個容納了1000 個數據的數組,對它進行希爾排序可以是間隔序列爲364 ,121 , 40,13 , 4 ,最後是1 的增量排序。
- 希爾排序很難分析,但是它運行的時間複雜度大約爲O(N* (logN內。這比時間複雜度爲O(N2 )的排序算法要快,例如 比插入排序快,但是比時間複雜度爲O(N*logN) 的算法慢,例如比快速排序慢。
- 劃分數組就是把數組分爲兩個子數組,在一組中所有的數據項關鍵字的值都小於指定的
值,而在另一組中所有數據項關鍵字的值則大於或等於給定值。 - 樞紐是在劃分的過程中確定數據項應該放在哪一組的值。小於樞紐的數據項都放在左邊一組:而大於樞紐的數據項都放在右邊一組。
- 快速排序劃分一個數組,然後遞歸調用自身,對劃分得到的兩個子數組進行快速排序。
- 快速排序算法劃分時的樞紐是一個特定數據項關鍵字的值,這個數據項稱爲pivot (樞紐)。
- 在快速排序的簡單版本中,總是由於數組的最右端的數據項作爲樞紐。
- 快速排序的簡單版本,對已經有序(或者逆序)的數據項排序的執行效率只有O(N*N)
- 更高級的快速排序版本中,樞紐是爲"三數據項取中" 劃分。
- 在快速排序已經對大於切割界限的子數組排完序之後,插入排序也可用於整個的數組。
- 基數排序的時間複雜度和快速排序相同,只是它需要兩倍的存儲空間。