├── Chapter03
├── List.h
├── README.md
├── test.cpp
├── Queue.h
├── Vector.h
├── Stack.h
└── SimpleCalculator.h
├── Data-Structure.vcxproj.user
├── Chapter02
├── README.md
├── exercise02.cpp
├── example.h
└── exercise02.h
├── Chapter01
├── README.md
├── exercise01.cpp
└── exercise01.h
├── Chapter08
├── test.cpp
├── README.md
└── DisjSets.h
├── Chapter04
├── README.md
├── test.cpp
├── BinarySearchTree.h
├── exercise04.h
└── AVLTree.h
├── Chapter05
├── README.md
├── test.cpp
├── HashTable(Separate_Chaining).h
└── HashTable(Open_Addressing_Hashing).h
├── Data-Structure.vcxproj.filters
├── Chapter07
├── Timer.h
├── test.cpp
├── README.md
└── Sort.h
├── Chapter06
├── test.cpp
├── README.md
├── BinaryHeap.h
├── LeftistHeap.h
└── BinomialQueue.h
├── Data-Structure.sln
├── README.md
├── .gitattributes
├── .gitignore
├── Data-Structure.vcxproj
└── LICENSE
/Chapter03/List.h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-square/Data-Structure/HEAD/Chapter03/List.h
--------------------------------------------------------------------------------
/Data-Structure.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
--------------------------------------------------------------------------------
/Chapter02/README.md:
--------------------------------------------------------------------------------
1 | # 第二章 算法分析
2 |
3 | ## 内容
4 | * 主要内容是复杂度分析
5 | * 大O标记
6 | * 计算大O时的一般法则
7 | - 对数规律的一般法则
8 | 如果一个算法用常数时间(O(1))将问题的大小削减为其一部分(通常是1/2),那么该算法就是O(logN)的。
9 |
10 | ## 例子
11 | 1. 二分搜索提供了O(logN)的查找算法
12 | 2. 最大公因数的欧几里得算法也是O(logN)的
13 | 3. 幂运算的递归算法
--------------------------------------------------------------------------------
/Chapter01/README.md:
--------------------------------------------------------------------------------
1 | # 第一章 引论
2 |
3 | ## 内容
4 |
5 | * 介绍基本数学知识
6 | * 简要复习递归
7 | * 介绍用到的C++知识
8 |
9 | ## 递归的四条基本法则
10 | 1. 基准情形。必须总有某些基准情形不用递归就能求解。
11 | 2. 不断推进。对于那些需要递归求解的情形,递归调用必须总能够朝着基准情形的方向推进。
12 | 3. 设计法则。假设所有的递归调用都能运行。
13 | 4. 合成效益法则。在求解一个问题的同一实例时,切勿在不同的递归调用中做重复性的工作。
--------------------------------------------------------------------------------
/Chapter03/README.md:
--------------------------------------------------------------------------------
1 | # 表、栈和队列
2 |
3 | ## 内容
4 | * 介绍三种基本的数据结构
5 | * 介绍抽象数据类型(ADT, abstract data type)的概念
6 | * 介绍栈ADT及其在实现递归方面的应用
7 | * 介绍队列ADT及其在操作系统和算法设计中的应用
8 | * 给出vector和list的重要子集的实现
9 |
10 | ## 栈
11 | ### 实现
12 | 栈是一个表,因此任何实现表的方法都能实现栈。
13 | ### 应用
14 | 1. 符号平衡
15 | 2. 后缀(逆波兰)表达式计算
16 | 3. 中缀到后缀的转换
17 | 4. 函数调用
18 | (代码实现了一个简单的计算器,应保证输入合法)
19 |
20 | ## 总结
21 | ### 快慢指针
22 | ex 3.34 提示:判断一个链表是否有环,只使用O(1)的额外空间,使用两个迭代器p,q p每次递增1,q每次递增2,若q到了末尾则没环,否则pq必定在环中间相遇
23 |
24 | 也可用于快速找出单链表的中间节点
--------------------------------------------------------------------------------
/Chapter01/exercise01.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "exercise01.h"
4 |
5 | int main()
6 | {
7 | OrderedCollection ARR;
8 |
9 | int temp = 0;
10 | cout << "Please input some numbers(-1 means end): ";
11 | while ((cin >> temp) && (temp != -1))
12 | ARR.insert(temp);
13 |
14 | if (ARR.size()) {
15 | cout << "Max number is: " << *ARR.findMax() << endl;
16 | cout << "Min number is: " << *ARR.findMin() << endl;
17 | }
18 |
19 | return 0;
20 | }
--------------------------------------------------------------------------------
/Chapter08/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | using namespace std;
3 |
4 | #include "DisjSets.h"
5 |
6 | int main()
7 | {
8 | DisjSets One(5000);
9 |
10 | int root1 = 0, root2 = 0;
11 | for (int i = 10; i < 1000; ++i) {
12 | root1 = One.find(2 * i + 1);
13 | root2 = One.find(3 * i + 1);
14 | One.unionSets(root1, root2);
15 | }
16 | for (int i = 0; i < 1000; ++i) {
17 | cout << i << " : " << One.find(i) << endl;
18 | }
19 |
20 | getchar();
21 | return 0;
22 | }
--------------------------------------------------------------------------------
/Chapter02/exercise02.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "exercise02.h"
4 |
5 | using namespace std;
6 |
7 | int main()
8 | {
9 | maxInfo i;
10 | vector arr;
11 | int temp = 0;
12 | cout << "Please input some numbers: ";
13 | while (cin >> temp) {
14 | arr.push_back(temp);
15 | }
16 |
17 | cout << "最小连续子序列和: " << minSubSum(arr) << endl;
18 |
19 | i = maxSubSum(arr);
20 | cout << "最大连续子序列和: " << i.Sum << ", 起始下标: " << i.begin << ", 结束下标: " << i.end << endl;
21 |
22 | return 0;
23 | }
--------------------------------------------------------------------------------
/Chapter04/README.md:
--------------------------------------------------------------------------------
1 | # 树
2 |
3 | ## 内容
4 | * 了解树是如何用于实现文件系统的
5 | * 了解树如何用来计算算术表达式的值
6 | * 了解如何用树实现O(logN)时间进行搜素
7 | * 讨论并使用set和map
8 |
9 | ## 二叉树的遍历
10 | * 前序:先处理自己后处理左右儿子
11 | * 中序:先处理左儿子再处理自己再处理右儿子
12 | * 后序:先处理左右儿子再处理自己
13 |
14 | ## 二叉查找树(平均深度O(logN))
15 | 性质:对于树中的每个节点X,左子树中所有项的值小于X中的项,右子树中所有项的值大于X中的项
16 | 缺点:不能动态调整,若输入为已排序序列则构造出最坏情况下的斜树
17 |
18 | ## AVL树
19 | * 带有**平衡条件**的二叉查找树
20 | * 一棵AVL树是每个节点的左子树和右子树的高度最多相差1的二叉查找树(空树高度定义为-1)
21 | * 插入新节点可能破坏AVL树的平衡,需要通过**旋转**解决
22 |
23 | 把需要平衡的节点叫α
24 |
25 | 1. 对α的左儿子的左子树进行一次插入
26 | 2. 对α的左儿子的右子树进行一次插入
27 | 3. 对α的右儿子的左子树进行一次插入
28 | 4. 对α的右儿子的右子树进行一次插入
29 |
30 | 1和4(左左,右右)发生在外边,进行一次**单旋转**即可,2和3(左右,右左)则发生在内部,需要通过**双旋转**调整
31 |
32 | ## 伸展树
33 | 节点可以达到任意深度,每次访问某节点后把该节点调整为根节点,任意连续M次操作花费O(MlogN)时间
34 |
35 | ## B树(平衡M路树)
36 | M=3时:2-3树,实现平衡查找树的另一种方法
37 |
38 | ## 注意
39 | 通过插入元素构造查找树,然后执行中序遍历,可以得到排序后的元素。
40 | 这是一种O(NlogN)的排序算法
--------------------------------------------------------------------------------
/Chapter08/README.md:
--------------------------------------------------------------------------------
1 | # 不相交集类
2 | 这一章介绍解决等价问题的一种有效数据结构。实现简单,也非常快,每种操作只需要常数平均时间。
3 |
4 | ## 等价关系 (equivalence relation)
5 | 若对于每一对元素(a,b),a,b∈S, `a R b`或者为true或者为false,则称在集合S上定义关系R。如果`a R b`为true,我们说a和b有关系。
6 |
7 | **等价关系**是满足下列三个性质的关系R:
8 |
9 | 1. 自反性:对于所有的a∈S,`a R a`
10 | 2. 对称性:`a R b`当且仅当`b R a`
11 | 3. 传递性:若`a R b`且b R c则`a R c`
12 |
13 | 元素a∈S的**等价类**(equivalence class)是S的子集,它包含所有与a有(等价)关系的元素。注意,等价类形成对S的一个划分:S的每一个成员恰好出现在一个等价类中。为确定是否a~b,我们只需验证a和b是否都在同一个等价类中。
14 |
15 | 输入数据最初是N个集合(collection)的类,每个集合含有一个元素。初始的描述是所有的关系均为false(自反的关系除外)。每个集合都有一个不同的元素,从而`Si∩Sj=⊙`,称为**不相交**(disjoint)
16 |
17 | 基本操作有两种,称为**求并/查找**(union/find)算法。
18 |
19 | ## 灵巧求并算法
20 | 直观的union操作相当随意,它简单地通过使第二棵树成为第一棵树的子树而完成合并。对其进行简单改进,使得总是较小的树成为较大的树的子树,称为**按大小求并**(union by size),它保证树的深度最大是O(logN)。
21 | 连续M次操作平均需要O(M)时间。
22 |
23 | 另一种方法是**按高度求并**(union by height),它同样保证树的深度最大是O(logN)。做法是使浅的树成为深的树的子树。
24 |
25 | ## 一个应用
26 | 应用求并/查找数据结构的一个例子是迷宫的生成。初始化时所有格子都在自己的等价类中,之后不断合并,最终生成迷宫。
--------------------------------------------------------------------------------
/Chapter08/DisjSets.h:
--------------------------------------------------------------------------------
1 | #ifndef DS_CH08_DISJSETS_H
2 | #define DS_CH08_DISJSETS_H
3 |
4 | #include
5 |
6 | class DisjSets {
7 | public:
8 | explicit DisjSets(int numElements) : s(numElements, -1) {}
9 |
10 | int find(int x) const
11 | {
12 | if (s[x] < 0)
13 | return x;
14 | else
15 | return find(s[x]);
16 | }
17 | //路径压缩
18 | int find(int x)
19 | {
20 | if (s[x] < 0)
21 | return x;
22 | else
23 | return s[x] = find(s[x]);
24 | }
25 | //按高度求并 初始为-1,存储高度-1
26 | void unionSets(int root1, int root2)
27 | {
28 | if (s[root2] < s[root1])
29 | s[root1] = root2;
30 | else {
31 | if (s[root1] == s[root2])
32 | --s[root1]; //更新高度
33 | s[root2] = root1;
34 | }
35 | }
36 |
37 | private:
38 | std::vector s;
39 | };
40 |
41 | #endif // DS_CH08_DISJSETS_H
42 |
--------------------------------------------------------------------------------
/Chapter05/README.md:
--------------------------------------------------------------------------------
1 | # 散列
2 | 散列表(hash table)的实现通常称为散列(hashing),指用于以O(1)时间执行插入、删除和查找的技术,但不支持需要排序信息的树操作,比如findMin、findMax以及在线性时间内按顺序打印整个表都不支持
3 |
4 | ## 内容
5 | 中心数据结构是**散列表**
6 |
7 | * 实现散列表的几种方法
8 | * 分析比较几种方法
9 | * 介绍散列的多种应用
10 | * 比较散列表与二叉查找树
11 |
12 | ## 散列函数
13 | 基本思想:将每个键(Key)映射到从[0, TableSize)这个范围中的某个数,并且将其放到适当的单元中,这个映射就称为**散列函数**。
14 | 问题:选择一个函数,决定当两个键散列到同一个值的时候(称为**冲突(collision)**应该做什么以及如何确定散列表的大小。
15 | _注:一般使表的大小为素数,有助于避免部分冲突问题_
16 |
17 | ## 装填因子(load factor)
18 | 定义散列表的装填因子 λ 为散列表中的元素个数与散列表大小的比值。
19 |
20 | ## 分离链接法
21 | 将散列到同一个值的所有元素保留到一个链表中。
22 | 一般法则:使 λ ≈ 1,控制链表的长度,若 λ > 1 则通过再散列扩充
23 |
24 | ## 开放定址法
25 | 不用链表存储,实现分配较大空间,称为**探测散列表**
26 | hi(x) = (hash(x) + f(i)) mod TableSize, f(0) = 0.
27 | 一般 λ > 0.5 就要再散列
28 |
29 | * 线性探测 f(i) = i
30 | * 平方探测 f(i) = i^2
31 | * 双散列 f(i) = i * hash2(x), hash2(x) = R - (x mod R) 这样的函数会起作用,其中R为小于TableSize的素数
32 |
33 | ## 再散列(rehash)
34 | 1. 只要表到一半就再散列
35 | 2. 只有插入失败时才再散列
36 | 3. 途中策略:当表到达某一个装填因子时进行再散列(最优)
37 |
--------------------------------------------------------------------------------
/Chapter05/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | using namespace std;
6 |
7 | #include "HashTable(Separate_Chaining).h"
8 | #include "HashTable(Open_Addressing_Hashing).h"
9 |
10 | int main()
11 | {
12 | default_random_engine e(static_cast(time(nullptr)));
13 | uniform_int_distribution u(0, 10000);
14 |
15 | int arr[10000] = { 0 };
16 | for (int i = 0; i < 10000; ++i)
17 | arr[i] = u(e);
18 |
19 | HashTable_SC tableSC(100);
20 | HashTable_OAH tableOAH(100);
21 |
22 | for (int i = 0; i < 10000; ++i) {
23 | tableSC.insert(arr[i]);
24 | tableOAH.insert(arr[i]);
25 | }
26 |
27 | cout << "SC size: " << tableSC.size() << ", find " << arr[5] << " ? "
28 | << boolalpha << tableSC.contains(arr[5]) << endl;
29 | cout << "OAH size: " << tableOAH.size() << ", find " << arr[5] << " ? "
30 | << boolalpha << tableOAH.contains(arr[5]) << endl;
31 |
32 | getchar();
33 | return 0;
34 | }
35 |
--------------------------------------------------------------------------------
/Chapter03/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "SimpleCalculator.h"
4 | #include "Queue.h"
5 | #include "List.h"
6 |
7 | using namespace std;
8 |
9 | int main()
10 | {
11 | SimpleCalculator cal("14+2-32+8*9+(54*7+92-4)");
12 | cout << cal.getResult() << endl;
13 | cout << cal.calcu("13+2-32+8*9+(54*7+92-4)") << endl;
14 |
15 | int a[20] = { 29, 14, 15, 84, 65, 85, 92, 81, 51, 13, 5, 18, -64, 89, 66, 21, 32, 27, 24, 52 };
16 | LinkQueue que;
17 | SingleList lst;
18 | List dlst;
19 |
20 | for (int i = 0; i < 20; ++i) {
21 | que.enqueue(a[i]);
22 | lst.add(a[i]);
23 | dlst.push_back(a[i]);
24 | }
25 |
26 | cout << "queue: ";
27 | for (int i = 0; i < 20; ++i)
28 | cout << que.dequeue() << "→";
29 | cout << endl;
30 |
31 | cout << "single list: ";
32 | lst.print();
33 |
34 | cout << dlst.back() << " pop_back: ";
35 | dlst.pop_back();
36 | cout << dlst.back() << endl;
37 |
38 | getchar();
39 |
40 | return 0;
41 | }
--------------------------------------------------------------------------------
/Chapter02/example.h:
--------------------------------------------------------------------------------
1 | #ifndef DS_CH02_EXAMPLE_H
2 | #define DS_CH02_EXAMPLE_H
3 |
4 | #include
5 | using std::vector;
6 |
7 | // 二分搜索
8 | template
9 | int binarySearch(const vector &a, const Comparable &x)
10 | {
11 | int low = 0, high = a.size() - 1, mid = 0;
12 | while (low <= high) {
13 | mid = (low + high) >> 1;
14 | if (a[mid] < x)
15 | low = mid + 1;
16 | else if (a[mid] > x)
17 | high = mid - 1;
18 | else
19 | return mid;
20 | }
21 | return -1; // -1 means NOT FOUND
22 | }
23 |
24 | // 最大公因数的欧几里得算法
25 | long gcd(long m, long n)
26 | {
27 | while (n != 0) {
28 | long rem = m % n;
29 | m = n;
30 | n = rem;
31 | }
32 | return m;
33 | }
34 |
35 | // 幂运算的递归算法
36 | long pow(long x, int n)
37 | {
38 | if (n == 0)
39 | return 1;
40 | if (n & 1)
41 | return pow(x, n - 1) * x;
42 | else
43 | return pow(x * x, n >> 1);
44 | }
45 |
46 | #endif // DS_CH02_EXAMPLE_H
47 |
--------------------------------------------------------------------------------
/Data-Structure.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | 头文件
20 |
21 |
22 |
23 |
24 | 源文件
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Chapter07/Timer.h:
--------------------------------------------------------------------------------
1 | #ifndef DS_CH07_TIMER_H
2 | #define DS_CH07_TIMER_H
3 |
4 | #include
5 | using namespace std::chrono;
6 |
7 | class Timer {
8 | public:
9 | using s = seconds;
10 | using ms = milliseconds;
11 | using us = microseconds;
12 | using ns = nanoseconds;
13 |
14 | public:
15 | Timer() : tpStart(high_resolution_clock::now()), tpStop(tpStart) {}
16 |
17 | public:
18 | void start() { tpStart = high_resolution_clock::now(); }
19 | void stop() { tpStop = high_resolution_clock::now(); }
20 |
21 | template
22 | auto delta() const { return duration_cast(tpStop - tpStart).count(); }
23 |
24 | template
25 | auto stop_delta() { stop(); return duration_cast(tpStop - tpStart).count(); }
26 |
27 | template
28 | auto stop_delta_start()
29 | {
30 | stop();
31 | auto ts = duration_cast(tpStop - tpStart).count();
32 | start();
33 | return ts;
34 | }
35 |
36 | private:
37 | time_point tpStart;
38 | time_point tpStop;
39 | };
40 |
41 | #endif // DS_CH07_TIMER_H
42 |
--------------------------------------------------------------------------------
/Chapter07/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | using namespace std;
5 |
6 | #include "Timer.h"
7 | #include "Sort.h"
8 |
9 | #define PRINT_SORT_TIME(A, sortfun) \
10 | {\
11 | vector arrt(A);\
12 | Timer ts;\
13 | sortfun(arrt);\
14 | cout << #sortfun##" " << maxSize << " 个随机数用时: " << ts.stop_delta() << " ms" << endl;\
15 | }
16 |
17 | int main()
18 | {
19 | const int maxSize = 10000;
20 | default_random_engine e(static_cast(time(nullptr)));
21 | uniform_int_distribution u(1, maxSize);
22 |
23 | vector arr;
24 | for (int i = 0; i < maxSize; ++i)
25 | arr.push_back(u(e));
26 |
27 | if (maxSize <= 1000)
28 | PRINT_SORT_TIME(arr, insertionSort);
29 | PRINT_SORT_TIME(arr, shellSort);
30 | PRINT_SORT_TIME(arr, heapSort);
31 | PRINT_SORT_TIME(arr, mergeSort);
32 | PRINT_SORT_TIME(arr, mergeSort2);
33 | PRINT_SORT_TIME(arr, quickSort);
34 | {
35 | vector arr1(arr);
36 | quickSelect(arr1, 0, maxSize - 1, maxSize >> 1);
37 | cout << "the median num is: " << arr1[(maxSize >> 1) - 1] << endl;
38 | }
39 |
40 | getchar();
41 | return 0;
42 | }
--------------------------------------------------------------------------------
/Chapter06/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | using namespace std;
6 |
7 | #include "BinaryHeap.h"
8 | #include "LeftistHeap.h"
9 | #include "BinomialQueue.h"
10 |
11 | int main()
12 | {
13 | const int maxSize = 13;
14 | default_random_engine e(static_cast(time(nullptr)));
15 | uniform_int_distribution u(1, maxSize);
16 |
17 | int arr[maxSize] = { 0 };
18 | for (int i = 0; i < maxSize; ++i)
19 | arr[i] = u(e);
20 |
21 | BinaryHeap BHeap(maxSize);
22 | LeftistHeap LHeap;
23 | BinomialQueue BQ;
24 |
25 | for (int i = 0; i < maxSize; ++i) {
26 | BHeap.insert(arr[i]);
27 | LHeap.insert(arr[i]);
28 | BQ.insert(arr[i]);
29 | }
30 |
31 | cout << "BHeap size: " << BHeap.size() << ", min: " << BHeap.findMin() << endl;
32 | cout << "LHeap min: " << LHeap.findMin() << endl;
33 | cout << "BQ min: " << BQ.findMin() << endl;
34 | cout << "LHeap:" << endl;
35 | LHeap.printTree();
36 | cout << endl << "BinomialQueue:" << endl;
37 | BQ.printForest();
38 | BQ.deleteMin();
39 | cout << "deleteMin:" << endl;
40 | BQ.printForest();
41 |
42 | getchar();
43 | return 0;
44 | }
45 |
--------------------------------------------------------------------------------
/Chapter02/exercise02.h:
--------------------------------------------------------------------------------
1 | #ifndef DS_CH02_EXERCISE02_H
2 | #define DS_CH02_EXERCISE02_H
3 |
4 | #include
5 | using std::vector;
6 |
7 | //2.17
8 | //a.求最小连续子序列和 O(N)算法
9 | int minSubSum(const vector &a)
10 | {
11 | int minSum = 0, thisSum = 0;
12 | for (int i = 0; i < a.size(); ++i) {
13 | thisSum += a[i];
14 | if (thisSum < minSum)
15 | minSum = thisSum;
16 | else if (thisSum > 0)
17 | thisSum = 0;
18 | }
19 | return minSum;
20 | }
21 |
22 | //2.19
23 | //返回最大连续子序列和以及对应的下标
24 | struct maxInfo {
25 | maxInfo() = default;
26 | int Sum;
27 | int begin;
28 | int end;
29 | };
30 |
31 | maxInfo info;
32 |
33 | maxInfo maxSubSum(const vector &a)
34 | {
35 | int thisSum = 0;
36 | bool isBegin = false;
37 | for (int i = 0; i < a.size(); ++i) {
38 | thisSum += a[i];
39 | if (thisSum > info.Sum) {
40 | info.Sum = thisSum;
41 | if (!isBegin) {
42 | isBegin = true;
43 | info.begin = i;
44 | }
45 | info.end = i;
46 | } else if (thisSum < 0) {
47 | thisSum = 0;
48 | isBegin = false;
49 | }
50 | }
51 | return info;
52 | }
53 |
54 | #endif // DS_CH02_EXERCISE02_H
55 |
--------------------------------------------------------------------------------
/Data-Structure.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Data-Structure", "Data-Structure.vcxproj", "{BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Debug|x64.ActiveCfg = Debug|x64
17 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Debug|x64.Build.0 = Debug|x64
18 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Debug|x86.ActiveCfg = Debug|Win32
19 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Debug|x86.Build.0 = Debug|Win32
20 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Release|x64.ActiveCfg = Release|x64
21 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Release|x64.Build.0 = Release|x64
22 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Release|x86.ActiveCfg = Release|Win32
23 | {BB2BB86B-D2ED-42B0-AF95-C1F72D5031E6}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data-Structure
2 |
3 | ## 代码内容
4 | 《数据结构与算法分析C++描述拆分》上的代码实现,
5 | 按照该书的章节顺序,主要实现书上给出的例子,包括部分课后习题。
6 |
7 | ## 文件内容
8 | 所有实现均~~计划~~给出`.h` `.cpp`文件以及部分用于测试的`test.cpp`文件
9 |
10 | ## 环境
11 | - Windows 10 \& 8.1
12 | - Visual Studio 2015 with update 3
13 | - C++ (部分C++11语法)
14 |
15 | ## 章节列表
16 | ### 第一章 引论 & 第二章 算法分析
17 | :white_check_mark: 部分课后习题+简单例程
18 | ### 第三章 表、栈和队列
19 | :white_check_mark: Vector和List
20 | :white_check_mark: 链表
21 | :white_check_mark: 栈
22 | :white_check_mark: 队列
23 | ### 第四章 树
24 | :white_check_mark: 二叉查找树
25 | :white_check_mark: AVL树
26 | ### 第五章 散列
27 | :white_check_mark: 哈希表(分离链接法)
28 | :white_check_mark: 哈希表(开放定址法/平方探测)
29 | ### 第六章 优先队列(堆)
30 | :white_check_mark: 二叉堆
31 | :white_check_mark: 左式堆
32 | :white_check_mark: 二项队列
33 | ### 第七章 排序
34 | :white_check_mark: 插入排序
35 | :white_check_mark: 希尔排序
36 | :white_check_mark: 堆排序
37 | :white_check_mark: 归并排序
38 | :white_check_mark: 快速排序
39 | :white_check_mark: 快速选择
40 | ### 第八章 不相交集类
41 | :white_check_mark: 不相交集
42 | ### 第九章 图论算法
43 | :white_large_square: 邻接表(Version 1,2)
44 | :white_large_square: 拓扑排序(Version 1,2)
45 | :white_large_square: 单源最短路径算法
46 | :white_large_square: 最大网络流
47 | :white_large_square: 最小生成树
48 | :white_large_square: 深度优先搜索
49 | :white_large_square: 双连通性
50 | :white_large_square: 欧拉回路
51 | ### 第十章 算法设计技巧
52 | :white_large_square: 分治算法:最近点问题
53 | :white_large_square: 动态规划:斐波那契数列,递归关系,矩阵乘法顺序,最优搜索二叉树
54 | :white_large_square: 随机化算法:跳表
55 | :white_large_square: 回溯法:收费公路重建,三连棋游戏(带AI)
56 | ### 第十二章 高级数据结构及其实现
57 | :white_large_square: 红黑树:自顶向下插入,自顶向下删除
58 | :white_large_square: AA树
59 | :white_large_square: Treap树
60 | :white_large_square: Kd树
61 | :white_large_square: 配对堆
--------------------------------------------------------------------------------
/Chapter04/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | using namespace std;
6 |
7 | #include "BinarySearchTree.h"
8 | #include "AVLTree.h"
9 | #include "exercise04.h"
10 |
11 | int main()
12 | {
13 | default_random_engine e(static_cast(time(nullptr)));
14 | uniform_int_distribution u(-50, 50);
15 |
16 | int arr[50] = { 0 };
17 | for (int i = 0; i < 50; ++i)
18 | arr[i] = u(e);
19 |
20 | cout << "BinarySearchTree Test:" << endl;
21 | BinarySearchTree BSTree;
22 | for (int i = 0; i < 20; ++i)
23 | BSTree.insert(arr[i]);
24 |
25 | cout << "max: " << BSTree.findMax() << endl;
26 | cout << "min: " << BSTree.findMin() << endl;
27 | cout << "是否包含5 ? " << boolalpha << BSTree.contains(5) << endl;
28 | BSTree.printTree();
29 | cout << endl << endl << endl;
30 |
31 | cout << "AVLTree Test:" << endl;
32 | AVLTree AvlTree;
33 |
34 | for (int i = 0; i < 30; ++i)
35 | AvlTree.insert(arr[i+20]);
36 |
37 | AvlTree.printTree();
38 |
39 | cout << endl << "Remove ";
40 | for (int i = 0; i < 5; ++i) {
41 | int t = arr[i + 20 + 4 * i];
42 | cout << t << ",";
43 | AvlTree.remove(t);
44 | }
45 | cout << endl << endl;
46 |
47 | AvlTree.printTree();
48 |
49 | //ex4.11
50 | Set st;
51 | for (int i = 0; i < 50; ++i)
52 | st.insert(arr[i]);
53 |
54 | cout << "Set contains: " << endl;
55 | Set::const_iterator itr = st.begin();
56 | for (int i = 0; i < st.size(); ++i, ++itr) {
57 | cout << *itr << ",";
58 | }
59 | cout << endl;
60 | cout << "erase: " << arr[10] << endl;
61 | st.erase(arr[10]);
62 | cout << "Now contains: " << endl;
63 | itr = st.begin();
64 | for (int i = 0; i < st.size(); ++i, ++itr) {
65 | cout << *itr << ",";
66 | }
67 | cout << endl;
68 |
69 | getchar();
70 | return 0;
71 | }
--------------------------------------------------------------------------------
/Chapter06/README.md:
--------------------------------------------------------------------------------
1 | # 优先队列(堆)
2 | 本章讨论优先队列(priority queue),介绍优先队列在离散事件模拟中的应用
3 | 作者评价:这类数据结构属于计算机科学中最雅致的一种
4 |
5 | ## 内容
6 | * 优先队列ADT的高效实现
7 | * 优先队列的使用
8 | * 优先队列的高级实现
9 |
10 | ## 二叉堆 (binary heap)
11 | 插入删除最坏O(logN),实际上插入花费常数平均时间,若无删除干扰,该结构将以线性时间建立一个具有N项的优先队列。
12 | 与二叉查找树一样,堆具有两个性质,堆的操作必须满足所有性质才能终止。
13 |
14 | ### 结构性质
15 | 堆是一棵**完全二叉树**(三角形缺右下角),特例是满二叉树(三角形),最底层元素必须从左往右填入,如有空缺则不是完全二叉树
16 | 一棵高为h的完全二叉树有[2^h , 2^(h+1) - 1]个节点,这意味着完全二叉树的高是 下取整(logN),显然它是O(logN)的
17 | 因为此规律,所以堆可以用数组表示而不用链表,对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的(2i+1)上,它的父亲在位置 下取整(i/2) 上
18 |
19 | ### 堆序性质
20 | 在堆中,除根节点以外,每一个节点的值都大于(或等于)它的父节点的值
21 | 根据堆序性质,最小值总在根结点,因此可以以O(1)时间做findMin
22 | 相应地,通过改变堆序性质,也可以建立一个max堆,以O(1)时间做findMax
23 |
24 | ### 插入(上滤策略)
25 | 为了插入新元素X,在堆的下一个可用位置(为了满足结构性质)创建一个空穴,若X放入空穴仍满足堆序性质,则插入完成,否则交换空穴和其父节点,直到X被放入并满足堆序性质为止
26 |
27 | ### 删除(下滤策略)
28 | 找出最小元很容易,难的是删除它。
29 | 当删除一个最小元时,堆中最后一个元素X必须移动到该堆的某个地方。策略是在根节点建立一个空穴,然后将两个儿子中的较小者移入空穴,重复该步骤直到X可以被放入空穴中。代码中则是用X直接替换根结点的值,然后下滤。
30 |
31 | ### 注意
32 | 在堆的实现中经常出现的错误是,当堆中存在偶数个元素时,将出现一个节点只有一个儿子的情况。因此我们必须以节点不总有两个儿子为前提,这需要额外的测试。
33 |
34 | ### 应用
35 | #### 选择问题
36 | 输入N个元素及整数k,找出第k个最大的元素,极端情况是k=上取整(N/2),此时实际上是找中位数,以下两个算法都能在找中位数的情况下以O(NlogN)时间运行
37 |
38 | * A 将N个元素读入数组,对数组应用buildHeap,再执行k次deleteMin,最后根节点上的就是第k个最小值,构造一个最大堆就可以找到第k个最大值
39 | * B 用buildHeap将前k个元素构造成一个最大堆,若下一个元素大于堆里的最小值,则删除最小值,插入新元素,最终的最小值就是所求的第k个最大值
40 |
41 | ## d堆
42 | 类似B树,深度变浅,每个节点有d个儿子
43 |
44 | ## 左式堆 (leftist heap)
45 | 左式堆也是二叉树,但它不是理想平衡的,事实上是趋于非常不平衡
46 |
47 | 定义任一节点X的**零路径长(null path length)**npl(X)为从X到一个不具有两个儿子的节点的最短路径长
48 | 因此,具有0个或1个儿子的节点npl为0,而npl(NULL)=-1
49 | 注意,任一节点的npl比它儿子节点的npl的最小值多1
50 |
51 | ### 左式堆性质
52 | 对于堆中的每一个节点X,左儿子的npl至少与右儿子的npl一样大
53 | 这个性质导致树向左增加深度,沿左式堆右侧的右路径是堆中最短的路径
54 | 定理:在右路径上有r个节点的左式堆必然至少有2^r -1个节点
55 |
56 | 对左式堆的基本操作是合并。插入可以看成是合并一个单节点堆,删除即是删掉根结点,然后合并左右子树。
57 |
58 | ## 斜堆 (skew heap)
59 | 斜堆是左式堆的自调节形式,具有堆序,但不存在结构限制。斜堆不需要存储npl,每次合并无条件交换左右儿子。
60 |
61 | ## 二项队列 (binomial queue)
62 | 以最坏O(logN)支持插入、合并、deleteMin,插入操作平均花费常数时间
63 |
64 | 实质是由**二项树**(binomial tree)构成的**森林**(forest)。
65 | 每一个高度上最多存在一棵二项树。高度为k的二项树Bk是通过将一棵二项树B(k-1)附接到另一棵二项树B(k-1)的根上构成的。高度为k的二项树有2^k个节点,在深度d处的节点数是二项系数C(d,k)
66 |
67 | 如果把堆序性质施加到二项树上并允许任意高度上最多一棵二项树,则可以用二项树的集合唯一地表示任意大小的优先队列。如大小为13的优先队列可以用B3,B2,B0表示,可以写成1101,同时也是13的二进制形式。
68 |
69 | ### 操作
70 | 基本操作仍然是合并,思想是从小到大合并相同高度的二项树
71 | 插入是特殊情况下的合并
72 | deleteMin将原二项队列一分为二,再合并
73 |
74 | 编程需要注意**进位**的实现
--------------------------------------------------------------------------------
/Chapter01/exercise01.h:
--------------------------------------------------------------------------------
1 | #ifndef DS_CH01_EXERCISE01_H
2 | #define DS_CH01_EXERCISE01_H
3 |
4 | // ex 1.5
5 | int ones(int n)
6 | {
7 | if (n < 2)
8 | return n;
9 | return (n & 1) + ones(n >> 1);
10 | }
11 |
12 | #include
13 |
14 | using namespace std;
15 |
16 | //ex 1.13
17 | template
18 | class Collection {
19 | public:
20 | typedef typename vector