说明: 只要代码中没有复杂的循环条件,不管代码的函数是多少,一概为常数阶\(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; } } } }