本文出自 “在雲端的追夢” 博客,請務必保留此出處http://computerdragon.blog.51cto.com/6235984/1161859
一 簡要介紹
一般排序均值的是將一個已經無序的序列數據重新排列成有序的
常見的排序分爲:
1 插入類排序
主要就是對於一個已經有序的序列中,插入一個新的記錄。它包括:直接插入排序,折半插入排序和希爾排序
2 交換類排序
這類排序的核心就是每次比較都要“交換”,在每一趟排序都會兩兩發生一系列的“交換”排序,但是每一趟排序都會讓一個記錄排序到它的最終位置上。它包括:起泡排序,快速排序
3 選擇類排序
每一趟排序都從一系列數據中選擇一個最大或最小的記錄,將它放置到第一個或最後一個爲位置交換,只有在選擇後才交換,比起交換類排序,減少了交換記錄的時間。屬於它的排序:簡單選擇排序,堆排序
4 歸併類排序
將兩個或兩個以上的有序序列合併成一個新的序列
5 基數排序
主要基於多個關鍵字排序的。
下面針對上面所述的算法,講解一些常用的java代碼寫的算法
二 插入類排序之直接插入排序
直接插入排序,一般對於已經有序的隊列排序效果好。
基本思想:每趟將一個待排序的關鍵字按照大小插入到已經排序好的位置上。
算法思路,從後往前先找到要插入的位置,如果小於則就交換,將元素向後移動,將要插入數據插入該位置即可。時間複雜度爲O(n2),空間複雜度爲O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package sort.algorithm; public class DirectInsertSort { public static void main(String[] args) { // TODO Auto-generated method stub int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20 }; int temp, j; for (int i = 1; i < data.length; i++) { temp = data[i]; j = i - 1; // 每次比較都是對於已經有序的 while (j >= 0 && data[j] > temp) { data[j + 1] = data[j]; j--; } data[j + 1] = temp; } // 輸出排序好的數據 for (int k = 0; k < data.length; k++) { System.out.print(data[k] + " "); } } } |
三 插入類排序之折半插入排序(二分法排序)
條件:在一個已經有序的隊列中,插入一個新的元素
折半插入排序記錄的比較次數與初始序列無關
思想:折半插入就是首先將隊列中取最小位置low和最大位置high,然後算出中間位置mid
將中間位置mid與待插入的數據data進行比較,
如果mid大於data,則就表示插入的數據在mid的左邊,high=mid-1;
如果mid小於data,則就表示插入的數據在mid的右邊,low=mid+1
最後整體進行右移操作。
時間複雜度O(n2),空間複雜度O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package sort.algorithm; //折半插入排序 public class HalfInsertSort { public static void main(String[] args) { int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20 }; // 存放臨時要插入的元素數據 int temp; int low, mid, high; for (int i = 1; i < data.length; i++) { temp = data[i]; // 在待插入排序的序號之前進行折半插入 low = 0; high = i - 1; while (low <= high) { mid = (low + high) / 2; if (temp < data[mid]) high = mid - 1; else // low=high的時候也就是找到了要插入的位置, // 此時進入循環中,將low加1,則就是要插入的位置了 low = mid + 1; } // 找到了要插入的位置,從該位置一直到插入數據的位置之間數據向後移動 for (int j = i; j >= low + 1; j--) data[j] = data[j - 1]; // low已經代表了要插入的位置了 data[low] = temp; } for (int k = 0; k < data.length; k++) { System.out.print(data[k] + " "); } } } |
四 插入類排序之希爾排序
希爾排序,也叫縮小增量排序,目的就是儘可能的減少交換次數,每一個組內最後都是有序的。
將待續按照某一種規則分爲幾個子序列,不斷縮小規則,最後用一個直接插入排序合成
空間複雜度爲O(1),時間複雜度爲O(nlog2n)
算法先將要排序的一組數按某個增量d(n/2,n爲要排序數的個數)分成若干組,每組中記錄的下標相差d.對每組中全部元素進行直接插入排序,然後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。當增量減到1時,進行直接插入排序後,排序完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package sort.algorithm; public class ShellSort { public static void main(String[] args) { int a[] = { 1, 54, 6, 3, 78, 34, 12, 45, 56, 100 }; double d1 = a.length; int temp = 0; while (true) { //利用這個在將組內倍數減小 //這裏依次爲5,3,2,1 d1 = Math.ceil(d1 / 2); //d爲增量每個分組之間索引的增量 int d = (int) d1; //每個分組內部排序 for (int x = 0; x < d; x++) { //組內利用直接插入排序 for (int i = x + d; i < a.length; i += d) { int j = i - d; temp = a[i]; for (; j >= 0 && temp < a[j]; j -= d) { a[j + d] = a[j]; } a[j + d] = temp; } }
if (d == 1) break; } for (int i = 0; i < a.length; i++) System.out.print(a[i]+" "); } } |
五 交換類排序之冒泡排序
交換類排序核心就是每次比較都要進行交換
冒泡排序:是一種交換排序
每一趟比較相鄰的元素,較若大小不同則就會發生交換,每一趟排序都能將一個元素放到它最終的位置!每一趟就進行比較。
時間複雜度O(n2),空間複雜度O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package sort.algorithm; //冒泡排序:是一種交換排序 public class BubbleSort { // 按照遞增順序排序 public static void main(String[] args) { // TODO Auto-generated method stub int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20, 13, 100, 37, 16 }; int temp = 0; // 排序的比較趟數,每一趟都會將剩餘最大數放在最後面 for (int i = 0; i < data.length - 1; i++) { // 每一趟從開始進行比較,將該元素與其餘的元素進行比較 for (int j = 0; j < data.length - 1; j++) { if (data[j] > data[j + 1]) { temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } } for (int i = 0; i < data.length; i++) System.out.print(data[i] + " "); } } |
六 交換類排序之快速排序
快速排序算法,初始的時候選擇一個軸線,一般來說選擇第一個元素,每一趟排序交換後,最後出現的就是該軸左邊比它小,右邊比他大!交替掃描,先從右邊開始掃描,如果遇到比它小的就停止,將該元素與軸線交換,馬上換成從左開始掃描,如果遇到比它大的就停止,將該元素與軸線數據交換,重複這樣的!一般就是遞歸做的
時間複雜度O(nlog2n),平均時間是最好的
空間複雜度O(long2n),快速排序需要遞歸用到了棧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package sort.algorithm; //快速排序算法:是一種交換排序 public class QuikSort { public static void main(String[] args) { // TODO Auto-generated method stub int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20, 105, 34, 44, 19 }; QuikSort sort = new QuikSort(); sort.sortArray(data, 0, data.length - 1); for (int i = 0; i < data.length; i++) System.out.print(data[i] + " "); } // 這裏不用返回值,直接對傳入的數組進行操作 public void sortArray(int data[], int first, int end) { int temp; int i = first, j = end; if (first < end) { temp = data[i]; // 當i=j的時候,則說明掃描完成了 while (i < j) { // 從右邊向左邊掃描找到一個小於temp的元素 while (j > i && data[j] > temp) j--; if (i < j) { // 將該元素賦值給temp data[i] = data[j]; // 賦值後就應該將i+1指向下一個序號 i++; } // 然後從左邊向右邊開始掃描,找到一個大於temp的元素 while (i < j && temp > data[i]) i++; if (i < j) { // 將該元素賦值給temp data[j] = data[i]; // 賦值後就應該將j-1指向前一個序號 j--; } } // 將軸數據放在i位置中 data[i] = temp; sortArray(data, first, i - 1); sortArray(data, i + 1, end); } } } |
七 選擇類排序之簡單選擇排序
簡單選擇排序,每一趟從數據中選擇一個最小值放到最前面,但是不需要交換位置,只記錄該交換的位置,只有找到後才做一次交換!不同於冒泡之處在於,不進行頻繁的交換,快於冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package sort.algorithm; //簡單選擇排序:是一種選擇排序 public class SelectSort { public static void main(String[] args) { // TODO Auto-generated method stub int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20, 11, 3, 1, 100, 89 }; int temp, k; // 開始時間 long start = System.nanoTime(); // 選擇的每一趟數,每一趟都會將一個最小的放在最前面。 for (int i = 0; i < data.length - 1; i++) { // 使用k來記錄要交換的位置,且k在比較的過程不斷變化 k = i; // 由於每一趟都會將最小的放在最前面,所以索引+1 for (int j = i; j < data.length; j++) // 這裏始終要與k比較 if (data[j] < data[k]) k = j; // k已經存放了交換的位置了 temp = data[i]; data[i] = data[k]; data[k] = temp; } System.out.println(System.nanoTime() - start); // 輸出排序好的數據 for (int m = 0; m < data.length; m++) { System.out.print(data[m] + " "); } } } |
八 選擇類排序之堆排序
堆排序就是建立大頂堆或者小頂堆,若建立大頂堆,每次對於建好的大頂堆將根元素與最後一個元素交換,無序的數目減少,有序的數目增加。
對於求N個數據中的前n個最小的數據,首先就是建立一個n個的大頂堆,然後讓其餘的元素來進行與這堆頂元素比較,如果小於則與堆頂互換元素。
這裏採用數組存儲節點,並且下標統一從0,length-1,所以對於這樣處理的左孩子節點下標爲
2 * i+1,右孩子的節點下標爲2 * i+2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | package sort.algorithm; public class HeapSort { public static int heap_size; // 左孩子編號 public static int leftChild(int i) { return 2 * i+1; } // 右孩子編號 public static int rightChild(int i) { return 2 * i + 2; } /** * 保持最大堆的性質 * 堆中的數組元素 * 對以該元素爲根元素的堆進行調整,假設前提:左右子樹都是最大堆 * 由於左右孩子都是最大堆,首先比較根元素與左右孩子,找出最大值,假如大於根元素,則調整兩個元素的值; * 由於左孩子(右孩子)的值與根元素交換,有可能打破左子樹(右子樹)的最大堆性質,因此繼續調用,直至葉子元素。 */ public static void max_heapify(int[] a, int i) { int left = leftChild(i); int right = rightChild(i); int largest = 0;
if (left < heap_size && a[i] < a[left]) { largest = left; } else { largest = i; }
if (right < heap_size && a[right] > a[largest]) { largest = right; } if (largest == i) { return; } else { int temp = a[i]; a[i] = a[largest]; a[largest] = temp; max_heapify(a, largest); } } /** * 建立最大堆。在數據中,下標a.length/2+1一直到最後的元素a.length-1都是葉子元素 * 因此從其前一個元素開始,一直到 * 第一個元素,重複調用max_heapify函數,使其保持最大堆的性質 */ public static void build_max_heap(int[] a) { //從0~a.length/2中建立最大堆 for (int i = a.length / 2; i >= 0; i--) { max_heapify(a, i); } } /** * 堆排序:首先建立最大堆,然後將堆頂元素(最大值)與最後一個值交換,同時使得 堆的長度減小1 * 調用保持最大堆性質的算法調整,使得堆頂元素成爲最大值,此時最後一個元素已被排除在外、 */ public static void heapSort(int[] a) { //構建最大堆 build_max_heap(a); for (int i = a.length - 1; i >= 0; i--) { //將第一個元素和最後一個元素進行互換 int temp = a[0]; a[0] = a[i]; a[i] = temp;
heap_size--; //調整堆爲最大堆 max_heapify(a, 0); } } public static void main(String[] args) { int a[] = {5, 4, 1, 3, 2, 16, 9, 10, 14, 8, 7}; heap_size = a.length;//最大數 heapSort(a); //輸出結果 for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } } } |
九 二路歸併排序
歸併排序主要分爲分割和歸併,每次分割後,對於每一個部分進行排序,然後進行歸併,建立一個臨時表存儲歸併後的結果,在將兩路進行歸併的時候,每一路都已經是有序的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package sort.algorithm; import java.util.Arrays; //二路歸併排序主要分爲 //分割和合並 public class MergeSort { public static void main(String[] args) { int data[] = { 2, 6, 10, 3, 9, 80, 1, 16, 27, 20 }; mergeSort(data,0,data.length-1); //直接打印 System.out.println(Arrays.toString(data)); } //二路歸併的分割處理 public static void mergeSort(int[] array,int start,int end) { if(start<end) { //劃分爲兩部分,每次兩部分進行歸併 int mid=(start+end)/2; //兩路歸併 //先遞歸處理每一個部分 mergeSort(array,start,mid); mergeSort(array,mid+1,end); //然後將已經排序好的,兩兩歸併排序再進行合併處理 merge(array,start,mid,mid+1,end); } } //二路歸併兩個部分的時候進行排序 public static void merge(int[] array,int start1,int end1,int start2,int end2) { int i=start1;//左路起始索引 int j=start2;//右路起始索引 int k=0; //歸併的時候,會將兩個數組數據按照大小輸入到一個臨時數組中 //建立臨時長度爲兩個子列表長度的數組 int[] temp=new int[end2-start1+1]; //循環遍歷,按順序找出兩個表中的最小數據依次放入臨時表中 //注意此時左路和右路已經是有序的了。 //當一路有一個小的,則會索引加1,繼續喝另外一路的上次索引進行比較 while(i<=end1&&j<=end2) { //這裏確定歸併的次序大小 if(array[i]>array[j]) temp[k++]=array[j++]; else temp[k++]=array[i++]; } //把剩下的元素放入臨時數組中,只有一路的 while(i<=end1) temp[k++]=array[i++]; while(j<=end2) temp[k++]=array[j++]; k=start1; for(int item:temp) array[k++]=item; } } |
十 各種排序總結:
時間複雜度:巧記“快些以nlog2n歸隊”,快代表快速排序,些代表希爾排序,歸代表歸併排序,隊代表堆排序
算法穩定性:巧記“心情不穩定,快些選一堆人吧”,快代表快速排序,些代表希爾排序,選代表選擇排序,隊代表堆排序
從一大堆中選擇最大的幾個或者最小的幾個數,直接用堆排序
原始序列有序號,直接用插入排序
經過一趟排序能使一個元素達到它最終位置的是交換類排序(冒泡,快速)和選擇類排序(簡單選擇,堆)。
排序方法元素比較次數與原始序列無關---簡單選擇排序,折半插入排序
排序方法的排序趟數和原始隊列無有關--交換類排序
本文出自 “在雲端的追夢” 博客,請務必保留此出處http://computerdragon.blog.51cto.com/6235984/1161859