数据结构-5

第九章 红黑树和 set 与 map

1. 二叉搜索树

所以中序遍历的结果是从小到大的

1 查找

如果查找24 那么就查找失败 找到的是空节点 为失败

时间复杂度:树的高h

2 插入

3 构造 BST 树

⼆叉搜索树的构造就是不断向原来的树中插入新的结点即可。

4 删除操作


时间复杂度:查找前驱和后继的操作,会把树进行查找完,然后进行删除,最差也会遍历整个二叉树的高,因此时间复杂度为 O (N) 。(树高)

2.平衡二叉树

cpp 复制代码
在上⼀⼩节中提到,在某些特定的情况下,⼆叉搜索树是会退化成单链表的,并且各种操作的效率也
会明显的下降,因此我们需要⼀些特别的⼿段保证这个⼆叉搜索树的"平衡",进⽽保证各种操作的
效率。这就是我们接下来要学习的平衡⼆叉树。



1 查找

cpp 复制代码
与⼆叉搜索树的查找⼀样:从根结点开始,沿某个分⽀逐层向下⽐较的过程,若⾮空,先将给定值与
根结点的关键字⽐较,若相等,则查找成功;若不等,如果⼩于根结点的关键字,则在根结点的左⼦
树上查找,否则在根结点的右⼦树上查找。


左旋:


右旋:


时间复杂度:由于平衡二叉树会限制树的高度不会过高,趋近于 log n 级别,因此时间复杂度为 O (log N) 。

2 插入



LL 型 - 右单旋


RR 型 - 左单旋

RR 表示:新结点由于插⼊在 T 结点的右孩⼦(R)的右子树(RR)中,从而导致失衡。

LR 型 - 左右双旋




RL 型 - 右左双旋

RL 表示:新结点由于插入在 T 结点的右孩子(R)的左子树(RL)中,从而导致失衡。




插入操作的时间复杂度: 旋转操作仅需修改指针,因此最大的时间开销就是先把结点插入到空结点的位置,时间复杂度和查找⼀致,因此为 O (log N) 。

3 构造 AVL 树

平衡二叉树的构造,就是不断向树中插入新的结点。
案例:根据序列 a= {15, 6, 10, 17, 11, 13, 9, 20, 16, 22} ,构造⼀棵二叉排序树


4 删除








时间复杂度:由于可能会调整很多次,最差情况下会从叶子结点开始向上到根,整个过程遍历⼀个树高,因此时间复杂度为 O (log N) 。

3. 红黑树

1 红黑树的规则

左<根<右 根叶黑 不红红 黑路同

2 红黑树的查找

3 红黑树的插入


情况二:叔叔是红色

情况三:叔叔是黑色

4 红黑树的构造

4. set / multiset

因为返回的是迭代器 所以需要*

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

int a[] = {10, 60, 20, 70, 80, 30, 90, 40, 100, 50};
int main()
{
    set<int> mp;
    // 插入
    for(auto x : a)
    {
        mp.insert(x);
    }
    // 遍历 set,最终的结果应该是有序的
    for(auto x : mp)
    {
        cout << x << " ";
    }
    cout << endl;
    // if(mp.count(1)) cout << "1" << endl;
    // if(mp.count(99)) cout << "99" << endl;
    // if(mp.count(30)) cout << "30" << endl;
    // if(mp.count(10)) cout << "10" << endl;
    // mp.erase(30);
    // mp.erase(10);
    // if(mp.count(30)) cout << "30" << endl;
    // else cout << "no:30" << endl;
    // if(mp.count(10)) cout << "10" << endl;
    // else cout << "no:10" << endl;

    auto x = mp.lower_bound(20);
    auto y = mp.upper_bound(20);
    
    cout << *x << " " << *y << endl;
    return 0;
}

5.map / multimap

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

void print(map<string, int>& mp)
{
    for(auto& p : mp)
    {
        cout << p.first << " " << p.second << endl;
    }
}

// 统计一堆字符串中,每一个字符串出现的次数
void fun()
{
    string s;
    map<string, int> mp; // <字符串,字符串出现的次数>
    for(int i = 1; i <= 10; i++)
    {
        cin >> s;
        mp[s]++; // 体现了 operator 的强大
    }
    print(mp);
}

int main()
{
    fun();
    // map<string, int> mp;
    // // 插入
    // mp.insert({"张三", 1});
    // mp.insert({"李四", 2});
    // mp.insert({"王五", 3});
    // // print(mp);

    // // operator[] 可以让 map 像数组一样使用
    // cout << mp["张三"] << endl;
    // mp["张三"] = 110;
    // cout << mp["张三"] << endl;
    // // 注意事项:operator[] 有可能会向 map 中插入本不想插入的元素
    // // [] 里面的内容如果不存在 map 中,会先插入,然后再拿值
    // // 插入的时候:第一个关键字就是 [] 里面的内容,第二个关键字是一个默认值
    if(mp.count("赵六") && mp["赵六"] == 4) cout << "yes" << endl; //这样就不会插入了
    // else cout << "no" << endl;
    // // 删除
    // mp.erase("张三");
    // print(mp);
    return 0;
}
1 英语作文
cpp 复制代码
#include <iostream>
#include <map>
using namespace std;
typedef long long LL;

int n, p;
map<string, int> mp; // <高级词汇,含金量>

// 判断 ch 是否合法
bool check(char ch)
{
    if((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
    {
        return true;
    }
    else return false;
}

int main()
{
    cin >> n >> p;
    for(int i = 1; i <= n; i++)
    {
        string s; int x;
        cin >> s >> x;
        mp[s] = x;
    }
    LL ret = 0;
    char ch; // 一个字符一个字符读
    string t = "";
    while(scanf("%c", &ch) != EOF)
    {
        if(check(ch)) t += ch;
        else
        {
            // 读到分隔符
            ret = (ret + mp[t]) % p;
            t = "";
        }
    }
    cout << ret << endl;
    return 0;
}

EOF是文件结束的标识符

2 营业额统计
cpp 复制代码
#include <iostream>
#include <set>
using namespace std;
const int INF = 1e7 + 10;

int n;
set<int> mp; // 存储 i 天之前的营业额
int main()
{
    cin >> n;
    int ret; cin >> ret;
    mp.insert(ret);
    // 左右护法 - 防止越界访问
    mp.insert(-INF); 
    mp.insert(INF);
    
    for(int i = 2; i <= n; i++)
    {
        int x; cin >> x;
        // 找出第一个 >= x 的元素
        auto it = mp.lower_bound(x);
        auto tmp = it;
        tmp--;
        
        if(*it == x) continue;
        ret += min(abs(*tmp - x), abs(*it - x));
        mp.insert(x);
    }
    cout << ret << endl;
    return 0;
}

INF是无穷大

3 木材仓库

cpp 复制代码
#include <iostream>
#include <set>
using namespace std;
typedef long long LL;
const LL INF = 1e10 + 10;
set<LL> mp;

int main()
{
    int q; cin >> q;
    // 左右护法 - 处理边界情况
    mp.insert(-INF); 
    mp.insert(INF);
    while(q--)
    {
        LL op, len; cin >> op >> len;
        if(op == 1) // 进货
        {
            if(mp.count(len)) 
                cout << "Already Exist" << endl;
            else 
                mp.insert(len);
        }
        else // 出货
        {
            if(mp.size() == 2)
            {
                cout << "Empty" << endl;
            }
            else
            {
                // 找到距离 len 最近的那一个
                auto it = mp.lower_bound(len);
                auto tmp = it;
                tmp--;
                if(abs(*tmp - len) <= abs(*it - len))
                {
                    cout << *tmp << endl;
                    mp.erase(tmp); // 别忘了删除
                }
                else
                {
                    cout << *it << endl;
                    mp.erase(it);
                }
            }
        }
    }
    return 0;
}

第十章 哈希表和 unordered_set 与unordered_map

1. 哈希表的概念

2.常见的哈希函数

3. 处理哈希冲突

h(20)=9 的时候因为9有位置了 所以20去10的地方了 同时21去1的地方

寻找的时候也是这样 如果冲突太多的话 时间复杂度就会太大 所以需要让数组多点 为传入数量的两倍

如果都是冲突的话 那么时间复杂度是n 这个时候可以用红黑树 这样复杂度就是O(logN)

4.哈希表的模拟实现

线性探测

创建

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

const int N = 23, INF = 0x3f3f3f3f;
int h[N]; // 哈希表数组

// 哈希表初始化
void init()
{
    memset(h, 0x3f, sizeof h);
}

// 哈希定位 + 线性探测查找下标
int f(int x)
{
    int id = (x % N + N) % N;
    // 当前位置被占用且不是目标值,向后探测
    while (h[id] != INF && h[id] != x)
    {
        id++;
        if (id == N) id = 0; // 数组到头,循环回到开头
    }
    return id;
}

// 插入元素
void insert(int x)
{
    int id = f(x);
    h[id] = x;
}

// 查询元素是否存在
bool find(int x)
{
    int id = f(x);
    return h[id] == x;
}

int main()
{
    init();
    int n;
    cin >> n;
    while (n--)
    {
        int op, x;
        cin >> op >> x;
        if (op == 1)
        {
            insert(x);
        }
        else
        {
            if (find(x))
                cout << "yes" << endl;
            else
                cout << "no" << endl;
        }
    }
    return 0;
}

链地址法

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

const int N = 23;
int h[N];    // 哈希表头结点数组
int e[N];    // 存储实际数值
int ne[N];   // 存储链表下一个节点下标
int id;      // 当前可用节点编号

// 哈希函数,兼容负数
int f(int x)
{
    return (x % N + N) % N;
}

// 插入元素(头插法构建链表)
void insert(int x)
{
    int idx = f(x);
    id++;
    e[id] = x;
    ne[id] = h[idx];
    h[idx] = id;
}

// 遍历链表查找元素
bool find(int x)
{
    int idx = f(x);
    for (int i = h[idx]; i != 0; i = ne[i])
    {
        if (e[i] == x)
            return true;
    }
    return false;
}

int main()
{
    int n;
    cin >> n;
    while (n--)
    {
        int op, x;
        cin >> op >> x;
        if (op == 1)
        {
            insert(x);
        }
        else
        {
            if (find(x))
                cout << "yes" << endl;
            else
                cout << "no" << endl;
        }
    }
    return 0;
}

5.unordered_set / unordered_multiset

cpp 复制代码
#include <iostream>
#include <unordered_set>
#include <string>   // 字符串需要此头文件,原代码缺失补上
using namespace std;

int main()
{
    string strs[] = {"张三", "李四", "王五", "赵六", "小杨", "小珂"};
    unordered_set<string> mp;

    // 循环插入所有姓名
    for (auto& s : strs)
    {
        mp.insert(s);
    }

    // 查询张三是否存在
    if (mp.count("张三"))
        cout << "yes" << endl;
    else
        cout << "no" << endl;

    // 删除张三
    mp.erase("张三");

    // 再次查询张三
    if (mp.count("张三"))
        cout << "yes" << endl;
    else
        cout << "no" << endl;

    // 遍历输出容器内剩余所有元素
    for (auto& s : mp)
    {
        cout << s << " ";
    }

    return 0;
}

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

void test()
{
    // key:顶点编号,value:邻接顶点数组(邻接表写法)
    unordered_map<int, vector<int>> mp;
    mp[1].push_back(2);
    mp[2] = {3, 4, 5};
    mp[3].push_back(1);
    mp[3].push_back(2);
    mp[3].push_back(3);

    // 遍历哈希表,打印邻接表
    for (auto& [a, v] : mp)
    {
        cout << a << ": ";
        for (auto b : v)
            cout << b << " ";
        cout << endl;
    }
}

int main()
{
    test();

    unordered_map<string, int> mp;
    // 方式1:insert插入键值对
    // 写⼀个⼤括号,把需要放进去的元素括起来即可
    mp.insert({"张三", 1});
    mp.insert({"李四", 2});
    mp.insert({"王五", 3});

    // 方式2:[]运算符赋值插入
    // operator[]:可以让 map 像数组⼀样使⽤
    // 赋值
    mp["赵六"] = 4;

    // 查询数值
    if (mp["赵六"] == 4)
        cout << "yes" << endl;
    else
        cout << "no" << endl;
    // 使⽤查询的时候要注意,如果 map 中本⾝没有该元素,它会先插⼊,然后再拿值
    // 插⼊的时候,第⼆个关键字是默认值
    // 如果是数,那就是 0
    // 如果是字符串,那就是空串
    if (mp["小美"])    //// 会把 <"⼩美", 0> 放进去
        cout << "no" << endl;

    // count() 查询,不会新增元素
    if (mp.count("小帅"))
        cout << "yes" << endl;
    else
        cout << "no" << endl;

    // 删除键值对
    mp.erase("小美");

    // 遍历全部数据
    for (auto& [s, num] : mp)
    {
        cout << s << "编号为: " << num << endl;
    }

    return 0;
}
1 学籍管理
cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int main()
{
    unordered_map<string, int> mp;
    int T;
    cin >> T;
    while (T--)
    {
        int op;
        string name;
        cin >> op;
        if (op == 1) // 插入/修改数据
        {
            cin >> name;
            int x;
            cin >> x;
            mp[name] = x;
            cout << "OK" << endl;
        }
        else if (op == 2) // 查询数据
        {
            cin >> name;
            if (mp.count(name))
                cout << mp[name] << endl;
            else
                cout << "Not found" << endl;
        }
        else if (op == 3) // 删除数据
        {
            cin >> name;
            if (mp.count(name))
            {
                mp.erase(name);
                cout << "Deleted successfully" << endl;
            }
            else
                cout << "Not found" << endl;
        }
        else // 输出哈希表中元素总个数
        {
            cout << mp.size() << endl;
        }
    }
    return 0;
}
2 不重复数字
cpp 复制代码
#include <iostream>
#include <unordered_set>
using namespace std;

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int n;
        scanf("%d", &n);
        unordered_set<int> mp;
        
        for (int i = 1; i <= n; i++)
        {
            int x;
            scanf("%d", &x);
            // 该数字没有出现过
            if (!mp.count(x))
            {
                printf("%d ", x);
                mp.insert(x);
            }
        }
        puts("");
    }
    return 0;
}

cin比较慢

3 阅读理解

如果用vector的话会因为haha右两个 导致右两个1 会麻烦 但是用set会去重

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

// key:单词,value:有序集合,保存该单词出现过的所有文章编号
unordered_map<string, set<int>> mp;

int main()
{
    int n;
    cin >> n;
    // 依次读取第 1 ~ n 篇文章
    for (int i = 1; i <= n; i++)
    {
        int l;
        cin >> l;
        // 读取本篇的 l 个单词
        while (l--)
        {
            string s;
            cin >> s;
            mp[s].insert(i);
        }
    }

    int m;
    cin >> m;
    // m 次查询单词对应的文章编号
    while (m--)
    {
        string s;
        cin >> s;
        for (auto num : mp[s])
        {
            cout << num << " ";
        }
        cout << endl;
    }

    return 0;
}
4 A - B 数对
cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
LL n, c;
LL a[N];
unordered_map<int, int> mp;  // key:数值  value:该数值出现次数

int main()
{
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        mp[a[i]]++; //出现一次就+1次
    }

    LL ret = 0;
    for (int i = 1; i <= n; i++)
    {
        // 统计满足 a[j] - a[i] = c 的数量,等价查找 a[i]+c 的个数
        ret += mp[c + a[i]];
    }

    cout << ret << endl;
    return 0;
}
5 Cities and States
cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int n;
int main()
{
    cin >> n;
    unordered_map<string, int> mp;
    int ret = 0;
    while (n--)
    {
        string a, b;
        cin >> a >> b;
        // 截取字符串a前两位
        a = a.substr(0, 2);
        // 起点、终点属于同一个州,不计入答案
        if (a == b)
            continue;
        // 累加反向配对的数量
        ret += mp[b + a];
        // 把当前正向组合存入哈希表
        mp[a + b]++;
    }
    cout << ret << endl;
    return 0;
}
相关推荐
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-37 整数分解为若干项之和(C语言实现)
c语言·开发语言·数据结构·算法
long3162 小时前
PostgreSQL 学习资料 · 入门 / 练习 / 精通 / 扩展
java·数据结构·数据库·spring boot·sql·postgresql·数据库开发
烬羽2 小时前
一个队列,怎么让滑动窗口从 O(nk) 变 O(n)?单调队列彻底搞懂
javascript·数据结构·算法
XH华2 小时前
C++语言第四章:模板初阶
数据结构·c++·算法
木木子2215 小时前
# 鸿蒙 ArkTS 实战:秒表 Stopwatch(示例 5)
数据结构·华为·list·harmonyos
geats人山人海16 小时前
数据结构第六章c语言 树的存储下二叉树的概念和存储
c语言·数据结构·算法
白狐_79817 小时前
408 数据结构算法题 01:线性表暴力求解保分指南
java·数据结构·算法
冻柠檬飞冰走茶17 小时前
PTA基础编程题目集 7-31 字符串循环左移(C语言实现)
c语言·开发语言·数据结构·算法
来一碗刘肉面18 小时前
树的存储结构
数据结构