目录
- 树的概念和结构
- 树的结构
- 树的概念
- 树的结构代码
- 二叉树的概念和结构
- 二叉树的概念
- 满二叉树的概念
- 完全二叉树的概念
- 二叉树的存储结构
- 顺序表结构-堆
- 如何建堆
- 堆的插入和删除
- 向下调整法
- 向上调整法
- 堆排序
- TopK问题
- 链式结构-二叉树
- 二叉树前中后序遍历
- 二叉树层序遍历
- 二叉树基础题目
- 顺序表结构-堆
- 二叉树的概念
树的概念和结构
树的结构
数据结构的树是倒过来的
- 根在上方
- 子树(枝叶)朝下生长
- 树是一个非线性数据结构
- 任何一棵树都由根和子树组成
- 子树是不相交的
- 除了根结点外,每个结点有且仅有一个父节点
- 一棵N个结点的树有N - 1条边
树的概念
一点要记住这几个概念(题目中经常碰到)
叶结点,结点的祖先,结点的度,树的度,结点的层次,树的深度
树结构的代码
树(普通树,非二叉树和堆)的代码表示最优为左孩子右兄弟表示法
原因:一个结点不需要知道他的度是多少就可以将此结点创建出来。要加入树时只需尾插链表
代码如下:
//正常代码思路 --缺点:需要知道树的度
#define N 5
struct TreeNode
{
struct TreeNode* a[N];
int data;
};
//优化代码思路
typedef int DataType;
struct TreeNode
{
struct TreeNode* firstChild;//第一个孩子结点
struct TreeNode* pNextBrother;//指向其下一个兄弟结点
DataType data;//结点中的数据域
}
普通树不常用,二叉树很常用
二叉树的概念和结构
二叉树概念
一棵二叉树是结点的一个有限集合,该集合:
- 空结点
- 由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
- 特点:
- 二叉树不存在大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
特殊的二叉树–满二叉树,完全二叉树
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
完全二叉树通俗点讲:完全二叉树有n层,前n-1层是满的,最后一层可以不满,但从左到右是连续的
满二叉树通俗点讲:完全二叉树的基础上最后一层是满的
关于二叉树的数学知识
选择题中经常会遇到,记住这些结论就够用了:
- 满二叉树高度为h的结点总个数:
第一层:1
第二次:2
第三层:4
第四层:8
…
第h层:2^(h - 1)
由高中基础数学知识可得:Sn = 1+2+4+……+2^ (h-1) = 2^ h - 1
- 完全二叉树的高度为h,结点数量的范围 [2 ^(h - 1), 2 ^h - 1]
- 对任何一棵二叉树, 如果度为0(叶结点)个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
度为0的永远比度为2的多一个
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=LogN(一般log代表以2为底的log)
二叉树的存储结构
1. 顺序存储
二叉树的值在数组位置中父子下标关系
parent = (child - 1) / 2
leftChild = parent * 2 + 1
rightChild = parent * 2 + 2
数组存储只适合完全二叉树,如果普通二叉树也用,则会浪费很多空间
堆
逻辑结构是二叉树,物理结构是顺序表
- 堆中某个结点的值总是不大于或不小于其父结点的值
- 堆总是一棵完全二叉树
树中所有的父亲都小于等于孩子
堆的实现
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
堆的插入:
向上调整(最多只调整LogN次):
child比父亲大,则和父亲交换
child比父亲小,则结束
代码实现如下
void AdjustUp(HPDataType* a, int n, int child)
{
assert(a);
int parent = (child-1)/2;
while (child > 0)
{
//如果孩子大于父亲,进行交换
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child-1)/2;
}
else
{
break;
}
}
}
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//检查容量
if (hp->_size == hp->_capacity)
{
hp->_capacity *= 2;
hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity);
}
//尾插
hp->_a[hp->_size] = x;
hp->_size++;
//向上调整
AdjustUp(hp->_a, hp->_size, hp->_size-1);
}
堆的删除
根和尾交换,再删除尾,再向下调整
向下调整:
前提条件:左右子树必须是堆
parent和值最大的孩子交换,依次向下(最多调整LogN次)
实现代码如下
void AdjustDown(HPDataType* a, int n, int root)
{
int parent = root;
int child = parent*2+1;
while (child < n)
{
// 选左右孩纸中大的一个
if (child+1 < n
&& a[child+1] > a[child])
{
++child;
}
//如果孩子大于父亲,进行调整交换
if(a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp);
//交换
Swap(&hp->_a[0], &hp->_a[hp->_size-1]);
hp->_size--;
//向下调整
AdjustDown(hp->_a, hp->_size, 0);
}
如何建堆
方法一:
模拟堆的插入:
对从头开始对每一个结点用向上调整法
实现代码如下:
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
memcpy(hp->_a, a, sizeof(HPDataType) * n);
hp->_capacity = n;
hp->_size = n;
for(int i = 1; i < n; ++i)
{
AdjustUp(a, i);
}
}
时间复杂度为N*LogN
证明如下:
T(N) = 2^11 + 2 ^22+…+2 ^(h-2)(h-2)+2 ^(h-1)(h-1)
化简T(N) = 2^h*h-2 ^h+2-2 ^h
结点个数N为2^h-1
化简得T(N) = (LogN-2)N
约等于NLogN
方法二:
使用向下调整法
实现代码如下
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
JustDown(a, n, i);
}
while (n > 0)
{
swap(a[0], a[n - 1]);
n--;
JustDown(a, n, 0);
}
}
时间复杂度是o(N)
证明:
根据高中数学知识可得
T(N) = 2^h - 1 - h(2 ^h - 1是树总结点个数)
= N - h
约等于N
堆排序
时间复杂度为N*LogN
排升序—建大堆(以升序为例子)
排降序—建小堆
实现代码如下
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
JustDown(a, n, i);
}
while (n > 0)
{
swap(a[0], a[n - 1]);
n--;
JustDown(a, n, 0);
}
}
TopK问题
求出一堆数据中的前k个最大(小)的数。
最优解:可以从成千上万个数据里找出前K个最大值
求前K个最大数建小堆
求前K个最小数建大堆
C++实现代码如下:
void _heapSortK(vector<int>& v, int root, int k)
{
int parent = root;
while (parent * 2 + 1 < k)
{
int lessChild = parent * 2 + 1;
if (parent * 2 + 2 < k && v[lessChild] > v[parent * 2 + 2])
{
lessChild++;
}
if (v[parent] > v[lessChild])
{
swap(v[parent], v[lessChild]);
parent = lessChild;
}
else
{
break;
}
}
}
vector<int> TopK(vector<int>& v, int k)
{
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
_heapSortK(v, i, k);
}
for (int i = k; i < v.size(); ++i)
{
if (v[0] < v[i])
{
swap(v[0], v[i]);
_heapSortK(v, 0, k);
}
}
vector<int> ret;
for (int i = 0; i < k; ++i)
{
ret.push_back(v[i]);
}
return ret;
}
2. 链式存储二叉树(普通二叉树)
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
遍历顺序
遍历顺序分为:
- 前序遍历:根 左子树 右子树
顺序为:1 2 3 空 空 4 5 空 空 6 空 空
实现代码如下:
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->_data);
PreOrder(root->_left);
PreOrder(root->_right);
}
- 中序遍历:左子树 根 右子树
顺序为:空 3 空 2 空 1 空 5 空 4 空 6 空
实现代码如下:
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->_left);
printf("%d ", root->_data);
InOrder(root->_right);
}
- 后序遍历:左子树 根 右子树
顺序为: 空 空 3 空 2 空 空 5 空 空 6 4 1
实现代码如下:
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->_left);
PostOrder(root->_right);
printf("%d ", root->_data);
}
知道栈帧开辟和结束的顺序
调用函数即开辟函数栈帧
函数里的return即销毁函数栈帧
- 层序遍历:1 2 4 3 5 6
用队列实现,队列出结点时将该结点的左右孩子入队
c++实现代码如下:
void BinaryTreeLevelOrder(BTNode* root)
{
queue<BTNode*> q;
if (root == NULL)
return;
q.push(root);
while (!q.empty())
{
BTNode* front = q.front();
q.pop();
printf("%c ", front->_data);
if (front->_left)
{
q.push(front->_left);
}
if (front->_right)
{
q.push(front->_right);
}
}
}
二叉树的基础题目:
做二叉树题目的方法:
不会的题目将递归代码展开的流程自己画很多边,这就是一种新的逻辑,没有技巧而言
一定要非常熟练前中后序的递归代码展开流程
- 二叉树结点的个数
实现代码如下:
int BinaryTreeSize(BTNode* root)
{
if (root == nullptr)
return 0;
return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}
- 二叉树的叶子结点个数
实现代码如下:
int BinaryTreeLeafSize(BTNode* root)
{
if (root == nullptr)
return 0;
if (root->_right == nullptr && root->_left == nullptr)
return 1;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
- 二叉树的高度
方法一:
int BinaryTreeHeightSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeHeightSize(root->_left) > BinaryTreeHeightSize(root->_right) ?
BinaryTreeHeightSize(root->_left) + 1 : BinaryTreeHeightSize(root->_right) + 1;
}
这种方法效率很低,无用的递归了很多次
改进代码如下
方法二:
实现代码如下:
int BinaryTreeHeightSize(BTNode* root)
{
if (root == NULL)
return 0;
int leftHight = BinaryTreeHeightSize(root->_left);
int rightHight = BinaryTreeHeightSize(root->_right);
if (leftHight > rightHight)
return leftHight + 1;
else
return rightHight + 1;
}
- 第k层结点的个数
根的第k层结点 = 左子树的第k-1层结点个数 + 右子树的第k-1层结点个数
实现代码如下
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
- 二叉树的结点的值是否相同
代码实现如下
bool isUnivalTree(BTNode* root)
{
if (root == NULL)
return true;
if (root->_right->_data != root->_left->_data)
return false;
if (root->_data != root->_left->_data)
return false;
return isUnivalTree(root->_left) && isUnivalTree(root->_right);
}
- 查找值为x的结点
实现代码如下:
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
BTNode* node1 = BinaryTreeFind(root->_left, x);
if (node1)
return node1;
BTNode* node2 = BinaryTreeFind(root->_right, x);
if (node2)
return node2;
return NULL;
}
- 检查两棵二叉树是否相同
实现代码如下:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q != nullptr)
return false;
if (p != nullptr && q == nullptr)
return false;
if (p == nullptr && q == nullptr)
return true;
if (p->val != q->val)
return false;
return isSameTree(p->left, q->left) && isSameTree(q->right,p->right);
}
- 检查二叉树是不是对称的
bool _isSymmetric(TreeNode* p, TreeNode* q)
{
if (p == nullptr && q == nullptr)
return true;
if (p == nullptr || q == nullptr)
return false;
return p->val == q->val && _isSymmetric(p->right, q->left) && _isSymmetric(p->left, q->right);
}
bool isSymmetric(TreeNode* root) {
if (root == nullptr)
return true;
if (root->left == nullptr && root->right == nullptr)
return true;
return _isSymmetric(root->left, root->right);
}
- 一棵树是否是另一棵树的子树
bool isSametree(TreeNode* q, TreeNode* p)
{
if (q == nullptr && p == nullptr)
return true;
if (q == nullptr || p == nullptr)
return false;
if (q->val != p->val)
return false;
return isSametree(q->left, p->left) && isSametree(q->right, p->right);
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (root == nullptr)
return false;
if (root->val == subRoot->val)
{
if (isSametree(root, subRoot))
return true;
}
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
- 二叉树的创建
输入先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。创建出二叉树
实现代码如下:
BTNode* BinaryTreeCreate(char* ch, int n, int* i)
{
if (ch[*i] == '#')
{
(*i)++;
return nullptr;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->_data = ch[*i];
(*i)++;
root->_left = BinaryTreeCreate(ch, n, i);
root->_right = BinaryTreeCreate(ch, n, i);
return root;
}