二叉树和堆的一点基本小知识(1)

在数据结构中,二叉树这一块是十分重要的,在实际应用中也得到很多的应用,其中堆就很重要。这篇博客首先讲一下二叉树的一点小知识:
首先说一下树的概念:
树在自然界中很常见,在计算机的数据结构中,树也是一块很重要的知识/
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因
为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此,树是递归定义的。
接下来是树的表示:
在计算机中,树一般用孩子兄弟表示法来表示
给出代码:

typedef int DataType;
struct Node
{
 struct Node* _firstChild1; // 第一个孩子结点
 struct Node* _pNextBrother; // 指向其下一个兄弟结点
 DataType _data; // 结点中的数据域
};

接下来介绍一下二叉树的一些简单知识:
二叉树是特殊的树:
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树
的二叉树组成。
二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。
    特殊的二叉树:
  3. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
    说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
  4. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
    的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
    应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
    二叉树的性质
  5. 若规定根节点的层数为0,则一棵非空二叉树的第i层上最多有2^i 个结点.
  6. 若规定只有根节点的二叉树的深度为0,则深度为h的二叉树的最大结点数是2^(h+1) - 1.
  7. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
  8. 具有n个结点的完全二叉树的深度h=Log2(n+1). (ps:Log2(n+1)是log以2为底,n+1为对数)
  9. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:
  10. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
  11. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  12. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
    二叉树是如何实现的?
    代码如下:
BTNode *BinaryTreeCreate(BTDataType * src, int n, int* pi) {
 if (src[*pi] == '#' || *pi >= n)
 {
 (*pi)++;
 return NULL;
 }
 BTNode * cur = (BTNode *)malloc(sizeof(BTNode));
 cur->_data = src[s_n];
 (*pi)++;
 cur->_left = BinaryTreeCreateExe(src);
 cur->_right = BinaryTreeCreateExe(src);
 return cur; }
void BinaryTreePrevOrder(BTNode* root) {
 if (root)
 { 
 putchar(root->_data);
 BinaryTreePrevOrder(root->_left);
 BinaryTreePrevOrder(root->_right);
 }
}
void BinaryTreeInOrder(BTNode* root) {
 if (root)
 {
 BinaryTreeInOrder(root->_left);
 putchar(root->_data);
 BinaryTreeInOrder(root->_right);
 }
}
void BinaryTreePostOrder(BTNode* root) {
 if (root)
 {
 BinaryTreePostOrder(root->_left);
 BinaryTreePostOrder(root->_right);
 putchar(root->_data);
 }
}
void BinaryTreeDestory(BTNode** root) {
 if (*root)
 {
 BinaryTreeDestory((*root)->_left);
 BinaryTreeDestory((*root)->_right);
 free(*root);
 *root = NULL;
 }
}
void BinaryTreeLevelOrder(BTNode* root) {
 Queue qu;
 BTNode * cur;
 QueueInit(&qu);
 QueuePush(&qu, root);
 while (!QueueIsEmpty(&qu))
 {
 cur = QueueTop(&qu);
 putchar(cur->_data);
 if (cur->_left)
 {
 QueuePush(&qu, cur->_left);
 }
 if (cur->_right)
 {
 QueuePush(&qu, cur->_right);
 }
 QueuePop(&qu);
 }
 QueueDestory(&qu);
}
int BinaryTreeComplete(BTNode* root) {
 Queue qu;
里面会用到栈和队列的一些东西,具体请看我前一篇博客,那片博客有点水,不要介意。

接下来介绍一下堆的概念以及如何实现堆的代码设计:
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为
小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
堆的实现:


void Swap(HPDataType* x1, HPDataType* x2) {
 HPDataType x = *x1;
 *x1 = *x2;
 *x2 = x; }
void AdjustDown(HPDataType* a, int n, int root) {
 int parent = root;
 int child = parent*2+1;
 while (child < n)
 {
 // 选左右孩纸中大的一个
 if (child+1 < n
 && a[child+1] > a[child])
 {
 ++child;
 }
 //如果孩子大于父亲,进行调整交换
 if(a[child] > a[parent])
 {
 Swap(&a[child], &a[parent]);
 parent = child;
 child = parent*2+1;
 }
 else
 {
 break;
 }
 }
}
void AdjustUp(HPDataType* a, int n, int child) {
 int parent;
 assert(a);
 parent = (child-1)/2;
 //while (parent >= 0)
 while (child > 0)
 {
 //如果孩子大于父亲,进行交换
 if (a[child] > a[parent])
 {
 Swap(&a[parent], &a[child]);
比特科技
 child = parent;
 parent = (child-1)/2;
 }
 else
 {
 break;
 }
 }
}
void HeapInit(Heap* hp, HPDataType* a, int n) {
 int i;
 assert(hp && a);
 hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n);
 hp->_size = n;
 hp->_capacity = n;
 for (i = 0; i < n; ++i)
 {
 hp->_a[i] = a[i];
 }
 // 建堆: 从最后一个非叶子节点开始进行调整
 // 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2
 // 最后一个位置索引: n - 1
 // 故最后一个非叶子节点位置: (n - 2) / 2
 for(i = (n-2)/2; i >= 0; --i)
 {
 AdjustDown(hp->_a, hp->_size, 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)
 {
 hp->_capacity *= 2;
 hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity);
 }
 //尾插
 hp->_a[hp->_size] = x;
 hp->_size++;
 //向上调整
 AdjustUp(hp->_a, hp->_size, hp->_size-1);
}
void HeapPop(Heap* hp) {
 assert(hp);
 //交换
 Swap(&hp->_a[0], &hp->_a[hp->_size-1]);
 hp->_size--;
 //向下调整
 AdjustDown(hp->_a, hp->_size, 0);
}
HPDataType HeapTop(Heap* hp) {
 assert(hp);
 return hp->_a[0];
}
int HeapSize(Heap* hp) {
 return hp->_size; }
int HeapEmpty(Heap* hp) {
 return hp->_size == 0 ? 0 : 1; }
void HeapPrint(Heap* hp) {
 int i;
 for (i = 0; i < hp->_size; ++i)
 {
 printf("%d ", hp->_a[i]);
 }
 printf("\n");
}
// 升序
void HeapSort(HPDataType* a, int n) {
 // 排升序需要建大堆:
 // 因为每次都会把堆顶元素拿出来放到当前堆的最后一个位置
 // 相当于每次都会把剩余元素中的最大值(即堆顶元素)找出来
 // 放到它该有的位置(当前堆的最后一个位置)
 int i, end;
 for(i = (n-2)/2; i >= 0; --i)
 {
 AdjustDown(a, n, i);
 }
 // 
 end = n-1;
 while (end > 0)
 {
 Swap(&a[0], &a[end]);
 // 调堆,选次大的数
 AdjustDown(a, end, 0);
 --end;
 }
}

void PrintTopK(int* a, int n, int k) {
 Heap hp;
 //建立含有K个元素的堆
 HeapInit(&hp, a, k);
 for (size_t i = k; i < n; ++i) // N
 {
 //每次和堆顶元素比较,大于堆顶元素,则删除堆顶元素,插入新的元素
 if (a[i] > HeapTop(&hp)) // LogK
 {
 HeapPop(&hp);
 HeapPush(&hp, a[i]);
 }
 }
 for(int i = 0; i < k; ++i){
 printf("%d ",HeapTop(&hp));
 HeapPop(&hp);
 }
}
//2. 找最小的K个元素
//假设堆为大堆
void PrintTopK(int* a, int n, int k) {
 Heap hp;
 //建立含有K个元素的堆
 HeapInit(&hp, a, k);
 for (size_t i = k; i < n; ++i) // N
 {
 //每次和堆顶元素比较,小于堆顶元素,则删除堆顶元素,插入新的元素
 if (a[i] < HeapTop(&hp)) // LogK
 {
 HeapPop(&hp);
 HeapPush(&hp, a[i]);

上面就是这篇博客的全部内容啦,只是很少的一部分。下篇博客我会更深入的说一下这个的知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值