详解数据结构--堆,二叉树

在这里插入图片描述
目录

  • 树的概念和结构
    • 树的结构
    • 树的概念
      • 树的结构代码
  • 二叉树的概念和结构
    • 二叉树的概念
      • 满二叉树的概念
      • 完全二叉树的概念
    • 二叉树的存储结构
      • 顺序表结构-堆
        • 如何建堆
        • 堆的插入和删除
        • 向下调整法
        • 向上调整法
        • 堆排序
        • TopK问题
      • 链式结构-二叉树
        • 二叉树前中后序遍历
        • 二叉树层序遍历
        • 二叉树基础题目

树的概念和结构

树的结构

数据结构的树是倒过来的

  • 根在上方
  • 子树(枝叶)朝下生长
  1. 树是一个非线性数据结构
  2. 任何一棵树都由根和子树组成
  3. 子树是不相交
  4. 除了根结点外,每个结点有且仅有一个父节点
  5. 一棵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;//结点中的数据域
}

在这里插入图片描述
普通树不常用,二叉树很常用

二叉树的概念和结构

二叉树概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 空结点
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
  • 特点:
  1. 二叉树不存在大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

特殊的二叉树–满二叉树,完全二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

完全二叉树通俗点讲:完全二叉树有n层,前n-1层是满的,最后一层可以不满,但从左到右是连续的
满二叉树通俗点讲:完全二叉树的基础上最后一层是满的

在这里插入图片描述

关于二叉树的数学知识

选择题中经常会遇到,记住这些结论就够用了:

  1. 满二叉树高度为h的结点总个数:
    第一层:1
    第二次:2
    第三层:4
    第四层:8

    第h层:2^(h - 1)
    由高中基础数学知识可得:Sn = 1+2+4+……+2^ (h-1) = 2^ h - 1
  1. 完全二叉树的高度为h,结点数量的范围 [2 ^(h - 1), 2 ^h - 1]
  1. 对任何一棵二叉树, 如果度为0(叶结点)个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
    度为0的永远比度为2的多一个
  1. 若规定根节点的层数为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
约等于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);
	}
}

时间复杂度是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. 前序遍历: 左子树 右子树

顺序为: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);
}
  1. 中序遍历:左子树 右子树

顺序为:空 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);
}
  1. 后序遍历:左子树 右子树

顺序为: 空 空 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. 层序遍历: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);
		}
	}
}
二叉树的基础题目:

做二叉树题目的方法:

不会的题目将递归代码展开的流程自己画很多边,这就是一种新的逻辑,没有技巧而言
一定要非常熟练前中后序的递归代码展开流程

  1. 二叉树结点的个数
实现代码如下:
int BinaryTreeSize(BTNode* root)
{
	if (root == nullptr)
		return 0;
	return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}
  1. 二叉树的叶子结点个数
实现代码如下:
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);
}
  1. 二叉树的高度

方法一:

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;
}
  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);
}
  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);
}
  1. 查找值为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;
}

  1. 检查两棵二叉树是否相同
实现代码如下:
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);
    }
  1. 检查二叉树是不是对称的
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);
    }
  1. 一棵树是否是另一棵树的子树
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);
    }
  1. 二叉树的创建
    输入先序遍历字符串: 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;
}
	
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值