数据结构——树和二叉树

简介: 数据结构——树和二叉树

树的定义

  • 树(Tree)是n(n≥0)个结点的有限集,它或为空树(n = 0);或为非空树,对于非空树T:

    • 有且仅有一个称之为根的结点;
    • 除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1, T2, …, Tm, 其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
  • 树是n个结点的有限集

在这里插入图片描述

  • 树的其他表示方式

树的概念

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;

树的基本术语

  • 根:即根结点(没有前驱)
  • 叶子:即终端结点(没有后继)
  • 节点:即树的数据元素
  • 节点的度:一个节点含有的子树的个数称为该节点的度;
  • 树的度:一棵树中,最大的节点的度称为树的度;
  • 叶节点或终端节点:度为零的节点;
  • 分支节点:即度不为0的结点(也称为内部结点)
  • 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次;
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
  • 节点的祖先:从根到该节点所经分支上的所有节点;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林;

树的种类

  • 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
  • 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
  • 二叉树:每个节点最多含有两个子树的树称为二叉树;
  • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均

已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树;

  • 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
  • 排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉

树);

  • 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树;
  • B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树。

常见应用场景

  • xml,html等,那么编写这些东西的解析器的时候,不可避免用到树
  • 路由协议就是使用了树的算法
  • mysql数据库索引
  • 文件系统的目录结构
  • 所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构

二叉树

  • 二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)

    • 有且仅有一个称之为根的结点;
    • 除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树。
  • 基本特点

    • 结点的度小于等于2
    • 有序树(子树有序,不能颠倒)

在这里插入图片描述

二叉树的性质

  • 在二叉树的第i层上至多有2^(i-1)个结点
  • 深度为k的二叉树至多有2^(k-1)个结点
  • 对于任何一棵二叉树,若2度的结点数有n2个,则叶子数n0必定为n2+1 (即n0=n2+1)
  • 具有n个结点的完全二叉树的深度必为[log2n]+1
  • 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2。

二叉树节点表示

  • 案例ly01.py

二叉树遍历

  • 深度优先,一般用递归

    • 先序(Preorder)
    • 中序(Inorder)
    • 后序(Postorder)
  • 广度优先,一般用队列

满二叉树

  • 满二叉树_百度百科
  • 除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
  • 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

    • 满足等比数列公式,具有如下性质

      • 满二叉树的结点数一定是奇数个
      • 第i层上的结点数为:2^i-1
      • 层数为k的满二叉树的叶子结点个数(最后一层): 2^k-1
  • 国外(国际)定义:a binary tree T is full if each node is either a leaf or possesses exactly two childnodes.(如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。(一棵满二叉树的每一个结点要么是叶子结点,要么它有两个子结点,但是反过来不成立,因为完全二叉树也满足这个要求,但不是满二叉树))

    • 度为0或者2,不存在度为1的结点

满二叉树和完全二叉树的区别

满二叉树是叶子一个也不少的树,而完全二叉树虽然前n-1层是满的,但最底层却允许在右边缺少连续若干个结点。满二叉树是完全二叉树的一个特例。
在这里插入图片描述在这里插入图片描述


C++代码实现

#include<iostream>
#include<queue>
using namespace std;

#define OVERFLOW -2 
#define OK 1
#define NULL 0
#define ERROR -1

#define MAXSIZE 100

typedef int Status;

typedef char TelemType;

/*------------------二叉树顺序存储----------------*/
/*
适于存满二叉树和完全二叉树
*/
// typedef TelemType SqBiTree[MAXSIZE];

// SqBiTree bt;  // 包含100存放TelemType类型数据的数组

/*-----------------二叉树链式存储------------------*/

/*
二叉链表
*/
typedef struct BiTNode {
    TelemType data;  // 结点数据域
    struct BiTNode* lchild, * rchild;  // 左右子树指针 
    int flag;  // 后序遍历标志位
}BiTNode, * BiTree;

typedef BiTree SElemType;

typedef struct {
    BiTNode* link;
    int tag;  // 后序遍历标志位
}BElemType;


// 顺序栈结构体
typedef struct {
    SElemType* base;
    SElemType* top;
    int stacksize;
}SqStack;

// 顺序栈初始化
Status InitStack(SqStack& S) {
    S.base = new SElemType[MAXSIZE];
    if (!S.base) return OVERFLOW;
    S.top = S.base;
    S.stacksize = MAXSIZE;
    return OK;
}

// 判断顺序栈是否为空
bool StackEmpty(SqStack S) {
    if (S.top == S.base) return true;
    else return false;
}

// 判断是否为满栈
bool StackFull(SqStack S) {
    if (S.top - S.base >= S.stacksize)
        return true;
    else return false;
}

// 入栈
Status Push(SqStack& S, BiTree e) {
    if (StackFull(S))  // 满栈 
        return ERROR;
    *S.top++ = e;
    // *S.top = e;
    // S.top ++;
    return OK;
}

// 出栈
Status Pop(SqStack& S, BiTree& e) {
    if (StackEmpty(S))  // 栈空
        return ERROR;
    e = *--S.top;
    // S.top --;
    // e = *S.top;
    return OK;
}

/*
# 遍历规则
- 若二叉树为空树,则空操作
- DLR-先序遍历,先根再左再右
- LDR-中序遍历,先左再根再右
- LRD-后序遍历,先左再右再根
*/


/*--------先序(根)遍历---------*/
// 递归算法
/*
若二叉树为空,则空操作
否则:
    访问根结点 (D)
    中序遍历左子树 (L)
    中序遍历右子树 (R)
*/
void PreOrder(BiTree T) {
    if (T) {
        cout << T->data;
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

// 非递归算法
void PreOrder_1(BiTree T) {
    SqStack S;
    InitStack(S);
    BiTree p = T;
    while (p || !StackEmpty(S)) {
        // p非空且栈非空
        if (p) {
            /*
            输出元素
            保存入栈
            p = p->lchild
            */
            cout << p->data;
            Push(S, p);
            p = p->lchild;
        }
        else {
            /*
            出栈到p
            p = p->rchild
            */
            Pop(S, p);
            p = p->rchild;
        }
    }
}


/*--------中序(根)遍历---------*/
// 递归算法
/*
若二叉树为空,则空操作
否则:
    中序遍历左子树 (L)
    访问根结点 (D)
    中序遍历右子树 (R)
*/
void InOrder(BiTree T) {
    if (T) {
        InOrder(T->lchild);
        cout << T->data;
        InOrder(T->rchild);
    }
}

// 非递归算法
void InOrder_1(BiTree T) {
    BiTree p = T;
    SqStack S;
    InitStack(S);
    while (p || !StackEmpty(S)) {
        if (p) {
            /*
            保存--入栈
            p = p->lchild
            */
            Push(S, p);
            p = p->lchild;
        }
        else {
            /*
            出栈到p
            输出p->data
            p = p->rchild
            */
            Pop(S, p);
            cout << p->data;
            p = p->rchild;
        }
    }
}




/*
/*--------后序(根)遍历---------*/
// 递归算法
/*
若二叉树为空,则空操作
否则:
    中序遍历左子树 (L)
    中序遍历右子树 (R)
    访问根结点 (D)
*/
void PostOrder(BiTree T) {
    if (T) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        cout << T->data;
    }
}

// 非递归算法
void PostOrder_1(BiTree T) {
    /*
    此函数应将数据类型改为"结点+标志位",即上面代码中BElemType类型
    但由于修改类型后,需重新改写栈相关函数,故将标志位作为结构体BiTNode参数
    */
    BiTree p = T;
    SqStack S;
    // BElemType w;
    InitStack(S);
    while (p || !StackEmpty(S)) {
        if (p) {
            /*
            保存--入栈 flag = 1(结构体 p,flag)
            p = p->lchild
            */
            // w.link = p;
            // Push(S,w);
            Push(S, p);
            p->flag = 1;
            // w.tag = 1;
            p = p->lchild;
        }
        else {
            /*
            出栈到p
            switch(flag){
                case '1':
                    入栈 flag = 2
                    p = p->rchild
                case '2'
                    输出 p->data
                    p = NULL
            }
            保存--入栈
            p = p->rchild;
            */
            // Pop(S, w);
            Pop(S, p);
            // p = w.link;
            switch (p->flag) {
            case 1:
                p->flag = 2;
                // w.tag = 2;
                Push(S, p);
                p = p->rchild;
                break;
            case 2:
                cout << p->data;
                // 强制置空
                p = NULL;
                break;
            }
        }
    }
}


// 求二叉树叶子结点的个数
int CountLeaf(BiTree T) {
    // 先序遍历
    // 此处必须为static类型,递归会调用自身,只赋一次值
    static int count = 0;
    if (T) {
        if (!T->lchild && !T->rchild)
            count++;
        CountLeaf(T->lchild);
        CountLeaf(T->rchild);
    }
    return count;
}

// 求二叉树深度
/*
1. 求左二叉树深度
2. 求右二叉树深度
3. 取最大值加1
*/
int Depth(BiTree T) {
    // 后序遍历
    int dl, dr, d;
    if (!T)
        d = 0;
    else {
        dl = Depth(T->lchild);
        dr = Depth(T->rchild);
        d = 1 + (dl > dr ? dl : dr);
    }
    return d;
}

// 复制二叉树
BiTree Copy(BiTree T) {
    BiTree t, newl, newr;
    if (!T) {
        t = NULL;
        return t;
    }
    else {
        if (!T->lchild)
            newl = NULL;
        else
            newl = Copy(T->lchild);
        if (!T->rchild)
            newr = NULL;
        else
            newr = Copy(T->rchild);
        t = new BiTNode;
        t->data = T->data;
        t->lchild = newl;
        t->rchild = newr;
    }
    return t;
}

// 先序创建二叉树
Status CreateBiTree(BiTree& T) {
    char ch;
    cin >> ch;
    if (ch == '#') T = NULL;
    else {
        if (!(T = new BiTNode)) exit(OVERFLOW);
        T->data = ch;
        CreateBiTree(T->lchild);
        CreateBiTree(T->rchild);
    }
    return OK;
}

int main() {
    // 测试
    BiTree T, t;
    int n;
    CreateBiTree(T);

    
    // 递归遍历
    cout << "先序遍历为:" << endl;
    PreOrder(T);
    cout << endl << "中序遍历为:" << endl;
    InOrder(T);
    cout << endl << "后序遍历为: " << endl;
    PostOrder(T);
    cout << endl;

    // 叶子结点个数测试
    cout << "叶子结点个数为: " << CountLeaf(T) << endl;

    // 求树深度测试
    cout << "树的深度为:" << Depth(T) << endl;

    // 复制测试
    t = Copy(T);
    cout << "t 的先序遍历为: " << endl;
    PreOrder(t);
    cout << endl;
    


    /*
    // 非递归遍历
    cout << "先序遍历为:" << endl;
    PreOrder_1(T);
    cout << endl;

    cout << "中序遍历为:" << endl;
    InOrder_1(T);
    cout << endl;

    cout << "后序遍历为:" << endl;
    PostOrder_1(T);
    cout << endl;
    */

    return 0;
}

ab##c##
先序遍历为:
abc
中序遍历为:
bac
后序遍历为:
bca
叶子结点个数为: 2
树的深度为:2
t 的先序遍历为:
abc

python代码实现

'''案例ly01.py'''

class Node(object):
    '''节点类'''
    def __init__(self, elem=-1, lchild=None, rchild=None):
        self.elem = elem;
        self.lchild = lchild
        self.rchild = rchild

class Tree(object):
    '''树类'''
    def __init__(self, root=None):
        self.root = root

    def add(self, elem):
        '''为树添加节点'''
        node = Node(elem)
        # 如果树是空的,则对根节点赋值
        if self.root == None:
            self.root = node
        else:
            queue = []
            queue.append(self.root)
            # 对已有的节点进行层次遍历
            while queue:
                # 弹出队列的第一个元素
                cur = queue.pop(0)
                if cur.lchild == None:
                    cur.lchild = node
                    return
                elif cur.rchild == None:
                    cur.rchild = node
                    return
                else:
                    # 如果左右子树都不为空,加入队列继续判断
                    queue.append(cur.lchild)
                    queue.append(cur.rchild)


    def preorder(self, root):
        '''递归实现先序遍历'''
        if root == None:
            return
        print(root.elem)
        self.preorder(root.lchild)
        self.preorder(root.rchild)

    def inorder(self, root):
        '''递归实现中序遍历'''
        if root == None:
            return
        self.inorder(root.lchild)
        print(root.elem)
        self.inorder(root.rchild)

    def postorder(self, root):
        '''递归实现后序遍历'''
        if root == None:
            return
        self.postorder(root.lchild)
        self.postorder(root.rchild)
        print(root.elem)

    def breadth_travel(self, root):
        '''利用队列实现树的层次遍历'''
        if root == None:
            return
        queue = []
        queue.append(root)
        while queue:
            node = queue.pop(0)
            print(node.elem)
            if node.lchild != None:
                queue.append(node.lchild)
            if node.rchild != None:
                queue.append(node.rchild)

if __name__ == '__main__':
    t = Tree()

    for i in range(10):
        t.add(i)

    t.preorder(t.root)
    print('*' * 20)
    t.inorder(t.root)
    print('*' * 20)
    t.postorder(t.root)
    print('*' * 20)
    t.breadth_travel(t.root)
0
1
3
7
8
4
9
2
5
6
********************
7
3
8
1
9
4
0
5
2
6
********************
7
8
3
9
4
1
5
6
2
0
********************
0
1
2
3
4
5
6
7
8
9
[Finished in 0.1s]

线索二叉树

  • 对一个非线性结构进行线性化操作,使每个结点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。
  • n 个结点的二叉链表中必定存在 n+1 个空链域,因此可利用这些空链域来存放结点的前驱和后继信息。
  • 二叉链表加一头结点,lchild 域的指针指向二叉树的根结点,rchild 域的指针指向中序遍历时访问的最后一个结点。
  • 二叉树中序序列中第一个结点的lchild 域的指针和最后一个结点rchild 域的指针均指向头结点。
/*---------二叉树的二叉线索存储表示---------*/
typedef int ElemType;
typedef enum PointerTag {Link, Thread};  // Link==0:指针,Thread==1:线索

typedef struct BiThrNode {
    ElemType data;  // 数据域
    struct BiThrNode* lchild, * rchild;  // 左右孩子指针
    PointerTag LTag, Rtag;  // 左右标志
}BiThrNode, * BiThrTree;

线索二叉树的术语

  • 线索:指向结点前驱和后继的指针
  • 线索链表:加上线索二叉链表
  • 线索二叉树:加上线索的二叉树(图形式样)
  • 线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程
/*----------以结点p为根的子树中序线索化---------*/
void InThreading(BiThrTree p, BiThrNode *&pre) {
    if (p) {
        InThreading(p->lchild, pre);  // 递归线索化左子树
        if (!p->lchild) {
            p->LTag = Thread;  // 前驱线索化
            p->lchild = pre;  // p的左孩子指针指向pre(前驱)
        }
        if (!pre->rchild) {
            // 前驱无右孩子
            pre->RTag = Thread;  // 后继线索
            pre->rchild = p;  // 前驱右孩子指向后继
        }
        pre = p;
        InThreading(p->rchild, pre);  // 递归线索化右子树
    }
}

树和森林

树的存储结构

  • 双亲表示法

    • 以一组连续空间存储树的结点,同时在结点中附设一个指针,存放双亲结点在链表中的位置。
    • 特点:找双亲容易,找孩子难
  • 孩子链表表示法

    • 每个结点有多个指针域,分别指向其子树的根。
  • 树的二叉链表(孩子-兄弟)存储表示法

    /*----树的存储结构->二叉链表表示法----*/
    typedef struct CSNode {
        ElemType data;
        struct CSNode* firstchild, * nextsibling;
    }CSNode, * CSTree;

将树转化为二叉树

  • 加线:在兄弟之间加一连线
  • 抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的连线
  • 旋转:以树的根结点为轴心,将整棵树顺时针转45°
树转换成的二叉树其右子树一定为空

将二叉树转换成树

  • 加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与p的双亲用线连起来
  • 抹线:抹掉原二叉树中双亲与右孩子之间的连线
  • 调整:将结点按层次排列,形成树结构
树的先序遍历与二叉树先序遍历相同
树的后序遍历相当于对应二叉树的中序遍历
目录
相关文章
|
2月前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
65 0
|
4天前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
33 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
4天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
30 12
|
4天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
29 10
|
4天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
18 2
|
18天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
79 5
|
2月前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
108 4
|
2月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
157 8
|
2月前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
72 0