跟着左神学算法——数据结构与算法学习日记(一)

678 阅读4分钟
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

最近开始接触与学习数据结构与算法,一方面为了解决课内数据结构课解题思路不清晰的问题,另一方面也是听说左神的大名,故开始跟着左神一起学习数据结构与算法。同时以写博客的形式作为输出,也算是为了对所学的知识能掌握的更深吧

选择排序

思路:用一个minIndex记录下最小元素的索引,从i到n-1遍历表,得到i~N-1中最小元素的索引minIndex,然后交换第i个元素与最小索引位置的元素。以此类推,一共要遍历N-1次表,每一次遍历表要遍历N-i次元素

代码实现:

public static void selectionSort(int arr[]){
	if(arr.length<2 || arr == null) return;
	for(int i = 0;i<arr.length-1;i++){
		int minIndex = i;
		for(int j = i+1;j<arr.length;j++){
			if(arr[j]<arr[minIndex]){
				minIndex = j;//更新minIndex
			}
		}
		swap(arr,i,minIndex);//交换minIndex和i处的元素
	}
}

交换排序

冒泡排序

原理:冒泡排序的每一趟都只能将一个最大的元素归位,也可以叫做是把最大的元素浮出。以此类推,如果有n个数进行排序,只需要将n-1个数归位,也就是进行n-1趟排序。 而每一趟都需要从第一位元素开始,与其相邻的元素进行比较然后决定是否交换位置,交换位置后(此时索引指向下一位元素)继续比较接下来的元素与其下一位元素的大小关系并且决定是否交换位置,重复此步骤,直到所有元素到达其正确的位置

代码实现:

public static void BubbleSort(int arr[]){
	if(arr == null || arr.length<2) return;
	for(int i = arr.length-1;i>0;i--){
		for(int j = 0;j<i;j++){
			if(arr[j]>arr[j+1]){
				swap(arr,j,j+1);
			}
		}
	}
}

代码实现(进阶版):增加一个tag作为标志判断数组是否已经是有序状态(在一次遍历表中没有进行任何交换即代表该表已经是有序状态)

针对问题:在进行第i次遍历链表之后,链表已经是有序的状态,此时不需要再遍历链表浪费时间

public static void BubbleSort(int arr[]){
	if(arr == null || arr.length<2) return;
	for(int i = arr.length-1;i>0;i--){
		boolean tag = true;//默认表现在是有序状态
		for(int j = 0;j<i;j++){
			if(arr[j]>arr[j+1]){
				swap(arr,j,j+1);
				tag = false;//发生了交换,表现在是无序状态
			}
		}

		if(tag == true){
			//没有发生交换,不需要再进行循环,退出
			break;
		}
	}

}

代码实现(进进阶版):将tag换成一个int类型的tag,用来记录表有序区间开始的位置(索引),tag初始化成0;每次遍历链表进行元素交换的时候更新tag的位置,当遍历完一次链表之后,tag的位置是最新的有序区间开始的索引。如果在第i次遍历链表之后,tag是0,说明没有发生元素交换,链表已经是有序状态

针对问题:在遍历链表时,剩下还没遍历的区域已经是有序的状态,此时不需要再继续遍历

public static void BubbleSort(int arr[]){
	if(arr == null || arr.length <2) return;
	int tag = 0;
	for(int i = length-1;i>0;i--){
		if(tag>0){
			//说明进行了元素交换,更新有序区间的起始索引
			i = tag;
			tag = 0;
		}
		
		for(int j = 0;j<i;j++){
			if(arr[j]>arr[j+1]){
				swap(arr,j,j+1);
				tag = j;
			}
		}

		if(tag == 0){
		//没有进行过元素交换,表已经是有序状态
			break;
		}
	}
}

基于异或逻辑实现不使用额外空间交换两数

异或运算:相同取0,相异取1,可以理解为无进位相加

满足运算:

  1. 0 ^ N = N N ^ N = 0
  2. 交换律、结合律
  3. 若干个数进行异或操作,结果只取决于其中的元素个数,与顺序无关

交换两数:

int a = X;
int b = Y;
//开始交换
a = a ^ b;
b = a ^ b;
a = a ^ b;
/*
执行完第一行:a = X ^ Y;  b = Y;
执行完第二行:a = X ^ Y;  b = X ^ Y ^ Y = X;
执行完第三行:a = X ^ Y ^ X = Y;   b = X;
*/

练手题:

  1. 在一个数组中,假设a出现了奇数次,其他数字出现了偶数次,试找出a
  2. 在一个数组中,假设a和b出现了奇数次,其他数字出现了偶数次,试找出a和b

第一题: 思路:由于若干个数进行异或操作,结果只取决于其中相同的元素个数是奇数个还是偶数个,所以我们可以使用一个变量eor,依次对数组中的元素进行异或操作,出现偶数次的元素都会消成0,最后只留下0和出现奇数次的元素进行异或操作,最后eor就是我们要找的a

代码实现:

public static void SearchNum(int arr[]){
	int eor = 0;
	for(int i = 0;i<arr.length;i++){
		eor = eor ^ arr[i];
	}
	System.out.println(eor);
}

第二题: 思路:由第一题的思路可知,使用一个eor变量,依次与数组中的元素进行异或操作,最后eor的值是a^b(a异或b)。由于a和b是不相等的,所以eor的值不为0。 对于int类型变量eor,它的32位二进制码中一定有一位是不为0的(因为eor不为0) ,假设为第i位,那么在第i位上为1的元素个数一定是奇数个(无进制相加的结果),而这个二进制码的第i位为1的元素就是a/b。(eor的二进制码的第i位不为0的现象就是出现了奇数次的a/b进行异或操作后的结果)

对于如何拿到这个元素,我们可以找出二进制码第i位为1,其他位均为0 的那个数为onlyOne,对所有第i位上是1的数依次进行异或操作,由于该元素出现的次数是奇数,所以经过不断的异或操作后onlyOne的值一定就是我们要找的元素a/b

对于如何拿到另一个元素,我们只需要将eor和onlyOn进行异或操作(a^b^a = b 或 a^b^b = a)即可

代码实现:

	int eor = 0;
	for(int i = 0;i<arr.length;i++){
		eor = eor ^ arr[i];
	}

	int rightOne = eor & (~eor + 1);//提取出最右边的1
	int onlyOne = 0;
	for(int i = 0;i<arr.length;i++){
		if(arr[i] & rightOne == 1){
			onlyOne = onlyOne ^ arr[i];
		}
	}
	//另外一个元素就是(eor^onlyOne)

二分法

应用
  1. 在一个有序数组中,找某个数是否存在
  2. 在一个有序数组中,找>=某个数最左侧的位置tong
  3. 局部最小值问题(数组中相邻位置的元素互不相等)

思路:

  1. 在有序数组中,先取中点位置,判断该位置的元素是否与要找的元素相等,相等则直接返回;若是大于(小于)要找的元素,则取数值较小(较大)的区间的中点,再进行比较。重复此步骤直到找到要找的元素或者无法再取到中点为止
  2. 在一个有序数组中,找大于等于某个数最左侧的位置图示

image.png 3.