当前位置:   article > 正文

数据结构 二叉树和堆总结

数据结构 二叉树和堆总结

概念

树是一种层次结构非线性的数据结构,其是由节点和边组成,可以用来表示层次关系的数据。

树的相关概念

  • 节点:树的基本组成单位,每个节点都包含数据,同时与其他节点相互连接
  • 根节点:树的顶层节点,没有父节点
  • 子节点:由一个节点直接连接的下层节点
  • 父节点:直接连接子节点的上层节点
  • 叶子节点:没有子节点的就是叶子节点
  • 内部节点:至少有一个子节点的节点
  • 兄弟节点:具有同一个父节点的节点
  • 路径:从一个节点到另外一个节点所经过的节点路径
  • 深度:节点到根节点的路径长度
  • 高度:节点到叶子节点的最长路径长度
  • 度:节点的子节点数
  • 子树:由某个节点机器所有后代节点组成的树

二叉树

概念

二叉树是一种特殊的树结构,其每个节点最多有两个子节点,也就是左子节点和右子节点,二叉树可以为空(没有节点,也可以只有一个根节点)。

二叉树性质

重要性质总结

  • 节点数与层数

    • 二叉树第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 树和红黑树。

二叉树存储结构

链式存储结构:一个节点中有一个表示数据的,以及左右节点指针

  1. #include<iostream>
  2. struct TreeNode
  3. {
  4. int data;
  5. TreeNode* left;
  6. TreeNode* right;
  7. TreeNode(int value):data(value),left(nullptr),right(nullptr){}
  8. };
  9. TreeNode* CreateTree()
  10. {
  11. TreeNode* root = new TreeNode(1);
  12. root->left = new TreeNode(2);
  13. root->right = new TreeNode(3);
  14. root->left->left = new TreeNode(4);
  15. root->left->right = new TreeNode(5);
  16. root->right->left = new TreeNode(6);
  17. root->right->right = new TreeNode(7);
  18. return root;
  19. }
  20. void prevOrderTree(TreeNode* root)
  21. {
  22. if (root == nullptr) return;
  23. std::cout << root->data << " ";
  24. prevOrderTree(root->left);
  25. prevOrderTree(root->right);
  26. }
  27. int main()
  28. {
  29. TreeNode* root = CreateTree();
  30. prevOrderTree(root);
  31. std::cout << std::endl;
  32. return 0;
  33. }

顺序存储结构:使用一个数组进行存储,针对于第i个节点,2i位置是左子树,2i+1节点是右子树(基本不用,只适合完全静态的树)

  1. #include <iostream>
  2. #include <vector>
  3. #include <cmath>
  4. std::vector<int> createBinaryTreeArray() {
  5. // 假设二叉树的深度为3
  6. std::vector<int> tree(8); // 大小为2^3 = 8,index从0开始
  7. tree[1] = 1; // 根节点
  8. tree[2] = 2; // 左子节点
  9. tree[3] = 3; // 右子节点
  10. tree[4] = 4; // 左子节点的左子节点
  11. tree[5] = 5; // 左子节点的右子节点
  12. tree[6] = 6; // 右子节点的左子节点
  13. tree[7] = 7; // 右子节点的右子节点
  14. return tree;
  15. }
  16. void preOrderTraversalArray(const std::vector<int>& tree, int index) {
  17. if (index >= tree.size() || tree[index] == 0) return;
  18. std::cout << tree[index] << " ";
  19. preOrderTraversalArray(tree, 2 * index); // 左子节点
  20. preOrderTraversalArray(tree, 2 * index + 1); // 右子节点
  21. }
  22. int main() {
  23. std::vector<int> tree = createBinaryTreeArray();
  24. std::cout << "Pre-order traversal: ";
  25. preOrderTraversalArray(tree, 1); // 从根节点开始遍历
  26. std::cout << std::endl;
  27. return 0;
  28. }

前中后序遍历

总结

  • 前序遍历:根--左--右
  • 后序遍历:左--右--根
  • 中序遍历:左--根--右

  1. #include<iostream>
  2. struct TreeNode
  3. {
  4. int data;
  5. TreeNode* left;
  6. TreeNode* right;
  7. TreeNode(int value):data(value),left(nullptr),right(nullptr){}
  8. };
  9. TreeNode* CreateTree()
  10. {
  11. TreeNode* root = new TreeNode(1);
  12. root->left = new TreeNode(2);
  13. root->right = new TreeNode(3);
  14. root->left->left = new TreeNode(4);
  15. root->left->right = new TreeNode(5);
  16. root->right->left = new TreeNode(6);
  17. root->right->right = new TreeNode(7);
  18. return root;
  19. }
  20. void prevOrderTree(TreeNode* root)
  21. {
  22. if (root == nullptr) return;
  23. std::cout << root->data << " ";
  24. prevOrderTree(root->left);
  25. prevOrderTree(root->right);
  26. }
  27. void inOrderTree(TreeNode* root)
  28. {
  29. if (root == nullptr) return;
  30. inOrderTree(root->left);
  31. std::cout << root->data << " ";
  32. inOrderTree(root->right);
  33. }
  34. void postOrderTree(TreeNode* root)
  35. {
  36. if (root == nullptr) return;
  37. postOrderTree(root->left);
  38. postOrderTree(root->right);
  39. std::cout << root->data << " ";
  40. }
  41. int main()
  42. {
  43. TreeNode* root = CreateTree();
  44. std::cout << "前序遍历:";
  45. prevOrderTree(root);
  46. std::cout << std::endl;
  47. std::cout << "中序遍历:";
  48. inOrderTree(root);
  49. std::cout << std::endl;
  50. std::cout << "后序遍历:";
  51. postOrderTree(root);
  52. std::cout << std::endl;
  53. return 0;
  54. }

 层序遍历

实现方式:借助队列实现

  • 初始化队列,将根节点的数值放入进去
  • 设置循环直到队列为空: 队列中提取并出队,然后分别放入左右的数值进行循环判断

  1. #include<iostream>
  2. #include<queue>
  3. struct TreeNode
  4. {
  5. int data;
  6. TreeNode* left;
  7. TreeNode* right;
  8. TreeNode(int value):data(value),left(nullptr),right(nullptr){}
  9. };
  10. TreeNode* CreateTree()
  11. {
  12. TreeNode* root = new TreeNode(1);
  13. root->left = new TreeNode(2);
  14. root->right = new TreeNode(3);
  15. root->left->left = new TreeNode(4);
  16. root->left->right = new TreeNode(5);
  17. root->right->left = new TreeNode(6);
  18. root->right->right = new TreeNode(7);
  19. return root;
  20. }
  21. void levelOrderTree(TreeNode* root)
  22. {
  23. if (!root) return;
  24. std::queue<TreeNode*>q;
  25. q.push(root);
  26. while (!q.empty())
  27. {
  28. TreeNode* tem = q.front();
  29. q.pop();
  30. std::cout << tem->data << " ";
  31. if (tem->left) q.push(tem->left);
  32. if (tem->right) q.push(tem->right);
  33. }
  34. }
  35. int main()
  36. {
  37. TreeNode* root = CreateTree();
  38. levelOrderTree(root);
  39. std::cout << std::endl;
  40. return 0;
  41. }

存储方式

  • 通常借助数组来存储
    • 父节点下标:(i -1)/2
    • 左孩子:2i + 1
    • 右孩子:2i + 2

概念

  • 堆是一颗完全二叉树,除了最后一层,所有层都是满的,并且最后一层的节点是从左往右依次排列
  • 堆主要有两种表现形式
    • 大堆:每个节点的值都大于等于其子节点的值
    • 小堆:每个节点的值都小于等于其节点的值

向上调整与向下调整

向上调整

  • 插入新元素时,将新元素放入到堆的末尾
  • 从新插入元素的下标(堆的末尾),检查父节点是否比自己大或者小(根据大小堆来决定,大堆的话则是当前元素比父元素大就交换)
  • 如果大于或者小于父节点,则交换两个节点,然后当前下标更新成为父元素的下标,继续检查
  • 重复此过程,直到当前元素不大于父元素,或者已经到达栈顶
  • 事例代码(建大堆)
  • 场景:当插入新元素的时候使用向上调整,因为新元素是插入到数组尾部
  1. //向上调整
  2. void AdjuestUp(int index)
  3. {
  4. while (index > 0 && heap[index] > heap[(index - 1) / 2])
  5. {
  6. std::swap(heap[index], heap[(index - 1) / 2]);
  7. index = (index - 1) / 2;
  8. }
  9. }

 向下调整

  • 从堆中删除第一个元素(大堆就是删除最大元素,小堆则是最小)下面叙述按照大堆逻辑。将堆的最后一个元素移动到堆顶
  • 从堆顶开始,找到当前节点、左孩子、右孩子三个节点的最大值
  • 如果当前节点不是最大值,则将其与最大值节点交换,并更新当前索引为最大子节点索引,继续检查
  • 重复上述过程,直到当前节点大于或者等于其子节点,或者已经到达堆的底部
  • 使用场景:删除元素的时候
  • 代码事例,大堆的向下调整
  1. //改写法适合排序堆排使用
  2. void AdjustDown(vector<int>& arr, int n, int i)
  3. {
  4. int largest = i;
  5. int left = 2 * i + 1, right = 2 * i + 2;
  6. if (left<n && arr[left]>arr[largest]) largest = left;
  7. if (right<n && arr[right]>arr[largest]) largest = right;
  8. if (largest != i)
  9. {
  10. swap(arr[i], arr[largest]);
  11. AdjustDown(arr, n, largest);
  12. }
  13. }
  1. //向下调整
  2. void AdjuestDown(int index)
  3. {
  4. int size = heap.size();
  5. while (index * 2 + 1 < size)
  6. {
  7. int largest = index;
  8. int leftChild = index * 2 + 1;
  9. int rightChild = index * 2 + 2;
  10. //找到是三个节点的最大值
  11. if (leftChild < size && heap[leftChild]>heap[largest])
  12. {
  13. largest = leftChild;
  14. }
  15. if (rightChild<size && heap[rightChild]>heap[largest])
  16. {
  17. largest = rightChild;
  18. }
  19. //如果最大节点不是当前节点,则交换最大值,然后继续向下调整
  20. if (largest != index)
  21. {
  22. std::swap(heap[index], heap[largest]);
  23. index = largest;
  24. }
  25. else
  26. {
  27. break;
  28. }
  29. }
  30. }

时间复杂度分析

堆操作相关时间复杂度分析

  • 插入操作(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(nlog⁡n)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(nlog⁡n)=O(nlog⁡n)O(n) + O(n \log n) = O(n \log n)O(n)+O(nlogn)=O(nlogn)。

堆排序

思路总结(以大堆为例分析)

  • 构建大堆,将数组转化成为堆
  • 交换堆顶元素与末尾元素,同时缩小堆的范围,对堆顶元素进行向下调整,直到数组排序完成

 

  1. #include<iostream>
  2. #include<vector>
  3. #include<algorithm>
  4. using namespace std;
  5. void AdjustDown(vector<int>& arr, int n, int i)
  6. {
  7. int largest = i;
  8. int left = 2 * i + 1, right = 2 * i + 2;
  9. if (left<n && arr[left]>arr[largest]) largest = left;
  10. if (right<n && arr[right]>arr[largest]) largest = right;
  11. if (largest != i)
  12. {
  13. swap(arr[i], arr[largest]);
  14. AdjustDown(arr, n, largest);
  15. }
  16. }
  17. void heapSort(vector<int>& arr)
  18. {
  19. int n = arr.size();
  20. //构建大堆(找父节点然后调整)
  21. for (int i = n / 2 - 1; i >= 0; i--)
  22. {
  23. AdjustDown(arr, n, i);
  24. }
  25. //交换头尾元素,然后再次向下调整
  26. for (int i = n - 1; i > 0; i--)
  27. {
  28. swap(arr[0], arr[i]);
  29. AdjustDown(arr, i, 0);
  30. }
  31. }
  32. int main()
  33. {
  34. std::vector<int> arr{ 12,89,5,4,66,82,56,99,1 };
  35. cout << "begin arr:";
  36. for (auto& ch : arr) {
  37. cout << ch << " ";
  38. }
  39. cout << endl;
  40. heapSort(arr);
  41. cout << "after sort :";
  42. for (auto& ch : arr)
  43. {
  44. cout << ch << " ";
  45. }
  46. }

堆的简单实现

插入与删除实现

  • 插入:末尾插入新元素,然后向上调整
  • 删除:删除头部元素,尾部元素填充头部元素,然后向下调整
  • 下述代码(插入、删除、向上调整、向下调整整体实现)
  1. #include<iostream>
  2. #include<vector>
  3. #include<algorithm>
  4. class MaxHeap
  5. {
  6. private:
  7. std::vector<int> heap;
  8. //向上调整
  9. void AdjuestUp(int index)
  10. {
  11. while (index > 0 && heap[index] > heap[(index - 1) / 2])
  12. {
  13. std::swap(heap[index], heap[(index - 1) / 2]);
  14. index = (index - 1) / 2;
  15. }
  16. }
  17. //向下调整
  18. void AdjuestDown(int index)
  19. {
  20. int size = heap.size();
  21. while (index * 2 + 1 < size)
  22. {
  23. int largest = index;
  24. int leftChild = index * 2 + 1;
  25. int rightChild = index * 2 + 2;
  26. //找到是三个节点的最大值
  27. if (leftChild < size && heap[leftChild]>heap[largest])
  28. {
  29. largest = leftChild;
  30. }
  31. if (rightChild<size && heap[rightChild]>heap[largest])
  32. {
  33. largest = rightChild;
  34. }
  35. //如果最大节点不是当前节点,则交换最大值,然后继续向下调整
  36. if (largest != index)
  37. {
  38. std::swap(heap[index], heap[largest]);
  39. index = largest;
  40. }
  41. else
  42. {
  43. break;
  44. }
  45. }
  46. }

TopK问题

实现思路总结

  • 问题回顾:TopK就是从一组数据中找出前K大或者前K小的元素
  • 思路(通过小根堆解决
    • 维护一个大小为K的小根堆
    • 遍历全部的数据,将每个元素与栈顶元素(也就是最小值)比较
      • 如果当前元素大于栈顶元素,则替换掉栈顶元素,然后重新调整堆
      • 否则的话就继续遍历下一个元素
  • 事例(前K大的元素,因为小根堆的首元素永远都是最小的元素)

  1. #include<iostream>
  2. #include<queue>
  3. #include<vector>
  4. using namespace std;
  5. vector<int> topKElements(vector<int>& data, int k)
  6. {
  7. if (k <= 0) return {};
  8. //利用比较器实现小堆
  9. priority_queue<int, vector<int>, greater<int>> minheap;
  10. //遍历data中的全部属于,没添加满就继续添加,添加满了就比较堆顶元素
  11. for (int num : data)
  12. {
  13. if (minheap.size() < k)
  14. {
  15. minheap.push(num);
  16. }
  17. else if (num > minheap.top())
  18. {
  19. minheap.pop();
  20. minheap.push(num);
  21. }
  22. }
  23. vector<int>ret;
  24. while (!minheap.empty())
  25. {
  26. ret.push_back(minheap.top());
  27. minheap.pop();
  28. }
  29. return ret;
  30. }
  31. int main() {
  32. vector<int> data = { 3, 1, 5, 12, 2, 11, 10, 7, 6 };
  33. int k = 3;
  34. vector<int> result = topKElements(data, k);
  35. cout << "Top " << k << " elements: ";
  36. for (int num : result) {
  37. cout << num << " ";
  38. }
  39. cout << endl;
  40. return 0;
  41. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/945103
推荐阅读
相关标签
  

闽ICP备14008679号