排序演算法__java
最後更新:2018-07-27
來源:互聯網
上載者:User
排序------------------參考至程傑著:《大話資料結構》第九章
1.排序的基本概念
假設含有n個記錄的序列為{r1,r2,r3,r4,...,rn},其相應的關鍵字為{k1,k2,k3,k4,...,kn},需要確定1,2,3,4,...,n的一種排列p1,p2,p3,p4,...,pn,使其相應的關鍵字滿足
kp1<=kp2<=kp3<=kp4<=....<=kpn(非遞減或非遞增)關係,即使得序列成為一個按關鍵字有序的序列{rp1,rp2,rp3,rp4,...,rpn},這樣的操作就稱作排序。
2.內排序和外排序
根據在排序中待排序的記錄是否全部被放置在記憶體中,排序分為內排序和外排序。
內排序是在排序的整個過程中,待排序的所有記錄全部被放置在記憶體中。外排序是由於排序的記錄個數太多,不能同時放置在記憶體中,整個排序過程需要在內外存之間多次交換資料才能進行
。下面主要是介紹的內排序的演算法。
內排序的效能:時間效能、輔助空間、演算法的複雜性
內排序分為插入排序、交換排序、選擇排序、歸併排序。
注意:
這裡的排序的數組均是從下表1開始,數組的長度均是從1開始計算的數值大小
如a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],...,a[len-1],a[len]
這裡的排序均為升序排列
3.冒泡排序
冒泡排序是一種交換排序,兩兩比較,如果反序則交換,直到沒有反序為止。(這麼說有點遞迴的味道,實際上可能的是for等迴圈的範例)
//a[1],a[2],...,a[len]
public int[] MPSort(int a[],int len){
for(int i=1;i<len;i++)
{
for(int j=i+1;j<=len;j++){ if(a[i]>a[j]){ int temp = a[i]; a[i] = a[j]; a[j] = temp; } } }
return a; }
其時間複雜度為n*(n-1)/2,最壞情況為o(n^2),最好情況只需要比較n-1次,則是o(n-1)
4.簡單選擇排序
思想:通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i(1<=i<=n)個記錄交換之。
如i=1 , n-1次關鍵詞比較,從n-1+1=n個記錄中選出關鍵字最小的記錄,並和第i=1個記錄交換
i=2 , n-2次關鍵詞比較,從 n-2+1= n-1個記錄中選出關鍵字最小的記錄,並和第i=2個記錄交換
如此繼續下去,直到i=n-1 ,1次關鍵詞比較,從n-(n-1)+1=2個記錄中選擇出關鍵字最小的記錄,並和i=n-1交換,從而完成排序。
其時間複雜度為o(n) 選擇上 (1+2+3+...+(n-1)) = o(n(n-1)/2) ,交換上最壞情況是n-1次,最好情況是0次。
void simpleSelect(int []a,int len){
int i,j,min;
for(i =1;i<len;i++)
{
min = i; for(j=len-i+1;j>=2;j--){ if(a[min]>a[j]){ min = j; }
} //交換i和min的下標
if(i!=min)
swap(i,min,a);
} }
或者為:
public static void simpleSelect(int []a ,int len){
int i,j,min;
for(i=1;i<len;i++)
{
min = i; for(j=i+1;j<=len;j++){ if(a[min]>a[j]) min =j; } //swap(a,i,min) if(min!=i){ int temp = a[i]; a[i] = a[min]; a[min] = temp; } } }
簡單排序和冒泡排序的區別(按照遞增):冒泡每一次的迴圈只要前面比後面的大,就要交換;而簡單排序則不是,它是本i次迴圈比較完後,將最小的值才和第i個位置進行交換,因此簡單
排序要比冒泡排序要好一些。(時間和空間交換)
其時間複雜度為o(n^2)
5.直接插入排序
思想:將一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄數增加1的有序表。
void insertSort(int []a ,int len){
int i=1,j=1;
for(i=2;i<=len;i++)
{
for(j=1;j<i;j++){ if(a[i]<a[j]) { //i插入到j之前,同時還有j及其j之後的元素後移 insert(i,j,a); } }
} }
雖然這樣也可以實現其排序(對於排好序列是從前面開始判斷),但是對於直接插入排序的優勢好像還未完全利用,最佳情況如果採用上面情形則效果達不到預期;下面是排好序的從後面開始
判斷
void insertSort(int []a ,int len){
int i,j,temp;
for(i=2;i<=len;i++)
{
if(a[i]<a[i-1]) { temp = a[i]; for(j=i-1;temp<a[j];j--) { a[j+1] = a[j]; }
a[j+1] =temp;
} } }
此外從排好序的有序表從後往前進行比較。最好情況,拿到的資料本身就是排好序的,因此只是比較則時間複雜度為:n-1 = o(n)
最壞情況則是逆序,比較次數1+2+...+n-1=n(n-1)/2,移動的次數2+3+...+(n-1)+n = (n-1)(n+2)/2
一般的其時間複雜度為o(n^2).
直接插入排序要比冒泡排序和簡單選擇排序的效能要好些。因為是對排好序的有序列再進行操作,而且冒泡和簡單選擇排序的最好效能的時間複雜度為o(n^2) .
也就是說問題從雜亂無章,開始產生了一定的約束,或者說是輸入的資料有了一定的特點。
6.希爾排序
達到思想:將相據某個增量的記錄組成一個子序列,然後採取在每個子序列內進行分別的直接插入排序得到的結果是基本有序而不是局部有序。
基本有序:小的關鍵字基本在前面,大的基本在後面,不大不小的基本在中間
基本思想是:分區間(類似於劃窗的移動)段逐漸靠近的過程,且區間短點相互移動,隨著區間的逐漸縮小,相似資料逐漸相互靠近的過程。
void shellSort(int []a ,int len)
{
int i,j,temp;
int increment ;
increment = len;
do{
increment = increment/3+1;//增量序列 for(i=increment+1;i<=len;i++)
{
if(a[i]<a[i-increment]){
temp = a[i];
for(j=i-increment;j>0&&temp<a[j];j-=increment){
a[j+increment] = a[j];
}
a[j+increment] = temp;
}
} }while(increment>1);
}
這裡的增量的選取也是有技巧的。需要細細品味。
o(n^1.5),由於是跳躍式的移動,希爾排序並不是穩定的排序演算法。
7.堆排序
堆排序,這裡就是利用大頂堆的概念進行排序。
基本思想是:構造大頂堆;將大頂堆的根節點(最大值)和未排序的最後一位進行交換,未排序的資料個數-1,然後再將未排序的資料構造大頂堆,如此直到完全排好序列。
這裡利用了完全二叉樹的優點,雖然資料存放於數組中(或鏈表中)其實際上仍然是利用完全二叉樹的思想,比較和排序的
//堆排序
public static void HeapSort(int []a, int len)
{
int i; //先構造一個大頂堆 for(i=(int)(len/2);i>=1;i--){ HeapAdjust(a,i,len);//HeapAdjust(int[]a,int s,int n) ,a為數組,s為開始比較位置,n為數組的大小 } //交換最大值,並重新構造大頂堆 for(i=len;i>1;i--){ swap(a,1,i); HeapAdjust(a,1,i-1); } }
public static void swap(int a[],int n,int m){
int temp = a[n]; a[n] = a[m]; a[m] = temp; }
//#一種是從最頂層的根節點逐層往下迴圈
public static void HeapAdjust(int []a ,int s,int n){
int i,temp; temp = a[s]; for(i=2*s;i<=n;i *= 2){ if (i<n && a[i]<a[i+1])i++; if (temp>a[i]) break; a[s] = a[i]; s=i;
} a[s] = temp; }
這裡開始構造的大頂堆,每個子節點的根節點的資料都要比左右子節點要大。大頂堆構造好之後,交換之後,只需要最頂層根節點,然後順著其中一個支路調整,往下走,直到又構造出一個
大頂堆。
//交換最大值,並重新構造大頂堆,的這部分也可以改為
for(i=len;i>1;i--){
swap(a,1,i);
HeapAdjust(a,1,i-1);
}
#HeapAdjust這裡的思想是從完全二叉樹的最後一個葉子節點的根節點開始向最頂層的根節點迴圈,雖然這樣是調整了所有的資料,相當於每次都把最開始的構造,排除掉已經排好位置之外的
其他資料又重新進行構造了一遍。效率降低,使用先前的那種則就以滿足需要了。
void HeapAdjust(int []a,int s,int n){
int i,j,temp;
for(i=(int)((n-1)/2);i>=1&&j+1<=n;i--){
j = i*2;
temp = a[j];
if(a[j]<a[j+1])j++;
if(temp<a[j])a[i] =a[j];
a[j] = temp;
}
}
堆排序的演算法複雜的是:o(n *log n)
8.歸併排序
思想:假設初始序列含有N個記錄,可以看成是N個有序的子序列,每個子序列的長度是1,然後兩兩歸併,得到N/2向下取整個長度為2或1的有序子序列;再涼涼歸併,如此,重複
,直到長度為N的有序序列為止,這種排序方法稱為2路歸併排序。
注意歸併的允出準則是:N==N
sr 歸併排序放到 tr
/////////////////////////////////////////////歸併排序1///////////////////////////////////////////////////////
final static int MAXSIZE = 20;
static int TR2 [] = new int[MAXSIZE+1];
public static int[] MSort(int sr[],int s,int t){
int m;
if(s==t) TR2[s] = sr[s]; else{ m = (s+t)/2; //取整 MSort(sr,s,m);//前半部分 MSort(sr,m+1,t);//後半部分 Merge(TR2,s,m,t);//合并 } return TR2; }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//先將原數組中資料複製到新數組
static int temp_array[] = new int[MAXSIZE+1];
//移動s,t,兩端資料分別向後移動,因為m,m+1兩端分別都是已經排到順序的資料,這樣移動資料更快捷,[s,m][m+1,t]
public static void Merge(int tr[], int s,int m ,int t){
int j,k,i; for(j=m+1,k=s;s<=m&& j<=t;k++){
if(tr[s]>tr[j]){ temp_array[k] = tr[j++]; } else{ temp_array[k] = tr[s++]; }
} //前部分如果還有剩餘資料 if(s<=m) for(i=0;i<=m-s;i++){ temp_array[k+i] = tr[s+i];//??? } if(j<=t) for(i=0;i<=t-j;i++){ temp_array[k+i] = tr[j+i];// } //將臨時數組重新付給tr for(i=0;i<tr.length;i++){ tr[i] = temp_array[i]; } }
歸併排序這裡有些複雜,需要在棧中多次多留歸併留下的程式斷點(歸併連接點),,在java中如果涉及到歸併,同時使用同一個對象參數,需要注意其他參數和該對象參數的關係。
歸併排序的話時間複雜度為o(nlogn).
同時對於遞迴,要求注意(1)遞迴的允出準則;(2)遞迴如果資料量很大的話,容易造成堆疊溢位。一般的歸併,最好用非遞迴實現,因為非遞迴不需要更多的在堆棧中保留相應的程式入
口。
9快速排序
基本思想是:通過一趟排序將記錄分割成獨立的兩部分,其中一部分的所有的數值都要比另一部分的數值要小,然後再對於這兩部分繼續進行排序,直到所有資料的排序都達到目的。
public static void QuickSort(int a[],int s,int n){
if (s>=n) return; //選擇中間值,然後兩邊比較,前半部分要小,後半部分要大 int mid = Partition(a,s,n); //前半部分和後半部分再分別進行該種排序 QuickSort(a,s,mid-1); QuickSort(a,mid+1,n); }
public static int Partition(int a[],int s,int n){
int temp = a[s]; while(s<n){ while(s<n && a[n]>temp)n--; swap(a,s,n); while(s<n&& a[s]<temp)s++; swap(a,s,n);