位运算
Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。位运算符作用在所有的位上,并且按位运算。假设a = 60,b = 13;它们的二进制格式表示将如下:
A = 0011 1100
B = 0000 1101
有:
A&B = 0000 1100
A | B = 0011 1101
A ^ B = 0011 0001
~A= 1100 0011
运算规则:
- & 如果相对应位都是1,则结果为1,否则为0 (A&B),得到12,即0000 1100
- | 如果相对应位都是 0,则结果为 0,否则为 1 (A | B)得到61,即 0011 1101
- ^ 如果相对应位值相同,则结果为0,否则为1 (A ^ B)得到49,即 0011 0001
- 〜 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 (〜A)得到-61,即1100 0011
- << 按位左移运算符。左操作数按位左移右操作数指定的位数。 A << 2得到240,即 1111 0000
- [ >> ](带符号位右移)按位右移运算符。左操作数按位右移右操作数指定的位数。 A >> 2得到15即 1111
- [ >>> ](不带符号位右移)按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。A>>>2得到15即0000 1111
看一到题:实现一个函数:打印一个数的二进制形式(32位),通过测试,我们也发现一个数取反再+1等于这个数的相反数:(~n+1 = -n)
public class PrintBinary {
/**
* 逻辑:数字1 向左移几位,那此位置的二进制一定是1,利用这一点让num 与 其做&运算
* &运算逻辑:两者都是1结果才是1,有一个是0结果都是0
* 所以,让我们的目标数 和 左移的1 进行与运算,就得到此位置是0还是1
*
* 又因为,int类型在底层存储是32位,即下标为0~31,
* 所以我们用31~0的循环,打印出这个数 32个位置的二进制数
*/
public static void print(int num) {
for (int i = 31; i >= 0; i--) {
System.out.print((num & (1 << i)) == 0 ? "0" : "1");
}
System.out.println();
}
public static void main(String[] args) {
// int c = Integer.MIN_VALUE;
// int d = -c ;
//
// print(c);
// print(d);
// 测试 一个数取反再+1等于这个数的相反数
// ~n+1 = -n
int c = 5;
int d = -c;
print(c);
print(d);
print(~c);
print(~c+1);
}
}
//结果:
00000000000000000000000000000101
11111111111111111111111111111011
11111111111111111111111111111010
11111111111111111111111111111011
随机数
随机数的使用:Math.random() 等概率返回 [0,1) 的一个小数
看下面代码,实现了:等概率返回15,等概率得到0和1,等概率返回06
public class RandToRand {
// 此函数只能用,不能修改
// 等概率返回1~5
public static int f() {
return (int) (Math.random() * 5) + 1;
}
// 等概率得到0和1
public static int a() {
int ans = 0;
do {
ans = f();
} while (ans == 3);
return ans < 3 ? 0 : 1;
}
// 等概率返回0~6
public static int b() {
int ans = 0;
do {
// 这个等概率得到[0,7]
ans = (a() << 2) + (a() << 1) + a();
} while (ans == 7);
return ans;
}
}
题1: 从1-5随机 到 1-7随机
看完以上代码,看题:
从1-5随机 到 1-7随机,已知一个函数可以等概率返回[1,5]中的一个整数,请设计一个返回等概率返回[1,7]中的一个整数
再发散下:从a-b随机 到 c-d随机
解题步骤:
-
1、利用a-b随机 这个条件 实现一个函数f:等概率返回0,1
-
2、对f函数的返回值,运用位运算实现c-d随机
// lib里的,不能改! 等概率返回[1,5]中的一个整数,一直条件
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 g() {
return f4() + 1;
}
// 或者这种实现:基于f3返回:1 ~ 7等概率返回一个
public static int f4_2() {
// f3()实现了0 ~ 7等概率返回一个,所以如果遇到0就重做
int ans = 0;
do {
ans = f3();
} while (ans == 0);
return ans;
}
题2:已知一个函数等概率返回3-19,请你实现一个函数随机返回20-56。
再练习下吧:题2:已知一个函数等概率返回3-19,请你实现一个函数随机返回20-56。
解题:
- 利用3-19随机,实现一个函数f:等概率返回0,1。思路:3-19共有17个数,所以3-10返回0,12-19返回1,11就继续循环
- 在刚刚实现的 f() 的基础上,运用位运算的技巧实现20-56随机。思路:20-56随机,就是0-36随机再加上20,36和2的n次方相比较,36<64(2的6次方),所以我执行函数f() 6次可得到0-63随机,如果这个数大于36就重做。
代码:
public class RandToRand {
// 已知一个函数等概率返回3-19 [3,19]
public static int f1() {
return (int) (Math.random() * 17) + 3;
}
// 随机机制,只能用f1,
// 实现:等概率返回0和1
public static int f2() {
int ans = 0;
do {
ans = f1();
} while (ans == 11);
return ans < 11 ? 0 : 1;
}
// 利用位运算:得到000000 ~ 111111 做到等概率返回[0,63]
// 也就实现了0 ~ 63等概率返回一个
public static int f3() {
return (f2() << 5) + (f2() << 4) + (f2() << 3) + (f2() << 2) + (f2() << 1) + f2();
}
// 0 ~ 36等概率返回一个
public static int f4() {
int ans = 0;
do {
ans = f3();
} while (ans > 36);
return ans;
}
// 等概率得到 20~56
public static int g() {
return f4() + 20;
}
public static void main(String[] args) {
int testTimes = 10000000;
int[] counts = new int[57];
for (int i = 0; i < testTimes; i++) {
int num = g();
counts[num]++;
}
for (int i = 0; i < 57; i++) {
System.out.println(i + "这个数,出现了 " + counts[i] + " 次");
}
}
}
// 测试结果:随机返回20~56, 等概率返回[20,56]中的一个数
0这个数,出现了 0 次
1这个数,出现了 0 次
2这个数,出现了 0 次
3这个数,出现了 0 次
4这个数,出现了 0 次
5这个数,出现了 0 次
6这个数,出现了 0 次
7这个数,出现了 0 次
8这个数,出现了 0 次
9这个数,出现了 0 次
10这个数,出现了 0 次
11这个数,出现了 0 次
12这个数,出现了 0 次
13这个数,出现了 0 次
14这个数,出现了 0 次
15这个数,出现了 0 次
16这个数,出现了 0 次
17这个数,出现了 0 次
18这个数,出现了 0 次
19这个数,出现了 0 次
20这个数,出现了 270951 次
21这个数,出现了 270111 次
22这个数,出现了 270425 次
23这个数,出现了 270275 次
24这个数,出现了 269053 次
25这个数,出现了 270534 次
26这个数,出现了 270461 次
27这个数,出现了 270523 次
28这个数,出现了 270013 次
29这个数,出现了 270291 次
30这个数,出现了 269515 次
31这个数,出现了 270477 次
32这个数,出现了 270468 次
33这个数,出现了 270088 次
34这个数,出现了 270316 次
35这个数,出现了 269305 次
36这个数,出现了 270113 次
37这个数,出现了 270623 次
38这个数,出现了 270777 次
39这个数,出现了 270154 次
40这个数,出现了 271016 次
41这个数,出现了 269814 次
42这个数,出现了 269498 次
43这个数,出现了 270216 次
44这个数,出现了 270542 次
45这个数,出现了 269779 次
46这个数,出现了 270281 次
47这个数,出现了 270366 次
48这个数,出现了 270299 次
49这个数,出现了 270331 次
50这个数,出现了 269878 次
51这个数,出现了 270756 次
52这个数,出现了 270731 次
53这个数,出现了 270644 次
54这个数,出现了 271271 次
55这个数,出现了 269483 次
56这个数,出现了 270622 次
题3: 01不等概率随机 到 01等概率随机
已知 函数x()会以固定概率(不一定是等概率)返回0和1,请实现一个函数:等概率返回0和1
public class EqualProbabilityRandom {
// 你只能知道,x会以固定概率(不一定是等概率)返回0和1,但是x的内容,你看不到!
public static int x() {
return Math.random() < 0.84 ? 0 : 1;
}
// 使用x函数实现:等概率返回0和1
/**
* 因为x函数是固定概率(不一定是等概率)返回0和1,假设0的概率位p,那1的概率是1-p
* roll两次x函数,会得到四种可能:00、11、01、10
* 其中得到10、01的概率是相等的
*/
public static int y() {
int ans = 0;
do {
ans = x();
} while (ans == x());
return ans;
}
public static void main(String[] args) {
int testTimes = 10000000;
int[] counts = new int[3];
for (int i = 0; i < testTimes; i++) {
int num = y();
counts[num]++;
}
for (int i = 0; i < 3; i++) {
System.out.println(i + "这个数,出现了 " + counts[i] + " 次");
}
}
}
// 测试结果:
0这个数,出现了 5001910 次
1这个数,出现了 4998090 次
2这个数,出现了 0 次