目录
1.二叉树概念及结构
1.1 概念
一个二叉树是结点的一个有限集合,该集合
1.或者为空
2.由一个根结点加上两棵分别称为左子树和右子树的二叉树组成
1.2 特殊的二叉树
1.满二叉树:一个二叉树,如果每一个结点树都达到最大值,则这个二叉树就是满二叉树.
2.完全二叉树:对于深度为k的,n个结点的二叉树,当且仅当每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树.满二叉树是一种特殊的完全二叉树.
1.3 二叉树的性质
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
3.对任意一棵二叉树,如果度为0其叶子结点个数为n0,度为2的分支结点个数为n2,则n0=n2+1
2.二叉树的顺序结构及实现
2.1 二叉树的顺序结构
普通二叉树不适合用数组来存储,因为可能会存在大量的空间浪费,而完全二叉树更适合使用顺序结构存储.现实中我们通常把堆使用顺序结构的数组来存储.
2.2 堆的概念及结构
如果有一个关键码的集合K = { ,
},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足: 且
且
i=0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质
- 堆中的某个结点总是不大于或不小于其父结点的值
- 堆总是一棵完全二叉树
2.3 堆的实现
2.3.1 堆向下调整算法
现在我们给出一个数组,逻辑上看做一棵完全二叉树,我们通过根节点开始的向下调整算法可以把它调整成一个小堆.向下调整算法有一个前提:左右子树必须是一个堆,才能调整
void AdjustDown(HPDataType* a,int size,int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (a[child + 1] < a[child] && child+1 < size)
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}
2.3.2 堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
for (int i = (n - 1 - 1) / 2;i >= 0;i--)
{
AdjustDown(a, n ,i);
}
2.3.4 堆的插入
先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
2.3.5 堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
2.4 堆的应用
2.4.1 堆排序
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1.建堆
- 升序:建大堆
- 降序:建小堆
2.利用堆删除思想来排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
void HeapSort(int* a, int n)
{
//向上调整:时间复杂度O(N*log N)
//for (int i = 1;i < n;i++)
//{
// AdjustUp(a,i);
//}
//向下调整:时间复杂度O(N)
for (int i = (n - 1 - 1) / 2;i >= 0;i--)
{
AdjustDown(a, n ,i);
}
int end = n - 1;
//O(N*log N)
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
2.4.2 TOP-K问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
1. 用数据集合中前K个元素来建堆
- 前k个最大的元素,则建小堆
- 前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
void PrintTopK(int* a, int n, int k)
{
int* KMinHeap = (int*)malloc(sizeof(int) * k);
assert(KMinHeap);
//先把前k个数建成小堆
for (int i = 0;i < k;i++)
{
KMinHeap[i] = a[i];
}
for (int j = (k - 1 - 1) / 2;j >= 0;j--)
{
AdjustDown(KMinHeap, k, j);
}
//后面n-k个数依次与堆顶的数比较,比堆顶的数大就进堆
for (int i = k; i < n; i++)
{
if (a[i] > KMinHeap[0])
{
KMinHeap[0] = a[i];
AdjustDown(KMinHeap, k, 0);
}
}
for (int j = 0; j < k;j++)
{
printf("%d ", KMinHeap[j]);
}
printf("\n");
}
3.二叉树链式结构的实现
3.1 二叉树的创建
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->_left = node2;
node1->_right = node4;
node2->_left = node3;
node4->_left = node5;
node4->_right = node6;
return node1;
}
3.2 二叉树的遍历
3.2.1 前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
- 1前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后
这里主要介绍前序遍历,剩余两个遍历都是前序类似
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
3.3 节点个数及高度等
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->left)
+ BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == 0 && root->right == 0)
return 1;
return BinaryTreeLeafSize(root->left)
+ BinaryTreeLeafSize(root->right);
}
// 二叉树第k层节点个数
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);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = BinaryTreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = BinaryTreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}