二叉树
1.树的概念及结构
树是一种非线性的数据结构。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。由根节点,分支节点向外扩伸,最后到叶子节点。树是递归定义的。树的子树之间不能由交际,否则就不是树形结构了。
树的相关概念
节点的度:一个节点含有的子树的个数
叶子节点:度为0的节点
分支节点:度不为0的节点
双亲节点:若个一个节点含有子节点,就说这个节点是子节点的双亲节点
孩子节点:一个节点含有的子树的根节点称为该节点的子节点
树的度:一棵树中,节点的度中最大的度是树的度
节点层次:从根节点开始,根为第一层,一次类推
树的高度或深度:树中节点的最大层次
子孙:以某节点为根的树中任一节点都是根节点的子孙
森林:由m棵不相交的树的集合
树的表示
树可以用双亲表示法和左孩子右兄弟的表示法表示
2.二叉树概念及结构
二叉树是度不大于2的树。从结构上看,任何一颗二叉树都可以分为三部分:根,左子树,右子树。
特殊的二叉树
完全二叉树:只允许最后一层有空缺节点且节点都集中在最左边,上面的h-1层都是满的。 完全二叉树节点个数:[
2
(
h
−
1
)
2^{(h-1)}
2(h−1),
2
h
−
1
2^h - 1
2h−1]
满二叉树:满二叉树是一种特殊的完全二叉树,他的所有层都是满的 。 满二叉树节点个数:
2
h
−
1
2^h - 1
2h−1。
二叉树的性质
规定根节点为第一层,一颗非空二叉树第k层最多有
2
(
h
−
1
)
2^{(h-1)}
2(h−1)个节点。
规定根节点为第一层, 一颗高度为h的二叉树最多有
2
h
−
1
2^h - 1
2h−1个节点。
对于任意一颗二叉树,度为0的节点个数为
n
0
n_0
n0 ,度为2的节点个数为
n
2
n_2
n2,则
n
0
n_0
n0 =
n
2
n_2
n2+1。
规定根节点为第一层,一颗有n个节点的满二叉树的高度为h =
l
o
g
2
log_2
log2(n+1)。
二叉树的存储结构
二叉树一般可以使用两种存储结构,顺序结构和链式结构。
顺序结构是用一个数组来存储,一般只适用于完全二叉树。顺序结构在物理上是一个数组,在逻辑上是一颗二叉树。
链式结构就是用链表的方式链接起每一个节点。
3.二叉树的链式结构
因为二叉树是递归定义的,所以一般二叉树的方法都是用递归写的。
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType val;
}BTNode;
//创建一个节点
BTNode* BuyTreeNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if(!newnode)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->left = newnode->right = NULL;
}
前序 --- 根 左子树 右子树
void PrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
中序 --- 左子树 根 右子树
void InOrder(BTNode* root)
{
if (root == NULL)
return;
InOrder(root->left);
printf("%c ", root->val);
InOrder(root->right);
}
后序 --- 左子树 右子树 根
void PostOrder(BTNode* root)
{
if (root == NULL)
return;
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->val);
}
//二叉树的节点个数
int TreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
//二叉树的叶子个数
int TreeLeaveSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL&& root->right == NULL)
return 1;
return TreeLeaveSize(root->left) + TreeLeaveSize(root->right);
}
//二叉树第k层的节点个数
int TreeLevelSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
当前的第k层转换为左子树和右子树的第k-1层
return TreeLevelSize(root->left, k - 1) + TreeLevelSize(root->right, k - 1);
}
//查找一个val值
BTNode* TreeFind(BTNode* root, int x)
{
if (root == NULL)
return NULL;
if (root->val == x)
return root;
BTNode* ret = TreeFind(root->left, x);先去左子树找
if (ret)
return ret;
ret = TreeFind(root->right, x);左子树没找到,再去右子树找
if (ret)
return ret;
return NULL;
}
//二叉树的销毁
void TreeDestroy(BTNode* root)
{
if (root == NULL)
return;
TreeDestroy(root->left);
TreeDestroy(root->right);
free root;
}
//层序遍历
void LevelOrder(BTNode* root)
{
这里用了C++的内容,就是创建一个队列
queue<BTNode*> q;
if (root)
q.push(root);
while (!q.empty())
{
BTNode* node = q.front();
printf("%c ", node->val);
q.pop();
if (node->left)
q.push(node->left);
if (node->right)
q.push(node->right);
}
printf("\n");
}
4.二叉树的顺序结构
在上面我们提过,二叉树的顺序结构适合完全二叉树,我们把二叉树的顺序结构叫做堆。虽然在物理上是一个数组,但在逻辑上我们认为是一颗完全二叉树。
堆的概念及结构
如果有一个关键码的集合K = {
k
0
k_0
k0,
k
1
k_1
k1,
k
2
k_2
k2,…,
k
n
−
1
k_{n-1}
kn−1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:
k
i
k_i
ki<=
k
2
∗
i
+
1
k_{2*i+1}
k2∗i+1且
k
i
k_i
ki<=
k
2
∗
i
+
2
k_{2*i+2}
k2∗i+2 (
k
i
k_i
ki>=
k
2
∗
i
+
1
k_{2*i+1}
k2∗i+1且
k
i
k_i
ki>=
k
2
∗
i
+
2
k_{2*i+2}
k2∗i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的实现
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//向下调整算法 时间复杂度O(N)
void AdjustDown(int* a, int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
child++;
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
//向上调整算法 时间复杂度O(NlogN)
void AdjustUp(int* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if (!hp->_a)
{
perror("malloc fail");
exit(-1);
}
memcpy(hp->_a, a, sizeof(HPDataType) * n);
hp->_size = n;
hp->_capacity = n;
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(hp->_a, n, i);
}
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_size = hp->_capacity = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->_size == hp->_capacity)
{
int newcapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
HPDataType* p = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
if (!p)
{
perror("realloc fail");
exit(-1);
}
hp->_a = p;
hp->_capacity = newcapacity;
}
hp->_a[hp->_size++] = x;
AdjustUp(hp->_a, hp->_size - 1);
}
// 堆的判空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
hp->_size--;
AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
堆的应用
1.堆排序
注意:要排升序,建大堆,排逆序,建小堆
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
2.topK问题
前K个最小的数,建大堆;前K个最大的数,建小队。