三、树


一、树


1.1 树的基本概念

NameDescription
结点的度该结点子结点个数
度为2的树至少有三个结点
二叉树可为空

1.2 树的性质

  • 结点数=所有结点的度数+1
  • 度为 m 的树,第i层至多有 mi-1 个结点
  • 高为 h 的 m 叉树,至多有 (mh-1)/(m-1) 个结点
  • n 个结点的 m 叉树,最小高度 ⌈logm(n(m-1)+1)⌉
  • n 个结点的树有 n-1 条边,N1+2N2+3N3+…+mNm=N0+N1+N2+…+Nm

1.3 树的存储结构


1.3.1 双亲表示法

  • 数组+伪指针
  • 找双亲快
#define MAX_TREE_SIZE 100
typedef int TElemType;              // 假设为int

typedef struct PTNode              // 结点结构
{
	TElemType data;	               // 数据域
	int parent;	               	   // 指针域
}PTNode;

typedef struct                	   // 树结构
{
	PTNode nodes[MAX_TREE_SIZE];   // 结点数组
	int r;						   // 根的位置
	int n;		                   // 结点数
}PTree;

在这里插入图片描述


1.3.2 孩子表示法

  • 数组+单链表
  • 找孩子快
#define MAX_TREE_SIZE 	100
typedef char TElemType;

typedef struct CTNode                   // 孩子结点
{
	int child;			                  
	struct CTNode *next;
} *ChildPtr;

typedef struct                          // 表头结构 
{	
	TElemType data;		
	int parent;			
	ChildPtr firstchild;	
} CTBox;

typedef struct                          // 树结构
{
	CTBox nodes[MAX_TREE_SIZE];	        // 表头数组
	int r, n;                           // 根的位置和结点数
}

在这里插入图片描述


1.3.3 孩子兄弟表示法

  • 二叉链表(左孩子、右兄弟)
  • 找孩子快
typedef struct CSNode{
	TElemType data;
	struct CSNode *firstchild, *rightsib;
}CSNode,*CSTree;

1.4 树、森林与二叉树之间的转换


1.5 树、森林与二叉树的遍历的对应关系

森林二叉树
先根遍历先序遍历先序遍历
后根遍历中序遍历中序遍历

二、二叉树


2.1 二叉树的性质

  • 是有向树
  • 一般二叉树性质:
    • N0+N1+N2=N1+2N2+1故 N0=N2+1
    • 第 i 层,至多有 2i-1 个结点
    • 高为 h,至多有 2h-1 个结点
  • 完全二叉树性质:
    • i 的双亲结点i/2
    • i 的左孩子2i,右孩子2i+1
    • i 所在层次 ⌈log2(i+1)⌉
    • n 个结点完全二叉树高 ⌈log2(n+1)⌉

2.2 二叉树的存储结构


2.2.1 顺序存储

  • 适合完全二叉树

2.2.2 链式存储

typedef struct TNode *Position;
typedef Position BinTree; /* 二叉树类型 */
struct TNode { /* 树结点定义 */
    ElementType Data; /* 结点数据 */
    BinTree Left;     /* 指向左子树 */
    BinTree Right;    /* 指向右子树 */
};

三、二叉树的遍历


3.1 递归(先、中、后)

//      先序遍历
void PreorderTraversal( BinTree BT ) {
    if( BT ) {
        printf("%d ", BT->Data );
        PreorderTraversal( BT->Left );
        PreorderTraversal( BT->Right );
    }
}
//      中序遍历
void InorderTraversal( BinTree BT ) {
    if( BT ) {
        InorderTraversal( BT->Left );
        /* 此处假设对BT结点的访问就是打印数据 */
        printf("%d ", BT->Data); /* 假设数据为整型 */
        InorderTraversal( BT->Right );
    }
}
//      后序遍历
void PostorderTraversal( BinTree BT ) {
    if( BT ) {
        PostorderTraversal( BT->Left );
        PostorderTraversal( BT->Right );
        printf("%d ", BT->Data);
    }
}

3.2 非递归(先、中、后)(堆栈)

//      中序遍历
void InOrderTraversal(BinTree BT) {
    BinTree T=BT;
    Stack S=CreatStack(MaxSize); //创建并初始化堆栈
    while(T||!IsEmpty(S)) { //树、堆都不空
        while(T) {
            Push(S,T);  //一直向左将结点入栈
            T=T->Left;
        }
        if(!IsEmpty(S)) {
            T=Pop(S);
            printf("%5d",T->Data);
            T=T->Right;
        }
    }
}
//      先序遍历
void PreOrderTraversal(BinTree BT) {
    BinTree T=BT;
    Stack S=CreatStack(MaxSize); //创建并初始化堆栈
    while(T||!IsEmpty(S)) { //树、堆都不空
        while(T) {
            printf("%5d",T->Data);  //遇到就访问结点
            Push(S,T);  //一直向左将结点入栈
            T=T->Left;
        }
        if(!IsEmpty(S)) {
            T=Pop(S);
            T=T->Right;
        }
    }
}
//      后序遍历
void PostOrderTraversal(BinTree BT) {
    BinTree T=BT;
    BinTree Exist=NULL;
    Stack S=CreatStack(MaxSize); //初始化堆
    while(T||!IsEmpty(S)) {
        while(T && T->Left!=Exist && T->Right!=Exist) { //是否遍历完,左孩子或右孩子是否已入栈
            Push(S,T); //从(子)树根向左将结点入栈
            T=T->Left;
        }
        if(!IsEmpty(S)) {
            T=GetTop(S); //获取栈顶元素
            if(T->Right && T->Right!=Exist) { //右孩子存在且未入过栈
                T=T->Right; //如果左边最后的结点有右子树,继续上述入栈操作
            } else {
                T=Pop(S); //没有右子树了,就出栈
                printf("%5d",T->Data); //读根
                Exist=T; //存放已读根的左孩子
                T=GetTop(S); //获取栈顶元素,即返回已读根的父结点
            }
        }
    }
}

3.3 层序遍历(队列)

void LevelorderTraversal ( BinTree BT ) {
    Queue Q;
    BinTree T;
    if ( !BT )
        return; /* 若是空树则直接返回 */
    Q = CreatQueue(); /* 创建空队列Q */
    AddQ( Q, BT );
    while ( !IsEmpty(Q) ) {
        T = DeleteQ( Q );
        printf("%d ", T->Data); /* 访问取出队列的结点 */
        if ( T->Left )            //把出队的结点的左右孩子入队
            AddQ( Q, T->Left );
        if ( T->Right )
            AddQ( Q, T->Right );
    }
}

3.4 二叉树遍历的应用

// 1.输出二叉树的叶子结点:输出前加判定即可
//      注意:二叉树前中后遍历对结点的访问顺序是一样的,仅仅是打印时机不一样,
//            对于叶子结点,由于没有左右根,故访问到叶子结点都会直接打印出来,
//            故对于叶子结点无论哪种遍历方式,打印出的叶子结点序列都一致
void PreorderTraversal( BinTree BT ) {
    if( BT ) {
        if(!BT->Left&&!BT->Right)
            printf("%d ", BT->Data );
        PreorderTraversal( BT->Left );
        PreorderTraversal( BT->Right );
    }
}

// 2.求二叉树高度:利用后序遍历
int PostOrderGetHeight(BinTree BT){
    int HL, HR, MaxH;
    if(BT){
        HL=PostOrderGetHeight(BT->Left);
        HR=PostOrderGetHeight(BT->Right);
        MaxH=(HL>HR)?HL:HR; //返回左右子树最大深度
        return(MaxH+1;)
    }else{
        return 0;
    }
}

// 3.二元运算表达式树
//      叶结点是操作数、度为2的结点是操作符
//      先序、中序、后续遍历 得到 前缀、中缀、后缀表达式
//      中缀表达式涉及到运算的优先级问题,输出左子树前先加一个左括号,输出右子树后再加右括号


// 4.两种遍历序列确定二叉树
//      已知中序和任一其他的遍历序列都可确定二叉树

四、二叉搜索树

//查找

//递归函数效率低
Position Find(ElementType X, BinTree BST) {
    if(!BST)
        return NULL;
    if(X > BST->Data)
        return Find(X, BST->Right);
    else if(X < BST->Data)
        return Find(X, BST->Left);
    else
        return BST;
}
//可将尾递归改为迭代函数,查找效率取决于树的高度
Position IterFind (ElementType X, BinTree BST) {
    while(BST) {
        if(X > BST->Data)
            BST = BST->Right;
        else if(X < BST->Data)
            BST = BST->Left;
        else
            return BST;
    }
    return NULL;
}
//找最小值、最大值
Position FindMin(BinTree BST) { //递归法
    if(!BST)
        return NULL;
    else if(!BST->Left)
        return BST;
    else
        return FindMin(BST->Left);
}
Position FindMax(BinTree BST) { //迭代法
    if(BST)
        while(BST0->Right)
            BST=BST->Right;
    return BST;
}


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


//删除
BinTree Delete( BinTree BST, ElementType X ) {
    Position Tmp;

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

五、平衡二叉树

  • h 层AVL树的最少结点数:
    • N1 = 1N2 = 2
    • Nh = N(h-1) + N(h-2) + 1
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode {
    ElementType Data; /* 结点数据 */
    AVLTree Left;     /* 指向左子树 */
    AVLTree Right;    /* 指向右子树 */
    int Height;       /* 树高 */
};

int Max ( int a, int b ) {
    return a > b ? a : b;
}

AVLTree SingleLeftRotation ( AVLTree A ) {
    /* 注意:A必须有一个左子结点B */
    /* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */

    AVLTree B = A->Left;
    A->Left = B->Right;
    B->Right = A;
    A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1;
    B->Height = Max( GetHeight(B->Left), A->Height ) + 1;

    return B;
}

AVLTree DoubleLeftRightRotation ( AVLTree A ) {
    /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */
    /* 将A、B与C做两次单旋,返回新的根结点C */

    /* 将B与C做右单旋,C被返回 */
    A->Left = SingleRightRotation(A->Left);
    /* 将A与C做左单旋,C被返回 */
    return SingleLeftRotation(A);
}

/*************************************/
/* 对称的右单旋与右-左双旋请自己实现 */
/*************************************/

AVLTree Insert( AVLTree T, ElementType X ) {
    /* 将X插入AVL树T中,并且返回调整后的AVL树 */
    if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */
        T = (AVLTree)malloc(sizeof(struct AVLNode));
        T->Data = X;
        T->Height = 0;
        T->Left = T->Right = NULL;
    } /* if (插入空树) 结束 */

    else if ( X < T->Data ) {
        /* 插入T的左子树 */
        T->Left = Insert( T->Left, X);
        /* 如果需要左旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 )
            if ( X < T->Left->Data )
                T = SingleLeftRotation(T);      /* 左单旋 */
            else
                T = DoubleLeftRightRotation(T); /* 左-右双旋 */
    } /* else if (插入左子树) 结束 */

    else if ( X > T->Data ) {
        /* 插入T的右子树 */
        T->Right = Insert( T->Right, X );
        /* 如果需要右旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 )
            if ( X > T->Right->Data )
                T = SingleRightRotation(T);     /* 右单旋 */
            else
                T = DoubleRightLeftRotation(T); /* 右-左双旋 */
    } /* else if (插入右子树) 结束 */

    /* else X == T->Data,无须插入 */

    /* 别忘了更新树高 */
    T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1;

    return T;
}

六、堆

  • 结构性:用数组表示的完全二叉树
  • 有序性:任一结点的KEY是其子树所有结点的最大值(最小值)
    • 大顶堆 MaxHeap
    • 小顶堆 MinHeap
typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
    ElementType *Data; /* 存储元素的数组 */
    int Size;          /* 堆中当前元素个数 */
    int Capacity;      /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */

#define MAXDATA 1000  /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */

//堆的初始化,建立一个空堆
MaxHeap CreateHeap( int MaxSize ) {
    /* 创建容量为MaxSize的空的最大堆 */

    MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
    H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType)); //Data[0]为哨兵
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/

    return H;
}

//判满
bool IsFull( MaxHeap H ) {
    return (H->Size == H->Capacity);
}

//插入
bool Insert( MaxHeap H, ElementType X ) {
    /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
    int i;

    if ( IsFull(H) ) {
        printf("最大堆已满");
        return false;
    }
    i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
    for ( ; H->Data[i/2] < X; i/=2 ) //最后的i到父结点位置了
        H->Data[i] = H->Data[i/2]; /* 上滤X */
    H->Data[i] = X; /* 将X插入 */
    return true;
}

#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
//判空
bool IsEmpty( MaxHeap H ) {
    return (H->Size == 0);
}

//删除堆顶元素
ElementType DeleteMax( MaxHeap H ) {
    /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
    int Parent, Child;
    ElementType MaxItem, X;

    if ( IsEmpty(H) ) {
        printf("最大堆已为空");
        return ERROR;
    }

    MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */

    /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
    X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */
    for( Parent=1; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] )
            break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;

    return MaxItem;
}


/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p ) {
    /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
    int Parent, Child;
    ElementType X;

    X = H->Data[p]; /* 取出根结点存放的值 */
    for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] )
            break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
}

void BuildHeap( MaxHeap H ) {
    /* 调整H->Data[]中的元素,使满足最大堆的有序性  */
    /* 这里假设所有H->Size个元素已经存在H->Data[]中 */

    int i;

    /* 从最后一个结点的父节点开始,到根结点1 */
    for( i = H->Size/2; i>0; i-- )
        PercDown( H, i );
}

七、哈夫曼树与哈夫曼编码

  • 带权路径长度WPL
  • 哈夫曼树(最优二叉树):WPL最小的树
  • 哈夫曼树特点:
    • 没有度为1的结点
    • n个叶子结点的哈夫曼树共有2n-1个结点(n2=n0-1)
    • 任意非叶结点的左右子树交换后仍是哈夫曼树
    • 对同一组权值,可能存在不同构的几个哈夫曼树,但是WPL是一样的
  • 哈夫曼编码:
    • 不等长编码
    • 前缀码:避免二义性,任何字符的编码不是另一个字符编码的前缀
    • 用二叉树进行编码:
      • 可左右分支 0、1
      • 字符只在叶结点上(如果有字符在非叶结点上,则其子树上的叶结点编码不是前缀码)
    • 利用哈夫曼树构造前缀码代价最小
typedef struct TreeNode *HuffmanTree;
struct TreeNode {
    int Weight;
    HuffmanTree Left, Right;
};

HuffmanTree Huffman(MinHeap H) {
    int i;
    HuffmanTree T;
    BuildMinHeap(H); //利用最小堆选取两个最小值
    for(int i=1; i<H->Size; i++) { //H->Size - 1 次合并即可
        T=(HuffmanTree)malloc(sizeof(struct TreeNode));
        T->Left=DeleteMin(H);
        T->Right=DeleteMin(H);
        T->Weight=T->Left+T->Right->Weight;
        Insert(H,T);
    }
    T=DeleteMin(H); //堆中最后的结点即为树根
    return T;
}

八、集合及运算(并查集)

  • 集合运算:交、并、补、差、判断是否属于某一集合
  • 并查集:集合并、查某元素属于什么集合
  • 并查集问题中集合的存储:
    • 1.树表示,每个结点代表一个集合元素
      双亲表示法,孩子指向双亲
    • 2.数组表示,data + parent (根的parent为 -1,或 -集合中元素个数)
typedef struct {
    ElementType Data;
    int Parent;
} SetType;

int Find(SetType S[], ElementType X) {
    int i;
    for(i=0; i<MaxSize&&S[i].Data!=X; i++); //i为X在数组中的下标
    if(i>=MaxSize)
        return -1;
    for(; S[i].Parent>=0; i=S[i].Parent); //找到根结点在数组中的下标
    return i; //找到X所属集合,返回树根结点在数组S中的下标
}

void Union(SetType S[], ElementType X1, ElementType X2) {
    int Root1, Root2;
    Root1=Find(S,X1);
    Root2=Find(S,X2);
//    if(Root1!=Root2)
//        S[Root2].Parent=Root1;
    //为改善合并后查找的性能,将小的集合合并到相对大的集合中
    //根的Parent为 该集合中元素个数的相反数
    if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */
        S[Root2] += S[Root1];     /* 集合1并入集合2  */
        S[Root1] = Root2;
    } else {                       /* 如果集合1比较大 */
        S[Root1] += S[Root2];     /* 集合2并入集合1  */
        S[Root2] = Root1;
    }
}

九、Practice

03-树1 树的同构
03-树2 List Leaves
03-树3 Tree Traversals Again
04-树4 是否同一棵二叉搜索树
04-树5 Root of AVL Tree
04-树7 二叉搜索树的操作集
05-树7 堆中的路径
05-树8 File Transfer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值