C++和Python要点对比 【数据结构】

2 篇文章 0 订阅
1 篇文章 0 订阅

C++和Python要点对比


前言

本人以C++作为工作项目应用主语言 ,但是也会用到python,而且经常使用python作为力扣算法题的刷题主语言,经常发现容易混淆的函数、语法、和数据结构,于是想做个整理,持续更新。


提示:以下是本篇文章正文内容,下面案例可供参考

一、数据结构对比

1.链表功能实现

1.C++

C++中的链表是一种线性数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表可以分为单向链表和双向链表。

单向链表:每个节点只有一个指向下一个节点的指针。
双向链表:每个节点有两个指针,一个指向前一个节点,另一个指向后一个节点。
下面是一个简单的单向链表实现及其示例:

#include <iostream>

// 定义链表节点结构体
struct ListNode {
    int val; // 数据域
    ListNode *next; // 指针域,指向下一个节点
    ListNode(int x) : val(x), next(NULL) {} // 构造函数
};

// 创建链表
ListNode* createList(int arr[], int n) {
    if (n == 0) return NULL;
    ListNode *head = new ListNode(arr[0]);
    ListNode *cur = head;
    for (int i = 1; i < n; ++i) {
        cur->next = new ListNode(arr[i]);
        cur = cur->next;
    }
    return head;
}

// 打印链表
void printList(ListNode *head) {
    ListNode *cur = head;
    while (cur != NULL) {
        std::cout << cur->val << " -> ";
        cur = cur->next;
    }
    std::cout << "NULL" << std::endl;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    ListNode *head = createList(arr, 5);
    printList(head);
    return 0;
}

也可以使用STL
C++的标准模板库(STL)中,std::list是一个双向链表容器,它允许在任何位置快速地插入和删除元素。std::list是模板类,可以存储任何类型的对象,包括基本数据类型、类对象和其它STL容器对象。

C++ STL中的std::list的特点:
双向链表:每个元素都是一个节点,包含数据和两个指向前一个和后一个节点的指针。
动态内存分配:std::list自动处理节点的内存分配和释放。
插入和删除操作效率高:由于std::list是链表结构,所以在任意位置插入和删除节点的操作都具有较高的效率,不需要移动其他元素。
不支持随机访问:与数组和std::vector不同,std::list不支持随机访问迭代器,因为它是链表结构,访问一个元素需要从头节点或尾节点遍历。
支持双向迭代器:std::list提供双向迭代器,可以从头到尾或从尾到头遍历链表。
大小可变:std::list的大小可以随时改变,可以根据需要动态增长和缩小。
std::list的简单使用示例:

#include <iostream>
#include <list>

int main() {
    // 创建一个空的std::list
    std::list<int> myList;

    // 向list中添加元素
    myList.push_back(1); // 在尾部添加元素
    myList.push_front(2); // 在头部添加元素

    // 插入元素到指定位置
    auto it = myList.begin();
    ++it; // 移动到第二个元素
    myList.insert(it, 3); // 在第二个元素后面插入新元素

    // 删除元素
    myList.remove(1); // 删除值为1的元素

    // 遍历并打印元素
    for (const auto& item : myList) {
        std::cout << item << " ";
    }
    std::cout << std::endl;

    // 输出链表大小
    std::cout << "Size of list: " << myList.size() << std::endl;

    return 0;
}

2.python

Python中没有内置的链表数据类型,但可以通过类来实现链表。

链表是一种数据结构,它通过指针将一系列节点连接起来,每个节点包含数据和指向下一个节点的指针。与数组不同,链表中的元素在内存中不是连续存储的,它们可以分散存储在内存的各个位置。链表有两种类型:单向链表和双向链表。单向链表的节点只有一个指向下一个节点的指针,而双向链表的节点既有指向下一个节点的指针,也有指向前一个节点的指针。

在Python中,虽然没有像C++ STL中的std::list那样的内置链表类型,但我们可以使用类(class)来自定义链表,并实现链表的各种操作。以下是实现链表的基本步骤:

定义节点:首先需要定义链表的节点类,每个节点包含数据部分和指向下一个节点的指针(或引用)。
定义链表:然后定义链表类,它通常包含对链表进行操作的方法,如添加节点、删除节点、遍历链表等。
操作链表:通过链表类提供的方法,可以对链表进行各种操作,如在链表头部或尾部添加新节点,或者在链表中搜索特定值的节点并删除它。
需要注意的是,由于Python的内存管理是自动的,使用链表时不需要担心内存分配和释放的问题,这一点与C++等语言有所不同。此外,Python的列表(list)实际上是一种动态数组,它在内部实现上与链表不同,但在功能上可以满足大多数链表的使用场景。如果需要在Python中实现链表,通常是因为学习数据结构的需要或者是因为链表在某些特定应用场景下的性能优势。

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None

    def add_node(self, data):
        new_node = Node(data)
        if self.head is None:
            self.head = new_node
        else:
            current = self.head
            while current.next is not None:
                current = current.next
            current.next = new_node

    def delete_node(self, data):
        if self.head is None:
            return
        if self.head.data == data:
            self.head = self.head.next
            return
        current = self.head
        while current.next is not None and current.next.data != data:
            current = current.next
        if current.next is not None:
            current.next = current.next.next

    def traverse(self):
        current = self.head
        while current is not None:
            print(current.data)
            current = current.next

# 创建链表对象
my_list = LinkedList()

# 添加节点
my_list.add_node(1)
my_list.add_node(2)
my_list.add_node(3)
my_list.add_node(4)
my_list.add_node(5)

# 遍历链表
my_list.traverse() # 输出:1 2 3 4 5

# 删除节点
my_list.delete_node(3)
my_list.delete_node(1)
my_list.delete_node(5)

# 再次遍历链表
my_list.traverse() # 输出:2 4

2.数组功能实现

C++的数组实现

vector 向量

C++中的vector是一种动态数组,它可以根据需要自动调整大小。vector是C++标准库中的一部分,位于头文件中。

vector的相关知识点:
动态数组:vector是一个动态数组,可以在运行时动态地增加或减少元素。
随机访问:vector支持随机访问,可以通过下标直接访问任意元素。
自动管理内存:vector会自动管理内存,当需要更多空间时,会自动分配新的内存,并将原有元素复制到新的位置。
支持迭代器:vector支持迭代器,可以使用迭代器遍历元素。
支持STL算法:vector支持STL算法,可以直接使用各种算法对vector进行操作。
vector的实现原理:

vector的内部实现基于动态数组,它维护了一个指向数组首元素的指针和一个表示数组大小的变量。当需要添加新元素时,如果数组已满,则会重新分配一个更大的数组,并将原有元素复制到新数组中。当删除元素时,如果数组过大,则会释放多余的空间。

std::vector<int> myVector;
myVector.push_back(1);
std::cout << "第一个元素:" << myVector[0] << std::endl;

// 在第二个位置插入一个元素
myVector.insert(myVector.begin() + 1, 4);

//使用范围for循环遍历vector
for (int num : myVector) {
    std::cout << num << " ";
}
std::cout << std::endl;

// 删除最后一个元素
myVector.pop_back();

// 删除指定范围内的元素
myVector.erase(myVector.begin() + 1, myVector.begin() + 3);
// 将vector大小调整为5个元素
myVector.resize(5);
// 将vector大小调整为3个元素,多余的元素将被丢弃
myVector.resize(3);
// 预分配至少能容纳10个元素的内存空间
myVector.reserve(10);


以下是vector的所有成员函数及其示例:

构造函数相关:
vector():创建一个空的vector。
vector(n):创建一个包含n个元素的vector,每个元素默认初始化为0。
vector(n, val):创建一个包含n个元素的vector,每个元素都初始化为val。
vector(begin, end):创建一个包含指定范围内元素的vector。
vector(initList):使用初始化列表创建vector。
vector(otherVector):从另一个vector创建一个新的vector。
元素访问:
at(index):访问指定位置的元素,如果索引无效,会抛出out_of_range异常。
operator:访问指定位置的元素,不进行边界检查。
元素操作:
push_back(val):在vector的尾部添加一个新元素。
pop_back():删除vector的最后一个元素。
insert(iterator, element):在指定位置插入一个元素。
erase(iterator):删除指定位置的元素。
erase(first, last):删除指定范围内的元素。
clear():删除所有元素,但不释放内存。
大小和容量:
size():返回vector中元素的个数。
capacity():返回vector在不重新分配内存的情况下最多可以容纳的元素个数。
resize(newSize):调整vector的大小。
reserve(newCapacity):预分配内存,提高添加元素时的效率。
迭代器相关:
begin():返回指向第一个元素的迭代器。
end():返回指向最后一个元素之后的迭代器。
rbegin():返回指向最后一个元素的逆向迭代器。
rend():返回指向第一个元素之前的逆向迭代器。
其他函数:
empty():检查vector是否为空。
data():返回指向内部存储的第一个元素的指针。

array容器

在C++中,std::array是C++11标准引入的一个STL容器,它提供了与原生数组类似的功能和性能。以下是std::array的一些主要特点:

  1. 固定大小std::array的大小在编译时必须是确定的,这意味着一旦创建,其大小不能更改。
  2. 性能优势:由于std::array的元素直接存放在实例内部,而不是在堆上分配空间,因此具有与原生数组相似的性能特征。
  3. 安全性:与原生数组相比,std::array提供了更多的安全保证,例如不会发生越界访问的问题。
  4. 构造和析构std::array的构造函数、析构函数和赋值操作符都是编译器隐式声明的,这使得它们的使用更加简单直观。
  5. 不支持动态调整:由于std::array的大小是固定的,它不支持添加和删除元素以及改变容器大小的操作。
  6. 适用场景std::array非常适合于那些需要高性能且大小确定不变的数组应用场景。

总的来说,虽然std::array在某些方面可能不如std::vector等其他STL容器灵活,但它在需要高性能和固定大小的场景下是一个很好的选择。了解std::array的特性和适用场景可以帮助开发者更好地利用这一工具,提高代码的效率和安全性。

std::array提供了一些常用的成员函数和方法,以下是其中的一些

#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // size(): 返回数组的大小。
    std::cout << "Array size: " << arr.size() << std::endl;

    // empty(): 检查数组是否为空。
    if (arr.empty()) {
        std::cout << "Array is empty." << std::endl;
    } 

    // at(index): 返回指定索引位置的元素的引用。
    std::cout << "Element at index 2: " << arr.at(2) << std::endl;

    // front(): 返回第一个元素的引用。
    std::cout << "First element: " << arr.front() << std::endl;

    // back(): 返回最后一个元素的引用。
    std::cout << "Last element: " << arr.back() << std::endl;

    // fill(value): 将数组的所有元素设置为指定的值。
    arr.fill(0);
    
    // swap(other_array): 交换两个数组的内容。
    std::array<int, 5> other_arr = {6, 7, 8, 9, 10};
    arr.swap(other_arr);
    std::cout << "Array after swapping: ";
    for (const auto& element : arr) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // begin(): 返回指向第一个元素的迭代器。
    std::cout << "First element using iterator: " << *arr.begin() << std::endl;

    // end(): 返回指向最后一个元素之后的迭代器。
    std::cout << "End iterator: " << *arr.end() << std::endl;

    return 0;
}


//C++中的std::array没有直接提供sort方法,但可以使用标准库中的算法对其进行排序
//vector也是
#include <algorithm>
#include <array>
#include <vector>
std::array<int, 5> arr = {5, 3, 4, 1, 2};
std::sort(arr.begin(), arr.end());

std::vector<int> vec = {5, 3, 4, 1, 2};
std::reverse(vec.begin(), vec.end());

array与vector的区别

std::arraystd::vector是C++ STL中的两种不同的容器,它们在内存分配、大小变更、迭代器稳定性以及性能方面存在显著差异。

  1. 内存分配
  • std::vector是一个动态数组,它允许在运行时增加和减少元素,因此其内存空间大小是可以改变的。当需要存储更多元素时,std::vector会重新分配内存,通常是申请一个更大的连续内存区域,并将原有元素复制到新的内存区域。
  • std::array则是一个静态数组,其大小在编译时就已经确定,不能在运行时改变。这意味着一旦创建了std::array实例,它将始终占用相同的内存空间。
  1. 大小变更
  • std::vector可以通过push_backpop_back等成员函数方便地添加或删除元素,从而实现动态调整容器的大小。
  • 相比之下,std::array由于大小固定,不支持添加或删除元素的操作。
  1. 迭代器稳定性
  • std::vector的扩容过程中,由于可能涉及到内存的重新分配和元素的移动,指向std::vector中元素的迭代器可能会失效。
  • std::array由于不会进行内存重新分配,其迭代器始终稳定,不会因为元素的添加或删除而失效。
  1. 性能方面
  • std::array由于其内存布局和原生数组相似,访问速度非常快,且没有额外的内存开销。
  • std::vector虽然也提供了快速的随机访问能力,但由于可能需要进行内存管理和元素的移动,在某些情况下可能会有额外的性能开销。

综上所述,std::arraystd::vector各有优势,选择使用哪个容器取决于具体的应用场景。如果需要固定大小的数组并且对性能有较高要求,std::array是一个不错的选择;如果需要动态调整数组大小,那么std::vector更加合适。在实际编程中,了解这两种容器的特点可以帮助开发者做出更合适的选择。

Python的数组实现

Python中,列表(List)是一种非常灵活且功能强大的数据结构。它允许存储不同类型的数据,并且可以动态地增加和减少元素。以下是关于Python列表的详细讲解:
创建列表:

1.创建空列表:empty_list = []
创建带有初始元素的列表:my_list = [1, ‘hello’, 3.14]

2.访问列表元素:
使用索引访问元素:my_list[0]返回第一个元素(1)
使用负数索引从尾部开始访问:my_list[-1]返回最后一个元素(3.14)

3.修改列表:
更新元素:my_list[0] = 100将第一个元素更新为100
添加元素到列表末尾:my_list.append(‘world’)
插入元素到指定位置:my_list.insert(1, 200)在第二个位置插入200
删除特定位置的元素:del my_list[1]删除第二个位置的元素
删除特定值的元素:my_list.remove(‘hello’)删除第一个值为’hello’的元素
4.切片操作:
获取列表的一部分:sub_list = my_list[1:4]获取从索引1到3的元素
复制列表:copy_list = my_list[:]

5.列表方法:
len(my_list):返回列表的长度
max(my_list):返回列表中的最大值
min(my_list):返回列表中的最小值
sum(my_list):返回列表元素的总和
sorted(my_list):对列表进行排序并返回新列表
reversed(my_list):反转列表并返回迭代器

6.列表推导式:
创建新列表:squares = [x**2 for x in range(10)]生成0到9的平方数列表

7.列表内置函数:
list():将可迭代对象转换为列表
list.append():在列表末尾添加新的对象
list.extend():在列表末尾一次性追加另一个序列中的多个值
list.count():返回某个元素在列表中出现的次数
list.index():返回某个元素首次出现时的索引
list.insert():在指定位置插入一个元素
list.remove():移除列表中某个值的第一个匹配项
list.pop():移除列表中的一个元素(默认最后一个元素),并返回该元素的值
list.reverse():反向列表中元素
list.sort():对列表进行排序

8.成员操作:
判断元素是否在列表中:‘hello’ in my_list返回True或False
列表与循环:

遍历列表:for item in my_list: print(item)

# 创建空列表
empty_list = []

# 创建带有初始元素的列表
my_list = [1, 'hello', 3.14]

# 访问列表元素
first_element = my_list[0]  # 返回第一个元素(1)
last_element = my_list[-1]  # 返回最后一个元素(3.14)

# 修改列表
my_list[0] = 100  # 更新第一个元素为100
my_list.append('world')  # 在列表末尾添加新元素'world'
my_list.insert(1, 200)  # 在第二个位置插入200
del my_list[1]  # 删除第二个位置的元素
my_list.remove('hello')  # 删除第一个值为'hello'的元素

# 切片操作
sub_list = my_list[1:4]  # 获取从索引1到3的元素
copy_list = my_list[:]  # 复制整个列表

# 列表方法
length = len(my_list)  # 返回列表的长度
maximum = max(my_list)  # 返回列表中的最大值
minimum = min(my_list)  # 返回列表中的最小值
sum_of_elements = sum(my_list)  # 返回列表元素的总和
sorted_list = sorted(my_list)  # 对列表进行排序并返回新列表
reversed_list = reversed(my_list)  # 反转列表并返回迭代器

# 列表推导式
squares = [x**2 for x in range(10)]  # 生成0到9的平方数列表

# 列表内置函数
new_list = list((1, 2, 3))  # 将元组转换为列表
extended_list = my_list.extend([4, 5])  # 在列表末尾一次性追加另一个序列中的多个值
count = my_list.count(100)  # 返回某个元素在列表中出现的次数
index = my_list.index('world')  # 返回某个元素首次出现时的索引
my_list.insert(2, 'hello')  # 在指定位置插入一个元素
my_list.remove(200)  # 移除列表中某个值的第一个匹配项
popped_element = my_list.pop()  # 移除列表中的一个元素(默认最后一个元素),并返回该元素的值
my_list.reverse()  # 反向列表中元素
my_list.sort()  # 对列表进行排序
my_list.clear()  # 清空列表中的所有元素
copied_list = my_list.copy()  # 返回列表的一个浅拷贝
joined_string = ', '.join(map(str, my_list))  # 将列表中的元素连接成一个字符串

# 列表与循环
for item in my_list:
    print(item)

# 列表与函数
squared_numbers = list(map(lambda x: x**2, [1, 2, 3]))
even_numbers = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5]))

# 列表与切片
sub_list = my_list[1:4]
copy_list = my_list[:]
every_second_element = my_list[::2]
reversed_list = my_list[::-1]

# 列表与元组
tuple_to_list = list((1, 2, 3))
list_to_tuple = tuple([1, 2, 3])

# 列表与字典
keys_list = list({'a': 1, 'b': 2}.keys())
values_list = list({'a': 1, 'b': 2}.values())
items_list = list({'a': 1, 'b': 2}.items())

# 列表与集合
set_to_list = list({1, 2, 3})
list_to_set = set([1, 2, 3])

3.双端队列 deque

C++中的deque

C++中的deque(双端队列)是一个功能强大的容器,特别擅长在两端进行高效的插入和删除操作。具体有以下方面:

功能:它允许我们在容器的前端和后端快速地进行插入和删除操作。这一点与vector不同,后者在前端插入和删除时效率较低,因为需要移动大量元素来维护连续内存空间。
内部工作原理:deque内部由多个缓冲区块组成,这些块通过一个中控器链接起来。每个块包含一定数量的元素,并且可以独立地管理这些元素,使得deque能够支持在两端的高效操作。
迭代器:deque的迭代器支持随机访问,这意味着我们可以通过索引直接访问任何元素,就像使用数组一样。
应用场景:由于deque具有在两端都能高效操作的特点,它特别适合那些需要在序列两端频繁进行插入或删除元素的场合。同时,如果需要随机访问元素,但又希望保持在两端操作的高效率,deque也是很好的选择。
性能考虑:尽管deque在两端的操作非常高效,但由于其分块的内部结构,它在中间位置插入或删除元素时可能效率不如vector。此外,如果对内存的连续性有严格要求,deque可能也不是最佳选择,因为它的内存布局是分块的,不是完全连续的。
替代其他容器:deque可以用来代替stack、queue等容器,因为它支持sort、lower_bound、reverse等STL算法和操作。但要注意,虽然理论上可以替代,实际使用时还需考虑性能差异和具体需求。

函数方法

push_back():在deque的尾部添加一个元素。
push_front():在deque的头部添加一个元素。
pop_back():删除deque的尾部元素。
pop_front():删除deque的头部元素。
size():返回deque中元素的个数。
empty():检查deque是否为空。
clear():清空deque中的所有元素。
begin():返回指向deque第一个元素的迭代器。
end():返回指向deque最后一个元素之后的迭代器。
rbegin():返回指向deque最后一个元素的逆向迭代器。
rend():返回指向deque第一个元素之前的逆向迭代器。

#include <iostream>
#include <deque>

int main() {
    // 创建一个整数类型的deque
    std::deque<int> my_deque;

    // 向deque中添加元素
    my_deque.push_back(1);
    
    // 输出deque的大小
    std::cout << "Deque size: " << my_deque.size() << std::endl;

    // 输出deque中的元素
    for (auto it = my_deque.begin(); it != my_deque.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 删除头部和尾部元素
    my_deque.pop_front();
    my_deque.pop_back();

    // 输出修改后的deque大小和内容
    std::cout << "Modified deque size: " << my_deque.size() << std::endl;
    for (auto it = my_deque.begin(); it != my_deque.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

python中的deque

Python中的deque(双端队列)是来自collections模块的一个类,所以使用的时候需要import,它提供了在两端快速添加和删除元素的高效数据结构。

以下是deque的一些主要特点和方法:

双向操作:deque支持在队列的头部和尾部进行添加(append)和删除(pop 或 popleft)元素的操作,这使得它在处理需要频繁在两端操作的数据时非常高效。
性能优势:与使用列表(list)相比,deque在执行添加和删除操作时具有更好的性能,尤其是在列表较大时。
线程安全:deque是一个线程安全的实现,这意味着它可以在多线程环境中安全使用。
固定和可变长度:deque可以设置为固定长度(通过maxlen参数),当达到最大长度时,新添加的元素会导致最早添加的元素被自动删除,这可以用于实现类似于循环缓存的功能。
功能丰富:除了基本的添加和删除操作,deque还支持旋转(rotate)、扩展(extend)、复制(copy)、清空(clear)等操作,以及in操作符来检查元素是否存在于deque中。
应用场景:由于其高效的性能,deque常用于需要快速插入和删除的场景,如实现一个任务队列、日志循环存储等

from collections import deque

# 创建一个deque
d = deque()

# 在尾部添加元素
d.append(1)
d.append(2)
d.append(3)

# 在头部添加元素
d.appendleft(0)

# 从尾部删除元素
last = d.pop()

# 从头部删除元素
first = d.popleft()

# 判断deque是否包含某个元素
print(1 in d)  # 输出: True

# 设置最大长度
d = deque(maxlen=3)
d.append(1)
d.append(2)
d.append(3)
d.append(4)  # 此时deque为 [2, 3, 4],因为超过了maxlen
# 扩展deque
d.extend([5, 6])
print(d)  # 输出:deque([3, 4, 5, 6], maxlen=3)

# 旋转deque
d.rotate(1)
print(d)  # 输出:deque([6, 3, 4, 5], maxlen=3)

# 反转deque
d.reverse()
print(d)  # 输出:deque([5, 4, 3, 6], maxlen=3)

# 清空deque
d.clear()
print(d)  # 输出:deque([], maxlen=3)

4. 集合 功能的实现 set

C++的集合实现

C++ 当中 提供了三种不同的集合的形式,

集合底层实现是否有序数值是否可以重复能否更改数值时间复杂度
std::set红黑树有序O(log n)
std::multiset红黑树有序O(log n)
std::unordered_set哈希表无序O(1)

因为unordered_set的底层是哈希表所以它可以是无序的,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储,不过给我们的使用方式,还是哈希法的使用方式,即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。

C++的 std::unordered_set

如果是刷力扣题目的话,最常见的 其实还是 unordered_set
unordered_set是一个无序的集合,它允许快速插入和查找操作,平均时间复杂度为O(1)。与set不同,unordered_set内部使用哈希表来存储元素,这意味着元素不是按照任何特定顺序存储的。这种数据结构非常适合于那些不需要有序遍历元素,但需要快速访问和查找元素的场景。例如,当需要检查一个元素是否存在于集合中时,unordered_set是一个很好的选择。

unordered_set的特点包括:

  • 快速访问:由于使用了哈希表,因此访问时间通常是常数级别的。
  • 无序存储:元素在内部是无序的,不支持顺序访问。
  • 去重:与set一样,unordered_set也不允许存储重复的元素。
  • 支持迭代器:尽管是无序的,但unordered_set仍然支持迭代器进行遍历。

以下是一些常用的unordered_set成员函数及其用法示例

size(): 返回unordered_set中元素的个数。
empty(): 检查unordered_set是否为空。
insert(value): 向unordered_set中插入一个元素。
erase(value): 从unordered_set中删除指定的元素。
clear(): 清空unordered_set中的所有元素。
find(value): 查找unordered_set中是否存在指定的元素。
count(value): 返回unordered_set中指定元素的个数。
begin(): 返回指向unordered_set中第一个元素的迭代器。
end(): 返回指向unordered_set中最后一个元素之后位置的迭代器。

#include <iostream>
#include <unordered_set>
#include <string>

int main() {
    std::unordered_set<std::string> my_set;

    // 向集合中添加元素
    my_set.insert("a");
    my_set.insert("b");
    my_set.insert("o");

    // 检查集合中是否包含某个元素
    if (my_set.find("a") != my_set.end()) {
        std::cout << "找到了a" << std::endl;
    } else {
        std::cout << "没有找到a" << std::endl;
    }

    // 遍历集合中的元素
    for (const auto& item : my_set) {
        std::cout << item << std::endl;
    }

    // 删除集合中的元素
    my_set.erase("b");

    // 获取集合的大小
    std::cout << "集合的大小为:" << my_set.size() << std::endl;

    return 0;
}

python中的set

Python中的set是一个无序的、不包含重复元素的集合

底层实现:set的底层实现是基于哈希表的,这意味着set中的元素是无序的,并且具有快速查找和插入的特点。
元素唯一性:set中的元素必须是唯一的,这是通过哈希表的特性来保证的。当尝试添加一个已经存在于set中的元素时,该元素不会被重复添加。
可哈希性:set中的元素必须是不可变的,因为只有不可变的类型才能被哈希。例如,数字、字符串和元组都是不可变的,因此可以作为set的元素。
操作方法:set提供了丰富的方法来进行集合运算,如并集、交集、差集和对称差集等。这些方法使得处理集合相关的数学问题变得非常方便。
性能优势:由于set是基于哈希表实现的,所以它的查找和插入操作通常比列表快得多。这使得set在需要快速检查元素是否存在的场景下非常有用。
去重功能:set经常被用来去除列表中的重复元素。通过将列表转换为集合,可以轻松地去除所有重复的元素,因为集合不允许有重复的元素存在。

比如给列表去重的时候非常方便,

lst = [1, 2, 3, 2, 4, 5, 3]
new_lst = list(set(lst))
print(new_lst)

在Python中,set类型提供了多种方法来操作和处理集合。以下是一些常用的set方法和它们的使用方式:

  1. 创建集合

使用花括号 {} 或 set() 函数来创建一个集合。

my_set = {1, 2, 3}  # 通过花括号创建集合
empty_set = set()  # 创建一个空集合
  1. 添加元素

使用 add() 方法向集合中添加单个元素。

my_set.add(4)  # 将元素 4 添加到集合中
  1. 更新集合

使用 update() 方法将一个集合(或任何可迭代对象)的所有元素添加到集合中。

my_set.update([5, 6, 7])  # 添加多个元素到集合中
#或者
set1 = {'a', 'b', 'c'}
set2 = {1, 2, 3}
set1.update(set2)
print(set1)
#输出
{1, 2, 3, 'b', 'a', 'c'}
  1. 删除元素

使用 discard() 或 remove() 方法从集合中删除指定元素。
discard() 不会引发错误,如果元素不存在;而 remove() 会引发错误。
pop() 方法用于随机移除一个元素。

my_set.discard(3)  # 如果元素存在则删除
my_set.remove(4)   # 如果元素存在则删除,如果不存在则引发 KeyError
my_set.pop()

使用pop()方法删除元素,一般此方法将删除容器最后一项。但,集合set是无序的,所以随机被删除一个项目。

  1. 清空集合

使用 clear() 方法清空集合中的所有元素。

my_set.clear()  # 清空集合中的所有元素
  1. 检查子集,检查超集

使用 issubset() 方法检查一个集合是否是另一个集合的子集
使用 issuperset() 方法检查一个集合是否是另一个集合的超集

is_subset = my_set.issubset({1, 2, 3, 4, 5})  # 检查 my_set 是否为给定集合的子集
is_superset = my_set.issuperset({1, 2})  # 检查 my_set 是否包含给定集合的所有元素

7.集合相等

set1 == set2 可以比较两个集合是否相等

set1 = {1, 2, 3}
set2 = {3, 2, 1}
print(set1 == set2)  # 输出 True
  1. 合并成一个新的集合
    可以使用集合的|操作符或union()方法。这两种方式都可以得到两个集合的并集,即包含所有唯一元素的集合。

如果有两个集合set1和set2,可以使用union方法将它们合并成一个新的集合,这个新的集合包含set1和set2中的所有元素,重复的元素只会出现一次。

set1 = {1, 2, 3}
set2 = {3, 4, 5}
merged_set = set1.union(set2)

在这个例子中,merged_set将是一个新的集合,包含了set1和set2中的所有元素,即{1, 2, 3, 4, 5}。

此外,还可以使用|操作符来获取两个集合的并集:

merged_set = set1 | set2

  1. 差集操作
    set1 - set2可以执行集合的差集操作。

差集操作会返回一个新集合,其中包含所有仅存在于第一个集合(set1)中的元素,而不包括第二个集合(set2)中的元素。换句话说,从set1中移除任何在set2中也出现的元素后,剩下的元素组成的集合就是差集。

set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7}
difference_set = set1 - set2
#    
在这个例子中,difference_set将会是{1, 2, 3},因为45同时出现在set1和set2中,所以它们被移除了。

此外,还可以使用difference()方法来获取差集:

difference_set = set1.difference(set2)

与“set1-set2”效果相同

  1. 交集操作
    在Python中,集合的交集表示两个(或多个)集合共有的元素。可以通过几种方式来获取这些元素:
    可以多个集合操作
    使用 & 符号:当使用 & 运算符时,会返回包含两个集合共有元素的新集合。例如,若有两个集合 x = {1, 2, 3, 4} 和 y = {3, 4, 5, 6},那么 x & y 将得到 {3, 4}。
x = {1, 2, 3, 4}
y = {3, 4, 5, 6}
intersection_set = x & y
print(intersection_set)  # 输出 {3, 4}

intersection() :这个方法同样用于计算两个集合的交集。继续上面的例子,x.intersection(y) 也会得到 {3, 4}。

x = {'a', 'b', 'c'}
y = {'x', 'y', 'z', 'c'}
z = {'c', 'a', 'd'}
print(x.intersection(y, z))
# 输出 {'c'}

intersection_update():这个方法会将交集的结果更新到调用该方法的集合上,并返回 None。它不会创建新的集合,而是就地修改集合。

x = {1, 2, 3, 4}
y = {3, 4, 5, 6}
x.intersection_update(y)
print(x)  # 输出 {3, 4}

5. 映射 功能的实现 map和dictionary

C++的map实现

映射底层实现是否有序数值是否可以重复能否更改数值时间复杂度
std::map红黑树key有序key不可重复key不可修改O(log n)
std::multimap红黑树key有序key可重复key不可修改O(log n)
std::unordered_map哈希表key不可重复key不可修改O(1)

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的
std::unordered_map的key是无序的。

map及红黑树的原理:
map是一个有序的关联容器,它根据键的大小进行排序。map的内部实现通常是红黑树,这是一种自平衡的二叉搜索树。在红黑树中,每个节点都包含一个键和一个值,左子树的所有键都小于根节点的键,右子树的所有键都大于根节点的键。这种结构保证了map中的元素按照键的顺序存储。

map和multimap:

Map/Multimap:Map的元素是成对的键值/实值,内部的元素依据其值自动排序,Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素,内部由二叉树实现,便于查找;
容器类自动申请和释放内存,无需new和delete操作。
map和multimap都需要#include,唯一的不同是,map的键值key不可重复,而multimap可以,也正是由于这种区别,map支持[ ]运算符,multimap不支持[ ]运算符。在用法上没什么区别。

C++中map提供的是一种键值对容器,里面的数据都是成对出现的,如下图:每一对中的第一个值称之为关键字(key),每个关键字只能在map中出现一次;第二个称之为该关键字的对应值。

Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。

map和unordered_map:

都是c++中可以充当字典(key-value)来用的数据类型。
map:
底层数据结构红黑树,因此map内部所有的数据都是有序的,map的查询、插入、删除操作的时间复杂度都是O(logn)。

#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> my_map;

    // 插入元素
    my_map[1] = "one";
    my_map[2] = "two";
    my_map[3] = "three";

    // 查找元素
    auto it = my_map.find(2);
    if (it != my_map.end()) {
        std::cout << "Found: " << it->first << " -> " << it->second << std::endl;
    } else {
        std::cout << "Not found" << std::endl;
    }

    // 遍历元素
    for (const auto& kv : my_map) {
        std::cout << kv.first << ": " << kv.second << std::endl;
    }

    return 0;
}

//Found: 2 -> two
//1: one
//2: two
//3: three

unordered_map:
unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序,存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的。unordered_map的底层是一个防冗余的哈希表(开链法避免地址冲突)。unordered_map的key需要定义hash_value函数并且重载operator ==。

哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,时间复杂度为O(1);而代价仅仅是消耗比较多的内存。哈希表的查询时间虽然是O(1),但是并不是unordered_map查询时间一定比map短,因为实际情况中还要考虑到数据量,而且unordered_map的hash函数的构造速度也没那么快,所以不能一概而论,应该具体情况具体分析。

map的函数方法:

map<Key, T>::map():构造一个空的map。
map<Key, T>::map(const map<Key, T>& other):拷贝构造函数,创建一个与other相同的map。
template map<Key, T>::map(InputIterator first, InputIterator last):通过迭代器范围构造map。
void map<Key, T>::clear():清除map中的所有元素。
typename map<Key, T>::size_type map<Key, T>::size() const:返回map中的元素数量。
bool map<Key, T>::empty() const:如果map为空,则返回true,否则返回false。
typename map<Key, T>::iterator map<Key, T>::begin():返回指向map第一个元素的迭代器。
typename map<Key, T>::iterator map<Key, T>::end():返回指向map最后一个元素之后的迭代器。
typename map<Key, T>::reverse_iterator map<Key, T>::rbegin():返回指向map最后一个元素的反向迭代器。
typename map<Key, T>::reverse_iterator map<Key, T>::rend():返回指向map第一个元素之前的反向迭代器。
typename map<Key, T>::iterator map<Key, T>::find(const Key& key):查找具有指定键的元素,返回指向该元素的迭代器。如果找不到,则返回end()。
typename map<Key, T>::iterator map<Key, T>::lower_bound(const Key& key):返回指向首个不小于给定键的元素的迭代器。如果不存在这样的元素,则返回end()。
typename map<Key, T>::iterator map<Key, T>::upper_bound(const Key& key):返回指向首个大于给定键的元素的迭代器。如果不存在这样的元素,则返回end()。
std::pair<typename map<Key, T>::iterator, bool> map<Key, T>::insert(const value_type& value):插入一个键值对到map中,并返回一个包含指向新插入元素的迭代器和布尔值的对。如果键已经存在,则不会插入新元素,并返回指向已存在元素的迭代器和false。
void map<Key, T>::erase(iterator position):删除指向给定位置的元素。
void map<Key, T>::erase(iterator first, iterator last):删除迭代器范围内的所有元素。
void map<Key, T>::erase(const Key& key):删除具有指定键的元素。
void map<Key, T>::swap(map<Key, T>& other):交换两个map的内容。
成员函数:
key_comp():返回用于比较键的比较对象。
value_comp():返回用于比较值的比较对象。
max_size():返回map可以容纳的最大元素数量。
operator=:赋值运算符,将一个map赋值给另一个map。
operator[]:下标运算符,访问具有指定键的元素。如果键不存在,则插入一个新元素并返回其值。
at(const Key& key):访问具有指定键的元素。如果键不存在,则抛出out_of_range异常。
count(const Key& key):返回具有指定键的元素的数量。
emplace(Args&&… args):使用参数构造一个新的元素,并将其插入到map中。
emplace_hint(iterator hint, Args&&… args):使用参数构造一个新的元素,并将其插入到map中的指定位置。
equal_range(const Key& key):返回一个包含两个迭代器的对,这两个迭代器分别指向首个不小于和大于给定键的元素。

unordered_map:

  1. 带元素初始化
#include <iostream>
#include <map>

int main() {
    // 初始化一个包含一些元素的map
    std::map<int, std::string> my_map = {
        {1, "one"},
        {2, "two"},
        {3, "three"}
    };

    // 输出map中的元素
    for (const auto& pair : my_map) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}
//1: one
//2: two
//3: three

  1. size():返回unordered_map中的元素数量。
std::unordered_map<int, std::string> my_unordered_map;
my_unordered_map[1] = "one";
my_unordered_map[2] = "two";
my_unordered_map[3] = "three";

size_t size = my_unordered_map.size(); // 3

3.empty():如果unordered_map为空,则返回true,否则返回false。

std::unordered_map<int, std::string> my_unordered_map;
bool empty = my_unordered_map.empty(); // true

my_unordered_map[1] = "one";
empty = my_unordered_map.empty(); // false
  1. begin()和end():返回指向unordered_map第一个元素和最后一个元素之后的迭代器。
std::unordered_map<int, std::string> my_unordered_map;
my_unordered_map[1] = "one";
my_unordered_map[2] = "two";
my_unordered_map[3] = "three";

auto it = my_unordered_map.begin(); // 指向键值对(1, "one")
++it; // 指向键值对(2, "two")
++it; // 指向键值对(3, "three")
++it; // 指向最后一个元素之后的位置

  1. insert():插入一个键值对到unordered_map中,并返回一个包含指向新插入元素的迭代器和布尔值的对。如果键已经存在,则不会插入新元素,并返回指向已存在元素的迭代器和false。
std::unordered_map<int, std::string> my_unordered_map;
auto result = my_unordered_map.insert({1, "one"}); // 插入成功,result.second为true
result = my_unordered_map.insert({1, "another one"}); // 插入失败,result.second为false

  1. erase():删除具有指定键的元素或删除迭代器范围内的所有元素。
std::unordered_map<int, std::string> my_unordered_map;
my_unordered_map[1] = "one";
my_unordered_map[2] = "two";
my_unordered_map[3] = "three";

//迭代器擦除
auto it = my_unordered_map.find(2);  // 找到键为2的元素的迭代器
my_unordered_map.erase(it);  // 删除该元素
my_unordered_map.erase(my_unordered_map.begin(), my_unordered_map.end()); // 删除所有元素

//找到键 key ,擦除
my_unordered_map.erase(2);  // 删除键为2的元素

//按范围擦除
std::unordered_map<int, std::string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}};
auto it1 = myMap.find(2);  // 找到键为2的元素的迭代器
auto it2 = myMap.find(4);  // 找到键为4的元素的迭代器
myMap.erase(it1, it2);  // 删除从键为2到键为4之间的所有元素(包括键为2和键为4的元素)
  1. swap():交换两个unordered_map的内容。
std::unordered_map<int, std::string> my_unordered_map1;
my_unordered_map1[1] = "one";
my_unordered_map1[2] = "two";
my_unordered_map1[3] = "three";

std::unordered_map<int, std::string> my_unordered_map2;
my_unordered_map2[4] = "four";
my_unordered_map2[5] = "five";
my_unordered_map2[6] = "six";

my_unordered_map1.swap(my_unordered_map2); // 交换两个unordered_map的内容

python 中 哈希 的实现 dictionary

字典的特点:

无序性:字典中的元素顺序与插入顺序无关,这是因为Python中的字典是通过哈希表实现的。
可变性:字典是可变的,这意味着可以在运行时添加、修改或删除其中的键值对。
索引性:字典中的每个元素都由一个键和一个值组成,可以通过键来快速访问对应的值。
键的唯一性:字典中的键必须是唯一的,不能有重复的键存在。
类型的多样性:字典可以存储任意类型的对象作为键和值,这提供了极大的灵活性。

字典的操作:

创建字典:可以使用大括号{}来创建一个空字典,或者在大括号中放入一些键值对来创建一个非空字典。
访问字典:可以通过键来访问字典中的值,如果键不存在于字典中,会抛出KeyError异常。
修改字典:可以通过赋值操作来修改字典中的值,或者添加新的键值对。
删除字典:可以使用del语句来删除字典中的单个键值对,或者使用clear方法来删除字典中的所有键值对。
遍历字典:可以使用for循环来遍历字典中的键值对,通常会使用items()方法来同时获取键和值。

由于python实现的集合和字典是基于哈希表或哈希函数的,为了确保键的唯一性(键与哈希值一 一对应),则规定集合成员和字典中的键必须是可哈希的,这意味着其值在其生命周期内不会改变,所以又叫做不可变的。这样, Python 就可以创建一个唯一的哈希值来识别它,字典可以使用它来跟踪唯一键和集合来跟踪唯一值。

不可变类型(immutable types):int, float, decimal, complex, bool, string, tuple, range, frozenset, bytes

可变类型(mutable types):list, dict, set, bytearray, user-defined classes

  1. 字典的创建:
    d = {}
    dict():创建一个空字典或从已有的可迭代对象创建字典。
###字典的创建
d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male') 
d1 == d2 == d3 ==d4

#或者
# 创建一个空字典
d = {}
  1. 添加元素和访问(get(key,default))、修改
# 创建一个空字典
d = {}

# 向字典中添加元素
d['apple'] = 'fruit'
d['carrot'] = 'vegetable'
#字典访问可以直接索引键,如果不存在,就会抛出异常
# 访问字典中的元素
print(d['apple'])  # 输出: fruit
# 修改字典中的元素
d['apple'] = 'red apple'

使用 get(key, default) 函数来进行索引。
如果键不存在,调用 get() 函数可以返回一个默认值。

d = {'name': 'jason', 'age': 20}
d.get('name')
'jason'
d.get('location', 'null')
'null'

3. 删除元素
1、del d[key]:删除指定键的项

d={}
d['carrot'] = 'vegetable'
# 删除字典中的元素
del d['carrot']

2、d.pop(key, default):删除并返回指定键的值,如果键不存在则返回默认值。

my_dict = {'a': 1, 'b': 2, 'c': 3}
my_dict.pop('a')
print(my_dict)  # 输出:{'b': 2, 'c': 3}

3、使用clear()方法清空字典:

my_dict = {'a': 1, 'b': 2, 'c': 3}
my_dict.clear()
print(my_dict)  # 输出:{}

4. update函数
update()函数是字典的一个方法,用于将一个字典的键值对添加到另一个字典中。如果被添加的键已经存在于原字典中,则该键的值将被更新为新值。

dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict1.update(dict2)
print(dict1)


#输出:
#{'a': 1, 'b': 3, 'c': 4}

在这个例子中,dict1和dict2都有键’b’,但是使用update()函数后,dict1中的’b’的值被更新为3。同时,dict2中的键值对’c’: 4也被添加到了dict1中。

5. 字典—判断键、值和键值对是否在字典中
1.判断键是否在字典中
d.keys():返回一个视图对象,包含d中所有键。

<> in <字典名>   #(最快)
<> in <字典名>.keys()

d = {}
d['apple'] = 'fruit'
# 检查键是否在字典中
print('apple' in d)  # 输出: True

2.判断值是否在字典中
d.values():返回一个视图对象,包含d中所有值。

<> in <字典名>.values()

d = {'a': 1, 'b': 2, 'c': 3}
values_view = d.values()
print(values_view)  # 输出:dict_values([1, 2, 3])

# 将视图对象转换为列表
values_list = list(values_view)
print(values_list)  # 输出:[1, 2, 3]

3.判断键值对是否在字典中

(<><>) in <字典名>.items()
# 创建一个字典
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

# 遍历字典中的元素并打印键值对
for key, value in my_dict.items():
    print(key, value)

#输出结果
apple 1
banana 2
orange 3

总结

文章介绍了 C++ 以及 python对应的常见数据结构的知识点原理还有使用方法的对比还有详解,对于写算法题的时候很有帮助,也有助于同时学习和理解这两种编程语言。

参考:链接: link

链接: link
链接: link

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值