授课语音

数据结构分类

1. 介绍

常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆和图,分为逻辑结构和物理结构。

1.1 逻辑结构

逻辑结构描述了数据之间的关系和组织方式,主要分为线性结构和非线性结构。

线性结构

线性结构中的数据元素按顺序排列,每个元素有且只有一个前驱和一个后继。主要包括:

  1. 数组(Array):一组相同类型的元素,按顺序存储在内存中。支持随机访问,但插入和删除操作较慢,因为可能需要移动大量元素。

  2. 链表(Linked List):由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的变体包括:

    • 单链表(Singly Linked List):每个节点只有一个指针,指向下一个节点。
    • 双链表(Doubly Linked List):每个节点有两个指针,分别指向前一个节点和下一个节点。
    • 循环链表(Circular Linked List):链表的尾节点指向头节点,形成一个环。
  3. 栈(Stack):遵循“后进先出”(LIFO)原则的数据结构。主要操作有推入(push)和弹出(pop)。

  4. 队列(Queue):遵循“先进先出”(FIFO)原则的数据结构。主要操作有入队(enqueue)和出队(dequeue)。它的变体包括:

    • 双端队列(Deque):可以在两端进行插入和删除操作的队列。
    • 优先队列(Priority Queue):每个元素都有一个优先级,按照优先级顺序进行操作。

非线性结构

非线性结构中的数据元素没有线性排列的顺序,元素间的关系更加复杂。主要包括:

  1. 树(Tree):一种分层的数据结构,包含节点和边。每个节点可以有零个或多个子节点。常见的树结构包括:

    • 二叉树(Binary Tree):每个节点最多有两个子节点(左子节点和右子节点)。

      • 完全二叉树(Complete Binary Tree):除了最后一层外,每层的节点都完全填满,最后一层的节点从左到右依次填充。
      • 满二叉树(Full Binary Tree):每个节点要么是叶子节点,要么有两个子节点。
      • 平衡二叉树(Balanced Binary Tree):每个节点的左右子树的高度差不超过某个阈值(通常为1)。
    • 二叉搜索树(Binary Search Tree, BST):二叉树的一种,其中左子树的值小于根节点的值,右子树的值大于根节点的值。

      • AVL平衡树(Balanced Tree):自平衡的二叉搜索树,任意节点的左右子树高度差不超过1。
      • 红黑树(Red-Black Tree):自平衡的二叉搜索树,节点颜色限制。
    • B树(B Tree):自平衡的多路搜索树,适合存储和检索。每个节点可以有多个子节点,每个节点包含多个键(keys)和子节点指针。

    • B+树(B+ Tree):B树的变体,所有键值都存储在叶子节点中,内部节点仅存索引。

  2. 堆(Heap):一种特殊的完全二叉树,用于实现优先队列。分为最大堆(根节点最大)和最小堆(根节点最小)。

  3. 图(Graph):由节点(顶点)和连接节点的边组成的结构。图可以是:

    • 无向图(Undirected Graph):边没有方向,连接两个节点。
    • 有向图(Directed Graph):边有方向,从一个节点指向另一个节点。
    • 加权图(Weighted Graph):边有权值,表示连接的代价或距离。
    • 无权图(Unweighted Graph):边没有权值,只有连接关系。

哈希表 是一种非常重要的数据结构,用于实现高效的插入、删除和查找操作。它通过哈希函数将数据映射到数组中的特定位置,以便在常数时间内完成这些操作。

图示 Image___1397757897068464246===e5c28f80226d9111e57d30444442bc3c===2_1.png___

1.2 物理结构

物理结构描述了数据如何在计算机内存中实际存储。主要分为内存连续结构和内存分散结构。

  1. 内存连续结构:数据在内存中是连续存储的。主要包括:

    • 数组(Array):数据元素在内存中连续存储,支持快速随机访问。
  2. 内存分散结构:数据在内存中不是连续存储的,而是通过指针或引用连接起来。主要包括:

    • 链表(Linked List):节点在内存中分散存储,每个节点包含数据和指向下一个节点的指针(或双向链表中还包含指向前一个节点的指针)。

所有的数据结构都是基于数组、链表或两者的组合实现的。

2. 代码案例

#include <iostream>
#include <vector>
#include <stdexcept>
#include <map>
#include <queue>

// ====================== 线性结构 ===========================

// 1. 数组(Array)类
class ArrayStructure {
private:
    int size;  // 数组大小
    std::vector<int> array;  // 数组数据

public:
    // 构造函数,初始化数组
    ArrayStructure(int size) {
        this->size = size;
        array.resize(size, -1);  // 初始化数组,默认值为 -1
    }

    // 设置指定索引的值
    void set(int index, int value) {
        if (index >= 0 && index < size) {
            array[index] = value;
        } else {
            throw std::out_of_range("Index out of bounds");  // 索引越界错误
        }
    }

    // 获取指定索引的值
    int get(int index) {
        if (index >= 0 && index < size) {
            return array[index];
        } else {
            throw std::out_of_range("Index out of bounds");  // 索引越界错误
        }
    }

    // 打印数组内容
    void print() {
        for (int i = 0; i < size; ++i) {
            std::cout << array[i] << " ";  // 输出数组元素
        }
        std::cout << std::endl;
    }
};

// 2. 链表(Linked List)
// 2.1 单链表(Singly Linked List)节点类
class Node {
public:
    int data;  // 节点数据
    Node* next;  // 指向下一个节点的指针

    Node(int data) : data(data), next(nullptr) {}  // 构造函数,初始化数据和指针
};

// 单链表(Singly Linked List)类
class SinglyLinkedList {
private:
    Node* head;  // 链表头节点

public:
    SinglyLinkedList() : head(nullptr) {}  // 构造函数,初始化空链表

    // 向链表末尾添加新节点
    void append(int data) {
        Node* newNode = new Node(data);  // 创建新节点
        if (head == nullptr) {
            head = newNode;  // 如果链表为空,直接将头指针指向新节点
        } else {
            Node* last = head;
            while (last->next != nullptr) {  // 找到链表的最后一个节点
                last = last->next;
            }
            last->next = newNode;  // 将最后一个节点的指针指向新节点
        }
    }

    // 打印链表内容
    void print() {
        Node* current = head;
        while (current != nullptr) {  // 遍历链表
            std::cout << current->data << " -> ";  // 输出节点数据
            current = current->next;
        }
        std::cout << "None" << std::endl;  // 输出链表结束标志
    }
};

// 2.2 双链表(Doubly Linked List)节点类
class DoublyNode {
public:
    int data;  // 节点数据
    DoublyNode* next;  // 指向下一个节点的指针
    DoublyNode* prev;  // 指向前一个节点的指针

    DoublyNode(int data) : data(data), next(nullptr), prev(nullptr) {}  // 构造函数
};

// 双链表(Doubly Linked List)类
class DoublyLinkedList {
private:
    DoublyNode* head;  // 双链表头节点

public:
    DoublyLinkedList() : head(nullptr) {}  // 构造函数,初始化空链表

    // 向双链表末尾添加新节点
    void append(int data) {
        DoublyNode* newNode = new DoublyNode(data);  // 创建新节点
        if (head == nullptr) {
            head = newNode;  // 如果链表为空,直接将头指针指向新节点
        } else {
            DoublyNode* last = head;
            while (last->next != nullptr) {  // 找到链表的最后一个节点
                last = last->next;
            }
            last->next = newNode;  // 将最后一个节点的指针指向新节点
            newNode->prev = last;  // 设置新节点的前向指针
        }
    }

    // 打印双链表内容
    void print() {
        DoublyNode* current = head;
        while (current != nullptr) {  // 遍历双链表
            std::cout << current->data << " <-> ";  // 输出节点数据
            current = current->next;
        }
        std::cout << "None" << std::endl;  // 输出链表结束标志
    }
};

// 2.3 循环链表(Circular Linked List)节点类
class CircularNode {
public:
    int data;  // 节点数据
    CircularNode* next;  // 指向下一个节点的指针

    CircularNode(int data) : data(data), next(nullptr) {}  // 构造函数
};

// 循环链表(Circular Linked List)类
class CircularLinkedList {
private:
    CircularNode* head;  // 循环链表头节点

public:
    CircularLinkedList() : head(nullptr) {}  // 构造函数,初始化空链表

    // 向循环链表末尾添加新节点
    void append(int data) {
        CircularNode* newNode = new CircularNode(data);  // 创建新节点
        if (head == nullptr) {
            head = newNode;  // 如果链表为空,将头指针指向新节点
            newNode->next = newNode;  // 新节点的 next 指向自己,形成循环
        } else {
            CircularNode* last = head;
            while (last->next != head) {  // 找到链表的最后一个节点
                last = last->next;
            }
            last->next = newNode;  // 将最后一个节点的 next 指向新节点
            newNode->next = head;  // 新节点的 next 指向头节点,完成循环
        }
    }

    // 打印循环链表内容
    void print() {
        if (head == nullptr) {  // 如果链表为空
            std::cout << "Empty List" << std::endl;
            return;
        }
        CircularNode* current = head;
        do {  // 遍历循环链表
            std::cout << current->data << " -> ";  // 输出节点数据
            current = current->next;
        } while (current != head);  // 循环直到回到头节点
        std::cout << "(back to head)" << std::endl;  // 输出回到头节点的标志
    }
};

// 3. 栈(Stack)类
class Stack {
private:
    std::vector<int> items;  // 栈内存储的元素

public:
    // 压栈操作
    void push(int item) {
        items.push_back(item);
    }

    // 弹栈操作
    int pop() {
        if (isEmpty()) {
            throw std::underflow_error("pop from empty stack");  // 栈空时弹栈错误
        }
        int top = items.back();  // 获取栈顶元素
        items.pop_back();  // 弹出栈顶元素
        return top;
    }

    // 查看栈顶元素
    int peek() {
        if (isEmpty()) {
            throw std::underflow_error("peek from empty stack");  // 栈空时查看栈顶错误
        }
        return items.back();  // 返回栈顶元素
    }

    // 判断栈是否为空
    bool isEmpty() {
        return items.empty();
    }

    // 获取栈的大小
    int size() {
        return items.size();
    }
};

// 4. 队列(Queue)类
class Queue {
private:
    std::vector<int> items;  // 队列内存储的元素

public:
    // 入队操作
    void enqueue(int item) {
        items.push_back(item);
    }

    // 出队操作
    int dequeue() {
        if (isEmpty()) {
            throw std::underflow_error("dequeue from empty queue");  // 队列空时出队错误
        }
        int front = items.front();  // 获取队首元素
        items.erase(items.begin());  // 移除队首元素
        return front;
    }

    // 判断队列是否为空
    bool isEmpty() {
        return items.empty();
    }

    // 获取队列的大小
    int size() {
        return items.size();
    }
};

// ====================== 非线性结构 ===========================

// 1. 二叉树(Binary Tree)节点类
class TreeNode {
public:
    int data;  // 节点数据
    TreeNode* left;  // 左子节点
    TreeNode* right;  // 右子节点

    TreeNode(int data) : data(data), left(nullptr), right(nullptr) {}  // 构造函数
};

// 二叉树(Binary Tree)类
class BinaryTree {
private:
    TreeNode* root;  // 树根节点

    // 递归插入节点
    void insert(TreeNode*& node, int data) {
        if (node == nullptr) {
            node = new TreeNode(data);  // 如果节点为空,创建新节点
        } else if (data < node->data) {
            insert(node->left, data);  // 如果数据小于当前节点,递归插入左子树
        } else {
            insert(node->right, data);  // 如果数据大于当前节点,递归插入右子树
        }
    }

    // 中序遍历
    void inorder(TreeNode* node) {
        if (node != nullptr) {
            inorder(node->left);  // 遍历左子树
            std::cout << node->data << " ";  // 输出节点数据
            inorder(node->right);  // 遍历右子树
        }
    }

public:
    BinaryTree() : root(nullptr) {}  // 构造函数,初始化为空树

    // 插入节点
    void insert(int data) {
        insert(root, data);  // 从根节点开始插入
    }

    // 中序遍历
    void inorder() {
        inorder(root);  // 从根节点开始中序遍历
        std::cout << std::endl;
    }
};

// 2. 堆(Heap)类
class MinHeap {
private:
    std::vector<int> heap;  // 存储堆元素

    // 向上调整堆
    void heapifyUp(int index) {
        while (index > 0) {
            int parent = (index - 1) / 2;
            if (heap[index] < heap[parent]) {
                std::swap(heap[index], heap[parent]);  // 如果当前元素小于父节点,交换
                index = parent;  // 更新当前索引
            } else {
                break;
            }
        }
    }

    // 向下调整堆
    void heapifyDown(int index) {
        int leftChild = 2 * index + 1;
        int rightChild = 2 * index + 2;
        int smallest = index;

        if (leftChild < heap.size() && heap[leftChild] < heap[smallest]) {
            smallest = leftChild;  // 左子节点更小
        }
        if (rightChild < heap.size() && heap[rightChild] < heap[smallest]) {
            smallest = rightChild;  // 右子节点更小
        }
        if (smallest != index) {
            std::swap(heap[index], heap[smallest]);  // 交换当前节点与最小子节点
            heapifyDown(smallest);  // 继续调整
        }
    }

public:
    // 插入元素
    void insert(int value) {
        heap.push_back(value);  // 将元素添加到堆末尾
        heapifyUp(heap.size() - 1);  // 调整堆
    }

    // 删除最小元素
    int extractMin() {
        if (heap.empty()) {
            throw std::underflow_error("Heap is empty");  // 堆为空错误
        }
        int minValue = heap[0];  // 获取最小值
        heap[0] = heap.back();  // 将堆尾元素移到堆顶
        heap.pop_back();  // 移除堆尾元素
        heapifyDown(0);  // 调整堆
        return minValue;
    }
};

// 3. 图(Graph)类
class Graph {
private:
    std::map<int, std::vector<int>> adjList;  // 图的邻接表表示

public:
    // 添加边
    void addEdge(int u, int v) {
        adjList[u].push_back(v);  // 添加 u -> v 边
        adjList[v].push_back(u);  // 添加 v -> u 边(无向图)
    }

    // 打印图的邻接表
    void printGraph() {
        for (const auto& pair : adjList) {
            std::cout << pair.first << " -> ";
            for (int neighbor : pair.second) {
                std::cout << neighbor << " ";  // 输出邻接节点
            }
            std::cout << std::endl;
        }
    }
};

// 主函数,测试各种数据结构
int main() {
    // ====================== 测试数组 =======================
    ArrayStructure arr(5);
    arr.set(0, 10);
    arr.set(1, 20);
    arr.print();

    // ====================== 测试单链表 =======================
    SinglyLinkedList sll;
    sll.append(1);
    sll.append(2);
    sll.append(3);
    sll.print();

    // ====================== 测试栈 =======================
    Stack stack;
    stack.push(10);
    stack.push(20);
    std::cout << stack.pop() << std::endl;

    // ====================== 测试队列 =======================
    Queue queue;
    queue.enqueue(1);
    queue.enqueue(2);
    std::cout << queue.dequeue() << std::endl;

    // ====================== 测试二叉树 =======================
    BinaryTree bt;
    bt.insert(5);
    bt.insert(3);
    bt.insert(7);
    bt.inorder();

    // ====================== 测试堆 =======================
    MinHeap minHeap;
    minHeap.insert(3);
    minHeap.insert(1);
    std::cout << minHeap.extractMin() << std::endl;

    // ====================== 测试图 =======================
    Graph g;
    g.addEdge(1, 2);
    g.addEdge(1, 3);
    g.printGraph();

    return 0;
}

解释:

  1. 数组(Array)类: 使用 std::vector<int> 来模拟动态数组。
  2. 链表类: 使用普通的指针和类来实现单链表、双链表和循环链表。
  3. 栈和队列: 使用 std::vector 来实现栈和队列。
  4. 二叉树: 二叉树的实现使用递归方式插入节点并进行中序遍历。
  5. : 使用 std::vector 来实现最小堆,并支持插入和弹出操作。
  6. : 使用 std::map 来实现邻接表。

3. 总结

本文概述了常见的数据结构及其分类,分别介绍了逻辑结构和物理结构,并通过代码示例展示了各种数据结构的基本实现。掌握这些数据结构将有助于高效地处理和存储数据。

去1:1私密咨询

系列课程: