C++ 数据结构

数组(Array)

定义

        在C++中,数组是一种基本的数据结构,用于存储一系列相同类型的元素。这些元素在内存中连续存储,并且可以通过索引(或下标)来访问。数组的索引从0开始,即第一个元素的索引为0,第二个元素的索引为1,依此类推。

        数组的定义语法如下:

dataType   arrayName[ size ];

  • dataType 是数组中元素的类型(如int, float, char等)。
  • arrayName 是数组的名称。
  • size 是数组的大小,即数组中元素的数量。

特点

  • 固定大小:数组的大小在定义时确定,之后不能改变。
  • 连续存储:数组中的元素在内存中连续存储,因此访问速度较快。
  • 相同类型:数组中的所有元素必须是相同的数据类型。
  • 随机访问:可以通过索引直接访问数组中的任何元素,时间复杂度为O(1)。

优缺点

优点

  • 访问速度快,因为元素在内存中连续存储。
  • 支持随机访问。

缺点

  • 数组的大小在定义后不能改变,灵活性较差。
  • 如果数组过大,可能会导致内存浪费;如果数组过小,可能无法满足存储需求。

示例

        以下是一个简单的C++程序,演示了如何定义、初始化和访问数组:

#include <iostream>  
using namespace std;  
  
int main() 
{  
    // 定义一个整型数组,包含5个元素  
    int numbers[5];  
  
    // 初始化数组元素  
    numbers[0] = 10;  
    numbers[1] = 20;  
    numbers[2] = 30;  
    numbers[3] = 40;  
    numbers[4] = 50;  
  
    // 使用循环访问并打印数组元素  
    for (int i = 0; i < 5; i++) {  
        cout << "Element at index " << i << " is " << numbers[i] << endl;  
    }  
  
    // 另一种初始化数组的方法:在定义时直接初始化  
    int anotherArray[3] = {1, 2, 3};  
  
    // 打印anotherArray的元素  
    for (int i = 0; i < 3; i++) {  
        cout << "Another array element at index " << i << " is " << anotherArray[i] << endl;  
    }  
  
    return 0;  
}

结构体(Struct)

定义

        在C++中,结构体是一种用户自定义的数据类型,它允许将不同类型的数据项组合成一个单一的类型。结构体通常用于表示具有多个属性的实体,如点、矩形、日期等。结构体的定义使用struct关键字,并在花括号内列出其成员(变量和可能的函数)。

        结构体的定义语法如下:

struct structName {  
    dataType1 member1;  
    dataType2 member2;  
    // ... 可以包含更多成员  
    returnType functionName(parameters); // 可选的成员函数  
};
  • structName 是结构体的名称。
  • dataType1 member1; 等行定义了结构体的成员及其类型。
  • returnType  functionName(parameters); 是可选的成员函数声明,它定义了可以在结构体对象上调用的方法。

特点

  • 自定义数据类型:结构体允许将不同类型的数据组合在一起,形成一个新的数据类型。
  • 封装:结构体可以封装相关的数据成员和成员函数,使代码更加模块化和易于管理。
  • 默认访问权限:在C++中,结构体的成员默认是公开的(public),这意味着它们可以直接被访问和修改。
  • 灵活性:结构体可以根据需要包含任意数量的成员,包括其他结构体作为成员。

优缺点

优点

  • 提高了代码的可读性和可维护性,因为相关的数据被组织在一起。
  • 允许通过结构体名直接访问其成员(在C++中,但需要注意默认访问权限与类的区别)。
  • 可以包含成员函数,提供对数据的操作。

缺点

  • 如果结构体包含大量成员或复杂的成员函数,可能会导致代码变得笨重。
  • 结构体的默认访问权限是公开的,这可能会违反封装原则(尽管这可以通过将成员声明为私有并提供公共接口函数来解决)。

示例

        以下是一个简单的C++程序,演示了如何定义、初始化和使用结构体:

#include <iostream>  
using namespace std;  
  
// 定义一个结构体来表示点  
struct Point {  
    int x; // x坐标  
    int y; // y坐标  
  
    // 成员函数,用于输出点的坐标  
    void print() 
    {  
        cout << "Point coordinates: (" << x << ", " << y << ")" << endl;  
    }  
};  
  
int main() 
{  
    // 定义一个Point结构体变量并初始化  
    Point p1;  
    p1.x = 10;  
    p1.y = 20;  
  
    // 调用结构体的成员函数  
    p1.print();  
  
    // 另一种初始化结构体变量的方法:在定义时直接初始化  
    Point p2 = {30, 40};  
  
    // 打印p2的坐标  
    p2.print();  
  
    return 0;  
}

类(Class)

定义

        在C++中,类(class)是一种用户自定义的数据类型,它提供了比结构体(struct)更丰富的功能和更高的封装性。类不仅可以包含数据成员(属性),还可以包含成员函数(方法),用于操作这些数据。此外,类还支持访问控制(public、private、protected),允许更精细地控制成员的可访问性。

        类的定义语法如下:

class className {  
public:    // 公共成员,可以被外部访问  
    dataType1 member1;  
    returnType function1(parameters);  
  
private:   // 私有成员,只能被类内部的成员函数访问  
    dataType2 member2;  
    returnType function2(parameters);  
  
protected: // 受保护成员,可以被类及其派生类访问  
    dataType3 member3;  
    returnType function3(parameters);  
  
    // 成员函数的具体实现可以在类内部(内联函数)或外部  
    returnType function1(parameters) {  
        // 函数体  
    }  
};
  • className 是类的名称。
  • public、private 和 protected 是访问控制标签,用于指定成员的访问权限。
  • dataType1 member1; 等行定义了类的数据成员。
  • returnType  function1(parameters); 等行定义了类的成员函数原型。

特点

  • 封装:类将数据成员和成员函数封装在一起,隐藏了内部实现细节,只暴露必要的接口给外部使用。
  • 继承:C++支持类之间的继承关系,允许新类(派生类)继承现有类(基类)的属性和方法。
  • 多态:通过虚函数和继承,C++支持运行时多态性,允许用基类指针或引用来调用派生类的重写函数。
  • 访问控制:类可以使用public、private和protected关键字来控制成员的访问权限。
  • 构造函数和析构函数:类可以定义构造函数来初始化对象,定义析构函数来释放资源。

优缺点

优点

  • 提高了代码的可读性和可维护性,因为相关的数据和行为被封装在一起。
  • 提供了更高级的封装和访问控制机制。
  • 支持继承和多态,使得代码更加灵活和可扩展。

缺点

  • 类的设计需要更多的思考和规划,以避免过度复杂和难以维护的代码。
  • 如果类设计不当,可能会导致性能问题,如不必要的内存分配和复制。

示例

        以下是一个简单的C++程序,演示了如何定义、初始化和使用类:

#include <iostream>  
using namespace std;  
  
// 定义一个表示矩形的类  
class Rectangle {  
public:  
    // 构造函数,用于初始化矩形的宽度和高度  
    Rectangle(int w, int h) : width(w), height(h) {}  
  
    // 获取矩形面积的成员函数  
    int getArea() const {  
        return width * height;  
    }  
  
    // 获取矩形周长的成员函数  
    int getPerimeter() const {  
        return 2 * (width + height);  
    }  
  
private:  
    int width;  // 矩形的宽度  
    int height; // 矩形的高度  
};  
  
int main() 
{  
    // 创建一个Rectangle对象并初始化  
    Rectangle rect(10, 5);  
  
    // 调用成员函数获取面积和周长  
    cout << "Area: " << rect.getArea() << endl;  
    cout << "Perimeter: " << rect.getPerimeter() << endl;  
  
    return 0;  
}

链表(Linked List)

定义

        链表(Linked List)是C++中一种常见的数据结构,它由一系列节点(Node)组成,每个节点包含两部分:一部分用于存储数据(data),另一部分用于存储指向下一个节点的指针(pointer)或引用。链表不像数组那样在内存中连续存储,而是通过指针将各个节点链接起来,从而形成一个链式结构。

        根据指针的指向方式,链表可以分为单向链表(Singly Linked List)和双向链表(Doubly Linked List)等类型。单向链表中的每个节点只包含一个指向下一个节点的指针,而双向链表中的每个节点则包含两个指针,一个指向下一个节点,另一个指向前一个节点。

特点

  • 动态大小:链表的大小可以在运行时动态变化,不需要像数组那样在创建时指定大小。
  • 插入和删除操作高效:在链表中插入或删除节点时,只需要修改相关节点的指针,而不需要移动大量数据。这使得链表在频繁进行插入和删除操作的场景中非常高效。
  • 内存利用率较低:由于链表中的每个节点都需要额外的空间来存储指针,因此与数组相比,链表的内存利用率较低。
  • 访问速度较慢:链表不支持像数组那样的随机访问,访问某个节点需要从头节点开始遍历链表,直到找到目标节点。这使得链表的访问速度相对较慢。

优缺点

优点

  • 插入和删除操作高效,特别是在列表中间或末尾进行操作时。
  • 不需要预先分配大量内存,可以动态地分配和释放内存。

缺点

  • 访问特定元素的速度较慢,因为需要从头节点开始遍历链表。
  • 需要额外的内存来存储指针。
  • 链表的操作(如插入、删除和遍历)相对复杂,容易出错。

示例

        以下是一个简单的C++程序,演示了如何定义、初始化和操作单向链表:

#include <iostream>  
  
// 定义链表节点结构体  
struct Node 
{  
    int data;       // 存储的数据  
    Node* next;     // 指向下一个节点的指针  
  
    // 构造函数,用于初始化节点  
    Node(int val) : data(val), next(nullptr) {}  
};  
  
// 定义链表类  
class LinkedList {  
public:  
    // 构造函数,初始化头节点为空  
    LinkedList() : head(nullptr) {}  
  
    // 在链表末尾添加节点  
    void append(int val) 
    {  
        Node* newNode = new Node(val);  
        if (head == nullptr) 
        {  
            head = newNode;  
        } else {  
            Node* temp = head;  
            while (temp->next != nullptr) 
            {  
                temp = temp->next;  
            }  
            temp->next = newNode;  
        }  
    }  
  
    // 打印链表中的所有节点  
    void printList() const 
    {  
        Node* temp = head;  
        while (temp != nullptr) 
        {  
            std::cout << temp->data << " ";  
            temp = temp->next;  
        }  
        std::cout << std::endl;  
    }  
  
    // 析构函数,释放链表占用的内存  
    ~LinkedList() 
    {  
        Node* temp;  
        while (head != nullptr) 
        {  
            temp = head;  
            head = head->next;  
            delete temp;  
        }  
    }  
  
private:  
    Node* head; // 链表的头节点  
};  
  
int main() 
{  
    // 创建一个链表对象  
    LinkedList list;  
  
    // 向链表中添加节点  
    list.append(1);  
    list.append(2);  
    list.append(3);  
  
    // 打印链表  
    list.printList();  
  
    // 程序结束时,析构函数会自动释放链表占用的内存  
    return 0;  
}

栈(Stack)

定义

        栈(Stack)是一种线性数据结构,它只允许在某一端(称为栈顶)进行数据的插入(push)和删除(pop)操作。栈遵循后进先出(LIFO, Last In First Out)的原则,即最后插入的元素会是第一个被删除的元素。

特点

  • 后进先出原则:这是栈最核心的特点,意味着最后添加的元素最先被移除。
  • 受限的访问:栈只提供对栈顶元素的访问,不允许直接访问栈中的其他元素。
  • 动态大小:虽然栈的大小是固定的,但大多数现代实现允许栈根据需要动态地增长和缩小。

优缺点

优点

  • 简单的数据结构,易于实现。
  • 支持快速的插入和删除操作,时间复杂度为O(1)。
  • 适合用于需要逆序处理元素的场景,如表达式求值、函数调用栈等。

缺点

  • 栈的访问受到限制,只能访问栈顶元素。
  • 由于后进先出的特性,栈不适合用于需要随机访问元素的场景。

示例

        下面是一个使用C++标准库中的std::stack实现的简单栈操作示例:

#include <iostream>  
#include <stack> // 包含stack头文件  
  
int main() 
{  
    std::stack<int> myStack; // 创建一个存储int类型的栈  
  
    // 插入元素到栈中  
    myStack.push(10); // 将10压入栈顶  
    myStack.push(20); // 将20压入栈顶  
    myStack.push(30); // 将30压入栈顶  
  
    // 输出栈顶元素  
    std::cout << "栈顶元素: " << myStack.top() << std::endl; // 输出30  
  
    // 删除栈顶元素  
    myStack.pop(); // 移除栈顶的30  
    std::cout << "新的栈顶元素: " << myStack.top() << std::endl; // 输出20  
  
    // 检查栈是否为空  
    if (myStack.empty()) 
    {  
        std::cout << "栈是空的" << std::endl;  
    } else {  
        std::cout << "栈不是空的" << std::endl; // 输出此信息  
    }  
  
    // 获取栈的大小  
    std::cout << "栈的大小: " << myStack.size() << std::endl; // 输出2  
  
    return 0;  
}

队列(Queue)

定义

        队列(Queue)是一种特殊的线性数据结构,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。这种操作原则遵循了先进先出(FIFO, First In First Out)的原则,即最早进入队列的元素最早离开队列。

特点

  • FIFO原则:元素按照进入队列的顺序被处理。
  • 限定操作:只能在队列的一端(rear)添加元素,在另一端(front)移除元素。
  • 动态大小:队列的大小可以动态变化,根据需要增加或减少。

优缺点

优点

  • 简单易用,符合很多实际问题的处理需求,如任务调度、打印队列等。
  • 提供了高效的元素访问顺序。

缺点

  • 队列不支持随机访问,即不能直接访问队列中间的元素。
  • 在某些实现中(如基于数组的队列),可能存在空间浪费的问题,因为队列的前端和后端可能不连续。

示例

        下面是一个基于C++标准模板库(STL)的队列示例:

#include <iostream>  
#include <queue> // 包含队列的头文件  
  
int main() 
{  
    // 创建一个int类型的队列  
    std::queue<int> q;  
  
    // 向队列中添加元素  
    q.push(10); // 在队列的尾部添加元素10  
    q.push(20); // 在队列的尾部添加元素20  
    q.push(30); // 在队列的尾部添加元素30  
  
    // 输出队列的大小  
    std::cout << "Queue size: " << q.size() << std::endl; // 输出:Queue size: 3  
  
    // 检查队列是否为空  
    if (!q.empty()) 
    {  
        std::cout << "Queue is not empty." << std::endl; // 输出:Queue is not empty.  
    }  
  
    // 访问队列的前端元素(不移除)  
    std::cout << "Front element: " << q.front() << std::endl; // 输出:Front element: 10  
  
    // 移除队列的前端元素  
    q.pop(); // 移除元素10  
  
    // 再次访问队列的前端元素(不移除)  
    std::cout << "Front element after pop: " << q.front() << std::endl; // 输出:Front element after pop: 20  
  
    // 遍历队列中的所有元素  
    std::queue<int> tempQueue = q; // 创建一个临时队列用于遍历,避免修改原队列  
    while (!tempQueue.empty()) 
    {  
        std::cout << tempQueue.front() << " "; // 访问前端元素  
        tempQueue.pop(); // 移除已访问的元素  
    }  
    std::cout << std::endl; // 输出:20 30  
  
    return 0;  
}

双端队列(Deque)

定义

        双端队列(Deque,全称Double-Ended Queue)是一种具有两个端点的队列,它允许在队列的两端进行插入和删除操作。这意味着你可以在双端队列的前端(front)和后端(rear)同时添加或移除元素。

特点

  • 双端操作:支持在队列的两端进行插入(push_front, push_back)和删除(pop_front, pop_back)操作。
  • 动态大小:双端队列的大小可以动态变化,根据需要增加或减少。
  • FIFO和LIFO的结合:虽然双端队列通常被视为FIFO结构,但由于其双端操作的能力,它也可以在某些情况下用作LIFO结构(例如,只使用一端进行所有操作)。

优缺点

优点

  • 提供了更灵活的元素操作方式,允许在两端进行插入和删除。
  • 适用于需要频繁在两端进行操作的场景,如撤销/重做操作栈、滑动窗口等。

缺点

  • 相对于简单的队列或栈,双端队列的实现可能更复杂,因此可能会有一些性能开销。
  • 在某些情况下,如果只使用双端队列的一端进行操作,可能会浪费另一端的空间。

示例

        下面是一个基于C++标准模板库(STL)的双端队列示例:

#include <iostream>  
#include <deque> // 包含双端队列的头文件  
  
int main() 
{  
    // 创建一个int类型的双端队列  
    std::deque<int> dq;  
  
    // 向双端队列的两端添加元素  
    dq.push_back(10); // 在双端队列的尾部添加元素10  
    dq.push_front(20); // 在双端队列的头部添加元素20  
  
    // 输出双端队列的大小  
    std::cout << "Deque size: " << dq.size() << std::endl; // 输出:Deque size: 2  
  
    // 检查双端队列是否为空  
    if (!dq.empty()) {  
        std::cout << "Deque is not empty." << std::endl; // 输出:Deque is not empty.  
    }  
  
    // 访问双端队列的前端和后端元素(不移除)  
    std::cout << "Front element: " << dq.front() << std::endl; // 输出:Front element: 20  
    std::cout << "Back element: " << dq.back() << std::endl; // 输出:Back element: 10  
  
    // 从双端队列的两端移除元素  
    dq.pop_front(); // 移除前端元素20  
    dq.pop_back(); // 移除后端元素10  
  
    // 再次检查双端队列是否为空  
    if (dq.empty()) {  
        std::cout << "Deque is empty now." << std::endl; // 输出:Deque is empty now.  
    }  
  
    // 向双端队列中添加多个元素,并遍历它们  
    dq.push_back(30);  
    dq.push_front(40);  
    dq.push_back(50);  
  
    // 使用迭代器遍历双端队列  
    std::cout << "Deque elements: ";  
    for (std::deque<int>::iterator it = dq.begin(); it != dq.end(); ++it) 
	{  
        std::cout << *it << " ";  
    }  
    std::cout << std::endl; // 输出:Deque elements: 40 30 50  
  
    return 0;  
}

哈希表(Hash Table)

定义

        哈希表,又称散列表,是一种根据关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数,存放记录的数组叫做哈希表。

特点

  • 哈希函数:哈希表通过一个哈希函数将关键码值映射到表中的一个位置。这个映射不是一一对应的,即可能存在多个关键码值映射到同一个位置(称为哈希冲突或碰撞)。

  • 快速查找:在理想情况下,哈希表的查找、插入和删除操作的时间复杂度都是O(1),即常数时间。这是因为哈希表通过哈希函数直接定位到元素存储的位置,避免了像链表或数组那样的顺序查找。

  • 无序性:哈希表中的元素是无序的,它们的位置是由哈希函数决定的,而不是按照某种顺序排列的。

  • 动态大小:哈希表的大小可以动态调整,以适应存储的元素数量的变化。当哈希表中的元素数量超过一定的阈值时,哈希表会进行扩容操作,以减少哈希冲突的概率。

优缺点

优点

  • 查找、插入和删除操作在平均情况下具有O(1)的时间复杂度。
  • 支持快速的元素访问。
  • 可以动态调整大小。

缺点

  • 哈希函数的选择和设计对哈希表的性能有很大影响。
  • 哈希冲突需要额外的处理机制,如链地址法(拉链法)或开放地址法。
  • 哈希表不支持顺序访问,即不能按照元素的插入顺序或某种特定顺序遍历元素。

示例

        下面是一个基于C++标准模板库(STL)的unordered_map(即哈希表)的示例:

#include <iostream>  
#include <unordered_map> // 包含哈希表的头文件  

int main()
{
    // 创建一个哈希表,键和值都是int类型  
    std::unordered_map<int, int> hashTable;

    // 向哈希表中插入元素  
    hashTable[1] = 100; // 键为1,值为100  
    hashTable[2] = 200; // 键为2,值为200  
    hashTable[3] = 300; // 键为3,值为300  

    // 访问哈希表中的元素  
    std::cout << "Value for key 1: " << hashTable[1] << std::endl; // 输出:Value for key 1: 100  
    std::cout << "Value for key 2: " << hashTable[2] << std::endl; // 输出:Value for key 2: 200  

    // 检查哈希表中是否存在某个键  
    if (hashTable.find(3) != hashTable.end())
    {
        std::cout << "Key 3 exists in the hash table." << std::endl; // 输出:Key 3 exists in the hash table.  
    }

    // 遍历哈希表中的所有元素  
    std::cout << "All elements in the hash table:" << std::endl;
    for (const auto& pair : hashTable)
    {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    // 删除哈希表中的元素  
    hashTable.erase(2); // 删除键为2的元素  

    // 再次遍历哈希表,确认元素已被删除  
    std::cout << "Hash table after deletion:" << std::endl;
    for (const auto& pair : hashTable)
    {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

映射(Map)

定义

        在C++中,map是一种关联容器,它存储的是键值对(key-value pairs)。每个键(key)在map中都是唯一的,并且每个键都映射到一个值(value)。map内部通常使用红黑树(Red-Black Tree)实现,这意味着map中的元素是按照键的顺序自动排序的。

特点

  • 键唯一性:map中的每个键都是唯一的。

  • 自动排序:map中的元素会根据键的排序规则自动排序。

  • 快速查找:由于使用了红黑树,map提供了对数时间复杂度的查找、插入和删除操作。

  • 键值对存储:map存储的是键值对,可以通过键快速访问对应的值。

优缺点

优点

  • 有序性:map中的元素按照键的顺序排列,便于顺序访问。
  • 快速查找:查找、插入和删除操作的时间复杂度为O(log n)。
  • 键唯一性:自动维护键的唯一性,无需手动检查。

缺点

  • 空间开销:由于使用了红黑树,map的空间开销相对较大。
  • 不支持随机访问:虽然map提供了顺序访问,但不支持像数组那样的随机访问。

示例

        下面是一个简单的map使用示例,包括插入、查找和遍历操作:

#include <iostream>
#include <map>
#include <string>

int main()
{
    // 定义一个map,键为string类型,值为int类型
    std::map<std::string, int> myMap;

    // 插入键值对
    myMap["apple"] = 5;
    myMap["banana"] = 3;
    myMap["orange"] = 7;

    // 查找并输出某个键对应的值
    std::string key = "banana";
    if (myMap.find(key) != myMap.end())
    {
        std::cout << key << " has " << myMap[key] << " items.\n";
    } else {
        std::cout << key << " not found.\n";
    }

    // 遍历map并输出所有键值对
    std::cout << "Map contents:\n";
    for (const auto& pair : myMap)
    {
        std::cout << pair.first << ": " << pair.second << "\n";
    }

    // 删除一个键值对
    myMap.erase("apple");

    // 再次遍历map,验证删除操作
    std::cout << "Map contents after deletion:\n";
    for (const auto& pair : myMap)
    {
        std::cout << pair.first << ": " << pair.second << "\n";
    }

    return 0;
}

集合(Set)

定义

        在C++中,集合(Set)是一种不允许有重复元素的数据结构。它通常用于存储唯一的元素,并且不关心元素的顺序(即,集合是无序的)。C++标准库提供了std::set和std::unordered_set两种主要的集合实现,分别基于红黑树和哈希表。

特点

std::set:

  • 有序性:std::set内部的元素是按照严格的弱排序顺序(通常是升序)排列的。这意味着每次插入、删除或查找元素时,集合都会自动维护其有序性。
  • 基于红黑树:std::set通常使用红黑树实现,这种数据结构确保了插入、删除和查找操作的时间复杂度为O(log n)。

std::unordered_set:

  • 无序性:std::unordered_set内部的元素存储顺序是随机的,不保证任何特定顺序。元素的位置由哈希函数决定。
  • 基于哈希表:std::unordered_set使用哈希表实现,这种数据结构提供了平均时间复杂度为O(1)的查找、插入和删除操作(在最坏情况下可能退化到O(n),但这种情况很少发生)。

优缺点

std::set:

  • 优点:元素有序,便于顺序访问;自动维护平衡,确保操作的高效性。
  • 缺点:需要额外的空间来维护树的平衡;插入和删除操作的时间复杂度为O(log n),虽然通常很快,但在处理大量数据时可能不如哈希表高效。

std::unordered_set:

  • 优点:查找、插入和删除操作平均时间复杂度为O(1),非常高效;不需要维护元素的有序性,因此通常比std::set占用更少的空间。
  • 缺点:元素无序,不便于顺序访问;在最坏情况下(哈希冲突严重时),操作的时间复杂度可能退化到O(n)。

示例

        下面是一个简单的使用示例,std::set用于创建有序集合,而std::unordered_set用于创建无序集合。输出将清晰地展示这两个集合在有序性和无序性方面的差异:

#include <iostream>
#include <set>
#include <unordered_set>

int main() 
{
    // 使用 std::set(有序集合)
    std::set<int> mySet;

    // 插入元素
    mySet.insert(5);
    mySet.insert(2);
    mySet.insert(8);
    mySet.insert(2); // 尝试插入重复元素,将不会成功

    // 输出集合中的元素(有序)
    std::cout << "Elements in std::set (ordered): ";
    for (int num : mySet) 
	{
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 使用 std::unordered_set(无序集合)
    std::unordered_set<int> myUnorderedSet;

    // 插入元素
    myUnorderedSet.insert(5);
    myUnorderedSet.insert(2);
    myUnorderedSet.insert(8);
    myUnorderedSet.insert(2); // 尝试插入重复元素,将不会成功

    // 输出集合中的元素(无序)
    std::cout << "Elements in std::unordered_set (unordered): ";
    for (int num : myUnorderedSet) 
	{
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

动态数组(Vector)

定义

        std::vector 是 C++ 标准模板库(STL)中的一种序列容器,它提供了能够存储任意类型对象的动态数组功能。与普通的 C 风格数组不同,std::vector 能够根据需要自动调整其大小,从而避免了数组越界和手动管理内存的问题。

特点

  • 动态大小:std::vector  能够根据需要自动扩展或收缩其容量,以存储更多的元素或释放不再需要的内存。

  • 随机访问:与数组类似,std::vector  支持通过索引快速访问任意位置的元素,时间复杂度为 O(1)。

  • 尾部操作高效:在 std::vector  的尾部添加或删除元素通常是非常高效的,因为容器只需要调整其内部指针和大小,而不需要移动元素(除非需要扩展容量)。

  • 内存连续性:std::vector  保证其元素在内存中是连续存储的,这使得它能够与 C 风格的数组和指针操作兼容。

  • 迭代器失效:在 std::vector  中插入或删除非尾部元素可能会导致指向这些元素或之后元素的迭代器、指针和引用失效,因为容器可能需要重新分配内存并移动元素。

优缺点

优点

  • 动态大小管理,无需手动分配和释放内存。
  • 支持随机访问,能够快速查找元素。
  • 尾部操作高效。
  • 内存连续,便于与 C 风格数组交互。

缺点

  • 在非尾部插入或删除元素时,可能需要重新分配内存并移动大量元素,导致性能下降。
  • 迭代器失效问题需要注意。

示例

#include <iostream>  
#include <vector>  
  
int main() 
{  
    // 创建一个空的 vector,用于存储 int 类型的元素  
    std::vector<int> myVector;  
  
    // 向 vector 中添加元素  
    myVector.push_back(10); // 在尾部添加元素 10  
    myVector.push_back(20); // 在尾部添加元素 20  
    myVector.push_back(30); // 在尾部添加元素 30  
  
    // 访问 vector 中的元素  
    std::cout << "Element at index 0: " << myVector[0] << std::endl; // 输出 10  
    std::cout << "Element at index 1: " << myVector[1] << std::endl; // 输出 20  
    std::cout << "Element at index 2: " << myVector[2] << std::endl; // 输出 30  
  
    // 修改 vector 中的元素  
    myVector[1] = 25; // 将索引 1 处的元素修改为 25  
  
    // 输出修改后的 vector  
    std::cout << "Modified vector: ";  
    for (int num : myVector) 
	{  
        std::cout << num << " ";  
    }  
    std::cout << std::endl;  
  
    // 删除 vector 中的元素(通过迭代器)  
    auto it = myVector.begin(); // 获取指向第一个元素的迭代器  
    std::advance(it, 1); // 将迭代器向前移动一个位置,指向第二个元素  
    myVector.erase(it); // 删除迭代器指向的元素(即原索引 1 处的元素)  
  
    // 输出删除元素后的 vector  
    std::cout << "Vector after erasing an element: ";  
    for (int num : myVector) 
	{  
        std::cout << num << " ";  
    }  
    std::cout << std::endl;  
  
    // 获取 vector 的大小  
    std::cout << "Size of vector: " << myVector.size() << std::endl;  
  
    // 清空 vector  
    myVector.clear();  
    std::cout << "Vector after clearing: size = " << myVector.size() << std::endl;  
  
    return 0;  
}
内含资源如下: 1.基本数据结构 1.1.Array ........... 动态数组 1.2.LinkedList ... 链表 1.3.BST .............. 二分搜索树 1.4.MapBST ..... 二分搜索树(用于实现映射) 1.5.AVLTree ...... AVL树 2.接口 2.1.Queue ........... 队列接口 2.2.Stack .............. 栈接口 2.3.Set .................. 集合接口 2.4.Map ............... 映射接口 2.5.Merger .......... 自定义函数接口 2.6.UnionFind ..... 并查集接口 3.高级数据结构 3.1.ArrayQueue .......................... 队列_基于动态数组实现 3.2.LinkedListQueue .................. 队列__基于链表实现 3.3.LoopQueue ........................... 循环队列_基于动态数组实现 3.4.PriorityQueue ....................... 优先队列_基于最大二叉堆实现 3.5.ArrayPriorityQueue ............. 优先队列_基于动态数组实现 3.6.LinkedListPriorityQueue ..... 优先队列_基于链表实现 3.7.ArrayStack ............................. 栈_基于动态数组实现 3.8.LinkedListStack ..................... 栈_基于链表实现 3.9.BSTSet ..................................... 集合_基于二分搜索树实现 3.10.LinkedListSet ....................... 集合_基于链表实现 3.11.BSTMap ................................ 映射_基于二分搜索树实现 3.12.AVLTreeMap ....................... 映射_ 基于AVL树实现 3.13.LinkedListMap .................... 映射_基于链表实现 3.14.MaxHeap ............................. 最大二叉堆 3.15.SegmentTree ...................... 线段树 3.16.Trie ......................................... 字典树 3.17.QuickFind ............................ 并查集_基于数组实现 3.18.QuickUnion ......................... 并查集_基于树思想实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值