ARTS-第16周

ARTS (第16周)

总是在拼命的学习,不动手其实永远不会。

之前学习了跳表,当初以为会了,就没去写。

最近看到索引的知识想实现个跳表看看,但实际写起来却发现东缺西缺,问题多多。

直到我在次复习了跳表的过程和思想,找了一些别的资料,才写好跳表。

本周:

跳表、Huffman编码、分治算法,回溯算法(穷举)

Algorithm 算法

跳表

本质来说就是跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。

具体内容如下:代码里都有注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package search;

import java.util.Random;

import search.SkipList.Node;

/**
*/
public class SkipList {
public static void main(String[] args) throws Exception {
SkipList sl = new SkipList();
for (int i = 0; i < 10; i++) {
Thread.sleep(8);
sl.insert(i);
}
sl.printAll();
for (int i = 0; i < 11; i++) {
Node find = sl.find(i);
System.out.println(find !=null);
}
}

private static final int MAX_LEVEL = 16;

private int levelCount = 1;

private Node head = new Node(); // 带头链表

private Random r = new Random();

public Node find(int value) {
// 从最高层节点开始查找
// 每层都是找比当前节点小的最后一个值 直到找到
Node p = head;
for (int i = levelCount - 1; i >= 0; i--) {
while (p.next[i] != null && p.next[i].data < value) {
p = p.next[i];
}
}
// 因为找的是比当前节点小的最后一个节点
// 所以找到节点的下一个节点的值如果和匹配值相等就是找到了
if (p.next[0] != null && p.next[0].data == value) {
return p.next[0];
}
return null;

}

public void insert(int value) {
Node n = new Node();
n.data = value;
int level = randomLevel();
Node[] upd = new Node[level];
// 极客时间里的代码 有将upd全部初始化为根节点 我这里没有 TODO

Node p = head;
// 从最高层到最低层匹配就不用每次都重设P的值
// 因为最高层有的低层一定有 而高层没有的低层可能有
// 所以高层匹配完了 进行下一层的匹配的时候 从高层的位置开始匹配就行了
for (int i = level - 1; i >= 0; i--) {

// 这里的判断是 如果有下一个节点 并且下一个节点值更大
// 就是直到找到比当前值小的最后节点 即当前节点的前一个节点
// 假设已有1 3 节点 现在插入2节点
// 那么从head开始 head存在下一个节点值是1,1比2大,所以判断成功进入下一次
// 那么p就重设为1节点,继续判断,1节点的下一个节点是3,也2大,所以结束,
// 那么结果P节点就是1节点,也就是说upd[i]=1节点,就是说1节点是新增的2节点的前一个节点
while (p.next[i] != null && p.next[i].data < value) {
// 写的时候感觉也可以找比当前值小的最后个节点 即当前节点的前一个节点
// 想了想发现这样可能每次都得重设P的值就没这么写了
p = p.next[i];
}
upd[i] = p;// 当前节点的前一个节点 都没有就是head节点
}
// 根据upd 将新节点插入到指定的位置
for (int i = 0; i < level; i++) {
n.next[i] = upd[i].next[i];
upd[i].next[i] = n;
}
if (levelCount < level) {
// 高度更高了
levelCount = level;
}

}

public void delete(int value) {
Node p = head;
for (int i = levelCount - 1; i >= 0; i--) {
// 一样是找比当前节点小的最后一个节点
while (p.next[i] != null && p.next[i].data < value) {
p = p.next[i];
}
// 找到了就删除节点
if (p.next[i] != null && p.next[i].data == value) {
p.next[i] = p.next[i].next[i];
}
}

}

// 随机 level 次,如果是奇数层数 +1,防止伪随机
private int randomLevel() {
int level = 1;
for (int i = 1; i < MAX_LEVEL; ++i) {
if (r.nextInt() % 2 == 1) {
level++;
}
}

return level;
}

public void printAll() {
for (int i = levelCount; i >= 0; i--) {
Node p = head;
while (p.next[i] != null) {
System.out.print(p.next[i].data + " ");
p = p.next[i];
}
System.out.println();
}
}

public class Node {
private int data = -1;
private Node next[] = new Node[MAX_LEVEL];
private int maxLevel = 0;

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{ data: ");
builder.append(data);
builder.append("; levels: ");
builder.append(maxLevel);
builder.append(" }");

return builder.toString();
}
}

}

Huffman编码

哈夫曼编码可以应用于压缩文件,以UTF-8编码为例,一个字母所占的空间为1个字节(即8个位)
而使用哈夫曼编码,可以根据字母出现频率统计出哈夫曼编码,字母出现的最多的占1个位,第二多的占2个位,以此类推。
然后再利用统计出来的哈夫曼编码对数据进行压缩处理。这里我写了哈夫曼编码的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package greed;

import java.util.Collections;
import java.util.LinkedList;
import java.util.Random;

public class Huffman {
static LinkedList<Node> p;
// "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String CHARS = "abcdef";//// 随机字符串池

public static void main(String[] args) throws Exception {
String str = generateString(50);
System.out.println("源字符串" + str);
init(str);
build(p);

for (Node n : p) {
System.out.println(n.text + ":出现次数:" + n.count + ",0的数量:" + n.zeroCount + ",编码:" + n.code);
}

}
/**
* 随机字符串
*
* @author lfy
* @param length
* @return
*/
public static String generateString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(CHARS.charAt(random.nextInt(CHARS.length())));
}
return sb.toString();
}
public static void init(String str) {
//
p = new LinkedList<>();
/*
* Node n1 = new Node('a', 0); Node n2 = new Node('b', 0); Node n3 = new
* Node('c', 0); Node n4 = new Node('d', 0); Node n5 = new Node('e', 0);
* Node n6 = new Node('f', 0); p.add(n1); p.add(n2); p.add(n3);
* p.add(n4); p.add(n5); p.add(n6);
*/
for (int i = 0; i < CHARS.length(); i++) {
Node n1 = new Node(CHARS.charAt(i), 0);
p.add(n1);
}
char[] array = str.toCharArray();
for (int i = 0; i < array.length; i++) {
int idx = array[i] - 'a';
p.get(idx).count++;
}

}

public static void build(LinkedList<Node> p) {
Collections.sort(p);
Node m = new Node(p.get(0), p.get(1));
// 全部遍历过去
for (int i = 2; i < p.size(); i++) {
Node min = p.get(i);
m = new Node(m, min);
}
toCode(m);
}

public static void toCode(Node head) {
LinkedList<Node> queue = new LinkedList<>();
queue.add(head);
head.code = "";
head.zeroCount = 0;
// 构建string类型的
while (queue.size() > 0) {
Node n = queue.remove();
Node left = n.left;
Node right = n.right;
if (left != null) {
left.code = n.code + "0";
left.zeroCount = n.zeroCount + 1;
queue.add(left);
}
if (right != null) {
right.code = n.code + "1";
right.zeroCount = n.zeroCount;
queue.add(right);
}
}

}

static class Node implements Comparable<Node> {
char text;
int count;
String code;
Node left;
Node right;
int zeroCount;// 0的数量

public int compareTo(Node o) {
// 小的靠前
if (this.count > o.count)
return 1;
return -1;
}

public Node(Node l, Node r) {
this.left = l;
this.right = r;
this.count = l.count + r.count;

}

public Node(char text, int count) {
super();
this.text = text;
this.count = count;
}

@Override
public String toString() {
return "{text:'" + text + "', count:'" + count + "', code:'" + code + "', left:'" + left + "', right:'"
+ right + "'}";
}

}


}

分治算法

分治算法,顾名思义,核心思想就是分而治之。

这里我做了个求逆序度的题

这个解法本质来说就是归并排序,统计归并排序过程中出现的逆序次数,将这些逆序次数统计在一起,那么结果就是逆序度了。(ps:归并排序也是一种分治算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package divideandconquer;

import java.util.Arrays;

public class OrderPair {

public static void main(String[] args) {
// 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2
int[] arr = {};
arr = new int[] { 1, 2, 3, 4, 5, 6 };
arr = new int[] { 3, 4, 1, 2 };
arr = new int[] { 6, 5, 4, 3, 2, 1 };
arr = new int[] { 1, 2, 3, 4, 5, 6, 7 };
int len = arr.length;
System.out.println("=====逆序度=====");
System.out.println("数组长度:"+len + ",满逆序度:" + len * (len - 1) / 2);
int a = count(arr, len);
System.out.println(Arrays.toString(arr));
arr = new int[] { 1, 2, 3, 4, 5, 6 };
arr = new int[] { 3, 4, 1, 2 };
arr = new int[] { 6, 5, 4, 3, 2, 1 };
arr = new int[] { 1, 2, 3, 4, 5, 6, 7 };
int b = getOrderPari(arr, 0, arr.length - 1, 0);
System.out.println(Arrays.toString(arr));
System.out.println(a + ":" + b);
}

private static int num = 0; // 全局变量或者成员变量

public static int getOrderPari(int[] a, int s, int e, int n) {
if (s >= e) {
return n;
}
int m = (e + s) / 2;
n = getOrderPari(a, s, m, n);
n = getOrderPari(a, m + 1, e, n);
n = mergePari(a, s, m, e, n);
return n;
}

public static int mergePari(int[] a, int s, int m, int e, int n) {
int[] temp = new int[e - s + 1];//
for (int i = 0; i < temp.length; i++) {
temp[i] = a[s + i];
}
int i = s;// 主串坐标
int p1 = 0;// 前半段指针
int e1 = m - s;//
int p2 = e1 + 1;// 后半段指针
int e2 = temp.length - 1;
while (p1 <= e1 && p2 <= e2) {
// 前半段的更小的时候
// 即有序的时候
if (temp[p1] <= temp[p2]) {
a[i++] = temp[p1++];
} else {
// 前半段指针小的时候 先放后半段的
a[i++] = temp[p2++];

// 累加逆序数量
// 每次进入的时候判断 前半个数组还有几个未计算的 就是后半个数组当前数字逆序的数量
// 因为前半个数组如果还有剩余 就代表着后面的数组比前面的剩余数组里的数都要更小
// 因此这里每次计算的都是前半个数组 比后半个数组大的有几个
// 前半段剩余的数量就是(前半段结束指针-前半段指针 + 1),进行累加即可
//
n += e1 - p1 + 1;

}

}
while (p1 <= e1) {
a[i++] = temp[p1++];
}
while (p2 <= e2) {

a[i++] = temp[p2++];
}
return n;
}

public static int count(int[] a, int n) {
num = 0;
mergeSortCounting(a, 0, n - 1);
return num;
}

private static void mergeSortCounting(int[] a, int p, int r) {
if (p >= r)
return;
int q = (p + r) / 2;
mergeSortCounting(a, p, q);
mergeSortCounting(a, q + 1, r);
merge(a, p, q, r);
}

private static void merge(int[] a, int p, int q, int r) {
int i = p, j = q + 1, k = 0;
int[] tmp = new int[r - p + 1];
while (i <= q && j <= r) {
if (a[i] <= a[j]) {
tmp[k++] = a[i++];
} else {
// num ++;
num += (q - i + 1); // 统计 p-q 之间,比 a[j] 大的元素个数
tmp[k++] = a[j++];
}
}
while (i <= q) { // 处理剩下的
tmp[k++] = a[i++];
}
while (j <= r) { // 处理剩下的
tmp[k++] = a[j++];
}
for (i = 0; i <= r - p; ++i) { // 从 tmp 拷贝回 a
a[p + i] = tmp[i];
}
}

}

回溯算法(穷举)

即穷举出所有的情况,根据要求得出需要的结果。

内容如下:

1、8皇后问题

2、0-1背包问题

3、简易的正则匹配(只匹配?和*)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package back;

import java.util.Arrays;
import java.util.Random;
//回溯算法
public class Back {
public static void main(String[] args) {
queueStart();
test01Package();
testPattern();
}

private static void testPattern() {
int err = 0;
int times = 55555;
int i = 0;
for (; i < times; i++) {
String t = generateString(10);
String p = generatePattern(5);
boolean r1 = pattern(t, p);
boolean r2 = pattern3(t, p);
System.out.println(i + " =====>");
System.out.println("text:" + t);
System.out.println("pattern:" + p);
System.out.println(r1 + ":" + r2);
if (r1 != r2) {
err++;
System.exit(1);
}
}

System.out.println("err:" + err);
}

// "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String CHARS = "abcdef";//// 随机字符串池

/**
* 随机字符串
*
* @author lfy
* @param length
* @return
*/
public static String generateString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(CHARS.charAt(random.nextInt(CHARS.length())));
}
return sb.toString();
}

private static final String CHARS2 = "ab?*";//// 随机字符串池

public static String generatePattern(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(CHARS2.charAt(random.nextInt(CHARS2.length())));
}
return sb.toString();
}

static boolean match = false;

public static boolean pattern(String t, String p) {
match = false;
pattern(t.toCharArray(), p.toCharArray(), 0, 0);
return match;
}

public static boolean pattern3(String t, String p) {
return pattern3(t.toCharArray(), p.toCharArray(), 0, 0);
}

// 简易正则 只匹配*和?
public static void pattern(char[] t, char[] p, int i, int j) {
if (match) {
return;
}
if (j == p.length) {
if (i == t.length) {
match = true;
}
return;
}
// 单字符或无字符
if (p[j] == '?') {
pattern(t, p, i + 1, j + 1);
pattern(t, p, i, j + 1);

} else if (p[j] == '*') {
// 0~无限任意字符
for (int x = 0; x <= t.length - i; x++) {
pattern(t, p, i + x, j + 1);
}

} else if ((i < t.length) && p[j] == t[i]) {
// 固定字符
pattern(t, p, i + 1, j + 1);
}

}

// 简易正则 只匹配*和?
public static boolean pattern3(char[] t, char[] p, int i, int j) {
if (j == p.length) {
if (i == t.length) {
//符合的情况
return true;
}
//不符合的情况
return false;
}
// 单字符或无字符
if (p[j] == '?') {
//有一个匹配就return true
if(pattern3(t, p, i , j + 1)||pattern3(t, p, i+ 1, j + 1)){
return true;
}
} else if (p[j] == '*') {
// 0~无限 任意长度字符放入匹配 成功就return true结束
for (int x = 0; x <= t.length - i; x++) {
if(pattern3(t, p, i + x, j + 1)){
return true;
}
}

} else if ((i < t.length) && p[j] == t[i]) {
// 固定字符
// 匹配就return true
if(pattern3(t, p, i + 1, j + 1)){
return true;
}
}
return false;
}

// 0-1背包
public static void test01Package() {
int times = 55555;
int err = 0;
int[] a = new int[13];
for (int j = 0; j < times; j++) {
maxPackage = 0;
maxPackage2 = 0;
for (int i = 0; i < a.length; i++) {
a[i] = (int) (Math.random() * 10);
}
// a = new int[] { 2, 9, 7 };
int max = (int) (Math.random() * 100 + 50);
// int max = 10;

System.out.println("===============");
System.out.println("current:" + j);
System.out.println(max + ":" + Arrays.toString(a));
fillPackage2(max, 0, a, 0);
fillPackage(max, 0, a, 0);
System.out.println("maxp2:" + maxPackage2);
System.out.println("maxP1:" + maxPackage);
if (maxPackage2 != maxPackage) {
err++;
System.exit(0);
}
}
//
System.out.println("err:" + err);
System.out.println("method1:" + t1);
System.out.println("method2:" + t2);
}

static int maxPackage2 = 0;
static int maxPackage = 0;
static int t1 = 0;
static int t2 = 0;

/**
*
* @param m
* 背包最大数量
* @param c
* 当前重量
* @param it
* 物品重量列表
* @param i
* 当前第几个物品
*/
public static void fillPackage2(int m, int c, int[] it, int i) {
t2++;
int len = it.length;
if (c == m || i >= len) {
if (c > maxPackage2)
maxPackage2 = c;
return;
}
for (int j = i; j < it.length; j++) {
if (it[j] + c <= m) {
// 当作当前物品已经放入,然后进入下一个物品
fillPackage2(m, c + it[j], it, j + 1);
} else {
// 不能放入就不累加重量,然后进入下一个物品
fillPackage2(m, c, it, j + 1);
}
}

}

/**
*
* @param m
* 背包最大数量
* @param c
* 当前重量
* @param it
* 物品重量列表
* @param i
* 当前第几个物品
*/
public static void fillPackage(int m, int c, int[] it, int i) {
t1++;
int len = it.length;
if (c == m || i == len) {
if (c > maxPackage)
maxPackage = c;
return;
}
// 把下一个物品当前当前填充的物品填充进去
// 即
// 第一次运行的时候 把第1个物品当作首个物品放进去
// 第二次运行的时候 把第2个物品当作首个物品放进去
fillPackage(m, c, it, i + 1);
// 判断当前物品是否能放 能防就放
// 不能放结束 即此路已经走到终点
if (it[i] + c <= m) {
// 当作当前物品已经放入,然后放入下一个物品
fillPackage(m, c + it[i], it, i + 1);
}
}

///////////////
// 8皇后
///////////////
static int[] q = new int[8];
static int qc = 0;

public static void queueStart() {
for (int i = 0; i < q.length; i++) {
q[i] = -1;
}
queue8(0);
System.out.println("数量:" + qc);
}

// 8皇后
public static void queue8(int r) {
if (r == 8) {
// 符合
qc++;
printQueue();
return;
}
for (int i = 0; i < 8; i++) {
//if (isOk(r, i)) {
if (isOk2(r, i)) {
q[r] = i;
queue8(r + 1);
}

}

}

private static void printQueue() {

System.out.println("===============");
for (int i = 0; i < q.length; i++) {
for (int j = 0; j < q.length; j++) {
if (q[i] == j) {
System.out.print("* ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
}

// 行号和索引 从第一行开始
public static boolean isOk2(int row, int idx) {
// 最大直到当前行
for (int i = 0; i < row; i++) {
// row - i 行号差
int l = idx - (row - i);//
int r = idx + (row - i);//
// 上下
if (q[i] == idx)
return false;
// 左
if (l >= 0 && q[i] == l) {
return false;
}
// 右
if (l <= 8 && q[i] == r) {
return false;
}
}

return true;
}

// 行号和索引 从当前行的前一行开始
public static boolean isOk(int row, int idx) {
int l = idx - 1;
int r = idx + 1;
// 从当前行到首行
for (int i = row - 1; i >= 0; i--) {
if (q[i] == idx)
return false;
if (l >= 0 && q[i] == l)
return false;
if (r < 8 && q[i] == r) {
return false;
}
l--;
r++;
}
return true;

}
}

Review 英文文章

https://redis.io/documentation

https://redis.io/modules

redis的介绍文档、模块,常见问题等。

Tip 技巧

极客时间的专栏《数据结构与算法之美》

本周学习了该专栏中的38-39篇

内容如下:

分治算法:将大问题拆成小问题,再由小问题的结果组合成大问题,比较适合用递归的方式写。

回溯算法:穷举一切可能,以此来发现最好的结果,比较适合用递归的方式写。

Share 分享

https://blog.csdn.net/ityouknow/article/details/94685167

如何写出让同事无法维护的代码

https://blog.csdn.net/epubit17/article/details/89449589

如何写出让同事膜拜的漂亮代码