赞
踩
树是一种层次结构非线性的数据结构,其是由节点和边组成,可以用来表示层次关系的数据。
树的相关概念
- 节点:树的基本组成单位,每个节点都包含数据,同时与其他节点相互连接
- 根节点:树的顶层节点,没有父节点
- 子节点:由一个节点直接连接的下层节点
- 父节点:直接连接子节点的上层节点
- 叶子节点:没有子节点的就是叶子节点
- 内部节点:至少有一个子节点的节点
- 兄弟节点:具有同一个父节点的节点
- 路径:从一个节点到另外一个节点所经过的节点路径
- 深度:节点到根节点的路径长度
- 高度:节点到叶子节点的最长路径长度
- 度:节点的子节点数
- 子树:由某个节点机器所有后代节点组成的树
二叉树是一种特殊的树结构,其每个节点最多有两个子节点,也就是左子节点和右子节点,二叉树可以为空(没有节点,也可以只有一个根节点)。
重要性质总结
节点数与层数:
- 二叉树第iii层上最多有 2i−12^{i-1}2i−1 个节点。
- 深度为 hhh 的二叉树最多有 2h−12^h - 12h−1 个节点。
满二叉树:
- 深度为 hhh 且有 2h−12^h - 12h−1 个节点的二叉树称为满二叉树。
- 满二叉树中每一层的节点数都达到最大。
完全二叉树:
- 深度为 hhh 且有 nnn 个节点的二叉树,当且仅当其每一个节点都与深度为 hhh 的满二叉树中编号为 1 到 nnn 的节点对应时,称其为完全二叉树。
- 完全二叉树除了最后一层外,每层都是满的,且最后一层的节点从左到右依次排列。
节点的度:
- 二叉树中度为 0 的节点(即叶节点)的数量为 n0n_0n0。
- 度为 2 的节点数量为 n2n_2n2。
- 对于一个二叉树,满足关系 n0=n2+1n_0 = n_2 + 1n0=n2+1。
二叉树的遍历:
- 前序遍历(Pre-order Traversal):先访问根节点,再访问左子树,最后访问右子树。
- 中序遍历(In-order Traversal):先访问左子树,再访问根节点,最后访问右子树。
- 后序遍历(Post-order Traversal):先访问左子树,再访问右子树,最后访问根节点。
- 层次遍历(Level-order Traversal):按层次从上到下,从左到右依次访问各节点。
子树:
- 每个节点的左子树和右子树也是二叉树。
树的高度:
- 二叉树的高度是从根节点到叶节点的最长路径的节点数。
平衡二叉树:
- 一种特殊的二叉树,任何一个节点的左右子树的高度差不超过 1。常见的平衡二叉树包括 AVL 树和红黑树。
链式存储结构:一个节点中有一个表示数据的,以及左右节点指针
- #include<iostream>
-
- struct TreeNode
- {
- int data;
- TreeNode* left;
- TreeNode* right;
-
- TreeNode(int value):data(value),left(nullptr),right(nullptr){}
- };
-
- TreeNode* CreateTree()
- {
- TreeNode* root = new TreeNode(1);
- root->left = new TreeNode(2);
- root->right = new TreeNode(3);
- root->left->left = new TreeNode(4);
- root->left->right = new TreeNode(5);
- root->right->left = new TreeNode(6);
- root->right->right = new TreeNode(7);
- return root;
- }
-
- void prevOrderTree(TreeNode* root)
- {
- if (root == nullptr) return;
- std::cout << root->data << " ";
- prevOrderTree(root->left);
- prevOrderTree(root->right);
- }
-
- int main()
- {
- TreeNode* root = CreateTree();
- prevOrderTree(root);
- std::cout << std::endl;
- return 0;
- }
顺序存储结构:使用一个数组进行存储,针对于第i个节点,2i位置是左子树,2i+1节点是右子树(基本不用,只适合完全静态的树)
- #include <iostream>
- #include <vector>
- #include <cmath>
-
- std::vector<int> createBinaryTreeArray() {
- // 假设二叉树的深度为3
- std::vector<int> tree(8); // 大小为2^3 = 8,index从0开始
- tree[1] = 1; // 根节点
- tree[2] = 2; // 左子节点
- tree[3] = 3; // 右子节点
- tree[4] = 4; // 左子节点的左子节点
- tree[5] = 5; // 左子节点的右子节点
- tree[6] = 6; // 右子节点的左子节点
- tree[7] = 7; // 右子节点的右子节点
- return tree;
- }
-
- void preOrderTraversalArray(const std::vector<int>& tree, int index) {
- if (index >= tree.size() || tree[index] == 0) return;
- std::cout << tree[index] << " ";
- preOrderTraversalArray(tree, 2 * index); // 左子节点
- preOrderTraversalArray(tree, 2 * index + 1); // 右子节点
- }
-
- int main() {
- std::vector<int> tree = createBinaryTreeArray();
- std::cout << "Pre-order traversal: ";
- preOrderTraversalArray(tree, 1); // 从根节点开始遍历
- std::cout << std::endl;
- return 0;
- }
总结
- 前序遍历:根--左--右
- 后序遍历:左--右--根
- 中序遍历:左--根--右
- #include<iostream>
-
- struct TreeNode
- {
- int data;
- TreeNode* left;
- TreeNode* right;
-
- TreeNode(int value):data(value),left(nullptr),right(nullptr){}
- };
-
- TreeNode* CreateTree()
- {
- TreeNode* root = new TreeNode(1);
- root->left = new TreeNode(2);
- root->right = new TreeNode(3);
- root->left->left = new TreeNode(4);
- root->left->right = new TreeNode(5);
- root->right->left = new TreeNode(6);
- root->right->right = new TreeNode(7);
- return root;
- }
-
- void prevOrderTree(TreeNode* root)
- {
- if (root == nullptr) return;
- std::cout << root->data << " ";
- prevOrderTree(root->left);
- prevOrderTree(root->right);
- }
-
- void inOrderTree(TreeNode* root)
- {
- if (root == nullptr) return;
- inOrderTree(root->left);
- std::cout << root->data << " ";
- inOrderTree(root->right);
- }
-
- void postOrderTree(TreeNode* root)
- {
- if (root == nullptr) return;
- postOrderTree(root->left);
- postOrderTree(root->right);
- std::cout << root->data << " ";
- }
-
- int main()
- {
- TreeNode* root = CreateTree();
- std::cout << "前序遍历:";
- prevOrderTree(root);
- std::cout << std::endl;
-
- std::cout << "中序遍历:";
- inOrderTree(root);
- std::cout << std::endl;
-
- std::cout << "后序遍历:";
- postOrderTree(root);
- std::cout << std::endl;
- return 0;
- }
实现方式:借助队列实现
- 初始化队列,将根节点的数值放入进去
- 设置循环直到队列为空: 队列中提取并出队,然后分别放入左右的数值进行循环判断
- #include<iostream>
- #include<queue>
-
- struct TreeNode
- {
- int data;
- TreeNode* left;
- TreeNode* right;
-
- TreeNode(int value):data(value),left(nullptr),right(nullptr){}
- };
-
- TreeNode* CreateTree()
- {
- TreeNode* root = new TreeNode(1);
- root->left = new TreeNode(2);
- root->right = new TreeNode(3);
- root->left->left = new TreeNode(4);
- root->left->right = new TreeNode(5);
- root->right->left = new TreeNode(6);
- root->right->right = new TreeNode(7);
- return root;
- }
-
- void levelOrderTree(TreeNode* root)
- {
- if (!root) return;
-
- std::queue<TreeNode*>q;
- q.push(root);
-
- while (!q.empty())
- {
- TreeNode* tem = q.front();
- q.pop();
- std::cout << tem->data << " ";
-
- if (tem->left) q.push(tem->left);
- if (tem->right) q.push(tem->right);
- }
- }
-
- int main()
- {
- TreeNode* root = CreateTree();
- levelOrderTree(root);
- std::cout << std::endl;
- return 0;
- }
- 通常借助数组来存储
- 父节点下标:(i -1)/2
- 左孩子:2i + 1
- 右孩子:2i + 2
- 堆是一颗完全二叉树,除了最后一层,所有层都是满的,并且最后一层的节点是从左往右依次排列
- 堆主要有两种表现形式
- 大堆:每个节点的值都大于等于其子节点的值
- 小堆:每个节点的值都小于等于其节点的值
向上调整
- 插入新元素时,将新元素放入到堆的末尾
- 从新插入元素的下标(堆的末尾),检查父节点是否比自己大或者小(根据大小堆来决定,大堆的话则是当前元素比父元素大就交换)
- 如果大于或者小于父节点,则交换两个节点,然后当前下标更新成为父元素的下标,继续检查
- 重复此过程,直到当前元素不大于父元素,或者已经到达栈顶
- 事例代码(建大堆)
- 场景:当插入新元素的时候使用向上调整,因为新元素是插入到数组尾部
- //向上调整
- void AdjuestUp(int index)
- {
- while (index > 0 && heap[index] > heap[(index - 1) / 2])
- {
- std::swap(heap[index], heap[(index - 1) / 2]);
- index = (index - 1) / 2;
- }
- }
向下调整
- 从堆中删除第一个元素(大堆就是删除最大元素,小堆则是最小)下面叙述按照大堆逻辑。将堆的最后一个元素移动到堆顶
- 从堆顶开始,找到当前节点、左孩子、右孩子三个节点的最大值
- 如果当前节点不是最大值,则将其与最大值节点交换,并更新当前索引为最大子节点索引,继续检查
- 重复上述过程,直到当前节点大于或者等于其子节点,或者已经到达堆的底部
- 使用场景:删除元素的时候
- 代码事例,大堆的向下调整
- //改写法适合排序堆排使用
-
- void AdjustDown(vector<int>& arr, int n, int i)
- {
- int largest = i;
- int left = 2 * i + 1, right = 2 * i + 2;
-
- if (left<n && arr[left]>arr[largest]) largest = left;
- if (right<n && arr[right]>arr[largest]) largest = right;
-
- if (largest != i)
- {
- swap(arr[i], arr[largest]);
- AdjustDown(arr, n, largest);
- }
- }
- //向下调整
- void AdjuestDown(int index)
- {
- int size = heap.size();
- while (index * 2 + 1 < size)
- {
- int largest = index;
- int leftChild = index * 2 + 1;
- int rightChild = index * 2 + 2;
-
- //找到是三个节点的最大值
- if (leftChild < size && heap[leftChild]>heap[largest])
- {
- largest = leftChild;
- }
- if (rightChild<size && heap[rightChild]>heap[largest])
- {
- largest = rightChild;
- }
-
- //如果最大节点不是当前节点,则交换最大值,然后继续向下调整
- if (largest != index)
- {
- std::swap(heap[index], heap[largest]);
- index = largest;
- }
- else
- {
- break;
- }
- }
- }
堆操作相关时间复杂度分析
插入操作(Insert)
- 时间复杂度:O(log n)
- 插入一个新元素时,首先将其添加到堆的末尾,然后通过向上调整(heapify up)使堆恢复最大堆或最小堆的性质。向上调整的过程最多需要经过堆的高度层,而堆的高度是 O(logn)O(log n)O(logn)。
删除堆顶元素(Extract Max / Extract Min)
- 时间复杂度:O(log n)
- 删除堆顶元素(即最大堆中的最大元素或最小堆中的最小元素)时,将堆的最后一个元素移动到堆顶,然后通过向下调整(heapify down)使堆恢复堆的性质。向下调整的过程最多需要经过堆的高度层,时间复杂度也是 O(logn)O(log n)O(logn)。
获取堆顶元素(Get Max / Get Min)
- 时间复杂度:O(1)
- 获取堆顶元素不需要进行任何调整操作,只需要返回堆数组的第一个元素即可,因此时间复杂度是 O(1)O(1)O(1)。
建堆操作(Build Heap)
- 时间复杂度:O(n)
- 从一个无序数组构建一个堆可以通过自底向上的方法进行,即从最后一个非叶子节点开始向下调整(heapify down)。这种方法的总时间复杂度是 O(n)O(n)O(n),而不是 O(nlogn)O(n \log n)O(nlogn),因为每个节点向下调整的平均次数比堆的高度要少。
堆排序(Heap Sort)
- 时间复杂度:O(n \log n)
- 堆排序包括两个主要步骤:首先将无序数组构建成一个堆(时间复杂度 O(n)O(n)O(n)),然后重复地提取堆顶元素并调整堆(每次提取和调整的时间复杂度是 O(logn)O(log n)O(logn))。因此,总的时间复杂度是 O(n)+O(nlogn)=O(nlogn)O(n) + O(n \log n) = O(n \log n)O(n)+O(nlogn)=O(nlogn)。
思路总结(以大堆为例分析)
- 构建大堆,将数组转化成为堆
- 交换堆顶元素与末尾元素,同时缩小堆的范围,对堆顶元素进行向下调整,直到数组排序完成
- #include<iostream>
- #include<vector>
- #include<algorithm>
-
- using namespace std;
-
- void AdjustDown(vector<int>& arr, int n, int i)
- {
- int largest = i;
- int left = 2 * i + 1, right = 2 * i + 2;
-
- if (left<n && arr[left]>arr[largest]) largest = left;
- if (right<n && arr[right]>arr[largest]) largest = right;
-
- if (largest != i)
- {
- swap(arr[i], arr[largest]);
- AdjustDown(arr, n, largest);
- }
- }
-
- void heapSort(vector<int>& arr)
- {
- int n = arr.size();
-
- //构建大堆(找父节点然后调整)
- for (int i = n / 2 - 1; i >= 0; i--)
- {
- AdjustDown(arr, n, i);
- }
-
- //交换头尾元素,然后再次向下调整
- for (int i = n - 1; i > 0; i--)
- {
- swap(arr[0], arr[i]);
- AdjustDown(arr, i, 0);
- }
- }
-
- int main()
- {
- std::vector<int> arr{ 12,89,5,4,66,82,56,99,1 };
- cout << "begin arr:";
- for (auto& ch : arr) {
- cout << ch << " ";
- }
- cout << endl;
-
- heapSort(arr);
- cout << "after sort :";
- for (auto& ch : arr)
- {
- cout << ch << " ";
- }
- }
插入与删除实现
- 插入:末尾插入新元素,然后向上调整
- 删除:删除头部元素,尾部元素填充头部元素,然后向下调整
- 下述代码(插入、删除、向上调整、向下调整整体实现)
- #include<iostream>
- #include<vector>
- #include<algorithm>
-
- class MaxHeap
- {
- private:
- std::vector<int> heap;
-
- //向上调整
- void AdjuestUp(int index)
- {
- while (index > 0 && heap[index] > heap[(index - 1) / 2])
- {
- std::swap(heap[index], heap[(index - 1) / 2]);
- index = (index - 1) / 2;
- }
- }
-
- //向下调整
- void AdjuestDown(int index)
- {
- int size = heap.size();
- while (index * 2 + 1 < size)
- {
- int largest = index;
- int leftChild = index * 2 + 1;
- int rightChild = index * 2 + 2;
-
- //找到是三个节点的最大值
- if (leftChild < size && heap[leftChild]>heap[largest])
- {
- largest = leftChild;
- }
- if (rightChild<size && heap[rightChild]>heap[largest])
- {
- largest = rightChild;
- }
-
- //如果最大节点不是当前节点,则交换最大值,然后继续向下调整
- if (largest != index)
- {
- std::swap(heap[index], heap[largest]);
- index = largest;
- }
- else
- {
- break;
- }
- }
- }
实现思路总结
- 问题回顾:TopK就是从一组数据中找出前K大或者前K小的元素
- 思路(通过小根堆解决)
- 维护一个大小为K的小根堆
- 遍历全部的数据,将每个元素与栈顶元素(也就是最小值)比较
- 如果当前元素大于栈顶元素,则替换掉栈顶元素,然后重新调整堆
- 否则的话就继续遍历下一个元素
- 事例(前K大的元素,因为小根堆的首元素永远都是最小的元素)
- #include<iostream>
- #include<queue>
- #include<vector>
-
- using namespace std;
-
- vector<int> topKElements(vector<int>& data, int k)
- {
- if (k <= 0) return {};
- //利用比较器实现小堆
- priority_queue<int, vector<int>, greater<int>> minheap;
-
- //遍历data中的全部属于,没添加满就继续添加,添加满了就比较堆顶元素
- for (int num : data)
- {
- if (minheap.size() < k)
- {
- minheap.push(num);
- }
- else if (num > minheap.top())
- {
- minheap.pop();
- minheap.push(num);
- }
- }
-
- vector<int>ret;
- while (!minheap.empty())
- {
- ret.push_back(minheap.top());
- minheap.pop();
- }
- return ret;
- }
-
- int main() {
- vector<int> data = { 3, 1, 5, 12, 2, 11, 10, 7, 6 };
- int k = 3;
- vector<int> result = topKElements(data, k);
-
- cout << "Top " << k << " elements: ";
- for (int num : result) {
- cout << num << " ";
- }
- cout << endl;
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。