初阶数据结构:二叉树(补充扩展)

目录

  • [1. 堆排序](#1. 堆排序)
  • [2. TopK问题](#2. TopK问题)
  • [3. 二叉树的链式结构及其遍历方式](#3. 二叉树的链式结构及其遍历方式)
    • [3.1 二叉树的链式结构](#3.1 二叉树的链式结构)
    • [3.2 二叉树的前序遍历](#3.2 二叉树的前序遍历)
    • [2.2 二叉树的中序遍历](#2.2 二叉树的中序遍历)
    • [2.3 后序遍历](#2.3 后序遍历)
    • [2.4 层序遍历](#2.4 层序遍历)
  • [4. 二叉树OJ练习](#4. 二叉树OJ练习)
    • [4.1 单值二叉树](#4.1 单值二叉树)
    • [4.2 判断两棵二叉树是否相等](#4.2 判断两棵二叉树是否相等)
    • [4.3 判断对称二叉树](#4.3 判断对称二叉树)
    • [4.4 另一棵二叉树的子树](#4.4 另一棵二叉树的子树)
    • [4.5 二叉树的构建与销毁](#4.5 二叉树的构建与销毁)
    • [4.6 判断二叉树是否为完全二叉树](#4.6 判断二叉树是否为完全二叉树)
    • [4.7 补充练习](#4.7 补充练习)
      • [4.7.1 二叉树的节点数](#4.7.1 二叉树的节点数)
      • [4.7.2 二叉树的叶子节点数](#4.7.2 二叉树的叶子节点数)
      • [4.7.3 二叉树的第K层结点数](#4.7.3 二叉树的第K层结点数)
      • [4.7.4 查找二叉树中为x值的结点](#4.7.4 查找二叉树中为x值的结点)

1. 堆排序

1.1补充:建堆的时间复杂度

这里使用满二叉树近似计算,向下调整法建堆的时间复杂度:

等比数列求和,得需调整n - log(n + 1),时间复杂度为O(n)

1.2 堆排序:升序与降序

思路:我们先建堆,然后将堆顶(堆顶元素总是为最大或最小)的元素与尾部元素交换,size--,然后向下调整,直至堆为空。

  1. 排升序,建大堆(大堆的父亲结点都大于小堆)
  2. 排降序,建小堆

代码实现如下:

c 复制代码
void HeapAdjustDown(int* arr, int n, int parent)
{
	//n为元素个数
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}

		if (arr[parent] < arr[child])
		{
			swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

int arr[] = { 27,15,18,28,34,65,49,25,37 };
//向下调整建堆
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	HeapAdjustDown(arr, n, i);
}

ArrPrint(arr, n);

//交换首尾元素,size--,再向下调整
int size = n;
while (size)
{
	swap(&arr[0], &arr[size - 1]);
	size--;
	HeapAdjustDown(arr, size, 0);
}

2. TopK问题

当数据量很大时,且为乱序,数据无法一下全部加载到内存中,所以整体排序无法得出最大或最小的前K个数,那么,如何快速得出前K个最大/最小数。
思路:

先于数据首部建一个大小为K的

  1. 得出最大,建小堆
  2. 得出最小,建大堆

然后,再将剩余的N- K个数据与堆顶元素比较

  1. 当需要最大的数时,若比较元素大于堆顶元素,则交换二者再进行向下调整。
  2. 当需要最小的数时,若比较元素小于堆顶元素,则交换二者再进行向下调整。
c 复制代码
void HeapAdjustDown2(int* arr, int n, int parent)
{
	//n为元素个数
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}

		if (arr[parent] > arr[child])
		{
			swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void TopK(int* arr, int k, int n)
{
	//筛选k个最大的数
	//在数据顶部建一个大小为k的小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		HeapAdjustDown2(arr, k, i);
	}

	//若遍历到大于堆顶的元素,交换并向下调整
	for (int j = k; j < n; j++)
	{
		if (arr[j] > arr[0])
		{
			swap(&arr[j], &arr[0]);
			HeapAdjustDown2(arr, k, 0);
		}
	}
}

3. 二叉树的链式结构及其遍历方式

3.1 二叉树的链式结构

在前面的学习中,我们已经了解到,二叉树的结构由三部分组成根,左子树,右子树

当我们使用链式存储的方式构建二叉树时,其结点的结构定义如下:

c 复制代码
typedef struct TreeNode
{
	int val;
	//左子树指针
	struct TreeNode* left;
	//右子树指针
	struct TreeNode* right;
}

根据逻辑遍历的方式顺序不同,二叉树的遍历方式分为四种:前序,中序,后序,层序

3.2 二叉树的前序遍历

  1. 遍历逻辑顺序:根结点,左子树,右子树(根左右)
  2. 题目链接:
    前序遍历
c 复制代码
void preorder_part(struct TreeNode* root, int* arr, int* size)
{
    if(root == NULL)
    {
        return;
    }

    arr[*size] = root->val;
    (*size)++;
    preorder_part(root->left, arr, size);
    preorder_part(root->right, arr, size);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) 
{
    //根左右
    //为空树时,不反回空指针
    //返回数组
    
    *returnSize = 0;
    int* arr = (int*)malloc(100 * sizeof(int));
    preorder_part(root, arr, returnSize);

    return arr;
}

2.2 二叉树的中序遍历

  1. 遍历逻辑顺序:左根右
  2. 题目链接:
    中序遍历
c 复制代码
void inorder_part(struct TreeNode* root, int* arr, int* size)
{
    if(root == NULL)
    {
        return;
    }

    inorder_part(root->left, arr, size);
    arr[*size] = root->val;
    (*size)++;
    inorder_part(root->right, arr, size);
}

int* inorderTraversal(struct TreeNode* root, int* returnSize) 
{
    *returnSize = 0;
    int* arr = (int*)malloc(100 * sizeof(int));
    inorder_part(root, arr, returnSize);

    return arr;    
}

2.3 后序遍历

  1. 遍历逻辑顺序:左右根
  2. 题目链接:
    后序遍历
c 复制代码
void postorder_part(struct TreeNode* root, int* arr, int* size)
{
    if(root == NULL)
    {
        return;
    }

    postorder_part(root->left, arr, size);
    postorder_part(root->right, arr, size);
    arr[*size] = root->val;
    (*size)++;
}

int* postorderTraversal(struct TreeNode* root, int* returnSize) 
{
    *returnSize = 0;
    int* arr = (int*)malloc(100 * sizeof(int));
    postorder_part(root, arr, returnSize);

    return arr;        
}

2.4 层序遍历

遍历逻辑:依次按层进行遍历(利用队列存储子节点)

c 复制代码
void LevelOrder(TreeNode* root)
{
	Queue q1;
	QueueInit(&q1);
	QueuePush(&q1, root);

	TreeNode* top = QueueFront(&q1);
	
	while (!QueueEmpty(&q1))
	{
		if (top)
		{
			QueuePush(&q1, top->left);
			QueuePush(&q1, top->right);
		}
		
		if (top)
		{
			printf("%c ", top->val);
		}
		else
		{
			printf("NULL ");
		}

		QueuePop(&q1);
		if (!QueueEmpty(&q1))
		{
			top = QueueFront(&q1);
		}
	}
}

4. 二叉树OJ练习

4.1 单值二叉树

  1. 题目信息:
  2. 题目链接:
    单值二叉树
  3. 思路:任选一种遍历方式遍历整个二叉树,同时判断左右孩子结点的值等于父亲结点的值
c 复制代码
bool isUnivalTree(struct TreeNode* root) 
{
    if(root == NULL)
    {
        return true;
    }

    if(root->left && root->val != root->left->val)
    {
        return false;
    }

    if(root->right && root->val != root->right->val)
    {
        return false;
    }
    
    return isUnivalTree(root->left) && isUnivalTree(root->right);    
}

4.2 判断两棵二叉树是否相等

  1. 题目信息:
  2. 题目链接:
    判断两颗二叉树是否相等
    3 . 思路:按同样顺序遍历两棵树,同时判断
c 复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    //调整逻辑顺序,不出现越界情况
    //二者都为NULL
    if(p == NULL && q == NULL)
    {
        return true;
    }

    //有一个为NULL
    if(p && q == NULL)
    {
        return false;
    }

    if(q && p == NULL)
    {
        return false;
    }

    //都不为NULL
    if(p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

4.3 判断对称二叉树

  1. 题目信息:
  2. 题目链接:
    对称二叉树
  3. 思路:按照对称的遍历方式,遍历判断二叉树的左右子树
c 复制代码
bool order_part(struct TreeNode* left, struct TreeNode* right)
{
    if(left == NULL && right == NULL)
    {
        return true;
    }

    if(left && right == NULL)
    {
        return false;
    }

    if(right && left == NULL)
    {
        return false;
    }

    if(left->val != right->val)
    {
        return false;
    }

    return order_part(left->left, right->right) && order_part(left->right, right->left);
}

bool isSymmetric(struct TreeNode* root) 
{
    
    return order_part(root->left, root->right);
}

4.4 另一棵二叉树的子树

1.题目信息:

  1. 题目链接:
    另一颗树的子树

  2. 思路:遇到与配对子树的根节点相同的结点即开始配对,直至遍历完整个树。

  3. 注:将逻辑,语言转换(翻译)为代码

c 复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    //调整逻辑顺序,不出现越界情况
    //二者都为NULL
    if(p == NULL && q == NULL)
    {
        return true;
    }

    //有一个为NULL
    if(p && q == NULL)
    {
        return false;
    }

    if(q && p == NULL)
    {
        return false;
    }

    //都不为NULL
    if(p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
    if(root == NULL)
    {
        return false;
    }

    if(root->val == subRoot->val && isSameTree(root, subRoot))
    {
        return true;
    }

    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

4.5 二叉树的构建与销毁

  1. 题目信息:
  2. 题目链接:
    二叉树的构建与遍历
  3. 先创建根结点,再依次创建左子树,右子树
c 复制代码
typedef struct TreeNode
{
    char val;
    struct TreeNode* left;
    struct TreeNode* right;
}TreeNode;

void inorder_part(struct TreeNode* root, char* arr, int* size)
{
	if (root == NULL)
	{
		return;
	}

	inorder_part(root->left, arr, size);
	arr[*size] = root->val;
	(*size)++;
	inorder_part(root->right, arr, size);
}

char* inorderTraversal(struct TreeNode* root, int* returnSize)
{
	//根左右
	//为空树时,不反回空指针
	//返回数组

	*returnSize = 0;
	char* arr = (char*)malloc(100 * sizeof(int));
	inorder_part(root, arr, returnSize);

	return arr;
}

TreeNode* BuyTreeNode(char val)
{
	TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));
	
	newnode->val = val;
	newnode->left = newnode->right = NULL;

	return newnode;
}

TreeNode* BinaryTreeCreate(char* a, int n, int* pi)
{
	TreeNode* root = NULL;

	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	if (a[*pi] != '#')
	{
		root = BuyTreeNode(a[*pi]);
	}
	(*pi)++;

	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);

	return root;
}

int main()
{
	char* a = (char*)malloc(100 * sizeof(char));
    char str;
    int count = 0;
    while(scanf("%c", &str) != EOF)
    {
        a[count++] = str;
    }

	int i = 0;
	TreeNode* root = BinaryTreeCreate(a, strlen(a), &i);
	
	int returnSize = 0;
	char* ret = inorderTraversal(root, &returnSize);

	for (int j = 0; j < returnSize; j++)
	{
		printf("%c ", ret[j]);
	}

    return 0;
}

二叉树的销毁:

c 复制代码
void BinaryTreeDestory(TreeNode* root)
{
	//后序遍历
	if (root == NULL)
	{
		return;
	}

	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

4.6 判断二叉树是否为完全二叉树

思路:使用层序遍历,如果出现NULL后,又出现非NULL指针,那么此树就不是完全二叉树,反之,则是。

c 复制代码
int BinaryTreeComplete(TreeNode* root)
{
	//层序遍历判断是否为完全二叉树
	Queue q1;
	QueueInit(&q1);
	QueuePush(&q1, root);

	TreeNode* top = QueueFront(&q1);
	//标志有NULL指针出现
	int count = 0;

	while (!QueueEmpty(&q1))
	{
		if (top)
		{
			QueuePush(&q1, top->left);
			QueuePush(&q1, top->right);
		}
		
		if (top == NULL)
		{
			count++;
		}

		if (count && top)
		{
			return 0;
		}

		QueuePop(&q1);
		if (!QueueEmpty(&q1))
		{
			top = QueueFront(&q1);
		}
	}

	return 1;
}

4.7 补充练习

4.7.1 二叉树的节点数

c 复制代码
// 二叉树节点个数
int BinaryTreeSize(TreeNode* root)
{
	//根结点 + 左子树结点 + 右子树结点

	if (root == NULL)
	{
		return 0;
	}

	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

4.7.2 二叉树的叶子节点数

c 复制代码
// 二叉树叶子节点个数
int BinaryTreeLeafSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

4.7.3 二叉树的第K层结点数

c 复制代码
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(TreeNode* root, int k)
{
	if (k == 0)
	{
		return 1;
	}

	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

4.7.4 查找二叉树中为x值的结点

c 复制代码
// 二叉树查找值为x的节点
TreeNode* BinaryTreeFind(TreeNode* root, char x)
{
	//没找到
	if (root == NULL)
	{
		return NULL;
	}
	
	//找到了
	if (root->val == x)
	{
		return root;
	}
	
	//在左侧找
	TreeNode* left = BinaryTreeFind(root->left, x);
	//在右侧找
	TreeNode* right = BinaryTreeFind(root->right, x);
	
	if (left)
	{
		return left;
	}

	return right;
}
相关推荐
Ning_.几秒前
力扣第 67 题 “二进制求和”
算法·leetcode
IT 青年7 分钟前
数据结构 (11)串的基本概念
数据结构
bbppooi24 分钟前
堆的实现(完全注释版本)
c语言·数据结构·算法·排序算法
FFDUST31 分钟前
C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)
c语言·c++·算法·leetcode
m0_738054561 小时前
【leetcode】全排列 回溯法
c++·算法·leetcode·回溯法
ZZZ_O^O1 小时前
【贪心算法第五弹——300.最长递增子序列】
c++·学习·算法·leetcode·贪心算法
码农多耕地呗1 小时前
哈希表—acwing
数据结构·散列表
呼啦啦啦啦啦啦啦啦1 小时前
刷题日常(移动零,盛最多水的容器,三数之和,无重复字符的最长子串)
算法·双指针·滑动窗口
Koishi_TvT1 小时前
蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)
c语言·c++·算法·性能优化·蓝桥杯·动态规划·c
孤独且没人爱的纸鹤1 小时前
C++ 二叉搜索树(Binary Search Tree, BST)深度解析与全面指南:从基础概念到高级应用、算法优化及实战案例
c语言·数据结构·c++·算法