数据结构成神篇4:树(下)

简介: 迭代函数:由于非递归函数的执行效率高,可将“尾递归” 函数改为迭代函数

二叉搜索树:


一,什么是二叉搜索树?


二叉搜索树(BST, Binary Search Tree),也称二叉排序树或二叉查找树


二叉搜索树: 一棵二叉树,可以为空;如果不为空,满足以下性质:

1.非空左子树的所有键值小于其根结点的键值。

2.非空右子树的所有键值大于其根结点的键值。

3.左、右子树都是二叉搜索树。


二,操作函数


Position Find( ElementType X, BinTree BST):从二叉搜索树BST中查找元素X,返回其所在结点的地址;

Position FindMin( BinTree BST ):从二叉搜索树BST中查找并返回最小元素所在结点的地址;

Position FindMax( BinTree BST):从二叉搜索树BST中查找并返回最大元素所在结点的地址。

P BinTree Insert( ElementType X, BinTree BST )

P BinTree Delete( ElementType X, BinTree BST )


1,Find(查找)


查找从根结点开始,如果树为空,返回NULL


若搜索树非空,则根结点关键字和X进行比较,并进行不同处理:

①若X小于根结点键值,只需在左子树中继续搜索;

②如果X大于根结点的键值,在右子树中进行继续搜索;

③若两者比较结果是相等,搜索完成,返回指向此结点的指针。.


代码实现:


尾递归:


Posi tion Find( ElementType X,BinTree BST )
{   
    if.(!BST)returnNULL;/*如果为空,返回NULL*/
    if.(X.>BST->Data)
        return FindT X, BST->Right ) ; /*在右子树中继续查找*,
    else if( X< BST->Data )
        return Find( x, BST->left ) ; /*在左子树中继续查找*/
    else     /* X == BST->Data */
        return BST; /*查找成功,返回结点的找到结点的地址*/
}


迭代函数:由于非递归函数的执行效率高,可将“尾递归” 函数改为迭代函数


Position IterFind( E1ementType X, BinTree BST )
{    
    while( BST ) {
        if( X > BST->Data )
            BST = BST->Right; /*向右子树中移动,继续查找*/
        else if( X < BST->Data )
            BST = BST->Ieft; /*向左子树中移动,继续查找*/
        else /* X == BST->Data */
            return BST; /*查找成功,返回结点的找到结点的地址*/
    }
    return NULL; /*查找失败*/
}


查找最大和最小元素

1)最大元素一定是在树的最右分枝的端结点上

2)最小元素一定是在树的最左分枝的端结点上


找最小元素:


Position FindMin( BinTree BST )
{
    if( !BST )     return NULL; /*空的二叉搜索树,返回NULL*/
    else if( !BST-> Left )
        return BST; / *找到最左叶结点并返回*/
    else
        return FindMin( BST->Left ) ; /*沿左分支继续查找*/
}


找最大元素:


Position FindMax( BinTree BST )
{
    if (BST )
        while ( BST-> >Right )
            BST = BST->Right ;
                /*沿右分支继续查找, 直到最右叶结点*/
    return BST ;
}


2,Insert(插入)


BinTree Insert( E1ementType X,BinTree BST )
{
    if( !BST ) {
        /*若原树为空, 生成并返回一个结点的二叉搜索树*/
        BST = malloc(sizeof (struct TreeNode) ) ;
        BST->Data = X;
        BST->Left = BST->Right = NULL;
    }else / *开始找要插入元素的位置*/
        if( X < BST->Data )
            BST->Left = Insert( X,BST-> Left) ;
                /*递归插入左子树*/
        else if( X > BST->Data )
            BST->Right = Insert( X, BST->Right) ;
                        /*递归插入右子树*/ .
        /* else x已经存在,什么都不做*/
    return BST ;
}


3,delete(删除)


1)删叶结点:


要删除的是叶结点:直接删除,并再修改其父结点指针--置为NULL


2)要删除的结点只有一个孩子结点:将其父结点的指针指向要删除结点的孩子结点


3699e60d95d24d10aeba834d61418283.png


3)要删除的结点有左、右两棵子树:


用另一结点替代被删除结点:右子树的最小元素或者左子树的最大元素


代码实现:


BinTree Delete ( E1ementType X,BinTree BST )
{    
    Position Tmp;
    if( !BST ) printf ("要删除的元素未找到") ;
    else if ( X < BST->Data )
        BST->Left = Delete( x,BST->Ieft) ; /*左子树递归删除*/
    else if( X > BST->Data )
        BST->Right = Delete( X,BST->Right) ; /*右子树递归删除*/
    else / *找到要删除的结点*/
        if( BST->Left && BST->Right ) { /*被删除结点有左右两个子结点*/
            Tmp = FindMin( BST->Right ) ;
                /*在右子树中找最小的元素填充删除结点*/
            BST->Data = Tmp->Data;
            BST->Right = Delete( BST->Data, BST->Right) ;
                /*在删除结点的右子树中删除最小元素*/
        } else { /*被删除结点有一个或无子结点*/
            Tmp=BST;
            if( !BST->Ieft ) /* 有右孩子或无子结点*/
                BST = BST- >Right;
            else if( !BST->Right ) /*有左孩子或无子结点*/
                BST = BST->Left;
            free( Tmp ) ;
    return BST ;
}


平衡二叉树:


一,什么是平衡二叉树?


平衡因子( Balance Factor,简称BF) : BF(T)= hL-hR,其中hL和hR分别为T的左、右子树的高度。


平衡二叉树( Balanced Binary Tree) ( AVL树)

空树,或者

任一结点左、右子树高度差的绝对值不超过1,即|BF(T)|S 1


d2aa2cb16ccb4ebd8efa841ba3722876.png


平衡二叉树的高度能达到log2n吗?

设nh高度为h的平衡二叉树的最少结点数。结点数最少时:


52e57c343f5c413fb28acec41a59ff7b.png

161020a206274aeda7c4d7be8071756c.png


二,平衡二叉树的调整


对二叉树的一些操作会导致其平衡失调,所以我们要对其做出调整。


1,右单旋


cf1d9073d13f46be9a77a799a17e2b7d.png


2,左单旋


113d19a7af2b42bcae2a77953882aec1.png


3,左右单旋


9eb98e67572c45dcbf2a0a261a03b92d.png


4,右左单旋


a862947330134b628b0456c55d28d3d8.png


堆(heap):


优先队列(Priority Queue) :特殊的“队列”,取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。


1,问题:如何组织优先队列?


一般的数组、链表?

有序的数组或者链表?

二叉搜索树? AVL树?


3605e5c1932141468641d72fab403d9c.png


2,是否可以采用二叉树存储结构?


二叉搜索树?

如果采用二叉树结构,应更关注插入还是删除?

➢树结点顺序怎么安排?

➢树结构怎样?


fe0b41d09911496b80767e7014f7095d.png


➢堆的两个特性

结构性:用数组表示的完全二叉树;

有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)


最大堆(MaxHeap)”,也称“大顶堆”:最大值

最小堆(MinHeap)” ,也称“小顶堆”:最小值


761d5d9141de4ff3aa3a2a50331521bc.png


1fe24a2c15a9433fb43ff753c67984b9.png


一,堆的抽象数据类型描述


类型名称:最大堆(MaxHeap)

数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值

操作集:最大堆H∈MaxHeap,元素item∈ElementType,主要操作有:

●MaxHeap Create( int MaxSize ):创建一 - 个空的最大堆。

●Boolean IsFull( MaxHeap H):判断最大堆H是否已满。

●Insert( MaxHeap H, ElementType item):将元素item插入最大堆H。

●Boolean IsEmpty( MaxHeap H );判断最大堆H是否为空。

●ElementType DeleteMax( MaxHeap H):返回H中最大元素(高优先级)。


1,最大堆的创建:


typedef struct HeapStruct *MaxHeap;
struct HeapStruct {
    ElementType *Elements; /* 存储堆元素首地址*/
    int Size;/*堆的当前元素个数*/
    int Capacity;/*堆的最大容量/
};


MaxHeap Create ( int MaxSize )
{
    /*创建容量为MaxSize的空的最大堆*/
    MaxHeap H = malloc( sizeof( struct HeapStruct ) ) ;
    H->Elements = mal1oc( (MaxSize+1) * sizeof (ElementType) ) ;
    H->Size = 0 ;
    H- >Capacity = MaxSize;
    H->Elements[0] = MaxData ;
        /*定义“哨兵”为大于堆中所有可能元素的值,便于以后更快操作*/
    return H ;
}


2,最大堆的插入:


083507f9de9c4b8c9acd69bed2026be5.png


将新增结点插入到从其父结点到根结点的有序序列中


void Insert( MaxHeap H,ElementType item )
{ /*将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵*/
    int i;
    if ( IsFu11(H) ) {
        printf ("最大堆已满") ;
        return;
    }
    i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置*/
    for ( ; H->E1ements[i/2] < item ; i/=2 )
        H->E1ements[i] = H->Elements[i/2]; /*向下过滤结点*/
        H->E1ements[i] = item; /*将item插入*/
}


哨兵的作用:i>1


T(N)=O(logN)


3,最大堆的删除:


取出根结点(最大值)元素,同时删除堆的一个结点。


ElementType DeleteMax( MaxHeap H )
{ /*从最大堆8中取出键值为最大的元素,并删除-一个结点*/
    int Parent, Child;
    E1ementType MaxItem,temp ;
    if ( IsEmpty (H) ) {
        printf ("最大堆已为空") ;
        return ;
    }
    MaxItem = H->E1ements[1]; /*取出根结点最大值*/
        /*用最大堆中最后一个元素从根结点开始向上过滤下层结点*/
    temp = H- > Elements[H->Size--] ;
    for( Parent=1; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent ★2 ;
        if( (Child!= H->Size)&&(H->Elements [Chi1d] < H->Elements [Chi1d+1]) )
            Child++; /* Child指向左右子结点的较大者*/
        if( temp >= H-> Elements [Chi1d] ) break ;
        else /*移动temp元素到下一层*/
            H->Elements [Parent] = H->Elements [Child] ;
    }
    H->Elements [Parent] = temp ; 
    return MaxItem :
}


4,最大堆的建立:


建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中


方法1: 通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(N logN)。

方法2:在线性时间复杂度下建立最大堆。

(1)将N个元素按输入顺序存入,先满足完全二叉树的结构特性

(2)调整各结点位置,以满足最大堆的有序特性。


哈夫曼树与哈夫曼编码


一,哈夫曼树的定义


带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值Wk;从根结点到每个叶子结点的长度为Ik,则每个叶子结点的带权路径长度之和就是:


2c80a00958e341ddbddee991a8e87c76.png


最优二叉树或哈夫曼树: WPL最小的二叉树


c810160825d24b35b6363aba99a003c8.png


二,哈夫曼树的构造


每次把权值最小的两棵子树合并


typedef struct TreeNode * Huf fmanTree ;
struct TreeNode {
    int Weight;
    HuffmanTree Left, Right;
}
Huf fmanTree Huffman( MinHeap H )
{     /*假设H->Size个权值已经存在H->Elements []->Weight里*/
    inti;
    Huf fmanTree T ;
    BuildMinHeap(H) ; /*将H->Elements []按权值调整为最小堆*/
    for(i = 1; i < H->Size; i++) { /*做H->Size-1次合并*/
        T = malloc( sizeof( struct TreeNode) ) ; /*建立新结点*/
        T->Left = DeleteMin(H) ;
            /*从最小堆中删除-一个结点, 作为新T的左子结点*/
        T->Right = DeleteMin(H) ;
            /*从最小堆中删除-一个结点,作为新T的右子结点*/
        T->Weight = T->Left->Weight+T->Right->Weight ;
            /*计算新权值*/
        Insert( H,T ) ; /*将新T插入最小堆*/
    }
    T =DeleteMin(H) ;
        //整体复杂度为O(N logN)
    returnT;
}


三,哈夫曼树的特点


没有度为1的结点;

n个叶子结点的哈夫曼树共有2n-1个结点;

哈夫曼树的任意非叶节点的左右子树交换后仍是哈夫曼树;

对同一 组权值{w1 ,W2, .... wn},是否存在不同构的两

棵哈夫曼树呢?

对一组权值{1,2,3,3},不同构的两棵哈夫曼树:


e6706fe57ab24a12a4c4e4090abf5bfa.png


四,哈夫曼编码


给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最少?


[例]假设有一段文本,包含58个字符,并由以下7个字符构: a, e, i,s, t,空格(sp),换行(nl) ;这7个字符出现的次数不同。如何对这7个字符进行编码,使得总编码空间最少?


[分析]

(1)用等长ASCII编码: 58 X8 = 464位;

(2)用等长3位编码: 58 X3= 174位;

(3)不等长编码:出现频率高的字符用的编码短些,出现频率低的字符则可以编码长些?


怎么进行不等长编码?

如何避免二义性?

⑨前缀码prefix code:任何字符的编码都不是另一字符编码的前缀

◆可以无二义地解码


二叉树用于编码

用二叉树进行编码:

(1)左右分支: 0、1

(2)字符只在叶结点上


c7d05620b04642799d5e3a6c01194478.png


ca08fe0a17bf4583bbdd6e436f9aff4e.png


9ec148c7f9624a4d94b9fa7ee2ca492c.png

目录
相关文章
|
3月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
101 1
|
1月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
99 64
|
13天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
57 16
|
1月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
19 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
1月前
|
存储 编译器 C++
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
|
1月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(三)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
1月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(二)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
1月前
|
存储
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(一)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
1月前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
28 0
|
2月前
|
JSON 前端开发 JavaScript
一文了解树在前端中的应用,掌握数据结构中树的生命线
该文章详细介绍了树这一数据结构在前端开发中的应用,包括树的基本概念、遍历方法(如深度优先遍历、广度优先遍历)以及二叉树的先序、中序、后序遍历,并通过实例代码展示了如何在JavaScript中实现这些遍历算法。此外,文章还探讨了树结构在处理JSON数据时的应用场景。
一文了解树在前端中的应用,掌握数据结构中树的生命线

热门文章

最新文章