二叉树(初阶)

树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图: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-1层都是满的,最后一层从左到右是连续的

二叉树的性质

1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点. 2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 . 3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1 4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2 为底,n+1为对数) 5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有: 1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点 2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子 3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

二叉树的顺序实现

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。

堆的概念及结构

堆的性质:

1、堆中某个节点的值总是不大于或不小于其父节点的值(也就是大堆或者小堆); 2、堆总是一棵完全二叉树。

堆的代码实现

//Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Heap {
    DataType* arr;
    int size;
    int capacity;
}Hp;
void AdjustUp(DataType* arr, int child);//向上调整
void AdjustDown(DataType* arr, int size, int parent);//向下调整
void Swap(DataType* p1, DataType* p2);//交换
void HeapInit(Hp* hp);//初始化
void HeapDestroy(Hp* hp);//销毁
void HeapPrint(Hp* hp);//打印
void HeapPush(Hp* hp, DataType x);//入数据
void HeapPop(Hp* hp);//删除堆顶的数据
bool HeapEmpty(Hp* hp);//判空
int HeapSize(Hp* hp);//堆存放数据个数
DataType HeapTop(Hp* hp);//获取堆顶的数据
#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"
//heap.c
void HeapInit(Hp* hp) {
    assert(hp);
    hp->arr = NULL;
    hp->capacity = hp->size = 0;
}
void HeapDestroy(Hp* hp) {
    assert(hp);
    free(hp->arr);
    hp->size = hp->capacity = 0;
}
void HeapPrint(Hp* hp) {
    for (int i = 0; i < hp->size; i++) {
        printf(" %d ", hp->arr[i]);
    }
    printf("\n");
}
//向上调整
void AdjustUp(DataType* arr, int child) {
    assert(arr);
    int parent = (child - 1) / 2;
    while (child) {
        if (arr[child] < arr[parent]) {
            Swap(&arr[child], &arr[parent]);
            //DataType tmp = arr[child];
            //arr[child] = arr[parent];
            //arr[parent] = tmp;

            child = parent;
            parent = (child - 1) / 2;
        }
        else {
            break;
        }
    }
}
//向下调整
void AdjustDown(DataType* arr, int size, int parent) {
    assert(arr);
    int kid = parent * 2 + 1;
    while (kid < size) {
        //选择出左右孩子较大的那一个
        if (kid + 1 < size && arr[kid + 1] < arr[kid]) {
            kid++;
        }
        //如果父节点比子节点小,就交换
        if (arr[kid] < arr[parent]) {
            Swap(&arr[parent], &arr[kid]);
            //迭代
            parent = kid;
            kid = parent * 2 + 1;
        }
        else {
            break;
        }
    }
}
//交换
void Swap(DataType* p1, DataType* p2) {
    DataType tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

void HeapPush(Hp* hp, DataType x) {
    assert(hp);
    if (hp->capacity == hp->size) {
        //增容
        int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
        DataType* tmp = (DataType*)realloc(hp->arr, sizeof(DataType) * newCapacity);
        if (tmp == NULL) {
            perror("realloc\n");
            exit(-1);
        }
        hp->arr = tmp;
        hp->capacity = newCapacity;
    }

    //插入数据
    hp->arr[hp->size] = x;
    hp->size++;
    AdjustUp(hp->arr, hp->size - 1);
}
//判空
bool HeapEmpty(Hp* hp) {
    assert(hp);
    return hp->size == 0;
}
//堆存放数据个数
int HeapSize(Hp* hp) {
    assert(hp);
    return hp->size;
}
//删除堆顶的数据
void HeapPop(Hp* hp) {
    assert(hp && !HeapEmpty(hp));

    Swap(&(hp->arr[0]), &(hp->arr[hp->size - 1]));
    hp->size--;
    
    AdjustDown(hp->arr, hp->size, 0);
}
//获取堆顶的数据
DataType HeapTop(Hp* hp) {
    assert(hp && !HeapEmpty(hp));
    return hp->arr[0];
}

堆的应用

堆排序

//升序,建大堆
//降序,建小堆
void HeapSort(int* a,int n) {
    //首先找到最后一个分支节点,依次递减循环向下调整
    for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
        AdjustDown(a, n, i);
    }
    //依次选数,调堆
    for (int i = n - 1; i > 0; i--) {
        //将堆头数据和队尾数据交换,即排好一个数,i--意思是排好的数不看做堆里的数据,继续调未排的数据
        Swap(&a[0], &a[i]);
        //向下调堆
        AdjustDown(a, i, 0);
    }
}

topK问题

即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大

找最大的k个值,建小堆

找最小的k个值,建大堆

void PrintTopK(int* a, int n, int k)
{
    // 1. 建堆--用a中前k个元素建堆
    Hp hp;
    HeapInit(&hp);
    for (int i = 0; i < k; i++) {
        HeapPush(&hp, a[i]);
    }
    // 2. 将剩余n-k个元素依次与堆顶元素交换,不满足则替换
    for (int i = k; i < n; i++) {
        if (a[i] > HeapTop(&hp)) {
            HeapPop(&hp);
            HeapPush(&hp, a[i]);
        }
    }
    HeapPrint(&hp);
    HeapDestroy(&hp);
}
void TestTopk()
{
    int n = 10000;
    int* a = (int*)malloc(sizeof(int) * n);
    srand(time(0));
    for (int i = 0; i < n; ++i)
    {
        a[i] = rand() % 1000000;
    }
    //设置10个比100w大的数
    a[5] = 1000000 + 1;
    a[1231] = 1000000 + 2;
    a[531] = 1000000 + 3;
    a[5121] = 1000000 + 4;
    a[115] = 1000000 + 5;
    a[2335] = 1000000 + 6;
    a[9999] = 1000000 + 7;
    a[76] = 1000000 + 8;
    a[423] = 1000000 + 9;
    a[3144] = 1000000 + 10;
    PrintTopK(a, n, 10);
}

二叉树的链式实现

//BinaryTree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef char BTDataType;
typedef struct BinaryTreeNode {
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
    BTDataType data;
}BTNode;

BTNode* CreatBinaryTree();//造树

void PreOrder(BTNode* root);// 二叉树前序遍历

void InOrder(BTNode* root);// 二叉树中序遍历

void PostOrder(BTNode* root);// 二叉树后序遍历

int BinaryTreeSize(BTNode* root);// 二叉树节点个数

int BinaryTreeLeafSize(BTNode* root);// 二叉树叶子节点个数

int BinaryTreeLevelKSize(BTNode* root, int k);// 二叉树第k层节点个数

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);// 二叉树查找值为x的节点

int BinaryTreeDepth(BTNode* root);//二叉树深度/高度

void LevelOrder(BTNode* root);// 层序遍历

int BinaryTreeComplete(BTNode* root);// 判断二叉树是否是完全二叉树

void BinaryTreeDestory(BTNode** root);//二叉树的销毁
//BinaryTree.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "BinaryTree.h"
#include "Queue.h"

//开辟新节点
BTNode* BuyNode(BTDataType x) {
    BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
    if (newNode == NULL) {
        perror("malloc\n");
        exit(-1);
    }
    newNode->data = x;
    newNode->right = newNode->left = NULL;
}
//造树
BTNode* CreatBinaryTree()
{
    BTNode* nodeA = BuyNode('A');
    BTNode* nodeB = BuyNode('B');
    BTNode* nodeC = BuyNode('C');
    BTNode* nodeD = BuyNode('D');
    BTNode* nodeE = BuyNode('E');
    BTNode* nodeF = BuyNode('F');
    BTNode* nodeG = BuyNode('G');

    nodeA->left = nodeB;
    nodeA->right = nodeC;
    nodeB->left = nodeD;
    nodeC->left = nodeE;
    nodeC->right = nodeF;
    //nodeF->right = nodeG;
    nodeB->right = nodeG;
    return nodeA;
}

// 二叉树前序遍历
//根        左子树    右子树
void PreOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    printf("%c ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}
// 二叉树中序遍历
//左子树        根    右子树
void InOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    InOrder(root->left);
    printf("%c ", root->data);
    InOrder(root->right);
}
// 二叉树后序遍历
//左子树        右子树    根
void PostOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%c ", root->data);
}
// 二叉树节点个数
//遍历计数思想:多次调用存在问题
int BinaryTreeSize(BTNode* root) {
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root) {
    if (root == NULL) {
        return 0;
    }
    else if (root->left == NULL && root->right == NULL) {
        return 1;
    }
    else {
        return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
    }
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {
    assert(k > 0);
    if (root == NULL) {
        return 0;
    }
    if (k == 1) {
        return 1;
    }
    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
//二叉树深度/高度
int BinaryTreeDepth(BTNode* root) {
    if (root == NULL) {
        return 0;
    }
    //return BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? BinaryTreeDepth(root->left) + 1 : BinaryTreeDepth(root->right) + 1;
    int LeftDepth = BinaryTreeDepth(root->left);
    int RightDepth = BinaryTreeDepth(root->right);
    return LeftDepth > RightDepth ? LeftDepth + 1 : RightDepth + 1;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
    if (root == NULL || root->data == x) {
        return root;
    }
    BTNode* left = BinaryTreeFind(root->left, x);
    if (left)
        return left;
    BTNode* right = BinaryTreeFind(root->right, x);
    if (right)
        return right;
    return NULL;
}
// 层序遍历
void LevelOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);
    while (!QueueEmpty(&q)) {
        //取出队首的数据域
        BTNode* front = QueueFront(&q);
        //再将队首pop出队列
        QueuePop(&q);
        printf("%c ", front->data);

        //孩子带进队列
        if (front->left) {
            QueuePush(&q, front->left);
        }
        if (front->right) {
            QueuePush(&q, front->right);
        }
    }
    printf("\n");
    QueueDestroy(&q);
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root) {
    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);
    while (!QueueEmpty(&q)) {
        //取出队首的数据域
        BTNode* front = QueueFront(&q);
        //再将队首pop出队列
        QueuePop(&q);
        if (front) {
            QueuePush(&q, front->left);
            QueuePush(&q, front->right);
        }
        else {
            break;
        }
    }
    //遇到空了以后,检查队列中剩下的节点
    //1、剩下全是空,则是完全二叉树
    //2、剩下存在非空,则不是完全二叉树
    while (!QueueEmpty(&q)) {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front) {
            return false;
        }
    }

    QueueDestroy(&q);
    return true;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode* root) {
    if (root == NULL) {
        return;
    }
    BinaryTreeDestory(root->left);
    BinaryTreeDestory(root->right);
    free(root);
}

总结

二叉树的顺序存储一般适用于完全二叉树的存储,因为对于不规则的树会有很多的空间浪费。而在顺序存储中又有堆的分支,堆分为大堆和小堆。可以一次处理topK问题和堆排序的应用。

链式的存储的方式,不存在空间的浪费。但是树是递归定义的,很多的函数接口可以采用递归思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值