- 前缀和数组、对数器和随机行为
- 一、最基本的数据结构
- 二、前缀数组
- 三、介绍随机函数
- 例1 随机数函数的应用
- 例2 生成平方随机函数
- 例3 从1~5随机到1~7随机
- 例4 从a~b随机到c~d随机
- 例5 01不等概率随机到01等概率随机
- 四、对数器的使用
- 五、写在后面
-
数组
便于寻址,不便于增删数据
-
链表
便于增删数据,不便于寻址
假设有一个数组arr,用户总是频繁的查询arr中某一段的累加和你如何组织数据,能让这种查询变得便利和快捷?
思路一:生成二维数组,只用上一半,其中 a[i][j] 表示 i 位置到 j 位置需要的距离. (适用于数组长度大,查询量极其大)
思路二:生成前缀和数组,长度等于原数组长度,a[i] = a[i-1] + arr[i] . 求3到7的累积和 sum(3,7) = a[7] - a[2] . (适用于数组长度大,查询量大)
public class PreSum {
public static class RangeSum1 {
private int[] arr;
public RangeSum1(int[] array) {
arr = array;
}
public int rangeSum(int L, int R) {
int sum = 0;
for (int i = L; i <= R; i++) {
sum += arr[i];
}
return sum;
}
}
public static class RangeSum2 {
private int[] preSum;
public RangeSum2(int[] array) {
int N = array.length;
preSum = new int[N];
preSum[0] = array[0];
for (int i = 1; i < N; i++) {
preSum[i] = preSum[i - 1] + array[i];
}
}
public int rangeSum(int L, int R) {
return L == 0 ? preSum[R] : preSum[R] - preSum[L - 1];
}
}
}
三、介绍随机函数
例1 随机数函数的应用
Java中的Math.random()函数
public class RandToRand {
public static void main(String[] args) {
System.out.println("测试开始");
// Math.random() -> double -> [0,1)
//
int testTimes = 10000000;
int count = 0;
for (int i = 0; i < testTimes; i++) {
if (Math.random() < 0.75) {
count++;
}
}
System.out.println((double) count / (double) testTimes); // 0.7498939
System.out.println("=========");
// [0,1) -> [0,8)
count = 0;
for (int i = 0; i < testTimes; i++) {
if (Math.random() * 8 < 5) {
count++;
}
}
System.out.println((double) count / (double) testTimes); // 0.6247808
System.out.println((double) 5 / (double) 8); // 0.625
int K = 9;
// [0,K) -> [0,8]
int[] counts = new int[9];
for (int i = 0; i < testTimes; i++) {
int ans = (int) (Math.random() * K); // [0,K-1]
counts[ans]++;
}
for (int i = 0; i < K; i++) {
System.out.println(i + "这个数,出现了 " + counts[i] + " 次");
}
}
}
例2 生成平方随机函数
- 如何利用Math.random()函数,把得到[0,x)范围上的数的概率从x调整成 x 2 x^2 x2
- 如何利用Math.random()函数,把得到[0,x)范围上的数的概率从x调整成 x 3 x^3 x3
- 如何利用Math.random()函数,把得到[0,x)范围上的数的概率从x调整成 1 − ( x − 1 ) 3 1-(x-1)^3 1−(x−1)3
public class RandToRand {
public static void main(String[] args) {
int testTimes = 10000000;
int count = 0;
double x = 0.3;
for (int i = 0; i < testTimes; i++) {
if (xToXPower() < x) {
count++;
}
}
System.out.println("x^2的平均分布");
System.out.println((double) count / (double) testTimes); // 0.089931
System.out.println(Math.pow(x, 2)); // 0.09
System.out.println("==========");
count = 0;
for (int i = 0; i < testTimes; i++) {
if (xToXCube() < x) {
count++;
}
}
System.out.println("x^3的平均分布");
System.out.println((double) count / (double) testTimes); // 0.0270039
System.out.println(Math.pow(x, 3)); // 0.026999999999999996
System.out.println("==========");
count = 0;
for (int i = 0; i < testTimes; i++) {
if (xToXOther() < x) {
count++;
}
}
System.out.println("1-(1-x)^2 的平均分布");
System.out.println((double) count / (double) testTimes); // 0.5099946
System.out.println((double) 1 - Math.pow((double) 1 - x, 2)); // 0.51
}
// 返回[0,1)的一个小数
// 任意的x,x属于[0,1),[0,x)范围上的数出现概率由原来的x调整成x平方
public static double xToXPower() {
return Math.max(Math.random(), Math.random());
}
// 返回[0,1)的一个小数
// 任意的x,x属于[0,1),[0,x)范围上的数出现概率由原来的x调整成x立方
public static double xToXCube() {
return Math.max(Math.random(), Math.max(Math.random(), Math.random()));
}
// 返回[0,1)的一个小数
// 任意的x,x属于[0,1),[0,x)范围上的数出现概率由原来的x调整成 1-(1-x)^2
public static double xToXOther() {
return Math.min(Math.random(), Math.random());
}
}
例3 从1~5随机到1~7随机
public class RandToRand {
// 此函数只能用,不能修改
// 等概率返回1~5
public static int f() {
return (int) (Math.random() * 5) + 1;
}
public static void main(String[] args) {
System.out.println("测试开始");
int testTimes = 10000000;
int count = 0;
for (int i = 0; i < testTimes; i++) {
if (f2() == 0) {
count++;
}
}
System.out.println("测试自己构造随机函数返回0的概率");
System.out.println((double) count / (double) testTimes); // 0.4999907
System.out.println("==========");
System.out.println("测试等概率生成1-7");
int[] counts = new int[8];
for (int i = 0; i < testTimes; i++) {
int num = g() ;
counts[num]++;
}
for (int i = 0; i < counts.length; i++) {
System.out.println(i + "这个数,出现了 " + counts[i] + " 次");
}
}
// lib里的,不能改!
public static int f1() {
return (int) (Math.random() * 5) + 1;
}
// 随机机制,只能用f1,
// 等概率返回0和1
public static int f2() {
int ans = 0;
do {
ans = f1();
} while (ans == 3);
return ans < 3 ? 0 : 1;
}
// 得到000 ~ 111 做到等概率 0 ~ 7等概率返回一个
public static int f3() {
return (f2() << 2) + (f2() << 1) + f2();
}
// 0 ~ 6等概率返回一个
public static int f4() {
int ans = 0;
do {
ans = f3();
} while (ans == 7);
return ans;
}
// 1~ 7等概率返回一个
public static int f5() {
return f4() + 1;
}
// 1~ 7等概率返回一个 优化版
public static int g() {
int ans = 0;
do {
ans = f3();
} while (ans == 0);
return ans;
}
}
例4 从a~b随机到c~d随机
例:从 [ 3 , 19 ] [3,19] [3,19] 随机等概率生成 [ 20 , 56 ] [20,56] [20,56] 等概率返回=> [ 3 , 10 ] [3,10] [3,10] 生成 0, [ 12 , 19 ] [12,19] [12,19] 生成 1 ,11 重来
[ 17 , 56 ] [17,56] [17,56] 等概率生成 => [ 0 + 17 , 39 + 17 ] [0 + 17,39 + 17] [0+17,39+17] 等概率生成 => [ 0 , 39 ] [0,39] [0,39] 等概率生成 +17 => 5 个 二进制位(均为生成 0 - 63),大于39重来.
public class RandToRand {
// 这个结构是唯一的随机机制
// 你只能初始化并使用,不可修改
public static class RandomBox {
private final int min;
private final int max;
// 初始化时请一定不要让mi==ma
public RandomBox(int mi, int ma) {
min = mi;
max = ma;
}
// 13 ~ 17
// 13 + [0,4]
public int random() {
return min + (int) (Math.random() * (max - min + 1));
}
public int min() {
return min;
}
public int max() {
return max;
}
}
// 利用条件RandomBox,如何等概率返回0和1
public static int rand01(RandomBox randomBox) {
int min = randomBox.min();
int max = randomBox.max();
// min ~ max
int size = max - min + 1;
// size是不是奇数,odd 奇数
boolean odd = (size & 1) != 0;
int mid = size / 2;
int ans = 0;
do {
ans = randomBox.random() - min;
} while (odd && ans == mid);
return ans < mid ? 0 : 1;
}
// 给你一个RandomBox,这是唯一能借助的随机机制
// 等概率返回from~to范围上任何一个数
// 要求from<=to
public static int random(RandomBox randomBox, int from, int to) {
if (from == to) {
return from;
}
// 3 ~ 9
// 0 ~ 6
// 0 ~ range
int range = to - from;
int num = 1;
// 求0~range需要几个2进制位
while ((1 << num) - 1 < range) {
num++;
}
// 我们一共需要num位
// 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0...
int ans = 0;
do {
ans = 0;
for (int i = 0; i < num; i++) {
ans |= (rand01(randomBox) << i);
}
} while (ans > range);
return ans + from;
}
}
例5 01不等概率随机到01等概率随机
public class RandToRand {
public static void main(String[] args) {
System.out.println("测试开始");
int testTimes = 10000000;
int count = 0;
int[] counts = new int[2];
for (int i = 0; i < testTimes; i++) {
int num = y();
counts[num]++;
}
for (int i = 0; i < 2; i++) {
System.out.println(i + "这个数,出现了 " + counts[i] + " 次");
}
}
// 你只能知道,x会以固定概率返回0和1,但是x的内容,你看不到!
public static int x() {
return Math.random() < 0.84 ? 0 : 1;
}
// 等概率返回0和1
public static int y() {
int ans = 0;
do {
ans = x();
} while (ans == x());
return ans;
}
}
四、对数器的使用
选择、冒泡、插入排序的对数器验证
public class Comp {
// 返回一个数组arr,arr长度[0,maxLen-1],arr中的每个值[0,maxValue-1]
public static int[] lenRandomValueRandom(int maxLen, int maxValue) {
int len = (int) (Math.random() * maxLen);
int[] ans = new int[len];
for (int i = 0; i < len; i++) {
ans[i] = (int) (Math.random() * maxValue);
}
return ans;
}
public static int[] copyArray(int[] arr) {
int[] ans = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ans[i] = arr[i];
}
return ans;
}
// arr1和arr2一定等长
public static boolean isSorted(int[] arr) {
if (arr.length < 2) {
return true;
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max > arr[i]) {
return false;
}
max = Math.max(max, arr[i]);
}
return true;
}
public static void main(String[] args) {
int maxLen = 5;
int maxValue = 1000;
int testTime = 10000;
for (int i = 0; i < testTime; i++) {
int[] arr1 = lenRandomValueRandom(maxLen, maxValue);
int[] tmp = copyArray(arr1);
selectionSort(arr1);
if (!isSorted(arr1)) {
for (int j = 0; j < tmp.length; j++) {
System.out.print(tmp[j] + " ");
}
System.out.println();
System.out.println("选择排序错了!");
break;
}
}
}
}
五、写在后面
欢迎关注,会经常记录一些算法学习中遇到的问题。
欢迎随时留言讨论,与君共勉,知无不答!
暑期编程PK赛 得CSDN机械键盘等精美礼品!


