【数据结构】树与图复习

树与图

特性

  1. 整棵树的度定义为最大的度
  2. 节点数=度之和+1
  3. 每个节点的度特指往字数的个数
路径长度

特指边的个数
和深度不一样哦

树的高度(深度)

特指在第几层。
根节点在第一层,理论上高度比(到根节点的)长度多一个。

二叉树

n次树 n叉树 与度为n的树
  1. "n"次树,注意定义度为n,隐含的条件是有这样一个节点的度是n。与“叉”树不一样。
  2. 二叉树与度为2的树不一样,区分左右子树且可以度为0;
  3. 二叉树的定义:二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个节点。

二叉树有五种形态

  1. 空子树
  2. 只有一个根节点
  3. 右子树为空
  4. 左子树为空
  5. 都非空
区分-特例
满二叉树

确实是满的

完全二叉树

若二叉树中最多只有最下面两层的结点的度数可以小于2,并且最下面一层的叶子结点都依次排列在该层最左边的位置上,则这样 的二叉树称为完全二叉树。
最后一层最右边可以差几个

性质
  1. 非空二叉树上叶结点数等于双分支结点数加1。即n0=n2+1。
  2. 还有基于高度的节点数计算
  3. 若完全二叉树的根结点编号为1(如果是0就减一个,也不难,实在不行考试的时候自己画一个图),对于编号为i(1≤i≤n)的结点有:
    (1) 2i≤n,则编号为i的结点为分支结点,否则为叶子结点,也就是说,最后一个分支结点的编号为n/2。
    (2) 若n为奇数,则n1=0,每个分支结点都是双分支结点;若n为偶数,则n1=1,只有一个单分支结点。
    (3) 若编号为i的结点有双亲结点,其双亲结点的编号为i/2(向下取整)。
    还有很显然的就懒得看了
二叉树的存储
顺序存储

有无效节点

存两个儿子的指针

//开摆了,没写怎么建树

#include <iostream>
using namespace std;
class BTNode{
    
    
    int data;
public:
    BTNode * lson;
    BTNode * rson;
    BTNode():lson(nullptr),rson(nullptr){
    
    };
    BTNode(int d){
    
    
        data=d;
        lson=rson=nullptr;
    }

    ~BTNode() {
    
    
        if(lson!= nullptr)
            delete lson;
        if(rson!= nullptr)
            delete rson;
    }
    void output() {
    
    
        printf("%d ",data);
    }
};
int prt[1005];
int x[1005],y[1005];
class BTree{
    
    
public:
    BTNode * root;
    BTree():root(nullptr){
    
    };
    BTree(BTNode *r):root(r){
    
    };
    void creatTree(int rt) {
    
    
        BTNode * p=new BTNode(rt);
        root=p;
    }
    void buildTree() {
    
    

    }
};
void DestroyTree(BTNode *s) {
    
    //注意是指针,因为每一层都是指针
    if(s==nullptr) return;
    DestroyTree(s->lson);
    DestroyTree(s->rson);
    delete s;
}
void dfs(BTNode *s) {
    
    
    if(s== nullptr) return ;
    s->output();
    dfs(s->lson);
    dfs(s->rson);
}

int main() {
    
    
    int n;
    for(int i=1;i<n;i++) {
    
    
        //int x,y;
        //scanf("%d%d",&x,&y);//prt
        //prt[y]=x;
    }
    int rt=0;
    for(int i=1;i<=n;i++) {
    
    
        if(!prt[i]) {
    
    
            rt=i;
            break;
        }
    }
    BTree tree;
    tree.creatTree(rt);
    tree.buildTree();
    dfs(tree.root);
    return 0;
}

树的基本运算

查找

插入/删除

遍历

先/后/中序遍历
层次遍历

反确认

(节点不确定)
由先序遍历序列和中序遍历序列能够唯一确定一棵二叉树。
由后序遍历序列和中序遍历序列能够唯一确定一棵二叉树。
由先序遍历序列和后序遍历序列不能唯一确定一棵二叉树。
反例:链,左链和右链

线索二叉树

ltag rtag 为0 的时候表示有孩子,不然就指向对应序列(前中后序遍历)的下一个后继®,或者前驱(l)

哈夫曼树

定理7.3 对于具有n0个叶子结点的哈夫曼树,共有2n0-1个结点。
儿子合并,没啥好说的
三叉树合并需要一点啸寄巧()

哈夫曼编码
左0右1
在一组字符的哈夫曼编码中,任一字符的哈夫曼编码不可能是另一字符哈夫曼编码的前缀。

森林与二叉树

可以类比长子兄弟链存储。。。左子树都是长子,右子树都是兄弟。

并查集

prt[]
Kruscal 要用

  1. 初始化prt[i]=i
  2. 寻找父亲:路径压缩prt[x]=Find(prt[x])
  3. 合并:fx=Find(x),fy=Find(y),prt[fy]=fx;
  4. 优化:高度较小的可以拿来优化(秩)
存储结构
  1. 双亲存储
    找父亲(相当于存了一个prt)
    好像老师没用指针,全部是数组(相当于最简单的方法)
class PNode{
    
    
    int parent;
    int data;
public:
    PNode(int p,int d):parent(p),data(d){
    
    };
    PNode(){
    
    };
};
  1. 孩子链存储
    用vector存储指向孩子Node的指针
class SonNode{
    
    
    int data;
    vector<SonNode*> sons;
public:
    SonNode(){
    
    };
    SonNode(int d):data(d){
    
    };
};
  1. 长子兄弟链存储结构
    指针,父亲指向长子,长子指向兄弟。
class EBNode{
    
    
    int data;
    EBNode * eson;
    EBNode * brother;
public:
    EBNode(){
    
    };
    EBNode(int d){
    
    
        data=d;
        eson=nullptr;
        brother=nullptr;
    }
}
  1. 前向星(邻接表)
    (图里面看吧)

###概念

  1. 邻接点

  2. 度:
    入度
    出度

  3. 完全图:
    完全有向图
    完全无向图

  4. 稠密图
    稀疏图

  5. 子图

  6. 路径
    路径长度
    简单路径

  7. 回路/环
    简单回路/环

  8. 连通图
    连通分量
    强连通图
    强连通分量

  9. 权-> 网

存储结构

邻接矩阵


邻接矩阵的n次方可以用于求路径种数

邻接表(俗称前向星)
//简单版
int h[1005];
struct mapp{
    
    
    int next,to,weight;
}t[1005];
int cnt=0;
void Addedge(int x,int y,int w) {
    
    
    cnt++;
    t[cnt].next=h[x];
    h[x]=cnt;
    t[cnt].to=y;
    t[cnt].weight=w;
}
//类版

图的遍历

  1. 连通图
    dfs bfs
  2. 非连通图
  3. visit数组:if !visit[x]
  4. 回溯法
  5. bfs找最短路(暴力咯)

最小生成树

生成树:遍历一次节点得到的树,分为bfs和dfs得到的

Kruscal

先排序,再并查集解决。

Prim

和dijkstra类似,都是选择“距离最短”的结点加入集合,Prim的“距离最短”是指未访问的结点到已经访问的所有结点距离最小,即将已经访问的结点视为一个整体,将距离最小的结点加入到已访问的集合中。
Dijkstra是指到目标。
但是都会用到优先队列。

总结

这俩真的很像。。。
区别就是Kruscal先排序再合并,Prim一边合并一边排序,前者需要有辅助确认是否已经合并过该节点,厚点需要辅助计算某一节点的最短边。

最短路

本质:进行路径的松弛操作

dijkstra

不能负权图求
O(n^2)-> O(e*log2(e))

priority_queue <inQue> q;
int d[1005];
int n,m;//n nodes in the map
bool bj[1005];
int Dijkstra(int st,int ed) {
    
    //
    for(int i=1;i<=n;i++) d[i]=inf;
    d[st]=0;
    inQue tmp;
    tmp.d=0;
    tmp.id=st;
    q.push(tmp);
    while(!q.empty()) {
    
    
        tmp=q.top();
        q.pop();
        int x=tmp.id;
        if(bj[x]) continue;
        bj[x]=true;
        for(int i=head[x];i;i=t[i].next) {
    
    
            int y=t[i].to;
            if(d[y]>d[x]+t[i].v) {
    
    
                d[y]=d[x]+t[i].v;
                tmp.d=d[y];
                tmp.id=y;
                tmp.prt=x;
                q.push(tmp);
            }
        }
    }
    if(d[ed]!=inf) return d[ed];
    else return -1;
}
Floyd

不适合负权回路,但是可以负权。
O(N^3)

for(int i=1;i<=n;i++) {
    
    
    for(int j=1;j<=n;j++) {
    
    
        for(int k=1;k<=n;k++) {
    
    
            if(a[i][k]+a[k][j]<=a[i][j]) 
                a[i][j]=a[i][k]+a[k][j];
        }
    }
}

拓扑排序

找入度为0的,一直找。
O(n+e)

AOE网与关键路径

源点(唯一)
汇点(唯一)
在AOE网中,从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径。完成整个工程的最短时间就是网中关键路径的长度。
关键路径上的活动称为关键活动,或者说关键路径是由关键活动构成的。只要找出AOE网中的全部关键活动,也就找到了全部关键路径了。
求解:

  1. 事件的最早开始时间:从左向右推取max
    事件的最晚开始时间:从右向左推取min
  2. 活动的最早开始/最晚开始时间
  3. 关键活动:不存在富余时间
    省流:正跑、反跑

猜你喜欢

转载自blog.csdn.net/qq_39440588/article/details/129148289