# 第8节 前缀树、不基于比较的排序、排序稳定性

# 前缀树(prefix tree & trie)

  • 单个字符串中,字符从前到后的加到一棵多叉树上
  • 字符放在路上,节点上有专属的数据项(常见的是pass和end值)
  • 所有样本都这样添加,如果没有路就新建,如有路就复用
  • 沿途节点的pass值增加1,每个字符串结束时来到的节点end值增加1
  • 可以完成前缀相关的查询

例子

设计一种结构。用户可以:

  • void insert(String str) 添加某个字符串,可以重复添加,每次算1个
  • int search(String str) 查询某个字符串在结构中还有几个
  • void delete(String str) 删掉某个字符串,可以重复删除,每次算1个
  • int prefixNumber(String str) 查询有多少个字符串,是以str做前缀的

# 前缀树路的实现方式

  • 固定数组实现

  • 哈希表实现

    PS:我们实际来一把,对数器帮你找到bug的展示

package class08;

import java.util.HashMap;

/**
 * 	前缀树
 * @author yangyanchao
 *
 */
public class Test01_TrieTree {
	
	/**
	 * 	固定数组实现的前缀树节点信息
	 * @author yangyanchao
	 *
	 */
	public static class Node1 {
		public int pass;
		public int end;
		public Node1[] nexts;
		
		public Node1() {
			this.pass = 0;
			this.end = 0;
			this.nexts = new Node1[26];
		}
	}
	
	/**
	 * 	前缀树--固定数组实现
	 * @author yangyanchao
	 *
	 */
	public static class Trie1 {
		/**
		 * 	前缀树根
		 */
		private Node1 root;
		public Trie1() {
			this.root = new Node1();
		}
		
		/**
		 * 	添加某个字符串,可以重复添加,每次算1个
		 * @param str
		 */
		public void insert(String str) {
			if(str == null || str.length() == 0) {
				return;
			}
			char[] charArrs = str.toCharArray();
			Node1 cur = this.root;
			cur.pass++;
			int path = 0;
			for(int i = 0; i < charArrs.length; i++) {
				path = charArrs[i] - 'a';
				if(cur.nexts[path] == null) {
					cur.nexts[path] = new Node1();
				}
				cur = cur.nexts[path];
				cur.pass++;
			}
			cur.end++;
		}
		
		/**
		 * 	查询某个字符串在结构中还有几个
		 * @param str
		 * @return
		 */
		public int search(String str) {
			if(str == null || str.length() == 0) {
				return 0;
			}
			char[] charArrs = str.toCharArray();
			Node1 cur = this.root;
			int path = 0;
			for(int i = 0; i < charArrs.length; i++) {
				path = charArrs[i] - 'a';
				if(cur.nexts[path] == null) {
					return 0;
				}
				cur = cur.nexts[path];
			}
			return cur.end;
		}
		
		/**
		 * 	删掉某个字符串,可以重复删除,每次算1个
		 * @param str
		 */
		public void delete(String str) {
			if(this.search(str) != 0) {
				char[] charArrs = str.toCharArray();
				Node1 cur = this.root;
				cur.pass--;
				int path = 0;
				for(int i = 0; i < charArrs.length; i++) {
					path = charArrs[i] - 'a';
					if(--cur.nexts[path].pass == 0) {
						cur.nexts[path] = null;
						return;
					}
					cur = cur.nexts[path];
				}
				cur.end--;
			}
		}
		
		/**
		 * 	查询有多少个字符串,是以str做前缀的
		 * @param str
		 * @return
		 */
		public int prefixNumber(String str) {
			if(str == null || str.length() == 0) {
				return 0;
			}
			char[] charArrs = str.toCharArray();
			Node1 cur = this.root;
			int path = 0;
			for(int i = 0; i < charArrs.length; i++) {
				path = charArrs[i] - 'a';
				if(cur.nexts[path] == null) {
					return 0;
				}
				cur = cur.nexts[path];
			}
			return cur.pass;
		}
	}
	
	/**
	 * 	哈希表实现的前缀树节点信息
	 * @author yangyanchao
	 *
	 */
	public static class Node2 {
		public int pass;
		public int end;
		public HashMap<Integer, Node2> nexts;
		
		public Node2() {
			this.pass = 0;
			this.end = 0;
			this.nexts = new HashMap<Integer, Node2>();
		}
	}
	
	/**
	 * 	前缀树--哈希表实现
	 * @author yangyanchao
	 *
	 */
	public static class Trie2 {
		/**
		 * 	前缀树根
		 */
		private Node2 root;
		public Trie2() {
			this.root = new Node2();
		}
		
		/**
		 * 	添加某个字符串,可以重复添加,每次算1个
		 * @param str
		 */
		public void insert(String str) {
			if(str == null || str.length() == 0) {
				return ;
			}
			char[] charArrs = str.toCharArray();
			Node2 cur = root;
			int key = 0;
			cur.pass++;
			for(int i = 0; i < charArrs.length; i++) {
				key = (int)charArrs[i];
				if(!cur.nexts.containsKey(key)) {
					cur.nexts.put(key, new Node2());
				}
				cur = cur.nexts.get(key);
				cur.pass++;
			}
			cur.end++;
		}
		
		/**
		 * 	查询某个字符串在结构中还有几个
		 * @param str
		 * @return
		 */
		public int search(String str) {
			if(str == null || str.length() == 0) {
				return 0;
			}
			char[] charArrs = str.toCharArray();
			Node2 cur = root;
			int key = 0;
			for(int i = 0; i < charArrs.length; i++) {
				key = (int)charArrs[i];
				if(!cur.nexts.containsKey(key)) {
					return 0;
				}
				cur = cur.nexts.get(key);
			}
			return cur.end;
		}
		
		/**
		 * 	删掉某个字符串,可以重复删除,每次算1个
		 * @param str
		 */
		public void delete(String str) {
			if(this.search(str) != 0) {
				char[] charArrs = str.toCharArray();
				Node2 cur = root;
				int key = 0;
				cur.pass--;
				for(int i = 0; i < charArrs.length; i++) {
					key = (int)charArrs[i];
					if(--cur.nexts.get(key).pass == 0) {
						cur.nexts.remove(key);
						return ;
					}
					cur = cur.nexts.get(key);
				}
				cur.end--;
			}
		}
		
		/**
		 * 	查询有多少个字符串,是以str做前缀的
		 * @param str
		 * @return
		 */
		public int prefixNumber(String str) {
			if(str == null || str.length() == 0) {
				return 0;
			}
			char[] charArrs = str.toCharArray();
			Node2 cur = root;
			int key = 0;
			for(int i = 0; i < charArrs.length; i++) {
				key = (int)charArrs[i];
				if(!cur.nexts.containsKey(key)) {
					return 0;
				}
				cur = cur.nexts.get(key);
			}
			return cur.pass;
		}
	}
	
	/**
	 * 	比对类
	 * @author yangyanchao
	 *
	 */
	public static class Comparator {
		
		/**
		 *	字符的索引记录
		 */
		public HashMap<String, Integer> box;
		
		public Comparator() {
			this.box = new HashMap<String, Integer>();
		}
		
		/**
		 * 	添加某个字符串
		 * @param str
		 */
		public void insert(String str) {
			if(str == null || str.length() == 0) {
				return ;
			}
			if(!box.containsKey(str)) {
				box.put(str, 1);
			} else {
				box.put(str, (box.get(str) + 1));
			}
		}
		
		/**
		 * 	查询某个字符串并返回次数
		 * @param str
		 * @return
		 */
		public int search(String str) {
			if(str == null || str.length() == 0 || !box.containsKey(str)) {
				return 0;
			}
			return box.get(str);
		}
		
		/**
		 * 	删掉某个字符串,可以重复删除
		 * @param str
		 */
		public void delete(String str) {
			if(str == null || str.length() == 0 || !box.containsKey(str)) {
				return ;
			}
			int count = (box.get(str) - 1);
			if(count == 0) {
				box.remove(str);
			} else {
				box.put(str, count);
			}
		}
		
		/**
		 * 	查询有多少个字符串,是以str做前缀的并返回次数
		 * @param str
		 * @return
		 */
		public int prefixNumber(String str) {
			if(str == null || str.length() == 0) {
				return 0;
			}
			int count = 0;
			for(String key : box.keySet()) {
				if(key.startsWith(str)) {
					count +=  box.get(key);
				}
			}
			return count;
		}
	}
	
	public static void main(String[] args) {
		int testTimes = 100000;
		int arrLen = 100;
		int strLen = 20;
		boolean succed = true;
		Trie1 trie1 = null;
		Trie2 trie2 = null;
		Comparator comp = null;
		for(int i = 0; i < testTimes; i++) {
			String[] arr = generateRandomStrArray(arrLen, strLen);
			trie1 = new Trie1();
			trie2 = new Trie2();
			comp = new Comparator();
			for(int j = 0; j < arr.length; j++) {
				double decide = Math.random();
				if (decide < 0.25) {
					trie1.insert(arr[j]);
					trie2.insert(arr[j]);
					comp.insert(arr[j]);
				} else if (decide < 0.5) {
					trie1.delete(arr[j]);
					trie2.delete(arr[j]);
					comp.delete(arr[j]);
				} else if (decide < 0.75) {
					int ans1 = trie1.search(arr[j]);
					int ans2 = trie2.search(arr[j]);
					int ans3 = comp.search(arr[j]);
					if (ans1 != ans2 || ans2 != ans3) {
						succed = false;
						System.out.println("ooVoo!");
					}
				} else {
					int ans1 = trie1.prefixNumber(arr[j]);
					int ans2 = trie2.prefixNumber(arr[j]);
					int ans3 = comp.prefixNumber(arr[j]);
					if (ans1 != ans2 || ans2 != ans3) {
						succed = false;
						System.out.println("ooVoo!");
					}
				}
			}
			
		}
		System.out.println(succed ? "Nice." : "ooVoo");
	}

	/**
	 * 	生成随机字符串的随机数组
	 * @param arrLen
	 * @param strLen
	 * @return
	 */
	private static String[] generateRandomStrArray(int arrLen, int strLen) {
		String[] arr = new String[(int)(Math.random() * arrLen) + 1];
		for(int i = 0; i < arr.length; i++) {
			arr[i] = generateRandomStr(strLen);
		}
		return arr;
	}

	/**
	 * 	生成随机字符
	 * @param strLen
	 * @return
	 */
	private static String generateRandomStr(int strLen) {
		char[] arr = new char[(int)(Math.random() * strLen) + 1];
		for(int i = 0; i < arr.length; i++) {
			int random = (int)(Math.random() * 6);
			arr[i] = (char)(97 + random);
		}
		return String.valueOf(arr);
	}
	
}
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

# 不基于比较的排序

桶排序思想下的排序:计数排序 & 基数排序

  • 桶排序思想下的排序都是不基于比较的排序

  • 时间复杂度为O(N),额外空间负载度O(M)

  • 应用范围有限,需要样本的数据状况满足桶的划分

# 计数排序和基数排序

  • 一般来讲,计数排序要求,样本是整数,且范围比较窄

  • 一般来讲,基数排序要求,样本是10进制的正整数

一旦要求稍有升级,改写代价增加是显而易见的

计数排序:

  • 样本范围非常受限制
  • 统计年龄由小到大排序
package class08;

import java.util.Arrays;

/**
 * 	计数排序
 * 	样本范围非常受限制
 * 	统计年龄由小到大排序
 * @author yangyanchao
 *
 */
public class Test02_CountSort {
	/**
	 * 	计数排序
	 * 	统计年龄由小到大排序(0-200)
	 * @param arr
	 */
	public static void countSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		// 求出数组中最大值
		int max = Integer.MIN_VALUE;
		for(int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int[] tongArr = new int[max + 1];
		for(int i = 0; i < arr.length; i++) {
			tongArr[arr[i]]++;
		}
		int index = 0;
		for(int i = 0; i < tongArr.length; i++) {
			while(tongArr[i]-- > 0) {
				arr[index++] = i;
			}
		}
	}
	
	/**
	 * 	比对器
	 * @param arr
	 */
	public static void comprator(int[] arr) {
		Arrays.sort(arr);
	}
	
	public static void main(String[] args) {
		int testTimes = 100000;
		int maxSize = 100;
		int maxValue = 201;
		boolean succed = true;
		for(int i = 0; i < testTimes; i++) {
			int[] arr = generateRandomArray(maxSize, maxValue);
			int[] arr1 = copyArray(arr);
			countSort(arr);
			comprator(arr1);
			if(!isEqual(arr, arr1)) {
				succed = false;
				printArray(arr);
				printArray(arr1);
			}
		}
		System.out.println(succed ? "Nice." : "ooVoo" );
	}

	/**
	 * 	打印数组
	 * @param arr
	 */
	private static void printArray(int[] arr) {
		if(arr == null || arr.length == 0) {
			return ;
		}
		System.out.print("{ ");
		for(int i = 0; i < arr.length; i++) {
			if(i != 0) {
				System.out.print(", ");
			}
			System.out.print(arr[i]);
		}
		System.out.println("}");
	}

	/**
	 * 	判断两个数组是否值相等
	 * @param arr
	 * @param arr1
	 * @return
	 */
	private static boolean isEqual(int[] arr, int[] arr1) {
		if(arr == null || arr1 == null || arr.length != arr1.length) {
			return false;
		}
		for(int i = 0; i < arr.length; i++) {
			if(arr[i] != arr1[i]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 	复制数组
	 * @param arr
	 * @return
	 */
	private static int[] copyArray(int[] arr) {
		if(arr == null) {
			return null;
		}
		int[] arr1 = new int[arr.length];
		for(int i = 0; i < arr1.length; i++) {
			arr1[i] = arr[i];
		}
		return arr1;
	}

	/**
	 * 	生成长度及值随机的数组
	 * @param maxSize
	 * @param maxValue
	 * @return
	 */
	private static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int)(Math.random() * maxSize) + 1];
		for(int i = 0; i < arr.length; i++) {
			arr[i] = (int)(Math.random() * maxValue);
		}
		return arr;
	}
}

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

基数排序:

  • 样本范围在非负数的整数
  • 利用技巧可以做到负数的整数,例如[-5,-7,2,3,6,1,4]先求出最小值-7,然后所有数值都加7,进行排序,排好序再减7
  • 可以做到不使用桶的基数排序
package class08;

import java.util.Arrays;

/**
 * 	基数排序
 * - 样本范围在非负数的整数
 * - 利用技巧可以做到负数的整数,例如`[-5,-7,2,3,6,1,4]`先求出最小值-7,然后所有数值都加7,进行排序,排好序再减7
 * - 可以做到不使用桶的基数排序
 * @author yangyanchao
 *
 */
public class Test03_RadixSort {
	/**
	 * 	数据范围是正整数
	 * 	不使用桶的基数排序
	 * @param arr
	 */
	public static void radixSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		radixSort(arr, 0, arr.length - 1, maxbits(arr));
	}

	/**
	 * 	在这个范围上基数排序
	 * @param arr
	 * @param L
	 * @param R
	 * @param maxbits
	 */
	private static void radixSort(int[] arr, int L, int R, int maxbits) {
		final int radix = 10;
		for(int d = 1; d <= maxbits; d++) {
			int[] count = new int[radix];
			int ans = 0;
			for(int i = 0; i < arr.length; i++) {
				ans = getNumByBit(arr[i], d);
				count[ans]++;
			}
			// 变为累加和数组 变为0位置上的数是<=0的数的个数,1位置上的数是<=1的数的个数
			for(int i = 1; i < count.length; i++) {
				count[i] = count[i - 1] + count[i];
			}
			int[] help = copyArray(arr);
			// 从右往左遍历count数组 所以能够确定<=n的数的位置为arr[--count[ans]]
			for(int i = help.length - 1; i >= 0; i--) {
				ans = getNumByBit(help[i], d);
				arr[--count[ans]] = help[i];
			}
		}
		
	}

	/**
	 * 	复制数组
	 * @param arr
	 * @return
	 */
	private static int[] copyArray(int[] arr) {
		if(arr == null) {
			return null;
		}
		int[] arr1 = new int[arr.length];
		for(int i = 0; i < arr1.length; i++) {
			arr1[i] = arr[i];
		}
		return arr1;
	}

	/**
	 * 	获取数值第几位的数字
	 * @param num
	 * @param d
	 * @return
	 */
	private static int getNumByBit(int num, int d) {
		int mo = 0;
		while(d != 0) {
			mo = num % 10;
			num = num / 10;
			d--;
		}
		return mo;
	}

	/**
	 * 	返回数组中最大值的位数
	 * @param arr
	 * @return
	 */
	private static int maxbits(int[] arr) {
		int max = Integer.MIN_VALUE;
		for(int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int bits = 0;
		while(max != 0) {
			max = (max/10);
			bits++;
		}
		return bits;
	}
	
	/**
	 * 	比对器
	 * @param arr
	 */
	public static void comprator(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		Arrays.sort(arr);
	}
	
	public static void main(String[] args) {
//		System.out.println(getNumByBit(123, 5));
//		int[] arr = { 409, 44, 475, 369, 491};
//		radixSort(arr);
		int maxTestTimes = 100000;
		int maxSize = 100;
		int maxValue = 501;
		boolean succed = true;
		for(int i = 0; i < maxTestTimes; i++) {
			int[] arr = generateRandomArray(maxSize, maxValue);
			int[] arr1 = copyArray(arr);
			radixSort(arr);
			comprator(arr1);
			if(!isEqual(arr, arr1)) {
				succed = false;
				printArray(arr);
				printArray(arr1);
			}
		}
		System.out.println(succed ? "Nice." : "ooVoo" );
	}

	/**
	 * 	打印数组
	 * @param arr
	 */
	private static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		System.out.print("{ ");
		for(int i = 0; i < arr.length; i++) {
			if(i != 0) {
				System.out.print(", ");
			}
			System.out.print(arr[i]);
		}
		System.out.println("}");
	}

	/**
	 * 	判断数组是否值相等
	 * @param arr
	 * @param arr1
	 * @return
	 */
	private static boolean isEqual(int[] arr, int[] arr1) {
		if(arr == null || arr1 == null || arr.length != arr1.length) {
			return false;
		}
		for(int i = 0; i < arr.length; i++) {
			if(arr[i] != arr1[i]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 	生成长度及数值随机的数组
	 * @param maxSize
	 * @param maxValue
	 * @return
	 */
	private static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int)(Math.random() * maxSize) + 1];
		for(int i = 0; i < arr.length; i++) {
			arr[i] = (int)(Math.random() * maxValue);
		}
		return arr;
	}
}

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

# 排序算法的稳定性

稳定性是指同样大小的样本再排序之后不会改变相对次序

对基础类型来说,稳定性毫无意义

对非基础类型来说,稳定性有重要意义

有些排序算法可以实现成稳定的,而有些排序算法无论如何都实现不成稳定的