程序上遇到的问题总结

/*
 * 存在两个问题:一个是程序没看懂,一个是第二种解法中出现的堆栈溢出的问题
 * 堆栈溢出的错误通过举实际的例子即可得到,当begin值不为0时。
 * 通过eclipse的调试程序功能可以看到一步步运行,把main()中的arr设置的短一些,然后k值也设置小一点,方便调试。
 * 
 */
package advanceClassCode;
//BFPRT算法实现在无序数组中找到第k小的数
public class Code_06_BFPRT {

	// O(N*logK)堆排序,K个结点组成的树高度是logK,由于只需要将数组arr中的前k个最小的数抽取出来,这K个节点构成的树的高度是logK。heapInsert()的复杂度是O(logK),heapify()的复杂度是O(N),所以总的复杂度是O(NlogK)
	public static int[] getMinKNumsByHeap(int[] arr, int k) {
		if (k < 1 || k > arr.length) {
			return arr;
		}
		int[] kHeap = new int[k];
		for (int i = 0; i != k; i++) {
			heapInsert(kHeap, arr[i], i);//前k个结点构成最大根二叉树
		}
		//把原数组中从下标从K开始的到arr.length-1的下标的范围内的值,若发现有值小于KHeap[0]即之前选出的k个值中的最大值,则把较小的当前arr数组中的值调整到kHeap[0],因为kHeap[]里最后就是保存原数组中最小的k个值。之前KHeap[]中保存的k个值是把原数组中下标从0到k-1的数按照最大根的形式组成了一个二叉树,现在是把数组arr[]中剩下的数中,只要发现比KHeap[]中最大值小的数,就与之交换,在arr[]剩下的值中一个个遍历,直到最后KHeap[]中保存最小的K个数。
		for (int i = k; i != arr.length; i++) {
			if (arr[i] < kHeap[0]) {
				kHeap[0] = arr[i];//后面剩下的结点里若存在比之前构造出的最大根二叉树最大值小,
//				若有则把小值放置到最大根二叉树的根结点上。
				//这个函数就是实现当把数组中较小的值与KHeap[0]交换后,重新把KHeap调整为最大根二叉树
				heapify(kHeap, 0, k);//把二叉树调整
			}
		}
		return kHeap;
	}

	//从树的根节点开始构造,即下标为0开始构造,从上往下构建出这棵树,并且遇到后面插入的子节点值大于根节点,就把大值网上调整到根节点处。构造出总结点个数为K的最大根二叉树。
	public static void heapInsert(int[] arr, int value, int index) {
		arr[index] = value;
		while (index != 0) {
			int parent = (index - 1) / 2;//父节点
			if (arr[parent] < arr[index]) {
				swap(arr, parent, index);//插入新节点,大的值就往父节点转移,最大根
				index = parent;
			} else {
				break;
			}
		}
	}
	//从index=0开始对传入的数组KHeap[]对应的二叉树进行调整,调整为最大根二叉树。
	public static void heapify(int[] arr, int index, int heapSize) {
		int left = index * 2 + 1;
		int right = index * 2 + 2;
		int largest = index;
		//一层层往下遍历,把左右儿子中大的节点与其父节点值交换。
		//然后还要把当前考虑的下标index改为交换的原本大值的下标,若是与左儿子交换,则index下标改为left下标,若是与右儿子交换,则index改为right下标,然后在往下,再往新的index的左右儿子比较。
		while (left < heapSize) {
			if (arr[left] > arr[index]) {
				largest = left;
			}
			if (right < heapSize && arr[right] > arr[largest]) {
				largest = right;
			}
			if (largest != index) {
				swap(arr, largest, index);
			} else {
				break;
			}
			index = largest;
			left = index * 2 + 1;
			right = index * 2 + 2;
		}
	}
	
	
	
	
	
/*
 * 2020/1/13复习一遍	
 */
	

	// O(N)
	public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
		if (k < 1 || k > arr.length) {
			return arr;
		}
//		minKth就是满足题意的数组中第k小的数组值
		int minKth = getMinKthByBFPRT(arr, k);//这里调用用的参数k
//		创建大小为k的数组
		int[] res = new int[k];
		int index = 0;
		for (int i = 0; i != arr.length; i++) {
			if (arr[i] < minKth) {//遍历数组,挑选出符合条件的数组中所有比第k小的数还
//				要小的数,此时这些数并没有顺序
				res[index++] = arr[i];
			}
		}
//		index起始位置是接着上面的index位置,此时把前k个位置中未填满的位置全部填上第k小的那个值
		for (; index != res.length; index++) {
			res[index] = minKth;
		}
		return res;
	}

	public static int getMinKthByBFPRT(int[] arr, int K) {
		int[] copyArr = copyArray(arr);
		//返回的是满足题意的第k小的数组元素值,也是调用的select的返回值
		return select(copyArr, 0, copyArr.length - 1, K - 1);//这里调用用的参数k-1
	}

	public static int[] copyArray(int[] arr) {
		int[] res = new int[arr.length];
		for (int i = 0; i != res.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}

	public static int select(int[] arr, int begin, int end, int i) {
		if (begin == end) {
			return arr[begin];
		}
		//pivot是中位数数组里的中位数,不断循环嵌套递归调用,直到分割后剩下的中位数数组中不足5个元素,直接得到其中位数
//		元素即为pivot
//		因为传入select()的参数是begin和end,所以下面也是写begin和end
		int pivot = medianOfMedians(arr, begin, end);//求出由中位数构成的数组里的中位数
//		以这个最终的中位数为划分依据,将原数组划分为三部分区域
		int[] pivotRange = partition(arr, begin, end, pivot);//调用partition()
//		得到的是等于pivot值区域的左右边界
//		传入的i参数是k-1,k-1位于等于区域
//		看k-1落在三个区域的哪个范围
		if (i >= pivotRange[0] && i <= pivotRange[1]) {
//			落在等于区域那么值直接就是pivot
			return arr[i];
//		k-1位于小于区域,递归调用
		} else if (i < pivotRange[0]) {
//			落在小于区域,那么需要将小于区域也partition操作,递归嵌套循环调用
			return select(arr, begin, pivotRange[0] - 1, i);
//		k-1位于大于区域
		} else {
			return select(arr, pivotRange[1] + 1, end, i);
		}
	}

	public static int medianOfMedians(int[] arr, int begin, int end) {
		int num = end - begin + 1;
//		把数组值按照5为间隔分为若干区域,最后一个区域的数目可能不满5
		int offset = num % 5 == 0 ? 0 : 1;
		int[] mArr = new int[num / 5 + offset];
		for (int i = 0; i < mArr.length; i++) {
//			每个子数组的起始和终止下标,是begin+i*5不要粗心写成了begin*i+5
			int beginI = begin + i * 5;
			int endI = beginI + 4;
			//因为对于最后一个分出的子数组,数量可能不足5,所以需要在end和endI之间取小值
//			计算子数组的中位数
			mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
		}
//		mArr是由中位数构成的新数组,传入select()的参数不是k-1而是新数组的中位数下标,这里是循环嵌套调用,终止的
//		条件是当不断按照以5为区间分割出子数组,并且取中位数构成新数组,当新数组的数组不足5时,就不能继续调用了
//		此时直接就能得到不足5个元素数组的中位数
//		注意end是mArr.length-1而不是mArr.length
		return select(mArr, 0, mArr.length - 1, mArr.length / 2);
	}
//	借助荷兰国旗问题
	public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
//		小于区域
		int small = begin - 1;
//		注意这里cur是从begin开始而不是从0开始
		int cur = begin;
//		大于区域
		int big = end + 1;
//		注意这里的循环范围是不能达到big,而不是不越过数组的界即cur<arr.length
		while (cur != big) {
			if (arr[cur] < pivotValue) {
				swap(arr, ++small, cur++);
			} else if (arr[cur] > pivotValue) {
				swap(arr, cur, --big);
			} else {
				cur++;
			}
		}
		int[] range = new int[2];
		range[0] = small + 1;
		range[1] = big - 1;
		return range;//返回等于区域的两个边界下标
	}
//	计算给定子数组的中位数
	public static int getMedian(int[] arr, int begin, int end) {
		insertionSort(arr, begin, end);
//		下面的语句换成int sum=end-begin就会出现statck overflow的错误,
//		调试的时候程序无法结束??????????????????
		int sum = end + begin;
//		下面语句用int mid=sum/2代替也可以
		int mid = (sum / 2) + (sum % 2);
		return arr[mid];
	}
//	得到从小到大的升序排列,因为是对分割后的子数组内部排序,子数组间是不需要排序的,所以需要begin和end参数来确定出具体的子数组
	public static void insertionSort(int[] arr, int begin, int end) {
//		因为已经传入了参数begin和end,所以下面的范围写i!=end+1,而不是写i<arr.length
		for (int i = begin + 1; i != end + 1; i++) {//i要从begin+1开始因为后面j=i,需要j-1
			for (int j = i; j != begin; j--) {
				if (arr[j - 1] > arr[j]) {
					swap(arr, j - 1, j);
				} else {
					break;//退出本次循环,进行下一轮循环即j--
				}
			}
		}
	}

	public static void swap(int[] arr, int index1, int index2) {
		int tmp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = tmp;
	}

	public static void printArray(int[] arr) {
		for (int i = 0; i != arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
		// sorted : { 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 7, 9, 9, 9 }
//		得到数组中前k小的数,输出这符合要求的K个数是没有顺序的
		printArray(getMinKNumsByHeap(arr, 10));
		printArray(getMinKNumsByBFPRT(arr, 10));
//		若想输出数组,不能采用System.out.print(getMinKNumsByBFPRT(arr,10));

	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值