選擇排序(以遞增排序為例):通過內部循環第一次遍歷數組找到最小的元素與數組的第一個元素交換位置,第二次遍歷數組找到第二小的元素與數組的第二個元素交換位置,當內存循環找到最小的元素並交換位置后下次遍歷時應該避開這個最小元素。這種排序方法對任何結構的數組都是O(n²)的時間復雜度
public static int[] orderBySelect(int[] a){ for(int i=0;i<a.length;i++){ int temp=a[i]; int flag=i; for(int j=i+1;j<a.length;j++){ if(temp>a[j]){ temp=a[j]; flag=j; } } if(flag!=i){ a[flag]=a[i]; a[i]=temp; } } return a; }
插入排序(以遞增排序為例):假定第一個元素為最小元素,判定第二個元素與第一元素的大小,如果第二個小於第一個,則交換位置,這時候第一個和第二個已經排好序,通過第三個元素與前面已經排好的第二個元素進行比較,如果大於第二個,則進行下一輪循環、否則交換位置后繼續與第一個元素進行比較,外部控制循環到達直到到達數組末端。這種排序方式有個相對於選擇排序有個好處就是如果數組本身就已經有部分排好序,則在后面的比較中當與前面已經排好序的最大值進行比較時如果大於最大值的元素就會忽略掉與其他元素的比較,節省了時間
public static int[] orderByInsert(int[] a){ for(int i=1;i<a.length;i++){ int temp=a[i];//保存當前將要用於插入的值 int j=i-1;//用於遍歷已經排好序的子集的下標 if(temp<a[j]){//判斷子集的最大值與當前的值的大小,如果當前值大,則不需要循環 while(j>=0 && a[j]>temp){//如果子集的元素大於當前值,則修改當前值的位置 a[j+1]=a[j];//將j的位置的值向前移動,用於存放當前值 j--;//進入下一次循環 } a[j+1]=temp;//循環結束后子集中所有大於temp的值都向前移動了一步,這時候j+1的位置就是temp應該插入的位置 } } return a; }
冒泡排序(以遞增排序為例):冒泡排序的思想是從左到右(從右到左)進行相鄰元素的大小判定,如果后一個元素小於前一個元素,交換位置,一輪循環后最大值將在最右邊
public static int[] orderByBubble(int[] a){ for(int i=0;i<a.length;i++){ for(int j=0;j<a.length-i-1;j++){ if(a[j]>a[j+1]){ int temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } } return a; }
歸並排序(以遞增排序為例):感覺這個算法比較難以理解,花了不少時間,歸並排序的本質是利用空間換時間的方式,所謂規即指的是遞歸,並指的是合並,將目標數組進行遞歸平分直到最多只有2個元素的數組時執行排序,然后兩兩經過歸並排序的數組進行再次歸並······,由於在遞歸中需要不斷地創建數組來比較排序,因此空間復雜度為O(nlogn),具體算法如下:
/** * 歸並排序(遞歸和合並) * @param a 目標數組 * @param p 目標數組的起始位置 * @param r 目標數組的結束位置 */ public static void mergeSort(int[] a, int p, int r) { if(p<r){//條件判斷,直到將數組細分到為一個元素作為一個數組為止 int q = (p+r)/2;//平分數組向下取整 mergeSort(a, p, q);//對數組左半部分遞歸細分 mergeSort(a, q+1, r);//對數組右半部分遞歸細分 merge(a,p,q,r);//由條件可以看出,這個方法將在數組被細分到元素個數為2的時候被調用(當元素為1即r=0時,什么也不執行) } } /** * 對細分數組已經經過排序后的左半部分和右半部分進行合並排序 * 當細分數組的元素只有一個時,則僅僅是比較兩個單元素數組的大小進行排序 * @param a 目標數組 * @param p 起始位置 * @param q 終止位置 * @param r 數組大小 */ private static void merge(int[] a, int p, int q, int r) { int i,j,k,n1,n2; n1=q-p+1;//定義左半部分數組的大小 n2=r-q;//定義右半部分數組的大小 int[] L=new int[n1];//初始化左半部分數組 int[] R=new int[n2];//初始化右半部分數組 for(i=0,k=p;i<n1;i++,k++)//賦值 L[i]=a[k]; for(j=0,k=q+1;j<n2;j++,k++)//賦值 R[j]=a[k]; for(i=0,j=0,k=p;i<n1&&j<n2;k++){//歸並左半部分和右半部分,可以看到循環的終止條件是左或者右數組的元素全部放到了目標數組中 if(L[i]<R[j]){//如果左半部分第一個元素大於右半部分第一個元素,則將目標數組開始位置的值設置為L[0]並遞歸 a[k]=L[i]; i++; }else{ a[k]=R[j];//否則將目標數組開始位置的值設置為R[0]並遞歸 j++; } } if(i<n1){//執行這個條件表明L數組中存在大於R中最大元素的元素,這時候循環將這些元素放到目標中 for(j=i;j<n1;j++,k++) a[k]=L[j]; } if(j<n2){//執行這個條件表明R數組中存在大於L中最大元素的元素,這時候循環將這些元素放到目標中 for(i=j;i<n2;i++,k++) a[k]=R[i]; } }
快速排序(以遞增排序為例):
public static void quickSort(int[] a){ sort(a,0,a.length-1); } /** * 快速排序 * @param a 目標數組 * @param begin 起始下標 * @param end 終止下標 */ private static void sort(int[] a, int begin, int end) { int i,j,index; if(begin>=end)//數組合法性判定 return; i=begin; j=end; index=a[i];//拷貝第一個元素 //以下循環主要完成將一個數組平分成兩部分,左邊部分的每一個值均小於右邊的每一個值 //原理:一次循環中分別從尾端向首端遍歷找到一個小於index的元素並賦值給a[i](a[i]的值保存在index中不會丟失),這時候尾端的元素可 //通過j作為下標找到,接下來通過首端向尾端遍歷找到一個大於index的元素並賦值給之前已經賦值到左 //部分的元素,這些條件都是在i<j的情況下執行的,保證了遍歷不會過界的問題, while(i<j){ while(i<j&&a[j]>=index) j--; if(i<j) a[i++]=a[j]; while(i<j&&a[i]<index) i++; if(i<j) a[j--]=a[i]; } a[i]=index; sort(a,begin,i-1);//遞歸直到只有一個元素的數組,這時候整個數組就已經排好序 sort(a,i+1,end); }