树 在数据结构中,树 (Tree)是一种分层的非线性数据结构,它由节点(node)和边(edge)组成,并且具有以下性质:
树的概念
节点和边 :树包含一组节点,通过边连接,节点之间呈现出层级关系。
根节点 :树的起始节点称为根节点(root),通常位于树的顶端。
父节点和子节点 :每个节点可以连接其他节点,称为子节点(children);连接它们的节点称为父节点(parent)。
叶子节点 :没有子节点的节点称为叶子节点(leaf)。
层级 :树的每一层从根节点开始,按层级关系排列。
路径 :节点之间的路径由节点和边组成,从根节点到某个特定节点的路径是节点之间的唯一路径。
树的性质
唯一根节点 :树中只有一个根节点,没有父节点。
节点数量关系 :有 ( n ) 个节点的树,边的数量为 ( n - 1 )。
层次性 :树具有层次结构,自上而下逐级分层。
无回路性 :树中不存在回路,任何两个节点之间只有唯一一条路径。
连通性 :树是一个连通的无向图,从根节点可以到达所有节点。
特殊类型的树
二叉树 :每个节点最多只有两个子节点的树。
完全二叉树 :除最后一层外,所有层的节点数都达到最大值,且最后一层节点从左到右连续排列。
平衡二叉树 :任意节点的左右子树高度差不超过一定值(如 AVL 树)。
二叉搜索树(BST) :左子节点的值小于根节点,右子节点的值大于根节点,便于快速查找。
好的,这里是考试大纲所涉及的二叉树和树的基本概念、性质和C++的实现示例:
二叉树的概念、性质和实现 二叉树 是每个节点最多有两个子节点的树结构。每个节点的子节点通常被称为左子节点和右子节点。
性质 :
二叉树的第 (i) 层最多有 (2^{i-1}) 个节点。
高度为 (h) 的二叉树最多有 (2^h - 1) 个节点。
完全二叉树和满二叉树是特殊的二叉树。
实现 (链式存储):1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode (int x) : val (x), left (NULL ), right (NULL ) {} }; TreeNode* insert (TreeNode* root, int val) { if (!root) return new TreeNode (val); if (val < root->val) root->left = insert (root->left, val); else root->right = insert (root->right, val); return root; }
二叉树的顺序存储结构和链式存储结构 顺序存储结构 :顺序存储通常使用数组存储完全二叉树。第 (i) 个节点的左子节点在 (2i+1),右子节点在 (2i+2)。
链式存储结构 :链式存储用指针实现更灵活,适用于非完全二叉树。
1 2 3 4 5 6 struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode (int x) : val (x), left (NULL ), right (NULL ) {} };
遍历二叉树 前序遍历 、中序遍历 、后序遍历 和层序遍历 是常见的二叉树遍历方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void preorder (TreeNode* root) { if (root) { cout << root->val << " " ; preorder (root->left); preorder (root->right); } } void inorder (TreeNode* root) { if (root) { inorder (root->left); cout << root->val << " " ; inorder (root->right); } } void postorder (TreeNode* root) { if (root) { postorder (root->left); postorder (root->right); cout << root->val << " " ; } }
树和森林的存储结构、遍历 树的存储 :一般用链式结构存储树(多叉树),每个节点指向多个子节点。
1 2 3 4 5 struct TreeNode { int val; vector<TreeNode*> children; TreeNode (int x) : val (x) {} };
森林的概念 :森林是一组互不相交的树的集合。常用递归将森林表示为一棵树,每棵树的根节点作为链式存储的下一节点。
遍历森林 :可以通过先序、后序等遍历方式实现,每个子树递归遍历。
1 2 3 4 5 void traverseForest (const vector<TreeNode*>& forest) { for (TreeNode* tree : forest) { preorder (tree); } }
堆与优先队列 堆 是一种特殊的二叉树,是完全二叉树的一种。堆有两种常见的类型:最大堆 和最小堆 。
最大堆 :每个父节点的值都大于等于其子节点的值,根节点是堆中最大的元素。
最小堆 :每个父节点的值都小于等于其子节点的值,根节点是堆中最小的元素。
堆的性质
完全二叉树 :堆使用完全二叉树的结构,因此适合用数组表示。
节点关系 :在堆的数组表示中,对于第 i
个节点:
父节点位置为 (i-1)/2
。
左子节点位置为 2i+1
。
右子节点位置为 2i+2
。
优先队列 优先队列 是一种基于堆的数据结构,可以快速访问和删除优先级最高(或最低)的元素。在 C++ 中,标准库提供了 priority_queue
,其默认实现为最大堆。
最大堆 以下是一个最大堆的简单实现,包括插入和删除最大值的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <iostream> #include <vector> using namespace std;class MaxHeap { vector<int > heap; void heapifyUp (int index) { while (index > 0 ) { int parent = (index - 1 ) / 2 ; if (heap[index] > heap[parent]) { swap (heap[index], heap[parent]); index = parent; } else { break ; } } } void heapifyDown (int index) { int size = heap.size (); while (2 * index + 1 < size) { int leftChild = 2 * index + 1 ; int rightChild = 2 * index + 2 ; int largerChild = leftChild; if (rightChild < size && heap[rightChild] > heap[leftChild]) { largerChild = rightChild; } if (heap[index] < heap[largerChild]) { swap (heap[index], heap[largerChild]); index = largerChild; } else { break ; } } } public : void insert (int val) { heap.push_back (val); heapifyUp (heap.size () - 1 ); } int extractMax () { if (heap.empty ()) return -1 ; int maxVal = heap[0 ]; heap[0 ] = heap.back (); heap.pop_back (); heapifyDown (0 ); return maxVal; } bool isEmpty () const { return heap.empty (); } }; int main () { MaxHeap heap; heap.insert (10 ); heap.insert (20 ); heap.insert (15 ); cout << "Extracted Max: " << heap.extractMax () << endl; cout << "Extracted Max: " << heap.extractMax () << endl; cout << "Extracted Max: " << heap.extractMax () << endl; return 0 ; }
优先队列的实现(C++ 标准库) C++ STL 提供了 priority_queue
,默认是最大堆,但可以通过传递自定义比较器来实现最小堆。
好的,以下是一个直接实现的优先队列 ,不依赖 C++ 标准模板库(STL)。我们将使用最大堆 的方式来实现优先队列,可以快速地获取最大值并进行插入和删除操作。最小堆的实现方式类似,只需调整比较逻辑。
基于最大堆的优先队列实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include <iostream> #include <vector> using namespace std;class PriorityQueue {private : vector<int > heap; void heapifyUp (int index) { while (index > 0 ) { int parent = (index - 1 ) / 2 ; if (heap[index] > heap[parent]) { swap (heap[index], heap[parent]); index = parent; } else { break ; } } } void heapifyDown (int index) { int size = heap.size (); while (2 * index + 1 < size) { int leftChild = 2 * index + 1 ; int rightChild = 2 * index + 2 ; int largerChild = leftChild; if (rightChild < size && heap[rightChild] > heap[leftChild]) { largerChild = rightChild; } if (heap[index] < heap[largerChild]) { swap (heap[index], heap[largerChild]); index = largerChild; } else { break ; } } } public : void push (int val) { heap.push_back (val); heapifyUp (heap.size () - 1 ); } int top () const { if (heap.empty ()) throw runtime_error ("PriorityQueue is empty" ); return heap[0 ]; } void pop () { if (heap.empty ()) throw runtime_error ("PriorityQueue is empty" ); heap[0 ] = heap.back (); heap.pop_back (); heapifyDown (0 ); } bool empty () const { return heap.empty (); } }; int main () { PriorityQueue pq; pq.push (10 ); pq.push (20 ); pq.push (15 ); cout << "Top Element: " << pq.top () << endl; pq.pop (); cout << "Top Element after pop: " << pq.top () << endl; return 0 ; }
代码解释
push
方法 :将新元素插入堆的末尾,之后调用 heapifyUp
进行上浮调整,保证堆的最大性质。
top
方法 :返回堆顶元素,即优先级最高的元素(最大值)。
pop
方法 :删除堆顶元素,将堆的最后一个元素移到堆顶,并调用 heapifyDown
进行下沉调整,保证堆的最大性质。
heapifyUp
和 heapifyDown
方法 :维护堆的性质,分别用于元素插入和删除后的调整操作。
二叉排序树 二叉排序树 (Binary Search Tree, BST)是一种特殊的二叉树,用于快速查找、插入和删除操作。BST 在很多应用中用于构建集合和字典。
二叉排序树的概念
二叉排序树 是一种有序的二叉树。对于 BST 中的每个节点 N
,满足以下性质:
左子树 所有节点的值小于 N
节点的值。
右子树 所有节点的值大于 N
节点的值。
左右子树也是二叉排序树。
二叉排序树的性质
查找、插入和删除操作 的时间复杂度平均为 O(log n)
,最坏情况下为 O(n)
,当 BST 退化为链表(即每个节点只有一个子节点)时会达到最坏情况。
中序遍历 可以得到一个升序的序列。
二叉排序树不允许有重复的节点。
二叉排序树的简洁 C++ 实现 以下是一个简单的 C++ 实现,包括插入、查找和删除操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <iostream> using namespace std;struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode (int x) : val (x), left (nullptr ), right (nullptr ) {} }; class BinarySearchTree {public : TreeNode* insert (TreeNode* root, int val) { if (!root) return new TreeNode (val); if (val < root->val) root->left = insert (root->left, val); else if (val > root->val) root->right = insert (root->right, val); return root; } bool search (TreeNode* root, int val) { if (!root) return false ; if (val == root->val) return true ; else if (val < root->val) return search (root->left, val); else return search (root->right, val); } TreeNode* remove (TreeNode* root, int val) { if (!root) return nullptr ; if (val < root->val) root->left = remove (root->left, val); else if (val > root->val) root->right = remove (root->right, val); else { if (!root->left) { TreeNode* rightChild = root->right; delete root; return rightChild; } else if (!root->right) { TreeNode* leftChild = root->left; delete root; return leftChild; } else { TreeNode* minNode = getMin (root->right); root->val = minNode->val; root->right = remove (root->right, minNode->val); } } return root; } TreeNode* getMin (TreeNode* root) { while (root->left) root = root->left; return root; } void inorder (TreeNode* root) { if (root) { inorder (root->left); cout << root->val << " " ; inorder (root->right); } } }; int main () { BinarySearchTree bst; TreeNode* root = nullptr ; root = bst.insert (root, 50 ); bst.insert (root, 30 ); bst.insert (root, 20 ); bst.insert (root, 40 ); bst.insert (root, 70 ); bst.insert (root, 60 ); bst.insert (root, 80 ); cout << "Inorder traversal: " ; bst.inorder (root); cout << endl; cout << "Search for 40: " << (bst.search (root, 40 ) ? "Found" : "Not found" ) << endl; root = bst.remove (root, 20 ); cout << "Inorder traversal after deleting 20: " ; bst.inorder (root); cout << endl; return 0 ; }
代码解释
插入节点 :递归地找到正确的插入位置,将节点添加到左子树或右子树。
查找节点 :根据值在左子树或右子树中递归查找。
删除节点 :
如果要删除的节点没有子节点,直接删除。
如果只有一个子节点,用子节点替换当前节点。
如果有两个子节点,用右子树中最小的节点替换当前节点,然后删除替换节点。
中序遍历 :按照左子树 -> 根节点 -> 右子树的顺序遍历节点,输出排序后的值。
平衡二叉树 平衡二叉树 (Balanced Binary Tree)是一种改进的二叉搜索树,它保证树的高度保持平衡,从而避免最坏情况下退化为链表。常见的平衡二叉树包括 AVL树 和 红黑树 。这里以 AVL树 为例,来介绍平衡二叉树的概念、性质以及实现。
平衡二叉树的概念和性质
平衡因子 :每个节点的左子树和右子树的高度差不超过1,称为平衡二叉树。具体来说,对于 AVL 树,任一节点的平衡因子(左子树高度减去右子树高度的差)只能是 -1、0 或 1。
自平衡 :在插入或删除节点后,AVL 树会通过旋转操作(如单旋转、双旋转)来恢复平衡。
查找、插入和删除操作的时间复杂度 为 (O(\log n)),这是因为 AVL 树总是保持平衡,树的高度始终为 (O(\log n))。
AVL 树的旋转操作
左旋 :右子树高度大于左子树时,将右子节点旋转为根节点。
右旋 :左子树高度大于右子树时,将左子节点旋转为根节点。
双旋转 :当插入节点破坏平衡因子后,先进行一次左旋或右旋,再进行一次右旋或左旋。
AVL 树的简洁 C++ 实现 以下是一个包含插入操作的简单 AVL 树实现代码。插入操作后会进行平衡调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <iostream> #include <algorithm> using namespace std;struct TreeNode { int val; TreeNode* left; TreeNode* right; int height; TreeNode (int x) : val (x), left (nullptr ), right (nullptr ), height (1 ) {} }; class AVLTree {public : int getHeight (TreeNode* node) { return node ? node->height : 0 ; } int getBalance (TreeNode* node) { return node ? getHeight (node->left) - getHeight (node->right) : 0 ; } TreeNode* rightRotate (TreeNode* y) { TreeNode* x = y->left; TreeNode* T2 = x->right; x->right = y; y->left = T2; y->height = max (getHeight (y->left), getHeight (y->right)) + 1 ; x->height = max (getHeight (x->left), getHeight (x->right)) + 1 ; return x; } TreeNode* leftRotate (TreeNode* x) { TreeNode* y = x->right; TreeNode* T2 = y->left; y->left = x; x->right = T2; x->height = max (getHeight (x->left), getHeight (x->right)) + 1 ; y->height = max (getHeight (y->left), getHeight (y->right)) + 1 ; return y; } TreeNode* insert (TreeNode* node, int val) { if (!node) return new TreeNode (val); if (val < node->val) node->left = insert (node->left, val); else if (val > node->val) node->right = insert (node->right, val); else return node; node->height = 1 + max (getHeight (node->left), getHeight (node->right)); int balance = getBalance (node); if (balance > 1 && val < node->left->val) return rightRotate (node); if (balance < -1 && val > node->right->val) return leftRotate (node); if (balance > 1 && val > node->left->val) { node->left = leftRotate (node->left); return rightRotate (node); } if (balance < -1 && val < node->right->val) { node->right = rightRotate (node->right); return leftRotate (node); } return node; } void inorder (TreeNode* root) { if (root) { inorder (root->left); cout << root->val << " " ; inorder (root->right); } } }; int main () { AVLTree avl; TreeNode* root = nullptr ; root = avl.insert (root, 10 ); root = avl.insert (root, 20 ); root = avl.insert (root, 30 ); root = avl.insert (root, 40 ); root = avl.insert (root, 50 ); root = avl.insert (root, 25 ); cout << "Inorder traversal of AVL tree: " ; avl.inorder (root); cout << endl; return 0 ; }
代码解释
getHeight 和 getBalance :用于计算节点的高度和平衡因子。
右旋和左旋操作 :用于在失衡时恢复平衡。
insert 操作 :按二叉搜索树的规则插入新节点,并在每次插入后检查平衡性并进行必要的旋转操作。
inorder 遍历 :用于输出 AVL 树中节点的值,验证树结构的正确性。
哈夫曼(Huffman)树和哈夫曼编码 哈夫曼树 (Huffman Tree)和哈夫曼编码 (Huffman Coding)是数据压缩领域的重要概念。它们通过使用变长编码表对频率较高的数据分配较短的编码,从而有效地减少数据的存储空间。
哈夫曼树的概念
哈夫曼树 是一种带权路径长度最短的二叉树,用于实现最优前缀编码。通常用于无损数据压缩。
在哈夫曼树中,频率较高的字符会被分配较短的编码,而频率较低的字符会被分配较长的编码,从而使整体编码长度最小化。
哈夫曼编码的概念
哈夫曼编码 是一种基于哈夫曼树的前缀编码。每个字符用一串二进制数字表示,且不同字符的编码无公共前缀(前缀编码),避免了编码歧义。
在构建哈夫曼树后,从根到每个叶节点的路径形成该节点的编码,左子树为 0
,右子树为 1
。
哈夫曼树的性质
最优编码 :哈夫曼编码是无损压缩的最优编码方式之一,可有效减少存储空间。
前缀编码 :无公共前缀,避免编码歧义。
构造复杂度 :哈夫曼树构建过程的时间复杂度为 (O(n \log n)),其中 (n) 是字符的种类数。
无公共前缀 的意思是,在一个编码系统中,任何一个字符的编码都不是另一个字符编码的开头部分(前缀)。在这种情况下,解码时不会有歧义,因为每个编码是唯一且独立的。
在哈夫曼编码中,这种“无公共前缀”性质被称为前缀码 (Prefix Code)。具体来说:
例如,如果字符 A 的编码是 0
,字符 B 的编码是 10
,字符 C 的编码是 110
,字符 D 的编码是 111
,那么这些编码没有公共前缀,因为:
0
不会是其他编码(10
, 110
, 111
)的前缀。
10
也不会是 110
或 111
的前缀。
这样可以确保唯一解码 ,即每当我们解码时,遇到编码 0
时立即知道是 A,遇到 10
时就是 B,以此类推。
哈夫曼树和哈夫曼编码的C++实现 以下是一个简洁的C++实现,使用最小堆来构建哈夫曼树,并生成哈夫曼编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include <iostream> #include <unordered_map> #include <vector> #include <queue> #include <string> using namespace std;struct HuffmanNode { char ch; int freq; HuffmanNode* left; HuffmanNode* right; HuffmanNode (char c, int f) : ch (c), freq (f), left (nullptr ), right (nullptr ) {} }; struct Compare { bool operator () (HuffmanNode* a, HuffmanNode* b) { return a->freq > b->freq; } }; void generateCodes (HuffmanNode* root, string code, unordered_map<char , string>& huffmanCode) { if (!root) return ; if (!root->left && !root->right) { huffmanCode[root->ch] = code; } generateCodes (root->left, code + "0" , huffmanCode); generateCodes (root->right, code + "1" , huffmanCode); } void freeTree (HuffmanNode* root) { if (!root) return ; freeTree (root->left); freeTree (root->right); delete root; } unordered_map<char , string> buildHuffmanTree (const unordered_map<char , int >& freq) { priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> minHeap; for (const auto & pair : freq) { minHeap.push (new HuffmanNode (pair.first, pair.second)); } while (minHeap.size () > 1 ) { HuffmanNode* left = minHeap.top (); minHeap.pop (); HuffmanNode* right = minHeap.top (); minHeap.pop (); HuffmanNode* sum = new HuffmanNode ('\0' , left->freq + right->freq); sum->left = left; sum->right = right; minHeap.push (sum); } HuffmanNode* root = minHeap.top (); unordered_map<char , string> huffmanCode; generateCodes (root, "" , huffmanCode); freeTree (root); return huffmanCode; } int main () { unordered_map<char , int > freq = { {'a' , 5 }, {'b' , 9 }, {'c' , 12 }, {'d' , 13 }, {'e' , 16 }, {'f' , 45 } }; unordered_map<char , string> huffmanCode = buildHuffmanTree (freq); cout << "Huffman Codes:\n" ; for (const auto & pair : huffmanCode) { cout << pair.first << " : " << pair.second << endl; } return 0 ; }
代码解释
HuffmanNode :定义哈夫曼树的节点结构体,包含字符、频率及左右子节点。
Compare 函数对象 :用于最小堆比较,以频率为基础构建最小堆。
buildHuffmanTree :构建哈夫曼树,通过合并最小频率的节点,最终形成一棵树。
generateCodes :递归生成编码,通过树的路径生成每个字符的二进制编码。
main 函数 :定义字符频率,调用 buildHuffmanTree
构建哈夫曼树并生成编码,最后输出编码结果。
输出示例 假设频率表为 {'a': 5, 'b': 9, 'c': 12, 'd': 13, 'e': 16, 'f': 45}
,输出结果可能如下:1 2 3 4 5 6 7 Huffman Codes: a : 1100 b : 1101 c : 100 d : 101 e : 111 f : 0
代码说明
通过哈夫曼编码,字符 ‘f’ 频率最高,编码最短,而频率较低的字符 ‘a’ 和 ‘b’ 编码更长,从而达到压缩效果。