給定一個規模爲n的數組,在n個數字中尋找最小的一個,而後與第0位置的數交換,而後在後n-1個數中找最小的,與第1個位置的數字交換。當交換了i次之後,數組左邊的i個數字是有序的,右邊n-i個數字無序。而後把在n-i個數字中尋找最小的,與i位置的數字交換位置。直到交換n次之後,整個數組有序。算法
時間複雜度:O(n²)數組
實現:markdown
public class Selection{ public static void sort(int[] a) { int t; for(int i=0;i<a.length;i++){ int index=i;//用來保存剩餘亂序數組中的最小值的位置 for(int j=i+1;j<a.length;j++){ if(a[index]>a[j]){//尋找最小值並保存位置 index=j; } } //找到最小值的位置後交換 t=a[i]; a[i]=a[index]; a[index]=t; } } }
給定一個規模爲n的數組,把第i號(iui
public class Insertion{ public static void sort(int[] a) { int t=0; for(int i=1;i<a.length;i++){ int v=a[i],k; for(k=i-1;k>=0&&v<a[k];k--){//查找左邊比a[i]的元素大的元素,右移。 a[k+1]=a[k]; } a[k+1]=v;//把a[i]放入比a[i]小的元素的後面 } } }
根據插入排序,若是n個元素中最小的數在最右端,那麼該數字須要比較n-1次才能夠到達目標位置,由於是逐個比較元素的大小的。若是把逐個比較改成相隔1個比較並移動、相隔1個比較並移動、相隔h個比較並移動,那麼最右端的元素到目標位置的比較移動次數將會大幅度減少。因此,在每隔h個元素都比較移動完之後,n個數中將會出現n/h個有序對,他們彼此相隔h,而後把h–,而後再比較移動,直到h==1且比較移動完後,排序結束。插入排序就是h=1的希爾排序。
時間複雜度:未知
適用範圍:數據規模較大,數據較離散。
實現:spa
public class Shell { public static void sort(int[] a){ int h=1,t=0; h=a.length/2; while(h>=1){//直到相隔距離爲1時,最後一次比較移動 for(int i=h-1,j;i<a.length;i++){//從i位置開始,保證以前每隔h位置的元素都是有序的,一開始從h-1開始,左邊只有h個元素,因此每一組都是有序的 int base=a[i];//要插入的元素 for(j=i-h;j>=0&&a[j]>base;j-=h){//把要插入的元素與該組的其餘元素比較大小,若是被比較的元素較大,則右移h個位置 a[j+h]=a[j]; } a[j+h]=base;//把要插入的元素插入到找到的位置 } h/=2;//相隔距離每次減半 } // display(a); } }
經過遞歸的方式,把n個元素等分紅兩部分,而後再將這兩部分等分紅四部分,以此類推,知道將n個元素分紅n個單獨的組,這n個組由於只有一個元素,因此是有序的。
而後把其中相鄰的兩個組合並,要求合併後的組也是有序的,因而造成了n/2個組。而後再相鄰的組合並,造成n/4個組。依此類推,知道合併成只有一個組,排序完畢。
合併兩個組時,能夠將兩個指針指向兩個數組的第一個位置,而後比較大小,把小的放入臨時數組,而後該指針後移。若是其中一個數組的指針到了最右,則把另外一個數組剩下的元素複製到臨時數組。
過程以下圖:
時間複雜度:O(nlgn)
缺點:須要開闢一個與原數組相同大小的輔助數組
實現:指針
public class Merge { static int[] temp=null; public static void sort(int[] a){ int start,end; temp=new int[a.length]; start=0; end=a.length-1; merge(a,start,end); // display(a); } public static void merge(int[] a,int start,int end){ if(start==end) return; int mid=(start+end)/2; merge(a,start,mid);//把數組分紅兩半,分別排序 merge(a,mid+1,end); if(a[mid]<a[mid+1])//前一個數組中最大的數比後一個數組中最小的數小,說明兩個數組合並後已經有序,全部不須要再排序 return; mergeSort(a,start,end); } public static void mergeSort(int[] a,int start,int end){ int mid=(start+end)/2; for(int i=start,j=mid+1,k=start;k<=end;k++){//i,j分別表示兩個數組的第一個元素(物理上只有一個數組,但邏輯上分爲兩個數組) if(i>mid) temp[k]=a[j++];//若是第一個數組比較結束,則以此把第二個數組全部元素複製到臨時數組 else if(j>end) temp[k]=a[i++];//若是第二個數組比較結束,則以此把第一個數組全部元素複製到臨時數組 else if(a[i]<a[j]) temp[k]=a[i++];//若是第一個數組i位置元素比較小,則把a[i]複製到臨時數組,而且i右移 else temp[k]=a[j++];//若是第二個數組j位置元素比較小,則把a[j]複製到臨時數組,而且j右移 } System.arraycopy(temp, start, a, start, end-start+1);//把臨時數組中的元素複製到原數組 } public static void display(int[] a){ for(int i=0;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); } }
一、 給定n個數,從start到end進行排序
二、 從左邊往右邊找到i知足a[i]>a[start]
三、從右邊往左邊找到j知足a[j]>a[start]
四、 交換a[i]和a[j]
五、 重複三、4,直到i>=j
六、 交換a[start]和a[j],此時知足a[0~j-1]都小於a[j], a[j+1,end]都大於於a[j]
七、 從1從新執行,此時start=start,end=j-1 和start=j+1,end=end,直到start>=end
過程圖:
時間複雜度(平均):O(nlgn)
適用範圍:重複數字少,有序數字少
實現:code
public class Quick { public static void sort(int[] a) { sort(a, 0, a.length - 1); } private static void sort(int[] a, int start, int end) { if (start >= end) return; int i = partition(a, start, end);// 返回切割位置,此時i位置的元素大於[start,i-1],下雨[i+1,end] sort(a, start, i - 1); sort(a, i + 1, end); } private static int partition(int[] a, int start, int end) {// 要求返回位置的元素大於等於左邊元素,返回位置的元素都小於等於右邊的元素 int i = start + 1, j = end; i = start; j = end + 1; int value=a[start]; while (true) { while (a[++i] < value) // 向右尋找比a[start]大的數 if (i == end) break; while (a[--j] > value);// 向左尋找比a[start]小的數,但由於最左邊是a[start],因此j>start恆成立 // if (j == start) // break; if (i >= j)// 若是i,j相遇或i>j,則結束尋找 break; swap(a, i, j); } // j就是知足a[j]大於等於左邊,小於等於右邊的位置 // 由於a[j]必定小於等於a[start]或者j等於start // a[i]必定大於等於a[start]或者i等於end // 根據前一種狀況,i跟j不在同一位置的話,a[j]就是小於a[start]最右邊的數,再往右的話就是a[i],而a[i]必定大於a[start] // 若是a[j]>a[start],就意味着j到了最左邊(由於j是從右往左尋找比a[start]小的數的,若是沒找到且停下來了,就意味着到頭了) // 可是所謂的最左邊就是a[start]所在位置,因此a[j]必定小於或等於a[start] swap(a, j, start); return j; } private static void swap(int[] a, int p, int q) { int t = a[p]; a[p] = a[q]; a[q] = t; } }
(二叉)堆有序定義:在一顆二叉樹中,若是全部父節點不小於子節點,則該二叉樹就是堆有序
下潛操做定義:若是一個父節點大於兩個子節點或者子節點中的一個,那麼把父節點與子節點中大的那個交換位置,而後把以前的父節點(如今已被交換到子節點的位置)與其新的子節點進行比較並交換,知道該節點大於其子節點,則結束操做。該操做稱爲下潛操做。排序
對於一個有序堆,序號爲0的節點必定是最大的元素,把這個元素去掉,並把最後一個元素放到0位置,該堆又變成了無序堆。而後對0節點進行下潛操做,該堆從新變成了有序堆。而後繼續移除、繼續排序,最終該堆大小爲0,原數組的位置變爲一個有序序列。
該圖爲把一個有序堆的最大元素移除、排序最終獲得有序數列的過程。
那麼接下來的問題就是如何把一個無序堆構建成有序堆。
只要把一個堆自下而上進行下潛操做,那麼最終就是一個有序堆。而最後一層節點沒有子節點,因此不須要進行下潛操做。從倒數第二層的最後一個節點開始進行下潛操做,知道第一個元素,那麼就能夠造成一個有序堆,過程以下圖:
遞歸
算法複雜度:O(nlgn)圖片
實現:
public class Heap { // 以下一顆二叉樹數字表示其序號 // // 0 // 1 2 // 3 4 5 6 // // 根據二叉樹的定義,序號爲n的節點的父節點(若是存在)序號爲n/2⌉-1,其子節點(若是存在)序號爲n*2+1和n*2+2 // 若是共有n個元素,則倒數第二次的最後一個節點序號爲(n+1)/2⌉+1 public static void sort(int[] a) { int n = a.length; for (int k = (int) (Math.ceil((double) (n + 1) / 2) + 1); k >= 0; k--) // 從倒數第二層的最後一個幾點開始建堆 sink(a, k, n); while (n > 1) { swap(a, 0, --n); sink(a, 0, n); } } public static void sink(int[] a, int k, int n) {// 下潛操做 while (k * 2 + 2 < n) { int j = k * 2 + 1;// j表示第一個子節點 if (a[j] < a[j + 1])// 若是第一個子節點小於第二個子節點 j++;// 此時j表示第二個子節點 if (a[k] > a[j])// 若是大的子節點小於父節點,則表示父節點大於兩個子節點,則不須要繼續下潛了 break; swap(a, k, j);// 不然把父節點和子節點交換 k = j; } } private static void swap(int[] a, int p, int q) { int t = a[p]; a[p] = a[q]; a[q] = t; } }