【转载】树专题

1 关于树的重要定义

  • 节点的高度=从下往上,节点到叶子结点的最长路径(边数)。所以叶子结点高度为1
  • 节点的深度=从上往下,根节点到这个节点所经历的边数。所以根节点深度为0
  • 节点的层数(层次)=节点深度+1。根节点在1层。(这个地方有时候要看题目的定义的,不绝对)
  • 树的高度=根节点的高度
  • 树的深度=树的高度

img

普通的树用儿子兄弟表示法后,再向右旋转45度,就可以看到一颗二叉树了。

1.1 分类

  • 斜二叉树 skewed Binary Tree
  • 完美二叉树,满二叉树 Perfect Binary Tree, Full Binary Tree, 除了叶子节点外,每个节点都有左右两个子节点
  • 完全二叉树 Complete Binary Tree,n个节点从上到下,从左至右编号后,与满二叉树中相同编号的节点位置相同

img

斜二叉树和完美二叉树

img

完全二叉树

1.2 二叉树的重要性质

  • 对于n个结点的树(不一定是二叉树),一定有n-1条边
  • 第i层( i>=1)的最大结点数是2^(i-1)
  • k层(k>=1)的二叉树最大结点总数2^k-1
  • 对于任何非空二叉树T,若n0表示叶子结点的个数,n2表示度为2的非叶节点个数,那么满足关系n0=n2+1

2 二叉树的表示和基本操作

表示与存储:
二叉树可以使用数组或者链表进行表示。

一般情况下,对于完全二叉树我们使用数组,其他类型使用链表。因为斜二叉树使用数组表示会浪费大量空间,k层的斜二叉树需要2^k数组空间。

基本操作:

  1. 创建二叉树
  2. 判断是否为空
  3. 前序遍历(DFS)
  4. 中序遍历(DFS)
  5. 后序遍历(DFS)
  6. 层序遍历(BFS)

遍历二叉树的应用:

  1. 输出二叉树的所有叶子结点
  2. 求树的高度
  3. 二元运算表达式树及其遍历
  4. 根据某两种遍历,确定一颗二叉树
  5. 树的同构判别

2.1 数组存储完全二叉树

img

数组顺序存储

注意

  1. 从数组下标为1的位置开始存储(如果从0开始排序,左右子树的下标位置都要加1)
  2. 在数组中的顺序直接就是“层序遍历”的顺序,从上至下,从左至右
  3. 判断数组中某个结点(下标为k)是否为叶子结点的标志:2*k>N , N为结点总数
  4. 判断断数组中某个结点(下标为k)是否为空结点的标志:k>N , N为结点总数

04-树6 Complete Binary Search Tree (30分)
给定N (≤1000)个非负整数,创建一个完全二叉树形式的搜索树,输出层序遍历结果。
输入样例:
10
1 2 3 4 5 6 7 8 9 0
输出样例:
6 3 8 1 5 7 9 0 2 4

结题报告:
要求是完全二叉树,那么直接考虑用数组,而且数组从左到右就是层序遍历的顺序。
输入数据不一定有序,需要先排序。

这道题有两种解法,首先是陈越姥姥视频中的解法,根据完全二叉树的性质,递归地找到该有序数组中属于搜索树中点的那个点,依次放入结果序列中。

#include <stdio.h>
#include <vector>
#include <algorithm>
#include<cmath>
using namespace std;

vector<int> Arr; // 输入数据
int T[1001]; // 二叉搜索树数组,从1开始

// 计算n个结点的完全二叉树左边有多少个结点
int getLeftLength(int n){
    // n 为结点总数,X为最后一层叶子节点总数
    // 2^H -1 +X = n
    int H = log2(n+1);
    int X = n+1-pow(2,H);
    int maxLeaf = pow(2,H-1);
    int leftx = (X<maxLeaf)? X: maxLeaf;
    int L = pow(2,H-1)-1+leftx;
    return L;
}

void solve(int ALeft, int ARight, int TRoot){
    int n = ARight - ALeft +1;
    if (n==0){
        return;
    }
    int L = getLeftLength(n); // 左子树有L个结点
    T[TRoot] =  Arr[ALeft+L];
    int LeftRoot = TRoot*2;
    int RightRoot = LeftRoot+1;
    solve(ALeft, ALeft+L-1, LeftRoot);
    solve(ALeft+L+1, ARight, RightRoot);
}
int main() {
    int N = 0;
    scanf("%d", &N);
    for (int i=0; i<N;i++){
        int curr = 0;
        scanf("%d", &curr);
        Arr.push_back(curr);
    }
    sort(Arr.begin(), Arr.end());

    solve(0, Arr.size()-1, 1);

    printf("%d", T[1]);
    for (int i=2; i<=N;i++){
        printf(" %d", T[i]);
    }
    return 0;
}

}

方法2更加巧妙,其实输入数据排序后,从左到右是完全二叉树的中序遍历。所以问题转化成通过完全二叉树的中序遍历得到层序遍历。

#include <stdio.h>
#include <vector>
#include <algorithm>
#include<cmath>
using namespace std;

int N = 0;
int indexArr = 0;
vector<int> Arr;
int T[1001]; // 从1开始排序

void inOrder(int root){
    if (root >N){
        return;
    }
    int left = root*2;
    int right = left + 1;
    inOrder(left);
    T[root] = Arr[indexArr++];
    inOrder(right);
}

int main() {
    scanf("%d", &N);
    for (int i=0; i<N;i++){
        int curr = 0;
        scanf("%d", &curr);
        Arr.push_back(curr);
    }
    sort(Arr.begin(), Arr.end());

    inOrder(1);

    printf("%d", T[1]);
    for (int i=2; i<=N;i++){
        printf(" %d", T[i]);
    }
    return 0;
}

2.2 链表表示的二叉树

img

链表存储

先序、中序、后序三种遍历的过程,经历的结点的路线是一样的,只是访问结点的时机不同。每个结点都有3次碰到的机会,先序是在第一次碰到该结点的时候就访问,中序是第二次,后续是第三次。

img

三种遍历的关联性

递归方式进行的3种遍历

    // 前序遍历
    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种遍历
先序、中序、后序遍历的非递归方法需要借助栈,其实可以理解为深度优先搜索

// 先序遍历
void PreorderTraversal( BinTree BT ){
    BinTree T = BT;
    stack<BinTree> S;
    while (T || !S.empty()){
        while (T){
            S.push(T);
            printf("%d ", T->Data);
            T = T->Left;
        }
        if (!S.empty()){
            T = S.top();
            S.pop();
            T = T->Right;
        }
    }
}

// 中序遍历
void InorderTraversal( BinTree BT ){
    BinTree T = BT;
    stack<BinTree> S;
    while (T || !S.empty()){
        while (T){
            S.push(T);
            T = T->Left;
        }
        if (!S.empty()){
            T = S.top();
            S.pop();
            printf("%d ", T->Data);
            T = T->Right;
        }
    }
}

// 后序遍历
// 使用两个栈,s和s2。使用 【根->右->左】 的方式,在第一次遇到结点时,就压入栈s2中,那么最后遍历完了,根据栈的性质,s2依次弹出的就是【左->右->根】的后续遍历了
  void PostorderTraversal( BinTree BT ){
        BinTree T = BT;
        stack<BinTree> s;
        stack<ElementType> s2;
        while (T || !s.empty()){
            while (T){
                s.push(T);
                s2.push(T->Data);
                T = T->Right;
            }
            if (!s.empty()){
                T = s.top();
                s.pop();
                T = T->Left;
            }
        }
        while (!sr.empty()){
            printf("%5d", s2.top());
            s2.pop();
        }
    }

层次遍历
层次遍历需要借助队列,其实可以理解为广度优先搜索。

void LevelorderTraversal( BinTree BT ){
    queue<BinTree> Q;
    BinTree T;
    if (!BT) return;
    Q.push(BT);
    while (Q.empty() == false){
        T = Q.front();
        Q.pop();
        printf("%d ", T->Data);
        if (T->Left){
            Q.push(T->Left);
        }
        if (T->Right){
            Q.push(T->Right);
        }

    }
}

习题:leetcode-二叉树的层序遍历

  • 这道题注意对于每层节点的处理

习题:二叉树的序列化和反序列化

  • 这道题可以用先序遍历或者层序遍历实现

2.3 不需要创建树而得到遍历的方法

03-树3 Tree Traversals Again (25分)
根据题目描述的中序遍历中stack相关操作,输出树的后序遍历。
输入样例:
第一个正整数N(N<=30)表示结点个数

6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop

输出样例:
3 4 2 6 5 1

解题报告

这道题push操作的顺序其实是树的前序遍历:
1 2 3 4 5 6
而pop操作对应的是树的中序遍历:
3 2 4 1 6 5
也就是说不需要创建树,我们可以根据前序遍历数组和中序遍历数组,直接求得后续遍历数组。

img

核心算法

#include <stdio.h>
#include <string>
#include <vector>
#include <stack>
#include <iostream>
using namespace std;


vector<int> preOarr;
vector<int> inOarr;
int postVector[31];

// preL: 前序遍历数组最左元素下标
// inL: 中序遍历数组最左元素下标
// postL: 后序遍历数组最左元素下标
// n: 当前处理的元素个数
void solve(int preL, int inL, int postL, int n){
    if (n==0){
        return;
    }
    if (n==1){
        postVector[postL] = preOarr[preL];
    }
    int root = preOarr[preL];
    postVector[postL+n-1] = root;
    // 获取root在中序遍历中的下标位置i
    int i=0;
    for (i=0;i<n;i++){
        if (inOarr[inL+i] == root){
            break;
        }
    }
    int lenL = i; // 左边数组长度
    int lenR = n-i-1; // 右边数组长度
    solve(preL+1, inL, postL, lenL);
    solve(preL+lenL+1,inL+lenL+1, postL+lenL, lenR);
}
int main() {
    int N = 0;
    scanf("%d", &N);
    stack<int> s1;
    for (int i=0; i<2*N;i++){
        string str;
        cin>>str;
        if (str == "Push"){ // Push 2
            int currNum = 0;
            scanf("%d", &currNum);
            preOarr.push_back(currNum);
            s1.push(currNum);
        } else { // Pop
            if (!s1.empty()){
                int topNum =  s1.top();
                s1.pop();
                inOarr.push_back(topNum);
            }
        }
    }

    int preL = 0;
    solve(0,0,0,N);

    printf("%d", postVector[0]);
    for (int i=1;i<N;i++){
        printf(" %d", postVector[i]);
    }

    return 0;
}

这是最开始使用的方法,我仍然是使用了链表去存储了树结构了,虽然用在这里显得有点重了,但是可以作为“已知先序和中序遍历,构建二叉树”的例子记录在这里。

#include <stdio.h>
#include <stack>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#define ElementType int
using namespace std;

typedef struct TNode *BinTree;
struct TNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
};
BinTree RootTree = NULL;
vector<ElementType> preOarr;
vector<ElementType> inOarr;
vector<ElementType> postVector;

// 当前先序遍历区间[preL, preR]
// 当前中序遍历区间[inL, inR]
BinTree CreateTree(int preL, int preR, int inL, int inR){
    if (preL > preR){
        return NULL;
    }
    TNode* RootTree = new TNode;
    RootTree->Data = preOarr[preL];
    int k = inL;
    for (k=inL; k<=inR;k++){
        if (inOarr[k] == RootTree->Data){
            break;
        }
    }
    int numleft = k - inL;
    RootTree->Left = CreateTree(preL+1,preL+numleft,inL,k-1);
    RootTree->Right = CreateTree(preL+numleft+1,preR,k+1,inR);
    return RootTree;
}

void PostTravelsal(BinTree BT){
    if (BT){
        PostTravelsal(BT->Left);
        PostTravelsal(BT->Right);
        postVector.push_back(BT->Data);
    }
}
int main() {
    int N = 0;
    scanf("%d", &N);
    stack<ElementType> s1;
    for (int i=0; i<2*N;i++){
        string str;
        cin>>str;
        if (str == "Push"){ // Push 2
            int currNum = 0;
            scanf("%d", &currNum);
            preOarr.push_back(currNum);
            s1.push(currNum);
        } else { // Pop
            if (!s1.empty()){
                int topNum =  s1.top();
                s1.pop();
                inOarr.push_back(topNum);
            }
        }
    }

    RootTree = CreateTree(0,N-1,0,N-1);
    PostTravelsal(RootTree);

    printf("%d", postVector[0]);
    for (int i=1;i<postVector.size();i++){
        printf(" %d", postVector[i]);
    }

    return 0;
}

3 二叉搜索树BST

定义
二叉搜索树又叫二叉查找树

  • 空树,或者:
  • 其左子树和右子树也是二叉搜索树
  • 左子树的所有结点都小于等于根节点
  • 右子树的所有结点都大于根节点

二叉搜索树的基本操作

  • 建树
  • 查找
  • 插入
  • 删除

其中最麻烦的操作是删除。删除要保证操作结束后仍然是一颗二叉搜索树。如下图要删除结点5,那么找到比5大的最小元素6(右子树的最小值),覆盖结点5, 由于6是最小元素,肯定没有左子树,结点6原来的位置最多有右子树,提上来。

img

删除结点5

二叉搜索树性质

  • 二叉搜索树的中序遍历是有序递增的
#include <stdio.h>
#include <stdlib.h>
#include <queue>

using namespace std;

typedef int ElementType;
typedef struct TNode *Position;
typedef Position BinTree;
struct TNode{
    ElementType Data;
    BinTree Left;
    BinTree Right;
};

void PreorderTraversal( BinTree BT );
void InorderTraversal( BinTree BT );
void PostorderTraversal( BinTree BT );
void LevelorderTraversal( BinTree BT );

BinTree Insert( BinTree BST, ElementType X );
BinTree Delete( BinTree BST, ElementType X );
Position Find( BinTree BST, ElementType X );
Position FindMin( BinTree BST );
Position FindMax( BinTree BST );

int main()
{
    BinTree BST, MinP, MaxP, Tmp;
    ElementType X;
    int N, i;

    BST = NULL;
    scanf("%d", &N);
    for ( i=0; i<N; i++ ) {
        scanf("%d", &X);
        BST = Insert(BST, X);
    }

    printf("==遍历:==\n");
    printf("先序遍历:");
    PreorderTraversal(BST);
    printf("\n");
    printf("中序遍历:");
    InorderTraversal(BST);
    printf("\n");
    printf("后序遍历:");
    PostorderTraversal(BST);
    printf("\n");
    printf("层次遍历:");
    LevelorderTraversal(BST);
    printf("\n");

    printf("==查询结点:==\n");
    MinP = FindMin(BST);
    MaxP = FindMax(BST);
    scanf("%d", &N);
    for( i=0; i<N; i++ ) {
        scanf("%d", &X);
        Tmp = Find(BST, X);
        if (Tmp == NULL) printf("%d is not found\n", X);
        else {
            printf("%d is found\n", Tmp->Data);
            if (Tmp==MinP) printf("%d is the smallest key\n", Tmp->Data);
            if (Tmp==MaxP) printf("%d is the largest key\n", Tmp->Data);
        }
    }

    printf("==删除搜索树上的结点:==\n");
    scanf("%d", &N);
    for( i=0; i<N; i++ ) {
        scanf("%d", &X);
        BST = Delete(BST, X);
    }
    printf("中序遍历:"); InorderTraversal(BST); printf("\n");

    return 0;
}

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);
        printf("%d ", BT->Data);
        InorderTraversal(BT->Right);
    }
}

void PostorderTraversal( BinTree BT ){
    if (BT){
        PostorderTraversal(BT->Left);
        PostorderTraversal(BT->Right);
        printf("%d ", BT->Data);
    }
}

void LevelorderTraversal( BinTree BT ){
    queue<BinTree> Q;
    BinTree T;
    if (!BT) return;
    Q.push(BT);
    while (Q.empty() == false){
        T = Q.front();
        Q.pop();
        printf("%d ", T->Data);
        if (T->Left){
            Q.push(T->Left);
        }
        if (T->Right){
            Q.push(T->Right);
        }

    }
}

BinTree Insert( BinTree BST, ElementType X ){
    if (!BST){
        BST = (BinTree) malloc(sizeof(struct TNode));
        BST->Data = X;
        BST->Left = NULL;
        BST->Right = NULL;
    } else {
        if (X > BST->Data){
            BST->Right =Insert(BST->Right, X);
        } else if (X < BST->Data){
            BST->Left =Insert(BST->Left, X);
        }
    }
    return BST;
}

Position Find( BinTree BST, ElementType X ){
    while(BST){
        if (BST->Data == X){
            return BST;
        } else if (BST->Data < X){
            BST = BST->Right;
        } else {
            BST = BST->Left;
        }
    }
    return NULL;
}

Position FindMin( BinTree BST ){
    if (BST){
        while (BST->Left){
            BST = BST->Left;
        }
    }
    return BST;
}

Position FindMax( BinTree BST ){
    if (BST){
        while(BST->Right){
            BST =BST->Right;
        }
    }
    return BST;
}


BinTree Delete( BinTree BST, ElementType X ){
    Position Temp;
    if (!BST) {
        printf("Not Found\n");
    } else{
        if (X > BST->Data){
            BST->Right =Delete( BST->Right, X );
        } else if (X < BST->Data){
            BST->Left = Delete( BST->Left, X );
        } else {
            if (BST->Left && BST->Right){
                Temp = FindMin(BST->Right);
                BST->Data = Temp->Data;
                BST->Right =Delete( BST->Right, BST->Data);
            } else {
                Temp = BST;
                if (!BST->Left){
                    BST = BST->Right;
                } else if (!BST->Right){
                    BST = BST->Left;
                }
                free(Temp);
            }
        }
    }
    return BST;
}

img

运行结果

4 平衡二叉树AVL

定义

  • 空树,或者:
  • 任一结点左子树和右子树的高度差的绝对值小于等于1的二叉搜索树|BF(T)| <=1
  • BF:Balance Factor 平衡因子,BF(T)=hL - hR
  • 注意平衡二叉树仍然是二叉搜索树,只是在二叉搜索树的基础上增加了“平衡”的要求

平衡二叉树的基本操作

  • 建树
  • 查找,与二叉查找树相同
  • 插入, 需要进行调整
  • 删除,复杂

平衡二叉树的调整

LL旋转:麻烦节点在发现者的左子树的左边(X < T->Left->Data)

img

SingleLeftRotation

img

SingleLeftRotation

img

SingleLeftRotation

RR旋转:麻烦节点在发现者的右子树的右边

img

SingleRightRotation

LR旋转:麻烦节点在发现者的左子树的右边

RL旋转:麻烦节点在发现者的右子树的左边
右-左双旋, 一次SingleLeftRotation,一次SingleRightRotation

img

图片.png

img

图片.png

04-树5 Root of AVL Tree (25分)

#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
typedef struct AVLNode* Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode {
    ElementType Data; /* 结点数据 */
    AVLTree Left;     /* 指向左子树 */
    AVLTree Right;    /* 指向右子树 */
    int Height;       /* 树高 */
};

int GetHeight(AVLTree A) {
    if (A) {
        return A->Height;
    }
    else {
        return 0;
    }
}

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 SingleRightRotation(AVLTree A)
{
    AVLTree B = A->Right;
    A->Right = B->Left;
    B->Left = A;
    A->Height = Max(GetHeight(A->Left), GetHeight(A->Right)) + 1;
    B->Height = Max(GetHeight(B->Right), 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 DoubleRightLeftRotation(AVLTree A)
{
    A->Right = SingleLeftRotation(A->Right);

    return SingleRightRotation(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;
}

int main() {
    AVLTree Head = NULL;
    int n = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int x = 0;
        scanf("%d", &x);
        Head = Insert(Head, x);
    }


    printf("%d", Head->Data);
}

5 堆与哈夫曼树

堆定义
堆是一颗完全二叉树,可以用数组来进行表示。

  • 大顶堆:任意节点的数值是其左右子树所有节点的最大值
  • 小顶堆:任意节点的数值是其左右子树所有节点的最小值

堆基本操作

  • 初始化size=0的堆
  • 插入数据(向下过滤)
  • 删除数据
  • 调整堆

大顶堆

    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));
        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 )
            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 );
    }

05-树7 堆中的路径 (25分)
构造小顶堆,并输出路径
输入样例:

5 3
46 23 26 24 10
5 4 3

输出样例:

24 23 10
46 23 10
26 10

img

图片.png

#include <stdlib.h>
#include <stdio.h>

#define MINDATA -10001  /* 该值应根据具体情况定义为小于堆中所有可能元素的值 */
typedef int ElementType;
typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
    ElementType *Data; /* 存储元素的数组 */
    int Size;          /* 堆中当前元素个数 */
    int Capacity;      /* 堆的最大容量 */
};
typedef Heap MinHeap; /* 最小堆 */


MinHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最小堆 */

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

    return H;
}

bool IsFull( MinHeap H )
{
    return (H->Size == H->Capacity);
}

bool Insert( MinHeap 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 )
        H->Data[i] = H->Data[i/2]; /* 上滤X */
    H->Data[i] = X; /* 将X插入 */
    return true;
}

int main(){
    int N=0, M=0;
    scanf("%d %d", &N, &M);
    MinHeap H = CreateHeap(N);
    for (int i=0; i< N; i++){
        int curr = 0;
        scanf("%d", &curr);
        Insert(H, curr);
    }
    
    for (int i=0; i<M;i++){
        int curr = 0;
        scanf("%d", &curr);
        printf("%d", H->Data[curr]);
        curr = curr /2;
        while (curr>=1){
            printf(" %d", H->Data[curr]);
            curr = curr /2;
        }
        printf("\n");
    }

}

哈夫曼树

  • 哈夫曼树不唯一

6 并查集

数组中,下标表示连接元素的编号,值表示其父节点编号。其中负数都是根节点,负数对应的值表示该集合中元素个数。这种使用负数表示规模的技巧,是对Union方法的优化。
路径压缩,是对Find方法的优化。

img

图片.png

img

图片.png

    #define MAXN 1000                  /* 集合最大元素个数 */
    typedef int ElementType;           /* 默认元素可以用非负整数表示 */
    typedef int SetName;               /* 默认用根结点的下标作为集合名称 */
    typedef ElementType SetType[MAXN]; /* 假设集合元素下标从0开始 */
     
    void Union( SetType S, SetName Root1, SetName Root2 )
    { /* 这里默认Root1和Root2是不同集合的根结点 */
        /* 保证小集合并入大集合 */
        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;
        }
    }
     
    SetName Find( SetType S, ElementType X )
    { /* 默认集合元素全部初始化为-1 */
        if ( S[X] < 0 ) 
            return X; // 返回集合的根
        else
            /* 路径压缩 ,尾递归*/
            // 先找到X的根,然后覆盖X的父节点,最后返回根
            return S[X] = Find( S, S[X] ); 
    }

05-树8 File Transfer (25分)
输入样例:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
S

输出样例:

no
no
yes
There are 2 components.

注意这道题的节点编号是从1N的,我们需要在操作中映射到0N-1位置上

#include <stdio.h>
int SetType[10003];


int Find(int x){
    if (SetType[x] < 0) {
        return x; // x 就是根
    } else {
        return SetType[x] = Find(SetType[x]); // 路径压缩
    }
}

void Union(int RootA, int RootB){
    if (SetType[RootA] < SetType[RootB]){
        SetType[RootA]+=SetType[RootB];
        SetType[RootB] = RootA;
    } else {
        SetType[RootB] += SetType[RootA];
        SetType[RootA] = RootB;
    }

}
void initSet(int n){
    for (int i=0; i<n;i++){
        SetType[i] = -1;
    }
}

void Input_Two(int n){
    int a = 0, b = 0;
    scanf("%d %d\n", &a, &b);
    int RootA = Find(a-1);
    int RootB = Find(b-1);
    Union(RootA, RootB);
}

void Check_Two(int n){
    int a = 0, b = 0;
    scanf("%d %d\n", &a, &b);
    if (Find(a-1) == Find(b-1)){
        printf("yes\n");
    } else {
        printf("no\n");
    }
}

void Check_Whole(int n){
    int count = 0;
    for (int i=0;i<n;i++){
        if (SetType[i] < 0){
            count++;
        }
    }
    if (count == 1){
        printf("The network is connected.");
    } else {
        printf("There are %d components.", count);
    }
}
int main(){
    int N = 0;
    char in;
    scanf("%d\n", &N);
    initSet(N);
    do{
        scanf("%c", &in);
        switch(in){
            case 'I':Input_Two(N);break;
            case 'C':Check_Two(N);break;
            case 'S':Check_Whole(N);break;
        }
    }while (in != 'S');
    return 0;
}

作者:苏wisdom
链接:https://www.jianshu.com/p/88d0aff1ecfc
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我会尽力回答你的问题。针对你的问题,我可以告诉你如何使用Hibernate实现形结构无限级分类。 首先,你需要在实体类中定义一个父级ID属性,用于表示该节点的父级节点。例如: ``` @Entity @Table(name = "category") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; @Column(name = "parent_id") private Long parentId; // getter and setter methods } ``` 然后,你可以使用Hibernate的注解来表示该实体类是一个形结构: ``` @Entity @Table(name = "category") @Tree( rootVal = "0", id = "id", parentId = "parentId", useCache = true, cacheRegion = "treeCache" ) public class Category { // ... } ``` 在上面的代码中,我们使用`@Tree`注解来定义该实体类是一个形结构。其中,`rootVal`属性表示根节点的父级ID,`id`属性表示节点ID,`parentId`属性表示父级节点ID,`useCache`属性表示是否启用缓存,`cacheRegion`属性表示缓存区域的名称。 接下来,你可以使用Hibernate提供的API来操作形结构。例如,你可以使用`Session.getTreeBuilder()`方法来获取一个形结构构建器,然后使用`buildTree()`方法来构建一棵: ``` Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); TreeBuilder<Category> builder = session.getTreeBuilder(Category.class); List<Category> categories = session.createQuery("FROM Category WHERE parentId = 0").list(); Category root = builder.buildTree(categories); tx.commit(); session.close(); ``` 在上面的代码中,我们首先获取了一个Hibernate的Session对象,然后开启了一个事务。接着,我们使用`Session.getTreeBuilder()`方法获取了一个形结构构建器,并使用`buildTree()`方法构建了一棵。 最后,我们提交了事务,并关闭了Session对象。 总的来说,使用Hibernate实现形结构无限级分类并不复杂,只需要在实体类中定义一个父级ID属性,并使用Hibernate提供的注解和API来操作形结构即可。需要注意的是,由于形结构很容易产生循环引用,因此在保存和更新节点时需要特别注意,避免出现死循环。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值