【数据结构初阶】五、二叉树

一、树的相关概念

1、

节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的度为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;

2、如何用代码来表示树

方式一、链表方法
假设说明了树的度是N
struct TreeNode
{
int data;
struct TreeNode* subs[N];
};
存在的问题:
(1)、可能会存在空间的浪费;
(2)、万一没有限定树的度是多少,此方法就无法使用;

方式二、数组方法
typedef struct TreeNode* SLDataType;
struct TreeNode
{
int data;
SeqList s;
};
存在的问题:
结构相对复杂;

方式三、结构数组存储(双亲表示法)
struct TreeNode
{
int parent;
int data;
};
存储的是自己的数值和父亲的下标;
以上三种方式都各有优缺点,最有表示法为左孩子右兄弟表示法;

方法四、左孩子右兄弟表示法
typedef int DataType;
struct Node
{
struct Node* _firstChild1; //第一个孩子节点
struct Node* _pNextBrother; //指向其下一个兄弟节点
DataType _data; // 节点中的数据域
};

二、二叉树概念

1、概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树
的二叉树组成。(度最大为2)
二叉树的特点:
1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2. 二叉树的子树有左右之分,其子树的次序不能颠倒。
注意:对于任意的二叉树都是由集中情况复合而成。
空数、只有根节点、只有左子树、只有右子树、左右子树都存在。

2、特殊的二叉树

(1). 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
(2). 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
满二叉树:
第k层有2^(k-1)个节点;
假设树的高度是h的满二叉树,节点个数为2^h-1;
一颗满二叉树有N个节点,高度h=log2(N-1)个;
对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
完全二叉树:
前N-1层是满的;最后一层不满,但最后一层从左到右是连续的;

在具有 2n 个结点的完全二叉树中,叶子结点个数为( )

A n

B n+1

C n-1

D n/2

设度为2的节点个数为X2;
度为1的节点个数为X1;
度为0的几点个数为X0;
可得: X0+X1+X2=2n
因为对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1;
所以X2=X0-1;
又因为X1是度为1的节点个数,在完全二叉树中,度为1的节点个数只可能是0或1个;
而此时X1必须是1才能保证n是整数;
故求得X0=n;答案是B;

三、堆(完全二叉树)

1、适合用数组存储;
2、堆分为大堆和小堆;
大堆:树中一个树及子树中,任何一个父亲都大于等于孩子;
小堆:树中一个数及子树中,任何一个父亲都小于等于孩子;
堆的应用:堆排序;topK问题。在N个数中,找出最大/最小的前K个;

Heap.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int HPDataType;

typedef struct Heap
{
    int* a;
    int size;
    int capacity;
}HP;

void Swap(HPDataType* px, HPDataType* py);
void HeapInit(HP* hp);
void HeapDestroy(HP* hp);
void HeapPush(HP* hp, HPDataType x);
void HeapPop(HP* hp);
void HeapPrint(HP* hp);
bool HeapEmpty(HP* hp);
int HeapSize(HP* hp);

Heap.c

#include "Heap.h"

void Swap(HPDataType* px, HPDataType* py)
{
    HPDataType* tmp = *px;
    *px = *py;
    *py = tmp;
}

void HeapInit(HP* hp)
{
    assert(hp);
    hp->a = NULL;
    hp->size = hp->capacity = 0;
}

void HeapDestroy(HP* hp)
{
    assert(hp);
    free(hp->a);
    hp->capacity = hp->size = 0;
}

void AdjustUp(int* a, int child)
{
    assert(a);
    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 HeapPush(HP* hp, HPDataType x)
{
    assert(hp);
    if (hp->size == hp->capacity)
    {
        size_t newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
        HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType)*newCapacity);
        if (tmp == NULL)
        {
            printf("malloc fail!\n");
            exit(-1);
        }
        hp->a = tmp;
        hp->capacity = newCapacity;
    }
    hp->a[hp->size] = x;
    hp->size++;

    AdjustUp(hp->a, hp->size-1);
}

void* AdjustDown(int* 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[child] < a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent / 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void HeapPop(HP* hp)
{
    assert(hp);
    assert(!HeapEmpty(hp));

    Swap(&hp->a[0], &hp->a[hp->size - 1]);
    hp->size--;

    AdjustDown(hp->a, hp->size, 0);
}

void HeapPrint(HP* hp)
{
    for (int i = 0; i < hp->size; ++i)
    {
        printf("%d", hp->a[i]);
    }
    printf("\n");
}

bool HeapEmpty(HP* hp)
{
    assert(hp);
    return hp->size == 0;
}

int HeapSize(HP* hp)
{
    assert(hp);
    return hp->size;
}

1、topK问题

在N个数中找出最大的前K个 或者 在N个数中找出最小的前K个;
方式一、先排降序,前K个就是最大的:时间复杂度:O(n*logn);浪费资源,只要前K个,但将所有的数都排了一遍;

方式二、直接将N个数依次插入大堆,Pop K 次,每次取堆顶的数,就是前K个,时间复杂度:O(N+K*logN);一般K是远小于N的。

方式三、假设N非常大,N是10亿,内存中存不下这些数,它们存在文件中,K是100,此时方法一和方法二都不能用了。
此时,(1)、用前K个数建立一个K个数的小堆;
(2)、剩下的N-K个数,依次跟堆顶的数据进行比较,如果比堆顶的数据大,就替换堆顶的数据,再向下调整;
(3)、最后堆里边的K个数就是最大的K个数;
时间复杂度:O(K+(N-K)*logK);

代码如下:

void PrintTopK(int* a, int n, int k)
{
    HP hp;
    HeapInit(&hp);
    //创建一个k的数的小堆
    for (int i = 0; i < k; i++)
    {
        HeapPush(&hp, a[i]);
    }

    //剩下的N-K个数跟堆顶的数据比较,比他大,就替换他进堆;
    for (int i = k; i < n; ++i)
    {
        if (a[i] > HeapTop(&hp))
        {
            HeapPop(&hp);
            HeapPush(&hp, a[i]);
        }
    }

    HeapDestroy(&hp);

}

2、堆排序

排升序,用大堆;
排降序,用小堆;
void HeapSort(int* a, int n)
{
    /*for (int i = 1; i < n; i++)
    {
        AdjustUp(a, i);
    }*/

    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(a, n, i);
    }

    for (int end = n - 1; end > 0; --end)
    {
        Swap(&a[end], &a[0]);
        AdjustDown(a, end, 0);
    }
}

三、二叉树的前中后序遍历

前序遍历:访问根节点的操作发生在遍历其左右子树之前;
中序遍历:访问根节点的操作发生在遍历其左右子树之中;
后序遍历:访问根节点的操作发生在遍历其左右字数之后;

代码如下

#include <stdio.h>

typedef int BTDataType;

typedef struct BinaryTreeNode
{
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
    BTDataType data;
}BTNode;

//申请新节点
BTNode* BuyNode(BTDataType x)
{
    BTNode* node = (BTNode*)malloc(sizeof(BTNode));
    if (node == NULL)
    {
        printf("malloc fail!\n");
        exit(-1);
    }
    node->data = x;
    node->left = node->right = NULL;
    return node;
}

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;
}

//前序遍历
void PreOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL\n");
        return;
    }
    printf("%c ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL\n");
        return;
    }
    InOrder(root->left);
    printf("%c ", root->data);
    InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL\n");
        return;
    }
    PostOrder(root->left);
    
    PostOrder(root->right);
    printf("%c ", root->data);
}
计算节点个数:
方法一
int BinaryTreeSize(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }
    static int count = 0;

    ++count;
    BinaryTreeSize(root->left);
    BinaryTreeSize(root->right);

    return count;
}
此方法无法准确计算出

方法二

int BinaryTreeSize(BTNode* root,int* pn)
{
    if (root == NULL)
    {
        return;
    }

    ++(*pn);
    BinaryTreeSize(root->left,pn);
    BinaryTreeSize(root->right,pn);

}

方法三

int BinaryTreeSize(BTNode* root)
{
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    if (root->left == NULL && root->right == NULL)
    {
        return 1;
    }

    return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->left);
}

二叉树第K层节点的个数

int BinaryTreeLevelKSize(BTNode* root, int k)
{
    assert(k > 1);

    if (root == NULL)
    {
        return 0;
    }

    if (k == 1)
    {
        return 1;
    }

    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->left, k - 1);
}

二叉树深度/高度

int BinaryTreeDepth(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    int leftDepth = BinaryTreeDepth(root->left);
    int rightDepth = BinaryTreeDepth(root->right);

    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值