前言:
最近在看左神的算法课,因为我数据结构和算法非常薄弱,几乎是纯小白,所以我是从新手版开始看的~~在这里做个笔记,方便自己记忆。
文章索引:
前言:
首先先来实现一道简单的算法题,把一个整数打印32位状态(32位信息)。
关于&:
关于反码和补码
取反
负数为什么要取反+1?
右移
相反数的其他表达方式
所谓算法就是
算法的分类
为什么图灵是我们的祖师爷?
题目一 N的阶乘
题目二 选择排序
冒泡排序
冒泡排序和选择排序的区别:
插入排序:
首先先来实现一道简单的算法题,把一个整数打印32位状态(32位信息)。
大家都知道,我们平时用的都是10进制,我们指的整型就是我们常用的十进制的数字,比如1,2,3,我们看着是1、2、3其实他们是十进制的1、2、3。
在计算机底层,整型都是用2进制保存的,是用32位信息储存的。(long类型是用64位储存的)
32位信息的来源是指在计算机储存中用四个字节储存,每个字节是8位二进制,四个数一共是32位。
先看实现代码,然后再讲原理:
public class Code01_PrintBinary {
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) {
// 32位
int num = 4;
print(num)
}
}
打印出来的结果:
00000000000000000000000000000100
从右侧开始是第0~31位信息,
任何一个整数在计算机底层都是用32位信息存储的,在java中的整型都指的是有符号的整型,也就是1、2、3和-1、-2、-3这类的,所以他的最左边的那一位就是符号位,代表正数和负数。0代表正数,1代表负数。
我们看到的10进制数字其实是做了10进制定制,好让我们看到10进制的数字。
那么怎么计算一个整数的32位信息呢?
那么怎么知道每一位是0还是1呢?
就是要用num与1与左移i位进行与操作,如果两者不相同则会返回0,如果两者相同则会返回两者的数字,所以这里运用了一个三元运算符:
(num & (1 << i)) == 0 ? "0" : "1"
首先说明一下,与操作!=and! 与操作不仅是逻辑运算符,还是位运算符,会把左右两侧的数字转换为二进制进行运算。
比如上面这个例子,num=4时,第一次的i从31出发,不断--,i和num都转化成二进制进行对比,只有当两者二进制重合是1时才会留下来,在该位写入“1”,否则将写入0,这样位位对比,终于得出最终结果。
关于&:
12&5 的值是多少?答:12转成二进制数是1100(前四位省略了),5转成二进制数是0101,则运算后的结果为0100即4 这是两侧为数值时;
关于反码和补码
刚刚我们讲了,在java中用32位信息来表示一个整数,那么表示的范围就是-
~
-1。
为什么负数可以表示2的31次方,而正数却要减一呢?前面我们说了,第一位是符号位,如果第一位是1的话代表负数,也就是说负数可以用32位的数来表示,而正数不行,因为正数有个0!
0要占一位!而负数没有0!他可以取反之后加个1!(详情看后面的取反)
正数的表示是第一位是0,后面的2的0次方、2的1次方这样以此类推加起来。
而负数的表示是第一位是1,值就是后面的状态取反+1
比如-1的信息:
11111111111111111111111111111111
可以看到后面都是1,1取反就是0,再+1就等于1。
系统最小,表示出来就是:
10000000000000000000000000000000
首先第一位符号位1代表负数,后面的位都是0,0取反就是1,结果就是-
取反
取反的符号是~,所谓取反就是0的地方写1,1的地方写0,如果是负数,就需要取反之后在+1
比如:
int b = 123823138;
int c = ~b;
print(b);
print(c);
结果:
00000111011000010110010000100010
11111000100111101001101111011101
因为1比较瘦所以看起来短了一截 - -。实际上都是32位。
负数为什么要取反+1?
所有的+ - * / 这些符合 在计算机底层都是用位运算来实现的,而设计这套系统的人不想正数负数的加减乘除要分别写两套算法,他只想写一套,定义成取反+1就可以在+ - * /时使用同一套算法就能实现。
右移
右移有两个,
>>带符号右移 拿符号位来补
>>>不带符号右移 拿0来补
相反数的其他表达方式
我们都知道在正数前面加个-,就会拿到负数。
而我们还可以取反+1,也可以得到相同的结果。
不管是正数还是负数都可以用这一套逻辑计算。
系统最小取反之后还是他自己。
因为系统最小是10000000000000……
取反之后变成0111111111111111……再加上1又回去了,跟之前一模一样,所以系统最小还是他自己。
0取反之后还是他自己,因为0的二进制表达是000000000000000000……
取反之后是111111111111111111111111111
然后再加1,就是1000000000000000……(因为溢出了就不要了,所以还是他自己 哈哈哈嗝)
所谓算法就是
- 有具体的问题
- 有假设及解决这个问题的具体流程
- 有评价处理流程的可量化指标
算法的分类
算法的分类特别多,大体上可以分为两类,
- 明确知道怎么算的流程(比如,负数就是取反+1,比如1+1=2)
- 明确知道怎么尝试的流程(比如一个数的所有因子,我不知道怎么算,但我可以一个一个试出来)
为什么图灵是我们的祖师爷?
在计算机之前,有很多计算器,在图灵之前所有的计算器都有一个特点,我知道他怎么算,只是用机器来说更快。
是图灵破解出怎么试出来,才奠定了计算机的基础。
所谓的图灵机就是表示把一个不知道怎么算只知道怎么试的东西给他变成理论。
题目一 N的阶乘
给定一个参数N,返回:1!+2!+3!4+!+……+N!的结果。
算法是不需要语言的,会伪代码就可以。
首先说一下这个符号!叫阶乘。
阶乘(factorial)是基斯顿·卡曼(Christian Kramp, 1760 – 1826)于1808年发明的运算符号。阶乘,也是数学里的一种术语。阶乘指从1乘以2乘以3乘以4一直乘到所要求的数。例如所要求的数是4,则阶乘式是1×2×3×4,得到的积是24,24就是4的阶乘。 例如所要求的数是6,则阶乘式是1×2×3×……×6,得到的积是720,720就是6的阶乘。例如所要求的数是n,则阶乘式是1×2×3×……×n,设得到的积是x,x就是n的阶乘。
哈哈哈,有两种方法,先算一的阶乘,再算二的阶乘,再算三的阶乘……以此类推加起来。
第二种方法是先算1的阶乘,然后用1的阶乘*2就算出2的阶乘,然后用2的阶乘*3就算出3的阶乘……以此类推。
虽然我们还没有学到时间复杂度,但是能明显感觉出来,第二种方法比第一种要更好,节约时间/资源。
两者代码:
public class Code02_SumOfFactorial {
public static long f1(int N) {
long ans = 0;
for (int i = 1; i <= N; i++) {
ans += factorial(i);
}
return ans;
}
public static long factorial(int N) {
long ans = 1;
for (int i = 1; i <= N; i++) {
ans *= i;
}
return ans;
}
public static long f2(int N) {
long ans = 0;
long cur = 1;
for (int i = 1; i <= N; i++) {
cur = cur * i;
ans += cur;
}
return ans;
}
public static void main(String[] args) {
int N = 10;
System.out.println(f1(N));
System.out.println(f2(N));
}
}
题目二 选择排序
给你一个数组从小到大排序。
所谓选择排序就是:
0~N-1选除最小值放到0位置
1~N-1选除最小值放到1位置
2~N-1选除最小值放到2位置
……
以此类推
public class Code03_Sort {
public static void swap(int[] arr, int i, int j) {
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
public static void selectSort(int[] arr) {
//如果是空或者小于2就是 一个数不需要排序
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
for (int i = 0; i < N; i++) {
int minValueIndex = i;
for (int j = i + 1; j < N; j++) {
minValueIndex = arr[j] < arr[minValueIndex] ? j : minValueIndex;
}
swap(arr, i, minValueIndex);
}
}
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
for (int end = N - 1; end >= 0; end--) {
for (int second = 1; second <= end; second++) {
if (arr[second - 1] > arr[second]) {
swap(arr, second - 1, second);
}
}
}
}
public static void insertSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
for (int end = 1; end < N; end++) {
int newNumIndex = end;
while (newNumIndex - 1 >= 0 && arr[newNumIndex - 1] > arr[newNumIndex]) {
swap(arr, newNumIndex - 1, newNumIndex);
newNumIndex--;
}
}
}
public static void insertSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
for (int end = 1; end < N; end++) {
for (int pre = end - 1; pre >= 0 && arr[pre] > arr[pre + 1]; pre--) {
swap(arr, pre, pre + 1);
}
}
}
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 = { 7, 1, 3, 5, 1, 6, 8, 1, 3, 5, 7, 5, 6 };
printArray(arr);
insertSort2(arr);
printArray(arr);
}
}
冒泡排序
[A B C D E F G……N]
先用A和B进行比较,如果A比B大,则位置进行交换,再用B和C进行比较,B比C大则进行交换,直到N,此时N位置上的一定是数组最大值,所以就不用管了。
然后在进行重复,将A~N-1范围重复上述过程,交换完后N-1位置上的上的一定是当前数组最大值,不断重复后就得到了一个有序列表。
代码实现:
package class01;
import java.util.Arrays;
public class Code05_BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int end = arr.length - 1; end > 0; end--) {
for (int i = 0; i < end; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
// 交换arr的i和j位置上的值
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
bubbleSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
bubbleSort(arr);
printArray(arr);
}
}
冒泡排序和选择排序的区别:
冒泡每次都是2 2交换,以此类推……
选择排序只是找到一个最小的,直接与0位置交换,以此类推……
插入排序:
[A B C D E F G……N]
插入排序顺序:
0-0 首先先看一个A的位置,此时就一个数,所以肯定有序
0-1 由于0-0已经有序了,此时只要再看1位置上的B,用B和A进行比较,如果B比A小则进行交换。
0-2 由于0-1位置已经有序了,此时只需要看2位置上的C,此时C跟B进行比较,如果比B小则进行交换,交换完后,此时交换后的B在于A进行比较,如果比A还小,则再进行交换,再往前看,由于没有数了就到此停止。
0-3 由于0-2位置已经有序了,此时只需要看3位置上的D,此时的D跟C进行比较,如果比C小则交换,不比他小则停止……跟上面步骤一样,以此类推……
0-4……
为什么叫插入排序呢,就像我们平时玩斗地主,手里的牌已经有序了,只需要一一比对,插入到合适的位置。
代码实现:
package class01;
import java.util.Arrays;
public class Code06_InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
// i和j,数交换
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
// Math.random() -> [0,1) 所有的小数,等概率返回一个
// Math.random() * N -> [0,N) 所有小数,等概率返回一个
// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个
int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; // 长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random())
- (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100; // 随机数组的长度0~100
int maxValue = 100;// 值:-100~100
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
insertionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
// 打印arr1
// 打印arr2
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}