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

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

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

堆排序

什么是堆:特殊的数据结构,是最高效的优先级队列,堆可以看作一颗完全二叉树的数组对象

堆又分为大根堆和小根堆:

大根堆:在每一个堆中,父节点的值都是最大的。

image.png 无论是蓝色、紫色还是橙色所框住的堆,根节点的位置都是最大

小根堆:在每一个堆中,父节点的值都是最小的。

image.png

无论是蓝色、紫色还是橙色所框住的堆,根节点的位置都是最小

我们可以将一个一维数组对应成一个堆

image.png

如何构造有大根堆?

首先先了解由一维数组映射出来的堆中父节点与子节点之间的关系: 对于堆中任意一个结点(它在数组中的索引为i),它的左子节点在数组中的索引为(2 * i + 1),它的右子节点在数组中的索引是(2 * i + 2),它的父节点在数组中的索引是(i-1)/2

思路:对于一维数组,每当进入一个新的数字时,就将该数与它的父节点进行比较,若是该数比它父节点位置的值要大,则交换两数位置,直到没有该数所在的位置没有父节点或者是该数比它的父节点位置的值小为止。

代码实现

//index是新插入数组的数字的索引
public static void heapInsert(int[] arr,int index){
	while(arr[index]<arr[(index-1)/2]){
		swap(arr,index,(index-1)/2);
		index = (index-1)/2;
	}	
}

小根堆的构造也是同理,稍微修改循环终止的条件即可(没有父节点或者该数比它的父节点位置的值要大)

大根堆的删除操作

删除最大值: 在大根堆中删除最大的元素,并且使得删除后的堆仍是大根堆

思路:大根堆的最大元素即为根节点的位置,最大值删除后,将堆中最后一个元素放到根节点的位置,开始进行比较:若是该数比它的两个孩子中最大的孩子要小,则交换该数与它最大的孩子的位置,再接着与新的两个孩子中最大的孩子进行比较,若是该数小于它的新的两个孩子中最大的孩子,则再次进行交换,直到该数都大于它的两个孩子或者没有子节点可以比较为止。

image.png

代码实现:

public static void heapify(int[] arr,int index,int heapSize){
	int left = left*2+1;//左孩子的下标

	while(left<heapSize){//有左孩子
		int largest = (left+1<heapSize) && arr[left]>arr[left+1] ? 
		left : left+1;//找出最大值的索引

		//比较父节点与最大的孩子
		largest = arr[index]>arr[largest] ? index : largest;
		if(largest == index) break;//父节点最大,不用交换
		swap(arr,index,largest);
		index = largest;
		left = index*2+1;
	}
}

堆排序

思路:通过不断地构造大根堆获取最大值元素,将最大值排到数组末端并将其移除出堆,再对剩下的元素构造出大根堆获取最大值元素,排到数组倒数第二个位置,然后继续对剩下的元素构造大根堆...以此类推,每一次构造大根堆都可以获取一个最大值元素并将其归位。

代码实现:

public static void heapSort(int[] arr){
	if(arr == null || arr.length < 2) return;
	//构造出一个大根堆
	for(int i = 0;i<arr.length-1;i++){//O(N)
		heapInsert(arr,i);//O(logN)
	}
	int heapSize = arr.length;//初始化堆的大小
	swap(arr,0,--heapSize);//将最大值归位
	while(heapSize>0){
		heapify(arr,0,heapSize);//O(logN)
		swap(arr,0,--heapSize);
	}

}

堆排序的时间复杂度为O(N * logN) 额外空间复杂度O(1)

堆排序拓展题目

已知一个近乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。

思路:使用小根堆每次找出最小的元素并使其归位。将数组0到k-1的范围内的元素进行小根堆的构造,由于数组是近乎有序,并且每个元素离它正确的位置距离不超过k,所以构造后整个数组最小的元素一定在0号位置上(小根堆的根节点处),此时最小的元素正确排序到了0号位置上。然后将数组1到k的范围内的元素进行小根堆的构造,第二小的元素就到了1号位置上...不断重复这个过程直到小根堆的大小为0,说明数组已经排序完成。

代码实现:可以使用Java封装的数据结构PriorityQueue(优先级队列,实际上就是堆)的add和poll方法解决,详细代码略。

基数排序

桶排序

思路:关键字排序思想,首先按照个位数进行排序,然后按照十位数进行排序...

image.png

代码实现:

public static void radixSort(int[] arr){
	if(arr.length<2 || arr == null) return;
	radixSort(arr,0,arr.length-1,maxbits(arr));
}

public static void maxbits(int[] arr){
	int max = Integer.MIN_VALUE;
	for(int i = 0;i<arr.length;i++){//找出数组的最大值
		max = Math.max(max,<arr[i]);
	}
	//计算最大值max的位数
	int result = 0;
	while(max!=0){
		result++;
		max/=10;
	}
	return result;
}

public static void radixSort(int[] arr,int L,int R,int digit){
	final int radix = 10;//桶的个数
	int i= 0,j = 0;
	//准备辅助数组用于存放中间排序的结果
	int[] help = new int[R-L+1];
	for(int d = 1;d<=digit;d++){//有多少位就进出几次
		int[] count = new int[radix];//十个桶
		for(i=L;i<=R;i++){
			j = getDigit(arr[i],d);//获取arr[i]第d位上的数字
			count[j]++;
		}
		//加工count数组成前缀和数组
		for(i = 1;i<radix;i++){
			count[i] = count[i-1]+count[i];
		}
		//从右遍历原数组,将排序后的结果放入help数组中
		for(i = R;i>=L;i--){
			j = getDigit(arr[i],d);
			help[--count[j]] = arr[i];
			count[j]--;
		}
		//将help数组覆盖到原数组上
		for(i = L,j = 0;i<=R;i++){
			arr[i] = help[j];
		}
	}


}