授课语音

数据结构分类

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___1397757897067375284===e5c28f80226d9111e57d30444442bc3c===2_1.png___

1.2 物理结构

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

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

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

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

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

2. 代码案例

TypeScript 代码:

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

// 1. 数组(Array)类
class ArrayStructure {
    private size: number;         // 数组的大小
    private array: number[];      // 使用 TypeScript 的数组类型模拟数组

    // 构造函数:初始化指定大小的数组,默认值为 -1
    constructor(size: number) {
        this.size = size;
        this.array = new Array(size).fill(-1);  // 初始化数组,默认填充 -1
    }

    // 设置数组指定位置的值
    set(index: number, value: number): void {
        if (index >= 0 && index < this.size) {
            this.array[index] = value;  // 赋值
        } else {
            throw new Error("Index out of bounds");  // 索引越界异常
        }
    }

    // 获取数组指定位置的值
    get(index: number): number {
        if (index >= 0 && index < this.size) {
            return this.array[index];  // 返回值
        } else {
            throw new Error("Index out of bounds");  // 索引越界异常
        }
    }

    // 打印数组中的所有元素
    print(): void {
        console.log(this.array.join(" "));  // 使用 join 打印数组元素
    }
}

// 2. 链表(Linked List)

// 2.1 单链表(Singly Linked List)节点类
class Node {
    data: number;    // 节点的数据
    next: Node | null;  // 指向下一个节点的指针

    // 构造函数:初始化节点数据,指针默认为 null
    constructor(data: number) {
        this.data = data;
        this.next = null;  // 默认没有下一个节点
    }
}

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

    // 构造函数:初始化链表为空
    constructor() {
        this.head = null;
    }

    // 向链表末尾添加新节点
    append(data: number): void {
        const newNode = new Node(data);  // 创建新节点
        if (this.head === null) {
            this.head = newNode;  // 如果链表为空,直接将新节点作为头节点
        } else {
            let last = this.head;
            // 遍历链表找到最后一个节点
            while (last.next !== null) {
                last = last.next;
            }
            last.next = newNode;  // 将新节点连接到最后
        }
    }

    // 打印链表中的所有节点
    print(): void {
        let current = this.head;
        // 遍历链表,输出每个节点的数据
        while (current !== null) {
            process.stdout.write(`${current.data} -> `);
            current = current.next;
        }
        console.log("None");  // 输出结束符
    }
}

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

    // 构造函数:初始化节点数据,指针默认为 null
    constructor(data: number) {
        this.data = data;
        this.next = null;
        this.prev = null;
    }
}

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

    // 构造函数:初始化双链表为空
    constructor() {
        this.head = null;
    }

    // 向双链表末尾添加新节点
    append(data: number): void {
        const newNode = new DoublyNode(data);  // 创建新节点
        if (this.head === null) {
            this.head = newNode;  // 如果链表为空,直接将新节点作为头节点
        } else {
            let last = this.head;
            // 遍历链表找到最后一个节点
            while (last.next !== null) {
                last = last.next;
            }
            last.next = newNode;  // 将新节点连接到最后
            newNode.prev = last;  // 将新节点的前指针指向最后一个节点
        }
    }

    // 打印双链表中的所有节点
    print(): void {
        let current = this.head;
        // 遍历双链表,输出每个节点的数据
        while (current !== null) {
            process.stdout.write(`${current.data} <-> `);
            current = current.next;
        }
        console.log("None");  // 输出结束符
    }
}

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

    // 构造函数:初始化节点数据,指针默认为 null
    constructor(data: number) {
        this.data = data;
        this.next = null;
    }
}

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

    // 构造函数:初始化循环链表为空
    constructor() {
        this.head = null;
    }

    // 向循环链表末尾添加新节点
    append(data: number): void {
        const newNode = new CircularNode(data);  // 创建新节点
        if (this.head === null) {
            this.head = newNode;
            newNode.next = newNode;  // 如果链表为空,指向自身,形成循环
        } else {
            let last = this.head;
            // 遍历链表找到最后一个节点
            while (last.next !== this.head) {
                last = last.next;
            }
            last.next = newNode;  // 将新节点连接到最后
            newNode.next = this.head;  // 形成循环,最后一个节点指向头节点
        }
    }

    // 打印循环链表中的所有节点
    print(): void {
        if (this.head === null) {
            console.log("Empty List");  // 如果链表为空,输出提示
            return;
        }
        let current = this.head;
        // 遍历循环链表,直到回到头节点
        do {
            process.stdout.write(`${current.data} -> `);
            current = current.next;
        } while (current !== this.head);
        console.log("(back to head)");  // 输出结束符,表明循环回到了头节点
    }
}

// 3. 栈(Stack)类
class Stack {
    private items: number[];  // 使用数组存储栈的元素

    constructor() {
        this.items = [];  // 初始化栈为空
    }

    // 向栈中推入一个元素
    push(item: number): void {
        this.items.push(item);  // 使用数组的 push 方法插入元素
    }

    // 从栈顶弹出一个元素
    pop(): number {
        if (this.isEmpty()) {
            throw new Error("pop from empty stack");  // 如果栈为空,抛出异常
        }
        return this.items.pop()!;  // 弹出栈顶元素
    }

    // 查看栈顶元素,但不弹出
    peek(): number {
        if (this.isEmpty()) {
            throw new Error("peek from empty stack");  // 如果栈为空,抛出异常
        }
        return this.items[this.items.length - 1];  // 返回栈顶元素
    }

    // 判断栈是否为空
    isEmpty(): boolean {
        return this.items.length === 0;  // 判断数组是否为空
    }

    // 获取栈的大小
    size(): number {
        return this.items.length;  // 返回栈中元素的个数
    }
}

// 4. 队列(Queue)类
class Queue {
    private items: number[];  // 使用数组存储队列的元素

    constructor() {
        this.items = [];  // 初始化队列为空
    }

    // 向队列尾部添加一个元素
    enqueue(item: number): void {
        this.items.push(item);  // 使用数组的 push 方法插入元素
    }

    // 从队列头部删除一个元素
    dequeue(): number {
        if (this.isEmpty()) {
            throw new Error("dequeue from empty queue");  // 如果队列为空,抛出异常
        }
        return this.items.shift()!;  // 从队列头部移除元素
    }

    // 判断队列是否为空
    isEmpty(): boolean {
        return this.items.length === 0;  // 判断数组是否为空
    }

    // 获取队列的大小
    size(): number {
        return this.items.length;  // 返回队列中元素的个数
    }
}

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

// 1. 二叉树(Binary Tree)节点类
class TreeNode {
    data: number;           // 节点的数据
    left: TreeNode | null;  // 左子树
    right: TreeNode | null; // 右子树

    constructor(data: number) {
        this.data = data;
        this.left = this.right = null;  // 默认左右子树为 null
    }
}

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

    constructor() {
        this.root = null;  // 初始化二叉树为空
    }

    // 插入节点(简单实现,按顺序插入)
    insert(data: number): void {
        if (this.root === null) {
            this.root = new TreeNode(data);  // 如果树为空,直接插入根节点
        } else {
            this.insertRec(this.root, data);  // 否则递归插入
        }
    }

    // 递归插入节点
    private insertRec(node: TreeNode, data: number): void {
        if (data < node.data) {
            if (node.left === null) {
                node.left = new TreeNode(data);  // 插入到左子树
            } else {
                this.insertRec(node.left, data);  // 递归插入到左子树
            }
        } else {
            if (node.right === null) {
                node.right = new TreeNode(data);  // 插入到右子树
            } else {
                this.insertRec(node.right, data);  // 递归插入到右子树
            }
        }
    }

    // 中序遍历二叉树
    inorder(): void {
        this.inorderRec(this.root);  // 调用递归方法进行中序遍历
        console.log();  // 换行
    }

    // 递归中序遍历
    private inorderRec(node: TreeNode | null): void {
        if (node !== null) {
            this.inorderRec(node.left);  // 先遍历左子树
            process.stdout.write(`${node.data} `);  // 输出当前节点的数据
            this.inorderRec(node.right);  // 再遍历右子树
        }
    }
}

// 2. 堆(Heap)类:最小堆实现
class MinHeap {
    private heap: number[];  // 存储堆的数组

    constructor() {
        this.heap = [];
    }

    // 向堆中插入元素并调整堆
    insert(val: number): void {
        this.heap.push(val);  // 插入元素到堆的末尾
        this.heapifyUp(this.heap.length - 1);  // 调整堆
    }

    // 上浮调整堆
    private heapifyUp(index: number): void {
        const parent = Math.floor((index - 1) / 2);
        if (index > 0 && this.heap[index] < this.heap[parent]) {
            // 如果当前节点小于父节点,交换二者
            [this.heap[index], this.heap[parent]] = [this.heap[parent], this.heap[index]];
            this.heapifyUp(parent);  // 递归向上调整
        }
    }

    // 弹出堆顶元素并调整堆
    extractMin(): number {
        if (this.heap.length === 0) {
            throw new Error("extractMin from empty heap");
        }
        if (this.heap.length === 1) {
            return this.heap.pop()!;  // 如果堆中只有一个元素,直接弹出
        }

        const min = this.heap[0];  // 记录堆顶元素
        this.heap[0] = this.heap.pop()!;  // 弹出堆顶元素,堆的最后一个元素替代堆顶
        this.heapifyDown(0);  // 调整堆
        return min;
    }

    // 下沉调整堆
    private heapifyDown(index: number): void {
        let smallest = index;
        const left = 2 * index + 1;
        const right = 2 * index + 2;

        if (left < this.heap.length && this.heap[left] < this.heap[smallest]) {
            smallest = left;
        }
        if (right < this.heap.length && this.heap[right] < this.heap[smallest]) {
            smallest = right;
        }

        if (smallest !== index) {
            // 如果当前节点不满足堆的性质,交换并递归调整
            [this.heap[index], this.heap[smallest]] = [this.heap[smallest], this.heap[index]];
            this.heapifyDown(smallest);
        }
    }
}

// 3. 图(Graph)类:邻接表实现
class Graph {
    private graph: Map<number, number[]>;  // 使用 Map 来表示邻接表

    constructor() {
        this.graph = new Map();
    }

    // 向图中添加边
    addEdge(u: number, v: number): void {
        if (!this.graph.has(u)) {
            this.graph.set(u, []);
        }
        this.graph.get(u)?.push(v);  // 添加边 u -> v
    }

    // 打印图的邻接表
    printGraph(): void {
        for (const [key, value] of this.graph.entries()) {
            console.log(`${key} -> ${value.join(", ")}`);
        }
    }
}

// ======================示例代码==========================

const main = () => {
    // ====================== 测试数组 =======================
    const arr = new ArrayStructure(5);  // 创建一个大小为 5 的数组
    arr.set(0, 10); // 设置数组第一个元素为 10
    arr.set(1, 20); // 设置数组第二个元素为 20
    arr.print();    // 打印数组内容

    // ====================== 测试单链表 =======================
    const sll = new SinglyLinkedList();
    sll.append(1);  // 向链表中添加元素
    sll.append(2);
    sll.append(3);
    sll.print();  // 打印链表内容

    // ====================== 测试栈 =======================
    const stack = new Stack();
    stack.push(10);  // 向栈中压入元素
    stack.push(20);
    console.log(stack.pop());  // 弹出栈顶元素并打印

    // ====================== 测试队列 =======================
    const queue = new Queue();
    queue.enqueue(1);  // 向队列中加入元素
    queue.enqueue(2);
    console.log(queue.dequeue());  // 出队并打印

    // ====================== 测试二叉树 =======================
    const bt = new BinaryTree();
    bt.insert(5);
    bt.insert(3);
    bt.insert(7);
    bt.inorder();  // 打印二叉树的中序遍历

    // ====================== 测试堆 =======================
    const minHeap = new MinHeap();
    minHeap.insert(3);
    minHeap.insert(1);
    console.log(minHeap.extractMin());  // 弹出最小元素并打印

    // ====================== 测试图 =======================
    const g = new Graph();
    g.addEdge(1, 2);
    g.addEdge(1, 3);
    g.printGraph();  // 打印图的邻接表
};

main(); // 执行示例代码

解释:

  • 数组(Array)类:通过 TypeScript 数组实现,可以设置和获取数组中的元素。
  • 链表
    • 单链表(Singly Linked List):支持节点添加和遍历打印。
    • 双链表(Doubly Linked List):支持节点添加并维护前后指针。
    • 循环链表(Circular Linked List):实现循环链表结构,末尾节点指回头节点。
  • 栈(Stack):基于数组的栈实现,支持基本的 pushpoppeek 操作。
  • 队列(Queue):基于数组实现的队列,支持 enqueuedequeue 操作。
  • 二叉树(Binary Tree):实现了基本的节点插入和中序遍历功能。
  • 堆(MinHeap):实现了一个最小堆,支持插入元素、提取最小值操作,并自动维护堆的结构。
  • 图(Graph):使用邻接表(Map<number, number[]>)表示图,支持添加边,并可以打印图的邻接表。

示例输出:

10 20 -1 -1 -1
1 -> 2 -> 3 -> None
20
1
5 3 7
1
1 -> 2, 3

3. 总结

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

去1:1私密咨询

系列课程: