5天速成数据结构与算法-Day3

5天速成数据结构与算法-Day3

线性数据结构

在计算机科学中,线性数据结构是最基础也最重要的数据组织方式。今天我们将深入探讨三种核心的线性结构:链表队列。理解它们的特性、实现方式与应用场景,是掌握更复杂算法的必经之路。

链表(Linked List)

介绍

链表是一种物理存储单元上非连续、非顺序的存储结构。它通过指针将一组零散的内存块串联起来,每个内存块称为节点(Node)。每个节点包含两部分:数据域和指针域。

与数组相比,链表的最大优势在于插入和删除操作的时间复杂度为O(1)(在已知前驱节点的情况下),且不需要预先分配固定大小的内存空间。但链表的缺点也很明显:无法像数组一样通过索引随机访问元素,查找特定元素需要从头遍历。

常见链表类型

  • 单链表:每个节点只包含一个指向后继节点的指针
  • 双链表:每个节点包含前驱和后继两个指针,支持双向遍历
  • 循环链表:尾节点的指针指向头节点,形成环状结构

单链表基础实现

#include <iostream>
using namespace std;

// 定义链表节点
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

// 在链表头部插入节点
void insertAtHead(ListNode*& head, int val) {
    ListNode* newNode = new ListNode(val);
    newNode->next = head;
    head = newNode;
}

// 删除指定值的节点
void deleteNode(ListNode*& head, int val) {
    if (!head) return;
    
    if (head->val == val) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
        return;
    }
    
    ListNode* cur = head;
    while (cur->next && cur->next->val != val) {
        cur = cur->next;
    }
    
    if (cur->next) {
        ListNode* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
    }
}

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

int main() {
    ListNode* head = nullptr;
    
    insertAtHead(head, 3);
    insertAtHead(head, 2);
    insertAtHead(head, 1);
    
    cout << "初始链表: ";
    printList(head);
    
    deleteNode(head, 2);
    cout << "删除节点2后: ";
    printList(head);
    
    return 0;
}

链表经典技巧:快慢指针

快慢指针是解决链表问题的利器。快指针每次走两步,慢指针每次走一步:

  • 找中点:当快指针到达末尾时,慢指针正好在链表中间
  • 判环:如果链表有环,快慢指针必定相遇
  • 找倒数第K个节点:快指针先走K步,然后快慢指针同步前进

栈(Stack)

介绍

栈是一种后进先出(LIFO, Last In First Out)的线性数据结构。它只允许在一端(称为栈顶)进行插入和删除操作。生活中栈的例子比比皆是:一叠盘子、浏览器的后退按钮、函数调用的内存模型。

核心操作

  • push:将元素压入栈顶
  • pop:将栈顶元素弹出
  • top/peek:查看栈顶元素,但不弹出
  • isEmpty:判断栈是否为空

基于数组的栈实现

#include <iostream>
#include <vector>
using namespace std;

class Stack {
private:
    vector<int> data;

public:
    void push(int x) {
        data.push_back(x);
    }
    
    int pop() {
        if (isEmpty()) {
            throw runtime_error("Stack is empty");
        }
        int topVal = data.back();
        data.pop_back();
        return topVal;
    }
    
    int top() {
        if (isEmpty()) {
            throw runtime_error("Stack is empty");
        }
        return data.back();
    }
    
    bool isEmpty() {
        return data.empty();
    }
    
    int size() {
        return data.size();
    }
};

int main() {
    Stack s;
    s.push(10);
    s.push(20);
    s.push(30);
    
    cout << "栈顶元素: " << s.top() << endl;
    cout << "弹出: " << s.pop() << endl;
    cout << "当前大小: " << s.size() << endl;
    
    return 0;
}

栈的经典应用:括号匹配

给定一个只包含括号的字符串,判断其是否合法。基本思路:遇到左括号就入栈,遇到右括号就与栈顶匹配,若匹配成功则出栈,最后栈为空则合法。

bool isValid(string s) {
    vector<char> stack;
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            stack.push_back(c);
        } else {
            if (stack.empty()) return false;
            char top = stack.back();
            if ((c == ')' && top == '(') ||
                (c == ']' && top == '[') ||
                (c == '}' && top == '{')) {
                stack.pop_back();
            } else {
                return false;
            }
        }
    }
    return stack.empty();
}

队列(Queue)

介绍

队列是一种先进先出(FIFO, First In First Out)的线性数据结构。它只允许在一端(队尾)插入元素,在另一端(队头)删除元素。生活中的排队买票、打印任务调度、CPU进程调度都是队列的典型应用。

核心操作

  • enqueue:在队尾插入元素
  • dequeue:从队头移除元素
  • front:查看队头元素
  • isEmpty:判断队列是否为空

基于数组的循环队列实现

#include <iostream>
using namespace std;

class CircularQueue {
private:
    int* data;
    int front, rear;
    int capacity;
    int count;

public:
    CircularQueue(int k) {
        capacity = k;
        data = new int[k];
        front = 0;
        rear = -1;
        count = 0;
    }
    
    bool enqueue(int value) {
        if (count == capacity) return false;
        rear = (rear + 1) % capacity;
        data[rear] = value;
        count++;
        return true;
    }
    
    bool dequeue() {
        if (count == 0) return false;
        front = (front + 1) % capacity;
        count--;
        return true;
    }
    
    int Front() {
        if (count == 0) return -1;
        return data[front];
    }
    
    int Rear() {
        if (count == 0) return -1;
        return data[rear];
    }
    
    bool isEmpty() {
        return count == 0;
    }
    
    bool isFull() {
        return count == capacity;
    }
};

int main() {
    CircularQueue q(3);
    q.enqueue(1);
    q.enqueue(2);
    q.enqueue(3);
    
    cout << "队头: " << q.Front() << endl;
    q.dequeue();
    q.enqueue(4);
    cout << "队尾: " << q.Rear() << endl;
    
    return 0;
}

队列的经典应用:BFS的底层支撑

在Day2中,我们学习了广度优先搜索(BFS)。BFS之所以能够层层推进,正是因为它使用了队列来存储待访问的节点。每次从队头取出一个节点访问,同时将其未访问的邻居加入队尾,保证了距离起点近的节点优先被处理。

三种结构的对比总结

特性 数组 链表 队列
访问方式 随机访问 顺序访问 只能访问栈顶 只能访问队头
插入/删除 O(n) O(1) O(1) O(1)
查找 O(1)按索引 O(n) O(n) O(n)
内存分配 连续 离散 连续/离散 连续/离散
典型应用 索引查询 频繁增删 回溯、递归 BFS、调度

结语

链表、栈和队列是构建更复杂数据结构与算法的基石。链表让我们摆脱了连续内存的束缚;栈的LIFO特性完美适配了递归和回溯问题;队列的FIFO特性则是层级遍历和任务调度的核心支撑。掌握它们,你就为后续学习树、图、哈希表等高级结构打下了坚实的基础。

下一篇预告:5天速成数据结构与算法-Day4——树与二叉树。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇