分别基于顺序存储/链式存储设计一个二叉树(C语言)(数据结构学习7)

什么是二叉树

我们了解了什么是树(一对多的逻辑结构),那么对于二叉树简单地理解,满足以下两个条件的树就是二叉树:

  • 本身是有序树;
  • 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;

例如,图 1a) 就是一棵二叉树,而图 1b) 则不是。

图1二叉树示意图.png

二叉树的性质

经过前人的总结,二叉树具有以下几个性质:

  1. 二叉树中,第 i 层最多有 2i-1 个结点。
  2. 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
  3. 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
  • 性质 3 的计算方法为:对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点 n=n0+n1+n2。
    同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数 n=B+1。而分枝数是可以通过 n1 和 n2 表示的,即 B=n1+2n2。所以,n 用另外一种方式表示为 n=n1+2n2+1。
    两种方式得到的 n 值组成一个方程组,就可以得出 n0=n2+1。

二叉树还可以继续分类,衍生出满二叉树和完全二叉树。

满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。


图2.png

如图 2 所示就是一棵满二叉树。

满二叉树除了满足普通二叉树的性质,还具有以下性质:

  1. 满二叉树中第 i 层的节点数为 2n-1 个。
  2. 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
  3. 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
  4. 具有 n 个节点的满二叉树的深度为 log2(n+1)。

完全二叉树

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。

图3完全二叉树.png

如图 3a) 所示是一棵完全二叉树,图 3b) 由于最后一层的节点没有按照从左向右分布,因此只能算作是普通的二叉树。

完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质,比如说,n 个结点的完全二叉树的深度为 ⌊log2n⌋+1。

  • ⌊log2n⌋ 表示取小于 log2n 的最大整数。例如,⌊log24⌋ = 2,而 ⌊log25⌋ 结果也是 2。

对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立:

  1. 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点)
  2. 如果 2i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2i 。
  3. 如果 2i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2i+1 。

顺序存储(数组)实现二叉树

二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。

  • 满二叉树也可以使用顺序存储。要知道,满二叉树也是完全二叉树,因为它满足完全二叉树的所有特征。

普通二叉树转完全二叉树的方法很简单,只需给二叉树额外添加一些节点,将其"拼凑"成完全二叉树即可。如图所示:

拼成二叉树.png

解决了二叉树的转化问题,接下来学习如何顺序存储完全(满)二叉树。

完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。例如,存储所示的完全二叉树:

存储二叉树.png

其存储状态如下图所示:


存储状态.png

由此,我们就实现了完全二叉树的顺序存储。

不仅如此,从顺序表中还原完全二叉树也很简单。我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3,...),若节点 i 有左右孩子,则其左孩子节点为 2i,右孩子节点为 2i+1。此性质可用于还原数组中存储的完全二叉树。

实现代码如下:

定义二叉树结构

#define MAX_NODE_SIZE 100 // 二叉树的最大结点数
typedef int ElemType;//树结点的数据类型
typedef ElemType HjBiTree[MAX_NODE_SIZE];//定义树
ElemType Nil = 0;//定义一个空值,为空则没有节点

typedef struct {
    int level;//层级
    int order;//在当前层的序号
} Position;

1、初始化二叉树

void initBinaryTree(HjBiTree biTree){
    
    for (int i = 0; i < MAX_NODE_SIZE; i++) {
        //每个节点置空
        biTree[i] = Nil;
    }
}

2、按层序给二叉树的节点赋值

Status createBinaryTree(HjBiTree biTree){
    int i = 0;
    for (; i < 10; i++) {
        biTree[i] = i + 1;
        if (i != 0 && biTree[i] != Nil) {
            if (biTree[(i + 1) / 2 - 1] == Nil) {
                return ERROR;
            }
        }
    }
    while (i < MAX_NODE_SIZE) {
        biTree[i] = Nil;
        I++;
    }
    return OK;
}

3、层序遍历

void printfNode(ElemType data){
    printf("%d ",data);
}
void levelOrderTraverse(HjBiTree biTree){
    
    printf("层序遍历:");
    for (int i = 0; i < MAX_NODE_SIZE; i++) {
        ElemType data = biTree[I];
        if (data != Nil) {
            printfNode(data);
        }
    }
    printf("\n");
}

4、前序遍历

void preTraverse(HjBiTree biTree, int index){
    
    if (index >= MAX_NODE_SIZE || index < 0) {
        return;
    }
    ElemType data = biTree[index];
    if (data != Nil) {
        printfNode(data);
        preTraverse(biTree, index * 2 + 1);
        preTraverse(biTree, index * 2 + 2);
    }
}

void preOrderTraverse(HjBiTree biTree){
    printf("前序遍历:");
    preTraverse(biTree, 0);
    printf("\n");
}

5、中序遍历

void inTraverse(HjBiTree biTree, int index){
    if (index >= MAX_NODE_SIZE || index < 0) {
        return;
    }
    ElemType data = biTree[index];
    if (data != Nil) {
        inTraverse(biTree, index * 2 + 1);
        printfNode(data);
        inTraverse(biTree, index * 2 + 2);
    }
}

void inOrderTraverse(HjBiTree biTree){
    printf("中序遍历:");
    inTraverse(biTree, 0);
    printf("\n");
}

6、后序遍历

void postTraverse(HjBiTree biTree, int index){
    if (index >= MAX_NODE_SIZE || index < 0) {
        return;
    }
    ElemType data = biTree[index];
    if (data != Nil) {
        postTraverse(biTree, index * 2 + 1);
        postTraverse(biTree, index * 2 + 2);
        printfNode(data);
    }
}
void postOrderTraverse(HjBiTree biTree){
    printf("后序遍历:");
    postTraverse(biTree, 0);
    printf("\n");
}

7、判断树是否是空树

Status isBiTreeEmpty(HjBiTree biTree){
    //根结点为空,则二叉树为空
    return biTree[0] == Nil;
}

8、获取二叉树的深度

int getBiTreeDepth(HjBiTree biTree){
    
    int i = MAX_NODE_SIZE - 1;
    for (; i >= 0; i --) {
        if (biTree[i] != Nil) {
            break;;
        }
    }
    
    int j = -1;
    do {
        j++;
    } while (pow(2, j) <= i);
    return j;
}

9 返回处于位置pos的结点值

//(层从1开始,序号从1开始)
ElemType getNodeValue(HjBiTree biTree,Position pos){
    
    int index = pow(2, pos.level - 1) - 2;
    index += pos.order;
    
    if (index >= MAX_NODE_SIZE || index < 0) {
        return Nil;
    }
    return biTree[index];
}

10、获取二叉树根结点的值

Status getRootNode(HjBiTree biTree, ElemType *data){
    if (isBiTreeEmpty(biTree)) {
        return ERROR;
    }
    *data = biTree[0];
    return OK;
}

11、给处于位置pos的结点赋值

Status setValueForPos(HjBiTree biTree,Position pos,ElemType data){
    
    int index = pow(2, pos.level - 1) - 2;
    index += pos.order;
    
    if (index >= MAX_NODE_SIZE || index < 0) {
        return ERROR;
    }
    biTree[index] = data;
    return OK;
}

12、获取节点的双亲的值

ElemType getParentValue(HjBiTree biTree, ElemType data){
    
    if (biTree[0] == Nil) {
        return Nil;
    }
    for (int i = 1 ; i < MAX_NODE_SIZE; i++) {
        if (biTree[i] == data) {
            return biTree[ (i + 1) / 2 - 1];
        }
    }
    return Nil;
}

13、获取某个结点的左孩子的值

ElemType getLeftChild(HjBiTree biTree,ElemType data){
    
    if (biTree[0] == Nil) {
        return Nil;
    }
    for (int i = 0; i < MAX_NODE_SIZE; i++) {
        if (biTree[i] == data) {
            int leftChildIndex = i * 2 + 1;
            if (leftChildIndex < MAX_NODE_SIZE) {
                return biTree[leftChildIndex];
            }
        }
    }
    return Nil;
}

14、获取某个结点的右孩子的值

ElemType getRightChild(HjBiTree biTree,ElemType data){
    
    if (biTree[0] == Nil) {
        return Nil;
    }
    for (int i = 0; i < MAX_NODE_SIZE; i++) {
        if (biTree[i] == data) {
            int rightChildIndex = i * 2 + 2;
            if (rightChildIndex < MAX_NODE_SIZE) {
                return biTree[rightChildIndex];
            }
        }
    }
    return Nil;
}

其它辅助代码

#include "stdlib.h"
#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;

int main(int argc, const char * argv[]) {
    printf("---二叉树顺序存储结构实现!---\n");
    
    Status iStatus;
    Position p;
    ElemType e;
    HjBiTree biTree;
    
    initBinaryTree(biTree);
    createBinaryTree(biTree);
    printf("建立二叉树后,树空否?%d(1:是 0:否) \n",isBiTreeEmpty(biTree));
    printf("树的深度 = %d\n",getBiTreeDepth(biTree));
    
    levelOrderTraverse(biTree);
    preOrderTraverse(biTree);
    inOrderTraverse(biTree);
    postOrderTraverse(biTree);
    
    p.level = 3;
    p.order = 2;
    e = getNodeValue(biTree, p);
    printf("第%d层第%d个结点的值: %d\n",p.level,p.order,e);
    
    iStatus = getRootNode(biTree, &e);
    if (iStatus) {
        printf("二叉树的根为:%d\n",e);
    }
    else {
        printf("树为空,无根!\n");
    }
    
    //向树中3层第2个结点位置上结点赋值99
    e = 99;
    setValueForPos(biTree, p, e);
    
    //获取树中3层第2个结点位置结点的值是多少:
    e = getNodeValue(biTree,p);
    printf("第%d层第%d个结点的值: %d\n",p.level,p.order,e);
    
    //找到e这个结点的双亲;
    printf("结点%d的双亲为:%d ",e,getParentValue(biTree, e));
    //找到e这个结点的左右孩子;
    printf("左右孩子分别为:%d,%d\n",getLeftChild(biTree, e),getRightChild(biTree, e));
    
    printf("\n");
    return 0;
}

输出结果

输出结果.png

链式存储(链表)实现二叉树

如图所示,此为一棵普通的二叉树,若将其采用链式存储,则只需从树的根节点开始,将各个节点及其左右孩子使用链表存储即可。


二叉树.png

对应的链式存储结构如下图所示:


二叉树链式存储.png

由图可知,采用链式存储二叉树时,其节点结构由 3 部分构成:

  1. 指向左孩子节点的指针(Lchild);
  2. 节点存储的数据(data);
  3. 指向右孩子节点的指针(Rchild);


    二叉树链式节点.png

实现代码如下:

定义二叉树结构

typedef char ElemType;
ElemType Nil = ' ';
typedef struct BiTreeNode {
    ElemType data;
    struct BiTreeNode *leftChild, *rightChild;
}HjTreeNode, *HjBiTree;

1、初始化二叉树

void initBinaryTree(HjBiTree *biTree){
    
    *biTree = NULL;
}

2、创建二叉树

void createBinaryTree(HjBiTree *biTree, String str, int *index){
    
    ElemType data = str[*index];
    *index = *index + 1;
    //判断是不是空节点
    if (data == '#') {
        *biTree = NULL;
    }
    else {
        *biTree = malloc(sizeof(HjTreeNode));
        if (!*biTree) {
            exit(OVERFLOW);
        }
        (*biTree)->data = data;
        (*biTree)->leftChild = NULL;
        (*biTree)->rightChild = NULL;
        //构造左子树
        createBinaryTree(&(*biTree)->leftChild, str, &(*index));
        //构造右子树
        createBinaryTree(&(*biTree)->rightChild, str, &(*index));
    }
}

3、销毁二叉树

void destoryBinaryTree(HjBiTree *biTree){
    
    if (!*biTree) {
        return;
    }
    //销毁左子树
    if ((*biTree)->leftChild) {
        destoryBinaryTree(&(*biTree)->leftChild);
    }
    //销毁右子树
    if ((*biTree)->rightChild) {
        destoryBinaryTree(&(*biTree)->rightChild);
    }
    free(*biTree);
    *biTree = NULL;
}

4、获取二叉树的深度

int getBinaryTreeDepth(HjBiTree biTree){
    
    if (!biTree) {
        return 0;
    }
    int i = 0;
    int j = 0;
    if (biTree->leftChild) {
        i = getBinaryTreeDepth(biTree->leftChild);
    }
    if (biTree->rightChild) {
        j = getBinaryTreeDepth(biTree->rightChild);
    }
    return i > j ? i + 1 : j + 1;
}

5、前序遍历

void preTraverse(HjBiTree biTree){
    
    if (!biTree) {
        return;
    }
    ElemType data = biTree->data;
    if (data != Nil) {
        printfNode(data);
        preTraverse(biTree->leftChild);
        preTraverse(biTree->rightChild);
    }
}

6、中序遍历

void inTraverse(HjBiTree biTree){
    if (!biTree) {
        return;
    }
    ElemType data = biTree->data;
    if (data != Nil) {
        inTraverse(biTree->leftChild);
        printfNode(data);
        inTraverse(biTree->rightChild);
    }
}

7、后序遍历

void postTraverse(HjBiTree biTree){
    if (!biTree) {
        return;
    }
    ElemType data = biTree->data;
    if (data != Nil) {
        postTraverse(biTree->leftChild);
        postTraverse(biTree->rightChild);
        printfNode(data);
    }
}

其它辅助代码

#include "stdlib.h"
#include "math.h"
#include "time.h"
#include "string.h"

#define OK    1
#define ERROR 0
#define TRUE  1
#define FALSE 0
//状态码
typedef int Status;

#define MAX_SIZE 100

//定义二叉树字符串,0号单元存长度
typedef char String[MAX_SIZE];
Status assignString(String str, char *chars){
    
    long length = strlen(chars);
    if (length > MAX_SIZE) {
        return ERROR;
    }
    str[0] = length;
    for (int i = 1; i <= length; i++) {
//        str[i] = chars[i - 1];
        str[i] = *(chars + i - 1);
    }
    return OK;
}

int main(int argc, const char * argv[]) {
    
    printf("---二叉树链式存储结构实现!---\n");
    
    HjBiTree biTree;
    String str;
    int index = 1;
    
    initBinaryTree(&biTree);
    assignString(str,"ABDH#K###E##CFI###G#J##");
    printf("创建二叉树:%s",str);
    createBinaryTree(&biTree, str, &index);
    
    printf("\n二叉树的深度:%d",getBinaryTreeDepth(biTree));
    
    printf("\n前序遍历二叉树:");
    preTraverse(biTree);
    
    printf("\n中序遍历二叉树:");
    inTraverse(biTree);
    
    printf("\n后序遍历二叉树:");
    postTraverse(biTree);
    
    printf("\n");
    return 0;
}

输出结果

输出结果.png

如有不对的地方,请指正,谢谢您的阅读~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容