五種排序演算法的JAVA 實現
1.插入排序
- 函式程式碼如下:
public void insertSort(int length){
int k = 0;
for(int present = 2; present <= length; present++){
this.array[0] = this.array[present];
for(k = present-1; array[k] > array[0]; k--){
array[k+1] = array[k];
}
if (k < present - 1){
array[k+1] = array[0];
}
}
演算法分析
待排序陣列將從陣列的下標為1處開始儲存,array[0]將在每次迴圈前,儲存待出入元素。這樣做相較於普通插入排序有兩個優點:
1.不必在每次比較後交換之前元素與待比較元素,只需將之前比待比較元素移動到之後空出的位置上,待迴圈結束後再將待比較元素放在迴圈截至處便可。
2.待比較元素放在陣列第一位可做哨兵只用,而後的迴圈中無需再做陣列是否越界的檢測。由於插入排序每次比較物件均為相鄰元素,故插入排序是一種穩定的排序演算法。
時間複雜度分析
演算法包含兩重迴圈,步長均為n,故演算法的時間複雜度為O(n^2)。
2.選擇排序
- 函式程式碼如下:
public void sectionSort(int length){
int k = 0;
int max = 0;
for(int i = 0;i <= length; i++){
max = array[0];
int maxnum = 0;
for(k = 0;k <= length - i; k++){
if (array[k] > max){
max = array[k];
maxnum = k;
}
}
swap(maxnum, length-i);
}
}
演算法分析
選擇排序的主要思想是在每一趟迴圈中,找出未排序元素中的最大值或最小值,而後將其交換到已排好序的序列的末尾。第二層迴圈 K < length - i的終止條件可以減少不必要的掃描。
選擇排序的主要優點是交換次數少,因而適用於關鍵字資訊量較大、佔用儲存空間很大的記錄。時間複雜度分析
演算法包含兩重迴圈,步長均為n,故演算法的時間複雜度為O(n^2)。
3.希爾排序
- 函式程式碼如下:
public void shellSort(int length){
int k;
for(int increment = length/3 + 1; increment > 0; increment = increment/3 + 1){
for(int i = increment + 1; i <= length; i++){
array[0] = array[i];
for(k = i-increment; k > 0&&array[k] > array[0]; k = k-increment){
array[k+increment] = array[k];
}
if(k < i-increment){
swap(k+increment,0);
}
}
if(increment == 1){
break;
}
}
}
public void swap(int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
演算法分析
希爾排序又名增量遞減演算法,利用了插入排序對於最好情況的極佳處理。希爾排序增量不斷遞減,使待排序序列越來越接近有序的情況;同時直接比較交換下標相距一個增量的兩個元素可以使元素更快地接近最終位置。應當注意最後一次排序增量務必為1.希爾排序的效能主要與增量的大小選取有關,最好能在每一次迴圈中,使得總的序列更加接近有序。大量研究表明,當增量序列為increment[k] = 2^(t-k+1) -1時,可以獲得不錯的效果。一般情況下使用increment = increment/3+1的增量序列即可。
由於希爾排序比較的物件是下標相隔一個增量的元素,故而希爾排序是一種不穩定的排序演算法。
時間複雜度分析
在本段程式碼實現中,增量序列的選擇為increment = increment/3+1,顯然時間複雜度為O(n^3/2)。希爾排序的時間複雜度介於O(nlogn)~O(n^2)之間,取決於增量序列的選擇。最壞情況為插入排序的複雜度。
4.歸併排序
- 函式程式碼如下:
public void merge(int begin, int end){
int mid = (begin + end)/2;
int i = begin;
int j = end;
int k;
if(begin == end) return;
else{
merge(begin,mid);
merge(mid+1,end);
}
for(i = begin;i < mid+1;i++){
temp[i] = array [i];
}
//將第二個子序列逆置存放
for(j = 1;j <= end-mid; j++){
temp[end-j+1] = array[j+mid];
}
int a = array[begin];
int b = array[end];
for( k = begin,i = begin,j = end;k <= end;k++ ){
if(a > b){
array[k] = temp[j--];
b = temp[j];
}else{
array[k] = temp[i++];
a = temp[i];
}
}
}
演算法分析
歸併演算法通過遞迴的思想,先將排序序列拆分為不可拆分的子序列,而後兩兩合併,合併為新的有序的子序列。遞迴的基礎情形為begin == end。
程式碼為優化後的歸併排序,在將待排序序列拷貝到開闢的臨時陣列空間
時,將第二個子序列逆置存放,而後合併掃描則從陣列一頭一尾交替進行。較普通的歸併排序有幾點優點:
1.在迴圈合併中,兩個子陣列相互為監視哨,避免了在迴圈中對陣列是否越界的檢查。
2.避免了在一個子序列處理完後邊跳出迴圈進行另一子序列拷貝的情況。在其中某一個子序列處理結束後,工作指標將前移或後移指向另一個子序列的末尾。顯然將繼續另一未處理完的序列的拷貝而無需跳出迴圈。小於O(n^2)的排序中唯一的穩定排序。因此在看重穩定性且對空間要求不高的情況下,當使用歸併排序。
可通過改遞迴為迭代的方法減少歸併排序的棧開銷。時間複雜度分析
歸併排序需要將1~n中所有的記錄掃描一遍,因此耗費O(n)的時間,而由二叉樹的深度可知,整個歸併排序需要進行O(logn)次。故最好、最壞及平均時間複雜度均為O(nlogn)。
但歸併排序具有無法避免的空間複雜度的開銷,這是歸併排序無法迴避的缺點。
5.快速排序
- 函式程式碼如下:
public void quickSort(int begin,int end,int length){
int axis = 0;
setAxis(begin,end);
axis = disposal(begin, end, length);
if(axis-1 > begin)
quickSort(begin,axis-1,axis-begin);
if(end > axis+1)
quickSort(axis+1,end,end-axis);
}
//選擇軸心
//軸心將位於每組資料的第一位
public void setAxis(int begin,int end){
if(end - begin > 3){
int center = (begin + end)/2;
if(array[begin] > array[end])
swap(begin, end);
if(array[end] > array[center])
swap(end, center);
if(array[center] > array[begin]){
swap(begin,center);
}
}
}
//整理順序
//並將軸值歸位,返回值為軸值
public int disposal(int begin,int end,int length){
int i = begin + 1;
int j = end;
if(length == 2&&array[begin]<array[end]){
swap(begin,end);
}else{
while(i < j){
while(array[j] > array[begin])
j--;
while(array[i] < array[begin]&&i < end)
i++;
swap(i,j);
}
}
swap(i,j);
swap(begin,j);
return j;
}
演算法分析
快速排序演算法主要利用
了分而治之的思想,通過每次選擇的樞軸將序列一分為二,通過遞迴再分別進行快速排序。演算法中遞迴的基礎情形為begin == end.
為保證軸值能準確的將陣列分做大於樞軸的元素序列和小於樞軸的元素序列,使用兩指標從一頭一尾進行掃描,將序列左側大於樞軸的元素和序列右側小於樞軸的元素交換直至兩指標相遇,此時交換指標相遇位置的元素和樞軸。利用快速排序中樞軸可將序列分為大於樞軸元素的序列和小於樞軸元素的序列,將演算法稍加改進後可用於尋找第K大元素的問題。
時間複雜度分析
快速排序的最好時間複雜度與平均時間複雜度均為O(nlogn),最壞時間複雜度為O(n^2)。可以通過改進樞軸的選取方式來儘量避免最壞情況的出現。