数据结构(4)—— 树与二叉树

写在前面

树这节,主要考察的是选择题,对于代码的能力要求并不是很高。但以防万一,还是实现了一些常见的结构,供自己练习。

依照惯例,上一节地址:数据结构(3)—— 串

二叉树的各种遍历

注意点

由于树是一种递归结构,因此要实现树的深度优先遍历(先序,后序,中序)十分简单,直接递归就好了。对于广度优先(层次遍历),就需要借助队列来实现了,但也不是很难理解。直接看代码吧。

代码

/*
 * @Description: 二叉树的各种遍历方式
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-17 20:15:57
 */
#include <bits/stdc++.h>

// 定义链式存储的树结构
typedef struct BiTNode
{
    char data;
    // 左右孩子指针
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 定义结点的结构
typedef struct LinkNode{
    BiTNode *data;
    struct LinkNode *next;
}LinkNode;
// 定义链式队列的结构
typedef struct{
    LinkNode *front;
    LinkNode *rear;
}LinkQueue;

// 初始化
void initQueue(LinkQueue &Q){
    // 建立头结点
    Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
    Q.front->next = NULL;
}

// 判队空
bool isEmpty(LinkQueue Q){
    return Q.front == Q.rear;
}

// 入队
void enQueue(LinkQueue &Q,BiTNode *x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.rear->next = s;
    // 队尾指针移动到新的尾部
    Q.rear = s;
}

// 出队
bool deQueue(LinkQueue &Q,BiTree &x){
    if(isEmpty(Q)){
        return false;
    }
    LinkNode *p = Q.front->next;
    x = p->data;
    Q.front->next = p->next;
    // 如果原队列只有一个结点,删除后变空
    if(Q.rear == p){
        Q.rear = Q.front;
    }
    free(p);
    return true;
}

// 访问树结点 函数
void visit(BiTree T)
{
    printf("%c ", T->data);
}

// 先序遍历 递归算法
void preOrder(BiTree T)
{

    if (T != NULL)
    {
        visit(T);
        preOrder(T->lchild);
        preOrder(T->rchild);
    }
}

// 中序遍历 递归算法
void inOrder(BiTree T)
{
    if (T != NULL)
    {
        inOrder(T->lchild);
        visit(T);
        inOrder(T->rchild);
    }
}

// 后序遍历 递归
void postOrder(BiTree T)
{
    if (T != NULL)
    {
        postOrder(T->lchild);
        postOrder(T->rchild);
        visit(T);
    }
}

// 层次遍历 借助队列
void levelOrder(BiTree T){
    LinkQueue Q;
    initQueue(Q);
    BiTree p;
    enQueue(Q,T);
    while(!isEmpty(Q)){
        deQueue(Q,p);
        visit(p);
        if(p->lchild != NULL){
            enQueue(Q,p->lchild);
        }
        if(p->rchild != NULL){
            enQueue(Q,p->rchild);
        }
    }
}

// 通过先序遍历的方式 创建一棵树 
bool createBiTree(BiTree &T)
{
    char ch;
    scanf("%c",&ch);
    if (ch == '#')
        T = NULL;
    else{
        if (!(T = (BiTNode *)malloc(sizeof(BiTNode)))){
            return false;
        }
        T->data = ch;
        createBiTree(T->lchild);
        createBiTree(T->rchild);
    }
    return true;
}

// 主函数测试
int main(){
    BiTree T;
    // 测试用例:ABD##E##C##
    createBiTree(T);
    printf("树的先序遍历为:\n");
    preOrder(T);
    printf("\n");
    printf("树的中序遍历为:\n");
    inOrder(T);
    printf("\n");
    printf("树的后序遍历为:\n");
    postOrder(T);
    printf("\n");
    printf("树的层级遍历为:\n");
    levelOrder(T);
    printf("\n");
    return 0;
}

线索二叉树的实现

注意点

线索二叉树就是在二叉树的本来结构上加上线索这个元素,使得其可以很好的找到自己的前驱或后继,尤其是中序线索二叉树,可以实现很好的遍历效果。

这部分的实现,主要就是要理解线索二叉树是怎样的一种结构,如何把一个普通二叉树线索化。在理解了这些之后线索二叉树的实现也不算难了。

代码

/*
 * @Description: 线索二叉树的建立与使用
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-17 20:40:51
 */
#include<bits/stdc++.h>
typedef struct ThreadNode{
    char data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;
}ThreadNode,*ThreadTree;

// 中序线索二叉树线索化的递归算法
void inThread(ThreadTree &p,ThreadTree &pre){
    if(p != NULL){
        inThread(p->lchild,pre);
        // 左子树为空,建立前驱线索
        if(p->lchild == NULL){
            p->lchild = pre;
            p->ltag = 1;
        }
        // 右子树为空,建立后继线索
        if(pre != NULL && pre->rchild == NULL){
            pre->rchild = p;
            pre->rtag = 1;
        }
        pre = p;
        inThread(p->rchild,pre);
    }
}

// 构造中序线索二叉树
void createInThread(ThreadTree T){
    ThreadTree pre = NULL;
    if(T != NULL){
        inThread(T,pre);
        // 处理遍历的最后一个结点
        pre->rchild = NULL;
        pre->rtag = 1;
    }
}

// 访问树结点 函数
void visit(ThreadTree T)
{
    printf("%c ", T->data);
}

// 求中序序列下的第一个结点
ThreadNode* firstNode(ThreadNode *p){
    // 最左下结点
    while(p->ltag == 0){
        p = p->lchild;
    }
    return p;
}

// 求后继结点
ThreadNode* nextNode(ThreadNode *p){
    // 如果有没有后继线索,那就去找右子树的最左下结点
    if(p -> rtag == 0){
        return firstNode(p->rchild);
    }else{
        return p->rchild;
    }
}



// 中序线索树的遍历
void inOrder(ThreadNode *T){
    ThreadNode *p = firstNode(T);
    for(;p != NULL;p = nextNode(p)){
        visit(p);
    }
}

// 通过先序遍历的方式 创建一棵树 
bool createThreadTree(ThreadTree &T)
{
    char ch;
    scanf("%c",&ch);
    if (ch == '#')
        T = NULL;
    else{
        if (!(T = (ThreadNode *)malloc(sizeof(ThreadNode)))){
            return false;
        }
        T->data = ch;
        createThreadTree(T->lchild);
        createThreadTree(T->rchild);
    }
    return true;
}

// 中序遍历 递归算法
void inOrder2(ThreadTree T)
{
    if (T != NULL)
    {
        inOrder2(T->lchild);
        visit(T);
        inOrder2(T->rchild);
    }
}

// 主函数测试
int main(){
    ThreadTree T;
    createThreadTree(T);
    printf("通过常规方式来遍历的结果为:\n");
    inOrder2(T);
    printf("\n");
    createInThread(T);
    printf("通过线索来遍历的结果为:\n");
    inOrder(T);
    printf("\n");
    return 0;
}

二叉排序树的实现

注意点

二叉排序树,跟二分查找的判定树很像,二叉排序树也比较好理解,一般就规定左子树的值小于根结点,右子树的值大于根结点,然后递归即可。

对于这部分的实现,比较好搞。按照刚才的思路进行一个整理就可以很方便的构造出一颗二叉排序树了。

代码

/*
 * @Description: 二叉排序树
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-19 20:29:31
 */
#include<bits/stdc++.h>

// 二叉排序树使用链表存储的二叉树结构
typedef struct BiTNode
{
    int data;
    // 左右孩子指针
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

// 非递归查找
BiTNode *bstSearch(BiTree T,int key){
    while(T != NULL && key != T->data){
        if(key < T->data){
            T = T->lchild;
        }else{
            T = T->rchild;
        }
    }
    return T;
}

// 插入
bool insertBST(BiTree &T,int k){
    if(T == NULL){
        T = (BiTree)malloc(sizeof(BiTNode));
        T->data = k;
        T->lchild = T->rchild = NULL;
        return true;
    }else if(k == T->data){
        return false;
    }else if(k < T->data){
        return insertBST(T->lchild,k);
    }else{
        return insertBST(T->rchild,k);
    }
}

// 构造一颗排序二叉树
void createBST(BiTree &T,int str[],int n){
    T = NULL;
    int i = 0;
    while(i < n){
        insertBST(T,str[i]);
        i++;
    }
}
// 删除,具体的逻辑部分
bool bstDelete(BiTree *p)
{
    BiTree q, s;
    // 情况 1 结点 p 本身为叶子结点,直接删除即可
    if(!(*p)->lchild && !(*p)->rchild){
        *p = NULL;
    }
    // 左子树为空,只需用结点 p 的右子树根结点代替结点 p 即可;
    else if(!(*p)->lchild){ 
        q = *p;
        *p = (*p)->rchild;
        free(q);
    }
    // 右子树为空,只需用结点 p 的左子树根结点代替结点 p 即可;
    else if(!(*p)->rchild){
        q = *p;
        // 将左子树存储的结点的地址赋值给指针变量 p
        *p = (*p)->lchild;
        free(q);
    }
    // 左右子树均不为空,采用第 2 种方式
    else{
        q = *p;
        s = (*p)->lchild;
        // 遍历,找到结点 p 的直接前驱
        while(s->rchild)
        {
            q = s;
            s = s->rchild;
        }
        // 直接改变结点 p 的值
        (*p)->data = s->data;
        // 判断结点 p 的左子树 s 是否有右子树,分为两种情况讨论
        if( q != *p ){
            // 若有,则在删除直接前驱结点的同时,令前驱的左孩子结点改为 q 指向结点的孩子结点
            q->rchild = s->lchild;
        }else{
            // 否则,直接将左子树上移即可
            q->lchild = s->lchild;
        }
        free(s);
    }
    return true;
}

// 删除 递归方式
bool deleteBST(BiTree &T, int key)
{
    // 不存在关键字等于key的数据元素
    if( !T){
        return false;
    }else{
        if( key == T->data ){
            bstDelete(&T);
            return true;
        }else if(key < T->data){
            return deleteBST(T->lchild, key);
        }else{
            return deleteBST(T->rchild, key);
        }
    }
}

// 中序遍历 递归算法
void inOrder(BiTree T)
{
    if (T != NULL)
    {
        inOrder(T->lchild);
        printf("%d ",T->data);
        inOrder(T->rchild);
    }
}
// 主函数测试
int main(){
    // 创建一颗二叉排序树
    BiTree T;
    // 循环输入值
    int str;
    int strInput[5];
    for(int i = 0;i < 5;i++){
        scanf("%d",&str);
        strInput[i] = str;
    }
    createBST(T,strInput,5);
    // 输出看看长啥样
    inOrder(T);
    printf("\n");
    // 插入一个结点
    insertBST(T,6);  
    inOrder(T);
    printf("\n");
    // 删除一个结点
    deleteBST(T,3);
    inOrder(T);
    printf("\n");
}

平衡二叉树

注意点

平衡二叉树,指二叉树的两棵子树的深度之差不超过1,平衡二叉树可以提高二叉排序树的性能。想象这样一种情况,若是我们顺序输入一个序列来构建二叉排序树,那么可以预想到二叉排序树的深度会很大。而二叉排序树的性能又取决于其深度。

因此,平衡二叉树的存在就很有意义了。这里我贴出教程的代码链接,实现起来的最核心部分便是四种变换,这里自己就不再实现了。

代码

平衡二叉树(AVL树)及C语言实现 (biancheng.net)

总结

总的来说,树这块内容非常的多,我这里实现的只是很小一部分。碍于时间和复习进度的原因,树这块耗的时间太多了,哈夫曼树和平衡二叉树并没有实现,读者可以查找资料或者看我上面的链接,对于考研第一轮来说最基本的手算一定要掌握,树这块代码并不是很重要,如果后期有时间会把代码补上的。

上一篇:【Android】 BroadcastReceiver详解


下一篇:【数据结构实验作业】——以链式存储结构的二叉树的递归遍历输出它的先序,中序,后序遍历序列以及高度