数据结构与算法笔记——树(二叉树、并查集、堆、B树、B+树与红黑树)篇

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

 

 

 


前言

树结构是一种非常重要的数据结构


一、二叉树的存储结构

1.二叉树的顺序存储结构:

    用一组连续的存储单元依次从上到下、从左到右存储完全二叉树上的结点元素,对于一般的二叉树需要添加存储一些空的结点。因此对于普通的二叉树来说不适合用顺序存储浪费空间,该存储结构适合于完全二叉树。

注意:在树的顺序存储中数组下标仅表示节点的编号,而二叉树的顺序存储结构中数组的下标既表示节点的编号,又表示树中各结点的关系。(节点编号为数组下标编号为i,其左右孩子节点的数组下标为2i和2i+1),根结点存储在第一个位置。

总之,二叉树属于树,二叉树可以用树的存储结构来存储,但树不能用二叉树的存储结构来存储

练手题:洛谷P1305新二叉树

#include

#define MAX 1e3

struct Data{
    int a;
};

Data[MAX];

2.二叉树的链式存储(二叉链表)

#include

struct Data{
    int a;
};

typedef struct BiTNode{
    Data s;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

BiTree L;   //表示一棵树

二、二叉树的遍历、创建和线索化

二叉树的遍历是进行二叉树各种操作的基础,可以在遍历的过程中对结点进行各种操作,如对一个已知树,可求结点的双亲或孩子结点,判断结点的层数等,也可以在遍历的过程中生成结点。此外,还有递归使用分治策略求结点的高度。

//递归实现
void PreOrder(BiTree T){
    if(T){
        printf("%d", T->s.a);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

void InOrder(BiTree T){
    if(T){
        PreOrder(T->lchild);
        printf("%d", T->s.a);
        PreOrder(T->rchild);
    }
}

void PostOrder(BiTree T){
    if(T){
        PreOrder(T->lchild);
        PreOrder(T->rchild);
        printf("%d", T->s.a);
    }
}

//非递归实现

//队列实现层次遍历

//先序创建二叉树
void CreateBiTree(BiTree &T){
    int temp;
    scanf("%d", &temp);
    if(temp == -1)    T = NULL;
    else{
        T = (BiTree )malloc(sizeof(BiTNode));
        if(!T) return ;
        T->a = temp;
        CreateBiTree(T->lchild);
        CreateBiTree(T->rchild);
    }
}

//中序线索二叉树线索化
typedef struct Tree{
    Data s;
    struct Tree *lchild,*rchild;
    int ltag,rtag;
}Tree,*ThreadTree;

void CreateInThread(ThreadTree T){
    ThreadTree pre = NULL;
    if(T){
        InThread(T, pre);
        pre->rchild = NULL;
        pre->rtag = 1;
    }
}


void InThread(ThreadTree &p, ThreadTree &pre){
    if(p){
        InThread(p->lchild, pre);
        if(p->lchild==NULL){
            p->lchild = pre;
            p->tag = 1;
        }
        if(pre!=NULL&&pre->rchild==NULL){  //前驱结点
                pre->rchild = p;
                pre->tag = 1;
        }
        pre = p;
        InThread(p->rchild, pre);
    }
}
//中序线索二叉树的遍历
ThreadNode *FristNode(ThreadNode *p){
    while(p->ltag == 0)
        p = p->lchild;
    return p;
}

ThreadNode *NextNode(ThreadNode *p){
    if(p->rtag == 0)
        return FristNode(p->rchild);
    else
        return p->rchild;
}

void InOrder(ThreadNode *T){
    for(ThreadNode *p = FristNode(T); p!=NULL; p = NextNode(T))
        visit(p);
}

三、树的存储结构

每种存储方法需要考虑:结点数据,结点与双亲结点或孩子结点的映射关系,根结点的位置和树的所有结点个数的这些东西的存储。

1.双亲表示法(并查集中最常使用)

用一组连续的空间存储树的结点,每个结点中设置一个变量映射双亲结点的位置即数组下标,便于查找结点的根,对于结点的孩子查找需要遍历整个树

struct Data{
    int a;
    int parent;   //若每个结点存储需要结构体,就可以直接在里面设置指向父结点的游标
};

typedef struct{    
    Data[MAX];  //存储结点
    int n,r;    //根的位置和结点数
}PTree;


//int fa[MAX]; //存储每个对应结点的父节点的位置,把父子关系单独存储就显的更加直观

2.孩子表示法(便于查找孩子结点)

typedef struct CTNode{
    int child;
    struct CTNode *next;
}*ChildPtr;

typedef struct{
    int data;              //数据部分
    ChildPtr firstChild;   //孩子链表
}CTBox;

typedef struct{
    CTBox nodes[MAX];  //存储所有结点的数组
    int n, r;     //结点数,根结点的位置
}CTree;

3.左孩子右兄弟表示法(树转二叉树的方式)

typedef struct CSNode{
    int data;
    struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree

四、并查集

一个树型的数据结构,用于处理一些不交集的合并及查询问题,主要操作有:

Find:确定元素属于哪个子集,即找寻根结点,可在递归找寻根结点的途中压缩处理。

Union:将两个不相交的子集合并为同一个集合,先Find根结点,再看是否是同一集合,只有不是才会进行实际合并操作,即一个根结点认另一个根结点叫爸爸。

例题:洛谷P1111修复公路

例题:牛客 黑白边问题

n个点,m条边,每条边分为黑边和白边,现在需要挑一些边出来,使得n个点可以两两联通。由于牛牛特别讨厌白边,所以在挑中的边中,让白边最少,输出白边的条数,如果不能两两联通,输出−1.

输入:

第一行两个整数n,m. 1≤n,m≤2e5

接下来 m 行, 每行三个整数 x,y,z 代表xy之间有一条边。z的值为0或1,0 代表黑边,1代表白边

输出:

一行一个整数, 表示最少的白边数量。如果不能满足题目条件,输出 -1

#include

using namespace std;

const int N = 2e5 + 7;

struct Node{
    int x, y, z;
    bool operator <(const Node& a) const{
        return z < a.z;
    }
}Edge[N];
int fa[N];

int find(int x){
    return x == fa[x]? x : (fa[x] = f(fa[x]));
}

int main(){
    int n = 0, m = 0, ans = 0, total = 0;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++){
        scanf("%d%d%d", &Edge[i].x, &Edge[i].y, &Edge[i].z);
    }
    for(int i = 1; i <= n; i++)    fa[i] = i;
    
    sort(Edge, Edge + m);
    for(int i = 0; i < m; i++){
        int fx = find(Edge[i].x);
        int fy = find(Edge[i].y);
        if(fx != fy){
           fa[fx] = fa[fy]; 
           total++;
           if(Edge[i].z)    ans++;
        }
        if(total == n - 1)    break;
    }    
    if(total != n - 1) 
        printf("-1\n");
    else
        printf("%d\n", ans);
    return 0;
}

/*
输入:
4  4
1 2 0
2 3 0
3 4 1
1 4 0

输出:
0
*/

五、堆

一种使用顺序存储结构的特殊完全二叉树,

(1)若满足每个结点的关键字大于左右子树所有结点的关键字,则是大顶堆;

(2)若满足每个结点的关键字小于左右子树所有结点的关键字,则是小顶堆;

步骤:

(1)构造堆

(2)维护堆

应用问题:

堆排序

TOP k问题(海量数据查找k个最大或最小的数):先取K个数建立堆,再不断push()一个,pop()一个,每次在插入后K+1个数据中淘汰1个数据。

注意:C++STL中的priority_queue就是默认大顶堆,可重载比较运算符或者对sort方法新建比较器使其改变为小顶堆,具体运用可以直接用优先队列来实现堆,不必手写,当然你乐意的话,也可以手写堆。

六、B树与B+树

常见的索引结构,数据库中索引有所应用

七、红黑树

属于自平衡的搜索二叉树,C++STL关联容器set/multiset、 map/multimap的底层实现(unorder_map和unorder_set底层实现是哈希结构)


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

你可能感兴趣的:(数据结构与算法,二叉树,数据结构,算法)