数据结构基础知识——非线性数据结构(二叉树、二叉排序树、优先队列、散列表)
目录:
- 基础知识
- 二叉树
- 二叉排序树
- 优先队列
- 哈夫曼树
- 散列表
- 应用实例
- 哈夫曼树【北京邮电大学】
- 复数集合【北京邮电大学】
- 二叉搜索树【浙江大学】
- 二叉树遍历【清华大学】
- 二叉树遍历【华中科技大学】
- 二叉排序树Ⅰ【华中科技大学】
- 二叉排序树Ⅱ【华中科技大学】
- 查找学生信息【清华大学】
- 魔咒词典【浙江大学】
- 子串计算【北京大学】
- 统计同成绩学生人数【浙江大学】
- 开门人和关门人【浙江大学】
一、基础知识
1、二叉树(Binary Tree):
(1)定义:二叉树要么为空,要么由根结点(Root)、左子树(Left Subtree)和右子树(Right Subtree)构成,而左右两个子树又分别是一棵二叉树。
(2)结构体定义:
- struct TreeNode{
- ElementType data;
- TreeNode *lchild, *rchild;
- };
(3)根据遍历每个结点的左子树L、结点本身N、右子树R的顺序不同,可将对二叉树的遍历方法分为前序遍历(NLR),中序遍历(LNR),后序遍历(LRN),其中的序是指根结点在何时被访问。
- 前序遍历(PreOrder):
- void PreOrder(TreeNode *T){
- if(T != NULL){
- Visit(T->data);
- PreOrder(T->leftchild);
- PreOrder(T->rchild);
- }
- }
- 中序遍历(InOrder):
- void InOrder(TreeNode *T){
- if(T != NULL){
- PreOrder(T->leftchild);
- Visit(T->data);
- PreOrder(T->rchild);
- }
- }
- 后序遍历(PostOrder):
- void PostOrder(TreeNode *T){
- if(T != NULL){
- PreOrder(T->leftchild);
- PreOrder(T->rchild);
- Visit(T->data);
- }
- }
- 层次遍历(LevelOrder):需要借助一个队列实现。先将二叉树的根结点入队,在队伍非空的情况下访问队首结点,若它有左子树,则将左子树根结点入队;若它有右子树,则将右子树根结点入队。如此反复,直到队列为空为止。
- void LevelOrder(TreeNode *T){
- queue<TreeNode *> q;
- if(T){
- q.push(T);
- }
- while(!q.empty()){
- TreeNode *current = q.front();
- q.pop();
- Visit(current->data);
- if(current->lchild){
- q.push(current->lchild);
- }
- if(current->rchild){
- q.push(current->rchild);
- }
- }
- }
(4)根据先序遍历、中序遍历确定一棵二叉树:
- 在先序遍历中,第一个结点必定是二叉树的根结点;
- 在中序遍历中,该根结点必定可将中序遍历的序列分为两个子序列,前一个子序列是根结点左子树的中序遍历序列,后一个子序列是根结点右子树的中序遍历序列;
- 递归下去,则可以唯一确定该二叉树。
2、二叉排序树(Binary Search Tree):
(1)定义:对于一棵非空二叉排序树,
- 若左子树非空,则左子树上所有结点关键字的值均小于根结点关键字的值;
- 若右子树非空,则右子树上所有结点关键字的值均大于根结点关键字的值;
- 左右子树本身也是一棵二叉排序树。
(2)特点:
- 各个数字的插入顺序不同,则得到的二叉排序树的形态可能不同。
- 对二叉排序树进行中序遍历,会得到一个升序序列。
- 通过建立一棵二叉排序树,就能对原本无序的序列进行排序,并实现序列的动态维护。
3、优先队列(Priority Queue):
(1)定义:能够将元素按照事先规定的优先级次序进行动态组织,访问元素时,只能访问当前队列中优先级最高的元素,即最高级先出(First-In Greatest-Out)。
(2)STL-priority_queue:
- #include <queue>,先出队列的元素是队列中优先级最高的元素。用priority_queue<int> pq方式声明一个队列,取队首元素的方法由queue的front()变为了top()。
- 常用方法:
- priority_queue的状态:
- pq.size():返回当前优先队列的元素个数。
- pq.empty():返回当前优先队列是否为空。
- priority_queue元素的添加或删除:
- pq.push():元素的入队。
- pq.pop():元素的出队。
- priority_queue元素的访问:
- pq.top():访问当前优先队列中优先级最高的元素。
- priority_queue的状态:
4、哈夫曼树/最优树(Huffman Tree):
(1)定义:给定n个带有权值的结点,以它们为叶子结点构造一棵带权路径长度和最小的二叉树。
- 路径:一棵树中从任意一个结点到达另一个结点的通路。
- 路径长度:路径上所需经过的边的个数。
- 带权路径长度:从根结点到达该结点的路径长度再乘以该结点的权值称为该结点的带权路径长度。
- 树的带权路径长度:所有叶子结点的带权路径长度之和。
(2)特点:
- n个带有权值的结点构成的哈夫曼树可能不唯一。
5、散列表(Hash Table)/ Map(映射/关联数组):
(1)定义:一种根据关键字(key)直接访问值(Value)的数据结构。
(2)特点:
- 由于散列表摒弃了关键码有序,因此在理想情况下可在期望的常数时间内实现所有接口操作。就平均时间复杂度的意义而言,这些操作的复杂度都是O(1).
(3)STL-map:
- #include <map>,map是从键(key)到值(value)的映射。因为重载了[]运算符,map像是数组的高级版,如可以用map<string, int> month_name来表示月份名字到月份编号的映射,然后用month_name["July"] = 7的方式来赋值。
- map的底层由红黑树实现,内部仍然是有序的,其查找效率仍然为O(logn)。
- 无序映射unordered_map的底层是用散列表实现的,其期望的查找效率为O(1)。
- 常用方法:
- map的状态:
- map.empty():返回当前映射是否为空。
- map.size():返回当前映射元素个数。
- map元素的添加或删除:
- insert():实现元素的添加。
- erase():实现元素的删除。
- map元素的访问:
- 由于map内部重载了[]运算符,因此可以通过[key]的方式访问。
- 通过at()进行访问。
- 通过迭代器进行访问。
- map元素操作:
- find():查找特定的元素,找到则返回元素的迭代器,未找到则返回迭代器end()。
- clear():将映射清空。
- map迭代器操作:
- begin():返回映射中首元素迭代器。
- end():返回映射中尾元素之后一个位置的迭代器。
- map的状态:
示例代码:
#include <iostream>
#include <map>
#include <string>
using namespace std;
map<