說明: 只要代碼中沒有複雜的循環條件,不管代碼的函數是多少,一概爲常數階\(O(1)\)java
int i=1; int j=3; int m=0; m=i+j; ....
說明: 存在循環體,在不考慮循環體的代碼執行狀況,該循環體本該執行n次,可是循環體將改變循環變量i的大小,每執行一次,i就擴到兩倍,即i=i*2,這樣就致使循環體會加速趨近終止條件n;假設通過x次後,退出循環體,則有\(2^x>=n\),所以,\(x=log_2n\);同理,當\(i=i*3時,x=log_3n\),退出循環的速度更快,時間複雜度更小。git
while(i<n){ i=i*2; }
說明: 存在循環體。循環體內代碼執行的次數隨着規模n的變化而變化,而且是線性變化程序員
for(int i=0;i<n;i++){ int j=o; j++; }
說明: 將時間複雜度爲\(O(log_2n)\)的代碼重複執行n次,即爲線性對數階\(O(nlog_2n)\)算法
//n爲一個固定的常數 //i和j均爲循環變量 while(i<n){//線性節階 while(j<n){//對數階 j=j*2; } }
說明: 複雜度爲\(O(n)\)的代碼執行了n次。數組
for(int i=0;i<n;i++){//執行n次 for(int j=0;j<n;j++){//執行n次 int m=0; m++; } }
說明 和平方階原理同樣,時間複雜度爲\(O(n^2)\)的代碼執行n次緩存
for(int i=0;i<n;i++){//執行n次 for(int j=0;j<n;j++){//執行n次 for(int k=0;k<n;k++){ int m=0; m++; } } }
綜上分析不難發現:時間複雜度的大小關係爲:\(O(1)<O(log_2n)<O(n)<O(nlog_2n)<O(n^2)<O(n^k)\)函數
程序猿必備的排序算法有:優化
基本原理: 將待排序的數組從左到右(下標從小到大), 依次選取元素,並和其相鄰的元素比較大小,若是逆序,則交換兩個元素的位置,每進行一輪,數組較大的元素會移動到數組的尾部,如同水中泡泡逐漸上浮同樣。重重複進行多輪操做,直到數組有序爲止。
圖解案例
以3 9 -1 10 20 爲例進行講解
冒泡排序的幾個關鍵點ui
Java代碼實現部分編碼
/** * 優化的地方,當某一趟中,發現沒有進行過任何交換操做,則代表數組已經有序了,此時能夠當即結束操做,沒必要要進行循環操做。 * * */ public class BubbleSort{ public static void bubbleSort(int arr[]){ int temp = 0; boolean flag = false;// 表示是否進行過交換 for (int i = 0; i < arr.length - 1; i++) {//進行arr.length-1趟排序 for (int j = 0; j < arr.length - 1 - i; j++) {//每一趟排序都通過arr.length-1-i次兩兩比較 if (arr[j] > arr[j + 1]) { // 交換 flag = true; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } //優化代碼 if (flag == false) { // 沒有發生過交換,代表是有序的 break; } else { flag = false;// 重置flag,進行下次判斷 } } } }
時間複雜度分析
很顯然,兩個嵌套for循環,每一層for循環的時間複雜度均爲線性複雜度\(O(n)\),故冒泡排序算法的複雜度爲\(O(n^2)\)
基本原理:快速排序屬於交換排序,從數組中一個元素做爲基準元素(首元素或者中間元素),根據基準元素將數組分紅兩個部分,前半部分數組的元素均小於該基準值,後半部分元素均大於該基準值;而後使用遞歸的思想,對這兩個部分使用一樣的方法進行快速排序。
圖解案例
public void QuickSort(int[] array,int left,int right){ int l=left; int r=right; int pivot=array[(right+left)/2];//選取中間值爲基準值 while(l<r){ //從左邊開始找,直到找到大於或等於基準元素的位置爲止 while(array[l]<pivot){ l++; } //當找到了大於或等於基準元素的位置時,跳到右邊開始尋找 while(array[r]>pivot){ r++; } //此時,array[r]<=pivot<=array[l],所以交換順序 int tmp=array[r]; array[r]=array[l]; array[l]=tmp; //注意:當array[r]==pivot或者array[l]==pivot,移動l或者r,否者 //在這種狀況下會進入死循環 if(array[l]==pivot){ r--; } if(array[r]==pivot){ l++; } } //當l==r時,須要錯開l和r, if(l==r){ l++; r--; } if(left<r){ QuickSort(array,left,r); } if(right>r){ QuickSort(array,r,right); } }
時間複雜度: 顯然選擇排序的時間複雜度爲\(O(nlon^2n)\),
基本原理: 從array[0]array[n-1]中選擇一個最小的元素與array[0]交換位置,此時array[0]爲最小的元素;而後從array[1]array[n-1]中選取最小的元素與array[1]交換位置;選擇的範圍由全局全部變成0;因此須要通過n-1次選擇和交換的過程。
圖解案例
選擇排序的幾個關鍵點
java代碼
public void SelectionSort(int[] array){ for(int i=0;i<array.length-1;i++){//通過n-1輪選擇 int minIndex=i;//初始最小值對應的索引 int min=array[minIndex];//定義一個初始最小值 for(int j=i;j<array.length;j++){//選擇最小值[i,length-1];索引從0開始 if(array[j]<min){//找出當前選擇範圍的最小值 min=array[j];//更新最小值 minIndex=j;//更新最小值對應的索引 } } //交換最小值和array[i]的值 if(minIndex!=i){//不然不必交換 //交換兩個數組元素的標準步驟 array[minIndex]=array[i]; array[i]=min; } } }
時間複雜度: 顯然選擇排序的時間複雜度爲\(O(n^2)\),緣由很簡單:兩個嵌套的for循環均爲\(O(n)\),注意:\(O(n-1)=O(n)\)
基本原理: 將待排序數組array[0]~array[n-1]從邏輯上分爲有序數組和無序數組,初始條件:(array[0])爲有序數組,(array[1],..array[n-1])爲無序數組;則插入排序的基本過程是:逐一將無序數組中的元素插入到有序數組中去,每完成一次插入操做,有序數組中的元素加一,無序數組中的元素減一,直到全部的無序數組變成有序數組。
圖解案例
插入排序的幾個關鍵點
public void insertSort(int[] array){ for(int i=1;i<array.length;i++){//從第一個元素開始 int insertVal=array[i];//待插入的值爲當前索引指向的值(在尋找待插入位置時會覆蓋這個值,須要記錄下來) int insertIndex=i-1;//即將要插入的位置爲當前索引的前一個位置 ////將當前元素與有序數組中的元素逐一比較,找到要插入的位置 while(insertIndex>=0 && array[insertIndex]>insertVal){ array[insertIndex+1]=array[insertIndex];//空出待插入的位置出來 insertIndex--; } //開始執行插入操做,同時有個優化點,當插入的位置爲當前索引i時,不須要插入 //因爲退出while時,insertIndex-1了,故真實的insertIndex=insertIndex+1; if(insertIndex!=i){ array[insertIndex+1]=insertVal; } } }
時間複雜度: 顯然選擇排序的時間複雜度爲\(O(n^2)\)
基本原理: 將待排序的數組按照某個預約的增量gap分組,對每一個分組都採用直接插入排序的方式進行排序;隨後逐步按照某個策略縮小gap(如:gap=gap/2)並進行分組,使用直接插入排序算法分別對這些數組進行排序,當gap=1時,只有一個分組,使用希爾排序完成最後的一次排序。
圖解案例
希爾排序的關鍵步驟
Java代碼實現部分
public void ShellSort(int[] array){ //初始化gap爲數組長度的一半,每一次分組長度縮小一半 for(int gap=array.lentgh/2;gap>0;gap=gap/2){ //對當前分組中的全部組元素進行直接插入排序 for(int i=gap;i<array.length;i++){ int j=i; int tmp=array[j];//暫時保存待插入的值 if(array[j]<array[j-gap]){//優化,其餘狀況不須要進行插入 while(j-gap>=0 && tmp<array[j-gap]){ array[j]=array[j-gap];//空出待插入的位置 j-=gap; } //退出while循環時,證實找到了合適的插入位置 array[j+gap]=tmp; } } } }
時間複雜度
第一層for循環爲分組功能,其時間複雜度爲:\(log_2n\),內部希爾排序最差爲\(O(n^2)\)。
基本原理:採用分治思想和遞歸思想。歸併排序主要分爲兩大過程:分解和合並過程,分解是將數組二分至只有一個元素爲止。合併的過程當中同時進行排序,兩邊元素逐一比較,較小的元素填充到緩衝數組中。另外歸併排序須要額外的緩存數組。
圖解案例
分解過程使用了遞歸思想。
合併過程爲:
java代碼
public void mergeSort(int[] array,int left,int right,int[] temp){ //遞歸分解 //那麼遞歸的結束結束條件:left>=right if(left>=right){ return; } int mid=(left+rightight)/2; //向左邊遞歸 mergeSort(array,int left,mid,temp); //向右遞歸 mergeSort(array,int mid+1,right,temp); merge(array,left,mid,right,temp); } /** 合併過程 **/ public void merge(int[] array,int left,int mid,int right,int[] temp){ int i=left;//索引i範圍爲[left,mid]; int j=mid+1;//索引j的範圍爲[mid+1,right]; int t=0;//tmp額外數組的索引 while(i<=mid && j<=right){ if(array[i]<array[j]){ temp[t]=array[i]; t++; i++; }else{ temp[t]=array[j]; t++; j++; } } //當該循環退出的時候,有兩種種狀況(不可能同時超出邊界,最多同時到達邊界,但最後仍是有一個先) //超出邊界: //1.i>mid,右邊數組有剩餘 //2.j>right,左邊數組有剩餘 if(i>mid){ while(j<=right){ temp[t]=array[j]; t++; j++; } } if(j>right){ while(i<=mid){ temp[t]=array[i]; t++; i++; } } //將temp數組中的元素複製到array中 //注意,temp中的有效部分並非從0開始,而是從[left,right] //所以只要複製的範圍是[left,right]; t=0;//特別注意 int tmpLeft=left; while(tmpLeft<=right){ array[tmpLeft]=temp[t]; t++; tmpLeft++; } }
時間複雜度
第一層for循環爲分組功能,其時間複雜度爲:\(log_2n\),內部希爾排序最差爲\(O(nlog_2n)\)。
圖解
直接看圖解,語言難於生動的描述此過程
基本步驟:
(1)初始化1個二維數組10*array.length;表示10個桶,標號爲:0-9,每一個桶(用一個數組表示)最多容納array.length個元素。
(2)得到待排序數組中最大元素的最高位,依次按個位、十位、...最高位進行排序。
(3)第一輪,按個位進行選取元素,將個位放進對應的桶編號中,當全部的元素均放進桶中後,按順序讀出每一個桶中的元素,讀取到的序列做爲該輪排序的結果。再以該輪排序的結果做爲輸入條件,依照十位進行排序;最後直到全部的位數都完成。
編碼過程當中的幾個關鍵點
public class RadixSort { public static void radixSort(int[] arr){ //第一輪:針對每一個元素的個位進行排序 //定義一個2維數組,表示10個桶,每一個桶都是一個一維數組 //爲了防止數據的溢出,則每一個一維數組,大小定爲arr.length //很明顯,基數排序是使用空間換時間的經典排序算法 int[][] bucket=new int[10][arr.length]; //每一個桶都須要一個遊標指針。 //所以咱們能夠定義一個數組來記錄每一個桶的遊標 int[] bucketElementCounts=new int[10]; //獲得數組中最大數的位數 int max=arr[0]; for(int i=1;i<arr.length;i++){ if(arr[i]>max){ max=arr[i]; } } //技巧:獲取整數的最大位數 int maxLength=(max+"").length(); for(int i=0,n=1;i<maxLength;i++,n*=10){ for (int j = 0; j < arr.length; j++) { // 取出每一個元素的個位、十位、百位。。。 int digitOfElement = arr[j] / n % 10; bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j]; bucketElementCounts[digitOfElement]++; } // 按照桶的順序依次取出數據,並放入原來的數組 int index = 0; // 遍歷每個桶 for (int k = 0; k < bucketElementCounts.length; k++) { // 若是桶中有數據,咱們才放入數據到原數組 if (bucketElementCounts[k] != 0) { // 取出該個桶中的數據 for (int l = 0; l < bucketElementCounts[k]; l++) { // 取出元素放入到原數組中 arr[index] = bucket[k][l]; index++; } } // 第一輪處理後,須要將每一個桶的清零 bucketElementCounts[k] = 0; } } } }