跳转至

数据结构模板

以下将简要介绍常用的数据结构并给出代码模板。
存储的数据类型默认为int
P.S. 为了方便,析构函数有时候可以不写(代码能过应该就不会复查)。

数组(Array)

数组是最基本的数据结构,存储一组相同类型的数据。特点如下: - 大小固定,存储空间连续,内存分配效率高。 - 插入和删除操作效率低,时间复杂度为O(n)。 - 随机访问效率高,时间复杂度为O(1)

数组适用于存储数据集大小固定,且不需要频繁插入和删除的场景。

链表(Linked List)

链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向后继节点的指针。特点如下: - 大小可变,存储空间不连续,内存分配效率低。 - 插入和删除操作效率高,时间复杂度为O(1)。 - 随机访问效率低,时间复杂度为O(n)

链表适用于存储数据集大小可变,且需要频繁插入和删除的场景。

易错点: - 空指针:注意对头结点和边界条件的处理。 - 悬空指针(删除节点后访问指向此节点的指针)

双链表
双链表是链表的一种变体,每个节点额外包含了指向前驱节点的指针,可以进行双向遍历。
在某些场景下比单链表更高效,例如删除已知节点时。在维护和单链表类似,但需要注意前驱指针的更新。

参考代码
#include <iostream>
using namespace std;

struct Node {
    int data;
    Node *next;
    Node(int val = 0, Node *ptr = NULL) : data(val), next(ptr) {}
};

class List {
  public:
    Node *head; // 头节点
    int len;    // 表长(可选)

    void deepcopy(const List &other) { // 深拷贝
        Node *curOther = other.head->next;
        Node *curThis = head;
        while (curOther != NULL) {
            curThis->next = new Node(curOther->data);
            curThis = curThis->next;
            curOther = curOther->next;
        }
        len = other.len;
    }
    void clear() { // 清空链表
        Node *current = head->next;
        while (current != NULL) {
            Node *n = current->next;
            delete current;
            current = n;
        }
        head->next = NULL; // 防止悬空指针
        len = 0;
    }

    List() : len(0) { head = new Node(); } // 构造函数
    ~List() {
        clear();
        delete head;
    } // 析构函数
    // 拷贝构造函数
    List(const List &other) : len(0) {
        head = new Node();
        deepcopy(other);
    }
    // 赋值运算符重载
    List &operator=(const List &other) {
        if (this != &other) { // 防止自我赋值
            clear();          // 先清空自身
            deepcopy(other);
        }
        return *this;
    }

    int size() { return len; }
    bool isEmpty() { return head->next == NULL; }
    // 在指定位置插入元素(index需在[0, len]内)
    void insert(int data, int index) {
        Node *current = head;
        for (int i = 0; i < index; i++) {
            current = current->next;
        }
        // 在current后插入新节点
        Node *n = new Node(data, current->next);
        current->next = n;
        len++;
    }
    void insertAtHead(int data) { insert(data, 0); }
    void insertAtTail(int data) { insert(data, len); }
    // 删除指定位置的元素(index需在[0, len-1]内)
    void remove(int index) {
        Node *current = head;
        for (int i = 0; i < index; i++) {
            current = current->next;
        }
        // 删除current的后继节点
        Node *n = current->next;
        current->next = n->next;
        delete n;
        len--;
    }
    // 返回指定位置的元素(index需在[0, len-1]内)
    int get(int index) const {
        Node *current = head->next;
        for (int i = 0; i < index; i++) {
            current = current->next;
        }
        return current->data;
    }
    // 查找指定元素
    // 返回首个匹配元素的索引,如果未找到则返回-1
    int find(int val) {
        Node *current = head->next;
        int index = 0;
        while (current != NULL) {
            if (current->data == val) {
                return index;
            }
            current = current->next;
            index++;
        }
        return -1;
    }
    // 打印链表中的所有元素,依实际需要修改
    void print() {
        if (len == 0) {
            cout << "empty" << endl;
            return;
        }
        Node *current = head->next;
        while (current != NULL) {
            cout << current->data << endl;
            current = current->next;
        }
    }
};

栈(Stack)

栈是一种后进先出(LIFO,Last In First Out)数据结构。特点如下: - 只能在栈顶进行插入和删除操作。 - 遵循后进先出原则。

应用广泛,如函数调用、表达式求值、深度优先搜索、括号匹配等。

易错点: - 栈溢出(栈满时入栈) - 栈下溢(栈空时出栈)

参考代码
#include <iostream>
using namespace std;

class Stack {
  public:
    enum { MAX_SIZE = 100 }; // 栈最大容量
    int data[MAX_SIZE];      // 存储栈元素的数组
    int top;                 // 栈顶指针,指向栈顶元素的下一位

    Stack() : top(0) {}
    int size() { return top; } // 返回栈中元素的数量
    bool isEmpty() { return top == 0; }
    bool isFull() { return top == MAX_SIZE; }
    // 入栈(top需小于MAXSIZE)
    void push(int val) { data[top++] = val; }
    // 出栈(top需大于0)
    int pop() {
        if (top == 0) return -1;
        return data[--top];
    }
    // 查看栈顶元素
    int peek() {
        if (top == 0) return -1;
        return data[top - 1];
    }
    // 打印栈中所有元素 (从栈底到栈顶),依实际需要修改
    void print() {
        if (top == 0) {
            cout << "empty" << endl;
            return;
        }
        for (int i = 0; i < top; i++) {
            cout << data[i] << endl;
        }
    }
};

队列(Queue)

队列是一种先进先出(FIFO,First In First Out)数据结构。特点如下: - 只能在队头进行删除操作,在队尾插入。 - 遵循FIFO原则。

常用于任务调度、广度优先搜索等场景。

易错点: - 队列溢出(队列满时入队) - 队列下溢(空队列出队)

参考代码
#include <iostream>
using namespace std;

class Queue { // 使用循环数组实现队列
  public:
    enum { MAX_SIZE = 1000 }; // 队列最大容量
    int data[MAX_SIZE];       // 存储队列元素的数组
    int front;                // 队头指针,指向队头元素
    int rear;                 // 队尾指针,指向队尾元素的下一位
    int len;                  // 队列长度
    // 队列为空或满时,front和rear相同
    // 此时可以通过其长度来区分队列是否为空或满
    Queue() : front(0), rear(0), len(0) {}
    int size() { return len; }
    bool isEmpty() { return len == 0; }
    bool isFull() { return len == MAX_SIZE; }
    // 入队(len需小于MAXSIZE)
    void push(int val) {
        data[rear] = val;
        rear = (rear + 1) % MAX_SIZE; // 队尾指针循环后移
        len++;
    }
    // 出队(len需大于0)
    int pop() {
        if (len == 0) return -1;
        int val = data[front];
        front = (front + 1) % MAX_SIZE; // 队头指针循环后移
        len--;
        return val;
    }
    // 查看队头元素
    int peek() {
        if (len == 0) return -1;
        return data[front];
    }
    // 打印队列中所有元素 (从队头到队尾),依实际需要修改
    void print() {
        if (len == 0) {
            cout << "empty" << endl;
            return;
        }
        for (int i = 0; i < len; i++) {
            cout << data[(front + i) % MAX_SIZE] << endl;
        }
    }
};

图(Graph)

图由顶点和边组成。特点如下: - 顶点可以有任意数量的边连接。 - 边可以是有向或无向。 - 边可以附带权重,表示成本或距离。

常用于网络、关系、地图等复杂结构。
存储方式有邻接矩阵和邻接表。邻接矩阵空间复杂度为O(V^2),邻接表为O(V+E)。这里只给出邻接表的实现。

易错点: - 图的边或顶点数超出数组限制 - 无向图需存储双向边

参考代码
#include "Queue.cpp"
#include <iostream>

using namespace std;

enum { MAX_N = 100, MAX_M = 2000 };

class Graph {
  public:
    int n; // 顶点数
    struct Edge {
        float weight; // 边的权重
        int to, next; // 终点索引 / 下一条边的索引
        Edge(int t = -1, int n = -1, float w = 1.0)
            : to(t), next(n), weight(w) {}
    } edges[MAX_M];  // 存储所有边的数组
    int head[MAX_N]; // 存储每个顶点的第一条边的索引
    int count;       // 当前边的数量

    Graph(int n = 0) : n(n), count(0) {
        for (int i = 0; i < MAX_N; ++i) {
            head[i] = -1; // 初始化每个顶点的邻接表头为-1
        }
    }
    // 添加无向边
    void addEdge(int from, int to, float weight = 1.0) {
        addDirectedEdge(from, to, weight);
        addDirectedEdge(to, from, weight);
    }
    // 添加有向边
    void addDirectedEdge(int from, int to, float weight = 1.0) {
        edges[count] = Edge(to, head[from], weight);
        head[from] = count;
        count++;
    }
    bool hasEdge(int from, int to) {
        int index = head[from];
        while (index != -1) {
            if (edges[index].to == to) return true;
            index = edges[index].next;
        }
        return false;
    }
    // 深度优先遍历 (DFS)
    void dfs(int node, bool visited[]) {
        visited[node] = true;
        cout << node << " ";

        int index = head[node];
        while (index != -1) {
            int neighbor = edges[index].to;
            if (!visited[neighbor]) {
                dfs(neighbor, visited); // 递归访问
            }
            index = edges[index].next;
        }
    }
    // 广度优先遍历 (BFS)
    void bfs(int node, bool visited[]) {
        Queue q; // 使用前文定义的队列
        q.push(node);
        visited[node] = true;

        while (!q.isEmpty()) {
            int current = q.pop();
            cout << current << " ";

            int index = head[current];
            while (index != -1) {
                int neighbor = edges[index].to;
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    q.push(neighbor); // 将未访问的邻居入队
                }
                index = edges[index].next;
            }
        }
    }
};

堆(Heap)

堆是一种特殊的完全二叉树,特点如下: - 完全二叉树。 - 每个节点的值都大于等于(最大堆)或小于等于(最小堆)其子节点。 - 插入与删除时间复杂度为O(log n)

常用于优先队列、Dijkstra算法、Top K、堆排序等。
存储可以用数组(根节点下标为0)或链表,这里考虑数组的实现。

易错点: - 数组下标计算错误:左子节点2k+1,右子节点2k+2,父节点(k-1)/2 - 堆性质未维护:插入和删除后需调整堆结构

参考代码
#include <algorithm>
using namespace std;

// 最大堆
class MaxHeap {
  public:
    enum { MAX_SIZE = 100 }; // 堆的最大容量
    int data[MAX_SIZE];      // 存储堆中元素的数组
    int len;                 // 当前元素的数量
    MaxHeap() : len(0) {}
    int size() { return len; }
    bool isEmpty() { return len == 0; }
    bool isFull() { return len == MAX_SIZE; }
    // 上浮操作(新元素插入到堆底时)
    void heapifyUp(int index) {
        // 当前节点大于父节点
        while (index > 0 && data[index] > data[(index - 1) / 2]) {
            // 交换当前节点和父节点
            swap(data[index], data[(index - 1) / 2]);
            // 移动到父节点
            index = (index - 1) / 2;
        }
    }
    // 下沉操作(堆顶元素被移除或改变时)
    void heapifyDown(int index) {
        // 检查是否为叶子节点
        if (index > (len / 2 - 1)) return;
        int largest = index; // 假设当前节点最大
        int left = 2 * index + 1, right = 2 * index + 2;
        // 左子节点一定存在
        if (data[left] > data[largest]) {
            largest = left;
        }
        // 右子节点可能存在
        if (right < len && data[right] > data[largest]) {
            largest = right;
        }
        // 如果最大值不是当前节点,则交换并下沉
        if (largest != index) {
            swap(data[index], data[largest]);
            heapifyDown(largest);
        }
    }
    // 插入元素(len需小于MAXSIZE)
    void insert(int val) {
        data[len] = val;    // 将新元素放在堆的末尾
        len++;              // 增加堆大小
        heapifyUp(len - 1); // 上浮
    }
    // 弹出堆顶元素(len需大于MAXSIZE)
    int pop() {
        if (len == 0) return -1;
        int val = data[0];
        data[0] = data[len - 1]; // 将最后一个元素移到堆顶
        len--;                   // 减少堆大小
        heapifyDown(0);          // 下沉
        return val;
    }
    // 查看堆顶元素
    int peek() {
        if (len == 0) return -1;
        return data[0];
    }
    // 线性建堆(n需不大于MAX_SIZE)
    void build(int arr[], int n) {
        len = n;
        for (int i = 0; i < len; i++) {
            data[i] = arr[i];
        }
        // 从最后一个非叶子节点开始向上依次进行下沉
        for (int i = len / 2 - 1; i >= 0; i--) {
            heapifyDown(i);
        }
    }
    // 堆排序(从小到大)
    void HeapSort(int arr[], int n) {
        build(arr, n); // 建立最大堆
        for (int i = n - 1; i > 0; i--) {
            // 将堆顶元素(最大值)与最后一个元素交换
            swap(data[0], data[i]);
            len--;          // 减少堆大小
            heapifyDown(0); // 下沉
        }
        // 将排序后的元素放回原数组
        for (int i = 0; i < n; i++) arr[i] = data[i];
    }
};