├── .gitignore ├── README.md ├── code ├── alg │ ├── binary_search │ │ ├── Makefile │ │ ├── bsearch.c │ │ ├── bsearch.h │ │ └── main.c │ ├── knap │ │ ├── Makefile │ │ ├── knap.c │ │ ├── knap.h │ │ └── main.c │ ├── number │ │ ├── Makefile │ │ ├── main.c │ │ ├── number.c │ │ └── number.h │ ├── random │ │ ├── Makefile │ │ ├── main.c │ │ ├── random.c │ │ └── random.h │ ├── recursive │ │ ├── Makefile │ │ ├── main.c │ │ ├── recur.c │ │ └── recur.h │ └── sort │ │ ├── Makefile │ │ ├── main.c │ │ ├── sort.c │ │ └── sort.h ├── ds │ ├── heap │ │ ├── Makefile │ │ ├── heap.c │ │ ├── heap.h │ │ └── main.c │ ├── list │ │ ├── adlist │ │ │ ├── Makefile │ │ │ ├── adlist.c │ │ │ ├── adlist.h │ │ │ └── main.c │ │ └── aslist │ │ │ ├── Makefile │ │ │ ├── aslist.c │ │ │ ├── aslist.h │ │ │ └── main.c │ ├── stack │ │ ├── Makefile │ │ ├── main.c │ │ ├── stack.c │ │ └── stack.h │ ├── string │ │ ├── Makefile │ │ ├── main.cpp │ │ └── string.c │ └── tree │ │ └── binary_tree │ │ ├── Makefile │ │ ├── bt.c │ │ ├── bt.h │ │ ├── btqueue.c │ │ ├── btqueue.h │ │ ├── btstack.c │ │ ├── btstack.h │ │ ├── main.c │ │ ├── quiz.c │ │ └── quiz.h └── util │ ├── util.c │ └── util.h └── docs ├── 数据结构和算法面试题系列0—C语言基础.md ├── 数据结构和算法面试题系列10—随机算法总结.md ├── 数据结构和算法面试题系列11—递归算法总结.md ├── 数据结构和算法面试题系列12—背包问题总结.md ├── 数据结构和算法面试题系列13—数字题总结.md ├── 数据结构和算法面试题系列1—字符串.md ├── 数据结构和算法面试题系列2—链表.md ├── 数据结构和算法面试题系列3—栈.md ├── 数据结构和算法面试题系列4—二叉堆.md ├── 数据结构和算法面试题系列5—二叉树基础.md ├── 数据结构和算法面试题系列6—二叉树面试题汇总.md ├── 数据结构和算法面试题系列7—二分查找算法详解.md ├── 数据结构和算法面试题系列8—排序算法之基础排序.md └── 数据结构和算法面试题系列9—排序算法之快速排序.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.[oa] 2 | *~ 3 | main 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 3 | 本系列是我多年前毕业时整理的一些数据结构和算法基础及一些大公司的经典面试题,欢迎大家指正。 4 | 5 | 整理很费时费力,也参考了很多资料,也都有在参考资料页备注。如果有漏掉的,麻烦提醒我加上。 6 | 7 | 如果本系列文章有帮助到您,也希望您不吝star,给我鼓励,谢谢大家。 8 | 9 | # 运行 10 | 到对应代码目录执行 `make test` 即可。 11 | -------------------------------------------------------------------------------- /code/alg/binary_search/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | OBJECTS = $(UTILDIR)/util.o bsearch.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | main.o: main.c 9 | $(CC) -c -o $@ $< -I$(UTILDIR) 10 | 11 | test: clean main 12 | ./main 13 | 14 | .PHONY: clean 15 | clean: 16 | -rm -f main $(OBJECTS) 17 | -------------------------------------------------------------------------------- /code/alg/binary_search/bsearch.c: -------------------------------------------------------------------------------- 1 | #include "bsearch.h" 2 | 3 | /** 4 | * 基本二分查找算法 5 | */ 6 | int binarySearch(int a[], int n, int t) 7 | { 8 | int l = 0, u = n - 1; 9 | while (l <= u) { 10 | int m = l + (u - l) / 2; // 同(l+u)/ 2,这里是为了溢出 11 | if (t > a[m]) 12 | l = m + 1; 13 | else if (t < a[m]) 14 | u = m - 1; 15 | else 16 | return m; 17 | } 18 | return -(l+1); 19 | } 20 | 21 | /** 22 | * 二分查找第一次出现位置 23 | */ 24 | int binarySearchFirst(int a[], int n, int t) 25 | { 26 | int l = -1, u = n; 27 | while (l + 1 != u) { 28 | /*循环不变式a[l] a[m]) 31 | l = m; 32 | else 33 | u = m; 34 | } 35 | /*assert: l+1=u && a[l]=n || a[p]!=t) 38 | p = -1; 39 | return p; 40 | } 41 | 42 | /** 43 | * 二分查找最后一次出现位置 44 | */ 45 | int binarySearchLast(int a[], int n, int t) 46 | { 47 | int l = -1, u = n; 48 | while (l + 1 != u) { 49 | /*循环不变式, a[l] <= t < a[u]*/ 50 | int m = l + (u - l) / 2; 51 | if (t >= a[m]) 52 | l = m; 53 | else 54 | u = m; 55 | } 56 | /*assert: l+1 = u && a[l] <= t < a[u]*/ 57 | int p = l; 58 | if (p<=-1 || a[p]!=t) 59 | p = -1; 60 | return p; 61 | } 62 | 63 | /** 64 | * 二分查找第一次和最后一次出现位置 65 | */ 66 | int binarySearchFirstAndLast(int a[], int n, int t, int firstFlag) 67 | { 68 | int l = 0; 69 | int u = n - 1; 70 | while(l <= u) { 71 | int m = l + (u - l) / 2; 72 | if(a[m] == t) { //找到了,判断是第一次出现还是最后一次出现 73 | if(firstFlag) { //查询第一次出现的位置 74 | if(m != 0 && a[m-1] != t) 75 | return m; 76 | else if(m == 0) 77 | return 0; 78 | else 79 | u = m - 1; 80 | } else { //查询最后一次出现的位置 81 | if(m != n-1 && a[m+1] != t) 82 | return m; 83 | else if(m == n-1) 84 | return n-1; 85 | else 86 | l = m + 1; 87 | } 88 | } 89 | else if(a[m] < t) 90 | l = m + 1; 91 | else 92 | u = m - 1; 93 | } 94 | 95 | return -1; 96 | } 97 | 98 | /** 99 | * 旋转数组查找-两次二分查找 100 | */ 101 | int binarySearchRotateTwice(int a[], int n, int t) 102 | { 103 | int p = findRotatePosition(a, n); //找到旋转位置 104 | if (p == -1) 105 | return binarySearchFirst(a, n, t); //如果原数组有序,则直接二分查找即可 106 | 107 | int left = binarySearchFirst(a, p+1, t); //查找左半部分 108 | if (left != -1) 109 | return left; //左半部分找到,则直接返回 110 | 111 | int right = binarySearchFirst(a+p+1, n-p-1, t); //左半部分没有找到,则查找右半部分 112 | if (right == -1) 113 | return -1; 114 | 115 | return right+p+1; //返回位置,注意要加上p+1 116 | } 117 | 118 | /** 119 | * 查找旋转位置 120 | */ 121 | int findRotatePosition(int a[], int n) 122 | { 123 | int i; 124 | for (i = 0; i < n-1; i++) { 125 | if (a[i+1] < a[i]) 126 | return i; 127 | } 128 | return -1; 129 | } 130 | 131 | /** 132 | * 旋转数组二分查找-一次二分查找 133 | */ 134 | int binarySearchRotateOnce(int a[], int n, int t) 135 | { 136 | int l = 0, u = n-1; 137 | while (l <= u) { 138 | int m = l + (u-l) / 2; 139 | if (t == a[m]) 140 | return m; 141 | if (a[m] >= a[l]) { //数组左半有序 142 | if (t >= a[l] && t < a[m]) 143 | u = m - 1; 144 | else 145 | l = m + 1; 146 | } else { //数组右半段有序 147 | if (t > a[m] && t <= a[u]) 148 | l = m + 1; 149 | else 150 | u = m - 1; 151 | } 152 | } 153 | return -1; 154 | } 155 | -------------------------------------------------------------------------------- /code/alg/binary_search/bsearch.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINARY_SEARCH_H 2 | #define __BINARY_SEARCH_H 3 | 4 | int binarySearch(int a[], int n, int t); 5 | int binarySearchFirst(int a[], int n, int t); 6 | int binarySearchLast(int a[], int n, int t); 7 | int binarySearchFirstAndLast(int a[], int n, int t, int firstFlag); 8 | 9 | int findRotatePosition(int a[], int n); 10 | int binarySearchRotateTwice(int a[], int n, int t); 11 | int binarySearchRotateOnce(int a[], int n, int t); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /code/alg/binary_search/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bsearch.h" 4 | 5 | void testBinarySearch() 6 | { 7 | int a[] = {1, 2, 4, 8, 8, 9, 12}; 8 | int n = ALEN(a); 9 | int t = 8; 10 | int ret = binarySearch(a, n, t); 11 | printIntArray(a, n); 12 | printf("Binary Search: %d, result: %d\n", t, ret); 13 | ret = binarySearchFirst(a, n, t); 14 | printf("Binary Search First: %d, result: %d\n", t, ret); 15 | ret = binarySearchLast(a, n, t); 16 | printf("Binary Search Last: %d, result: %d\n", t, ret); 17 | ret = binarySearchFirstAndLast(a, n, t, 1); 18 | printf("Binary Search First: %d, result: %d\n", t, ret); 19 | ret = binarySearchFirstAndLast(a, n, t, 0); 20 | printf("Binary Search Last: %d, result: %d\n", t, ret); 21 | } 22 | 23 | void testBinarySearchRotate() 24 | { 25 | int a[] = {3, 4, 5, 1, 2}; 26 | int n = ALEN(a); 27 | int t = 5; 28 | printIntArray(a, n); 29 | int ret = binarySearchRotateTwice(a, n, t); 30 | printf("Binary Search Rotate(Twice): %d, result: %d\n", t, ret); 31 | ret = binarySearchRotateOnce(a, n, t); 32 | printf("Binary Search Rotate(Once): %d, result: %d\n", t, ret); 33 | t = 1; 34 | ret = binarySearchRotateTwice(a, n, t); 35 | printf("Binary Search Rotate(Twice): %d, result: %d\n", t, ret); 36 | ret = binarySearchRotateOnce(a, n, t); 37 | printf("Binary Search Rotate(Once): %d, result: %d\n", t, ret); 38 | } 39 | 40 | int main() 41 | { 42 | testBinarySearch(); 43 | testBinarySearchRotate(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /code/alg/knap/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | OBJECTS = $(UTILDIR)/util.o knap.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | knap.o: knap.c 9 | $(CC) -c -o $@ $< -I$(UTILDIR) 10 | 11 | main.o: main.c 12 | $(CC) -c -o $@ $< -I$(UTILDIR) 13 | 14 | test: clean main 15 | ./main 16 | 17 | .PHONY: clean 18 | clean: 19 | -rm -f main $(OBJECTS) 20 | -------------------------------------------------------------------------------- /code/alg/knap/knap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "knap.h" 5 | 6 | /* 7 | * 01背包问题 8 | * N为物品数目,C为背包容量,w[i]为物品i的重量,v[i]为物品i的价值,f为结果列表 9 | */ 10 | int knap01(int N, int C, int w[], int v[]) 11 | { 12 | int *f = (int *)calloc(sizeof(int), C+1); 13 | int i, c; 14 | for (i = 0; i < N; i++) { 15 | for (c = C; c >= w[i]; c--) { 16 | f[c] = max(f[c], f[c-w[i]] + v[i]); 17 | } 18 | printf("%d: ", i+1); 19 | printIntArray(f, C+1); 20 | 21 | } 22 | return f[C]; 23 | } 24 | 25 | /* 26 | * 完全背包问题 27 | */ 28 | int knapComplete(int N, int C, int w[], int v[]) 29 | { 30 | int *f = (int *)calloc(sizeof(int), C+1); 31 | int i, c, k; 32 | for (i = 0; i < N; i++) { 33 | for (c = C; c >= 0; c--) { 34 | for (k = 0; k <= c/w[i]; k++) { 35 | f[c] = max(f[c], f[c-k*w[i]] + k*v[i]); 36 | } 37 | } 38 | printf("%d: ", i+1); 39 | printIntArray(f, C+1); 40 | } 41 | return f[C]; 42 | } 43 | 44 | /** 45 | * 完全背包问题-仿01背包解法 46 | */ 47 | int knapCompleteLike01(int N, int C, int w[], int v[]) 48 | { 49 | int *f = (int *)calloc(sizeof(int), C+1); 50 | int i, c; 51 | for (i = 0; i < N; i++) { 52 | for (c = w[i]; c <= C; c++) { 53 | f[c] = max(f[c], f[c-w[i]] + v[i]); 54 | } 55 | printf("%d: ", i+1); 56 | printIntArray(f, C+1); 57 | 58 | } 59 | return f[C]; 60 | } 61 | -------------------------------------------------------------------------------- /code/alg/knap/knap.h: -------------------------------------------------------------------------------- 1 | #ifndef __KNAP_H 2 | #define __KNAP_H 3 | 4 | int knap01(int N, int C, int w[], int v[]); 5 | int knapComplete(int N, int C, int w[], int v[]); 6 | int knapCompleteLike01(int N, int C, int w[], int v[]); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /code/alg/knap/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "knap.h" 4 | 5 | void testKnap() 6 | { 7 | int w[] = {3, 4, 5}; 8 | int v[] = {4, 5, 6}; 9 | int N = ALEN(w); 10 | int C = 10; 11 | int max = knap01(N, C, w, v); 12 | printf("KNap01 max: %d\n", max); 13 | 14 | int maxComplete = knapComplete(N, C, w, v); 15 | printf("KNapComplete max: %d\n", maxComplete); 16 | 17 | int maxComplete01 = knapCompleteLike01(N, C, w, v); 18 | printf("KNapCompleteLike01 max: %d\n", maxComplete01); 19 | } 20 | 21 | int main() 22 | { 23 | testKnap(); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /code/alg/number/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | OBJECTS = $(UTILDIR)/util.o number.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | number.o: number.c 9 | $(CC) -c -o $@ $< -I$(UTILDIR) 10 | 11 | main.o: main.c 12 | $(CC) -c -o $@ $< -I$(UTILDIR) 13 | 14 | test: clean main 15 | ./main 16 | 17 | .PHONY: clean 18 | clean: 19 | -rm -f main $(OBJECTS) 20 | -------------------------------------------------------------------------------- /code/alg/number/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "number.h" 4 | 5 | void testPrimeGeneration() 6 | { 7 | int n = 100; 8 | primeGeneration(n); 9 | } 10 | 11 | void testNumOfZero() 12 | { 13 | int n = 10; 14 | int num = numOfZero(n); 15 | printf("NumOfZero(%d): %d\n", n, num); 16 | num = numOfZero(n); 17 | printf("NumOfZero2(%d): %d\n", n, num); 18 | } 19 | 20 | void testNumOfOne() 21 | { 22 | int n = 123; 23 | int num = numOfOne(n); 24 | printf("NumOfOne(%d): %d\n", n, num); 25 | } 26 | 27 | void testFindContinuousSequence() 28 | { 29 | int n = 15; 30 | printf("FindSequence(%d):\n", n); 31 | findContinuousSequence(n); 32 | printf("FindSequenceEndIndex(%d):\n", n); 33 | findContinuousSequenceEndIndex(n); 34 | } 35 | 36 | void testMaxSumOfContinousSequence() 37 | { 38 | int a[] = {1, 3, -2, 4, -5}; 39 | int n = ALEN(a); 40 | int maxSum = maxSumOfContinuousSequence(a, n); 41 | printf("MaxSumOfContinousSequence: %d\n", maxSum); 42 | maxSum = maxSumOfContinuousSequenceSub(a, 0, n-1); 43 | printf("MaxSumOfContinousSequenceSub: %d\n", maxSum); 44 | maxSum = maxSumOfContinuousSequenceEndIndex(a, n); 45 | printf("MaxSumOfContinousSequenceEndIndex: %d\n", maxSum); 46 | } 47 | 48 | void testMaxMultipleOfContinuousSequence() 49 | { 50 | int a[] = {3, -4, -5, 6, -2}; 51 | int n = ALEN(a); 52 | int maxMultiple = maxMultipleOfContinuousSequence(a, n); 53 | printf("MaxMultipleOfContinousSequence: %d\n", maxMultiple); 54 | } 55 | 56 | void testPowOf2() 57 | { 58 | int a = 100; 59 | int b = 64; 60 | int ra = powOf2(a); 61 | int rb = powOf2(b); 62 | printf("PowOf2 %d:%d, %d:%d\n", a, ra, b, rb); 63 | } 64 | 65 | void testNumOfBit1() 66 | { 67 | int a = 32; 68 | int b = 11; 69 | int ra = numOfBit1(a); 70 | int rb = numOfBit1(b); 71 | printf("NumOfBit1 %d:%d, %d:%d\n", a, ra, b, rb); 72 | ra = numOfBit1WithCheck(a); 73 | rb = numOfBit1WithCheck(b); 74 | printf("NumOfBit1WithCheck %d:%d, %d:%d\n", a, ra, b, rb); 75 | } 76 | 77 | void testReverseBit() 78 | { 79 | uint a = 18; 80 | uint ra = reverseXOR(a); 81 | printf("ReverseBit: %s -> %s\n", bin(a), bin(ra)); 82 | uint b = 19; 83 | uint rb = reverseXOR(b); 84 | printf("ReverseMask: %s -> %s\n", bin(b), bin(rb)); 85 | } 86 | 87 | int main() 88 | { 89 | testPrimeGeneration(); 90 | testNumOfZero(); 91 | testNumOfOne(); 92 | testFindContinuousSequence(); 93 | testMaxSumOfContinousSequence(); 94 | testMaxMultipleOfContinuousSequence(); 95 | testPowOf2(); 96 | testNumOfBit1(); 97 | testReverseBit(); 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /code/alg/number/number.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "number.h" 6 | 7 | /** 8 | * 找出前N个素数, N > 3 9 | */ 10 | int primeGeneration(int n) 11 | { 12 | int *prime = (int *)malloc(sizeof(int) * n); 13 | int gap = 2; 14 | int count = 3; 15 | int maybePrime = 5; 16 | int i, isPrime; 17 | 18 | /* 注意:2, 3, 5 是素数 */ 19 | prime[0] = 2; 20 | prime[1] = 3; 21 | prime[2] = 5; 22 | 23 | while (count < n) { 24 | maybePrime += gap; 25 | gap = 6 - gap; 26 | isPrime = 1; 27 | for (i = 2; prime[i]*prime[i] <= maybePrime && isPrime; i++) 28 | if (maybePrime % prime[i] == 0) 29 | isPrime = 0; 30 | 31 | if (isPrime) 32 | prime[count++] = maybePrime; 33 | } 34 | 35 | printf("\nFirst %d Prime Numbers are :\n", count); 36 | 37 | for (i = 0; i < count; i++) { 38 | if (i % 10 == 0) printf("\n"); 39 | printf("%5d", prime[i]); 40 | } 41 | printf("\n"); 42 | return 0; 43 | } 44 | 45 | /** 46 | * N!末尾0的个数 47 | */ 48 | int numOfZero(int n) 49 | { 50 | int cnt = 0, i, j; 51 | for (i = 1; i <= n; i++) { 52 | j = i; 53 | while (j % 5 == 0) { 54 | cnt++; 55 | j /= 5; 56 | } 57 | } 58 | return cnt; 59 | } 60 | 61 | /** 62 | * N!末尾0的个数-优化版 63 | */ 64 | int numOfZero2(int n) 65 | { 66 | int cnt = 0; 67 | while (n) { 68 | cnt += n/5; 69 | n /= 5; 70 | } 71 | return cnt; 72 | } 73 | 74 | 75 | /** 76 | * 1-N正整数中1的个数 77 | */ 78 | int numOfOne(int n) 79 | { 80 | int factor = 1, cnt = 0; //factor为乘数因子 81 | int low = 0, curr = 0, high = 0; 82 | while (n / factor != 0) { 83 | low = n - n/factor * factor; //low为低位数字,curr为当前考虑位的数字,high为高位数字 84 | curr = n/factor % 10; 85 | high = n/(factor * 10); 86 | 87 | switch(curr) { 88 | case 0: //当前位为0的情况 89 | cnt += high * factor; 90 | break; 91 | case 1: //当前位为1的情况 92 | cnt += high * factor + low + 1; 93 | break; 94 | default: //当前位为其他数字的情况 95 | cnt += (high+1) * factor; 96 | break; 97 | } 98 | factor *= 10; 99 | } 100 | return cnt; 101 | } 102 | 103 | 104 | /** 105 | * 查找和为N的连续序列 106 | */ 107 | int findContinuousSequence(int n) 108 | { 109 | int found = 0; 110 | int k = 2, x, m, i; // k为连续序列的数字的数目,x为起始的值,m用于判断是否有满足条件的值。 111 | while (1) { 112 | x = (n - k*(k-1)/2) / k; // 求出k个连续正整数和为n的起始值x 113 | m = (n - k*(k-1)/2) % k; // m用于判断是否有满足条件的连续正整数值 114 | 115 | if (x <= 0) 116 | break; // 退出条件,如果x<=0,则循环退出。 117 | 118 | if (!m) { // m为0,表示找到了连续子序列和为n。 119 | found = 1; 120 | printContinuousSequence(x, k); 121 | } 122 | k++; 123 | } 124 | return found; 125 | } 126 | 127 | /** 128 | * 查找和为N的连续序列-序列结尾位置法 129 | */ 130 | int findContinuousSequenceEndIndex(int n) 131 | { 132 | if (n < 3) return 0; 133 | 134 | int found = 0; 135 | int begin = 1, end = 2; 136 | int mid = (1 + n) / 2; 137 | int sum = begin + end; 138 | 139 | while (begin < mid) { 140 | if (sum > n) { 141 | sum -= begin; 142 | begin++; 143 | } else { 144 | if (sum == n) { 145 | found = 1; 146 | printContinuousSequence(begin, end-begin+1); 147 | } 148 | 149 | end++; 150 | sum += end; 151 | } 152 | } 153 | 154 | return found; 155 | } 156 | 157 | /** 158 | * 打印连续子序列 159 | */ 160 | void printContinuousSequence(int x, int k) 161 | { 162 | int i; 163 | for (i = 0; i < k; i++) { 164 | printf("%d ", x++); 165 | } 166 | printf("\n"); 167 | } 168 | 169 | /** 170 | * 最大连续子序列和 171 | */ 172 | int maxSumOfContinuousSequence(int a[], int n) 173 | { 174 | int max = a[0], i, j, sum; // 初始化最大值为第一个元素 175 | for (i = 0; i < n; i++) { 176 | sum = 0; // sum必须清零 177 | for (j = i; j < n; j++) { //从位置i开始计算从i开始的最大连续子序列和的大小,如果大于max,则更新max。 178 | sum += a[j]; 179 | if (sum > max) 180 | max = sum; 181 | } 182 | } 183 | return max; 184 | } 185 | 186 | /** 187 | * 最大连续子序列和-分治法 188 | */ 189 | int maxSumOfContinuousSequenceSub(int a[], int l, int u) 190 | { 191 | if (l > u) return 0; 192 | if (l == u) return a[l]; 193 | int m = (l + u) / 2; 194 | 195 | /*求横跨左右的最大连续子序列左半部分*/ 196 | int lmax = a[m], lsum = 0; 197 | int i; 198 | 199 | for (i = m; i >= l; i--) { 200 | lsum += a[i]; 201 | if (lsum > lmax) 202 | lmax = lsum; 203 | } 204 | 205 | /*求横跨左右的最大连续子序列右半部分*/ 206 | int rmax=a[m+1], rsum = 0; 207 | for (i = m+1; i <= u; i++) { 208 | rsum += a[i]; 209 | if (rsum > rmax) 210 | rmax = rsum; 211 | } 212 | 213 | return max3(lmax+rmax, maxSumOfContinuousSequenceSub(a, l, m), 214 | maxSumOfContinuousSequenceSub(a, m+1, u)); //返回三者最大值 215 | } 216 | 217 | /** 218 | * 最大连续子序列和-结束位置法 219 | */ 220 | int maxSumOfContinuousSequenceEndIndex(int a[], int n) 221 | { 222 | int maxSum, maxHere, i; 223 | maxSum = maxHere = a[0]; // 初始化最大和为a[0] 224 | 225 | for (i = 1; i < n; i++) { 226 | if (maxHere <= 0) 227 | maxHere = a[i]; // 如果前面位置最大连续子序列和小于等于0,则以当前位置i结尾的最大连续子序列和为a[i] 228 | else 229 | maxHere += a[i]; // 如果前面位置最大连续子序列和大于0,则以当前位置i结尾的最大连续子序列和为它们两者之和 230 | 231 | if (maxHere > maxSum) { 232 | maxSum = maxHere; //更新最大连续子序列和 233 | } 234 | } 235 | return maxSum; 236 | } 237 | 238 | /** 239 | * 最大连续子序列乘积 240 | */ 241 | int maxMultipleOfContinuousSequence(int a[], int n) 242 | { 243 | int minSofar, maxSofar, max, i; 244 | int maxHere, minHere; 245 | max = minSofar = maxSofar = a[0]; 246 | 247 | for (i = 1; i < n; i++) { 248 | int maxHere = max3(maxSofar*a[i], minSofar*a[i], a[i]); 249 | int minHere = min3(maxSofar*a[i], minSofar*a[i], a[i]); 250 | maxSofar = maxHere, minSofar = minHere; 251 | if(max < maxSofar) 252 | max = maxSofar; 253 | } 254 | return max; 255 | } 256 | 257 | /** 258 | * 判断正整数是2的幂次 259 | */ 260 | int powOf2(int n) 261 | { 262 | assert(n > 0); 263 | return !(n & (n-1)); 264 | } 265 | 266 | /** 267 | * 二进制表示中1的个数-解法1 268 | */ 269 | int numOfBit1(int n) 270 | { 271 | int cnt = 0; 272 | unsigned int flag = 1; 273 | while (flag) { 274 | if(n & flag) 275 | cnt++; 276 | 277 | flag = flag << 1; 278 | if (flag > n) 279 | break; 280 | } 281 | return cnt; 282 | } 283 | 284 | /** 285 | * 二进制表示中1的个数-解法2 286 | */ 287 | int numOfBit1WithCheck(int n) 288 | { 289 | int cnt = 0; 290 | while (n != 0) { 291 | n = (n & (n-1)); 292 | cnt++; 293 | } 294 | return cnt; 295 | } 296 | 297 | /** 298 | * 反转比特位 299 | */ 300 | uint swapBits(uint x, uint i, uint j) 301 | { 302 | uint lo = ((x >> i) & 1); // 取x的第i位的值 303 | uint hi = ((x >> j) & 1); // 取x的第j位的值 304 | if (lo ^ hi) { 305 | x ^= ((1U << i) | (1U << j)); // 如果第i位和第j位值不同,则交换i和j这两个位的值,使用异或操作实现 306 | } 307 | return x; 308 | } 309 | 310 | /** 311 | * 反转整数比特位-仿字符串反转 312 | */ 313 | uint reverseXOR(uint x) 314 | { 315 | uint n = sizeof(x) * 8; 316 | uint i; 317 | for (i = 0; i < n/2; i++) { 318 | x = swapBits(x, i, n-i-1); 319 | } 320 | return x; 321 | } 322 | 323 | /** 324 | * 反转整数比特位-分治法 325 | */ 326 | uint reverseMask(uint x) 327 | { 328 | assert(sizeof(x) == 4); // special case: only works for 4 bytes (32 bits). 329 | x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1); 330 | x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2); 331 | x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4); 332 | x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8); 333 | x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16); 334 | return x; 335 | } 336 | -------------------------------------------------------------------------------- /code/alg/number/number.h: -------------------------------------------------------------------------------- 1 | #ifndef __NUMBER_H 2 | #define __NUMBER_H 3 | 4 | typedef unsigned int uint; 5 | 6 | int primeGeneration(int n); 7 | int numOfZero(int n); 8 | int numOfOne(int n); 9 | 10 | int findContinuousSequence(int n); 11 | void printContinuousSequence(int x, int k); 12 | int findContinuousSequenceEndIndex(int n); 13 | 14 | int maxSumOfContinuousSequence(int a[], int n); 15 | int maxSumOfContinuousSequenceSub(int a[], int l, int u); 16 | int maxSumOfContinuousSequenceEndIndex(int a[], int n); 17 | 18 | int maxMultipleOfContinuousSequence(int a[], int n); 19 | 20 | int powOf2(int n); 21 | int numOfBit1(int n); 22 | int numOfBit1WithCheck(int n); 23 | 24 | uint swapBits(uint x, uint i, uint j); 25 | uint reverseXOR(uint x); 26 | uint reverseMask(uint x); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /code/alg/random/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | OBJECTS = $(UTILDIR)/util.o random.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | random.o: random.c 9 | $(CC) -c -o $@ $< -I$(UTILDIR) 10 | 11 | main.o: main.c 12 | $(CC) -c -o $@ $< -I$(UTILDIR) 13 | 14 | test: clean main 15 | ./main 16 | 17 | .PHONY: clean 18 | clean: 19 | -rm -f main $(OBJECTS) 20 | -------------------------------------------------------------------------------- /code/alg/random/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "random.h" 4 | 5 | void testRandomNtimes(void (*func)(int a[], int n), int a[], int n) 6 | { 7 | int i; 8 | for (i = 0; i < n; i++) { 9 | func(a, n); 10 | printIntArray(a, n); 11 | } 12 | } 13 | 14 | void testRandomArray() 15 | { 16 | int a[] = {1, 2, 3}; 17 | int n = ALEN(a); 18 | 19 | testRandomNtimes(randomInPlace, a, n); 20 | 21 | int b[] = {4, 5, 6}; 22 | n = ALEN(b); 23 | testRandomNtimes(permuteWithAll, b, n); 24 | } 25 | 26 | void testRandomOne() 27 | { 28 | int i, n = 5; 29 | for (i = 0; i < n; i++) { 30 | printf("RandomOne(%d): ", i); 31 | randomOne(i); 32 | } 33 | } 34 | 35 | void testRandomM() 36 | { 37 | int n = 5, m = 2; 38 | printf("RandomMKnuth(%d, %d): ", n, m); 39 | randomMKnuth(n, m); 40 | printf("RandomMArray(%d, %d): ", n, m); 41 | randomMArray(n, m); 42 | } 43 | 44 | void testRandom7ToRand10() 45 | { 46 | printf("Rand7ToRand10 Sample: %d\n", rand7ToRand10Sample()); 47 | printf("Rand7ToRand10 Utilize Sample: %d\n", rand7ToRand10UtilizeSample()); 48 | } 49 | 50 | int main() 51 | { 52 | testRandomArray(); 53 | testRandomOne(); 54 | testRandomM(); 55 | testRandom7ToRand10(); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /code/alg/random/random.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /** 7 | * 数组的均匀随机排列: 每个排列出现概率相等。 8 | */ 9 | void randomInPlace(int a[], int n) 10 | { 11 | int i; 12 | for (i = 0; i < n; i++) { 13 | int rand = randInt(i, n-1); 14 | swapInt(a, i, rand); 15 | } 16 | } 17 | 18 | /** 19 | * 错误示范:全排列,注意每个排列的概率并不相等。 20 | */ 21 | void permuteWithAll(int a[], int n) 22 | { 23 | int i; 24 | for (i = 0; i < n; i++) { 25 | int rand = randInt(0, n-1); 26 | swapInt(a, i, rand); 27 | } 28 | } 29 | 30 | /** 31 | * 未知长度的序列中随机选取一个数 32 | */ 33 | void randomOne(int n) 34 | { 35 | int i, select = 0; 36 | for (i = 1; i < n; i++) { 37 | int rd = rand() % n; 38 | if (rd == 0) { 39 | select = i; 40 | } 41 | } 42 | printf("%d\n", select); 43 | } 44 | 45 | /** 46 | * n个数中随机选取m个有序数-knuth算法 47 | */ 48 | void randomMKnuth(int n, int m) 49 | { 50 | int i; 51 | for (i = 0; i < n; i++) { 52 | if ((rand() % (n-i)) < m) { 53 | printf("%d ", i); 54 | m--; 55 | } 56 | } 57 | printf("\n"); 58 | } 59 | 60 | /** 61 | * n个数随机选取m个有序数-随机数组法 62 | */ 63 | void randomMArray(int n, int m) 64 | { 65 | int i, j; 66 | int *x = (int *)malloc(sizeof(int) * n); 67 | 68 | for (i = 0; i < n; i++) 69 | x[i] = i; 70 | 71 | for (i = 0; i < m; i++) { 72 | j = randInt(i, n-1); 73 | swapInt(x, i, j); 74 | } 75 | 76 | for (i = 0; i < m; i++) { 77 | for (j = i+1; j>0 && x[j-1]>x[j]; j--) { 78 | swapInt(x, j, j-1); 79 | } 80 | } 81 | 82 | for (i = 0; i < m; i++) { 83 | printf("%d ", x[i]); 84 | } 85 | 86 | printf("\n"); 87 | } 88 | 89 | /** 90 | * 从rand7 得到 rand10 91 | */ 92 | int rand7ToRand10Sample() { 93 | int row, col, idx; 94 | do { 95 | row = randInt(1, 7); 96 | col = randInt(1, 7); 97 | idx = col + (row-1)*7; 98 | } while (idx > 40); 99 | 100 | return 1 + (idx-1) % 10; 101 | } 102 | 103 | /** 104 | * 从rand7 得到 rand10-优化方法 105 | */ 106 | int rand7ToRand10UtilizeSample() { 107 | int a, b, idx; 108 | while (1) { 109 | a = randInt(1, 7); 110 | b = randInt(1, 7); 111 | idx = b + (a-1)*7; 112 | if (idx <= 40) 113 | return 1 + (idx-1)%10; 114 | 115 | a = idx-40; 116 | b = randInt(1, 7); 117 | // get uniform dist from 1 - 63 118 | idx = b + (a-1)*7; 119 | if (idx <= 60) 120 | return 1 + (idx-1)%10; 121 | 122 | a = idx-60; 123 | b = randInt(1, 7); 124 | // get uniform dist from 1-21 125 | idx = b + (a-1)*7; 126 | if (idx <= 20) 127 | return 1 + (idx-1)%10; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /code/alg/random/random.h: -------------------------------------------------------------------------------- 1 | #ifndef __RANDOM_H 2 | #define __RANDOM_H 3 | 4 | void randomInPlace(int a[], int n); 5 | void permuteWithAll(int a[], int n); 6 | 7 | void randomOne(int n); 8 | void randomMKnuth(int n, int m); 9 | void randomMArray(int n, int m); 10 | 11 | int rand7ToRand10Sample(); 12 | int rand7ToRand10UtilizeSample(); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /code/alg/recursive/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | OBJECTS = $(UTILDIR)/util.o recur.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | recur.o: recur.c 9 | $(CC) -c -o $@ $< -I$(UTILDIR) 10 | 11 | main.o: main.c 12 | $(CC) -c -o $@ $< -I$(UTILDIR) 13 | 14 | test: clean main 15 | ./main 16 | 17 | .PHONY: clean 18 | clean: 19 | -rm -f main $(OBJECTS) 20 | -------------------------------------------------------------------------------- /code/alg/recursive/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "recur.h" 4 | 5 | void testFactorial() 6 | { 7 | int n = 5; 8 | int r = factorial(n); 9 | printf("Factorial(%d): %d\n", n, r); 10 | } 11 | 12 | void testHano() 13 | { 14 | printf("Hano:\n"); 15 | int n = 3; 16 | hano('A', 'B', 'C', n); 17 | } 18 | 19 | void testPermute() 20 | { 21 | printf("Permute:\n"); 22 | int a[] = {1, 2, 3}; 23 | int n = ALEN(a); 24 | permute(a, 0, n); 25 | } 26 | 27 | void testCombination() 28 | { 29 | printf("Combination:\n"); 30 | int a[] = {1, 2, 3}; 31 | int n = ALEN(a); 32 | combination(a, n); 33 | } 34 | 35 | void testReversePrint() 36 | { 37 | const char *str = "hello world"; 38 | printf("Reverse print:%s\n", str); 39 | reversePrint(str); 40 | printf("\n"); 41 | } 42 | 43 | int main() 44 | { 45 | testFactorial(); 46 | testHano(); 47 | testPermute(); 48 | testCombination(); 49 | testReversePrint(); 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /code/alg/recursive/recur.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "recur.h" 5 | 6 | /** 7 | * 阶乘 8 | */ 9 | int factorial(int n) 10 | { 11 | if (n == 0) 12 | return 1; 13 | else { 14 | int recurse = factorial(n-1); 15 | int result = n * recurse; 16 | return result; 17 | } 18 | } 19 | 20 | /** 21 | * 汉诺塔 22 | */ 23 | void hano(char a, char b, char c, int n) 24 | { 25 | if (n <= 0) return; 26 | 27 | hano(a, c, b, n-1); 28 | move(a, c); 29 | hano(b, a, c, n-1); 30 | } 31 | 32 | void move(char a, char b) 33 | { 34 | printf("%c->%c\n", a, b); 35 | } 36 | 37 | 38 | /** 39 | * 全排列算法,k为起始位置,n为数组大小 40 | */ 41 | void permute(int a[], int k, int n) 42 | { 43 | if (k == n-1) { 44 | printIntArray(a, n); //输出排列 45 | } else { 46 | int i; 47 | for (i = k; i < n; i++) { 48 | swapInt(a, i, k); //交换 49 | permute(a, k+1, n); //下一次排列 50 | swapInt(a, i, k); //恢复原来的序列 51 | } 52 | } 53 | } 54 | 55 | 56 | /* 57 | * 组合主函数,包括选取1到n个数字 58 | */ 59 | void combination(int a[], int n) 60 | { 61 | int *select = (int *)calloc(sizeof(int), n); 62 | int k; 63 | for (k = 1; k <= n; k++) { 64 | combinationUtil(a, n, 0, k, select); 65 | } 66 | } 67 | 68 | /* 69 | * 组合工具函数:从数组a从位置i开始选取k个数 70 | */ 71 | void combinationUtil(int a[], int n, int i, int k, int *select) 72 | { 73 | if (i > n) return; //位置超出数组范围直接返回,否则非法访问会出段错误 74 | 75 | if (k == 0) { //选取完了,输出选取的数字 76 | int j; 77 | for (j = 0; j < n; j++) { 78 | if (select[j]) 79 | printf("%d ", a[j]); 80 | } 81 | printf("\n"); 82 | } else { 83 | select[i] = 1; 84 | combinationUtil(a, n, i+1, k-1, select); //第i个数字被选取,从后续i+1开始选取k-1个数 85 | select[i] = 0; 86 | combinationUtil(a, n, i+1, k, select); //第i个数字不选,则从后续i+1位置开始还要选取k个数 87 | } 88 | } 89 | 90 | /** 91 | * 逆序打印字符串 92 | */ 93 | void reversePrint(const char *str) 94 | { 95 | if (!*str) 96 | return; 97 | 98 | reversePrint(str + 1); 99 | putchar(*str); 100 | } 101 | -------------------------------------------------------------------------------- /code/alg/recursive/recur.h: -------------------------------------------------------------------------------- 1 | #ifndef __RECUR_H 2 | #define __RECUR_H 3 | 4 | int factorial(int n); 5 | void hano(char a, char b, char c, int n); 6 | void move(char a, char b); 7 | 8 | void permute(int a[], int k, int n); 9 | 10 | void combination(int a[], int n); 11 | void combinationUtil(int a[], int n, int i, int k, int *select); 12 | 13 | void reversePrint(const char *str); 14 | #endif 15 | -------------------------------------------------------------------------------- /code/alg/sort/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../util 3 | STACKDIR = ../../ds/stack 4 | OBJECTS = $(UTILDIR)/util.o $(STACKDIR)/stack.o sort.o main.o 5 | 6 | main: $(OBJECTS) 7 | $(CC) -o $@ $(OBJECTS) 8 | 9 | sort.o: sort.c 10 | $(CC) -c -o $@ $< -I$(UTILDIR) -I$(STACKDIR) 11 | 12 | main.o: main.c 13 | $(CC) -c -o $@ $< -I$(UTILDIR) 14 | 15 | test: clean main 16 | ./main 17 | 18 | .PHONY: clean 19 | clean: 20 | -rm -f main $(OBJECTS) 21 | -------------------------------------------------------------------------------- /code/alg/sort/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "sort.h" 5 | 6 | void testGeneralSort(void(*func)(int a[], int n), int a[], int n) 7 | { 8 | int *b = copyIntArray(a, n); 9 | func(b, n); 10 | printIntArray(b, n); 11 | } 12 | 13 | void testMergeSort(int a[], int n) 14 | { 15 | int *b = copyIntArray(a, n); 16 | mergeSort(b, 0, n-1); 17 | printIntArray(b, n); 18 | } 19 | 20 | void testQuickSort(void(*func)(int a[], int l, int u), int a[], int n) 21 | { 22 | int *b = copyIntArray(a, n); 23 | func(b, 0, n-1); 24 | printIntArray(b, n); 25 | } 26 | 27 | void testSort() 28 | { 29 | int a[] = {1, 3, 2, 4, 2, 5, 8, 7}; 30 | int n = ALEN(a); 31 | 32 | testGeneralSort(insertSort, a, n); 33 | testGeneralSort(shellSort, a, n); 34 | testGeneralSort(selectSort, a, n); 35 | testGeneralSort(bubbleSort, a, n); 36 | testGeneralSort(betterBubbleSort, a, n); 37 | testGeneralSort(countingSort, a, n); 38 | testGeneralSort(mergeSortIter, a, n); 39 | testGeneralSort(quickSortIter, a, n); 40 | 41 | testMergeSort(a, n); 42 | 43 | testQuickSort(quickSort, a, n); 44 | testQuickSort(quickSortLR, a, n); 45 | testQuickSort(quickSortLRRandom, a, n); 46 | testQuickSort(quickSortLRMedian3, a, n); 47 | } 48 | 49 | int main() 50 | { 51 | testSort(); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /code/alg/sort/sort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "sort.h" 5 | 6 | /** 7 | * 插入排序 8 | */ 9 | void insertSort(int a[], int n) 10 | { 11 | int i, j; 12 | for (i = 1; i < n; i++) { 13 | /* 14 | * 循环不变式:a[0...i-1]有序。每次迭代开始前,a[0...i-1]有序, 15 | * 循环结束后i=n,a[0...n-1]有序 16 | * */ 17 | int key = a[i]; 18 | for (j = i; j > 0 && a[j-1] > key; j--) { 19 | a[j] = a[j-1]; 20 | } 21 | a[j] = key; 22 | } 23 | } 24 | 25 | 26 | /** 27 | * 插入排序-另外一种实现 28 | */ 29 | void insertSortSwap(int a[], int l, int u) 30 | { 31 | int i, j; 32 | for (i = l; i < u; i++) { 33 | for (j = i+1; j>l && a[j-1]>a[j]; j--) 34 | swapInt(a, j, j-1); 35 | } 36 | } 37 | 38 | /** 39 | * 希尔排序 40 | */ 41 | void shellSort(int a[], int n) 42 | { 43 | int gap; 44 | for (gap = n/2; gap > 0; gap /= 2) { 45 | int i; 46 | for (i = gap; i < n; i++) { 47 | int key = a[i], j; 48 | for (j = i; j >= gap && key < a[j-gap]; j -= gap) { 49 | a[j] = a[j-gap]; 50 | } 51 | a[j] = key; 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 选择排序 58 | */ 59 | void selectSort(int a[], int n) 60 | { 61 | int i, j, min, tmp; 62 | for (i = 0; i < n-1; i++) { 63 | min = i; 64 | for (j = i+1; j < n; j++) { 65 | if (a[j] < a[min]) 66 | min = j; 67 | } 68 | if (min != i) 69 | tmp = a[i], a[i] = a[min], a[min] = tmp; //交换a[i]和a[min] 70 | } 71 | } 72 | 73 | /** 74 | * 冒泡排序-经典版 75 | */ 76 | void bubbleSort(int a[], int n) 77 | { 78 | int i, j, tmp; 79 | for (i = 0; i < n; i++) { 80 | for (j = n-1; j >= i+1; j--) { 81 | if (a[j] < a[j-1]) 82 | tmp = a[j], a[j] = a[j-1], a[j-1] = tmp; 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * 冒泡排序-优化版 89 | */ 90 | void betterBubbleSort(int a[], int n) 91 | { 92 | int tmp, i, j; 93 | for (i = 0; i < n; i++) { 94 | int sorted = 1; 95 | for (j = n-1; j >= i+1; j--) { 96 | if (a[j] < a[j-1]) { 97 | tmp = a[j], a[j] = a[j-1], a[j-1] = tmp; 98 | sorted = 0; 99 | } 100 | } 101 | if (sorted) 102 | return ; 103 | } 104 | } 105 | 106 | /** 107 | * 计数排序 108 | */ 109 | void countingSort(int a[], int n) 110 | { 111 | int i, j; 112 | int *b = (int *)malloc(sizeof(int) * n); 113 | int k = maxOfIntArray(a, n); // 求数组最大元素 114 | int *c = (int *)malloc(sizeof(int) * (k+1)); //辅助数组 115 | 116 | for (i = 0; i <= k; i++) 117 | c[i] = 0; 118 | 119 | for (j = 0; j < n; j++) 120 | c[a[j]] = c[a[j]] + 1; //c[i]包含等于i的元素个数 121 | 122 | for (i = 1; i <= k; i++) 123 | c[i] = c[i] + c[i-1]; //c[i]包含小于等于i的元素个数 124 | 125 | for (j = n-1; j >= 0; j--) { 126 | b[c[a[j]]-1] = a[j]; //结果存在b[0...n-1]中 127 | c[a[j]] = c[a[j]] - 1; 128 | } 129 | 130 | for (i = 0; i < n; i++) { 131 | a[i] = b[i]; 132 | } 133 | 134 | free(b); 135 | free(c); 136 | } 137 | 138 | 139 | /* 140 | * 归并排序-递归 141 | * */ 142 | void mergeSort(int a[], int l, int u) 143 | { 144 | if (l < u) { 145 | int m = l + (u-l)/2; 146 | mergeSort(a, l, m); 147 | mergeSort(a, m + 1, u); 148 | merge(a, l, m, u); 149 | } 150 | } 151 | 152 | /** 153 | * 归并排序合并函数 154 | */ 155 | void merge(int a[], int l, int m, int u) 156 | { 157 | int n1 = m - l + 1; 158 | int n2 = u - m; 159 | 160 | int left[n1], right[n2]; 161 | int i, j; 162 | for (i = 0; i < n1; i++) /* left holds a[l..m] */ 163 | left[i] = a[l + i]; 164 | 165 | for (j = 0; j < n2; j++) /* right holds a[m+1..u] */ 166 | right[j] = a[m + 1 + j]; 167 | 168 | i = j = 0; 169 | int k = l; 170 | while (i < n1 && j < n2) { 171 | if (left[i] < right[j]) 172 | a[k++] = left[i++]; 173 | else 174 | a[k++] = right[j++]; 175 | } 176 | while (i < n1) /* left[] is not exhausted */ 177 | a[k++] = left[i++]; 178 | while (j < n2) /* right[] is not exhausted */ 179 | a[k++] = right[j++]; 180 | } 181 | 182 | /** 183 | * 归并排序-非递归 184 | */ 185 | void mergeSortIter(int a[], int n) 186 | { 187 | int i, s=2; 188 | while (s <= n) { 189 | i = 0; 190 | while (i+s <= n){ 191 | merge(a, i, i+s/2-1, i+s-1); 192 | i += s; 193 | } 194 | 195 | //处理末尾残余部分 196 | merge(a, i, i+s/2-1, n-1); 197 | s*=2; 198 | } 199 | //最后再从头到尾处理一遍 200 | merge(a, 0, s/2-1, n-1); 201 | } 202 | 203 | /** 204 | * 快速排序-朴素版本 205 | */ 206 | void quickSort(int a[], int l, int u) 207 | { 208 | if (l >= u) return; 209 | 210 | int q = partition(a, l, u); 211 | quickSort(a, l, q-1); 212 | quickSort(a, q+1, u); 213 | } 214 | 215 | /** 216 | * 快速排序-划分函数 217 | */ 218 | int partition(int a[], int l, int u) 219 | { 220 | int i, q=l; 221 | for (i = l+1; i <= u; i++) { 222 | if (a[i] < a[l]) 223 | swapInt(a, i, ++q); 224 | } 225 | swapInt(a, l, q); 226 | return q; 227 | } 228 | 229 | /** 230 | * 快速排序-双向划分法 231 | */ 232 | void quickSortLR(int a[], int l, int u) 233 | { 234 | if (l >= u) return; 235 | 236 | int pivot = a[l]; 237 | int q = partitionLR(a, l, u, pivot); 238 | quickSortLR(a, l, q-1); 239 | quickSortLR(a, q+1, u); 240 | } 241 | 242 | /** 243 | * 快速排序-双向划分函数 244 | */ 245 | int partitionLR(int a[], int l, int u, int pivot) 246 | { 247 | int i = l; 248 | int j = u+1; 249 | while (1) { 250 | do { 251 | i++; 252 | } while (a[i] < pivot && i <= u); //注意i<=u这个判断条件,不能越界。 253 | 254 | do { 255 | j--; 256 | } while (a[j] > pivot); 257 | 258 | if (i > j) break; 259 | 260 | swapInt(a, i, j); 261 | } 262 | 263 | // 注意这里是交换l和j,而不是l和i,因为i与j交叉后,a[i...u]都大于等于枢纽元t, 264 | // 而枢纽元又在最左边,所以不能与i交换。只能与j交换。 265 | swapInt(a, l, j); 266 | 267 | return j; 268 | } 269 | 270 | /** 271 | * 快速排序-随机枢纽元版本 272 | */ 273 | void quickSortLRRandom(int a[], int l, int u) 274 | { 275 | if (l >= u) return; 276 | 277 | int pivot = pivotRandom(a, l, u); 278 | int p = partitionLR(a, l, u, pivot); 279 | quickSortLRRandom(a, l, p-1); 280 | quickSortLRRandom(a, p+1, u); 281 | } 282 | 283 | 284 | /** 285 | * 随机选择枢纽元 286 | */ 287 | int pivotRandom(int a[], int l, int u) 288 | { 289 | int rand = randInt(l, u); 290 | swapInt(a, l, rand); 291 | return a[l]; 292 | } 293 | 294 | 295 | /** 296 | * 快速排序-三数取中作为枢纽元版本 297 | */ 298 | void quickSortLRMedian3(int a[], int l, int u) 299 | { 300 | if (l >= u) return; 301 | 302 | int pivot = pivotMedian3(a, l, u); 303 | int p = partitionLR(a, l, u, pivot); 304 | 305 | quickSortLRMedian3(a, l, p-1); 306 | quickSortLRMedian3(a, p+1, u); 307 | } 308 | 309 | /** 310 | * 三数取中选择枢纽元 311 | */ 312 | int pivotMedian3(int a[], int l, int u) 313 | { 314 | int m = l + (u-l)/2; 315 | 316 | /* 317 | * 三数排序 318 | */ 319 | if( a[l] > a[m] ) 320 | swapInt(a, l, m); 321 | 322 | if( a[l] > a[u] ) 323 | swapInt(a, l, u); 324 | 325 | if( a[m] > a[u] ) 326 | swapInt(a, m, u); 327 | 328 | /* assert: a[l] <= a[m] <= a[u] */ 329 | swapInt(a, m, l); //交换枢纽元到位置l 330 | 331 | return a[l]; //返回枢纽元 332 | } 333 | 334 | /** 335 | * 快速排序-非递归版本 336 | */ 337 | void quickSortIter(int a[], int n) 338 | { 339 | Stack *stack = stackNew(n); 340 | int l = 0, u = n-1; 341 | int p = partition(a, l, u); 342 | 343 | if (p-1 > l) { //左半部分两个边界值入栈 344 | push(stack, p-1); 345 | push(stack, l); 346 | } 347 | 348 | if (p+1 < u) { //右半部分两个边界值入栈 349 | push(stack, u); 350 | push(stack, p+1); 351 | } 352 | 353 | while (!IS_EMPTY(stack)) { //栈不为空,则循环划分过程 354 | l = pop(stack); 355 | u = pop(stack); 356 | p = partition(a, l, u); 357 | 358 | if (p-1 > l) { 359 | push(stack, p-1); 360 | push(stack, l); 361 | } 362 | 363 | if (p+1 < u) { 364 | push(stack, u); 365 | push(stack, p+1); 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /code/alg/sort/sort.h: -------------------------------------------------------------------------------- 1 | #ifndef __SORT_H 2 | #define __SORT_H 3 | 4 | void insertSort(int a[], int n); 5 | void shellSort(int a[], int n); 6 | void selectSort(int a[], int n); 7 | void bubbleSort(int a[], int n); 8 | void betterBubbleSort(int a[], int n); 9 | void countingSort(int a[], int n); 10 | 11 | void mergeSortIter(int a[], int n); 12 | void mergeSort(int a[], int l, int u); 13 | void merge(int a[], int l, int m, int u); 14 | 15 | int partition(int a[], int l, int u); 16 | int partitionLR(int a[], int l, int u, int pivot); 17 | int pivotRandom(int a[], int l, int u); 18 | int pivotMedian3(int a[], int l, int u); 19 | void quickSort(int a[], int l, int u); 20 | void quickSortLR(int a[], int l, int u); 21 | void quickSortLRRandom(int a[], int l, int u); 22 | void quickSortLRMedian3(int a[], int l, int u); 23 | void quickSortIter(int a[], int n); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /code/ds/heap/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | INCLUDE = ../../util 3 | OBJECTS = $(INCLUDE)/util.o heap.o main.o 4 | 5 | main: $(OBJECTS) 6 | $(CC) -o $@ $(OBJECTS) 7 | 8 | heap.o: heap.c 9 | $(CC) -c -o $@ $< -I$(INCLUDE) 10 | 11 | main.o: main.c 12 | $(CC) -c -o $@ $< -I$(INCLUDE) 13 | 14 | test: clean main 15 | ./main 16 | 17 | .PHONY: clean 18 | clean: 19 | -rm -f main $(OBJECTS) 20 | -------------------------------------------------------------------------------- /code/ds/heap/heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "heap.h" 6 | 7 | /** 8 | * 保持最大堆性质 9 | */ 10 | void maxHeapify(int A[], int i, int heapSize) 11 | { 12 | int l = LEFT(i); 13 | int r = RIGHT(i); 14 | 15 | int largest = i; 16 | 17 | if (l <= heapSize-1 && A[l] > A[i]) { 18 | largest = l; 19 | } 20 | 21 | if (r <= heapSize-1 && A[r] > A[largest]) { 22 | largest = r; 23 | } 24 | 25 | if (largest != i) { // 最大值不是i,则需要交换i和largest的元素,并递归调用maxHeapify。 26 | swapInt(A, i, largest); 27 | maxHeapify(A, largest, heapSize); 28 | } 29 | } 30 | 31 | /** 32 | * 从一个数组建一个最大堆 33 | */ 34 | void buildMaxHeap(int A[], int n) 35 | { 36 | int i; 37 | for (i = n/2-1; i >= 0; i--) { 38 | maxHeapify(A, i, n); 39 | } 40 | } 41 | 42 | /** 43 | * 堆排序 44 | */ 45 | void heapSort(int A[], int n) 46 | { 47 | buildMaxHeap(A, n); 48 | int heapSize = n; 49 | int i; 50 | for (i = n-1; i >= 1; i--) { 51 | swapInt(A, 0, i); 52 | maxHeapify(A, 0, --heapSize); 53 | } 54 | } 55 | 56 | /** 57 | * 从数组创建优先级队列 58 | */ 59 | PQ *newPQ(int A[], int n) 60 | { 61 | PQ *pq = (PQ *)malloc(sizeof(PQ) + sizeof(int) * n); 62 | pq->size = 0; 63 | pq->capacity = n; 64 | 65 | int i; 66 | for (i = 0; i < pq->capacity; i++) { 67 | pq->elems[i] = A[i]; 68 | pq->size++; 69 | } 70 | buildMaxHeap(pq->elems, pq->size); 71 | 72 | return pq; 73 | } 74 | 75 | /** 76 | * 打印优先级队列 77 | */ 78 | void printPQ(PQ *pq) 79 | { 80 | int i; 81 | for (i = 0; i < pq->size; i++) { 82 | printf("%d ", pq->elems[i]); 83 | } 84 | printf("\n"); 85 | } 86 | 87 | 88 | int maximum(PQ *pq) 89 | { 90 | return pq->elems[0]; 91 | } 92 | 93 | int extractMax(PQ *pq) 94 | { 95 | int max = pq->elems[0]; 96 | pq->elems[0] = pq->elems[--pq->size]; 97 | maxHeapify(pq->elems, 0, pq->size); 98 | return max; 99 | } 100 | 101 | PQ *insert(PQ *pq, int key) 102 | { 103 | int newSize = ++pq->size; 104 | if (newSize > pq->capacity) { 105 | pq->capacity = newSize * 2; 106 | pq = (PQ *)realloc(pq, sizeof(PQ) + sizeof(int) * pq->capacity); 107 | } 108 | pq->elems[newSize-1] = INT_MIN; 109 | increaseKey(pq, newSize-1, key); 110 | return pq; 111 | } 112 | 113 | void increaseKey(PQ *pq, int i, int key) 114 | { 115 | int *elems = pq->elems; 116 | elems[i] = key; 117 | 118 | while (i > 0 && elems[PARENT(i)] < elems[i]) { 119 | swapInt(elems, PARENT(i), i); 120 | i = PARENT(i); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /code/ds/heap/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef __HEAP_H 2 | #define __HEAP_H 3 | 4 | #define PARENT(i) ( i >= 1 ? (i-1)/2 : 0) 5 | #define LEFT(i) (2 * i + 1) 6 | #define RIGHT(i) (2 * i + 2) 7 | 8 | typedef struct PriorityQueue { 9 | int capacity; 10 | int size; 11 | int elems[]; 12 | } PQ; 13 | 14 | void maxHeapify(int A[], int i, int heapSize); 15 | void buildMaxHeap(int A[], int n); 16 | void heapSort(int A[], int n); 17 | 18 | PQ *newPQ(int A[], int n); 19 | void printPQ(PQ *pq); 20 | int maximum(PQ *pq); 21 | int extractMax(PQ *pq); 22 | PQ *insert(PQ *pq, int key); 23 | void increaseKey(PQ *pq, int i, int key); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /code/ds/heap/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "heap.h" 4 | 5 | int main() 6 | { 7 | printf("Build max heap: \n"); 8 | int a[] = {16, 4, 10, 14, 7, 9, 3, 2, 8, 1}; 9 | buildMaxHeap(a, ALEN(a)); 10 | printIntArray(a, ALEN(a)); 11 | 12 | printf("Heapsort:\n"); 13 | int b[] = {1, 3, 2, 4, 5, 6}; 14 | printIntArray(b, ALEN(b)); 15 | heapSort(b, ALEN(b)); 16 | printIntArray(b, ALEN(b)); 17 | 18 | int c[] = {1, 3, 2, 4, 5, 6}; 19 | printIntArray(c, ALEN(c)); 20 | PQ *pq = newPQ(c, ALEN(c)); 21 | printf("PQ max: %d\n", maximum(pq)); 22 | printPQ(pq); 23 | printf("PQ extractMax: %d\n", extractMax(pq)); 24 | printPQ(pq); 25 | pq = insert(pq, 8); 26 | printPQ(pq); 27 | } 28 | -------------------------------------------------------------------------------- /code/ds/list/adlist/Makefile: -------------------------------------------------------------------------------- 1 | objects = main.o adlist.o 2 | cc = gcc 3 | 4 | main: $(objects) 5 | cc -o $@ $(objects) 6 | 7 | test: clean main 8 | ./main 9 | 10 | .PHONY: clean 11 | clean: 12 | -rm -f main $(objects) 13 | -------------------------------------------------------------------------------- /code/ds/list/adlist/adlist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "adlist.h" 4 | 5 | /** 6 | * 创建链表 7 | */ 8 | List *listCreate(void) 9 | { 10 | List *list; 11 | 12 | if ((list = malloc(sizeof(*list))) == NULL) 13 | return NULL; 14 | 15 | list->head = list->tail = NULL; 16 | list->len = 0; 17 | list->dup = NULL; 18 | list->free = NULL; 19 | list->match = NULL; 20 | 21 | return list; 22 | } 23 | 24 | void listRelease(List *list) 25 | { 26 | unsigned long len; 27 | ListNode *current, *next; 28 | 29 | current = list->head; 30 | len = list->len; 31 | while(len--) { 32 | next = listNextNode(current); 33 | if (list->free) 34 | list->free(current->value); 35 | 36 | free(current); 37 | current = next; 38 | } 39 | 40 | free(list); 41 | } 42 | 43 | /** 44 | * 新建链表结点 45 | */ 46 | ListNode *listNewNode(void *value) 47 | { 48 | ListNode *node; 49 | if ((node = malloc(sizeof(*node))) == NULL) 50 | return NULL; 51 | 52 | node->value = value; 53 | return node; 54 | } 55 | 56 | /** 57 | * 头插法 58 | */ 59 | List *listAddNodeHead(List *list, void *value) 60 | { 61 | ListNode *node; 62 | 63 | if ((node = listNewNode(value)) == NULL) 64 | return NULL; 65 | 66 | // list是空链表 67 | if (list->len == 0) { 68 | list->head = list->tail = node; 69 | node->prev = node->next = NULL; 70 | // 非空链表 71 | } else { 72 | node->prev = NULL; 73 | node->next = list->head; 74 | list->head->prev = node; 75 | list->head = node; 76 | } 77 | 78 | list->len++; 79 | 80 | return list; 81 | } 82 | 83 | /** 84 | * 尾插法 85 | */ 86 | List *listAddNodeTail(List *list, void *value) 87 | { 88 | ListNode *node; 89 | 90 | if ((node = listNewNode(value)) == NULL) 91 | return NULL; 92 | 93 | if (list->len == 0) { 94 | list->head = list->tail = node; 95 | node->prev = node->next = NULL; 96 | } else { 97 | node->prev = list->tail; 98 | node->next = NULL; 99 | list->tail->next = node; 100 | list->tail = node; 101 | } 102 | 103 | list->len++; 104 | 105 | return list; 106 | } 107 | 108 | void listDelNode(List *list, ListNode *node) 109 | { 110 | if (node->prev) 111 | node->prev->next = node->next; 112 | else 113 | list->head = node->next; 114 | 115 | if (node->next) 116 | node->next->prev = node->prev; 117 | else 118 | list->tail = node->prev; 119 | 120 | if (list->free) 121 | list->free(node->value); 122 | 123 | free(node); 124 | 125 | list->len--; 126 | } 127 | 128 | ListNode *listSearchKey(List *list, void *key) 129 | { 130 | ListIter *iter = listGetIterator(list, AL_START_HEAD); 131 | ListNode *node, *found = NULL; 132 | 133 | while ((node = listNext(iter))) { 134 | if (list->match) { 135 | if (list->match(node->value, key)) { 136 | found = node; 137 | break; 138 | } 139 | } else { 140 | if (key == node->value) { 141 | found = node; 142 | break; 143 | } 144 | } 145 | } 146 | 147 | listReleaseIterator(iter); 148 | return found; 149 | } 150 | 151 | ListNode *listIndex(List *list, long index) 152 | { 153 | ListNode *node; 154 | if (index < 0) { 155 | index = (-index) - 1; 156 | node = list->tail; 157 | while (index-- && node) 158 | node = node->prev; 159 | } else { 160 | node = list->head; 161 | while (index-- && node) 162 | node = node->next; 163 | } 164 | return node; 165 | } 166 | 167 | ListIter *listGetIterator(List *list, int direction) 168 | { 169 | ListIter *iter; 170 | if ((iter = malloc(sizeof(*iter))) == NULL) 171 | return NULL; 172 | 173 | if (direction == AL_START_HEAD) 174 | iter->next = list->head; 175 | else 176 | iter->next = list->tail; 177 | 178 | iter->direction = direction; 179 | return iter; 180 | } 181 | 182 | void listReleaseIterator(ListIter *iter) 183 | { 184 | free(iter); 185 | } 186 | 187 | ListNode *listNext(ListIter *iter) 188 | { 189 | ListNode *current = iter->next; 190 | if (current) { 191 | if (iter->direction == AL_START_HEAD) { 192 | iter->next = current->next; 193 | } else { 194 | iter->next = current->prev; 195 | } 196 | } 197 | return current; 198 | } 199 | 200 | List *listDup(List *orig) 201 | { 202 | List *copy; 203 | ListIter *iter; 204 | ListNode *node; 205 | 206 | if ((copy = listCreate()) == NULL) 207 | return NULL; 208 | 209 | copy->dup = orig->dup; 210 | copy->free = orig->free; 211 | copy->match = orig->match; 212 | 213 | iter = listGetIterator(orig, AL_START_HEAD); 214 | while((node = listNext(iter)) != NULL) { 215 | void *value; 216 | 217 | if (copy->dup) { 218 | value = copy->dup(node->value); 219 | if (value == NULL) { 220 | listRelease(copy); 221 | listReleaseIterator(iter); 222 | return NULL; 223 | } 224 | } else 225 | value = node->value; 226 | 227 | if (listAddNodeTail(copy, value) == NULL) { 228 | listRelease(copy); 229 | listReleaseIterator(iter); 230 | return NULL; 231 | } 232 | } 233 | 234 | listReleaseIterator(iter); 235 | return copy; 236 | } 237 | 238 | void listTraverse(List *list, int direction) 239 | { 240 | ListIter *iter = listGetIterator(list, direction); 241 | ListNode *current; 242 | while ((current = listNext(iter))) { 243 | printf("%s -> ", current->value); 244 | } 245 | printf("NULL\n"); 246 | } 247 | -------------------------------------------------------------------------------- /code/ds/list/adlist/adlist.h: -------------------------------------------------------------------------------- 1 | #ifndef __ADLIST_H__ 2 | #define __ADLIST_H__ 3 | 4 | #define debug 0 5 | 6 | /********************************/ 7 | /*代码取自redis 源码的 adlist.c */ 8 | /********************************/ 9 | 10 | // 链表结点 11 | typedef struct ListNode { 12 | struct ListNode *prev; 13 | struct ListNode *next; 14 | void *value; 15 | } ListNode; 16 | 17 | // 链表 18 | typedef struct List { 19 | ListNode *head; // 链表头 20 | ListNode *tail; // 链表尾 21 | unsigned long len; // 链表长度(结点数) 22 | 23 | void *(*dup)(void *ptr); // 结点复制函数 24 | void (*free)(void *ptr); // 结点释放函数 25 | int (*match)(void *ptr, void *key); // 结点匹配函数 26 | } List; 27 | 28 | // 链表迭代器 29 | typedef struct ListIter { 30 | ListNode *next; // 下一个结点 31 | int direction; // 链表遍历方向 32 | } ListIter; 33 | 34 | #define listLength(l) ((l)->len) 35 | #define listHead(l) ((l)->head) 36 | #define listLast(l) ((l)->tail) 37 | #define listPrevNode(n) ((n)->prev) 38 | #define listNextNode(n) ((n)->next) 39 | #define listNodeValue(n) ((n)->value) 40 | 41 | #define listSetMatchMethod(l, m) ((l)->match = (m)) 42 | #define listSetFreeMethod(l, m) ((l)->free = (m)) 43 | #define listSetDupMethod(l, m) ((l)->dup = (m)) 44 | 45 | List *listCreate(void); 46 | List *listAddNodeHead(List *list, void *value); 47 | List *listAddNodeTail(List *list, void *value); 48 | List *listDup(List *orig); 49 | ListIter *listGetIterator(List *list, int direction); 50 | void listDelNode(List *list, ListNode *node); 51 | void listReleaseIterator(ListIter *iter); 52 | 53 | ListNode *listNewNode(void *value); 54 | ListNode *listNext(ListIter *iter); 55 | ListNode *listSearchKey(List *list, void *key); 56 | ListNode *listIndex(List *list, long index); 57 | 58 | void listTraverse(List *list, int direction); 59 | void listRelease(List *list); 60 | 61 | #define AL_START_HEAD 0 // 从头遍历 62 | #define AL_START_TAIL 1 // 从尾遍历 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /code/ds/list/adlist/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "adlist.h" 5 | 6 | #define ALEN(a) (sizeof(a) / sizeof(a[0])) 7 | 8 | void freeString(void *ptr) 9 | { 10 | free(ptr); 11 | } 12 | 13 | int matchString(void *s1, void *s2) 14 | { 15 | if (strcmp(s1, s2) == 0) 16 | return 1; 17 | else 18 | return 0; 19 | } 20 | 21 | char *newString(char *s) 22 | { 23 | int len = strlen(s); 24 | char *news = (char *)malloc(sizeof(len+1)); 25 | strcpy(news, s); 26 | return news; 27 | } 28 | 29 | void *dupString(void *s1) 30 | { 31 | void *sdup = newString(s1); 32 | return sdup; 33 | } 34 | 35 | 36 | List *createTestListFromArray(char *a[], int len) 37 | { 38 | List *list = listCreate(); 39 | listSetFreeMethod(list, freeString); 40 | listSetDupMethod(list, dupString); 41 | listSetMatchMethod(list, matchString); 42 | int i; 43 | for (i = 0; i < len; i++) { 44 | listAddNodeTail(list, (void *)a[i]); 45 | } 46 | return list; 47 | } 48 | 49 | int main() 50 | { 51 | char *a[] = { 52 | newString("aa"), 53 | newString("bb"), 54 | newString("cc") 55 | }; 56 | List *list = createTestListFromArray(a, ALEN(a)); 57 | listTraverse(list, AL_START_HEAD); 58 | listTraverse(list, AL_START_TAIL); 59 | 60 | ListNode *node = listSearchKey(list, "aa"); 61 | if (node) { 62 | printf("Found Node:%s\n", node->value); 63 | } else { 64 | printf("Not Found"); 65 | } 66 | 67 | node = listIndex(list, 1); 68 | if (node) { 69 | printf("Found index 1 Node:%s\n", node->value); 70 | } else { 71 | printf("Not found\n"); 72 | } 73 | 74 | List *listCopy = listDup(list); 75 | 76 | listDelNode(list, node); 77 | printf("Del Node in index 1\n"); 78 | 79 | listTraverse(list, AL_START_HEAD); 80 | 81 | printf("Traverse dup list\n"); 82 | listTraverse(listCopy, AL_START_HEAD); 83 | listTraverse(listCopy, AL_START_TAIL); 84 | 85 | listRelease(list); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /code/ds/list/aslist/Makefile: -------------------------------------------------------------------------------- 1 | objects = main.o aslist.o 2 | cc = gcc 3 | 4 | main: $(objects) 5 | cc -o $@ $(objects) 6 | 7 | test: clean main 8 | ./main 9 | 10 | .PHONY: clean 11 | clean: 12 | -rm -f main $(objects) 13 | -------------------------------------------------------------------------------- /code/ds/list/aslist/aslist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "aslist.h" 4 | 5 | /** 6 | * 创建链表结点 7 | */ 8 | ListNode *listNewNode(int value) 9 | { 10 | ListNode *node; 11 | if (!(node = malloc(sizeof(ListNode)))) 12 | return NULL; 13 | 14 | node->value = value; 15 | node->next = NULL; 16 | return node; 17 | } 18 | 19 | /** 20 | * 头插法插入结点。 21 | */ 22 | ListNode *listAddNodeHead(ListNode *head, int value) 23 | { 24 | if (debug) { 25 | printf("AddNodeHeader: %d\n", value); 26 | } 27 | 28 | ListNode *node; 29 | if (!(node = listNewNode(value))) 30 | return NULL; 31 | 32 | if (head) 33 | node->next = head; 34 | 35 | head = node; 36 | return head; 37 | } 38 | 39 | /** 40 | * 尾插法插入值为value的结点。 41 | */ 42 | ListNode *listAddNodeTail(ListNode *head, int value) 43 | { 44 | if (debug) { 45 | printf("AddNodeTail: %d\n", value); 46 | } 47 | 48 | ListNode *node; 49 | if (!(node = listNewNode(value))) 50 | return NULL; 51 | 52 | return listAddNodeTailWithNode(head, node); 53 | } 54 | 55 | /** 56 | * 尾插法插入结点。 57 | */ 58 | ListNode *listAddNodeTailWithNode(ListNode *head, ListNode *node) 59 | { 60 | if (!head) { 61 | head = node; 62 | } else { 63 | ListNode *current = head; 64 | while (current->next) { 65 | current = current->next; 66 | } 67 | current->next = node; 68 | } 69 | return head; 70 | } 71 | 72 | /** 73 | * 从链表删除值为value的结点。 74 | */ 75 | ListNode *listDelNode(ListNode *head, int value) 76 | { 77 | if (debug) { 78 | printf("DelNode: %d\n", value); 79 | } 80 | 81 | ListNode *current=head, *prev=NULL; 82 | 83 | while (current) { 84 | if (current->value == value) { 85 | if (current == head) 86 | head = head->next; 87 | 88 | if (prev) 89 | prev->next = current->next; 90 | 91 | free(current); 92 | break; 93 | } 94 | 95 | prev = current; 96 | current = current->next; 97 | } 98 | return head; 99 | } 100 | 101 | /** 102 | * 链表遍历。 103 | */ 104 | void listTraverse(ListNode *head) 105 | { 106 | ListNode *current = head; 107 | while (current) { 108 | printf("%d", current->value); 109 | printf("->"); 110 | current = current->next; 111 | if (current == head) // 处理首尾循环链表情况 112 | break; 113 | } 114 | 115 | printf("NULL\n"); 116 | } 117 | 118 | /** 119 | * 使用数组初始化一个链表,共len个元素。 120 | */ 121 | ListNode *listCreate(int a[], int len) 122 | { 123 | ListNode *head = NULL; 124 | int i; 125 | for (i = 0; i < len; i++) { 126 | if (!(head = listAddNodeTail(head, a[i]))) 127 | return NULL; 128 | } 129 | return head; 130 | } 131 | 132 | /** 133 | * 释放链表 134 | */ 135 | void listRelease(ListNode *head) 136 | { 137 | ListNode *current = head; 138 | while (current) { 139 | ListNode *next = current->next; 140 | free(current); 141 | current = next; 142 | } 143 | } 144 | 145 | int listLength(ListNode *head) 146 | { 147 | int len = 0; 148 | while (head) { 149 | len++; 150 | head = head->next; 151 | } 152 | return len; 153 | } 154 | -------------------------------------------------------------------------------- /code/ds/list/aslist/aslist.h: -------------------------------------------------------------------------------- 1 | #ifndef __ASLIST_H__ 2 | #define __ASLIST_H__ 3 | 4 | #define debug 0 5 | 6 | typedef struct ListNode { 7 | struct ListNode *next; 8 | int value; 9 | } ListNode; 10 | 11 | ListNode *listAddNodeHead(ListNode *head, int value); 12 | ListNode *listAddNodeTail(ListNode *head, int value); 13 | ListNode *listDelNode(ListNode *head, int value); 14 | ListNode *listCreate(int a[], int len); 15 | ListNode *listNewNode(int value); 16 | ListNode *listAddNodeTailWithNode(ListNode *head, ListNode *node); 17 | int listLength(ListNode *head); 18 | 19 | void listTraverse(ListNode *head); 20 | void listRelease(ListNode *head); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /code/ds/list/aslist/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "aslist.h" 6 | 7 | #define ALEN(a) (sizeof(a) / sizeof(a[0])) 8 | 9 | /** 10 | * 链表增减结点测试 11 | */ 12 | void testListModify() 13 | { 14 | printf("\nTestListModify\n"); 15 | int a[] = {1, 2, 3, 4, 5, 6}; 16 | printf("Origin List:\n"); 17 | ListNode *head = listCreate(a, ALEN(a)); 18 | listTraverse(head); 19 | 20 | printf("Del Node 3\n"); 21 | head = listDelNode(head, 3); 22 | listTraverse(head); 23 | 24 | printf("Del Node 1\n"); 25 | head = listDelNode(head, 1); 26 | listTraverse(head); 27 | 28 | head = listDelNode(head, 6); 29 | head = listAddNodeTail(head, 7); 30 | printf("Del Node 6, Add Tail 7\n"); 31 | listTraverse(head); 32 | 33 | head = listDelNode(head, 4); 34 | head = listAddNodeTail(head, 8); 35 | head = listAddNodeHead(head, 9); 36 | printf("Del Node 4, Add Tail 8, Add Head 9\n"); 37 | listTraverse(head); 38 | } 39 | 40 | 41 | /**************/ 42 | /** 链表逆序 **/ 43 | /**************/ 44 | 45 | /** 46 | * 链表逆序,非递归实现。 47 | */ 48 | ListNode *listReverse(ListNode *head) 49 | { 50 | ListNode *newHead = NULL, *current = head; 51 | while (current) { 52 | ListNode *next = current->next; 53 | current->next = newHead; 54 | newHead = current; 55 | current = next; 56 | } 57 | 58 | return newHead; 59 | } 60 | 61 | /** 62 | * 链表逆序,递归实现。 63 | */ 64 | ListNode *listReverseRecursive(ListNode *head) 65 | { 66 | if (!head || !head->next) { 67 | return head; 68 | } 69 | 70 | ListNode *reversedHead = listReverseRecursive(head->next); 71 | head->next->next = head; 72 | head->next = NULL; 73 | return reversedHead; 74 | } 75 | 76 | /** 77 | * 链表逆序测试函数 78 | */ 79 | void testListReverse(void) 80 | { 81 | printf("\nTestListReverse\n"); 82 | int a[] = {1, 2, 3, 4, 5, 6}; 83 | ListNode *initList = listCreate(a, ALEN(a)); 84 | listTraverse(initList); 85 | ListNode *reversedList = listReverse(initList); 86 | listTraverse(reversedList); 87 | 88 | ListNode *reversedListRecursive = listReverseRecursive(reversedList); 89 | listTraverse(reversedListRecursive); 90 | 91 | listRelease(initList); 92 | } 93 | 94 | 95 | /**************/ 96 | /** 链表复制 **/ 97 | /**************/ 98 | 99 | /** 100 | * 链表复制-非递归 101 | */ 102 | ListNode *listCopy(ListNode *head) 103 | { 104 | ListNode *current = head, *newHead = NULL, *newTail = NULL; 105 | while (current) { 106 | ListNode *node = listNewNode(current->value); 107 | if (!newHead) { 108 | newHead = newTail = node; 109 | } else { 110 | newTail->next = node; 111 | newTail = node; 112 | } 113 | current = current->next; 114 | } 115 | return newHead; 116 | } 117 | 118 | /** 119 | * 链表复制-递归 120 | */ 121 | ListNode *listCopyRecursive(ListNode *head) 122 | { 123 | if (!head) 124 | return NULL; 125 | 126 | ListNode *newHead = listNewNode(head->value); 127 | newHead->next = listCopyRecursive(head->next); 128 | return newHead; 129 | } 130 | 131 | /** 132 | * 链表复制测试函数 133 | */ 134 | void testListCopy(void) 135 | { 136 | printf("\nTestListCopy\n"); 137 | int a[] = {1, 2, 3, 4, 5, 6}; 138 | ListNode *initList = listCreate(a, ALEN(a)); 139 | listTraverse(initList); 140 | 141 | ListNode *copyList = listCopyRecursive(initList); 142 | listTraverse(copyList); 143 | 144 | ListNode *reversedList = listReverse(copyList); 145 | listTraverse(reversedList); 146 | 147 | listTraverse(initList); 148 | } 149 | 150 | 151 | /**************/ 152 | /** 链表合并 **/ 153 | /**************/ 154 | 155 | /** 156 | * 链表合并-非递归 157 | */ 158 | ListNode *listMerge(ListNode *list1, ListNode *list2) 159 | { 160 | ListNode dummy; // 使用空结点保存合并链表 161 | ListNode *tail = &dummy; 162 | 163 | if (!list1) 164 | return list2; 165 | 166 | if (!list2) 167 | return list1; 168 | 169 | while (list1 && list2) { 170 | if (list1->value <= list2->value) { 171 | tail->next = list1; 172 | tail = list1; 173 | list1 = list1->next; 174 | } else { 175 | tail->next = list2; 176 | tail = list2; 177 | list2 = list2->next; 178 | } 179 | } 180 | 181 | if (list1) { 182 | tail->next = list1; 183 | } else if (list2) { 184 | tail->next = list2; 185 | } 186 | 187 | return dummy.next; 188 | } 189 | 190 | /** 191 | * 链表合并-递归 192 | */ 193 | ListNode *listMergeRecursive(ListNode *list1, ListNode *list2) 194 | { 195 | ListNode *result = NULL; 196 | 197 | if (!list1) 198 | return list2; 199 | 200 | if (!list2) 201 | return list1; 202 | 203 | if (list1->value <= list2->value) { 204 | result = list1; 205 | result->next = listMergeRecursive(list1->next, list2); 206 | } else { 207 | result = list2; 208 | result->next = listMergeRecursive(list1, list2->next); 209 | } 210 | 211 | return result; 212 | } 213 | 214 | /** 215 | * 链表合并测试函数 216 | */ 217 | void testListMerge() 218 | { 219 | printf("\nTestListMerge\n"); 220 | int a1[] = {1, 3, 5}; 221 | int a2[] = {2, 4, 6, 7, 8}; 222 | 223 | ListNode *list1 = listCreate(a1, ALEN(a1)); 224 | ListNode *list2 = listCreate(a2, ALEN(a2)); 225 | ListNode *listMerged = listMerge(list1, list2); 226 | listTraverse(listMerged); 227 | 228 | list1 = listCreate(a1, ALEN(a1)); 229 | list2 = listCreate(a2, ALEN(a2)); 230 | listMerged = listMergeRecursive(list2, list1); 231 | listTraverse(listMerged); 232 | } 233 | 234 | 235 | /**************/ 236 | /** 链表相交 **/ 237 | /**************/ 238 | 239 | /** 240 | * 链表相交判断,如果相交返回相交的结点,否则返回NULL。 241 | */ 242 | ListNode *listIntersect(ListNode *list1, ListNode *list2) 243 | { 244 | int len1 = listLength(list1); 245 | int len2 = listLength(list2); 246 | int delta = abs(len1 - len2); 247 | 248 | ListNode *longList = list1, *shortList = list2; 249 | 250 | if (len1 < len2) { 251 | longList = list2; 252 | shortList = list1; 253 | } 254 | 255 | int i; 256 | for (i = 0; i < delta; i++) { 257 | longList = longList->next; 258 | } 259 | 260 | while (longList && shortList) { 261 | if (longList == shortList) 262 | return longList; 263 | 264 | longList = longList->next; 265 | shortList = shortList->next; 266 | } 267 | 268 | return NULL; 269 | } 270 | 271 | /** 272 | * 链表相交测试函数 273 | */ 274 | void testListIntersect() 275 | { 276 | printf("\nTestListIntersect\n"); 277 | int a1[] = {1, 3, 5}; 278 | int a2[] = {2, 4, 6, 7}; 279 | 280 | ListNode *list1 = listCreate(a1, ALEN(a1)); 281 | ListNode *list2 = listCreate(a2, ALEN(a2)); 282 | 283 | ListNode *sameNode = listIntersect(list1, list2); 284 | assert(sameNode == NULL); 285 | 286 | listTraverse(list1); 287 | listTraverse(list2); 288 | 289 | ListNode *sameNode1 = listNewNode(8); 290 | ListNode *sameNode2 = listNewNode(9); 291 | 292 | list1 = listAddNodeTailWithNode(list1, sameNode1); 293 | list1 = listAddNodeTailWithNode(list1, sameNode2); 294 | list2 = listAddNodeTailWithNode(list2, sameNode1); 295 | listTraverse(list1); 296 | listTraverse(list2); 297 | 298 | sameNode = listIntersect(list1, list2); 299 | assert(sameNode == sameNode1); 300 | } 301 | 302 | 303 | /******************/ 304 | /** 链表模拟加法 **/ 305 | /******************/ 306 | 307 | /** 308 | * 链表模拟加法-非递归解法 309 | */ 310 | ListNode *listEnumarateAdd(ListNode *list1, ListNode *list2) 311 | { 312 | int carry = 0; 313 | ListNode *result = NULL; 314 | 315 | while (list1 || list2 || carry) { 316 | int value = carry; 317 | if (list1) { 318 | value += list1->value; 319 | list1 = list1->next; 320 | } 321 | 322 | if (list2) { 323 | value += list2->value; 324 | list2 = list2->next; 325 | } 326 | 327 | result = listAddNodeTail(result, value % 10); 328 | carry = ( value >= 10 ? 1: 0); 329 | } 330 | 331 | return result; 332 | } 333 | 334 | /** 335 | * 链表模拟加法-递归解法 336 | */ 337 | ListNode *listEnumarateAddRecursive(ListNode *list1, ListNode *list2, int carry) 338 | { 339 | if (!list1 && !list2 && carry==0) 340 | return NULL; 341 | 342 | int value = carry; 343 | if (list1) 344 | value += list1->value; 345 | 346 | if (list2) 347 | value += list2->value; 348 | 349 | ListNode *next1 = list1 ? list1->next : NULL; 350 | ListNode *next2 = list2 ? list2->next : NULL; 351 | ListNode *more = listEnumarateAddRecursive(next1, next2, (value >= 10 ? 1 : 0)); 352 | ListNode *result = listNewNode(carry); 353 | result->value = value % 10; 354 | result->next = more; 355 | 356 | return result; 357 | } 358 | 359 | /** 360 | * 链表模拟加法测试代码 361 | */ 362 | void testListEnumerateAdd() 363 | { 364 | printf("\nTestListEnumerateAdd\n"); 365 | int a1[] = {1, 3, 5}; 366 | int a2[] = {2, 4, 6, 7}; 367 | 368 | ListNode *list1 = listCreate(a1, ALEN(a1)); 369 | ListNode *list2 = listCreate(a2, ALEN(a2)); 370 | ListNode *result = listEnumarateAdd(list1, list2); 371 | listTraverse(result); 372 | 373 | int a3[] = {1, 3, 5}; 374 | int a4[] = {2, 8, 6, 7}; 375 | ListNode *list3 = listCreate(a3, ALEN(a3)); 376 | ListNode *list4 = listCreate(a4, ALEN(a4)); 377 | result = listEnumarateAddRecursive(list3, list4, 0); 378 | listTraverse(result); 379 | } 380 | 381 | 382 | /******************/ 383 | /*** 链表环判断 ***/ 384 | /******************/ 385 | /** 386 | * 检测链表是否有环-Flod判圈算法 387 | * 若存在环,返回相遇结点,否则返回NULL 388 | */ 389 | ListNode *listDetectLoop(ListNode *head) 390 | { 391 | ListNode *slow, *fast; 392 | slow = fast = head; 393 | 394 | while (slow && fast && fast->next) { 395 | slow = slow->next; 396 | fast = fast->next->next; 397 | if (slow == fast) { 398 | return slow; 399 | } 400 | } 401 | 402 | return NULL; 403 | } 404 | 405 | /** 406 | * 查找链表中环入口 407 | */ 408 | ListNode *findLoopNode(ListNode *head) 409 | { 410 | ListNode *meetNode = listDetectLoop(head); 411 | if (!meetNode) 412 | return NULL; 413 | 414 | ListNode *headNode = head; 415 | while (meetNode != headNode) { 416 | meetNode = meetNode->next; 417 | headNode = headNode->next; 418 | } 419 | return meetNode; 420 | } 421 | 422 | /** 423 | * 检测链表是否有环测试函数 424 | */ 425 | void testListDetectLoop() 426 | { 427 | printf("\nTestListDetectLoop\n"); 428 | int a[] = {1, 2, 3, 4}; 429 | ListNode *head = listCreate(a, ALEN(a)); 430 | ListNode *ret = listDetectLoop(head); 431 | if (ret) { 432 | printf("Found Loop\n"); 433 | } else { 434 | printf("No Loop\n"); 435 | } 436 | 437 | // 构造一个环 438 | head->next->next->next = head; 439 | ListNode *loopNode = findLoopNode(head); 440 | if (loopNode) { 441 | printf("Found Loop, start at:%d\n", loopNode->value); 442 | } else { 443 | printf("No Loop\n"); 444 | } 445 | } 446 | 447 | 448 | /**********************/ 449 | /*有序循环链表插入结点*/ 450 | /**********************/ 451 | 452 | /** 453 | * 简化版-有序无循环链表插入结点 454 | */ 455 | ListNode *sortedListAddNode(ListNode *head, int value) 456 | { 457 | ListNode *node = listNewNode(value); 458 | if (!head || head->value >= value) { //情况1 459 | node->next = head; 460 | head = node; 461 | } else { //情况2 462 | ListNode *current = head; 463 | while (current->next != NULL && current->next->value < value) 464 | current = current->next; 465 | node->next = current->next; 466 | current->next = node; 467 | } 468 | return head; 469 | } 470 | 471 | /** 472 | * 简化版-有序无循环链表插入结点(两种情况一起处理) 473 | */ 474 | void sortedListAddNodeUnify(ListNode **head, int value) 475 | { 476 | ListNode *node = listNewNode(value); 477 | ListNode **current = head; 478 | while ((*current) && (*current)->value < value) { 479 | current = &((*current)->next); 480 | } 481 | node->next = *current; 482 | *current = node; 483 | } 484 | 485 | /** 486 | * 有序循环链表插入结点 487 | */ 488 | ListNode *sortedLoopListAddNode(ListNode *head, int value) 489 | { 490 | ListNode *node = listNewNode(value); 491 | ListNode *current = head, *prev = NULL; 492 | do { 493 | prev = current; 494 | current = current->next; 495 | if (value >= prev->value && value <= current->value) 496 | break; 497 | } while (current != head); 498 | 499 | prev->next = node; 500 | node->next = current; 501 | 502 | if (current == head && value < current->value) // 判断是否要设置链表头 503 | head = node; 504 | 505 | return head; 506 | } 507 | 508 | /** 509 | * 有序循环链表插入结点测试函数 510 | */ 511 | void testSortedAddNode() 512 | { 513 | printf("\nTestSortedAddNode\n"); 514 | int a[] = {2, 4, 5}; 515 | ListNode *head = listCreate(a, ALEN(a)); 516 | listTraverse(head); 517 | 518 | // 添加到头部 519 | ListNode *newHead = sortedListAddNode(head, 1); 520 | listTraverse(newHead); 521 | 522 | // 添加到链表中间 523 | sortedListAddNodeUnify(&newHead, 3); 524 | listTraverse(newHead); 525 | 526 | // 添加到链表尾部 527 | sortedListAddNodeUnify(&newHead, 6); 528 | listTraverse(newHead); 529 | 530 | head = listCreate(a, ALEN(a)); 531 | head->next->next->next = head; // 构造循环 532 | listTraverse(head); 533 | head = sortedLoopListAddNode(head, 2); // 插入相等的值测试 534 | listTraverse(head); 535 | head = sortedLoopListAddNode(head, 1); 536 | listTraverse(head); 537 | head = sortedLoopListAddNode(head, 3); 538 | listTraverse(head); 539 | head = sortedLoopListAddNode(head, 6); 540 | listTraverse(head); 541 | 542 | } 543 | 544 | 545 | /*******************/ 546 | /*链表倒数第K个结点*/ 547 | /*******************/ 548 | 549 | /** 550 | * 链表倒数第K个结点-遍历两次算法 551 | */ 552 | ListNode *getLastKthNodeTwice(ListNode *head, int k) 553 | { 554 | int len = listLength(head); 555 | if (k > len) 556 | return NULL; 557 | 558 | ListNode *current = head; 559 | int i; 560 | for (i = 0; i < len-k; i++) //遍历链表,找出第N-K+1个结点 561 | current = current->next; 562 | 563 | return current; 564 | } 565 | 566 | /** 567 | * 链表倒数第K个结点-遍历一次算法 568 | */ 569 | ListNode *getLastKthNodeOnce(ListNode *head, int k) 570 | { 571 | ListNode *p1, *p2; 572 | p1 = p2 = head; 573 | 574 | for(; k > 0; k--) { 575 | if (!p2) // 链表长度不够K 576 | return NULL; 577 | p2 = p2->next; 578 | } 579 | 580 | while (p2) { 581 | p1 = p1->next; 582 | p2 = p2->next; 583 | } 584 | return p1; 585 | } 586 | 587 | /** 588 | * 链表倒数第K个结点测试函数 589 | */ 590 | void testGetLastKthNode() 591 | { 592 | printf("\nTestGetLastKthNode\n"); 593 | int a[] = {1, 2, 3, 4}; 594 | ListNode *head = listCreate(a, ALEN(a)); 595 | 596 | int k = 3; 597 | ListNode *node = getLastKthNodeTwice(head, k); 598 | if (node) { 599 | printf("Last Kth Node(Twice): %d\n", node->value); 600 | } else { 601 | printf("K > List LEN\n"); 602 | } 603 | 604 | node = getLastKthNodeOnce(head, k); 605 | if (node) { 606 | printf("Last Kth Node(Once): %d\n", node->value); 607 | } else { 608 | printf("K > List LEN\n"); 609 | } 610 | } 611 | 612 | int main(void) 613 | { 614 | testListModify(); 615 | testListReverse(); 616 | testListCopy(); 617 | testListMerge(); 618 | testListIntersect(); 619 | testListEnumerateAdd(); 620 | testListDetectLoop(); 621 | testGetLastKthNode(); 622 | testSortedAddNode(); 623 | return 0; 624 | } 625 | -------------------------------------------------------------------------------- /code/ds/stack/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | OBJECTS = main.o stack.o 3 | 4 | main: $(OBJECTS) 5 | $(CC) -o $@ $(OBJECTS) 6 | 7 | test: clean main 8 | ./main 9 | 10 | .PHONY: clean 11 | clean: 12 | -rm -f main $(OBJECTS) 13 | -------------------------------------------------------------------------------- /code/ds/stack/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "stack.h" 6 | 7 | /****************/ 8 | /** 栈基本操作 **/ 9 | /***************/ 10 | 11 | void testStackOperation() 12 | { 13 | Stack *stack = stackNew(5); 14 | if (!stack) { 15 | printf("No memory\n"); 16 | return; 17 | } 18 | 19 | push(stack, 1); 20 | push(stack, 2); 21 | push(stack, 3); 22 | 23 | printf("Top element is %d\n", peek(stack)); 24 | printf("Stack size is %d\n", peek(stack)); 25 | pop(stack); 26 | pop(stack); 27 | pop(stack); 28 | 29 | if (IS_EMPTY(stack)) { 30 | printf("Stack is empty\n"); 31 | } else { 32 | printf("Stack is not empty\n"); 33 | } 34 | } 35 | 36 | 37 | /*****************/ 38 | /* 后缀表达式求值*/ 39 | /*****************/ 40 | 41 | int evaluatePostfix(char *exp) 42 | { 43 | Stack* stack = stackNew(strlen(exp)); 44 | int i; 45 | 46 | if (!stack) { 47 | printf("New stack failed\n"); 48 | exit(E_NOMEM); 49 | } 50 | 51 | for (i = 0; exp[i]; ++i) { 52 | // 如果是数字,直接压栈 53 | if (isdigit(exp[i])) { 54 | push(stack, exp[i] - '0'); 55 | } else {// 如果遇到符号,则弹出栈顶两个元素计算,并将结果压栈 56 | int val1 = pop(stack); 57 | int val2 = pop(stack); 58 | switch (exp[i]) 59 | { 60 | case '+': push(stack, val2 + val1); break; 61 | case '-': push(stack, val2 - val1); break; 62 | case '*': push(stack, val2 * val1); break; 63 | case '/': push(stack, val2/val1); break; 64 | } 65 | } 66 | } 67 | 68 | return pop(stack); 69 | } 70 | 71 | void testEvaluatePostfix() 72 | { 73 | char *exp = "6523+8*+3+*"; 74 | int ret = evaluatePostfix(exp); 75 | printf("EvaluatePostfix result:%d\n", ret); 76 | } 77 | 78 | 79 | /*****************/ 80 | /*** 栈逆序 ******/ 81 | /*****************/ 82 | 83 | /** 84 | * 在栈底插入一个元素 85 | */ 86 | void insertAtBottom(Stack *stack, int v) 87 | { 88 | if (IS_EMPTY(stack)) { 89 | push(stack, v); 90 | } else { 91 | int x = pop(stack); 92 | insertAtBottom(stack, v); 93 | push(stack, x); 94 | } 95 | } 96 | 97 | /** 98 | * 栈逆序 99 | */ 100 | void stackReverse(Stack *stack) 101 | { 102 | if (IS_EMPTY(stack)) 103 | return; 104 | 105 | int top = pop(stack); 106 | stackReverse(stack); 107 | insertAtBottom(stack, top); 108 | } 109 | 110 | /** 111 | * 栈逆序测试函数 112 | */ 113 | void testStackReverse() 114 | { 115 | int capacity = 4; 116 | Stack *stack = stackNew(capacity); 117 | 118 | int i; 119 | for (i = 1; i <= capacity; i++) { 120 | push(stack, i); 121 | } 122 | stackTraverseTop(stack); 123 | printf("\n"); 124 | stackReverse(stack); 125 | stackTraverseTop(stack); 126 | printf("\n"); 127 | } 128 | 129 | /*********************************/ 130 | /*设计包含min函数的栈- 辅助栈方式*/ 131 | /*********************************/ 132 | void minStackPush(Stack *orgStack, Stack *minStack, int v) 133 | { 134 | if (IS_FULL(orgStack)) { 135 | printf("Stack Full\n"); 136 | exit(E_FULL); 137 | } 138 | 139 | push(orgStack, v); 140 | if (IS_EMPTY(minStack) || v < peek(minStack)) { 141 | push(minStack, v); 142 | } 143 | } 144 | 145 | int minStackPop(Stack *orgStack, Stack *minStack) 146 | { 147 | if (IS_EMPTY(orgStack)) { 148 | printf("Stack Empty\n"); 149 | exit(E_EMPTY); 150 | } 151 | 152 | if (peek(orgStack) == peek(minStack)) { 153 | pop(minStack); 154 | } 155 | return pop(orgStack); 156 | } 157 | 158 | int minStackMin(Stack *minStack) 159 | { 160 | return peek(minStack); 161 | } 162 | 163 | 164 | /********************************/ 165 | /** 设计包含min函数的栈-差值法 **/ 166 | /********************************/ 167 | void minStackPushUseDelta(Stack *stack, int v) 168 | { 169 | if (IS_EMPTY(stack)) { // 空栈,直接压入v两次 170 | push(stack, v); 171 | push(stack, v); 172 | } else { 173 | int oldMin = pop(stack); // 栈顶保存的是压入v之前的栈中最小值 174 | int delta = v - oldMin; 175 | int newMin = delta < 0 ? v : oldMin; 176 | push(stack, delta); // 压入 v 与之前栈中的最小值之差 177 | push(stack, newMin); // 最后压入当前栈中最小值 178 | } 179 | } 180 | 181 | int minStackPopUseDelta(Stack *stack) 182 | { 183 | int min = pop(stack); 184 | int delta = pop(stack); 185 | int v, oldMin; 186 | 187 | if (delta < 0) { // 最后压入的元素比min小,则min就是最后压入的元素 188 | v = min; 189 | oldMin = v - delta; 190 | } else { // 最后压入的值不是最小值,则min为oldMin。 191 | oldMin = min; 192 | v = oldMin + delta; 193 | } 194 | 195 | if (!IS_EMPTY(stack)) { // 如果栈不为空,则压入oldMin 196 | push(stack, oldMin); 197 | } 198 | return v; 199 | } 200 | 201 | int minStackMinUseDelta(Stack *stack) 202 | { 203 | return peek(stack); 204 | } 205 | 206 | void testMinStack() 207 | { 208 | int capacity = 5; 209 | Stack *orgStack = stackNew(capacity); 210 | Stack *minStack = stackNew(capacity); 211 | minStackPush(orgStack, minStack, 3); 212 | printf("Stack min:%d\n", minStackMin(minStack)); 213 | minStackPush(orgStack, minStack, 4); 214 | minStackPush(orgStack, minStack, 2); 215 | minStackPush(orgStack, minStack, 5); 216 | minStackPush(orgStack, minStack, 1); 217 | printf("Stack min:%d\n", minStackMin(minStack)); 218 | minStackPop(orgStack, minStack); 219 | minStackPop(orgStack, minStack); 220 | printf("Stack min:%d\n", minStackMin(minStack)); 221 | 222 | minStack = stackNew(capacity + 1); 223 | minStackPushUseDelta(minStack, 3); 224 | minStackPushUseDelta(minStack, 4); 225 | minStackPushUseDelta(minStack, 2); 226 | minStackPushUseDelta(minStack, 5); 227 | minStackPushUseDelta(minStack, 1); 228 | printf("Stack Delta min:%d\n", minStackMinUseDelta(minStack)); 229 | minStackPopUseDelta(minStack); 230 | printf("Stack Delta min:%d\n", minStackMinUseDelta(minStack)); 231 | minStackPopUseDelta(minStack); 232 | printf("Stack Delta min:%d\n", minStackMinUseDelta(minStack)); 233 | minStackPopUseDelta(minStack); 234 | printf("Stack Delta min:%d\n", minStackMinUseDelta(minStack)); 235 | minStackPopUseDelta(minStack); 236 | printf("Stack Delta min:%d\n", minStackMinUseDelta(minStack)); 237 | } 238 | 239 | 240 | /***************/ 241 | /** 出栈数目 **/ 242 | /**************/ 243 | 244 | /** 245 | * 计算出栈数目 246 | * - in:目前栈中的元素数目 247 | * - out:目前已经出栈的元素数目 248 | * - wait:目前还未进栈的元素数目 249 | */ 250 | int sumOfStackPopSequence(Stack *stack, int in, int out, int wait) 251 | { 252 | if (out == stack->capacity) { // 元素全部出栈了,则总数+1 253 | return 1; 254 | } 255 | 256 | int sum = 0; 257 | 258 | if (wait > 0) 259 | sum += sumOfStackPopSequence(stack, in + 1, out, wait - 1); 260 | 261 | if (in > 0) 262 | sum += sumOfStackPopSequence(stack, in - 1, out + 1, wait); 263 | 264 | return sum; 265 | } 266 | 267 | /** 268 | * 打印所有出栈序列。 269 | */ 270 | void printStackPopSequence(int input[], int i, int n, Stack *stk, Stack *output) 271 | { 272 | if (i >= n) { 273 | stackTraverseBottom(output); // output 从栈底开始打印 274 | stackTraverseTop(stk); // stk 从栈顶开始打印 275 | printf("\n"); 276 | return; 277 | } 278 | 279 | push(stk, input[i]); 280 | printStackPopSequence(input, i+1, n, stk, output); 281 | pop(stk); 282 | 283 | if (IS_EMPTY(stk)) 284 | return; 285 | 286 | int v = pop(stk); 287 | push(output, v); 288 | printStackPopSequence(input, i, n, stk, output); 289 | push(stk, v); 290 | pop(output); 291 | } 292 | 293 | void testSumOfStackPopSequence() 294 | { 295 | int input[] = {1, 2, 3}; 296 | int size = sizeof(input) / sizeof(input[0]); 297 | 298 | Stack *stack = stackNew(size); 299 | int ret = sumOfStackPopSequence(stack, 0, 0, size); 300 | printf("Sum of Pop Sequence:%d\n", ret); 301 | 302 | Stack *stk = stackNew(size); 303 | Stack *output = stackNew(size); 304 | printStackPopSequence(input, 0, size, stk, output); 305 | } 306 | 307 | int main() 308 | { 309 | testStackOperation(); 310 | testEvaluatePostfix(); 311 | testMinStack(); 312 | testSumOfStackPopSequence(); 313 | testStackReverse(); 314 | return 0; 315 | } 316 | -------------------------------------------------------------------------------- /code/ds/stack/stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "stack.h" 4 | 5 | /** 6 | * 初始化栈 7 | */ 8 | Stack *stackNew(int capacity) 9 | { 10 | Stack *stack = (Stack *)malloc(sizeof(*stack) + sizeof(int) * capacity); 11 | if (!stack) { 12 | printf("Stack new failed\n"); 13 | exit(E_NOMEM); 14 | } 15 | 16 | stack->capacity = capacity; 17 | stack->top = -1; 18 | return stack; 19 | } 20 | 21 | /** 22 | * 入栈 23 | */ 24 | void push(Stack *stack, int v) 25 | { 26 | if (IS_FULL(stack)) { 27 | printf("Push: Stack Overflow\n"); 28 | exit(E_FULL); 29 | } 30 | stack->items[++stack->top] = v; 31 | } 32 | 33 | /** 34 | * 出栈 35 | */ 36 | int pop(Stack *stack) 37 | { 38 | if (IS_EMPTY(stack)) { 39 | printf("Pop: Stack Empty\n"); 40 | exit(E_EMPTY); 41 | } 42 | 43 | return stack->items[stack->top--]; 44 | } 45 | 46 | /** 47 | * 返回栈顶元素 48 | */ 49 | int peek(Stack *stack) 50 | { 51 | if (IS_EMPTY(stack)) { 52 | printf("Peek: Stack Empty\n"); 53 | exit(E_EMPTY); 54 | } 55 | return stack->items[stack->top]; 56 | } 57 | 58 | /** 59 | * 从栈顶遍历 60 | */ 61 | void stackTraverseTop(Stack *stack) 62 | { 63 | int i; 64 | for (i = SIZE(stack) - 1; i >= 0; i--) { 65 | printf("%d ", stack->items[i]); 66 | } 67 | } 68 | 69 | /** 70 | * 从栈底遍历 71 | */ 72 | void stackTraverseBottom(Stack *stack) 73 | { 74 | int i; 75 | for (i = 0; i < SIZE(stack); i++) { 76 | printf("%d ", stack->items[i]); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /code/ds/stack/stack.h: -------------------------------------------------------------------------------- 1 | #ifndef __STACK_H 2 | #define __STACK_H 3 | 4 | typedef struct Stack { 5 | int capacity; 6 | int top; 7 | int items[]; 8 | } Stack; 9 | 10 | #define SIZE(stack) (stack->top + 1) 11 | #define IS_EMPTY(stack) (stack->top == -1) 12 | #define IS_FULL(stack) (stack->top == stack->capacity - 1) 13 | 14 | #define E_FULL -1 15 | #define E_EMPTY -2 16 | #define E_NOMEM -3 17 | 18 | Stack *stackNew(int capacity); 19 | void push(Stack *stack, int x); 20 | int pop(Stack *stack); 21 | int peek(Stack *stack); 22 | void stackTraverseTop(Stack *stack); 23 | void stackTraverseBottom(Stack *stack); 24 | 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /code/ds/string/Makefile: -------------------------------------------------------------------------------- 1 | CC = g++ 2 | OBJECTS = main.o 3 | 4 | main: $(OBJECTS) 5 | $(CC) -o $@ $(OBJECTS) 6 | 7 | test: clean main 8 | ./main 9 | 10 | .PHONY: clean 11 | clean: 12 | -rm -f main $(OBJECTS) 13 | -------------------------------------------------------------------------------- /code/ds/string/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | #define ALEN(a) (sizeof(a) / sizeof(a[0])) 8 | 9 | int matchstar(int c, const char *regexp, const char *text); 10 | int matchhere(const char *regexp, const char *text); 11 | 12 | 13 | /****************/ 14 | /**最长回文子串**/ 15 | /****************/ 16 | 17 | /** 18 | * 判断字符串s[start:end]是否是回文字符串 19 | */ 20 | int isPalindrome(string s, int start, int end) 21 | { 22 | for (; start < end; ++start,--end) { 23 | if (s[start] != s[end]) 24 | return 0; 25 | } 26 | return 1; 27 | } 28 | 29 | /** 30 | * 最长回文子串-蛮力法 O(N^3) 31 | */ 32 | string longestPalindrome(string s) 33 | { 34 | int len = s.length(), maxLen = 1; 35 | int start=0, i, j; 36 | 37 | /*遍历字符串所有的子串,若子串为回文字符串则更新最长回文的长度*/ 38 | for (i = 0; i < len - 1; i++) { 39 | for (j = i + 1; j < len; j++) { 40 | if (isPalindrome(s, i, j)) { //如果str[i,j]是回文,则判断其长度是否大于最大值,大于则更新长度和位置 41 | int pLen = j - i + 1; 42 | if (pLen > maxLen) { 43 | start = i; //更新最长回文起始位置 44 | maxLen = pLen; //更新最长回文的长度 45 | } 46 | } 47 | } 48 | } 49 | return s.substr(start, maxLen); 50 | } 51 | 52 | /** 53 | * 最长回文子串-动态规划法,该方法的时间复杂度为O(N^2),空间复杂度为O(N^2)。 54 | * 55 | * 思想:定义P[i, j] = 1 如果子串P[i, j]是回文字符串。 56 | * 则 P[i, j] <- (P[i+1, j-1] && s[i] == s[j])。 57 | * 58 | * Base Case: 59 | * P[ i, i ] <- 1 60 | * P[ i, i+1 ] <- s[i] == s[i+1] 61 | */ 62 | string longestPalindromeDP(string s) 63 | { 64 | int n = s.length(); 65 | int longestBegin = 0, maxLen = 1; 66 | 67 | int **P; 68 | int i; 69 | 70 | /*构造二维数组P*/ 71 | P = (int **)calloc(n, sizeof(int *)); 72 | for (i = 0; i < n; i++) { 73 | P[i] = (int *)calloc(n, sizeof(int)); 74 | } 75 | 76 | 77 | for (i = 0; i < n; i++) { 78 | P[i][i] = 1; 79 | } 80 | 81 | for (int i=0; i=0 && r<=n-1 && s[l]==s[r]) { 117 | l--, r++; 118 | } 119 | 120 | *longestBegin = l + 1; 121 | *longestLen = r - l - 1; 122 | } 123 | 124 | /** 125 | * 最长回文子串-中心法,时间O(N^2)。 126 | */ 127 | string longestPalindromeCenter(string s) 128 | { 129 | int n = s.length(); 130 | if (n == 0) 131 | return s; 132 | 133 | char longestBegin = 0; 134 | int longestLen = 1; 135 | 136 | for (int i = 0; i < n; i++) { 137 | int iLongestBegin, iLongestLen; 138 | expandAroundCenter(s, i, i, &iLongestBegin, &iLongestLen); //以位置i为中心的最长回文字符串 139 | if (iLongestLen > longestLen) { 140 | longestLen = iLongestLen; 141 | longestBegin = iLongestBegin; 142 | } 143 | 144 | expandAroundCenter(s, i, i+1, &iLongestBegin, &iLongestLen); //以i和i+1之间的位置为中心的最长回文字符串 145 | if (iLongestLen > longestLen) { 146 | longestLen = iLongestLen; 147 | longestBegin = iLongestBegin; 148 | } 149 | } 150 | return s.substr(longestBegin, longestLen); 151 | } 152 | 153 | /** 154 | * 最长回文子串测试函数 155 | */ 156 | void testLongestPalindrome() 157 | { 158 | string s = "abcbaef"; 159 | printf("String:%s LongestPalindrome: %s\n", s.c_str(), longestPalindrome(s).c_str()); 160 | printf("String:%s LongestPalindromeDP: %s\n", s.c_str(), longestPalindromeDP(s).c_str()); 161 | printf("String:%s LongestPalindromeCenter: %s\n", s.c_str(), longestPalindromeCenter(s).c_str()); 162 | 163 | string s2 = "abba"; 164 | printf("String:%s LongestPalindrome: %s\n", s2.c_str(), longestPalindrome(s2).c_str()); 165 | printf("String:%s LongestPalindromeDP: %s\n", s2.c_str(), longestPalindromeDP(s2).c_str()); 166 | printf("String:%s LongestPalindromeCenter: %s\n", s2.c_str(), longestPalindromeCenter(s2).c_str()); 167 | } 168 | 169 | 170 | /****************/ 171 | /**** RGB排序 ***/ 172 | /****************/ 173 | 174 | void swapChar(char *s, int i, int j) 175 | { 176 | char temp = s[i]; 177 | s[i] = s[j]; 178 | s[j] = temp; 179 | } 180 | 181 | /** 182 | * 划分函数 183 | */ 184 | void partition(char *s, int lo, int hi, char t) 185 | { 186 | int m = lo-1, i; 187 | for (i = lo; i <= hi; i++) { 188 | if (s[i] != t) { 189 | swapChar(s, ++m ,i); 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * RGB排序-遍历两次 196 | */ 197 | void rgbSortTwice(char *s) 198 | { 199 | int len = strlen(s); 200 | partition(s, 0, len-1, 'G'); // 以G划分,划分完为 RBBRBBGGGG 201 | partition(s, 0, len-1, 'B'); // 再以B划分,划分完为 RRGGGGBBBB 202 | } 203 | 204 | /** 205 | * RGB排序-遍历一次 206 | */ 207 | void rgbSortOnce(char *s) 208 | { 209 | int len = strlen(s); 210 | int lo = 0, hi = len - 1; 211 | 212 | int r, g, i; //++r和++g分别指向R和G交换的位置 213 | r = g = lo - 1; 214 | 215 | for (i = lo; i <= hi; i++) { 216 | if (s[i] == 'R') { // 遇到R 217 | swapChar(s, ++r, i); 218 | ++g; 219 | if (s[i] == 'G') // 交换后的值是G,继续交换 220 | swapChar(s, g, i); 221 | } else if (s[i] == 'G') { // 遇到G 222 | swapChar(s, ++g, i); 223 | } else { // 遇到B,什么都不做 224 | } 225 | } 226 | } 227 | 228 | 229 | /** 230 | * RGB排序测试函数 231 | */ 232 | void testRGBSort() 233 | { 234 | char s[] = "RGBBRGGBGB"; 235 | printf("Org:%s\n", s); 236 | rgbSortTwice(s); 237 | printf("Sorted:%s\n", s); 238 | char s2[] = "RGBBRGGBBGB"; 239 | rgbSortOnce(s2); 240 | printf("SortedOnce:%s\n", s2); 241 | } 242 | 243 | 244 | /******************/ 245 | /** 最大滑动窗口 **/ 246 | /******************/ 247 | 248 | /* 249 | * 求数组最大值 250 | */ 251 | int maxInArray(int A[], int n) 252 | { 253 | int max = A[0], i; 254 | for (i = 1; i < n; i++) { 255 | if (A[i] > max) { 256 | max = A[i]; 257 | } 258 | } 259 | return max; 260 | } 261 | 262 | /* 263 | * 最大滑动窗口-简单实现 264 | */ 265 | void maxSlidingWindowSimple(int A[], int n, int w, int B[]) 266 | { 267 | int i; 268 | for (i = 0; i <= n-w; i++) 269 | B[i] = maxInArray(A + i, w); 270 | } 271 | 272 | /** 273 | * 最大滑动窗口-最大堆解法 274 | */ 275 | void maxSlidingWindowPQ(int A[], int n, int w, int B[]) 276 | { 277 | typedef pair Pair; 278 | priority_queue Q; //优先级队列保存窗口里面的值 279 | 280 | for (int i = 0; i < w; i++) 281 | Q.push(Pair(A[i], i)); //构建w个元素的最大堆 282 | 283 | for (int i = w; i < n; i++) { 284 | Pair p = Q.top(); 285 | B[i-w] = p.first; 286 | while (p.second <= i-w) { 287 | Q.pop(); 288 | p = Q.top(); 289 | } 290 | Q.push(Pair(A[i], i)); 291 | } 292 | B[n-w] = Q.top().first; 293 | } 294 | 295 | /** 296 | * 最大滑动窗口-双向队列 297 | */ 298 | void maxSlidingWindowDQ(int A[], int n, int w, int B[]) 299 | { 300 | deque Q; 301 | for (int i = 0; i < w; i++) { 302 | while (!Q.empty() && A[i] >= A[Q.back()]) 303 | Q.pop_back(); 304 | Q.push_back(i); 305 | } 306 | 307 | for (int i = w; i < n; i++) { 308 | B[i-w] = A[Q.front()]; 309 | while (!Q.empty() && A[i] >= A[Q.back()]) 310 | Q.pop_back(); 311 | 312 | while (!Q.empty() && Q.front() <= i-w) 313 | Q.pop_front(); 314 | 315 | Q.push_back(i); 316 | } 317 | B[n-w] = A[Q.front()]; 318 | } 319 | 320 | void printIntArray(int a[], int len) 321 | { 322 | for (int i = 0; i < len; i++) { 323 | printf("%d ", a[i]); 324 | } 325 | printf("\n"); 326 | } 327 | 328 | /** 329 | * 最大滑动窗口测试函数 330 | */ 331 | void testMaxSlidingWindow() 332 | { 333 | printf("Test MaxSlidingWindow:\n"); 334 | int A[] = {1, 3, -1, -3, 5, 3, 6, 7}; 335 | int w = 3; 336 | int n = ALEN(A); 337 | int *B = (int *)calloc(sizeof(int), n); 338 | maxSlidingWindowSimple(A, n, w, B); 339 | printIntArray(B, n - w + 1); 340 | free(B); 341 | 342 | B = (int *)calloc(sizeof(int), n); 343 | maxSlidingWindowPQ(A, n, w, B); 344 | printIntArray(B, n - w + 1); 345 | free(B); 346 | 347 | B = (int *)calloc(sizeof(int), n); 348 | maxSlidingWindowDQ(A, n, w, B); 349 | printIntArray(B, n - w + 1); 350 | free(B); 351 | } 352 | 353 | 354 | /*****************/ 355 | /** 字符串全排列 */ 356 | /****************/ 357 | void perm(char *arr, int k, int len) { //k为起始位置,len为数组大小 358 | if (k == len-1) { 359 | printf("%s\n", arr); 360 | return; 361 | } 362 | 363 | for (int i = k; i < len; i++) { 364 | swapChar(arr, i, k); //交换 365 | perm(arr, k+1, len); //下一次排列 366 | swapChar(arr, i, k); //恢复原来的序列 367 | } 368 | } 369 | 370 | void testPerm() 371 | { 372 | printf("Test Perm:\n"); 373 | char arr[] = "abc"; 374 | perm(arr, 0, strlen(arr)); 375 | } 376 | 377 | 378 | /********************/ 379 | /** 最长公共子序列 **/ 380 | /********************/ 381 | 382 | #define UP 1 383 | #define LEFT 2 384 | #define UPLEFT 3 385 | 386 | /** 387 | * LCS-动态规划算法 388 | */ 389 | int lcsLength(char *X, char *Y, int **c, int **b) 390 | { 391 | int m = strlen(X); 392 | int n = strlen(Y); 393 | 394 | for (int i = 0; i <= m; i++) { 395 | c[i][0] = 0; 396 | } 397 | 398 | for (int j = 0; j <= n; j++) { 399 | c[0][j] = 0; 400 | } 401 | 402 | for (int i = 1; i <= m; i++) { 403 | for (int j = 1; j <= n; j++) { 404 | if (X[i-1] == Y[j-1]) { 405 | c[i][j] = c[i-1][j-1] + 1; 406 | b[i][j] = UPLEFT; 407 | } else if (c[i-1][j] >= c[i][j-1]) { 408 | c[i][j] = c[i-1][j]; 409 | b[i][j] = UP; 410 | } else { 411 | c[i][j] = c[i][j-1]; 412 | b[i][j] = LEFT; 413 | } 414 | } 415 | } 416 | 417 | return c[m][n]; 418 | } 419 | 420 | /** 421 | * LCS-递归算法 422 | */ 423 | int lcsLengthRecur(char *X, int m, char *Y, int n, int **b) 424 | { 425 | if (m == 0 || n == 0) 426 | return 0; 427 | 428 | if (X[m-1] == Y[n-1]) { 429 | b[m][n] = UPLEFT; 430 | return lcsLengthRecur(X, m-1, Y, n-1, b) + 1; 431 | } 432 | 433 | int len1 = lcsLengthRecur(X, m-1, Y, n, b); 434 | int len2 = lcsLengthRecur(X, m, Y, n-1, b); 435 | 436 | int maxLen; 437 | if (len1 >= len2) { 438 | maxLen = len1; 439 | b[m][n] = UP; 440 | } else { 441 | maxLen = len2; 442 | b[m][n] = LEFT; 443 | } 444 | return maxLen; 445 | } 446 | 447 | /** 448 | * 打印LCS,用到辅助数组b 449 | */ 450 | void printLCS(int **b, char *X, int i, int j) 451 | { 452 | if (i == 0 || j == 0) 453 | return; 454 | 455 | if (b[i][j] == UPLEFT) { 456 | printLCS(b, X, i-1, j-1); 457 | printf("%c ", X[i-1]); 458 | } else if (b[i][j] == UP) { 459 | printLCS(b, X, i-1, j); 460 | } else { 461 | printLCS(b, X, i, j-1); 462 | } 463 | } 464 | 465 | void testLCS() 466 | { 467 | char X[] = "ABCBDAB"; 468 | char Y[] = "BDCABA"; 469 | 470 | int lenX = strlen(X); 471 | int lenY = strlen(Y); 472 | 473 | int **b = (int **)calloc(sizeof(int *), lenX+1); 474 | for (int i = 0; i < lenX+1; i++) { 475 | b[i] = (int *)calloc(sizeof(int), lenY+1); 476 | } 477 | 478 | int **c = (int **)calloc(sizeof(int *), lenX+1); 479 | for (int i = 0; i < lenX+1; i++) { 480 | c[i] = (int *)calloc(sizeof(int), lenY+1); 481 | } 482 | 483 | printf("Test LCS:\n"); 484 | // int lcsLen = lcsLength(X, Y, c, b); 485 | int lcsLen = lcsLengthRecur(X, lenX, Y, lenY, b); 486 | printf("lcsLen: %d\n", lcsLen); 487 | printLCS(b, X, lenX, lenY); 488 | printf("\n"); 489 | } 490 | 491 | 492 | /*****************/ 493 | /** 正则表达式 **/ 494 | /*****************/ 495 | 496 | /** 497 | * 匹配主函数 498 | */ 499 | int match(const char *regexp, const char *text) 500 | { 501 | if (regexp[0] == '^') 502 | return matchhere(regexp+1, text); 503 | do { 504 | if (matchhere(regexp, text)) 505 | return 1; 506 | } while (*text++ != '\0'); 507 | return 0; 508 | } 509 | 510 | /** 511 | * 实际匹配函数 512 | */ 513 | int matchhere(const char *regexp, const char *text) 514 | { 515 | if (regexp[0] == '\0') 516 | return 1; 517 | 518 | if (regexp[0]=='$' && regexp[1]=='\0') 519 | return *text == '\0'; 520 | 521 | if (regexp[1] == '*') 522 | return matchstar(regexp[0], regexp+2, text); 523 | 524 | if (*text != '\0' && (regexp[0] == '.' || regexp[0] == *text)) 525 | return matchhere(regexp+1, text+1); 526 | 527 | return 0; 528 | } 529 | 530 | /** 531 | * 匹配星号函数 532 | */ 533 | int matchstar(int c, const char *regexp, const char *text) 534 | { 535 | do { 536 | if (matchhere(regexp, text)) 537 | return 1; 538 | } while (*text != '\0' && (*text++ == c || c == '.')); 539 | return 0; 540 | } 541 | 542 | /** 543 | * 正则表达式测试函数 544 | */ 545 | void testRegex() 546 | { 547 | printf("Test Regex:\n"); 548 | string regexp = "xa$"; 549 | string text = "bcabcdefgxa"; 550 | int ret = match(regexp.c_str(), text.c_str()); 551 | printf("%d\n", ret); 552 | } 553 | 554 | 555 | int main() 556 | { 557 | testLongestPalindrome(); 558 | testRGBSort(); 559 | testMaxSlidingWindow(); 560 | testRegex(); 561 | testPerm(); 562 | testLCS(); 563 | return 0; 564 | } 565 | -------------------------------------------------------------------------------- /code/ds/string/string.c: -------------------------------------------------------------------------------- 1 | // Basic string routines. Not hardware optimized, but not shabby. 2 | // From MIT6.828 lab. 3 | // string基本函数实现,学习用。 4 | 5 | /** 6 | * 字符串长度 7 | */ 8 | int strlen(const char *s) 9 | { 10 | int n; 11 | 12 | for (n = 0; *s != '\0'; s++) 13 | n++; 14 | return n; 15 | } 16 | 17 | /** 18 | * 字符串复制 19 | */ 20 | char *strcpy(char *dst, const char *src) 21 | { 22 | char *ret; 23 | 24 | ret = dst; 25 | while ((*dst++ = *src++) != '\0') 26 | /* do nothing */; 27 | return ret; 28 | } 29 | 30 | /** 31 | * 字符串拼接 32 | */ 33 | char *strcat(char *dst, const char *src) 34 | { 35 | int len = strlen(dst); 36 | strcpy(dst + len, src); 37 | return dst; 38 | } 39 | 40 | /** 41 | * 字符串比较 42 | */ 43 | int strcmp(const char *p, const char *q) 44 | { 45 | while (*p && *p == *q) 46 | p++, q++; 47 | return (int) ((unsigned char) *p - (unsigned char) *q); 48 | } 49 | 50 | /** 51 | * 返回字符串s中第一次出现c的位置 52 | */ 53 | char *strchr(const char *s, char c) 54 | { 55 | for (; *s; s++) 56 | if (*s == c) 57 | return (char *) s; 58 | return 0; 59 | } 60 | 61 | /** 62 | * 设置内存位置v开始的n个元素值为c 63 | */ 64 | void *memset(void *v, int c, size_t n) 65 | { 66 | char *p; 67 | int m; 68 | 69 | p = v; 70 | m = n; 71 | while (--m >= 0) 72 | *p++ = c; 73 | 74 | return v; 75 | } 76 | 77 | /** 78 | * 内存拷贝,注意覆盖情况 79 | */ 80 | void *memmove(void *dst, const void *src, size_t n) 81 | { 82 | const char *s; 83 | char *d; 84 | 85 | s = src; 86 | d = dst; 87 | if (s < d && s + n > d) { 88 | s += n; 89 | d += n; 90 | while (n-- > 0) 91 | *--d = *--s; 92 | } else 93 | while (n-- > 0) 94 | *d++ = *s++; 95 | 96 | return dst; 97 | } 98 | 99 | /** 100 | * 内存比较 101 | */ 102 | int memcmp(const void *v1, const void *v2, size_t n) 103 | { 104 | const uint8_t *s1 = (const uint8_t *) v1; 105 | const uint8_t *s2 = (const uint8_t *) v2; 106 | 107 | while (n-- > 0) { 108 | if (*s1 != *s2) 109 | return (int) *s1 - (int) *s2; 110 | s1++, s2++; 111 | } 112 | 113 | return 0; 114 | } 115 | 116 | /** 117 | * 字符串转数字 118 | */ 119 | long strtol(const char *s, char **endptr, int base) 120 | { 121 | int neg = 0; 122 | long val = 0; 123 | 124 | // gobble initial whitespace 125 | while (*s == ' ' || *s == '\t') 126 | s++; 127 | 128 | // plus/minus sign 129 | if (*s == '+') 130 | s++; 131 | else if (*s == '-') 132 | s++, neg = 1; 133 | 134 | // hex or octal base prefix 135 | if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) 136 | s += 2, base = 16; 137 | else if (base == 0 && s[0] == '0') 138 | s++, base = 8; 139 | else if (base == 0) 140 | base = 10; 141 | 142 | // digits 143 | while (1) { 144 | int dig; 145 | 146 | if (*s >= '0' && *s <= '9') 147 | dig = *s - '0'; 148 | else if (*s >= 'a' && *s <= 'z') 149 | dig = *s - 'a' + 10; 150 | else if (*s >= 'A' && *s <= 'Z') 151 | dig = *s - 'A' + 10; 152 | else 153 | break; 154 | if (dig >= base) 155 | break; 156 | s++, val = (val * base) + dig; 157 | // we don't properly detect overflow! 158 | } 159 | 160 | if (endptr) 161 | *endptr = (char *) s; 162 | return (neg ? -val : val); 163 | } 164 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | UTILDIR = ../../../util 3 | ASLISTDIR = ../../list/aslist 4 | OBJECTS = $(UTILDIR)/util.o $(ASLISTDIR)/aslist.o bt.o btstack.o btqueue.o quiz.o main.o 5 | 6 | main: $(OBJECTS) 7 | $(CC) -o $@ $(OBJECTS) 8 | 9 | quiz.o: quiz.c 10 | $(CC) -c -o $@ $< -I$(UTILDIR) -I$(ASLISTDIR) 11 | 12 | main.o: main.c 13 | $(CC) -c -o $@ $< -I$(UTILDIR) -I$(ASLISTDIR) 14 | 15 | test: clean main 16 | ./main 17 | 18 | .PHONY: clean 19 | clean: 20 | -rm -f main $(OBJECTS) 21 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/bt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bt.h" 4 | #include "btstack.h" 5 | #include "btqueue.h" 6 | 7 | /** 8 | * 创建BTNode 9 | */ 10 | BTNode *btNewNode(int value) 11 | { 12 | BTNode *node = (BTNode *)malloc(sizeof(BTNode)); 13 | node->value = value; 14 | node->left = node->right = NULL; 15 | return node; 16 | } 17 | 18 | /** 19 | * BST中插入结点,递归方法 20 | */ 21 | BTNode *bstInsert(BTNode *root, int value) 22 | { 23 | if (!root) 24 | return btNewNode(value); 25 | 26 | if (root->value > value) { 27 | root->left = bstInsert(root->left, value); 28 | } else { 29 | root->right = bstInsert(root->right, value); 30 | } 31 | return root; 32 | } 33 | 34 | /** 35 | * BST中插入结点,非递归方法 36 | */ 37 | BTNode *bstInsertIter(BTNode *root, int value) 38 | { 39 | BTNode *node = btNewNode(value); 40 | 41 | if (!root) 42 | return node; 43 | 44 | BTNode *current = root, *parent = NULL; 45 | 46 | while (current) { 47 | parent = current; 48 | if (current->value > value) 49 | current = current->left; 50 | else 51 | current = current->right; 52 | } 53 | 54 | if (parent->value >= value) 55 | parent->left = node; 56 | else 57 | parent->right = node; 58 | 59 | return root; 60 | } 61 | 62 | /** 63 | * BST中删除结点 64 | */ 65 | BTNode *bstDelete(BTNode *root, int value) 66 | { 67 | BTNode *parent = NULL, *current = root; 68 | BTNode *node = bstSearchIter(root, &parent, value); 69 | if (!node) { 70 | printf("Value not found\n"); 71 | return root; 72 | } 73 | 74 | if (!node->left && !node->right) { 75 | // 情况1:待删除结点是叶子结点 76 | if (node != root) { 77 | if (parent->left == node) { 78 | parent->left = NULL; 79 | } else { 80 | parent->right = NULL; 81 | } 82 | } else { 83 | root = NULL; 84 | } 85 | free(node); 86 | } else if (node->left && node->right) { 87 | // 情况2:待删除结点有两个子结点 88 | BTNode *predecessor = bstMax(node->left); 89 | bstDelete(root, predecessor->value); 90 | node->value = predecessor->value; 91 | } else { 92 | // 情况3:待删除结点只有一个子结点 93 | BTNode *child = (node->left) ? node->left : node->right; 94 | if (node != root) { 95 | if (node == parent->left) 96 | parent->left = child; 97 | else 98 | parent->right = child; 99 | } else { 100 | root = child; 101 | } 102 | free(node); 103 | } 104 | return root; 105 | } 106 | 107 | /** 108 | * BST查找结点-递归 109 | */ 110 | BTNode *bstSearch(BTNode *root, int value) 111 | { 112 | if (!root) return NULL; 113 | 114 | if (root->value == value) { 115 | return root; 116 | } else if (root->value > value) { 117 | return bstSearch(root->left, value); 118 | } else { 119 | return bstSearch(root->left, value); 120 | } 121 | } 122 | 123 | /** 124 | * BST查找结点-非递归 125 | */ 126 | BTNode *bstSearchIter(BTNode *root, BTNode **parent, int value) 127 | { 128 | if (!root) return NULL; 129 | 130 | BTNode *current = root; 131 | 132 | while (current && current->value != value) { 133 | *parent = current; 134 | if (current->value > value) 135 | current = current->left; 136 | else 137 | current = current->right; 138 | } 139 | 140 | return current; 141 | } 142 | 143 | /** 144 | * BST最小值结点 145 | */ 146 | BTNode *bstMin(BTNode *root) 147 | { 148 | if (!root->left) 149 | return root; 150 | 151 | return bstMin(root->left); 152 | } 153 | 154 | /** 155 | * BST最大值结点 156 | */ 157 | BTNode *bstMax(BTNode *root) 158 | { 159 | if (!root->right) 160 | return root; 161 | 162 | return bstMax(root->right); 163 | } 164 | 165 | /** 166 | * 二叉树结点数目 167 | */ 168 | int btSize(BTNode *root) 169 | { 170 | if (!root) return 0; 171 | 172 | return btSize(root->left) + btSize(root->right) + 1; 173 | } 174 | 175 | /** 176 | * 二叉树高度 177 | */ 178 | int btHeight(BTNode *root) 179 | { 180 | if (!root) return 0; 181 | 182 | int leftHeight = btHeight(root->left); 183 | int rightHeight = btHeight(root->right); 184 | int maxHeight = leftHeight > rightHeight ? leftHeight+1 : rightHeight+1; 185 | return maxHeight; 186 | } 187 | 188 | /** 189 | * 二叉树结点存在性判断 190 | */ 191 | int btExist(BTNode *root, BTNode *node) 192 | { 193 | if (!root) return 0; 194 | 195 | if (root == node) return 1; 196 | 197 | return btExist(root->left, node) || btExist(root->right, node); 198 | } 199 | 200 | 201 | /*********************/ 202 | /** 二叉树遍历-递归 **/ 203 | /*********************/ 204 | 205 | /** 206 | * 二叉树先序遍历 207 | */ 208 | void preOrder(BTNode *root) 209 | { 210 | if (!root) return; 211 | 212 | printf("%d ", root->value); 213 | preOrder(root->left); 214 | preOrder(root->right); 215 | } 216 | 217 | /** 218 | * 二叉树中序遍历 219 | */ 220 | void inOrder(BTNode *root) 221 | { 222 | if (!root) return; 223 | 224 | inOrder(root->left); 225 | printf("%d ", root->value); 226 | inOrder(root->right); 227 | } 228 | 229 | /** 230 | * 二叉树后序遍历 231 | */ 232 | void postOrder(BTNode *root) 233 | { 234 | if (!root) return; 235 | 236 | postOrder(root->left); 237 | postOrder(root->right); 238 | printf("%d ", root->value); 239 | } 240 | 241 | /** 242 | * 二叉树层序遍历 243 | */ 244 | void levelOrder(BTNode *root) 245 | { 246 | int height = btHeight(root); 247 | int level; 248 | for (level = 1; level <= height; level++) { 249 | levelOrderInLevel(root, level); 250 | printf("\n"); 251 | } 252 | } 253 | 254 | /** 255 | * 二叉树层序遍历辅助函数-打印第level层的结点 256 | */ 257 | void levelOrderInLevel(BTNode *root, int level) 258 | { 259 | if (!root) return; 260 | 261 | if (level == 1) { 262 | printf("%d ", root->value); 263 | return; 264 | } 265 | levelOrderInLevel(root->left, level-1); 266 | levelOrderInLevel(root->right, level-1); 267 | } 268 | 269 | 270 | /*********************/ 271 | /** 二叉树遍历-非递归 **/ 272 | /*********************/ 273 | 274 | /** 275 | * 先序遍历-非递归 276 | */ 277 | void preOrderIter(BTNode *root) 278 | { 279 | if (!root) return; 280 | 281 | int size = btSize(root); 282 | BTNodeStack *stack = stackNew(size); 283 | 284 | push(stack, root); 285 | while (!IS_EMPTY(stack)) { 286 | BTNode *node = pop(stack); 287 | printf("%d ", node->value); 288 | 289 | if (node->right) 290 | push(stack, node->right); 291 | 292 | if (node->left) 293 | push(stack, node->left); 294 | } 295 | free(stack); 296 | } 297 | 298 | /** 299 | * 中序遍历-非递归 300 | */ 301 | void inOrderIter(BTNode *root) 302 | { 303 | if (!root) return; 304 | 305 | BTNodeStack *stack = stackNew(btSize(root)); 306 | 307 | BTNode *current = root; 308 | while (current || !IS_EMPTY(stack)) { 309 | if (current) { 310 | push(stack, current); 311 | current = current->left; 312 | } else { 313 | BTNode *node = pop(stack); 314 | printf("%d ", node->value); 315 | current = node->right; 316 | } 317 | } 318 | free(stack); 319 | } 320 | 321 | /** 322 | * 后续遍历-使用一个栈非递归 323 | */ 324 | void postOrderIter(BTNode *root) 325 | { 326 | BTNodeStack *stack = stackNew(btSize(root)); 327 | BTNode *current = root; 328 | do { 329 | // 移动至最左边结点 330 | while (current) { 331 | // 将该结点右孩子和自己入栈 332 | if (current->right) 333 | push(stack, current->right); 334 | push(stack, current); 335 | 336 | // 往左子树遍历 337 | current = current->left; 338 | } 339 | 340 | current = pop(stack); 341 | 342 | if (current->right && peek(stack) == current->right) { 343 | pop(stack); 344 | push(stack, current); 345 | current = current->right; 346 | } else { 347 | printf("%d ", current->value); 348 | current = NULL; 349 | } 350 | } while (!IS_EMPTY(stack)); 351 | } 352 | 353 | /** 354 | * 后续遍历-使用两个栈,更好理解一点。 355 | */ 356 | void postOrderIterWith2Stack(BTNode *root) 357 | { 358 | if (!root) return; 359 | 360 | BTNodeStack *stack = stackNew(btSize(root)); 361 | BTNodeStack *output = stackNew(btSize(root)); 362 | 363 | push(stack, root); 364 | BTNode *node; 365 | 366 | while (!IS_EMPTY(stack)) { 367 | node = pop(stack); 368 | push(output, node); 369 | 370 | if (node->left) 371 | push(stack, node->left); 372 | 373 | if (node->right) 374 | push(stack, node->right); 375 | } 376 | 377 | while (!IS_EMPTY(output)) { 378 | node = pop(output); 379 | printf("%d ", node->value); 380 | } 381 | } 382 | 383 | /** 384 | * 层序遍历-非递归 385 | */ 386 | void levelOrderIter(BTNode *root) 387 | { 388 | if (!root) return; 389 | 390 | BTNodeQueue *queue = queueNew(btSize(root)); 391 | enqueue(queue, root); 392 | 393 | while (1) { 394 | int nodeCount = QUEUE_SIZE(queue); 395 | if (nodeCount == 0) 396 | break; 397 | 398 | while (nodeCount > 0) { 399 | BTNode *node = dequeue(queue); 400 | printf("%d ", node->value); 401 | 402 | if (node->left) 403 | enqueue(queue, node->left); 404 | 405 | if (node->right) 406 | enqueue(queue, node->right); 407 | 408 | nodeCount--; 409 | } 410 | printf("\n"); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/bt.h: -------------------------------------------------------------------------------- 1 | #ifndef __BST_H 2 | #define __BST_H 3 | 4 | typedef struct BTNode { 5 | int value; 6 | struct BTNode *left; 7 | struct BTNode *right; 8 | } BTNode; 9 | 10 | BTNode *btNewNode(int value); 11 | BTNode *bstInsert(BTNode *root, int value); 12 | BTNode *bstInsertIter(BTNode *root, int value); 13 | BTNode *bstDelete(BTNode *root, int value); 14 | BTNode *bstSearch(BTNode *root, int value); 15 | BTNode *bstSearchIter(BTNode *root, BTNode **parent, int value); 16 | BTNode *bstMin(BTNode *root); 17 | BTNode *bstMax(BTNode *root); 18 | 19 | int btSize(BTNode *root); 20 | int btHeight(BTNode *root); 21 | int btExist(BTNode *root, BTNode *node); 22 | 23 | void preOrder(BTNode *root); 24 | void inOrder(BTNode *root); 25 | void postOrder(BTNode *root); 26 | void levelOrder(BTNode *root); 27 | void levelOrderInLevel(BTNode *root, int level); 28 | 29 | void preOrderIter(BTNode *root); 30 | void inOrderIter(BTNode *root); 31 | void postOrderIter(BTNode *root); 32 | void postOrderIterWith2Stack(BTNode *root); 33 | void levelOrderIter(BTNode *root); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/btqueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "btqueue.h" 4 | 5 | /** 6 | * 初始化栈 7 | */ 8 | BTNodeQueue *queueNew(int capacity) 9 | { 10 | BTNodeQueue *queue = (BTNodeQueue *)malloc(sizeof(*queue) + sizeof(BTNode *) * capacity); 11 | if (!queue) { 12 | printf("BTNodeQueue new failed\n"); 13 | exit(-1); 14 | } 15 | 16 | queue->capacity = capacity; 17 | queue->size = 0; 18 | queue->front = queue->rear = -1; 19 | return queue; 20 | } 21 | 22 | /** 23 | * 入栈 24 | */ 25 | void enqueue(BTNodeQueue *queue, BTNode *v) 26 | { 27 | if (QUEUE_SIZE(queue) == queue->capacity) { 28 | printf("Queue Full\n"); 29 | exit(-1); 30 | } 31 | queue->rear = (queue->rear + 1) % queue->capacity; 32 | queue->items[queue->rear] = v; 33 | queue->size++; 34 | } 35 | 36 | /** 37 | * 出栈 38 | */ 39 | BTNode *dequeue(BTNodeQueue *queue) 40 | { 41 | if (QUEUE_SIZE(queue) == 0) { 42 | printf("Queue Empty\n"); 43 | exit(-1); 44 | } 45 | queue->front = (queue->front + 1) % queue->capacity; 46 | queue->size--; 47 | return queue->items[queue->front]; 48 | } 49 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/btqueue.h: -------------------------------------------------------------------------------- 1 | #ifndef __BT_QUEUE_H 2 | #define __BT_QUEUE_H 3 | 4 | #include "bt.h" 5 | 6 | typedef struct BTNodeQueue { 7 | int capacity; 8 | int size; 9 | int front; 10 | int rear; 11 | BTNode* items[]; 12 | } BTNodeQueue; 13 | 14 | #define QUEUE_SIZE(queue) (queue->size) 15 | 16 | BTNodeQueue *queueNew(int capacity); 17 | void enqueue(BTNodeQueue *queue, BTNode *v); 18 | BTNode *dequeue(BTNodeQueue *queue); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/btstack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "btstack.h" 4 | 5 | /** 6 | * 初始化栈 7 | */ 8 | BTNodeStack *stackNew(int capacity) 9 | { 10 | BTNodeStack *stack = (BTNodeStack *)malloc(sizeof(*stack) + sizeof(BTNode *) * capacity); 11 | if (!stack) { 12 | printf("BTNodeStack new failed\n"); 13 | exit(E_NOMEM); 14 | } 15 | 16 | stack->capacity = capacity; 17 | stack->top = -1; 18 | return stack; 19 | } 20 | 21 | /** 22 | * 入栈 23 | */ 24 | void push(BTNodeStack *stack, BTNode *v) 25 | { 26 | if (IS_FULL(stack)) { 27 | printf("Push: BTNodeStack Overflow\n"); 28 | exit(E_FULL); 29 | } 30 | stack->items[++stack->top] = v; 31 | } 32 | 33 | /** 34 | * 出栈 35 | */ 36 | BTNode *pop(BTNodeStack *stack) 37 | { 38 | if (IS_EMPTY(stack)) { 39 | return NULL; 40 | } 41 | 42 | return stack->items[stack->top--]; 43 | } 44 | 45 | /** 46 | * 返回栈顶元素 47 | */ 48 | BTNode *peek(BTNodeStack *stack) 49 | { 50 | if (IS_EMPTY(stack)) { 51 | return NULL; 52 | } 53 | return stack->items[stack->top]; 54 | } 55 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/btstack.h: -------------------------------------------------------------------------------- 1 | #ifndef __BT_STACK_H 2 | #define __BT_STACK_H 3 | 4 | #include "bt.h" 5 | 6 | typedef struct BTNodeStack { 7 | int capacity; 8 | int top; 9 | BTNode* items[]; 10 | } BTNodeStack; 11 | 12 | #define SIZE(stack) (stack->top + 1) 13 | #define IS_EMPTY(stack) (stack->top == -1) 14 | #define IS_FULL(stack) (stack->top == stack->capacity - 1) 15 | 16 | #define E_FULL -1 17 | #define E_EMPTY -2 18 | #define E_NOMEM -3 19 | 20 | BTNodeStack *stackNew(int capacity); 21 | void push(BTNodeStack *stack, BTNode *node); 22 | BTNode *pop(BTNodeStack *stack); 23 | BTNode *peek(BTNodeStack *stack); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bt.h" 4 | #include "quiz.h" 5 | 6 | /** 7 | * 创建测试的BST 8 | */ 9 | BTNode *createTestBST() 10 | { 11 | BTNode *root = NULL; 12 | root = bstInsertIter(root, 5); 13 | root = bstInsert(root, 2); 14 | root = bstInsertIter(root, 4); 15 | root = bstInsert(root, 1); 16 | root = bstInsert(root, 7); 17 | root = bstInsert(root, 6); 18 | return root; 19 | } 20 | 21 | /** 22 | * 创建测试的二叉树,但不是二叉搜索树 23 | */ 24 | BTNode *createTestBT() 25 | { 26 | BTNode *root = btNewNode(10); 27 | root->left = btNewNode(5); 28 | root->right = btNewNode(15); 29 | root->right->left = btNewNode(6); 30 | root->right->right = btNewNode(20); 31 | return root; 32 | } 33 | 34 | /** 35 | * 创建测试用的完全二叉树 36 | */ 37 | BTNode *createTestCompleteBT() 38 | { 39 | BTNode *root = btNewNode(10); 40 | root->left = btNewNode(5); 41 | root->right = btNewNode(11); 42 | root->left->left = btNewNode(2); 43 | root->left->right = btNewNode(6); 44 | return root; 45 | } 46 | 47 | void testBTOperation() 48 | { 49 | BTNode *root = createTestBST(); 50 | printf("PreOrder: "); 51 | preOrder(root); 52 | printf("\n"); 53 | printf("PreOrderIter: "); 54 | preOrderIter(root); 55 | printf("\n"); 56 | 57 | printf("InOrder: "); 58 | inOrder(root); 59 | printf("\n"); 60 | printf("InOrderIter: "); 61 | inOrderIter(root); 62 | printf("\n"); 63 | 64 | printf("PostOrder: "); 65 | postOrder(root); 66 | printf("\n"); 67 | printf("PostOrderIter: "); 68 | postOrderIter(root); 69 | printf("\n"); 70 | printf("PostOrderIterWith2Stack: "); 71 | postOrderIterWith2Stack(root); 72 | printf("\n"); 73 | 74 | printf("LevelOrder: \n"); 75 | levelOrder(root); 76 | printf("\n"); 77 | printf("LevelOrderIter: \n"); 78 | levelOrderIter(root); 79 | printf("\n"); 80 | 81 | int key = 8; 82 | BTNode *node = bstSearch(root, key); 83 | if (node) { 84 | printf("Found:%d\n", key); 85 | } else { 86 | printf("Not found:%d\n", key); 87 | } 88 | 89 | BTNode *parent; 90 | key = 2; 91 | node = bstSearchIter(root, &parent, key); 92 | if (node) { 93 | printf("Found:%d\n", key); 94 | if (parent) { 95 | printf("Parent:%d\n", parent->value); 96 | } 97 | } else { 98 | printf("Not found:%d\n", key); 99 | } 100 | 101 | int delete[] = {2, 5, 7}; 102 | int i; 103 | for (i = 0; i < 3; i++) { 104 | printf("Delete %d\n", delete[i]); 105 | root = bstDelete(root, delete[i]); 106 | printf("PreOrder: "); 107 | preOrder(root); 108 | printf("\n"); 109 | } 110 | 111 | printf("Min:%d, Max:%d\n", bstMin(root)->value, bstMax(root)->value); 112 | printf("Size:%d, Height:%d\n", btSize(root), btHeight(root)); 113 | } 114 | 115 | void testIsBST() 116 | { 117 | BTNode *bt = createTestBT(); 118 | printf("\nBinary Tree(but not BST):\n"); 119 | levelOrder(bt); 120 | printf("\n"); 121 | printf("isBSTError check:%d\n", isBSTError(bt)); 122 | printf("isBSTUnefficient check:%d\n", isBSTUnefficient(bt)); 123 | printf("isBSTEfficient check:%d\n", isBSTEfficient(bt, NULL, NULL)); 124 | printf("isBSTInOrder check:%d\n", isBSTInOrder(bt, NULL)); 125 | 126 | BTNode *bst = createTestBST(); 127 | printf("\nBinary Search Tree:\n"); 128 | levelOrder(bst); 129 | printf("\n"); 130 | printf("isBSTError check:%d\n", isBSTError(bst)); 131 | printf("isBSTUnefficient check:%d\n", isBSTUnefficient(bst)); 132 | printf("isBSTEfficient check:%d\n", isBSTEfficient(bst, NULL, NULL)); 133 | printf("isBSTInOrder check:%d\n", isBSTInOrder(bst, NULL)); 134 | } 135 | 136 | void testIsCompleteBT() 137 | { 138 | BTNode *bt = createTestBT(); 139 | printf("\nBinary Tree: \n"); 140 | levelOrder(bt); 141 | printf("Is Complete BT Level Order Method: %d\n", isCompleteBTLevelOrder(bt)); 142 | printf("Is Complete BT Index Method: %d\n", isCompleteBTIndexMethod(bt, 0, btSize(bt))); 143 | BTNode *cbt = createTestCompleteBT(); 144 | printf("\nBinary Tree: \n"); 145 | levelOrder(cbt); 146 | printf("Is Complete BT Level Order Method: %d\n", isCompleteBTLevelOrder(cbt)); 147 | printf("Is Complete BT Index Method: %d\n", isCompleteBTIndexMethod(cbt, 0, btSize(cbt))); 148 | } 149 | 150 | void testIsBalanceBT() 151 | { 152 | printf("\nTest Balanced Binary Tree: \n"); 153 | BTNode *bt = createTestBT(); 154 | levelOrder(bt); 155 | printf("Is balance: %d\n", isBalanceBTTop2Down(bt)); 156 | bt->right->right->right = btNewNode(18); // make it not balanced 157 | levelOrder(bt); 158 | int height; 159 | printf("Is balance: %d\n", isBalanceBTDown2Top(bt, &height)); 160 | } 161 | 162 | void testIsOmorphism() 163 | { 164 | BTNode *t1 = createTestBT(); 165 | BTNode *t2 = createTestBST(); 166 | BTNode *t3 = createTestBT(); 167 | printf("IsOmorphism:%d\n", isOmorphism(t1, t3)); 168 | printf("IsOmorphism:%d\n", isOmorphism(t1, t2)); 169 | } 170 | 171 | void testBuildBT() 172 | { 173 | printf("\nBuildBT from pre and in order\n"); 174 | int preorder[] = {7, 10, 4, 3, 1, 2, 8, 11}; 175 | int inorder[] = {4, 10, 3, 1, 7, 11, 8, 2}; 176 | int postorder[] = {4, 1, 3, 10, 11, 8, 2, 7}; 177 | int n = sizeof(inorder) / sizeof(inorder[0]); 178 | 179 | BTNode *root = buildBTFromPreInOrder(preorder, inorder, n, 0, n); 180 | printf("Preorder:\n"); 181 | preOrder(root); 182 | printf("\n"); 183 | printf("Inorder:\n"); 184 | inOrder(root); 185 | printf("\n"); 186 | printf("Postorder:\n"); 187 | postOrder(root); 188 | printf("\n"); 189 | 190 | printf("\nBuildBT from post and in order\n"); 191 | root = buildBTFromInPostOrder(postorder, inorder, n, 0, n); 192 | printf("Preorder:\n"); 193 | preOrder(root); 194 | printf("\n"); 195 | printf("Inorder:\n"); 196 | inOrder(root); 197 | printf("\n"); 198 | printf("Postorder:\n"); 199 | postOrder(root); 200 | printf("\n"); 201 | } 202 | 203 | void testSaveRestoreBST() 204 | { 205 | BTNode *bst = createTestBST(); 206 | printf("\nTest Save/Restore BST\n"); 207 | preOrder(bst); 208 | printf("\n"); 209 | 210 | FILE *fbst = fopen("/tmp/bst.data", "w+"); 211 | if (!fbst) { 212 | printf("File permission error\n"); 213 | return; 214 | } 215 | bstSave(bst, fbst); 216 | fclose(fbst); 217 | 218 | fbst = fopen("/tmp/bst.data", "r"); 219 | BTNode *bstFile = bstRestore(fbst); 220 | preOrder(bst); 221 | printf("\n"); 222 | fclose(fbst); 223 | } 224 | 225 | void testSaveRestoreBT() 226 | { 227 | BTNode *bt = createTestBT(); 228 | printf("\nTest Save/Restore BT\n"); 229 | preOrder(bt); 230 | printf("\n"); 231 | FILE *fbt = fopen("/tmp/bt.data", "w+"); 232 | btSave(bt, fbt); 233 | fclose(fbt); 234 | 235 | fbt = fopen("/tmp/bt.data", "r"); 236 | BTNode *btFile = btRestore(NULL, fbt); 237 | preOrder(btFile); 238 | printf("\n"); 239 | fclose(fbt); 240 | } 241 | 242 | void testLCABST() 243 | { 244 | BTNode *bst = createTestBST(); 245 | BTNode *p = bst->left->right; // node 4 246 | BTNode *q = bst->right; // node 7 247 | BTNode *lca = bstLCA(bst, p, q); 248 | printf("\nTest BST LCA, p:%d, q:%d, lca:%d\n", p->value, q->value, lca->value); 249 | 250 | q = bst->left; // node 2 251 | lca = bstLCA(bst, p, q); 252 | printf("Test BST LCA, p:%d, q:%d, lca:%d\n", p->value, q->value, lca->value); 253 | } 254 | 255 | void testLCABT() 256 | { 257 | BTNode *bt = createTestBT(); 258 | BTNode *p = bt->left; // node 5 259 | BTNode *q = bt->right; // node 10 260 | BTNode *lca = btLCATop2Down(bt, p, q); 261 | BTNode *lca2 = btLCADown2Top(bt, p, q); 262 | printf("\nTest BT LCA Top2Down, p:%d, q:%d, lca:%d\n", p->value, q->value, lca->value); 263 | printf("Test BT LCA Down2Top, p:%d, q:%d, lca:%d\n", p->value, q->value, lca2->value); 264 | 265 | p = bt->right->left; // node 6 266 | lca = btLCATop2Down(bt, p, q); 267 | lca2 = btLCADown2Top(bt, p, q); 268 | printf("Test BT LCA Top2Down, p:%d, q:%d, lca:%d\n", p->value, q->value, lca->value); 269 | printf("Test BT LCA Down2Top, p:%d, q:%d, lca:%d\n", p->value, q->value, lca2->value); 270 | } 271 | 272 | void testLargestSubBST() 273 | { 274 | BTNode *bst = createTestBST(); 275 | int bstSize; 276 | BTNode *subBST = largestSubBSTTop2Down(bst, &bstSize); 277 | printf("\nTest Max Sub BST, btSize:%d, root:%d\n", bstSize, subBST->value); 278 | subBST = largestSubBSTDown2Top(bst, &bstSize); 279 | printf("Test Max Sub BST, btSize:%d, root:%d\n", bstSize, subBST->value); 280 | 281 | BTNode *bt = createTestBT(); 282 | subBST = largestSubBSTTop2Down(bt, &bstSize); 283 | printf("Test Max Sub BST, btSize:%d, root:%d\n", bstSize, subBST->value); 284 | subBST = largestSubBSTDown2Top(bt, &bstSize); 285 | printf("Test Max Sub BST, btSize:%d, root:%d\n", bstSize, subBST->value); 286 | printf("\n"); 287 | 288 | } 289 | 290 | void testDistanceOf2BTNodes() 291 | { 292 | BTNode *root = btNewNode(1); 293 | root->left = btNewNode(2); 294 | root->right = btNewNode(3); 295 | root->left->left = btNewNode(4); 296 | root->left->right = btNewNode(5); 297 | root->right->left = btNewNode(6); 298 | root->right->right = btNewNode(7); 299 | root->right->left->right = btNewNode(8); 300 | printf("Dist(4, 5) = %d\n", distanceOf2BTNodes(root, root->left->left, root->left->right)); 301 | printf("Dist(4, 6) = %d\n", distanceOf2BTNodes(root, root->left->left, root->right->left)); 302 | printf("Dist(3, 4) = %d\n", distanceOf2BTNodes(root, root->right, root->left->left)); 303 | printf("Dist(2, 4) = %d\n", distanceOf2BTNodes(root, root->left, root->left->left)); 304 | printf("Dist(8, 5) = %d\n", distanceOf2BTNodes(root, root->right->left->right, root->left->right)); 305 | } 306 | 307 | void testDistanceOf2BSTNodes() 308 | { 309 | BTNode *root = createTestBST(); 310 | BTNode *p = root->left->left; // 1 311 | BTNode *q = root->right; // 7 312 | printf("Distance(1, 7) = %d\n", distanceOf2BSTNodes(root, p, q)); 313 | } 314 | 315 | void testBTMaxDistance() 316 | { 317 | printf("\nTest btMaxDistance:\n"); 318 | int maxDepth; 319 | BTNode *root = btNewNode(10); 320 | root->left = btNewNode(5); 321 | root->left->left = btNewNode(1); 322 | root->left->right = btNewNode(8); 323 | root->right = btNewNode(15); 324 | root->right->right = btNewNode(7); 325 | int maxDistance = btMaxDistance(root, &maxDepth); 326 | levelOrder(root); 327 | printf("MaxDistance:%d\n", maxDistance); 328 | 329 | root = btNewNode(10); 330 | root->left = btNewNode(5); 331 | root->left->left = btNewNode(1); 332 | root->left->right = btNewNode(8); 333 | root->left->left->left = btNewNode(2); 334 | root->left->right->right = btNewNode(3); 335 | maxDistance = btMaxDistance(root, &maxDepth); 336 | levelOrder(root); 337 | printf("MaxDistance:%d\n", maxDistance); 338 | } 339 | 340 | void testBTMaxWidth() 341 | { 342 | BTNode *root = btNewNode(1); 343 | root->left = btNewNode(2); 344 | root->right = btNewNode(3); 345 | root->left->left = btNewNode(4); 346 | root->left->right = btNewNode(5); 347 | root->right->left = btNewNode(6); 348 | root->right->right = btNewNode(7); 349 | printf("\nTest MaxWidth tree:\n"); 350 | levelOrder(root); 351 | printf("MaxWidth Levelorder:%d\n", btMaxWidthLevelOrder(root)); 352 | printf("MaxWidth Preorder:%d\n", btMaxWidthPreOrder(root)); 353 | } 354 | 355 | void testSortedArray2BST() 356 | { 357 | printf("\nTest SortedArray2BST:\n"); 358 | int a[] = {1, 2, 3, 4, 5, 6}; 359 | int n = ALEN(a); 360 | printIntArray(a, n); 361 | BTNode *root = sortedArray2BST(a, 0, n-1); 362 | preOrder(root); 363 | printf("\n"); 364 | inOrder(root); 365 | printf("\n"); 366 | } 367 | 368 | void testSortedList2BST() 369 | { 370 | printf("\nTest SortedList2BST:\n"); 371 | int a[] = {1, 2, 3, 4, 5, 6}; 372 | int n = ALEN(a); 373 | ListNode *list = listCreate(a, n); 374 | int len = listLength(list); 375 | listTraverse(list); 376 | BTNode *root = sortedList2BST(&list, 0, len-1); 377 | preOrder(root); 378 | printf("\n"); 379 | inOrder(root); 380 | printf("\n"); 381 | } 382 | 383 | void testBT2DoublyList() 384 | { 385 | printf("\nTest BT2DoublyList:\n"); 386 | BTNode *root = createTestBST(); 387 | inOrder(root); 388 | printf("\n"); 389 | BTNode *prev = NULL, *head = NULL; 390 | bt2DoublyList(root, &prev, &head); 391 | BTNode *current = head; 392 | 393 | while (current) { 394 | printf("%d ", current->value); 395 | current = current->right; 396 | if (current == head) 397 | break; 398 | } 399 | printf("\n"); 400 | } 401 | 402 | int main() 403 | { 404 | testBTOperation(); 405 | testIsBST(); 406 | testIsCompleteBT(); 407 | testIsBalanceBT(); 408 | testIsOmorphism(); 409 | testBuildBT(); 410 | testSaveRestoreBST(); 411 | testSaveRestoreBT(); 412 | testLCABST(); 413 | testLCABT(); 414 | testLargestSubBST(); 415 | testDistanceOf2BTNodes(); 416 | testDistanceOf2BSTNodes(); 417 | testBTMaxDistance(); 418 | testBTMaxWidth(); 419 | testSortedArray2BST(); 420 | testSortedList2BST(); 421 | testBT2DoublyList(); 422 | return 0; 423 | } 424 | -------------------------------------------------------------------------------- /code/ds/tree/binary_tree/quiz.h: -------------------------------------------------------------------------------- 1 | #ifndef __BT_QUIZ_H 2 | #define __BT_QUIZ_H 3 | 4 | #include 5 | #include 6 | #include "bt.h" 7 | 8 | int isBSTError(BTNode *root); 9 | int isBSTUnefficient(BTNode *root); 10 | int isBSTEfficient(BTNode *root, BTNode *left, BTNode *right); 11 | int isBSTInOrder(BTNode *root, BTNode *prev); 12 | 13 | int isCompleteBTLevelOrder(BTNode *root); 14 | int isCompleteBTIndexMethod(BTNode *root, int index, int nodeCount); 15 | 16 | int isBalanceBTTop2Down(BTNode *root); 17 | int isBalanceBTDown2Top(BTNode *root, int *height); 18 | 19 | int isOmorphism(BTNode *t1, BTNode *t2); 20 | 21 | int findBTRootIndex(int inorder[], int n, int rootVal); 22 | BTNode *buildBTFromPreInOrder(int preorder[], int inorder[], int n, int offset, int count); 23 | BTNode *buildBTFromInPostOrder(int postorder[], int inorder[], int n, int offset, int count); 24 | 25 | void bstSave(BTNode *root, FILE *fp); 26 | BTNode *bstRestore(FILE *fp); 27 | 28 | void btSave(BTNode *root, FILE *fp); 29 | BTNode *btRestore(BTNode *root, FILE *fp); 30 | 31 | BTNode *bstLCA(BTNode *root, BTNode *p, BTNode *q); 32 | BTNode *btLCATop2Down(BTNode *root, BTNode *p, BTNode *q); 33 | BTNode *btLCADown2Top(BTNode *root, BTNode *p, BTNode *q); 34 | 35 | BTNode *largestSubBSTTop2Down(BTNode *root, int *bstSize); 36 | BTNode *largestSubBSTDown2Top(BTNode *root, int *bstSize); 37 | int findLargestSubBST(BTNode *root, int *min, int *max, int *maxNodes, BTNode **largestSubBST); 38 | 39 | int distanceOf2BTNodes(BTNode *root, BTNode *p, BTNode *q); 40 | int btDistanceFromRoot(BTNode *root, BTNode *node, int level); 41 | 42 | int distanceOf2BSTNodes(BTNode *root, BTNode *p, BTNode *q); 43 | int bstDistanceFromRoot(BTNode *root, BTNode *node); 44 | 45 | int btMaxDistance(BTNode *root, int *maxDepth); 46 | 47 | int btMaxWidthLevelOrder(BTNode *root); 48 | int btLevelWidth(BTNode *root, int level); 49 | 50 | int btMaxWidthPreOrder(BTNode *root); 51 | void btLevelWidthCount(BTNode *root, int level, int count[]); 52 | 53 | BTNode *sortedArray2BST(int a[], int start, int end); 54 | BTNode *sortedList2BST(ListNode **pList, int start, int end); 55 | 56 | void bt2DoublyList(BTNode *root, BTNode **pPrev, BTNode **pHead); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /code/util/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "util.h" 5 | 6 | /** 7 | * 工具函数-打印数组元素 8 | */ 9 | void printIntArray(int a[], int len) 10 | { 11 | int i; 12 | for (i = 0; i < len; i++) { 13 | printf("%d ", a[i]); 14 | } 15 | printf("\n"); 16 | } 17 | 18 | void printCharArray(char a[], int len) 19 | { 20 | int i; 21 | for (i = 0; i < len; i++) { 22 | printf("%c ", a[i]); 23 | } 24 | printf("\n"); 25 | } 26 | 27 | 28 | /** 29 | * 工具函数-求子串 30 | */ 31 | char *substr(char *s, int start, int n) 32 | { 33 | char *subs = (char *)malloc(n+1); 34 | int i = 0; 35 | 36 | while (i < n && *s != '\0') { 37 | subs[i] = s[start + i]; 38 | ++i; 39 | } 40 | subs[i] = '\0'; 41 | return subs; 42 | } 43 | 44 | /** 45 | * 工具函数-交换数组两个元素 46 | */ 47 | void swapInt(int a[], int i, int j) 48 | { 49 | int temp = a[i]; 50 | a[i] = a[j]; 51 | a[j] = temp; 52 | } 53 | 54 | void swapChar(char a[], int i, int j) 55 | { 56 | char temp = a[i]; 57 | a[i] = a[j]; 58 | a[j] = temp; 59 | } 60 | 61 | /** 62 | * 取三个数中最大值,对称式递归 63 | */ 64 | int max3(int a, int b, int c) 65 | { 66 | if (a >=b && a >= c) 67 | return a; 68 | return max3(b, c, a); 69 | } 70 | 71 | int max(int a, int b) 72 | { 73 | return a >= b ? a : b; 74 | } 75 | 76 | /** 77 | * 取三个数中最小值 78 | */ 79 | int min3(int a, int b, int c) 80 | { 81 | if (a <=b && a <= c) 82 | return a; 83 | return max3(b, c, a); 84 | } 85 | 86 | int min(int a, int b) 87 | { 88 | return a <= b ? a : b; 89 | } 90 | 91 | /** 92 | * 数组最大值 93 | */ 94 | int maxOfIntArray(int a[], int len) 95 | { 96 | int max = a[0], i; 97 | for (i = 1; i < len; i++) { 98 | if (a[i] > max) 99 | max = a[i]; 100 | } 101 | return max; 102 | } 103 | 104 | /* 105 | * 返回[l,u]范围的随机数 106 | */ 107 | int randInt(int l, int u) 108 | { 109 | srand(time(NULL)); 110 | return rand()%(u-l+1) + l; 111 | } 112 | 113 | int *copyIntArray(int a[], int n) 114 | { 115 | int *b = (int *)malloc(sizeof(int) * n); 116 | int i; 117 | for (i = 0; i < n; i++) { 118 | b[i] = a[i]; 119 | } 120 | return b; 121 | } 122 | 123 | /** 124 | * 十进制转二进制 125 | */ 126 | char *bin(int x) 127 | { 128 | int n = sizeof(x) * 8; 129 | int i; 130 | char *ret = (char *)malloc(n + 1); 131 | 132 | for(i = n-1; i >= 0; i--) { 133 | if ( x & ( 1 << i) ) 134 | ret[i] = '1'; 135 | else 136 | ret[i] = '0'; 137 | } 138 | 139 | ret[n] = '\0'; 140 | return ret; 141 | } 142 | -------------------------------------------------------------------------------- /code/util/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL_H 2 | #define __UTIL_H 3 | 4 | #define ALEN(a) (sizeof(a) / sizeof(a[0])) 5 | 6 | char *substr(char *s, int start, int n); 7 | void swapInt(int a[], int i, int j); 8 | void swapChar(char a[], int i, int j); 9 | void printIntArray(int a[], int len); 10 | void printCharArray(char a[], int len); 11 | 12 | int max(int a, int b); 13 | int max3(int a, int b, int c); 14 | int min(int a, int b); 15 | int min3(int a, int b, int c); 16 | char *bin(int x); 17 | 18 | int maxOfIntArray(int a[], int len); 19 | int randInt(int l, int u); 20 | 21 | int *copyIntArray(int a[], int n); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列0—C语言基础.md: -------------------------------------------------------------------------------- 1 | # 数据结构和算法面试题系列-C指针、数组和结构体 2 | > 本系列多年前我找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN,也收到了一些很好的反馈。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。 3 | 4 | # 0 概述 5 | 在用C语言实现一些常见的数据结构和算法时,C语言的基础不能少,特别是指针和结构体等知识。 6 | 7 | # 1 关于ELF文件 8 | linux中的C编译得到的目标文件和可执行文件都是ELF格式的,可执行文件中以segment来划分,目标文件中,我们是以section划分。一个segment包含一个或多个section,通过readelf命令可以看到完整的section和segment信息。看一个栗子: 9 | 10 | ``` 11 | char pear[40]; 12 | static double peach; 13 | int mango = 13; 14 | char *str = "hello"; 15 | 16 | static long melon = 2001; 17 | 18 | int main() 19 | { 20 | int i = 3, j; 21 | pear[5] = i; 22 | peach = 2.0 * mango; 23 | return 0; 24 | } 25 | ``` 26 | 这是个简单的C语言代码,现在分析下各个变量存储的位置。其中mango,melon属于data section,pear和peach属于common section中,而且peach和melon加了static,说明只能本文件使用。而str对应的字符串"helloworld"存储在rodata section中。main函数归属于text section,函数中的局部变量i,j在运行时在栈中分配空间。注意到前面说的全局未初始化变量peach和pear是在common section中,这是为了强弱符号而设置的。那其实最终链接成为可执行文件后,会归于BSS segment。同样的,text section和rodata section在可执行文件中都属于同一个segment。 27 | 28 | 更多ELF内容参见《程序猿的自我修养》一书。 29 | 30 | 31 | # 2 指针 32 | 想当年学习C语言最怕的就是指针了,当然《c与指针》和《c专家编程》以及《高质量C编程》里面对指针都有很好的讲解,系统回顾还是看书吧,这里我总结了一些基础和易错的点。环境是ubuntu14.10的32位系统,编译工具GCC。 33 | 34 | ## 2.1 指针易错点 35 | 36 | ``` 37 | /*** 38 | 指针易错示例1 demo1.c 39 | ***/ 40 | 41 | int main() 42 | { 43 | char *str = "helloworld"; //[1] 44 | str[1] = 'M'; //[2] 会报错 45 | char arr[] = "hello"; //[3] 46 | arr[1] = 'M'; 47 | return 0; 48 | } 49 | ``` 50 | demo1.c中,我们定义了一个指针和数组分别指向了一个字符串,然后修改字符串中某个字符的值。编译后运行会发现[2]处会报错,这是为什么呢?用命令```gcc -S demo1.c``` 生成汇编代码就会发现[1]处的helloworld是存储在rodata section的,是只读的,而[3]处的是存储在栈中的。所以[2]报错而[3]正常。在C中,用[1]中的方式创建字符串常量并赋值给指针,则字符串常量存储在rodata section。而如果是赋值给数组,则存储在栈中或者data section中(如[3]就是存储在栈中)。示例2给出了更多容易出错的点,可以看看。 51 | 52 | ``` 53 | /*** 54 | 指针易错示例2 demo2.c 55 | ***/ 56 | char *GetMemory(int num) { 57 | char *p = (char *)malloc(sizeof(char) * num); 58 | return p; 59 | } 60 | 61 | char *GetMemory2(char *p) { 62 | p = (char *)malloc(sizeof(char) * 100); 63 | } 64 | 65 | char *GetString(){ 66 | char *string = "helloworld"; 67 | return string; 68 | } 69 | 70 | char *GetString2(){ 71 | char string[] = "helloworld"; 72 | return string; 73 | } 74 | 75 | void ParamArray(char a[]) 76 | { 77 | printf("sizeof(a)=%d\n", sizeof(a)); // sizeof(a)=4,参数以指针方式传递 78 | } 79 | 80 | int main() 81 | { 82 | int a[] = {1, 2, 3, 4}; 83 | int *b = a + 1; 84 | printf("delta=%d\n", b-a); // delta=4,注意int数组步长为4 85 | printf("sizeof(a)=%d, sizeof(b)=%d\n", sizeof(a), sizeof(b)); //sizeof(a)=16, sizeof(b)=4 86 | ParamArray(a); 87 | 88 | 89 | //引用了不属于程序地址空间的地址,导致段错误 90 | /* 91 | int *p = 0; 92 | *p = 17; 93 | */ 94 | 95 | char *str = NULL; 96 | str = GetMemory(100); 97 | strcpy(str, "hello"); 98 | free(str); //释放内存 99 | str = NULL; //避免野指针 100 | 101 | //错误版本,这是因为函数参数传递的是副本。 102 | /* 103 | char *str2 = NULL; 104 | GetMemory2(str2); 105 | strcpy(str2, "hello"); 106 | */ 107 | 108 | char *str3 = GetString(); 109 | printf("%s\n", str3); 110 | 111 | //错误版本,返回了栈指针,编译器会有警告。 112 | /* 113 | char *str4 = GetString2(); 114 | */ 115 | return 0; 116 | } 117 | ``` 118 | ## 2.2 指针和数组 119 | 在2.1中也提到了部分指针和数组内容,在C中指针和数组在某些情况下可以相互转换来使用,比如```char *str="helloworld"```可以通过```str[1]```来访问第二个字符,也可以通过```*(str+1)```来访问。 120 | 此外,在函数参数中,使用数组和指针也是等同的。**但是指针和数组在有些地方并不等同,需要特别注意。** 121 | 122 | 比如我定义一个数组```char a[9] = "abcdefgh";```(注意字符串后面自动补\0),那么用a[1]读取字符'b'的流程是这样的: 123 | 124 | - 首先,数组a有个地址,我们假设是9980。 125 | - 然后取偏移值,偏移值为索引值*元素大小,这里索引是1,char大小也为1,因此加上9980为9981,得到数组a第1个元素的地址。(如果是int类型数组,那么这里偏移就是1 * 4 = 4) 126 | - 取地址9981处的值,就是'b'。 127 | 128 | 那如果定义一个指针```char *a = "abcdefgh";```,我们通过a[1]来取第一个元素的值。跟数组流程不同的是: 129 | 130 | - 首先,指针a自己有个地址,假设是4541. 131 | - 然后,从4541取a的值,也就是字符串“abcdefgh”的地址,假定是5081。 132 | - 接着就是跟之前一样的步骤了,5081加上偏移1,取5082地址处的值,这里就是'b'了。 133 | 134 | 通过上面的说明可以发现,指针比数组多了一个步骤,虽然看起来结果是一致的。因此,下面这个错误就比较好理解了。在demo3.c中定义了一个数组,然后在demo4.c中通过指针来声明并引用它,显然是会报错的。如果改成```extern char p[];```就正确了(当然声明你也可以写成extern char p[3],声明里面的数组大小跟实际大小不一致是没有关系的),一定要保证定义和声明匹配。 135 | 136 | ``` 137 | /*** 138 | demo3.c 139 | ***/ 140 | char p[] = "helloworld"; 141 | 142 | /*** 143 | demo4.c 144 | ***/ 145 | extern char *p; 146 | int main() 147 | { 148 | printf("%c\n", p[1]); 149 | return 0; 150 | } 151 | ``` 152 | 153 | # 3 typedef和#define 154 | typedef和#define都是经常用的,但是它们是不一样的。一个typedef可以塞入多个声明器,而#define一般只能有一个定义。在连续声明中,typedef定义的类型可以保证声明的变量都是同一种类型,而#define不行。此外,typedef是一种彻底的封装类型,在声明之后不能再添加其他的类型。如代码中所示。 155 | 156 | 157 | ``` 158 | #define int_ptr int * 159 | int_ptr i, j; //i是int *类型,而j是int类型。 160 | 161 | typedef char * char_ptr; 162 | char_ptr c1, c2; //c1, c2都是char *类型。 163 | 164 | #define peach int 165 | unsigned peach i; //正确 166 | 167 | typdef int banana; 168 | unsigned banana j; //错误,typedef声明的类型不能扩展其他类型。 169 | ``` 170 | 171 | 另外,typedef在结构体定义中也很常见,比如下面代码中的定义。需要注意的是,[1]和[2]是很不同的。当你如[1]中那样用typedef定义了struct foo,那么其实除了本身的foo结构标签,你还定义了foo这种结构类型,所以可以直接用foo来声明变量。而如[2]中的定义是不能用bar来声明变量的,因为它只是一个结构变量,并不是结构类型。 172 | 173 | 还有一点需要说明的是,结构体是有自己名字空间的,所以结构体中的字段可以跟结构体名字相同,比如[3]中那样也是合法的,当然尽量不要这样用。后面一节还会更详细探讨结构体,因为在Python源码中也有用到很多结构体。 174 | 175 | ``` 176 | typedef struct foo {int i;} foo; //[1] 177 | struct bar {int i;} bar; //[2] 178 | 179 | struct foo f; //正确,使用结构标签foo 180 | foo f; //正确,使用结构类型foo 181 | 182 | struct bar b; //正确,使用结构标签bar 183 | bar b; // 错误,使用了结构变量bar,bar已经是个结构体变量了,可以直接初始化,比如bar.i = 4; 184 | 185 | struct foobar {int foorbar;}; //[3]合法的定义 186 | ``` 187 | 188 | # 4 结构体 189 | 在学习数据结构的时候,定义链表和树结构会经常用到结构体。比如下面这个: 190 | 191 | ``` 192 | struct node { 193 | int data; 194 | struct node* next; 195 | }; 196 | ``` 197 | 在定义链表的时候可能就有点奇怪了,为什么可以这样定义,貌似这个时候struct node还没有定义好为什么就可以用next指针指向用这个结构体定义了呢? 198 | 199 | ## 4.1 不完全类型 200 | 这里要说下C语言里面的不完全类型。C语言可以分为函数类型,对象类型以及不完全类型。而对象类型还可以分为标量类型和非标量类型。算术类型(如int,float,char等)和指针类型属于标量类型,而定义完整的结构体,联合体,数组等都是非标量类型。而不完全类型是指没有定义完整的类型,比如下面这样的 201 | 202 | ``` 203 | struct s; 204 | union u; 205 | char str[]; 206 | ``` 207 | 具有不完全类型的变量可以通过多次声明组合成一个完全类型。比如下面2词声明str数组是合法的: 208 | 209 | ``` 210 | char str[]; 211 | char str[10]; 212 | ``` 213 | 此外,如果两个源文件定义了同一个变量,只要它们不全部是强类型的,那么也是可以编译通过的。比如下面这样是合法的,但是如果将file1.c中的```int i;```改成强定义如```int i = 5;```那么就会出错了。 214 | 215 | ``` 216 | //file1.c 217 | int i; 218 | 219 | //file2.c 220 | int i = 4; 221 | ``` 222 | 223 | ## 4.2 不完全类型结构体 224 | 不完全类型的结构体十分重要,比如我们最开始提到的struct node的定义,编译器从前往后处理,发现```struct node *next```时,认为struct node是一个不完全类型,next是一个指向不完全类型的指针,尽管如此,指针本身是完全类型,因为不管什么指针在32位系统都是占用4个字节。而到后面定义结束,struct node成了一个完全类型,从而next就是一个指向完全类型的指针了。 225 | 226 | ## 4.3 结构体初始化和大小 227 | 结构体初始化比较简单,需要注意的是结构体中包含有指针的时候,如果要进行字符串拷贝之类的操作,对指针需要额外分配内存空间。如下面定义了一个结构体student的变量stu和指向结构体的指针pstu,虽然stu定义的时候已经隐式分配了结构体内存,但是你要拷贝字符串到它指向的内存的话,需要显示分配内存。 228 | 229 | ``` 230 | struct student { 231 | char *name; 232 | int age; 233 | } stu, *pstu; 234 | 235 | int main() 236 | { 237 | stu.age = 13; //正确 238 | // strcpy(stu.name,"hello"); //错误,name还没有分配内存空间 239 | 240 | stu.name = (char *)malloc(6); 241 | strcpy(stu.name, "hello"); //正确 242 | 243 | return 0; 244 | } 245 | ``` 246 | 247 | 结构体大小涉及一个对齐的问题,对齐规则为: 248 | 249 | - 结构体变量首地址为最宽成员长度(如果有```#pragma pack(n)```,则取最宽成员长度和n的较小值,默认pragma的n=8)的整数倍 250 | - 结构体大小为最宽成员长度的整数倍 251 | - 结构体每个成员相对结构体首地址的偏移量都是每个成员本身大小(如果有pragma pack(n),则是n与成员大小的较小值)的整数倍 252 | 因此,下面结构体S1和S2虽然内容一样,但是字段顺序不同,大小也不同,```sizeof(S1) = 8, 而sizeof(S2) = 12```. 如果定义了```#pragma pack(2)```,则```sizeof(S1)=8;sizeof(S2)=8``` 253 | 254 | ``` 255 | typedef struct node1 256 | { 257 | int a; 258 | char b; 259 | short c; 260 | }S1; 261 | 262 | typedef struct node2 263 | { 264 | char b; 265 | int a; 266 | short c; 267 | }S2; 268 | ``` 269 | ## 4.4 柔性数组 270 | 柔性数组是指结构体的最后面一个成员可以是一个大小未知的数组,这样可以在结构体中存放变长的字符串。如代码中所示。**注意,柔性数组必须是结构体最后一个成员,柔性数组不占用结构体大小.**当然,你也可以将数组写成```char str[0]```,含义相同。 271 | 272 | 注:在学习Python源码过程中,发现其柔性数组声明并不是用一个空数组或者```char str[0]```,而是用的```char str[1]```,即数组大小为1。这是因为ISO C标准不允许声明大小为0的数组(`gcc -pedanti`参数可以检查是否符合ISO C标准),为了可移植性,所以常常看到的是声明数组大小为1。当然,很多编译器比如GCC等把数组大小为0作为了一个非标准的扩展,所以声明空的或者大小为0的柔性数组在GCC中是可以正常编译的。 273 | 274 | ``` 275 | struct flexarray { 276 | int len; 277 | char str[]; 278 | } *pfarr; 279 | 280 | int main() 281 | { 282 | char s1[] = "hello, world"; 283 | pfarr = malloc(sizeof(struct flexarray) + strlen(s1) + 1); 284 | pfarr->len = strlen(s1); 285 | strcpy(pfarr->str, s1); 286 | printf("%d\n", sizeof(struct flexarray)); // 4 287 | printf("%d\n", pfarr->len); // 12 288 | printf("%s\n", pfarr->str); // hello, world 289 | return 0; 290 | } 291 | ``` 292 | 293 | # 5 总结 294 | 295 | - 关于const,c语言中的const不是常量,所以不能用const变量来定义数组,如```const int N = 3; int a[N];```这是错误的。 296 | - 注意内存分配和释放,杜绝野指针。 297 | - C语言中弱符号和强符号一起链接是合法的。 298 | - 注意指针和数组的区别。 299 | - typedef和#define是不同的。 300 | - 注意包含指针的结构体的初始化和柔性数组的使用。 301 | 302 | # 6 参考资料 303 | - 《c专家编程》 304 | - 《linux c一站式编程》 305 | - 《高质量C/C++编程》 306 | - [结构体字节对齐](http://www.cnblogs.com/dolphin0520/archive/2011/09/17/2179466.html) 307 | - [柔性数组](http://www.cnblogs.com/Daniel-G/archive/2012/12/02/2798496.html) -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列10—随机算法总结.md: -------------------------------------------------------------------------------- 1 | # 数据结构和算法面试题系列—随机算法总结 2 | 3 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 4 | 5 | # 0 概述 6 | 随机算法涉及大量概率论知识,有时候难得去仔细看推导过程,当然能够完全了解推导的过程自然是有好处的,如果不了解推导过程,至少记住结论也是必要的。本文总结最常见的一些随机算法的题目,是几年前找工作的时候写的。需要说明的是,这里用到的随机函数 `randInt(a, b)` 假定它能随机的产生范围 `[a,b]` 内的整数,即产生每个整数的概率相等(虽然在实际中并不一定能实现,不过不要太在意,这个世界很多事情都很随机)。本文代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/random)。 7 | 8 | # 1 随机排列数组 9 | 假设给定一个数组 `A`,它包含元素 1 到 N,我们的目标是构造这个数组的一个均匀随机排列。 10 | 11 | 一个常用的方法是为数组每个元素 `A[i]` 赋一个随机的优先级 `P[i]`,然后依据优先级对数组进行排序。比如我们的数组为 `A = {1, 2, 3, 4}`,如果选择的优先级数组为 `P = {36, 3, 97, 19}`,那么就可以得到数列 `B={2, 4, 1, 3}`,因为 `3` 的优先级最高(为97),而 `2` 的优先级最低(为3)。这个算法需要产生优先级数组,还需使用优先级数组对原数组排序,这里就不详细描述了,还有一种更好的方法可以得到随机排列数组。 12 | 13 | 产生随机排列数组的一个更好的方法是原地排列(`in-place`)给定数组,可以在 `O(N)` 的时间内完成。伪代码如下: 14 | 15 | ``` 16 | RANDOMIZE-IN-PLACE ( A , n ) 17 | for i ←1 to n do 18 | swap A[i] ↔ A[RANDOM(i , n )] 19 | ``` 20 | 21 | 如代码中所示,第 `i` 次迭代时,元素 `A[i]` 是从元素 `A[i...n]`中随机选取的,在第 `i` 次迭代后,我们就再也不会改变 `A[i]`。 22 | 23 | **A[i] 位于任意位置j的概率为 1/n**。这个是很容易推导的,比如 `A[1]` 位于位置 1 的概率为 `1/n`,这个显然,因为 `A[1]` 不被1到n的元素替换的概率为 `1/n`,而后就不会再改变 `A[1]` 了。而`A[1]` 位于位置 2 的概率也是 `1/n`,因为 `A[1]` 要想位于位置2,则必须在第一次与 `A[k]` (k=2...n) 交换,同时第二次 `A[2]` 与 `A[k]`替换,第一次与 `A[k]` 交换的概率为`(n-1)/n`,而第二次替换概率为 `1/(n-1)`,所以总的概率是 `(n-1)/n * 1/(n-1) = 1/n`。同理可以推导其他情况。 24 | 25 | 当然这个条件只能是随机排列数组的一个必要条件,也就是说,满足元素 `A[i]` 位于位置 `j` 的概率为`1/n` 不一定就能说明这可以产生随机排列数组。因为它可能产生的排列数目少于 `n!`,尽管概率相等,但是排列数目没有达到要求,算法导论上面有一个这样的反例。 26 | 27 | 算法 `RANDOMIZE-IN-PLACE`可以产生均匀随机排列,它的证明过程如下: 28 | 29 | 首先给出k排列的概念,所谓 k 排列就是从n个元素中选取k个元素的排列,那么它一共有 `n!/(n-k)!` 个 k 排列。 30 | 31 | **循环不变式:for循环第i次迭代前,对于每个可能的i-1排列,子数组A[1...i-1]包含该i-1排列的概率为 `(n-i+1)! / n!`。** 32 | 33 | - 初始化:在第一次迭代前,i=1,则循环不变式指的是对于每个0排列,子数组A[1...i-1]包含该0排列的概率为 `(n-1+1)! / n! = 1`。A[1...0]为空的数组,0排列则没有任何元素,因此A包含所有可能的0排列的概率为1。不变式成立。 34 | - 维持:假设在第i次迭代前,数组的i-1排列出现在 `A[1...i-1]` 的概率为 `(n-i+1) !/ n!`,那么在第i次迭代后,数组的所有i排列出现在 `A[1...i]` 的概率为 `(n-i)! / n!`。下面来推导这个结论: 35 | 36 | - 考虑一个特殊的 i 排列 p = {x1, x2, ... xi},它由一个 i-1 排列 p' ={x1, x2,..., xi−1} 后面跟一个 xi 构成。设定两个事件变量E1和E2: 37 | - E1为该算法将排列 `p'` 放置到 `A[1...i-1]`的事件,概率由归纳假设得知为 `Pr(E1) = (n-i+1)! / n!`。 38 | - E2为在第 i 次迭代时将 xi 放入到 `A[i]` 的事件。 39 | 因此我们得到 i 排列出现在 `A[1...i]` 的概率为 `Pr {E2 ∩ E1} = Pr {E2 | E1} Pr {E1}`。而`Pr {E2 | E1} = 1/(n − i + 1)`,所以 40 | `Pr {E2 ∩ E1} = Pr {E2 | E1} Pr {E1}= 1 /(n − i + 1) * (n − i + 1)! / n! = (n − i )! / n!`。 41 | 42 | - 结束:结束的时候 `i=n+1`,因此可以得到 `A[1...n]` 是一个给定 n 排列的概率为 `1/n!`。 43 | 44 | C实现代码如下: 45 | 46 | ``` 47 | void randomInPlace(int a[], int n) 48 | { 49 | int i; 50 | for (i = 0; i < n; i++) { 51 | int rand = randInt(i, n-1); 52 | swapInt(a, i, rand); 53 | } 54 | } 55 | ``` 56 | 57 | ### 扩展 58 | 如果上面的随机排列算法写成下面这样,是否也能产生均匀随机排列? 59 | 60 | ``` 61 | PERMUTE-WITH-ALL( A , n ) 62 | for i ←1 to n do 63 | swap A[i] ↔A[RANDOM(1 , n )] 64 | ``` 65 | 66 | 注意,该算法不能产生均匀随机排列。假定 `n=3`,则该算法可以产生`3*3*3=27`个输出,而3个元素只有`3!=6`个不同的排列,要使得这些排列出现概率等于 `1/6`,则必须使得每个排列出现次数 m 满足`m/27=1/6`,显然,没有这样的整数符合条件。而实际上各个排列出现的概率如下,如 `{1,2,3}` 出现的概率为`4/27`,不等于 `1/6`。 67 | 68 | 69 | 排 列 | 概 率 70 | ------------- | ------------- 71 | <1, 2, 3> | 4/27 72 | <1, 3, 2> | 5/27 73 | <2, 1, 3> | 5/27 74 | <2, 3, 1> | 5/27 75 | <3, 1, 2> | 4/27 76 | <3, 2, 1> | 4/27 77 | 78 | 79 | # 2 随机选取一个数字 80 | **题:** 给定一个未知长度的整数流,如何随机选取一个数?(所谓随机就是保证每个数被选取的概率相等) 81 | 82 | **解1:** 如果数据流不是很长,可以存在数组中,然后再从数组中随机选取。当然题目说的是未知长度,所以如果长度很大不足以保存在内存中的话,这种解法有其局限性。 83 | 84 | **解2:** 如果数据流很长的话,可以这样: 85 | 86 | - 如果数据流在第1个数字后结束,那么必选第1个数字。 87 | - 如果数据流在第2个数字后结束,那么我们选第2个数字的概率为1/2,我们以1/2的概率用第2个数字替换前面选的随机数,得到新的随机数。 88 | - ...... 89 | - 如果数据流在第n个数字后结束,那么我们选择第n个数字的概率为1/n,即我们以1/n的概率用第n个数字替换前面选的随机数,得到新的随机数。 90 | 91 | 一个简单的方法就是使用随机函数 `f(n)=bigrand()%n`,其中 `bigrand()` 返回很大的随机整数,当数据流到第 `n` 个数时,如果 `f(n)==0`,则替换前面的已经选的随机数,这样可以保证每个数字被选中的概率都是 `1/n`。如当 `n=1` 时,则`f(1)=0`,则选择第 1 个数,当 `n=2` 时,则第 2 个数被选中的概率都为 `1/2`,以此类推,当数字长度为 n 时,第 n 个数字被选中的概率为 `1/n`。代码如下(注:在 Linux/MacOS 下,`rand()` 函数已经可以返回一个很大的随机数了,就当做bigrand()用了): 92 | 93 | ``` 94 | void randomOne(int n) 95 | { 96 | int i, select = 0; 97 | for (i = 1; i < n; i++) { 98 | int rd = rand() % n; 99 | if (rd == 0) { 100 | select = i; 101 | } 102 | } 103 | printf("%d\n", select); 104 | } 105 | ``` 106 | 107 | # 3 随机选取M个数字 108 | **题**: 程序输入包含两个整数 m 和 n ,其中 `m0 && x[j-1]>x[j]; j--) { 161 | swapInt(x, j, j-1); 162 | } 163 | } 164 | 165 | for (i = 0; i < m; i++) { 166 | printf("%d ", x[i]); 167 | } 168 | 169 | printf("\n"); 170 | } 171 | ``` 172 | 173 | # 4 rand7 生成 rand10 问题 174 | **题:** 已知一个函数rand7()能够生成1-7的随机数,每个数概率相等,请给出一个函数rand10(),该函数能够生成 1-10 的随机数,每个数概率相等。 175 | 176 | **解1:** 要产生 1-10 的随机数,我们要么执行 rand7() 两次,要么直接乘以一个数字来得到我们想要的范围值。如下面公式(1)和(2)。 177 | 178 | ``` 179 | idx = 7 * (rand7()-1) + rand7() ---(1) 正确 180 | idx = 8 * rand7() - 7 ---(2) 错误 181 | ``` 182 | 183 | 上面公式 (1) 能够产生 `1-49` 的随机数,为什么呢?因为 rand7() 的可能的值为 1-7,两个 rand7() 则可能产生 49 种组合,且正好是 1-49 这 49 个数,每个数出现的概率为 `1/49`,于是我们可以将大于 40 的丢弃,然后取 `(idx-1) % 10 + 1` 即可。公式(2)是错误的,因为它生成的数的概率不均等,而且也无法生成49个数字。 184 | 185 | ``` 186 | 1 2 3 4 5 6 7 187 | 1 1 2 3 4 5 6 7 188 | 2 8 9 10 1 2 3 4 189 | 3 5 6 7 8 9 10 1 190 | 4 2 3 4 5 6 7 8 191 | 5 9 10 1 2 3 4 5 192 | 6 6 7 8 9 10 * * 193 | 7 * * * * * * * 194 | ``` 195 | 196 | 该解法基于一种叫做拒绝采样的方法。主要思想是只要产生一个目标范围内的随机数,则直接返回。如果产生的随机数不在目标范围内,则丢弃该值,重新取样。由于目标范围内的数字被选中的概率相等,这样一个均匀的分布生成了。代码如下: 197 | 198 | ``` 199 | int rand7ToRand10Sample() { 200 | int row, col, idx; 201 | do { 202 | row = rand7(); 203 | col = rand7(); 204 | idx = col + (row-1)*7; 205 | } while (idx > 40); 206 | 207 | return 1 + (idx-1) % 10; 208 | } 209 | ``` 210 | 211 | 由于row范围为1-7,col范围为1-7,这样idx值范围为1-49。大于40的值被丢弃,这样剩下1-40范围内的数字,通过取模返回。下面计算一下得到一个满足1-40范围的数需要进行取样的次数的期望值: 212 | 213 | ``` 214 | E(# calls to rand7) = 2 * (40/49) + 215 | 4 * (9/49) * (40/49) + 216 | 6 * (9/49)2 * (40/49) + 217 | ... 218 | 219 | ∞ 220 | = ∑ 2k * (9/49)k-1 * (40/49) 221 | k=1 222 | 223 | = (80/49) / (1 - 9/49)2 224 | = 2.45 225 | ``` 226 | 227 | **解2:** 上面的方法大概需要2.45次调用 rand7 函数才能得到 1 个 1-10 范围的数,下面可以进行再度优化。对于大于40的数,我们不必马上丢弃,可以对 41-49 的数减去 40 可得到 1-9 的随机数,而rand7可生成 1-7 的随机数,这样可以生成 1-63 的随机数。对于 1-60 我们可以直接返回,而 61-63 则丢弃,这样需要丢弃的数只有3个,相比前面的9个,效率有所提高。而对于61-63的数,减去60后为 1-3,rand7 产生 1-7,这样可以再度利用产生 1-21 的数,对于 1-20 我们则直接返回,对于 21 则丢弃。这时,丢弃的数就只有1个了,优化又进一步。当然这里面对rand7的调用次数也是增加了的。代码如下,优化后的期望大概是 2.2123。 228 | 229 | ``` 230 | int rand7ToRand10UtilizeSample() { 231 | int a, b, idx; 232 | while (1) { 233 | a = randInt(1, 7); 234 | b = randInt(1, 7); 235 | idx = b + (a-1)*7; 236 | if (idx <= 40) 237 | return 1 + (idx-1)%10; 238 | 239 | a = idx-40; 240 | b = randInt(1, 7); 241 | // get uniform dist from 1 - 63 242 | idx = b + (a-1)*7; 243 | if (idx <= 60) 244 | return 1 + (idx-1)%10; 245 | 246 | a = idx-60; 247 | b = randInt(1, 7); 248 | // get uniform dist from 1-21 249 | idx = b + (a-1)*7; 250 | if (idx <= 20) 251 | return 1 + (idx-1)%10; 252 | } 253 | } 254 | ``` 255 | 256 | # 5 趣味概率题 257 | ### 1)称球问题 258 | **题**: 有12个小球,其中一个是坏球。给你一架天平,需要你用最少的称次数来确定哪个小球是坏的,并且它到底是轻了还是重了。 259 | 260 | **解**: 之前有总结过二分查找算法,我们知道二分法可以加快有序数组的查找。相似的,比如在数字游戏中,如果要你猜一个介于 `1-64` 之间的数字,用二分法在6次内肯定能猜出来。但是称球问题却不同。称球问题这里 12 个小球,坏球可能是其中任意一个,这就有 12 种可能性。而坏球可能是重了或者轻了这2种情况,于是这个问题一共有 `12*2 = 24` 种可能性。每次用天平称,天平可以输出的是 `平衡、左重、右重` 3 种可能性,即称一次可以将问题可能性缩小到原来的 `1/3`,则一共 `24` 种可能性可以在 `3` 次内称出来(`3^3 = 27`)。 261 | 262 | **为什么最直观的称法 `6-6` 不是最优的?在 `6-6` 称的时候,天平平衡的可能性是0,而最优策略应该是让天平每次称量时的概率均等,这样才能三等分答案的所有可能性。** 263 | 264 | 具体怎么实施呢? 将球编号为1-12,采用 `4, 4` 称的方法。 265 | 266 | - 我们先将 `1 2 3 4` 和 `5 6 7 8` 进行第1次称重。 267 | - 如果第1次平衡,则坏球肯定在 `9-12` 号中。则此时只剩下 `9-12` 4个球,可能性为 `9- 10- 11- 12- 9+ 10+ 11+ 12+` 这8种可能。接下来将 `9 10 11` 和 `1 2 3`称第2次:如果平衡,则 `12` 号小球为坏球,将12号小球与1号小球称第3次即可确认轻还是重。如果不平衡,则如果重了说明坏球重了,继续将9和10号球称量,重的为坏球,平衡的话则11为坏球。 268 | - 如果第1次不平衡,则坏球肯定在 `1-8`号中。则还剩下的可能性是 `1+ 2+ 3+ 4+ 5- 6- 7- 8-` 或者 `1- 2- 3- 4- 5+ 6+ 7+ 8+`,如果是`1 2 3 4` 这边重,则可以将 `1 2 6` 和 `3 4 5` 称,如果平衡,则必然是 `7 8` 轻了,再称一次7和1,便可以判断7和8哪个是坏球了。如果不平衡,假定是 `1 2 6` 这边重,则可以判断出 `1 2` 重了或者 `5` 轻了,为什么呢?因为如果是`3+ 4+ 6-`,则 `1 2 3 4` 比 `5 6 7 8` 重,但是 `1 2 6` 应该比 `3 4 5` 轻。其他情况同理,最多3次即可找出坏球。 269 | 270 | 下面这个图更加清晰说明了这个原理。 271 | 272 | ![称球问题图示](https://user-gold-cdn.xitu.io/2018/9/28/1662052cd84928f4?w=476&h=480&f=jpeg&s=62339) 273 | 274 | ### 2)生男生女问题 275 | **题:** 在重男轻女的国家里,男女的比例是多少?在一个重男轻女的国家里,每个家庭都想生男孩,如果他们生的孩子是女孩,就再生一个,直到生下的是男孩为止。这样的国家,男女比例会是多少? 276 | 277 | **解:** 还是1:1。在所有出生的第一个小孩中,男女比例是1:1;在所有出生的第二个小孩中,男女比例是1:1;.... 在所有出生的第n个小孩中,男女比例还是1:1。所以总的男女比例是1:1。 278 |   279 | ### 3)约会问题 280 | **题:** 两人相约5点到6点在某地会面,先到者等20分钟后离去,求这两人能够会面的概率。 281 | 282 | **解:** 设两人分别在5点X分和5点Y分到达目的地,则他们能够会面的条件是 `|X-Y| <= 20`,而整个范围为 `S={(x, y): 0 =< x <= 60,  0=< y <= 60}`,如果画出坐标轴的话,会面的情况为坐标轴中表示的面积,概率为 `(60^2 - 40^2) / 60^2 = 5/9`。 283 | 284 | ### 4)帽子问题 285 | **题:** 有n位顾客,他们每个人给餐厅的服务生一顶帽子,服务生以随机的顺序归还给顾客,请问拿到自己帽子的顾客的期望数是多少? 286 | 287 | **解:** 使用指示随机变量来求解这个问题会简单些。定义一个随机变量X等于能够拿到自己帽子的顾客数目,我们要计算的是 `E[X]`。对于 `i=1, 2 ... n`,定义 Xi =I {顾客i拿到自己的帽子},则 X=X1+X2+...Xn。由于归还帽子的顺序是随机的,所以每个顾客拿到自己帽子的概率为1/n,即 Pr(Xi=1)=1/n,从而 E(Xi)=1/n,所以E(X)=E(X1 + X2 + ...Xn)= E(X1)+E(X2)+...E(Xn)=n*1/n = 1,即大约有1个顾客可以拿到自己的帽子。 288 | 289 | ### 5)生日悖论 290 | **题:** 一个房间至少要有多少人,才能使得有两个人的生日在同一天? 291 | 292 | **解:** 对房间k个人中的每一对(i, j)定义指示器变量 Xij = {i与j生日在同一天} ,则i与j生日相同时,Xij=1,否则 Xij=0。两个人在同一天生日的概率 Pr(Xij=1)=1/n 。则用X表示同一天生日的两人对的数目,则 E(X)=E(∑ki=1∑kj=i+1Xij) = C(k,2)*1/n = k(k-1)/2n,令 `k(k-1)/2n >=1`,可得到 `k>=28`,即至少要有 28 个人,才能期望两个人的生日在同一天。 293 | 294 | ### 6)概率逆推问题 295 | **题:** 如果在高速公路上30分钟内看到一辆车开过的几率是0.95,那么在10分钟内看到一辆车开过的几率是多少?(假设常概率条件下) 296 | 297 | **解:** 假设10分钟内看到一辆车开过的概率是x,那么没有看到车开过的概率就是1-x,30分钟没有看到车开过的概率是 `(1-x)^3`,也就是 `0.05`。所以得到方程 `(1-x)^3 = 0.05` ,解方程得到 x 大约是 0.63。  298 |   299 | # 参考资料 300 | - 算法导论第6章随机算法 301 | - 编程珠玑第12章 302 | - https://leetcode.com/articles/implement-rand10-using-rand7/ 303 | - http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/ 304 | - http://blog.csdn.net/wxwtj/article/details/6621430 305 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列11—递归算法总结.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | > 3 | > 注:此刻,我正在从广州回家的绿皮火车上整理的这篇文章,火车摇摇晃晃,颠簸的尽是乡愁,忍不住又想翻翻周云蓬的《绿皮火车》了。———记于2018年9月30日夜22:00分。 4 | 5 | 6 | # 0 概述 7 | 前面总结了随机算法,这次再把以前写的递归算法的文章梳理一下,这篇文章主要是受到宋劲松老师写的《Linux C编程》的递归章节启发写的。最能体现算法精髓的非递归莫属了,希望这篇文章对初学递归或者对递归有困惑的朋友们能有所帮助,如有错误,也恳请各路大牛指正。二叉树的递归示例代码请参见仓库的 binary_tree 目录,本文其他代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/recursive)。 8 | 9 | # 1 递归算法初探 10 | 本段内容主要摘自《linux C一站式编程》,作者是宋劲松老师,这是我觉得目前看到的国内关于`Linux C编程`的最好的技术书籍之一,强烈推荐下! 11 | 12 | 关于递归的一个简单例子是求整数阶乘,`n!=n*(n-1)!,0!=1` 。则可以写出如下的递归程序: 13 | 14 | ``` 15 | int factorial(int n) 16 | { 17 | if (n == 0) 18 | return 1; 19 | else { 20 | int recurse = factorial(n-1); 21 | int result = n * recurse; 22 | return result; 23 | } 24 | } 25 | ``` 26 | factorial这个函数就是一个递归函数,它调用了它自己。自己直接或间接调用自己的函数称为递归函数。**如果觉得迷惑,可以把 factorial(n-1) 这一步看成是在调用另一个函数--另一个有着相同函数名和相同代码的函数,调用它就是跳到它的代码里执行,然后再返回 factorial(n-1) 这个调用的下一步继续执行。** 27 | 28 | 为了证明递归算法的正确性,我们可以一步步跟进去看执行结果。记得刚学递归算法的时候,老是有丈二和尚摸不着头脑的感觉,那时候总是想着把递归一步步跟进去看执行结果。递归层次少还算好办,但是层次一多,头就大了,完全不知道自己跟到了递归的哪一层。比如求阶乘,如果只是factorial(3)跟进去问题还不大,但是若是factorial(100)要跟进去那真的会烦死人。 29 | 30 | **事实上,我们并不是每个函数都需要跟进去看执行结果的,比如我们在自己的函数中调用printf函数时,并没有钻进去看它是怎么打印的,因为我们相信它能完成打印工作。** 我们在写factorial函数时有如下代码: 31 | 32 | ``` 33 | int recurse = factorial(n-1); 34 | int result = n * recurse; 35 | ``` 36 | 37 | 这时,如果我们相信factorial是正确的,那么传递参数为n-1它就会返回(n-1)!,那么result=n*(n-1)!=n!,从而这就是factorial(n)的结果。 38 | 39 | 当然这有点奇怪:我们还没写完factorial这个函数,凭什么要相信factorial(n-1)是正确的?**如果你相信你正在写的递归函数是正确的,并调用它,然后在此基础上写完这个递归函数,那么它就会是正确的,从而值得你相信它正确。** 40 | 41 | 这么说还是有点玄乎,我们从数学上严格证明一下 `factorial` 函数的正确性。刚才说了,`factorial(n)` 的正确性依赖于 `factorial(n-1)` 的正确性,只要后者正确,在后者的结果上乘个 n 返回这一步显然也没有疑问,那么我们的函数实现就是正确的。因此要证明`factorial(n)` 的正确性就是要证明 `factorial(n-1)` 的正确性,同理,要证明`factorial(n-1)` 的正确性就是要证明 `factorial(n-2)` 的正确性,依此类推下去,最后是:要证明 `factorial(1)` 的正确性就是要证明 `factorial(0)` 的正确性。而`factorial(0)` 的正确性不依赖于别的函数调用,它就是程序中的一个小的分支`return 1;` 这个 1 是我们根据阶乘的定义写的,肯定是正确的,因此 `factorial(1)` 的实现是正确的,因此 `factorial(2)` 也正确,依此类推,最后 `factorial(n)` 也是正确的。 42 | 43 | 其实这就是在中学时学的数学归纳法,用数学归纳法来证明只需要证明两点:Base Case正确,递推关系正确。写递归函数时一定要记得写Base Case,否则即使递推关系正确,整个函数也不正确。如果 factorial 函数漏掉了Base Case,那么会导致无限循环。 44 | 45 | # 2 递归经典问题 46 | 从上一节的一个关于求阶乘的简单例子的论述,我们可以了解到递归算法的精髓:**要从功能上理解函数,同时你要相信你正在写的函数是正确的,在此基础上调用它,那么它就是正确的。** 下面就从几个常见的算法题来看看如何理解递归,这是我的一些理解,欢迎大家提出更好的方法。 47 | 48 | ## 2.1)汉诺塔问题 49 | **题:** 汉诺塔问题是个常见问题,就是说有n个大小不等的盘子放在一个塔A上面,自底向上按照从小到大的顺序排列。要求将所有n个盘子搬到另一个塔C上面,可以借助一个塔B中转,但是要满足任何时刻大盘子不能放在小盘子上面。 50 | 51 | **解:** 基本思想分三步,先把上面的 N-1 个盘子经 C 移到 B,然后将最底下的盘子移到 C,再将 B 上面的N-1个盘子经 A 移动到 C。总的时间复杂度 `f(n)=2f(n-1)+1`,所以 `f(n)=2^n-1`。 52 | 53 | ``` 54 | /** 55 | * 汉诺塔 56 | */ 57 | void hano(char a, char b, char c, int n) { 58 | if (n <= 0) return; 59 | 60 | hano(a, c, b, n-1); 61 | move(a, c); 62 | hano(b, a, c, n-1); 63 | } 64 | 65 | void move(char a, char b) 66 | { 67 | printf("%c->%c\n", a, b); 68 | } 69 | ``` 70 | ## 2.2)求二叉树的深度 71 | 这里的深度指的是二叉树从根结点到叶结点最大的高度,比如只有一个结点,则深度为1,如果有N层,则高度为N。 72 | 73 | ``` 74 | int depth(BTNode* root) 75 | { 76 | if (root == NULL) 77 | return 0; 78 | else { 79 | int lDepth = depth(root->left); //获取左子树深度 80 | int rDepth = depth(root->right); //获取右子树深度 81 | return lDepth>rDepth? lDepth+1: rDepth+1; //取较大值+1即为二叉树深度 82 | } 83 | } 84 | ``` 85 | 86 | 那么如何从功能上理解 `depth` 函数呢?我们可以知道定义该函数的目的就是求二叉树深度,也就是说我们要是完成了函数 `depth`,那么 `depth(root)` 就能正确返回以 root 为根结点的二叉树的深度。因此我们的代码中 `depth(root->left)` 返回左子树的深度,而`depth(root->right)` 返回右子树的深度。尽管这个时候我们还没有写完 `depth` 函数,但是我们相信 `depth` 函数能够正确完成功能。因此我们得到了 `lDepth` 和`rDepth`,而后通过比较返回较大值加1为二叉树的深度。 87 | 88 | **如果不好理解,可以想象在 depth 中调用的函数 depth(root->left) 为另外一个同样名字完成相同功能的函数,这样就好理解了。注意 Base Case,这里就是当 root==NULL 时,则深度为0,函数返回0**。 89 | 90 | ## 2.3)判断二叉树是否平衡 91 | 一颗平衡的二叉树是指其任意结点的左右子树深度之差不大于1。判断一棵二叉树是否是平衡的,可以使用递归算法来实现。 92 | 93 | ``` 94 | int isBalanceBTTop2Down(BTNode *root) 95 | { 96 | if (!root) return 1; 97 | 98 | int leftHeight = btHeight(root->left); 99 | int rightHeight = btHeight(root->right); 100 | int hDiff = abs(leftHeight - rightHeight); 101 | 102 | if (hDiff > 1) return 0; 103 | 104 | return isBalanceBTTop2Down(root->left) && isBalanceBTTop2Down(root->right); 105 | } 106 | ``` 107 | 该函数的功能定义是二叉树 root 是平衡二叉树,即它所有结点的左右子树深度之差不大于1。首先判断根结点是否满足条件,如果不满足,则直接返回 0。如果满足,则需要判断左子树和右子树是否都是平衡二叉树,若都是则返回1,否则0。 108 | 109 | ## 2.4)排列算法 110 | 排列算法也是递归的典范,记得当初第一次看时一层层跟代码,头都大了,现在从函数功能上来看确实好理解多了。先看代码: 111 | 112 | ``` 113 | /** 114 | * 输出全排列,k为起始位置,n为数组大小 115 | */ 116 | void permute(int a[], int k, int n) 117 | { 118 | if (k == n-1) { 119 | printIntArray(a, n); // 输出数组 120 | } else { 121 | int i; 122 | for (i = k; i < n; i++) { 123 | swapInt(a, i, k); // 交换 124 | permute(a, k+1, n); // 下一次排列 125 | swapInt(a, i, k); // 恢复原来的序列 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | 首先明确的是 `perm(a, k, n)` 函数的功能:输出数组 a 从位置 k 开始的所有排列,数组长度为 n。这样我们在调用程序的时候,调用格式为 `perm(a, 0, n)`,即输出数组从位置 0 开始的所有排列,也就是该数组的所有排列。基础条件是 `k==n-1`,此时已经到达最后一个元素,一次排列已经完成,直接输出。否则,从位置k开始的每个元素都与位置k的值交换(包括自己与自己交换),然后进行下一次排列,排列完成后记得恢复原来的序列。 132 | 133 | 假定数组a 134 | aan na a 135 | =3,则程序调用 perm(a, 0, 3) 可以如下理解: 136 | 第一次交换 0,0,并执行perm(a, 1, 3),执行完再次交换0,0,数组此时又恢复成初始值。 137 | 第二次交换 1,0(注意数组此时是初始值),并执行perm(a, 1, 3), 执行完再次交换1,0,数组此时又恢复成初始值。 138 | 第三次交换 2,0,并执行perm(a, 1, 3),执行完成后交换2,0,数组恢复成初始值。 139 | 140 | **也就是说,从功能上看,首先确定第0个位置,然后调用perm(a, 1, 3)输出从1开始的排列,这样就可以输出所有排列。而第0个位置可能的值为a[0], a[1],a[2],这通过交换来保证第0个位置可能出现的值,记得每次交换后要恢复初始值。** 141 | 142 | 如数组 `a={1,2,3}`,则程序运行输出结果为:`1 2 3 ,1 3 2 ,2 1 3 ,2 3 1 ,3 2 1 ,3 1 2`。即先输出以1为排列第一个值的排列,而后是2和3为第一个值的排列。 143 | 144 | ## 2.5)组合算法 145 | 组合算法也可以用递归实现,只是它的原理跟0-1背包问题类似。即要么选要么不选,注意不能选重复的数。完整代码如下: 146 | 147 | ``` 148 | /* 149 | * 组合主函数,包括选取1到n个数字 150 | */ 151 | void combination(int a[], int n) 152 | { 153 | int *select = (int *)calloc(sizeof(int), n); // select为辅助数组,用于存储选取的数 154 | int k; 155 | for (k = 1; k <= n; k++) { 156 | combinationUtil(a, n, 0, k, select); 157 | } 158 | } 159 | 160 | /* 161 | * 组合工具函数:从数组a从位置i开始选取k个数 162 | */ 163 | void combinationUtil(int a[], int n, int i, int k, int *select) 164 | { 165 | if (i > n) return; //位置超出数组范围直接返回,否则非法访问会出段错误 166 | 167 | if (k == 0) { //选取完了,输出选取的数字 168 | int j; 169 | for (j = 0; j < n; j++) { 170 | if (select[j]) 171 | printf("%d ", a[j]); 172 | } 173 | printf("\n"); 174 | } else { 175 | select[i] = 1; 176 | combinationUtil(a, n, i+1, k-1, select); //第i个数字被选取,从后续i+1开始选取k-1个数 177 | select[i] = 0; 178 | combinationUtil(a, n, i+1, k, select); //第i个数字不选,则从后续i+1位置开始还要选取k个数 179 | } 180 | } 181 | ``` 182 | 183 | ## 2.6) 逆序打印字符串 184 | 这个比较简单,代码如下: 185 | 186 | ``` 187 | void reversePrint(const char *str) 188 | { 189 | if (!*str) 190 | return; 191 | 192 | reversePrint(str + 1); 193 | putchar(*str); 194 | } 195 | ``` 196 | 197 | ## 2.7) 链表逆序 198 | 链表逆序通常我们会用迭代的方式实现,但是如果要显得特立独行一点,可以使用递归,如下,代码请见仓库的 `aslist` 目录。 199 | 200 | ``` 201 | /** 202 | * 链表逆序,递归实现。 203 | */ 204 | ListNode *listReverseRecursive(ListNode *head) 205 | { 206 | if (!head || !head->next) { 207 | return head; 208 | } 209 | 210 | ListNode *reversedHead = listReverseRecursive(head->next); 211 | head->next->next = head; 212 | head->next = NULL; 213 | return reversedHead; 214 | } 215 | ``` 216 | 217 | # 参考资料 218 | - 宋劲松老师《Linux C编程》递归章节 219 | - 数据结构与算法-C语言实现 -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列12—背包问题总结.md: -------------------------------------------------------------------------------- 1 | # 数据结构和算法面试题系列—背包问题总结 2 | 3 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 4 | 5 | # 0 概述 6 | 背包问题包括0-1背包问题、完全背包问题、部分背包问题等多种变种。其中,最简单的是部分背包问题,它可以采用贪心法来解决,而其他几种背包问题往往需要动态规划来求解。本文主要来源于《背包问题九讲》,我选择了比较简单的0-1背包问题和完全背包问题进行汇总。同时给出实现代码,如有错误,请各位大虾指正。本文代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/knap)。 7 | 8 | 9 | # 1 部分背包问题 10 | **部分背包问题描述:** 有 N 件物品和一个容量为 C 的背包。第 i 件物品的重量是 w[i],价值是 v[i]。求解将哪些物品装入背包可使价值总和最大。注意这里不要求把物品整个装入,可以只装入一个物品的部分。 11 | 12 | **解法:** 部分背包问题常采用贪心算法来解决,先对每件物品计算其每单位重量价值 `v[i]/w[i]`,然后从具有最大单位价值的物品开始拿,然后拿第二大价值的物品,直到装满背包。按照这种贪心策略拿到的必然是价值总和最大,这个比较简单,实现代码就略去了。 13 | 14 | # 2 0-1背包问题 15 | ### 0-1背包问题描述 16 | 17 | 有 N 件物品和一个容量为 C 的背包。第 i 件物品的重量是 w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。注意物品只能要么拿要么不拿,这也正是 0-1 的意义所在。可以把部分背包问题看作是拿金粉,而 0-1 背包问题则是拿金块,一个可分,一个不可分。 18 | 19 | ### 分析 20 | 21 | 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 `f[i][w]` 表示前 i 件物品恰放入一个容量为 c 的背包可以获得的最大价值。则其状态转移方程便是:  22 | 23 | ``` 24 | f[i][c] = max{f[i-1][c], f[i-1][c-w[i]]+v[i]} 25 | ``` 26 | 这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:`将前 i 件物品放入容量为 c 的背包中` 这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前 i-1 件物品的问题。 27 | 28 | - 如果不放第 i 件物品,那么问题就转化为 `前 i-1 件物品放入容量为 v 的背包中`,价值为 `f[i-1][c]`; 29 | - 如果放第i件物品,那么问题就转化为 `前 i-1 件物品放入剩下的容量为 c-w[i] 的背包中`,此时能获得的最大价值就是 `f[i-1][c-w[i]]`再加上通过放入第 i 件物品获得的价值 v[i]。 30 | 31 | ### 优化空间复杂度 32 | 33 | 以上方法的时间和空间复杂度均为 `O(CN)`,其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到 `O(N)`。 由于在计算 `f[i][c]` 的时候,我们只需要用到 `f[i-1][c]` 和 `f[i-1][c-w[i]]`,所以完全可以通过一维数组保存它们的值,这里用到的小技巧就是需要从 `c=C...0` 开始反推,这样就能保证在求 `f[c]` 的时候 `f[c-w[i]]` 保存的是 `f[i-1][c-w[i]]` 的值。注意,这里不能从 `c=0...C` 这样顺推,因为这样会导致 `f[c-w[i]]` 的值是 `f[i][c-w[i]]` 而不是 `f[i-1][c-w[i]`。这里可以优化下界,其实只需要从 `c=C...w[i]` 即可,可以避免不需要的计算。伪代码如下所示: 34 | 35 | ``` 36 | for i=0..N-1 37 | for c=C..w[i] 38 | f[c]=max{f[c],f[c-w[i]]+v[i]}; 39 | ``` 40 | 41 | 最终实现代码如下: 42 | 43 | ``` 44 | int knap01(int N, int C, int w[], int v[]) 45 | { 46 | int *f = (int *)calloc(sizeof(int), C+1); 47 | int i, c; 48 | 49 | for (i = 0; i < N; i++) { 50 | for (c = C; c >= w[i]; c--) { 51 | f[c] = max(f[c], f[c-w[i]] + v[i]); 52 | } 53 | printf("%d: ", i+1); 54 | printIntArray(f, C+1); 55 | 56 | } 57 | return f[C]; 58 | } 59 | ``` 60 | 61 | 测试结果如下,即在背包容量为 10 的时候装第1和第2个物品(索引从0开始),总重量为 4+5=9,最大价值为 5+6=11。 62 | 63 | ``` 64 | 参数: 65 | w = [3, 4, 5] //物品重量列表 66 | v = [4, 5, 6] //物品价值列表 67 | C = 10 68 | 69 | 结果(打印数组f,i为选择的物品索引,c为背包重量,值为背包物品价值): 70 | 71 | i/c 0 1 2 3 4 5 6 7 8 9 10 72 | 0: 0 0 0 4 4 4 4 4 4 4 4 73 | 1: 0 0 0 4 5 5 5 9 9 9 9 74 | 2: 0 0 0 4 5 6 6 9 10 11 11 75 | 76 | KNap01 max: 11 77 | ``` 78 | ### 初始化的细节问题 79 | 我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。 80 | 81 | 如果是第一种问法,要求恰好装满背包,那么在初始化时除了 f[0] 为 0 其它 `f[1..C]` 均设为 `-∞`,这样就可以保证最终得到的 f[N] 是一种恰好装满背包的最优解。如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 `f[0..C]` 全部设为0。 82 | 83 | 为什么呢?可以这样理解:初始化的 f 数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为 0 的背包可能被价值为 0 的东西 “恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是 -∞ 了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。 84 | 85 | # 3 完全背包问题 86 | ### 问题描述 87 | 有 N 种物品和一个容量为 C 的背包,每种物品都有无限件可用。第i种物品的重量是 w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大,物品不能只装部分。 88 | 89 | ### 基本思路 90 | 这个问题非常类似于0-1背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件...等很多种。如果仍然按照解01背包时的思路,令 f[i][c] 表示前 i 种物品恰放入一个容量为 c 的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:  91 | 92 | ``` 93 | f[i][c] = max{f[i-1][c-k*w[i]]+ k*w[i]| 0<=k*w[i]<=c } 94 | ``` 95 | 96 | 这跟0-1背包问题一样有O(CN)个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态 `f[i][c]` 的时间是 `O(c/w[i])`,总的复杂度可以认为是 `O(CN*Σ(c/w[i]))`,是比较大的。实现代码如下: 97 | 98 | ``` 99 | /* 100 | * 完全背包问题 101 | */ 102 | int knapComplete(int N, int C, int w[], int v[]) 103 | { 104 | int *f = (int *)calloc(sizeof(int), C+1); 105 | int i, c, k; 106 | for (i = 0; i < N; i++) { 107 | for (c = C; c >= 0; c--) { 108 | for (k = 0; k <= c/w[i]; k++) { 109 | f[c] = max(f[c], f[c-k*w[i]] + k*v[i]); 110 | } 111 | } 112 | printf("%d: ", i+1); 113 | printIntArray(f, C+1); 114 | } 115 | return f[C]; 116 | } 117 | ``` 118 | 119 | 使用与0-1背包问题相同的例子,运行程序结果如下,最大价值为 13,即选取 2个重量3,1个重量4的物品,总价值最高,为 `4*2 + 5 = 13`。 120 | 121 | ``` 122 | i/c: 0 1 2 3 4 5 6 7 8 9 10 123 | 0: 0 0 0 4 4 4 8 8 8 12 12 124 | 1: 0 0 0 4 5 5 8 9 10 12 13 125 | 2: 0 0 0 4 5 6 8 9 10 12 13 126 | 127 | KNapComplete max: 13 128 | ``` 129 | 130 | ### 转换为0-1背包问题 131 | 既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选 `C/w[i]` 件,于是可以把第 i 种物品转化为 `C/w[i]` 件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。 132 | 133 | 更高效的转化方法是:把第 i 种物品拆成重量为 `w[i]*2^k`、价值为 `w[i]*2^k` 的若干件物品,其中 k 满足 `w[i]*2^k<=C`。这是二进制的思想,因为不管最优策略选几件第 i 种物品,总可以表示成若干个 `2^k` 件物品的和。这样把每种物品拆成 `O(log C/w[i])` 件物品,是一个很大的改进。但我们有更优的 `O(CN)` 的算法。 134 | 135 | ### 进一步优化—O(CN)解法 136 | 我们可以采用与0-1背包问题相反的顺序遍历,从而可以得到 O(CN) 的解法,伪代码如下: 137 | 138 | ``` 139 | for i=0..N-1 140 | for c=w[i]..C 141 | f[c]=max{f[c],f[c-w[i]]+v[i]}; 142 | ``` 143 | 144 | 这个伪代码与0-1背包伪代码只是 C 的循环次序不同而已。0-1背包之所以要按照 `v=V..0`的逆序来循环。这是因为要保证第i次循环中的状态 `f[i][c]` 是由状态 `f[i-1][c-w[i]]` 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果 `f[i-1][c-w[i]]`。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果 `f[i][c-w[i]]`,所以就可以并且必须采用 `c=w[i]..C` 的顺序循环。这就是这个简单的程序为何成立的道理。实现代码如下: 145 | 146 | ``` 147 | /** 148 | * 完全背包问题-仿01背包解法 149 | */ 150 | int knapCompleteLike01(int N, int C, int w[], int v[]) 151 | { 152 | int *f = (int *)calloc(sizeof(int), C+1); 153 | int i, c; 154 | for (i = 0; i < N; i++) { 155 | for (c = w[i]; c <= C; c++) { 156 | f[c] = max(f[c], f[c-w[i]] + v[i]); 157 | } 158 | printf("%d: ", i+1); 159 | printIntArray(f, C+1); 160 | 161 | } 162 | return f[C]; 163 | } 164 | ``` 165 | 166 | # 参考资料 167 | - [背包问题九讲](https://github.com/tianyicui/pack) 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列3—栈.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | 3 | # 0 概述 4 | 栈作为一种基本的数据结构,在很多地方有运用,比如函数递归,前后缀表达式转换等。本文会用C数组来实现栈结构(使用链表实现可以参见链表那一节,使用头插法构建链表即可),并对常见的几个跟栈相关的面试题进行分析,本文代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/ds/stack)。 5 | 6 | # 1 定义 7 | 我们使用结构体来定义栈,使用柔性数组来存储元素。几个宏定义用于计算栈的元素数目及栈是否为空和满。 8 | 9 | ``` 10 | typedef struct Stack { 11 | int capacity; 12 | int top; 13 | int items[]; 14 | } Stack; 15 | 16 | #define SIZE(stack) (stack->top + 1) 17 | #define IS_EMPTY(stack) (stack->top == -1) 18 | #define IS_FULL(stack) (stack->top == stack->capacity - 1) 19 | ``` 20 | 21 | # 2 基本操作 22 | 栈主要有三种基本操作: 23 | 24 | - push:压入一个元素到栈中。 25 | - pop:弹出栈顶元素并返回。 26 | - peek:取栈顶元素,但是不修改栈。 27 | 28 | 如图所示: 29 | 30 | ![栈示意图](https://user-gold-cdn.xitu.io/2018/9/15/165db3c60596e201?w=773&h=540&f=png&s=9049) 31 | 32 | 代码如下: 33 | 34 | ``` 35 | Stack *stackNew(int capacity) 36 | { 37 | Stack *stack = (Stack *)malloc(sizeof(*stack) + sizeof(int) * capacity); 38 | if (!stack) { 39 | printf("Stack new failed\n"); 40 | exit(E_NOMEM); 41 | } 42 | 43 | stack->capacity = capacity; 44 | stack->top = -1; 45 | return stack; 46 | } 47 | 48 | void push(Stack *stack, int v) 49 | { 50 | if (IS_FULL(stack)) { 51 | printf("Stack Overflow\n"); 52 | exit(E_FULL); 53 | } 54 | stack->items[++stack->top] = v; 55 | } 56 | 57 | int pop(Stack *stack) 58 | { 59 | if (IS_EMPTY(stack)) { 60 | printf("Stack Empty\n"); 61 | exit(E_EMPTY); 62 | } 63 | 64 | return stack->items[stack->top--]; 65 | } 66 | 67 | int peek(Stack *stack) 68 | { 69 | if (IS_EMPTY(stack)) { 70 | printf("Stack Empty\n"); 71 | exit(E_EMPTY); 72 | } 73 | return stack->items[stack->top]; 74 | } 75 | 76 | ``` 77 | 78 | 79 | 80 | # 3 栈相关面试题 81 | ## 3.1 后缀表达式求值 82 | **题:** 已知一个后缀表达式 `6 5 2 3 + 8 * + 3 + *`,求该后缀表达式的值。 83 | 84 | **解:** 后缀表达式也叫逆波兰表达式,其求值过程可以用到栈来辅助存储。则其求值过程如下: 85 | 86 | - 1)遍历表达式,遇到的数字首先放入栈中,此时栈为 `[6 5 2 3]` 。 87 | - 2)接着读到 `+`,则弹出3和2,计算 `3 + 2`,计算结果等于 `5`,并将 `5` 压入到栈中,栈为 `[6 5 5]` 。 88 | - 3)读到 `8` ,将其直接放入栈中,`[6 5 5 8]` 。 89 | - 4)读到 `*`,弹出 `8` 和 `5` ,计算 `8 * 5`,并将结果 `40` 压入栈中,栈为 `[6 5 40]`。而后过程类似,读到 `+`,将 `40` 和 `5` 弹出,将 `40 + 5` 的结果 `45` 压入栈,栈变成`[6 45]`,读到3,放入栈 `[6 45 3]`...以此类推,最后结果为 `288`。 90 | 91 | **代码:** 92 | 93 | ``` 94 | int evaluatePostfix(char *exp) 95 | { 96 | Stack* stack = stackNew(strlen(exp)); 97 | int i; 98 | 99 | if (!stack) { 100 | printf("New stack failed\n"); 101 | exit(E_NOMEM); 102 | } 103 | 104 | for (i = 0; exp[i]; ++i) { 105 | // 如果是数字,直接压栈 106 | if (isdigit(exp[i])) { 107 | push(stack, exp[i] - '0'); 108 | } else {// 如果遇到符号,则弹出栈顶两个元素计算,并将结果压栈 109 | int val1 = pop(stack); 110 | int val2 = pop(stack); 111 | switch (exp[i]) 112 | { 113 | case '+': push(stack, val2 + val1); break; 114 | case '-': push(stack, val2 - val1); break; 115 | case '*': push(stack, val2 * val1); break; 116 | case '/': push(stack, val2/val1); break; 117 | } 118 | } 119 | } 120 | 121 | return pop(stack); 122 | } 123 | ``` 124 | 125 | ## 3.2 栈逆序 126 | **题:** 给定一个栈,请将其逆序。 127 | 128 | **解1:** 如果不考虑空间复杂度,完全可以另外弄个辅助栈,将原栈数据全部 `pop` 出来并 `push` 到辅助栈即可。 129 | 130 | **解2:** 如果在面试中遇到这个题目,那肯定是希望你用更好的方式实现。可以先实现一个在栈底插入元素的函数,然后便可以递归实现栈逆序了,不需要用辅助栈。 131 | 132 | ``` 133 | * 在栈底插入一个元素 134 | */ 135 | void insertAtBottom(Stack *stack, int v) 136 | { 137 | if (IS_EMPTY(stack)) { 138 | push(stack, v); 139 | } else { 140 | int x = pop(stack); 141 | insertAtBottom(stack, v); 142 | push(stack, x); 143 | } 144 | } 145 | 146 | /** 147 | * 栈逆序 148 | */ 149 | void stackReverse(Stack *stack) 150 | { 151 | if (IS_EMPTY(stack)) 152 | return; 153 | 154 | int top = pop(stack); 155 | stackReverse(stack); 156 | insertAtBottom(stack, top); 157 | } 158 | ``` 159 | 160 | ## 3.3 设计包含min函数的栈 161 | **题:** 设计一个栈,使得push、pop以及min(获取栈中最小元素)能够在常数时间内完成。 162 | 163 | **分析:** 刚开始很容易想到一个方法,那就是额外建立一个最小二叉堆保存所有元素,这样每次获取最小元素只需要 `O(1)` 的时间。但是这样的话,为了建最小堆 `push` 和 `pop` 操作就需要 `O(lgn)` 的时间了(假定栈中元素个数为n),不符合题目的要求。 164 | 165 | **解1:辅助栈方法** 166 | 167 | 那为了实现该功能,可以使用辅助栈使用一个辅助栈来保存最小元素,这个解法简单不失优雅。设该辅助栈名字为 `minStack`,其栈顶元素为当前栈中的最小元素。这意味着 168 | 169 | - 1)要获取当前栈中最小元素,只需要返回 minStack 的栈顶元素即可。 170 | - 2)每次执行 push 操作时,检查 push 的元素是否小于或等于 minStack 栈顶元素。如果是,则也push 该元素到 minStack 中。 171 | - 3)当执行 pop 操作的时候,检查 pop 的元素是否与当前最小值相等。如果相等,则需要将该元素从minStack 中 pop 出去。 172 | 173 | **代码:** 174 | 175 | ``` 176 | void minStackPush(Stack *orgStack, Stack *minStack, int v) 177 | { 178 | if (IS_FULL(orgStack)) { 179 | printf("Stack Full\n"); 180 | exit(E_FULL); 181 | } 182 | 183 | push(orgStack, v); 184 | if (IS_EMPTY(minStack) || v < peek(minStack)) { 185 | push(minStack, v); 186 | } 187 | } 188 | 189 | int minStackPop(Stack *orgStack, Stack *minStack) 190 | { 191 | if (IS_EMPTY(orgStack)) { 192 | printf("Stack Empty\n"); 193 | exit(E_EMPTY); 194 | } 195 | 196 | if (peek(orgStack) == peek(minStack)) { 197 | pop(minStack); 198 | } 199 | return pop(orgStack); 200 | } 201 | 202 | int minStackMin(Stack *minStack) 203 | { 204 | return peek(minStack); 205 | } 206 | ``` 207 | 208 | **示例:** 209 | 210 | 假定有元素 `3,4,2,5,1` 依次入栈 `orgStack`,辅助栈 `minStack` 中元素为 `3,2,1` 。 211 | 212 | **解2:差值法** 213 | 214 | 另外一种解法利用存储差值而不需要辅助栈,方法比较巧妙: 215 | 216 | - 栈顶多出一个空间用于存储栈最小值。 217 | - `push` 时压入的是当前元素与压入该元素前的栈中最小元素(栈顶的元素)的差值,然后通过比较当前元素与当前栈中最小元素大小,并将它们中的较小值作为新的最小值压入栈顶。 218 | - `pop` 函数执行的时候,先 `pop` 出栈顶的两个值,这两个值分别是当前栈中最小值 `min` 和最后压入的元素与之前栈中最小值的差值 `delta`。根据 `delta < 0` 或者 `delta >= 0` 来获得之前压入栈的元素的值和该元素出栈后的新的最小值。 219 | - `min` 函数则是取栈顶元素即可。 220 | 221 | **代码:** 222 | 223 | ``` 224 | void minStackPushUseDelta(Stack *stack, int v) 225 | { 226 | if (IS_EMPTY(stack)) { // 空栈,直接压入v两次 227 | push(stack, v); 228 | push(stack, v); 229 | } else { 230 | int oldMin = pop(stack); // 栈顶保存的是压入v之前的栈中最小值 231 | int delta = v - oldMin; 232 | int newMin = delta < 0 ? v : oldMin; 233 | push(stack, delta); // 压入 v 与之前栈中的最小值之差 234 | push(stack, newMin); // 最后压入当前栈中最小值 235 | } 236 | } 237 | 238 | int minStackPopUseDelta(Stack *stack) 239 | { 240 | int min = pop(stack); 241 | int delta = pop(stack); 242 | int v, oldMin; 243 | 244 | if (delta < 0) { // 最后压入的元素比min小,则min就是最后压入的元素 245 | v = min; 246 | oldMin = v - delta; 247 | } else { // 最后压入的值不是最小值,则min为oldMin。 248 | oldMin = min; 249 | v = oldMin + delta; 250 | } 251 | 252 | if (!IS_EMPTY(stack)) { // 如果栈不为空,则压入oldMin 253 | push(stack, oldMin); 254 | } 255 | return v; 256 | } 257 | 258 | int minStackMinUseDelta(Stack *stack) 259 | { 260 | return peek(stack); 261 | } 262 | ``` 263 | 264 | **示例:** 265 | 266 | ``` 267 | push(3): [3 3] 268 | push(4): [3 1 3] 269 | push(2): [3 1 -1 2] 270 | push(5): [3 1 -1 3 2] 271 | push(1): [3 1 -1 3 -1 1] 272 | 273 | min(): 1,pop(): 1,[3 1 -1 3 2] 274 | min(): 2,pop(): 5,[3 1 -1 2] 275 | min(): 2,pop(): 2,[3 1 3] 276 | min(): 3,pop(): 4,[3 3] 277 | min(): 3,pop(): 3,[ ] 278 | ``` 279 | 280 | ## 3.4 求出栈数目和出栈序列 281 | ### 求出栈数目 282 | **题:** 已知一个入栈序列,试求出所有可能的出栈序列数目。例如入栈序列为 `1,2,3`,则可能的出栈序列有5种:`1 2 3,1 3 2 ,2 1 3,2 3 1,3 2 1`。 283 | 284 | **解:** 要求解出栈序列的数目,还算比较容易的。已经有很多文章分析过这个问题,最终答案就是卡特兰数,也就是说 `n` 个元素的出栈序列的总数目等于 `C(2n, n) - C(2n, n-1) = C(2n, n) / (n+1)` ,如 3 个元素的总的出栈数目就是 `C(6, 3) / 4 = 5`。 285 | 286 | 如果不分析求解的通项公式,是否可以写程序求出出栈的序列数目呢?答案是肯定的,我们根据当前栈状态可以将 `出栈一个元素` 和 `入栈一个元素` 两种情况的总的数目相加即可得到总的出栈数目。 287 | 288 | ``` 289 | /** 290 | * 计算出栈数目 291 | * - in:目前栈中的元素数目 292 | * - out:目前已经出栈的元素数目 293 | * - wait:目前还未进栈的元素数目 294 | */ 295 | int sumOfStackPopSequence(Stack *stack, int in, int out, int wait) 296 | { 297 | if (out == stack->capacity) { // 元素全部出栈了,返回1 298 | return 1; 299 | } 300 | 301 | int sum = 0; 302 | 303 | if (wait > 0) // 进栈一个元素 304 | sum += sumOfStackPopSequence(stack, in + 1, out, wait - 1); 305 | 306 | if (in > 0) // 出栈一个元素 307 | sum += sumOfStackPopSequence(stack, in - 1, out + 1, wait); 308 | 309 | return sum; 310 | } 311 | ``` 312 | 313 | ### 求所有出栈序列 314 | **题:** 给定一个输入序列 `input[] = {1, 2, 3}`,打印所有可能的出栈序列。 315 | 316 | **解:** 这个有点难,不只是出栈数目,需要打印所有出栈序列,需要用到回溯法,回溯法比简单的递归要难不少,后面有时间再单独整理一篇回溯法的文章。出栈序列跟入栈出栈的顺序有关,对于每个输入,都会面对两种情况: **是先将原栈中元素出栈还是先入栈** ,这里用到两个栈来实现,其中栈 stk 用于模拟入栈出栈,而栈 output 用于存储出栈的值。**注意退出条件是当遍历完所有输入的元素,此时栈 stk 和 output 中都可能有元素,需要先将栈 output 从栈底开始打印完,然后将栈 stk 从栈顶开始打印即可。** 另外一点就是,当我们使用的模拟栈 stk 为空时,则这个分支结束。代码如下: 317 | 318 | ``` 319 | void printStackPopSequence(int input[], int i, int n, Stack *stk, Stack *output) 320 | { 321 | if (i >= n) { 322 | stackTraverseBottom(output); // output 从栈底开始打印 323 | stackTraverseTop(stk); // stk 从栈顶开始打印 324 | printf("\n"); 325 | return; 326 | } 327 | 328 | push(stk, input[i]); 329 | printStackPopSequence(input, i+1, n, stk, output); 330 | pop(stk); 331 | 332 | if (IS_EMPTY(stk)) 333 | return; 334 | 335 | int v = pop(stk); 336 | push(output, v); 337 | printStackPopSequence(input, i, n, stk, output); 338 | push(stk, v); 339 | pop(output); 340 | } 341 | ``` 342 | 343 | # 参考资料 344 | - http://www.techiedelight.com/stack-implementation/ 345 | - https://www.geeksforgeeks.org/stack-set-4-evaluation-postfix-expression/ 346 | - https://www.geeksforgeeks.org/reverse-a-stack-using-recursion/ 347 | - https://blog.csdn.net/yysdsyl/article/details/4283806 -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列4—二叉堆.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | > 3 | 4 | # 0 概述 5 | 本文要描述的堆是二叉堆。二叉堆是一种数组对象,可以被视为一棵完全二叉树,树中每个结点和数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层除外。二叉堆可以用于实现堆排序,优先级队列等。本文代码地址在 [这里](https://github.com/shishujuan/dsalg/tree/master/ds/heap)。 6 | 7 | # 1 二叉堆定义 8 | 使用数组来实现二叉堆,二叉堆两个属性,其中 `LENGTH(A)` 表示数组 `A` 的长度,而 `HEAP_SIZE(A)` 则表示存放在A中的堆的元素个数,其中 `LENGTH(A) <= HEAP_SIZE(A)`,也就是说虽然 `A[0,1,...N-1]` 都可以包含有效值,但是 `A[HEAP_SIZE(A)-1]` 之后的元素不属于相应的堆。 9 | 10 | 二叉堆对应的树的根为 `A[0]`,给定某个结点的下标 i ,可以很容易计算它的父亲结点和儿子结点。**注意在后面的示例图中我们标注元素是从1开始计数的,而实现代码中是从0开始计数。** 11 | 12 | ``` 13 | #define PARENT(i) ( i > 0 ? (i-1)/2 : 0) 14 | #define LEFT(i) (2 * i + 1) 15 | #define RIGHT(i) (2 * i + 2) 16 | ``` 17 | 18 | 注:堆对应的树每一层都是满的,所以一个高度为 `h` 的堆中,元素数目最多为 `1+2+2^2+...2^h = 2^(h+1) - 1`(满二叉树),元素数目最少为 `1+2+...+2^(h-1) + 1 = 2^h`。 19 | 由于元素数目 `2^h <= n <= 2^(h+1) -1`,所以 `h <= lgn < h+1`,因此 `h = lgn` 。即一个包含n个元素的二叉堆高度为 `lgn` 。 20 | 21 | # 2 保持堆的性质 22 | 23 | 本文主要建立一个最大堆,最小堆原理类似。为了保持堆的性质,`maxHeapify(int A[], int i)` 函数让堆数组 `A` 在最大堆中下降,使得以 `i` 为根的子树成为最大堆。 24 | 25 | ``` 26 | void maxHeapify(int A[], int i, int heapSize) 27 | { 28 | int l = LEFT(i); 29 | int r = RIGHT(i); 30 | 31 | int largest = i; 32 | 33 | if (l <= heapSize-1 && A[l] > A[i]) { 34 | largest = l; 35 | } 36 | 37 | if (r <= heapSize-1 && A[r] > A[largest]) { 38 | largest = r; 39 | } 40 | 41 | if (largest != i) { // 最大值不是i,则需要交换i和largest的元素,并递归调用maxHeapify。 42 | swapInt(A, i, largest); 43 | maxHeapify(A, largest, heapSize); 44 | } 45 | } 46 | ``` 47 | 48 | - 在算法每一步里,从元素 `A[i]` 和 `A[left]` 以及 `A[right]` 中选出最大的,将其下标存在 `largest` 中。如果 `A[i]` 最大,则以 `i` 为根的子树已经是最大堆,程序结束。 49 | 50 | - 否则,`i` 的某个子结点有最大元素,将 `A[i]` 与 `A[largest]` 交换,从而使i及其子女满足最大堆性质。此外,下标为 `largest` 的结点在交换后值变为 `A[i]`,以该结点为根的子树又有可能违反最大堆的性质,所以要对该子树递归调用`maxHeapify()`函数。 51 | 52 | 当 `maxHeapify()` 函数作用在一棵以 `i` 为根结点的、大小为 `n` 的子树上时,运行时间为调整`A[i]`、`A[left]`、`A[right]` 的时间 `O(1)`,加上对以 `i` 为某个子结点为根的子树递归调用 `maxHeapify` 的时间。`i` 结点为根的子树大小最多为 `2n/3`(最底层刚好半满的时候),所以可以推得 `T(N) <= T(2N/3) + O(1)`,所以 `T(N)=O(lgN)`。 53 | 54 | 下图是一个运行 `maxHeapify(heap, 2)` 的例子。`A[] = {16, 4, 10, 14, 7, 9, 3, 2, 8, 1}`,堆大小为 `10`。 55 | 56 | 57 | ![保持最大堆性质](https://user-gold-cdn.xitu.io/2018/9/16/165e2b510eed975f?w=613&h=399&f=png&s=34502) 58 | 59 | # 3 建立最大堆 60 | 61 | 我们可以知道,数组 `A[0, 1, ..., N-1]` 中,`A[N/2, ..., N-1]` 的元素都是树的叶结点。如上面图中的 `6-10` 的结点都是叶结点。每个叶子结点可以看作是只含一个元素的最大堆,因此我们只需要对其他的结点调用 `maxHeapify()` 函数即可。 62 | 63 | ``` 64 | void buildMaxHeap(int A[], int n) 65 | { 66 | int i; 67 | for (i = n/2-1; i >= 0; i--) { 68 | maxHeapify(A, i, n); 69 | } 70 | } 71 | ``` 72 | 73 | 之所以这个函数是正确的,我们需要来证明一下,可以使用循环不变式来证明。 74 | 75 | 循环不变式:在for循环开始前,结点 `i+1、i+2...N-1` 都是一个最大堆的根。 76 | 77 | 初始化:for循环开始迭代前,`i = N/2-1`, 结点 `N/2, N/2+1, ..., N-1`都是叶结点,也都是最大堆的根。 78 | 79 | 保持:因为结点 `i` 的子结点标号都比 `i` 大,根据循环不变式的定义,这些子结点都是最大堆的根,所以调用 `maxHeapify()` 后,`i` 成为了最大堆的根,而 `i+1, i+2, ..., N-1`仍然保持最大堆的性质。 80 | 81 | 终止:过程终止时,i=0,因此结点 `0, 1, 2, ..., N-1`都是最大堆的根,特别的,结点0就是一个最大堆的根。 82 | 83 | ![建立最大堆](https://user-gold-cdn.xitu.io/2018/9/16/165e2b5981302440?w=675&h=202&f=png&s=23107) 84 | 85 | 虽然每次调用 `maxHeapify()` 时间为 `O(lgN)`,共有 `O(N)` 次调用,但是说运行时间是 `O(NlgN)` 是不确切的,准确的来说,运行时间为 `O(N)`,这里就不证明了,具体证明过程参见《算法导论》。 86 | 87 | # 4 堆排序 88 | 开始用 `buildMaxHeap()` 函数创建一个最大堆,因为数组最大元素在 `A[0]`,通过直接将它与`A[N-1]` 互换来达到最终正确位置。去掉 `A[N-1]`,堆的大小 `heapSize` 减1,调用`maxHeapify(heap, 0, --heapSize)` 保持最大堆的性质,直到堆的大小由N减到1。 89 | 90 | ``` 91 | void heapSort(int A[], int n) 92 | { 93 | buildMaxHeap(A, n); 94 | int heapSize = n; 95 | int i; 96 | for (i = n-1; i >= 1; i--) { 97 | swapInt(A, 0, i); 98 | maxHeapify(A, 0, --heapSize); 99 | } 100 | } 101 | ``` 102 | 103 | # 5 优先级队列 104 | 最后实现一个最大优先级队列,主要有四种操作,分别如下所示: 105 | 106 | - `insert(PQ, key)`:将 key 插入到队列中。 107 | - `maximum(PQ)`: 返回队列中最大关键字的元素 108 | - `extractMax(PQ)`:去掉并返回队列中最大关键字的元素 109 | - `increaseKey(PQ, i, key)`:将队列 i 处的关键字的值增加到 key 110 | 111 | 这里定义一个结构体 `PriorityQueue` 便于操作。 112 | 113 | ``` 114 | typedef struct PriorityQueue { 115 | int capacity; 116 | int size; 117 | int elems[]; 118 | } PQ; 119 | ``` 120 | 121 | 最终优先级队列的操作实现代码如下: 122 | 123 | ``` 124 | /** 125 | * 从数组创建优先级队列 126 | */ 127 | PQ *newPQ(int A[], int n) 128 | { 129 | PQ *pq = (PQ *)malloc(sizeof(PQ) + sizeof(int) * n); 130 | pq->size = 0; 131 | pq->capacity = n; 132 | 133 | int i; 134 | for (i = 0; i < pq->capacity; i++) { 135 | pq->elems[i] = A[i]; 136 | pq->size++; 137 | } 138 | buildMaxHeap(pq->elems, pq->size); 139 | 140 | return pq; 141 | } 142 | 143 | int maximum(PQ *pq) 144 | { 145 | return pq->elems[0]; 146 | } 147 | 148 | int extractMax(PQ *pq) 149 | { 150 | int max = pq->elems[0]; 151 | pq->elems[0] = pq->elems[--pq->size]; 152 | maxHeapify(pq->elems, 0, pq->size); 153 | return max; 154 | } 155 | 156 | PQ *insert(PQ *pq, int key) 157 | { 158 | int newSize = ++pq->size; 159 | if (newSize > pq->capacity) { 160 | pq->capacity = newSize * 2; 161 | pq = (PQ *)realloc(pq, sizeof(PQ) + sizeof(int) * pq->capacity); 162 | } 163 | pq->elems[newSize-1] = INT_MIN; 164 | increaseKey(pq, newSize-1, key); 165 | return pq; 166 | } 167 | 168 | void increaseKey(PQ *pq, int i, int key) 169 | { 170 | int *elems = pq->elems; 171 | elems[i] = key; 172 | 173 | while (i > 0 && elems[PARENT(i)] < elems[i]) { 174 | swapInt(elems, PARENT(i), i); 175 | i = PARENT(i); 176 | } 177 | } 178 | ``` 179 | 180 | # 参考资料 181 | - 算法导论第6章《堆排序》 -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列5—二叉树基础.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | 3 | # 0 概述 4 | 在说二叉树前,先来看看什么是树。树中基本单位是结点,结点之间的链接,称为分支。一棵树最上面的结点称之为根节点,而下面的结点为子结点。一个结点可以有0个或多个子结点,没有子结点的结点我们称之为叶结点。 5 | 6 | 二叉树是指子结点数目不超过2个的树,它是一种很经典的数据结构。而二叉搜索树(BST)是有序的二叉树,BST需要满足如下条件: 7 | 8 | - 若任意结点的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 9 | - 若任意结点的右子树不空,则右子树上所有节点的值均**大于或等于**它的根节点的值;(有些书里面定义为BST不能有相同值结点,本文将相同值结点插入到右子树) 10 | - 任意结点的左、右子树也分别为二叉查找树; 11 | 12 | 本文接下来会从定义,二叉搜索树的增删查以及二叉树的递归和非递归遍历进行整理。 13 | 下一篇文章会对二叉树相关的经典面试题进行全面解析,本文代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/ds/tree/binary_tree)。 14 | 15 | 16 | # 1 定义 17 | 我们先定义一个二叉树的结点,如下: 18 | 19 | ``` 20 | typedef struct BTNode { 21 | int value; 22 | struct BTNode *left; 23 | struct BTNode *right; 24 | } BTNode; 25 | ``` 26 | 27 | 其中 `value` 存储值,`left` 和 `right` 指针分别指向左右子结点。二叉搜索树跟二叉树可以使用同一个结构,只是在插入或者查找时会有不同。 28 | 29 | # 2 基本操作 30 | 接下来看看二叉树和二叉查找树的一些基本操作,包括BST插入结点,BST查找结点,BST最大值和最小值,二叉树结点数目和高度等。二叉查找树(BST)特有的操作都在函数前加了 `bst` 前缀区分,其他函数则是二叉树通用的。 31 | 32 | ### 1) 创建结点 33 | 分配内存,初始化值即可。 34 | 35 | ``` 36 | 37 | /** 38 | * 创建BTNode 39 | */ 40 | BTNode *newNode(int value) 41 | { 42 | BTNode *node = (BTNode *)malloc(sizeof(BTNode)); 43 | node->value = value; 44 | node->left = node->right = NULL; 45 | return node; 46 | } 47 | ``` 48 | 49 | ### 2) BST 插入结点 50 | 插入结点可以用递归或者非递归实现,如果待插入值比根节点值大,则插入到右子树中,否则插入到左子树中。如下图所示(图来自参考资料1,2,3): 51 | 52 | 53 | ![BST插入结点](https://user-gold-cdn.xitu.io/2018/9/20/165f7985dc8d0b03?w=408&h=365&f=png&s=9964) 54 | 55 | ``` 56 | /** 57 | * BST中插入值,递归方法 58 | */ 59 | /** 60 | * BST中插入结点,递归方法 61 | */ 62 | BTNode *bstInsert(BTNode *root, int value) 63 | { 64 | if (!root) 65 | return newNode(value); 66 | 67 | if (root->value > value) { 68 | root->left = bstInsert(root->left, value); 69 | } else { 70 | root->right = bstInsert(root->right, value); 71 | } 72 | return root; 73 | } 74 | 75 | /** 76 | * BST中插入结点,非递归方法 77 | */ 78 | BTNode *bstInsertIter(BTNode *root, int value) 79 | { 80 | BTNode *node = newNode(value); 81 | 82 | if (!root) 83 | return node; 84 | 85 | BTNode *current = root, *parent = NULL; 86 | 87 | while (current) { 88 | parent = current; 89 | if (current->value > value) 90 | current = current->left; 91 | else 92 | current = current->right; 93 | } 94 | 95 | if (parent->value >= value) 96 | parent->left = node; 97 | else 98 | parent->right = node; 99 | 100 | return root; 101 | } 102 | ``` 103 | ### 3) BST 删除结点 104 | 删除结点稍微复杂一点,要考虑3种情况: 105 | 106 | - 删除的是叶子结点,好办,移除该结点并将该叶子结点的父结点的 `left` 或者 `right` 指针置空即可。 107 | 108 | ![BST删除结点-叶子结点](https://user-gold-cdn.xitu.io/2018/9/20/165f79915b1ef4f0?w=605&h=233&f=png&s=8328) 109 | 110 | - 删除的结点有两个子结点,则需要找到该结点左子树的最大结点(使用后面的`bstSearchIter` 函数),并将其值替换到待删除结点中,然后递归调用删除函数删除该结点左子树最大结点即可。 111 | 112 | ![BST删除结点-有两个子结点](https://user-gold-cdn.xitu.io/2018/9/20/165f799d87e043b7?w=586&h=352&f=png&s=11512) 113 | 114 | - 删除的结点只有一个子结点,则移除该结点并将其子结点的值填充到该删除结点即可(需要判断是左孩子还是右孩子结点)。 115 | 116 | 117 | ![BST删除结点-一个子结点](https://user-gold-cdn.xitu.io/2018/9/20/165f79a8c41db216?w=635&h=328&f=png&s=10583) 118 | 119 | 120 | ``` 121 | /** 122 | * BST中删除结点 123 | */ 124 | BTNode *bstDelete(BTNode *root, int value) 125 | { 126 | BTNode *parent = NULL, *current = root; 127 | BTNode *node = bstSearchIter(root, &parent, value); 128 | if (!node) { 129 | printf("Value not found\n"); 130 | return root; 131 | } 132 | 133 | if (!node->left && !node->right) { 134 | // 情况1:待删除结点是叶子结点 135 | if (node != root) { 136 | if (parent->left == node) { 137 | parent->left = NULL; 138 | } else { 139 | parent->right = NULL; 140 | } 141 | } else { 142 | root = NULL; 143 | } 144 | free(node); 145 | } else if (node->left && node->right) { 146 | // 情况2:待删除结点有两个子结点 147 | BTNode *predecessor = bstMax(node->left); 148 | bstDelete(root, predecessor->value); 149 | node->value = predecessor->value; 150 | } else { 151 | // 情况3:待删除结点只有一个子结点 152 | BTNode *child = (node->left) ? node->left : node->right; 153 | if (node != root) { 154 | if (node == parent->left) 155 | parent->left = child; 156 | else 157 | parent->right = child; 158 | } else { 159 | root = child; 160 | } 161 | free(node); 162 | } 163 | return root; 164 | } 165 | ``` 166 | 167 | ### 4) BST 查找结点 168 | 注意在非递归查找中会将父结点也记录下来。 169 | 170 | ![BST查找结点](https://user-gold-cdn.xitu.io/2018/9/20/165f79bbe647e4e6?w=406&h=365&f=png&s=10211) 171 | 172 | ``` 173 | /** 174 | * BST查找结点-递归 175 | */ 176 | BTNode *bstSearch(BTNode *root, int value) 177 | { 178 | if (!root) return NULL; 179 | 180 | if (root->value == value) { 181 | return root; 182 | } else if (root->value > value) { 183 | return bstSearch(root->left, value); 184 | } else { 185 | return bstSearch(root->left, value); 186 | } 187 | } 188 | 189 | /** 190 | * BST查找结点-非递归 191 | */ 192 | BTNode *bstSearchIter(BTNode *root, BTNode **parent, int value) 193 | { 194 | if (!root) return NULL; 195 | 196 | BTNode *current = root; 197 | 198 | while (current && current->value != value) { 199 | *parent = current; 200 | if (current->value > value) 201 | current = current->left; 202 | else 203 | current = current->right; 204 | } 205 | 206 | return current; 207 | } 208 | ``` 209 | 210 | ### 5)BST 最小值结点和最大值结点 211 | 最小值结点从左子树递归查找,最大值结点从右子树递归找。 212 | 213 | ``` 214 | /** 215 | * BST最小值结点 216 | */ 217 | BTNode *bstMin(BTNode *root) 218 | { 219 | if (!root->left) 220 | return root; 221 | 222 | return bstMin(root->left); 223 | } 224 | 225 | /** 226 | * BST最大值结点 227 | */ 228 | BTNode *bstMax(BTNode *root) 229 | { 230 | if (!root->right) 231 | return root; 232 | 233 | return bstMax(root->right); 234 | } 235 | 236 | ``` 237 | 238 | ### 6)二叉树结点数目和高度 239 | 240 | ``` 241 | /** 242 | * 二叉树结点数目 243 | */ 244 | int btSize(BTNode *root) 245 | { 246 | if (!root) return 0; 247 | 248 | return btSize(root->left) + btSize(root->right) + 1; 249 | } 250 | 251 | /** 252 | * 二叉树高度 253 | */ 254 | int btHeight(BTNode *root) 255 | { 256 | if (!root) return 0; 257 | 258 | int leftHeight = btHeight(root->left); 259 | int rightHeight = btHeight(root->right); 260 | int maxHeight = leftHeight > rightHeight ? leftHeight+1 : rightHeight+1; 261 | return maxHeight; 262 | } 263 | ``` 264 | 265 | # 3 二叉树遍历 266 | ### 递归遍历-先序、中序、后序、层序 267 | 二叉树遍历的递归实现比较简单,直接给出代码。这里值得一提的是层序遍历,先是计算了二叉树的高度,然后调用的辅助函数依次遍历每一层的结点,这种方式比较容易理解,虽然在时间复杂度上会高一些。 268 | 269 | ``` 270 | /** 271 | * 二叉树先序遍历 272 | */ 273 | void preOrder(BTNode *root) 274 | { 275 | if (!root) return; 276 | 277 | printf("%d ", root->value); 278 | preOrder(root->left); 279 | preOrder(root->right); 280 | } 281 | 282 | /** 283 | * 二叉树中序遍历 284 | */ 285 | void inOrder(BTNode *root) 286 | { 287 | if (!root) return; 288 | 289 | inOrder(root->left); 290 | printf("%d ", root->value); 291 | inOrder(root->right); 292 | } 293 | 294 | /** 295 | * 二叉树后序遍历 296 | */ 297 | void postOrder(BTNode *root) 298 | { 299 | if (!root) return; 300 | 301 | postOrder(root->left); 302 | postOrder(root->right); 303 | printf("%d ", root->value); 304 | } 305 | 306 | /** 307 | * 二叉树层序遍历 308 | */ 309 | void levelOrder(BTNode *root) 310 | { 311 | int btHeight = height(root); 312 | int level; 313 | for (level = 1; level <= btHeight; level++) { 314 | levelOrderInLevel(root, level); 315 | } 316 | } 317 | 318 | /** 319 | * 二叉树层序遍历辅助函数-打印第level层的结点 320 | */ 321 | void levelOrderInLevel(BTNode *root, int level) 322 | { 323 | if (!root) return; 324 | 325 | if (level == 1) { 326 | printf("%d ", root->value); 327 | return; 328 | } 329 | levelOrderInLevel(root->left, level-1); 330 | levelOrderInLevel(root->right, level-1); 331 | } 332 | ``` 333 | 334 | ### 非递归遍历-先序、中序、后序、层序 335 | - 非递归遍历里面先序遍历最简单,使用一个栈来保存结点,先访问根结点,然后将右孩子和左孩子依次压栈,然后循环这个过程。中序遍历稍微复杂一点,需要先遍历完左子树,然后才是根结点,最后才是右子树。 336 | - 后序遍历使用一个栈的方法`postOrderIter()`会有点绕,也易错。所以在面试时推荐用两个栈的版本`postOrderIterWith2Stack()`,容易理解,也比较好写。 337 | - 层序遍历用了队列来辅助存储结点,还算简单。 338 | - 这里我另外实现了一个队列 `BTNodeQueue` 和栈 `BTNodeStack`,用于二叉树非递归遍历。 339 | 340 | ``` 341 | 342 | /*********************/ 343 | /** 二叉树遍历-非递归 **/ 344 | /*********************/ 345 | /** 346 | * 先序遍历-非递归 347 | */ 348 | void preOrderIter(BTNode *root) 349 | { 350 | if (!root) return; 351 | 352 | int size = btSize(root); 353 | BTNodeStack *stack = stackNew(size); 354 | 355 | push(stack, root); 356 | while (!IS_EMPTY(stack)) { 357 | BTNode *node = pop(stack); 358 | printf("%d ", node->value); 359 | 360 | if (node->right) 361 | push(stack, node->right); 362 | 363 | if (node->left) 364 | push(stack, node->left); 365 | } 366 | free(stack); 367 | } 368 | 369 | /** 370 | * 中序遍历-非递归 371 | */ 372 | void inOrderIter(BTNode *root) 373 | { 374 | if (!root) return; 375 | 376 | BTNodeStack *stack = stackNew(btSize(root)); 377 | 378 | BTNode *current = root; 379 | while (current || !IS_EMPTY(stack)) { 380 | if (current) { 381 | push(stack, current); 382 | current = current->left; 383 | } else { 384 | BTNode *node = pop(stack); 385 | printf("%d ", node->value); 386 | current = node->right; 387 | } 388 | } 389 | free(stack); 390 | } 391 | 392 | /** 393 | * 后续遍历-使用一个栈非递归 394 | */ 395 | void postOrderIter(BTNode *root) 396 | { 397 | BTNodeStack *stack = stackNew(btSize(root)); 398 | BTNode *current = root; 399 | do { 400 | // 移动至最左边结点 401 | while (current) { 402 | // 将该结点右孩子和自己入栈 403 | if (current->right) 404 | push(stack, current->right); 405 | push(stack, current); 406 | 407 | // 往左子树遍历 408 | current = current->left; 409 | } 410 | 411 | current = pop(stack); 412 | 413 | if (current->right && peek(stack) == current->right) { 414 | pop(stack); 415 | push(stack, current); 416 | current = current->right; 417 | } else { 418 | printf("%d ", current->value); 419 | current = NULL; 420 | } 421 | } while (!IS_EMPTY(stack)); 422 | } 423 | 424 | /** 425 | * 后续遍历-使用两个栈,更好理解一点。 426 | */ 427 | void postOrderIterWith2Stack(BTNode *root) 428 | { 429 | if (!root) return; 430 | 431 | BTNodeStack *stack = stackNew(btSize(root)); 432 | BTNodeStack *output = stackNew(btSize(root)); 433 | 434 | push(stack, root); 435 | BTNode *node; 436 | 437 | while (!IS_EMPTY(stack)) { 438 | node = pop(stack); 439 | push(output, node); 440 | 441 | if (node->left) 442 | push(stack, node->left); 443 | 444 | if (node->right) 445 | push(stack, node->right); 446 | } 447 | 448 | while (!IS_EMPTY(output)) { 449 | node = pop(output); 450 | printf("%d ", node->value); 451 | } 452 | } 453 | 454 | /** 455 | * 层序遍历-非递归 456 | */ 457 | void levelOrderIter(BTNode *root) 458 | { 459 | if (!root) return; 460 | 461 | BTNodeQueue *queue = queueNew(btSize(root)); 462 | enqueue(queue, root); 463 | 464 | while (1) { 465 | int nodeCount = QUEUE_SIZE(queue); 466 | if (nodeCount == 0) 467 | break; 468 | btHeight 469 | while (nodeCount > 0) { 470 | BTNode *node = dequeue(queue); 471 | printf("%d ", node->value); 472 | 473 | if (node->left) 474 | enqueue(queue, node->left); 475 | 476 | if (node->right) 477 | enqueue(queue, node->right); 478 | 479 | nodeCount--; 480 | } 481 | printf("\n"); 482 | } 483 | } 484 | ``` 485 | 486 | # 参考资料 487 | 488 | - http://www.techiedelight.com/insertion-in-bst/ 489 | - http://www.techiedelight.com/search-given-key-in-bst/ 490 | - http://www.techiedelight.com/deletion-from-bst/ 491 | - https://www.geeksforgeeks.org/print-level-order-traversal-line-line/ 492 | - https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/ 493 | - https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/ 494 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列7—二分查找算法详解.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | 3 | # 0 概述 4 | 二分查找本身是个简单的算法,但是正是因为其简单,更容易写错。甚至于在二分查找算法刚出现的时候,也是存在bug的(溢出的bug),这个bug直到几十年后才修复(见《编程珠玑》)。本文打算对二分查找算法进行总结,并对由二分查找引申出来的问题进行分析和汇总。若有错误,请指正。本文完整代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/binary_search) 。 5 | 6 | # 1 二分查找基础 7 | 相信大家都知道二分查找的基本算法,如下所示,这就是二分查找算法代码: 8 | 9 | ``` 10 | /** 11 | * 基本二分查找算法 12 | */ 13 | int binarySearch(int a[], int n, int t) 14 | { 15 | int l = 0, u = n - 1; 16 | while (l <= u) { 17 | int m = l + (u - l) / 2; // 同(l+u)/ 2,这里是为了溢出 18 | if (t > a[m]) 19 | l = m + 1; 20 | else if (t < a[m]) 21 | u = m - 1; 22 | else 23 | return m; 24 | } 25 | return -(l+1); 26 | } 27 | ``` 28 | 29 | 算法的思想就是:从数组中间开始,每次排除一半的数据,时间复杂度为`O(lgN)`。这依赖于数组有序这个性质。如果t存在数组中,则返回t在数组的位置;否则,不存在则返回`-(l+1)`。 30 | 31 | 这里需要解释下为什么t不存在数组中时不是返回`-1`而要返回`-(l+1)`。首先我们可以观察 `l` 的值,如果查找不成功,则 `l` 的值恰好是 t 应该在数组中插入的位置。 32 | 33 | 举个例子,假定有序数组`a={1, 3, 4, 7, 8}`, 那么如果`t=0`,则显然t不在数组中,则二分查找算法最终会使得`l=0 > u=-1 `退出循环;如果t=9,则t也不在数组中,则最后`l=5 > u=4`退出循环。如果`t=5`,则最后`l=3 > u=2`退出循环。因此在一些算法中,比如DHT(一致性哈希)中,就需要这个返回值来使得新加入的节点可以插入到合适的位置中,在求最长递增子序列的NlgN算法中,也用到了这一点,参见博文[最长递增子序列算法](http://blog.csdn.net/ssjhust123/article/details/7798737)。 34 | 35 | 还有一个小点就是之所以返回`-(l+1)`而不是直接返回 `-l` 是因为 `l` 可能为0,如果直接返回 `-l` 就无法判断是正常返回位置0还是查找不成功返回的0。 36 | 37 | # 2 查找有序数组中数字第一次出现位置 38 | 现在考虑一个稍微复杂点的问题,如果有序数组中有重复数字,比如数组`a={1, 2, 3, 3, 5, 7, 8}`,需要在其中找出3第一次出现的位置。这里3第一次出现位置为2。这个问题在《编程珠玑》第九章有很好的分析,这里就直接用了。算法的精髓在于循环不变式的巧妙设计,代码如下: 39 | 40 | ``` 41 | /** 42 | * 二分查找第一次出现位置 43 | */ 44 | int binarySearchFirst(int a[], int n, int t) 45 | { 46 | int l = -1, u = n; 47 | while (l + 1 != u) { 48 | /*循环不变式a[l] a[m]) 51 | l = m; 52 | else 53 | u = m; 54 | } 55 | /*assert: l+1=u && a[l]=n || a[p]!=t) 58 | p = -1; 59 | return p; 60 | } 61 | ``` 62 | 63 | 算法分析:设定两个不存在的元素a[-1]和a[n],使得`a[-1] < t <= a[n]`,但是我们并不会去访问者两个元素,因为`(l+u)/2 > l=-1`, `(l+u)/2 < u=n`。循环不变式为`la[l] && t<=a[u]` 。循环退出时必然有`l+1=u`, 而且`a[l] < t <= a[u]`。循环退出后u的值为t可能出现的位置,其范围为`[0, n]`,如果t在数组中,则第一个出现的位置`p=u`,如果不在,则设置`p=-1`返回。该算法的效率虽然解决了更为复杂的问题,但是其效率比初始版本的二分查找还要高,因为它在每次循环中只需要比较一次,前一程序则通常需要比较两次。 64 | 65 | 举个例子:对于数组`a={1, 2, 3, 3, 5, 7, 8}`,我们如果查找`t=3`,则可以得到`p=u=2`,如果查找`t=4,a[3]=n`, 比如`t=9`,则`u=7`,此时也是设置`p=-1`.特别注意的是,`l=-1,u=n`这两个值不能写成`l=0,u=n-1`。虽然这两个值不会访问到,但是如果改成后面的那样,就会导致二分查找失败,那样就访问不到第一个数字。如在`a={1,2,3,4,5}`中查找1,如果初始设置`l=0,u=n-1`,则会导致查找失败。 66 | 67 | 扩展 68 | 如果要查找数字在数组中最后出现的位置呢?其实这跟上述算法是类似的,稍微改一下上面的算法就可以了,代码如下: 69 | 70 | ``` 71 | /** 72 | * 二分查找最后一次出现位置 73 | */ 74 | int binarySearchLast(int a[], int n, int t) 75 | { 76 | int l = -1, u = n; 77 | while (l + 1 != u) { 78 | /*循环不变式, a[l] <= t < a[u]*/ 79 | int m = l + (u - l) / 2; 80 | if (t >= a[m]) 81 | l = m; 82 | else 83 | u = m; 84 | } 85 | /*assert: l+1 = u && a[l] <= t < a[u]*/ 86 | int p = l; 87 | if (p<=-1 || a[p]!=t) 88 | p = -1; 89 | return p; 90 | } 91 | ``` 92 | 93 | 当然还有一种方法可以将查询数字第一次出现和最后一次出现的代码写在一个程序中,只需要对原始的二分查找稍微修改即可,代码如下: 94 | 95 | ``` 96 | /** 97 | * 二分查找第一次和最后一次出现位置 98 | */ 99 | int binarySearchFirstAndLast(int a[], int n, int t, int firstFlag) 100 | { 101 | int l = 0; 102 | int u = n - 1; 103 | while(l <= u) { 104 | int m = l + (u - l) / 2; 105 | if(a[m] == t) { //找到了,判断是第一次出现还是最后一次出现 106 | if(firstFlag) { //查询第一次出现的位置 107 | if(m != 0 && a[m-1] != t) 108 | return m; 109 | else if(m == 0) 110 | return 0; 111 | else 112 | u = m - 1; 113 | } else { //查询最后一次出现的位置 114 | if(m != n-1 && a[m+1] != t) 115 | return m; 116 | else if(m == n-1) 117 | return n-1; 118 | else 119 | l = m + 1; 120 | } 121 | } 122 | else if(a[m] < t) 123 | l = m + 1; 124 | else 125 | u = m - 1; 126 | } 127 | 128 | return -1; 129 | } 130 | ``` 131 | 132 | # 3 旋转数组元素查找问题 133 | 134 | ### 题目 135 | 把一个有序数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转。现在给出旋转后的数组和一个数,旋转了多少位不知道,要求给出一个算法,算出给出的数在该数组中的下标,如果没有找到这个数,则返回-1。要求查找次数不能超过n。 136 | 137 | ### 分析 138 | 由题目可以知道,旋转后的数组虽然整体无序了,但是其前后两部分是部分有序的。由此还是可以使用二分查找来解决该问题的。 139 | 140 | ### 解1:两次二分查找 141 | 142 | 首先确定数组分割点,也就是说分割点两边的数组都有序。比如例子中的数组以位置2分割,前面部分{3,4,5}有序,后半部分{1,2}有序。然后对这两部分分别使用二分查找即可。代码如下: 143 | 144 | ``` 145 | /** 146 | * 旋转数组查找-两次二分查找 147 | */ 148 | int binarySearchRotateTwice(int a[], int n, int t) 149 | { 150 | int p = findRotatePosition(a, n); //找到旋转位置 151 | if (p == -1) 152 | return binarySearchFirst(a, n, t); //如果原数组有序,则直接二分查找即可 153 | 154 | int left = binarySearchFirst(a, p+1, t); //查找左半部分 155 | if (left != -1) 156 | return left; //左半部分找到,则直接返回 157 | 158 | int right = binarySearchFirst(a+p+1, n-p-1, t); //左半部分没有找到,则查找右半部分 159 | if (right == -1) 160 | return -1; 161 | 162 | return right+p+1; //返回位置,注意要加上p+1 163 | } 164 | 165 | /** 166 | * 查找旋转位置 167 | */ 168 | int findRotatePosition(int a[], int n) 169 | { 170 | int i; 171 | for (i = 0; i < n-1; i++) { 172 | if (a[i+1] < a[i]) 173 | return i; 174 | } 175 | return -1; 176 | } 177 | ``` 178 | 179 | ### 解2:一次二分查找 180 | 二分查找算法有两个关键点:1)数组有序;2)根据当前区间的中间元素与t的大小关系,确定下次二分查找在前半段区间还是后半段区间进行。 181 | 182 | 仔细分析该问题,可以发现,每次根据 `l` 和 `u` 求出 `m` 后,`m` 左边(`[l, m]`)和右边(`[m, u]`)至少一个是有序的。a[m]分别与a[l]和a[u]比较,确定哪一段是有序的。 183 | - 如果左边是有序的,若 `ta[l]`, 则 `u=m-1`;其他情况,`l =m+1`; 184 | - 如果右边是有序的,若 `t> a[m] && t= a[l]) { //数组左半有序 199 | if (t >= a[l] && t < a[m]) 200 | u = m - 1; 201 | else 202 | l = m + 1; 203 | } else { //数组右半段有序 204 | if (t > a[m] && t <= a[u]) 205 | l = m + 1; 206 | else 207 | u = m - 1; 208 | } 209 | } 210 | return -1; 211 | } 212 | ``` 213 | # 参考资料 214 | - 编程珠玑第二版第九章 215 | - [旋转数组的二分查找](http://www.jobcoding.com/datastructure-and-algorithm/array/one-sorted-array/rotate_array/) 216 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列8—排序算法之基础排序.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | 3 | # 0 概述 4 | 排序算法也是面试中常常提及的内容,问的最多的应该是快速排序、堆排序。这些排序算法很基础,但是如果平时不怎么写代码的话,面试的时候总会出现各种bug。虽然思想都知道,但是就是写不出来。本文打算对各种排序算法进行一个汇总,包括插入排序、冒泡排序、选择排序、计数排序、归并排序,基数排序、桶排序、快速排序等。快速排序比较重要,会单独写一篇,而堆排序见本系列的二叉堆那篇文章即可。 5 | 6 | 需要提到的一点就是:插入排序,冒泡排序,归并排序,计数排序都是稳定的排序,而其他排序则是不稳定的。本文完整代码在 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/sort)。 7 | 8 | 9 | # 1 插入排序 10 | 插入排序是很基本的排序,特别是在数据基本有序的情况下,插入排序的性能很高,最好情况可以达到`O(N)`,其最坏情况和平均情况时间复杂度都是 `O(N^2)`。代码如下: 11 | 12 | ``` 13 | /** 14 | * 插入排序 15 | */ 16 | void insertSort(int a[], int n) 17 | { 18 | int i, j; 19 | for (i = 1; i < n; i++) { 20 | /* 21 | * 循环不变式:a[0...i-1]有序。每次迭代开始前,a[0...i-1]有序, 22 | * 循环结束后i=n,a[0...n-1]有序 23 | * */ 24 | int key = a[i]; 25 | for (j = i; j > 0 && a[j-1] > key; j--) { 26 | a[j] = a[j-1]; 27 | } 28 | a[j] = key; 29 | } 30 | } 31 | ``` 32 | 33 | # 2 希尔排序 34 | 希尔排序内部调用插入排序来实现,通过对 `N/2,N/4...1`阶分别排序,最后得到整体的有序。 35 | 36 | ``` 37 | /** 38 | * 希尔排序 39 | */ 40 | void shellSort(int a[], int n) 41 | { 42 | int gap; 43 | for (gap = n/2; gap > 0; gap /= 2) { 44 | int i; 45 | for (i = gap; i < n; i++) { 46 | int key = a[i], j; 47 | for (j = i; j >= gap && key < a[j-gap]; j -= gap) { 48 | a[j] = a[j-gap]; 49 | } 50 | a[j] = key; 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | # 3 选择排序 57 | 选择排序的思想就是第i次选取第i小的元素放在位置i。比如第1次就选择最小的元素放在位置0,第2次选择第二小的元素放在位置1。选择排序最好和最坏时间复杂度都为 `O(N^2)`。代码如下: 58 | 59 | ``` 60 | /** 61 | * 选择排序 62 | */ 63 | void selectSort(int a[], int n) 64 | { 65 | int i, j, min, tmp; 66 | for (i = 0; i < n-1; i++) { 67 | min = i; 68 | for (j = i+1; j < n; j++) { 69 | if (a[j] < a[min]) 70 | min = j; 71 | } 72 | if (min != i) 73 | tmp = a[i], a[i] = a[min], a[min] = tmp; //交换a[i]和a[min] 74 | } 75 | } 76 | ``` 77 | 78 | **循环不变式:在外层循环执行前,`a[0...i-1]`包含 `a` 中最小的 `i` 个数,且有序。** 79 | 80 | - 初始时,`i=0`,`a[0...-1]` 为空,显然成立。 81 | 82 | - 每次执行完成后,`a[0...i]` 包含 `a` 中最小的 `i+1` 个数,且有序。即第一次执行完成后,`a[0...0]` 包含 `a` 最小的 `1` 个数,且有序。 83 | 84 | - 循环结束后,`i=n-1`,则 `a[0...n-2]`包含 `a` 最小的 `n-1` 个数,且已经有序。所以整个数组有序。 85 | 86 | 87 | # 4 冒泡排序 88 | 冒泡排序时间复杂度跟选择排序相同。其思想就是进行 `n-1` 趟排序,每次都是把最小的数上浮,像鱼冒泡一样。最坏情况为 `O(N^2)`。代码如下: 89 | 90 | ``` 91 | /** 92 | * 冒泡排序-经典版 93 | */ 94 | void bubbleSort(int a[], int n) 95 | { 96 | int i, j, tmp; 97 | for (i = 0; i < n; i++) { 98 | for (j = n-1; j >= i+1; j--) { 99 | if (a[j] < a[j-1]) 100 | tmp = a[j], a[j] = a[j-1], a[j-1] = tmp; 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | **循环不变式:在循环开始迭代前,子数组 `a[0...i-1]` 包含了数组 `a[0..n-1]` 的 `i-1` 个最小值,且是排好序的。** 107 | 108 | 对冒泡排序的一个改进就是在每趟排序时判断是否发生交换,如果一次交换都没有发生,则数组已经有序,可以不用继续剩下的趟数直接退出。改进后代码如下: 109 | 110 | ``` 111 | /** 112 | * 冒泡排序-优化版 113 | */ 114 | void betterBubbleSort(int a[], int n) 115 | { 116 | int tmp, i, j; 117 | for (i = 0; i < n; i++) { 118 | int sorted = 1; 119 | for (j = n-1; j >= i+1; j--) { 120 | if (a[j] < a[j-1]) { 121 | tmp = a[j], a[j] = a[j-1], a[j-1] = tmp; 122 | sorted = 0; 123 | } 124 | } 125 | if (sorted) 126 | return ; 127 | } 128 | } 129 | ``` 130 | 131 | # 5 计数排序 132 | 假定数组为 `a[0...n-1]` ,数组中存在重复数字,数组中最大数字为k,建立两个辅助数组 `b[]` 和 `c[]`,`b[]` 用于存储排序后的结果,`c[]` 用于存储临时值。时间复杂度为 `O(N)`,适用于数字范围较小的数组。 133 | 134 | 135 | ![计数排序](https://user-gold-cdn.xitu.io/2018/9/27/1661b41b897fde91?w=1988&h=682&f=png&s=306292) 136 | 137 | 计数排序原理如上图所示,代码如下: 138 | 139 | ``` 140 | /** 141 | * 计数排序 142 | */ 143 | void countingSort(int a[], int n) 144 | { 145 | int i, j; 146 | int *b = (int *)malloc(sizeof(int) * n); 147 | int k = maxOfIntArray(a, n); // 求数组最大元素 148 | int *c = (int *)malloc(sizeof(int) * (k+1)); //辅助数组 149 | 150 | for (i = 0; i <= k; i++) 151 | c[i] = 0; 152 | 153 | for (j = 0; j < n; j++) 154 | c[a[j]] = c[a[j]] + 1; //c[i]包含等于i的元素个数 155 | 156 | for (i = 1; i <= k; i++) 157 | c[i] = c[i] + c[i-1]; //c[i]包含小于等于i的元素个数 158 | 159 | for (j = n-1; j >= 0; j--) { // 赋值语句 160 | b[c[a[j]]-1] = a[j]; //结果存在b[0...n-1]中 161 | c[a[j]] = c[a[j]] - 1; 162 | } 163 | 164 | /*方便测试代码,这一步赋值不是必须的*/ 165 | for (i = 0; i < n; i++) { 166 | a[i] = b[i]; 167 | } 168 | 169 | free(b); 170 | free(c); 171 | } 172 | ``` 173 | 174 | **扩展:** 如果代码中的给数组 `b[]` 赋值语句 `for (j=n-1; j>=0; j--)` 改为 `for(j=0; j<=n-1; j++)`,该代码仍然正确,只是排序不再稳定。 175 | 176 | 177 | # 6 归并排序 178 | 归并排序通过分治算法,先排序好两个子数组,然后将两个子数组归并。时间复杂度为 `O(NlgN)`。代码如下: 179 | 180 | ``` 181 | /* 182 | * 归并排序-递归 183 | * */ 184 | void mergeSort(int a[], int l, int u) 185 | { 186 | if (l < u) { 187 | int m = l + (u-l)/2; 188 | mergeSort(a, l, m); 189 | mergeSort(a, m + 1, u); 190 | merge(a, l, m, u); 191 | } 192 | } 193 | 194 | /** 195 | * 归并排序合并函数 196 | */ 197 | void merge(int a[], int l, int m, int u) 198 | { 199 | int n1 = m - l + 1; 200 | int n2 = u - m; 201 | 202 | int left[n1], right[n2]; 203 | int i, j; 204 | for (i = 0; i < n1; i++) /* left holds a[l..m] */ 205 | left[i] = a[l + i]; 206 | 207 | for (j = 0; j < n2; j++) /* right holds a[m+1..u] */ 208 | right[j] = a[m + 1 + j]; 209 | 210 | i = j = 0; 211 | int k = l; 212 | while (i < n1 && j < n2) { 213 | if (left[i] < right[j]) 214 | a[k++] = left[i++]; 215 | else 216 | a[k++] = right[j++]; 217 | } 218 | while (i < n1) /* left[] is not exhausted */ 219 | a[k++] = left[i++]; 220 | while (j < n2) /* right[] is not exhausted */ 221 | a[k++] = right[j++]; 222 | } 223 | ``` 224 | 225 | 226 | **扩展:归并排序的非递归实现怎么做?** 227 | 228 | 归并排序的非递归实现其实是最自然的方式,先两两合并,而后再四四合并等,就是从底向上的一个过程。代码如下: 229 | 230 | ``` 231 | /** 232 | * 归并排序-非递归 233 | */ 234 | void mergeSortIter(int a[], int n) 235 | { 236 | int i, s=2; 237 | while (s <= n) { 238 | i = 0; 239 | while (i+s <= n){ 240 | merge(a, i, i+s/2-1, i+s-1); 241 | i += s; 242 | } 243 | 244 | //处理末尾残余部分 245 | merge(a, i, i+s/2-1, n-1); 246 | s*=2; 247 | } 248 | //最后再从头到尾处理一遍 249 | merge(a, 0, s/2-1, n-1); 250 | } 251 | ``` 252 | 253 | 254 | # 7 基数排序、桶排序 255 | 基数排序的思想是对数字每一位分别排序(注意这里必须是稳定排序,比如计数排序等,否则会导致结果错误),最后得到整体排序。假定对 `N` 个数字进行排序,如果数字有 `d` 位,每一位可能的最大值为 `K`,则每一位的稳定排序需要 `O(N+K)` 时间,总的需要 `O(d(N+K))` 时间,当 `d` 为常数,`K=O(N)` 时,总的时间复杂度为O(N)。 256 | 257 | ![基数排序](https://user-gold-cdn.xitu.io/2018/9/27/1661b41f39b22007?w=704&h=296&f=png&s=85344) 258 | 259 | 而桶排序则是在输入符合均匀分布时,可以以线性时间运行,桶排序的思想是把区间 `[0,1)` 划分成 `N` 个相同大小的子区间,将 `N` 个输入均匀分布到各个桶中,然后对各个桶的链表使用插入排序,最终依次列出所有桶的元素。 260 | 261 | ![桶排序](https://user-gold-cdn.xitu.io/2018/9/27/1661b4215a0886e0?w=992&h=752&f=png&s=199439) 262 | 263 | 这两种排序使用场景有限,代码就略过了,更详细可以参考《算法导论》的第8章。 264 | 265 | # 参考资料 266 | - 《算法导论》 267 | - https://www.cnblogs.com/liushang0419/archive/2011/09/19/2181476.html (归并排序非递归实现参考的这里) 268 | 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /docs/数据结构和算法面试题系列9—排序算法之快速排序.md: -------------------------------------------------------------------------------- 1 | > 这个系列是我多年前找工作时对数据结构和算法总结,其中有基础部分,也有各大公司的经典的面试题,最早发布在CSDN。现整理为一个系列给需要的朋友参考,如有错误,欢迎指正。本系列完整代码地址在 [这里](https://github.com/shishujuan/dsalg)。 2 | 3 | # 0 概述 4 | 快速排序也是基于分治模式,类似归并排序那样,不同的是快速排序划分最后不需要merge。对一个数组 `A[p..r]` 进行快速排序分为三个步骤: 5 | 6 | - 划分: 数组 `A[p...r]` 被划分为两个子数组 `A[p...q-1]` 和 `A[q+1...r]`,使得 `A[p...q-1]` 中每个元素都小于等于 `A[q]`,而 `A[q+1...r]` 每个元素都大于 `A[q]`。划分流程见下图。 7 | - 解决: 通过递归调用快速排序,对子数组分别排序即可。 8 | - 合并:因为两个子数组都已经排好序了,且已经有大小关系了,不需要做任何操作。 9 | 10 | ![快速排序划分](https://user-gold-cdn.xitu.io/2018/9/28/1662050740abaabc?w=814&h=1190&f=png&s=208221) 11 | 12 | 快速排序算法不算复杂的算法,但是实际写代码的时候却是最容易出错的代码,写的不对就容易死循环或者划分错误,本文代码见 [这里](https://github.com/shishujuan/dsalg/tree/master/code/alg/sort)。 13 | 14 | # 1 朴素的快速排序 15 | 这个朴素的快速排序有个缺陷就是在一些极端情况如所有元素都相等时(或者元素本身有序,如 `a[] = {1,2,3,4,5}`等),朴素的快速算法时间复杂度为 `O(N^2)`,而如果能够平衡划分数组则时间复杂度为 `O(NlgN)`。 16 | 17 | ``` 18 | /** 19 | * 快速排序-朴素版本 20 | */ 21 | void quickSort(int a[], int l, int u) 22 | { 23 | if (l >= u) return; 24 | 25 | int q = partition(a, l, u); 26 | quickSort(a, l, q-1); 27 | quickSort(a, q+1, u); 28 | } 29 | 30 | /** 31 | * 快速排序-划分函数 32 | */ 33 | int partition(int a[], int l, int u) 34 | { 35 | int i, q=l; 36 | for (i = l+1; i <= u; i++) { 37 | if (a[i] < a[l]) 38 | swapInt(a, i, ++q); 39 | } 40 | swapInt(a, l, q); 41 | return q; 42 | } 43 | ``` 44 | 45 | # 2 改进-双向划分的快速排序 46 | 一种改进方法就是采用双向划分,使用两个变量 `i` 和 `j`,`i` 从左往右扫描,移过小元素,遇到大元素停止;`j` 从右往左扫描,移过大元素,遇到小元素停止。然后测试i和j是否交叉,如果交叉则停止,否则交换 `i` 与 `j` 对应的元素值。 47 | 48 | 注意,如果数组中有相同的元素,则遇到相同的元素时,我们停止扫描,并交换 `i` 和 `j` 的元素值。虽然这样交换次数增加了,但是却将所有元素相同的最坏情况由 `O(N^2)` 变成了差不多 `O(NlgN)` 的情况。比如数组 `A={2,2,2,2,2}`, 则使用朴素快速排序方法,每次都是划分 `n` 个元素为 `1` 个和 `n-1` 个,时间复杂度为 `O(N^2)`,而使用双向划分后,第一次划分的位置是 `2`,基本可以平衡划分两部分。代码如下: 49 | 50 | ``` 51 | /** 52 | * 快速排序-双向划分函数 53 | */ 54 | int partitionLR(int a[], int l, int u, int pivot) 55 | { 56 | int i = l; 57 | int j = u+1; 58 | while (1) { 59 | do { 60 | i++; 61 | } while (a[i] < pivot && i <= u); //注意i<=u这个判断条件,不能越界。 62 | 63 | do { 64 | j--; 65 | } while (a[j] > pivot); 66 | 67 | if (i > j) break; 68 | 69 | swapInt(a, i, j); 70 | } 71 | 72 | // 注意这里是交换l和j,而不是l和i,因为i与j交叉后,a[i...u]都大于等于枢纽元t, 73 | // 而枢纽元又在最左边,所以不能与i交换。只能与j交换。 74 | swapInt(a, l, j); 75 | 76 | return j; 77 | } 78 | 79 | /** 80 | * 快速排序-双向划分法 81 | */ 82 | void quickSortLR(int a[], int l, int u) 83 | { 84 | if (l >= u) return; 85 | 86 | int pivot = a[l]; 87 | int q = partitionLR(a, l, u, pivot); 88 | quickSortLR(a, l, q-1); 89 | quickSortLR(a, q+1, u); 90 | } 91 | ``` 92 | 93 | 虽然双向划分解决了所有元素相同的问题,但是对于一个已经排好序的数组还是会达到 `O(N^2)` 的复杂度。此外,双向划分还要注意的一点是代码中循环的写法,如果写成 `while(a[i] a[m] ) 120 | swapInt(a, l, m); 121 | 122 | if( a[l] > a[u] ) 123 | swapInt(a, l, u); 124 | 125 | if( a[m] > a[u] ) 126 | swapInt(a, m, u); 127 | 128 | /* assert: a[l] <= a[m] <= a[u] */ 129 | swapInt(a, m, l); // 交换枢纽元到位置l 130 | 131 | return a[l]; 132 | } 133 | ``` 134 | 135 | 此外,在数据基本有序的情况下,使用插入排序可以得到很好的性能,而且在排序很小的子数组时,插入排序比快速排序更快,可以在数组比较小时选用插入排序,而大数组才用快速排序。 136 | 137 | # 4 非递归写快速排序 138 | 非递归写快速排序着实比较少见,不过练练手总是好的。需要用到栈,注意压栈的顺序。代码如下: 139 | 140 | ``` 141 | /** 142 | * 快速排序-非递归版本 143 | */ 144 | void quickSortIter(int a[], int n) 145 | { 146 | Stack *stack = stackNew(n); 147 | int l = 0, u = n-1; 148 | int p = partition(a, l, u); 149 | 150 | if (p-1 > l) { //左半部分两个边界值入栈 151 | push(stack, p-1); 152 | push(stack, l); 153 | } 154 | 155 | if (p+1 < u) { //右半部分两个边界值入栈 156 | push(stack, u); 157 | push(stack, p+1); 158 | } 159 | 160 | while (!IS_EMPTY(stack)) { //栈不为空,则循环划分过程 161 | l = pop(stack); 162 | u = pop(stack); 163 | p = partition(a, l, u); 164 | 165 | if (p-1 > l) { 166 | push(stack, p-1); 167 | push(stack, l); 168 | } 169 | 170 | if (p+1 < u) { 171 | push(stack, u); 172 | push(stack, p+1); 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | # 参考资料 179 | - 《数据结构和算法-C语言实现》 180 | - 《算法导论》 --------------------------------------------------------------------------------