堆的实现
1.堆本质是完全二叉树
2.堆排序时间复杂度o(logn)
3.topK问题——最大的K或者最小的K
4.分为大根堆和小根堆
5.使用数组实现
数组下标计算父子关系的公式:leftchild = parent * 2+1 rightchild = parent * 2 + 2
parent = (child-1) / 2
建堆的方式——插入新节点,向上调整
删元素的方式——将尾节点与头节点互换,再依次向下调整——时间复杂度O(N)
Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HeapInit(HP* php);
void HeapDestroy(HP* php);
//插入x继续保持堆形态
void HeapPush(HP* php, HPDataType x);
//删除堆顶元素
void HeapPop(HP* php);
//返回堆顶的元素
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void HeapPrint(HP* php);
Heap.c
#define _CRT_SECURE_NO_WARNINGS
#include "Heap.h"
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void AdjustDown(HPDataType* a, int n, int parent)
{
int minchild = parent * 2 + 1;
while (minchild < n)
{
if (a[minchild + 1] < a[minchild] && minchild + 1 < n)
{
minchild++;
}
if (a[minchild] < a[parent])
{
Swap(&a[minchild], &a[parent]);
parent = minchild;
minchild = parent * 2 + 1;
}
else
break;
}
}
//插入x继续保持堆形态
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
++php->size;
AdjustUp(php->a, php->size - 1);
}
//删除堆顶元素——找次大或者次小
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(&php));
Swap(&php->a[php->size - 1], &php->a[0]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
//返回堆顶的元素
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size);
return php->a[0];
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
void HeapPrint(HP* php)
{
for (int i = 0; i < php->size; ++i)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
堆排序
思路一:从头开始进行往下进行向上调整的插入
思路二:从最后一个叶子节点的父节点开始往上进行向下调整直到根
void HeapSort(int* a, int n)
{
//向上调整建堆
//for (int i = 0; i < n; i++)
//{
// AdjustUp(a, i);
//}
//向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
}
建堆时间复杂度的证明:
向下建堆:
![](https://img-blog.csdnimg.cn/img_convert/b43470a271281cb57573ad5ecd6f54a8.png)
![](https://img-blog.csdnimg.cn/img_convert/28bb113c25552c79ac65105a27783706.png)
好处:相较于向上调整,最后一层个数最多的节点都不用调整,而节点数量越多的层调整次数越少
建堆类型的选择
升序排列——大堆
降序排列——小堆、
如果升序建小堆,取出第一个数据,顺序全乱了,需要重新排列,代价太大!——O(N)
解决方式:升序排列建大堆,最大节点和最后一个节点互换之后向下调整,保证其他节点大体位置不变
总体思想——选择排序,依次选数,从后向前排
void HeapSort(int* a, int n)
{
//向上调整建堆
//for (int i = 0; i < n; i++)
//{
// AdjustUp(a, i);
//}
//向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//选数
int i = 1;
while (i < n)
{
Swap(&a[0], &a[n - i]);
AdjustDown(a, n - i, 0);
++i;
}
}
int main()
{
int a[] = { 65, 100, 70, 32, 50, 60 };
HeapSort(a, sizeof(a) / sizeof(a[0]));
for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
TOP-K问题
N个数,找其中K个最大的,如何实现?
如果正常建堆完全排序——堆排序O(logN)
选大堆?——先建堆,然后每次拿下来最大的与最后一个交换再向下调整 0(N+logN * K)
但如果N很大,K很小,数据在内存中存不下,存储在磁盘上,读取代价太大
解决方法:1.用前K个数,建K个数的小堆
2.一次遍历后n-k个数,比堆顶数据大,就替换堆顶数据,向下调整
最后堆里面的数据就是前k个最大的数
时间复杂度 O(K + logK * (N - K))——O(N) 空间复杂度O(K)
void PrintTopK(const char* filename, int k)
{
assert(filename);
FILE* fout = fopen(filename, "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
int* minHeap = (int*)malloc(sizeof(int) * k);
if (minHeap == NULL)
{
perror("malloc fail");
return;
}
//读取前K个数据
for (int i = 0; i < k; ++i)
{
fscanf(fout, "%d", &minHeap[i]);
}
//建堆
for (int j = (k - 2) / 2; j >= 0; --j)
{
AdjustDown(minHeap, k, j);
}
//继续读取N=-K个数据
int val = 0;
while (fscanf(fout, "%d", &val) != EOF)
{
if (val > minHeap[0])
{
Swap(&minHeap[0], &val);
AdjustDown(minHeap, k, 0);
}
}
for (int i = 0; i < k; ++i)
{
printf("%d ", minHeap[i]);
}
free(minHeap);
fclose(fout);
}
void CreateDataFile(const char*filename,int N)
{
assert(filename);
FILE* fin = fopen(filename, "w");
if (fin == NULL)
{
perror("fopen fail");
return;
}
srand(time(0));
for (int i = 0; i < N; ++i)
{
fprintf(fin, "%d\n", rand() % 100000);
}
fclose(fin);
}
int main()
{
const char* filename = "Data.txt";
int N = 10000;
int k = 10;
//CreateDataFile(filename, N);
PrintTopK(filename, k);
return 0;
}
二叉树实现
test.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//二叉树前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
//二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
//二叉树后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
BTNode* CreateTree()
{
BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
assert(n1);
BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
assert(n2);
BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
assert(n3);
BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
assert(n4);
BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
assert(n5);
BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
assert(n6);
BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
assert(n7);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n5->data = 5;
n6->data = 6;
n7->data = 7;
n1->left = n2;
n1->right = n4;
n2->left = n3;
n2->right = NULL;
n4->left = n5;
n4->right = n6;
n3->left = NULL;
n3->right = n7;
n5->left = NULL;
n5->right = NULL;
n6->left = NULL;
n6->right = NULL;
n7->left = NULL;
n7->right = NULL;
return n1;
}
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int lh = TreeHeight(root->left);
int rh = TreeHeight(root->right);
return lh > rh ? lh + 1: rh + 1;
}
//求第k层的节点个数
int TreeKlevel(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return TreeKlevel(root->left, k - 1) + TreeKlevel(root->right, k - 1);
}
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* lret = TreeFind(root->left, x);
if (lret)
return lret;
//BTNode* rret = TreeFind(root->right, x);
//if (rret)
// return rret;
return TreeFind(root->right, x);
}
int main()
{
BTNode* root = CreateTree();
PreOrder(root);
//InOrder(root);
//PostOrder(root);
//printf("Tree size:%d\n", TreeSize(root));
//printf("Tree leaf size:%d\n", TreeLeafSize(root));
//printf("Tree height:%d\n", TreeHeight(root));
//printf("Tree K level:%d\n", TreeKlevel(root, 2));
BTNode* ret = TreeFind(root, 3);
ret->data *= 10;
PreOrder(root);
printf("\n");
return 0;
}
二叉树OJ
Oj链接
如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。
只有给定的树是单值二叉树时,才返回 true;否则返回 false。
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);
}
OJ链接
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
return true;
if (p == NULL || q == NULL)
return false;
else if (p->val != q->val)
return false;
else
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
OJ链接
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
return true;
if (p == NULL || q == NULL)
return false;
else if (p->val != q->val)
return false;
else
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 (isSameTree(root, subRoot))
return true;
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
OJ链接
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
int TreeSize(struct TreeNode* root)
{
if (root == NULL)
return 0;
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
void preorder(struct TreeNode* root, int* a, int* pi)
{
if (root == NULL)
return;
a[(*pi)++] = root->val;
preorder(root->left, a, pi);
preorder(root->right, a, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
int n = TreeSize(root);
int* a = (int*)malloc(sizeof(int) * n);
int i = 0;
preorder(root, a, &i);
*returnSize = n;
return a;
}
OJ链接
描述
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:
输入包括1行字符串,长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
//printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
return NULL;
}
root->data = a[*pi];
(*pi)++;
root->left = BinaryTreeCreate(a, pi);
root->right = BinaryTreeCreate(a, pi);
return root;
}
int main()
{
char str[100];
scanf("%s", str);
int i = 0;
BTNode* root = BinaryTreeCreate(str, &i);
InOrder(root);
return 0;
}
根据前序中序确定子树建立
前序确定根——中序确定左右子树——再根据前序构建左右子树的根——根据中序去确定左右子树
前序和后序不能唯一确定树