数据结构-4

第七章 二叉树

1.二叉树的概念

2.二叉树的存储

顺序存储

链式存储

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int n;
int l[N], r[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        // 存下 i 号结点的左右孩子
        cin >> l[i] >> r[i];
    }
    return 0;
}

3.二叉树的遍历

1.深度优先遍历

先序:先输出后遍历

中序:先遍历后输出

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int n;
int l[N], r[N];

// 先序遍历:根 → 左 → 右
void dfs1(int p)
{
    if(p == 0) return;
    cout << p << " "; //根
    dfs1(l[p]); //左
    dfs1(r[p]); //右
}

// 中序遍历:左 → 根 → 右
void dfs2(int p)
{
    if(p == 0) return;
    dfs2(l[p]);//左
    cout << p << " ";//根
    dfs2(r[p]); //右
}

// 后序遍历:左 → 右 → 根
void dfs3(int p)
{
    if(p == 0) return;
    dfs3(l[p]);
    dfs3(r[p]);
    cout << p << " ";
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> l[i] >> r[i];
    }
    dfs1(1); // 先序遍历
    cout << endl;
    dfs2(1); // 中序遍历
    cout << endl;
    dfs3(1); // 后序遍历
    cout << endl;
    return 0;
}

2.宽度优先遍历

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
const int N = 300;
int n;
int l[N], r[N];

void bfs()
{
    queue<int> q;
    q.push(1);
    while(q.size())
    {
        auto p = q.front(); 
        q.pop();
        cout << p << " ";
        // 左右孩子入队
        if(l[p]) q.push(l[p]);
        if(r[p]) q.push(r[p]);
    }
    cout << endl;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> l[i] >> r[i];
    }
    bfs();
    return 0;
}
1.新二叉树
cpp 复制代码
#include <iostream>
using namespace std;
const int N = 300;
int n;
char root;
char l[N], r[N]; // 存二叉树

void dfs(char root)
{
    if(root == '*') return; // * 代表空结点
    cout << root;
    dfs(l[root]);
    dfs(r[root]);
}

int main()
{
    cin >> n;
    // 处理根节点
    cin >> root;
    cin >> l[root] >> r[root];
    for(int i = 2; i <= n; i++)
    {
        char t; cin >> t;
        cin >> l[t] >> r[t];
    }
    // 前序遍历
    dfs(root);
    return 0;
}
2.二叉树的遍历

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N]; // 存二叉树

// 前序遍历:根 -> 左 -> 右
void dfs1(int root)
{
    if(!root) return;
    cout << root << " ";
    dfs1(l[root]);
    dfs1(r[root]);
}

// 中序遍历:左 -> 根 -> 右
void dfs2(int root)
{
    if(!root) return;
    dfs2(l[root]);
    cout << root << " ";
    dfs2(r[root]);
}

// 后序遍历:左 -> 右 -> 根
void dfs3(int root)
{
    if(!root) return;
    dfs3(l[root]);
    dfs3(r[root]);
    cout << root << " ";
}

int main()
{
    cin >> n;
    // 建树
    for(int i = 1; i <= n; i++) 
        cin >> l[i] >> r[i];
    // 前序遍历
    dfs1(1);
    cout << endl;
    // 中序遍历
    dfs2(1);
    cout << endl;
    // 后序遍历
    dfs3(1);
    return 0;
}
3.二叉树深度

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N];

int bfs()
{
    queue<int> q;
    q.push(1);
    int depth = 0;
    while(q.size())
    {
        int sz = q.size();
        depth++;
        for(int i = 0; i < sz; i++)
        {
            int u = q.front(); q.pop();
            if(l[u]) q.push(l[u]);
            if(r[u]) q.push(r[u]);
        }
    }
    return depth;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> l[i] >> r[i];
    cout << bfs() << endl;
    return 0;
}
4.求先序排列
cpp 复制代码
#include <iostream>
using namespace std;
string a, b;

// a:中序遍历序列
// b:后序遍历序列
// [l1,r1] 中序区间
// [l2,r2] 后序区间
void dfs(int l1, int r1, int l2, int r2)
{
    // 递归出口:区间为空
    if(l1 > r1) return;

    // 后序最后一个元素是当前子树根节点
    char root = b[r2];
    cout << root;

    // 在中序序列找到根的位置p
    int p = l1;
    while(a[p] != root) p++;

    // 左子树节点数量:p - l1
    // 左子树:中序[l1,p-1]  后序[l2 , l2 + p - l1 - 1]
    dfs(l1, p - 1, l2, l2 + p - l1 - 1);
    // 右子树:中序[p+1,r1]  后序[l2 + p - l1 , r2 - 1]
    dfs(p + 1, r1, l2 + p - l1, r2 - 1);
}

int main()
{
    cin >> a >> b;
    dfs(0, a.size() - 1, 0, b.size() - 1);
    return 0;
}
5.American Heritage
cpp 复制代码
#include <iostream>
using namespace std;
string a, b;

// a:中序遍历
// b:先序遍历
// 输出:后序遍历
void dfs(int l1, int r1, int l2, int r2)
{
    // 递归出口:区间没有节点
    if(l1 > r1) return;

    // 先序第一个元素 b[l2] 是根节点
    int p = l1;
    while(a[p] != b[l2]) p++;

    // 左子树递归
    dfs(l1, p - 1, l2 + 1, l2 + p - l1);
    // 右子树递归
    dfs(p + 1, r1, l2 + p - l1 + 1, r2);
    
    // 左右全部处理完,输出根(后序:左→右→根)
    cout << b[l2];
}

int main()
{
    cin >> a >> b;
    dfs(0, a.size() - 1, 0, b.size() - 1);
    return 0;
}
6.二叉树问题

这道题并没有告诉左右节点的位置 所以需要用vector或者链式前向星做

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 110;
int n;
vector<int> edges[N]; // 存树
int fa[N]; // fa[i] 表示 i 结点的父亲
int dist[N]; // dist[i] 表示 i 到 x 的最短距离

int dfs(int u)
{
    int ret = 0;
    for(auto v : edges[u])
    {
        ret = max(ret, dfs(v));
    }
    return ret + 1;
}

int bfs()
{
    queue<int> q;
    q.push(1);
    int ret = 0;
    while(q.size())
    {
        int sz = q.size();
        ret = max(ret, sz);
        while(sz--)
        {
            int u = q.front(); q.pop();
            for(auto v : edges[u])
            {
                q.push(v);
            }
        }
    }
    return ret;
}

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v; cin >> u >> v;
        // u -> v
        edges[u].push_back(v);
        fa[v] = u;
    }
    // 求深度
    cout << dfs(1) << endl;
    // 求宽度
    cout << bfs() << endl;
    // 求距离
    int x, y; cin >> x >> y;
    while(x != 1)
    {
        dist[fa[x]] = dist[x] + 1;
        x = fa[x];
    }
    int len = 0;
    while(y != 1 && dist[y] == 0)
    {
        len++;
        y = fa[y];
    }
    cout << dist[y] * 2 + len << endl;
    return 0;
}

第八章 堆和 priority_queue

1.堆的定义

2.堆的存储

3.核心操作

向上调整算法

cpp 复制代码
int n; // 标记堆的大小
int heap[N]; // 存堆 - 默认是一大根堆
// 向上调整算法
void up(int child)
{
    int parent = child / 2;
    // 如果父结点存在,并且权值比父结点大
    while(parent >= 1 && heap[child] > heap[parent])
    {
        swap(heap[child], heap[parent]);
        // 交换之后,修改下次调整的父子关系,注意顺序不能颠倒
        child = parent;
        parent = child / 2;
    }
}

如果是创建小根堆的话 那么需要比较看看是不是小 如果小的话 那么就是进行与父辈进行颠倒

时间复杂度: 最差情况需要走⼀个树高,因此时间复杂度为 log N

向下调整算法

cpp 复制代码
int n; // 标记堆的大小
int heap[N]; // 存堆 - 默认是一大根堆
// 向下调整算法
void down(int parent)
{
    int child = parent * 2;
    while(child <= n) // 如果还有孩子
    {
        // 找出两个孩子谁是最大的
        if(child + 1 <= n && heap[child + 1] > heap[child]) child++;
        // 最大的孩子都比我小,说明是一个合法的堆
        if(heap[child] <= heap[parent]) return;
        swap(heap[child], heap[parent]);
        // 交换之后,修改下次调整的父子关系,注意顺序不能颠倒
        parent = child;
        child = parent * 2;
    }
}

时间复杂度:最差情况需要走⼀个树高,因此时间复杂度为 log N

1.堆的模拟实现

1 创建

2 插入

时间复杂度:时间开销在向上调整算法上,时间复杂度为 O (log N) 。

3 删除栈顶元素

时间复杂度:时间开销在向上调整算法上,时间复杂度为 O (log N) 。

4 堆顶元素

时间复杂度:显然是 O(1) 。

5 堆的大小


时间复杂度:显然是 O(1) 。

2.priority_queue

1.优先级队列

2.创建 priority_queue - 初阶

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
// 优先级队列的使用
void test()
{
    int a[10] = {1, 41, 23, 10, 11, 2, -1, 99, 14, 0};
    priority_queue<int> heap; // 默认写法下,是一个大根堆
    for(auto x : a)
    {
        heap.push(x); // 插入元素
    }
    while(heap.size())
    {
        cout << heap.top() << " "; // 获取堆顶元素的值
        heap.pop(); // 删除元素
    }
}
int main()
{
    test();
    return 0;
}

3.创建 priority_queue - 内置类型

cpp 复制代码
内置类型就是 C++ 提供的数据类型,⽐如 int、double、long long 等。以 int 类型为例,分
别创建⼤根堆和⼩根堆。

vector指的是动态顺序表

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
// 内置类型
void test()
{
    int a[10] = {1, 41, 23, 10, 11, 2, -1, 99, 14, 0};
    // 大根堆
    priority_queue<int> heap1; // 默认就是大根堆
    // priority_queue<数据类型, 存数据的结构, 数据之间的比较方式>
    priority_queue<int, vector<int>, less<int>> heap2; // 也是大根堆
    // 小根堆
    priority_queue<int, vector<int>, greater<int>> heap3; // 小根堆
    // 测试
    for(auto x : a)
    {
        heap1.push(x);
        heap2.push(x);
        heap3.push(x);
    }
    while(heap1.size())
    {
        cout << heap1.top() << " "; // 获取堆顶元素的值
        heap1.pop(); // 删除元素
    }
    cout << endl;
    while(heap2.size())
    {
        cout << heap2.top() << " "; // 获取堆顶元素的值
        heap2.pop(); // 删除元素
    }
    cout << endl;
    while(heap3.size())
    {
        cout << heap3.top() << " "; // 获取堆顶元素的值
        heap3.pop(); // 删除元素
    }
    cout << endl;
}
int main()
{
    test();
    return 0;
}

4.创建 priority_queue - 结构体类型

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
struct node
{
    int a, b, c;
    // // 以 b 为基准,定义大根堆
    // bool operator < (const node& x) const
    // {
    //     return b < x.b;
    // }
    // 以 b 为基准,定义小根堆
    bool operator < (const node& x) const
    {
        return b > x.b;
    }
};
void test()
{
    priority_queue<node> heap;
    for(int i = 1; i <= 10; i++)
    {
        heap.push({i, i + 1, i + 2});
    }
    while(heap.size())
    {
        auto [a, b, c] = heap.top();
        heap.pop();
        cout << a << " " << b << " " << c << endl;
    }
}
int main()
{
    test();
    return 0;
}

1 堆
cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n; // 标记堆的大小
int heap[N]; // 小根堆

// 向上调整算法
void up(int child)
{
    int parent = child / 2;
    while(parent >= 1 && heap[child] < heap[parent])
    {
        swap(heap[child], heap[parent]);
        child = parent;
        parent = child / 2;
    }
}

// 向下调整算法
void down(int parent)
{
    int child = parent * 2; // 左孩子
    while(child <= n)
    {
        // 找出两个孩子中的最小值
        if(child + 1 <= n && heap[child + 1] < heap[child]) 
            child++;
        if(heap[child] >= heap[parent]) 
            return;
        swap(heap[child], heap[parent]);
        parent = child;
        child = parent * 2;
    }
}

// 进堆
void push(int x)
{
    n++;
    heap[n] = x;
    up(n);
}

// 删除堆顶元素
void pop()
{
    swap(heap[1], heap[n]);
    n--;
    down(1);
}

int main()
{
    int T; cin >> T;
    while(T--)
    {
        int op; cin >> op;
        if(op == 1) // 进堆
        {
            int x; cin >> x;
            push(x);
        }
        else if(op == 2) // 输出堆顶
        {
            cout << heap[1] << endl;
        }
        else // 删除堆顶元素 op == 3
        {
            pop();
        }
    }
    return 0;
}
2 第k小
cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;

int n, m, k;
priority_queue<int> heap; // C++默认priority_queue是【大根堆】

int main()
{
    cin >> n >> m >> k;
    // 先读入初始n个数字
    for(int i = 1; i <= n; i++)
    {
        int x; cin >> x;
        heap.push(x);
        // 一旦堆大小超过k,删掉最大的元素
        if(heap.size() > k) 
            heap.pop();
    }

    // m次操作
    while(m--)
    {
        int op; cin >> op;
        if(op == 1) // 添加新数字
        {
            int x; cin >> x;
            heap.push(x);
            if(heap.size() > k) 
                heap.pop();
        }
        else if(op == 2) // 查询第k小的数
        {
            if(heap.size() == k) 
                cout << heap.top() << endl;
            else 
                cout << -1 << endl;
        }
    }
    return 0;
}
3 除2!

找到最大的偶数减少一半 所以需要用大根堆

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;

int n, k;
priority_queue<int> heap; // 默认大根堆

int main()
{
    cin >> n >> k;
    LL sum = 0; // 所有数字总和
    for(int i = 1; i <= n; i++)
    {
        int x; cin >> x;
        sum += x;
        if(x % 2 == 0) // 只把偶数放进堆
            heap.push(x);
    }

    // 最多进行k次操作
    while(heap.size() && k--)
    {
        int t = heap.top() / 2; // 取出最大偶数,减半
        heap.pop();
        sum -= t;  // 总和减少t
        
        if(t % 2 == 0) // 减半之后依然是偶数,重新放回堆,可以继续切割
            heap.push(t);
    }
    cout << sum << endl;
    return 0;
}
4 最小函数值
cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
int n, m;
LL a[N], b[N], c[N];

struct node
{
    LL f;    // 当前函数计算结果
    LL num;  // 第几个函数的编号
    LL x;    // 当前代入的x值
    bool operator <(const node& x) const
    {
        // priority_queue默认大根堆,return f > x.f 实现【小根堆】
        return f > x.f;
    }
};
priority_queue<node> heap;

// 二次函数:a[i] * x² + b[i] * x + c[i]
LL calc(LL i, LL x)
{
    return a[i] * x * x + b[i] * x + c[i];
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i] >> c[i];
    }
    // 初始:所有函数先代入 x=1 放进小根堆
    for(int i = 1; i <= n; i++)
    {
        heap.push({calc(i, 1), i, 1});
    }
    // 取出前m个最小的函数值
    while(m--)
    {
        auto t = heap.top(); 
        heap.pop();
        LL f = t.f, num = t.num, x = t.x;
        cout << f << " ";
        // 当前x取出后,把同一个函数 x+1 的结果入堆
        heap.push({calc(num, x + 1), num, x + 1});
    }
    return 0;
}
5 序列合并

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], b[N];

struct node
{
    int sum; // a[i] + b[j] 的和
    int i, j; // 数组a下标i,数组b下标j
    bool operator < (const node& x) const
    {
        // priority_queue默认大根堆,sum>x.sum 构建小根堆
        return sum > x.sum;
    }
};
priority_queue<node> heap; // 小根堆

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) cin >> b[i];

    // 初始化:把所有 a[i]+b[1] 入堆
    for(int i = 1; i <= n; i++)
    {
        heap.push({a[i] + b[1], i, 1});
    }

    // 取出前n小的和
    for(int k = 1; k <= n; k++)
    {
        node t = heap.top(); heap.pop();
        int sum = t.sum, i = t.i, j = t.j;
        cout << sum << " ";
        // 当前j的下一个位置j+1,如果不越界,入堆 a[i]+b[j+1]
        if(j + 1 <= n) 
            heap.push({a[i] + b[j + 1], i, j + 1});
    }
    return 0;
}
6.舞蹈课
cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const int N = 2e5 + 10;
int n;
int s[N]; // 标记性别 0/1
//双向链表
int e[N];
int pre[N], ne[N];

struct node
{
    int d; // 技术差值
    int l, r; //相邻两个人编号
    //小根堆定义
    bool operator < (const node& x) const
    {
        if(d != x.d) return d > x.d;
        else if(l != x.l) return l > x.l;
        else return r > x.r;
    }
};
priority_queue<node> heap;
bool st[N]; //标记已经组队出局的人

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        char ch; cin >> ch;
        if(ch == 'B') s[i] = 1;
        else s[i] = 0;
    }
    for(int i = 1; i <= n; i++)
    {
        cin >> e[i];
        //初始化双向链表
        pre[i] = i - 1;
        ne[i] = i + 1;
    }
    pre[1] = 0; //第一个和最后一个分别没有前面和后面的
    ne[n] = 0; //0代表不存在
    
    //把所有相邻异性放入堆
    for(int i = 2; i <= n; i++)
    {
        if(s[i] != s[i - 1])
        {
            heap.push({abs(e[i] - e[i - 1]), i - 1, i});
        }
    }

    vector<node> ret;
    while(heap.size())
    {
        node t = heap.top(); heap.pop();
        int d = t.d, l = t.l, r = t.r;
        // 如果两人其中一人已经组队,直接跳过这条无效配对
        if(st[l] || st[r]) continue;
        
        ret.push_back(t);
        st[l] = st[r] = true; //标记两人出局
        
        //双向链表删除 l,r
        int L = pre[l];
        int R = ne[r];
        ne[L] = R;
        pre[R] = L;
        
        //新邻居如果是异性,加入堆
        if(L != 0 && R != 0 && s[L] != s[R])
        {
            heap.push({abs(e[L] - e[R]), L, R});
        }
    }

    cout << ret.size() << endl;
    for(auto& x : ret)
    {
        cout << x.l << " " << x.r << endl;
    }
    return 0;
}
相关推荐
0566463 小时前
Python康复训练——数据结构
数据结构·windows·python
冻柠檬飞冰走茶3 小时前
PTA基础编程题目集 7-27 冒泡法排序(C语言实现)
c语言·开发语言·数据结构·算法
noipp7 小时前
推荐题目:洛谷 P5843 [SCOI2012] Blinker 的噩梦
c语言·数据结构·c++·算法·游戏·洛谷·luogu
冻柠檬飞冰走茶7 小时前
PTA基础编程题目集 7-20 打印九九口诀表(C语言实现)
c语言·开发语言·数据结构·算法
红叶舞7 小时前
基于线段树的数据结构
数据结构·python·算法
断点之下8 小时前
从“理扑克牌”到“分组优化”——直接插入排序与希尔排序详解
数据结构·算法·排序算法
起个破名想半天了8 小时前
算法与数据结构之DFS深度优先遍历
数据结构·算法·深度优先
鱼子星_9 小时前
【C++】深入剖析list:list及其双向迭代器实现
开发语言·数据结构·c++·笔记·stl·list
江屿风10 小时前
【C++笔记】【二叉搜索树】流食般投喂
开发语言·数据结构·c++·笔记