前言
本次文章主要讲的是数据结构中的堆与链式二叉树。在堆中我们会将一种排序,就是堆排序,它的效率很高的,是logn*n。在链式二叉树中我们会讲到很多OJ题,把递归这玩意玩的明明白白。
1.堆的实现
1.1头文件
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
1.2堆的销毁
// 堆的销毁
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->size = php->capacity = 0;
}
1.3向上调整算法
Swap(HPDataType* child, HPDataType* parent)
{
HPDataType tmp = *child;
*child = *parent;
*parent = tmp;
}
AdjustUp(HPDataType* a, int child)
{
//这里我们建立小堆
//能这样干的前提,向上的都是小堆
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child],&a[parent]);
}
else
{
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
1.4堆的插入
// 堆的插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int new_capacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType*p = realloc(php->a, sizeof(HPDataType) * new_capacity);
if (p == NULL)
{
perror("HeapPush:");
return;
}
php->a = p;
php->capacity = new_capacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size-1);
}
1.5向下调整算法
void AdjustDown(HPDataType*a, int n, int parent)
{
int child = parent * 2 + 1;//先默认小的孩子为左孩子
while (child < n)
{
if (child + 1 < n && a[child + 1] < a[child])
{
child++;
}
//开始调整
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
}
else
{
break;
}
parent = child;
child = child * 2 + 1;
}
}
1.6堆的删除
void HeapPop(HP* php)//堆的删除就是删除堆顶元素
{
assert(php);
assert(php->size > 0);
//先交换第一个元素与最后一个元素
Swap(&php->a[0], &php->a[php->size - 1]);
//再删除最后一个元素
php->size--;
AdjustDown(php->a, php->size, 0);//传入数组长度和要向下调整的元素下标
//能这样干的前提,向下的都是小堆
}
1.7取堆顶的数据
// 取堆顶的数据
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
1.8堆的数据个数
// 堆的数据个数
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
1.9堆的判空
// 堆的判空
int HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
2.堆排序
2.1堆排序
法一
//堆排序
void HeapSort1(HPDataType* a, int n)//小堆就降序
{
//首先要把这个数组变成堆才行
//则从第一个元素开始就向上调整就可以了,因为向上调整的要求就是上面的为堆,所以从开头开始,或者从第二个开始,因为第一个已经是堆了
for (int i = 2; i < n; i++)
{
AdjustUp(a, i);
}
//这样的话就变成堆了,这样的话第一个元素就是最小的了
while (n > 0)
{
Swap(&a[0], &a[n - 1]);
n--;//因为这样的话下一步向下调整就不会涉及到我的最小元素了
//再对第一个元素进行向下调整
AdjustDown(a, n, 0);//这样的话第二小的元素就跑到堆顶了
}
}
法二
//堆排序
void HeapSort2(HPDataType* a, int n)//小堆就降序
{
//第二个方法就是建堆的方法不一样罢了
//倒着建堆,每一个都向下调整,因为向下调整的前提就是下面的为堆
//而且从第一个非叶子节点开始建堆,因为一个叶子节点就已经是堆了
for (int i =(n-1-1)/2; i >= 0; i--)
{
AdjustDown(a,n, i);
}
//这样的话就变成堆了,这样的话第一个元素就是最小的了
while (n > 0)
{
Swap(&a[0], &a[n - 1]);
n--;//因为这样的话下一步向下调整就不会涉及到我的最小元素了
//再对第一个元素进行向下调整
AdjustDown(a, n, 0);//这样的话第二小的元素就跑到堆顶了
}
}
2.2创造数据
//创造10万个数据
void CreatData()
{
FILE* pf = fopen("text.txt", "w");
int n = 100000;
srand((unsigned int)time(NULL));
for(int i=0;i<n;i++)
{
int x = (rand() + i) % 100000;
fprintf(pf, "%d\n", x);
}
fclose(pf);
}
2.3topk算法
//topk算法
void PrintTopK(int k)
{
//找出前k个大的,而且空间复杂度为1
//建立大小为k的堆,因为k远小于10万,所以视为常数
//向下调整建立堆
int* arr = (int*)malloc(sizeof(int)*k);
//将前k个数据放入数组中
FILE* pf = fopen("text.txt", "r");
for (int i = 0; i < k; i++)
{
fscanf(pf, "%d", &arr[i]);
}
//将前k个数据建堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, k, i);
}
//现在堆最上面放的肯定是最小的
//现在开始访问十万个数据,比堆顶大的,就放上去,并向下调整
int x = 0;
while (fscanf(pf, "%d", &x) != EOF)
{
if (x > arr[0])
{
arr[0] = x;
AdjustDown(arr, k, 0);
}
}
//打印出前k个最大的
for (int i = 0; i < k; i++)
{
printf("%d ", arr[i]);
}
}
3.链式二叉树的实现
3.1二叉树的创建
嗯二叉树的创建一定程度上是没有什么意义的,手搓就可以了
这里我们写头文件
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
3.2二叉树销毁
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
assert(root);
BTNode* node = *root;
if (node == NULL)
{
return;
}
BinaryTreeDestory(&node->left);
BinaryTreeDestory(&node->right);
free(node);
//printf("free一次");
node = NULL;
}
3.3二叉树节点个数
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return 1+BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
3.4二叉树叶子节点个数
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
3.5二叉树第k层节点个数
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
//求第k层的话,递归的话,就是求子树第k-1层的节点个数,如果是第k层的,就返回1
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
3.6二叉树查找值为x的节点
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
//找节点为x的话,递归的话,就相当于返回子树不为NULL的某个返回值
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
//先存下左右子树的返回值,不然会递归多次
BTNode* root_left = BinaryTreeFind(root->left, x);
BTNode* root_right = BinaryTreeFind(root->right, x);
if (root_left == NULL)//说明左子树没有x的节点
{
return root_right;
}
else//两个都没有x的节点。就随便返回一个吧
{
return root_left;
}
}
3.7二叉树前序遍历
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
//先访问根,就是先把根的东西写出来,最后再递归
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
3.8二叉树中序遍历
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
//中间访问根,就是再中间把根的东西写出来,递归放两边
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
3.9二叉树后序遍历
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
3.10层序遍历
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
//先把根入队列
//然后出一个队列就入其孩子队列
Queue q;
QueueInit(&q);
if (root == NULL)
{
return;
}
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
//出一个
BTNode* ret = QueueFront(&q);
QueuePop(&q);
printf("%c ", ret->data);
//如果孩子不为空就入队
if (ret->left)
{
QueuePush(&q, ret->left);
}
if (ret->right)
{
QueuePush(&q, ret->right);
}
}
QueueDestroy(&q);
}
3.11判断二叉树是否是完全二叉树
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
//这个都不能用递归了了
//这个也是和层序遍历是一样的,但不同的是,孩子为空指针也会入队,当出队的为空指针时,队列中还有
//非空指针,那就不是,如果后面全是空指针,那就是完全二叉树
Queue q;
QueueInit(&q);
if (root == NULL)
return true;
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
//出一个
BTNode* ret = QueueFront(&q);
QueuePop(&q);
if (ret == NULL)
{
break;
}
QueuePush(&q, ret->left);
QueuePush(&q, ret->right);
}
while (!QueueEmpty(&q))
{
//出一个
BTNode* ret = QueueFront(&q);
QueuePop(&q);
if (ret != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
4.二叉树相关OJ题
4.1单值二叉树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
// bool isUnivalTree(struct TreeNode* root) {
// //如何实现,只需要判断每个节点等不等于它的左右孩子就可以了
// //递归的话就是看每个子树满不满足单值了
// //然后再看结束条件
// //结束条件就是NULL return true,根不等于左或者右就返回false,但是左右肯定要不为空,不然怎么访问数据,如果根等于就递归
// 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);
// }
bool my_isUnivalTree(struct TreeNode* root, int x)
{
//递归的话,就是看左右子树是否同时为单值树
//结束条件的话就是NULL返回true,不等于就返回false,等于就没事,因为就一个节点等于也说明不了什么
if (root == NULL)
{
return true;
}
if (root->val != x)
{
return false;
}
return my_isUnivalTree(root->left,x) && my_isUnivalTree(root->right,x);
}
bool isUnivalTree(struct TreeNode* root) {
//方法二,看每个节点等不等于那个值,为了让那个值一直存在,我们在建立一个函数
return my_isUnivalTree(root, root->val);
}
4.2二叉树的最大深度

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int maxDepth(struct TreeNode* root) {
//大事化小
//如果为空就返回0,不为空的话就返回1+子树的长度中的那个较大值,而子树的长度就是与根相同的思路
//停止递归的条件就是NULL
if (root == NULL)
{
return 0;
}
//记录子树的长度,不然会多次递归
int left_len = maxDepth(root->left);
int right_len = maxDepth(root->right);
if (left_len > right_len)
{
return 1 + left_len;
}
else
{
return 1 + right_len;
}
}
4.3二叉树的前序遍历

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int TreeSize(struct TreeNode* root)
{
if (root == NULL)
{
return 0;
}
return 1 + TreeSize(root->left) + TreeSize(root->right);
}
void my_preorderTraversal(struct TreeNode* root, int* arr, int* pi)
{
//因为先序,所以先看根
//根如果为空的话就什么都不干,不为空的话就赋值到数组中
//然后再赋值左右子树,左右子树和根是一样的
if (root == NULL)
{
return;
}
arr[*pi] = root->val;
(*pi)++;
my_preorderTraversal(root->left, arr, pi);
my_preorderTraversal(root->right, arr, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
//按照题意,必须开辟一个数组,所以不能递归这个函数,不然一直开辟,所以在创建函数
//开辟多大呢,还要先求有多少个
int size = TreeSize(root);
int* arr = (int*)malloc(sizeof(int) * size);
//还要一个东西来记录下标,并且会一直变的,所以要取地址
int i = 0;
my_preorderTraversal(root, arr, &i);
*returnSize = size;
return arr;
}
4.4相同的树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
//判断两个树是不是相同的树的话,就先判断根是不是相同的树
//在判断两个子树是不是相同的树,而两个子树的判断的逻辑与根是相同的,这就叫大事化小
//因为要两个子树同时为相同的树,才是真正的树,所以用&&,再加上返回又不是地址之类的,不用再设变量去保存返回值了
//接下来再来看,递归停止条件,停止条件就是两个数值不相等的话就返回错误
//而在使用两个数值之前就要看他们是不是空,一个为空都不能看数值
//两个均为空,返回正确(说明前面的都相同了),一个空一个不为空就返回错误,这是处理空的时候,
if (p == NULL && q == NULL)
{
return true;
}
//走到这里已经说明了两个不同时为NULL了
//至少有一个为NULL
if (p == NULL || q == NULL)
{
return false;
}
//根相同根本不能说明什么,要子树也相同才可以
//不相同就说明返回错误
if (p->val != q->val)
{
return false;
}
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
4.5对称二叉树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool my_isSymmetric(struct TreeNode* left, struct TreeNode* right)
{
//其实看是不是对称的,一定程度上就是看两个子树的某些子树是不是相同的
//这里我们要递归的话,先看两个头相不相同
//
//相同的话的话,就就看左的右,右的左两个相不相同,而这两个也可以递归,还有左的左,右的右,两者要同时相同才可以
if (left == NULL && right == NULL)
{
return true;
}
if (left == NULL || right == NULL)
{
return false;
}
if (left->val != right->val)
{
return false;
}
return my_isSymmetric(left->right, right->left)&&my_isSymmetric(left->left, right->right);
}
bool isSymmetric(struct TreeNode* root) {
//递归的话就是,先看根,如果根相同,就看左子树与右子树是不是对称的//单独用个函数
if (root == NULL)
{
return true;
}
return my_isSymmetric(root->left, root->right);
}
4.6另一棵树的子树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
//判断两个树是不是相同的树的话,就先判断根是不是相同的树
//在判断两个子树是不是相同的树,而两个子树的判断的逻辑与根是相同的,这就叫大事化小
//因为要两个子树同时为相同的树,才是真正的树,所以用&&,再加上返回又不是地址之类的,不用再设变量去保存返回值了
//接下来再来看,递归停止条件,停止条件就是两个数值不相等的话就返回错误
//而在使用两个数值之前就要看他们是不是空,一个为空都不能看数值
//两个均为空,返回正确(说明前面的都相同了),一个空一个不为空就返回错误,这是处理空的时候,
if (p == NULL && q == NULL)
{
return true;
}
//走到这里已经说明了两个不同时为NULL了
//至少有一个为NULL
if (p == NULL || q == NULL)
{
return false;
}
//根相同根本不能说明什么,要子树也相同才可以
//不相同就说明返回错误
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) {
//这道题递归的话就是看以这个节点为根的话,这个树与subroot是不是相同的树,相同的话就ok,不相同的话就继续看左右孩子为根的树是不是,有一个就可以了,所以||
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.7二叉树遍历

#include <stdio.h>
#include<stdlib.h>
struct TreeNode
{
char val;
struct TreeNode*left;
struct TreeNode*right;
};
void PreOrderTree(struct TreeNode*tree)
{
if(tree==NULL)
{
return;
}
PreOrderTree(tree->left);//左
printf("%c ",tree->val);//根
PreOrderTree(tree->right);//右
}
struct TreeNode* TreeCreat(struct TreeNode*tree,char*arr,int*i)
{
//因为先序遍历,所以先序递归
//先创建头,先创建根
//再创建左右孩子,而且左右孩子的创建与根是一样的
//最后再返回头
//停止条件,第一NULL不为#创建,第二数组值为#,不创建,而且它肯定没子树了,
//什么都不用干,但要返回NULL了,因为它是某个非空的孩子
if(arr[*i]=='#')//
{
(*i)++;
return NULL;
}
//创建根,但是要tree不为空才创建//因为先序遍历,所以有可能到因为创建过的节点
if(tree==NULL)//创建
{
tree=(struct TreeNode*)malloc(sizeof(struct TreeNode));
tree->val=arr[*i];
(*i)++;
tree->left=tree->right=NULL;//这样才可以递归去创建,不然为随机值不好弄
tree->left=TreeCreat(tree->left,arr,i);
tree->right=TreeCreat(tree->right,arr,i);
}
return tree;//左右都安排好了就直接返回了
}
int main() {
//首先先创建这个数组
char arr[100]={0};
scanf("%s",arr);
//创建树
struct TreeNode*tree;
tree=NULL;
//主要函数
//因为要把数组的值打入创建树//所以还要传下标
int i=0;
tree=TreeCreat(tree,arr,&i);
PreOrderTree(tree);
return 0;
}
4.8翻转二叉树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/*
void my_invertTree(struct TreeNode* left, struct TreeNode* right)
{
//翻转左右子树的话,就直接先翻转两个根,再翻转左的左,右的右,,,,,左的右,右的左,先假设翻转的时候同时有数据,同时为空
//看来这样不行,因为不会同时为空,同时又数据,所以交换数据是不行的,要交换指针
if (left == NULL && right == NULL)
{
return;
}
int tmp = left->val;
left->val = right->val;
right->val = tmp;
my_invertTree(left->left, right->right);
my_invertTree(left->right, right->left);
}
struct TreeNode* invertTree(struct TreeNode* root) {
//翻转这个树,就只需要翻转这颗树的左右子树就可以了
if (root == NULL)
{
return NULL;
}
my_invertTree(root->left, root->right);
return root;
}
*/
struct TreeNode* invertTree(struct TreeNode* root) {
//先交换左右子树,在交换左右子树的左右子树就可以了
if (root == NULL)
{
return NULL;
}
struct TreeNode* tmp = root->left;
root->left = root->right;
root->right = tmp;
invertTree(root->left);
invertTree(root->right);
return root;
}
4.9平衡二叉树

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
// int Height(struct TreeNode* root)
// {
// if (root == 0)
// {
// return 0;
// }
// int h1 = Height(root->left);
// int h2 = Height(root->right);
// if (h1 > h2)
// {
// return h1 + 1;
// }
// else
// {
// return h2 + 1;
// }
// }
// bool isBalanced(struct TreeNode* root) {
// //法一:对每一个节点的左右孩子求高度,看高度是否满足平衡二叉树
// //每一个节点:先序遍历,求高度再来一个函数
// //一个树是平衡二叉树,要由三个保证,第一左右子树是平衡二叉树(调用本函数),第二左右子树高度差不超过一(调用height函数),
// //而左右子树是不是平衡二叉树,就同样思路
// //什么时候停止呢,==NULL就返回TRUE,除此之外判断一样
// if (root == NULL)//因为NULL无法访问孩子了
// {
// return true;
// }
// return (abs(Height(root->left) - Height(root->right))<=1) && isBalanced(root->left) && isBalanced(root->right);
// }
int my_isBalanced(struct TreeNode* root)
{
if (root == NULL)
{
return 0;
}
int h1= my_isBalanced(root->left);//这里可以看出,递归其实就是把这个函数的意义正常在函数中使用
int h2= my_isBalanced(root->right);//我这个函数的意义就是求树的高度,那么就正常调用这个函数去表示高度就可以了
if (h1 == -1 || h2 == -1)//看左右子树是不是平衡二叉树
{
return -1;
}
if (abs(h1 - h2) > 1)
{
return -1;
}
//走到这里就是平衡二叉树了
//返回两个高度的最大值
int max = h1;
if (h2 > h1)
{
max = h2;
}
return max + 1;
}
bool isBalanced(struct TreeNode* root) {
//改进一下,如果是平衡二叉树就返回树的高度,而树的高度就是真
//如果不是平衡二叉树就返回-1,因为NULL返回0,因为NULL是平衡二叉树,,,,,,但这样的话就会与bool的真值矛盾
//所以在创建一个函数吧
//而是不是平衡二叉树的判断就是两点,第一左右子树是平衡二叉树,第二两个高度之差不超过一
//这个方法的优点就是在计算高度时顺便就判断了是不是平衡二叉树
if (my_isBalanced(root) == -1)
{
return false;
}
return true;
}
这里可以看出,递归其实就是把这个函数的意义正常在函数中使用。。。。。。。。这句话是精华啊!!!!
总结
内容很多很丰富,读者觉得什么有用就看什么吧,或者也可以自己按照题目做一遍题的

1737

被折叠的 条评论
为什么被折叠?



