【1】认识复杂度和简单排序算法

目录

一、认识时间复杂度

二、排序算法

1.选择排序

2.冒泡排序

3.插入排序

4.二分法的详解与扩展

4.1 在一个有序数组中,找某个数是否存在

4.2 在一个有序数组中,找>=某个数最左侧的位置

4.3 局部最小值问题(数组元素相邻位置一定不相等)

5.异或运算的性质与扩展

6.对数器的概念与使用


一、认识时间复杂度

常数时间的操作:一个操作如果合样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。

时间复杂度为一个算法流程中,常数操作数量的一个指标。常用O(常读bigO)来表示。具体来说,先要对一个算法流程肥肠熟悉,然后去写出这个算法流程中,发生了多少常数操作,进而总结出常数操作数量的表达式。

在表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果为f(N),那么时间复杂度为O(f(N))。

评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下实际运行时间,也就是“常数项时间”。

        如上图,选择排序:第一次看了N次,比较了N次,交换了1次;第二次看了N-1次,比较了N-1次,交换了1次。。。一共看了N+(N-1)+(N-2)+...+1,比较N+(N-1)+(N-2)+...+1,交换N次。最终加合是aN^2+bN+c,所以上述操作的时间复杂度是O(N^2)。

package class01;

public class Test{

    public static void process1(){
        int N=1000;
        int a=0;
        for(int i=0;i<N;i++){
            a=2+5;
            a=4+7;
            a=6*8;    
        }
    }

    public static void process2(){
        int N=1000;
        int a=0;
        for(int i=0;i<N;i++){
           a=3|6;
           a=3&4;
           a=4^785;
        }
    }

}

         上述代码理论实践一样的,评价算法的好坏就要比较实际运行时间。

二、排序算法

1.选择排序

时间复杂度为O(N^2),额外空间复杂度O(1)

	public static void selectionSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		for(int i=0;i<arr.length-1;i++) { // i~N-1
			int minIndex=i;
			for(int j=i+1;j<arr.length;j++) { // i~N-1上找最小值的下标
				minIndex=arr[j]<arr[minIndex]?j:minIndex;
			}
			swap(arr, i, minIndex);
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp=arr[i];
		arr[i]=arr[j];
		arr[j]=tmp;
	}

2.冒泡排序

时间复杂度为O(N^2),额外空间复杂度O(1)

	public static void bubbleSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		for(int e=arr.length-1;e>0;e--) {
			for(int i=0;i<e;i++) {
				if(arr[i]>arr[i+1]) {
					swap(arr,i,i+1);
				}
			}
		}
	}
	
	//交换arr的i和j位置上的值
	public static void swap(int[] arr, int i, int j) {
        if(i!=j){
            arr[i]=arr[i]^arr[j];
		    arr[j]=arr[i]^arr[j];
		    arr[i]=arr[i]^arr[j];
        }
	}

 异或运算可以理解为无进位相加:(0+1=1, 1+1=0(无进位) 0+0=0)

解释通过三次异或交换a和b:

假设a=甲,b=乙

a=a^b  // a=甲^乙

b=a^b  // b=甲^乙^乙=甲^0=甲

a=a^b  // a=甲^乙^甲=甲^甲^乙=0^乙=乙

PS:一定要保证a和b是不一样的,如果一样,最终a=0, b=0

3.插入排序

选择排序和冒泡排序,数据状况对它们不会产生太大的影响,但是对于插入排序,会产生较大的影响。

时间复杂度O(N^2),额外空间复杂度O(1)

	public static void insertionSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		for(int i=1;i<arr.length;i++) {
			for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--) {
				swap(arr,j,j+1);
			}
		}
	}
	
	//交换arr的i和j位置上的值
	public static void swap(int[] arr, int i, int j) {
		if(i!=j) {
			arr[i]=arr[i]^arr[j];
			arr[j]=arr[i]^arr[j];
			arr[i]=arr[i]^arr[j];
		}
	}

4.二分法的详解与扩展

4.1 在一个有序数组中,找某个数是否存在

  

想要寻找num是否存在:

每次枚举区间中间的数x,然后和num比较

若x<num,则下次在x的右半区间找

若x>num,则下次在x的左半区间找

若x==num,则找到返回

时间复杂度O(logN)

4.2 在一个有序数组中,找>=某个数最左侧的位置

 找>=num最左侧位置也可以使用二分

每次枚举区间中间的数x,然后和num比较

若x<num,则下次在x的右半区间找 => l=mid+1

若x>=num,则下次在x的左半区间找 => r=mid

同理可以二分找>=num最右侧位置

4.3 局部最小值问题(数组元素相邻位置一定不相等)

 局部最小:

对于0位置的数,只要小于1位置的数即为局部最小

对于N-1位置的数,只要小于N-2位置的数即为局部最小

对于其他i位置,需要同时小于i-1和i+1位置的数才为局部最小

 每次枚举区间中间位置M,然后判断M位置情况

若M为局部最小,则直接返回

若arr[M-1] < arr[M],则在左半区间找

若arr[M] > arr[M+1],则在右半区间找

左边向下进入,右边向上出来,相邻的元素不同,则在区间中一定存在拐点(局部最小)

使用二分的情况:排查性的东西可以二分,左右两侧存在差异性,左边一定有,则可以甩掉右侧。

5.异或运算的性质与扩展

 异或拓展:同一个数异或奇数次,最终结果为非零;同一个数异或偶数次,最终结果为零。

 面试题1:给定一个数组arr,在其中有且仅有一个数出现了奇数次,其它出现偶数次,要求在O(N)时间内找出这个数。

 将数组的每个数异或在一起,由于偶数次的数异或相当于异或0,奇数次的数异或相当于异或该数,本数组只有一个数出现奇数次,所以,最终异或的结果就是这个出现奇数次的数。

 给出代码如下:

	public static void printOddTimesNum1(int[] arr) {
		int eor=0;
		for(int cur:arr) {
			eor^=eor;
		}
		System.out.println(eor);
	}

 面试题2:给定一个数组arr,在其中有且仅有两个数出现了奇数次,其它出现偶数次,要求在O(N)时间内找出这两个数。

0

假设数组中出现奇数次的两个数分别为a和b。 

eor是将所有数异或在一起的结果为a^b。

由于eor不等于0,所以一定存在某位为1,则可以根据该为是否为1,分为两类。

将其中任意一类中的所有数异或,others在该位的出现1或0一定为偶数次(那么筛选出来也一定是偶数个),所以一定不影响结果,结果为a或者b,记作eor'。

则最终出现奇数次的两个数为eor'和(eor^eor')

 提取出某个数最右边的1:eor&(~eor+1)

给出代码如下:

	public static void printOddTimesNum2(int[] arr) {
		int eor=0, onlyOne=0;
		for(int curNum:arr) {
			eor^=curNum;
		}
		// eor=a^b
		// eor!=0
		// eor必然有一个位置是1
		int rightOne=eor&(~eor+1); // 提取出最右的1
	    for(int cur :arr) {
	    	if((cur&rightOne)==0) {
	    		onlyOne^=cur;
	    	}
	    }
		System.out.println(onlyOne+" "+(eor^onlyOne));
	}

6.对数器的概念与使用

 可以用随机样本产生器产生测试用例,在a和b分别跑一遍,然后比较结果是否相等,来保证算法的正确性。

	//for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		
		// Math.random() -> [0,1)所有小数,等概率返回一个
		// Math.random()*N -> [0,N)所有小数,等概率返回一个
		// (int)(Math.random()*N) -> [0,N-1]所有整数,等概率返回一个
		
		int[] arr=new int[(int)((maxSize+1)*Math.random())]; //长度随机
		for(int i=0;i<arr.length;i++) {
			arr[i]=(int)((maxValue+1)*Math.random())-(int)(maxValue*Math.random());
		}
		return arr;
	}
	
	
	public static void main(String[] args) {
		int testTime=500000;
		int maxSize=100;
		int maxValue=100;
		boolean succeed=true;
		for(int i=0;i<testTime;i++) {
			int[] arr1=generateRandomArray(maxSize, maxValue);
			int[] arr2=copyArray(arr1);
			SelectionSort(arr1);
			comparator(arr2);
			if(!isEqual(arr1, arr2)) {
				succeed=false;
				break;
			}
			System.out.println(succeed?"Nice!":"Fucking fucked!");
 		}
	}

对于代码测试500000次,可以测试算法的正确性。generateRandomArray(),copyArray(),comparator(),isEqual()方法都是自己写的。

generateRandomArray(maxSize, maxValue)

产生长度不大于maxSize,元素在(-maxValue,maxValue)之间的随机数数组。

copyArray(int[] arr)

简单复制数组元素

comparator(int[] arr)

另一个算法来跑样例

isEqual(int[] arr1, int[] arr2)

比较两个结果是否一样

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看未来捏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值