数据结构(核心知识点与高频考点)

核心知识点

一、数据结构基础

  1. 基本概念

    • 数据、数据元素、数据项、数据结构

    • 逻辑结构:集合、线性、树形、图状

    • 物理结构(存储结构):顺序存储、链式存储、索引存储、散列存储

    • 抽象数据类型(ADT):定义、实现与操作

  2. 算法分析

    • 时间复杂度(大O表示法)

      • 常见阶:O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ)

      • 斐波那契数列递归实现的时间复杂度为 O (2ⁿ),而迭代实现为 O (n)16。

    • 空间复杂度

    • 最坏/平均/最好情况分析


二、线性结构

1. 数组 (Array)
  • 特点:连续内存、随机访问

  • 操作:插入、删除、查找(时间复杂度分析)

  • 应用:动态数组(Vector/ArrayList)

2. 链表 (Linked List)
  • 单向链表:节点结构、遍历、插入/删除(头/中/尾)

  • 双向链表:结构优势、插入/删除

  • 循环链表:约瑟夫问题应用

  • 链表 vs 数组:内存灵活性 vs 访问效率

3. 栈 (Stack)
  • 后进先出(LIFO)特性

  • 操作:pushpoppeek

  • 实现:数组栈 vs 链表栈

  • 应用:函数调用栈、括号匹配、表达式求值(中缀转后缀)

4. 队列 (Queue)
  • 先进先出(FIFO)特性

  • 操作:enqueuedequeue

  • 循环队列:解决假溢出问题

  • 双端队列 (Deque):两端操作

  • 优先队列(通常用堆实现,见树章节)


三、树形结构

1. 树基础
  • 术语:根、节点、边、度、深度、高度、叶子节点

  • 二叉树:定义、性质(第i层最多2ⁱ⁻¹节点)

  • 满二叉树 & 完全二叉树

2. 二叉树遍历
  • 深度优先

    • 先序(根-左-右)

    • 中序(左-根-右)→ BST有序输出

    • 后序(左-右-根)

  • 广度优先:层序遍历(队列实现)

3. 二叉搜索树 (BST)
  • 定义:左子树 < 根 < 右子树

  • 操作:查找(O(h))、插入、删除(三种情况)

  • 缺点:退化成链表(需平衡树优化)

4. 平衡二叉树
  • AVL树

    • 平衡因子(|左高-右高| ≤ 1)

    • 旋转操作:LL/RR/LR/RL

  • 红黑树(近似平衡):

    • 五大规则(根黑、叶黑、红节点子必黑等)

    • 应用:Java TreeMap, C++ map

5. 堆 (Heap)
  • 大顶堆 & 小顶堆

  • 操作:插入(上浮)、删除堆顶(下沉)

  • 建堆:O(n) 时间复杂度证明

  • 应用:优先队列、堆排序

6. 多叉树
  • B树:磁盘存储优化(多关键字、阶数)

  • B+树:非叶节点仅索引、叶子链表链接 → 数据库索引

  • Trie树(字典树):字符串前缀匹配


四、图 (Graph)

1. 图基础
  • 术语:顶点、边、有向图/无向图、权重、度、连通分量

  • 表示法:

    • 邻接矩阵:O(1) 查边,O(n²) 空间

    • 邻接表:O(deg(v)) 查边,节省空间

2. 图遍历
  • 广度优先搜索 (BFS):队列实现 → 最短路径(无权图)

  • 深度优先搜索 (DFS):递归/栈实现 → 拓扑排序、连通性检测

3. 图算法
  • 最短路径

    • Dijkstra(无负权单源最短)

    • Floyd(多源最短)

  • 最小生成树

    • Prim(贪心,优先队列)

    • Kruskal(并查集)

  • 拓扑排序:有向无环图(DAG)任务调度


五、散列表 (Hash Table)

  1. 核心思想

    • 关键字 → 哈希函数 → 存储位置

    • 冲突解决:

      • 开放定址法:线性探测、二次探测

      • 链地址法(常用):冲突位置拉链表

  2. 设计要点

    • 哈希函数设计(均匀性、效率)

    • 装载因子(Load Factor)与扩容机制

    • 应用:Python dict、Java HashMap


六、高级数据结构

  1. 并查集 (Disjoint Set)

    • 操作:find(路径压缩)、union(按秩合并)

    • 应用:连通分量检测、Kruskal算法

  2. 跳跃表 (Skip List)

    • 多层链表结构,O(log n) 查找

    • Redis有序集合实现

  3. 线段树 & 树状数组

    • 区间查询/更新问题(如区间求和)


七、查找算法

算法 时间复杂度(平均) 时间复杂度(最坏) 空间复杂度 适用场景
顺序查找 O(n) O(n) O(1) 无序小数据集
二分查找 O(log n) O(log n) O(1) 有序数组
插值查找 O(log log n) O(n) O(1) 均匀分布的有序数组
二叉搜索树查找 O(log n) O(n) O(n) 动态数据集
哈希查找 O(1) O(n) O(n) 快速查找,键值对存储
  1. 稳定性定义: 查找算法的 “稳定性” 指 “当存在重复元素时,是否保证查找结果的顺序一致性”(如顺序查找必找第一个匹配项,哈希链地址法必找同桶内最先插入的匹配项,故稳定;二分查找可能定位到中间匹配项,故不稳定)。

  2. 哈希查找细节: 若采用开放定址法(如线性探测),空间复杂度为 \(O(1)\)(复用原数组),但平均时间复杂度会因 “聚集效应” 上升,且稳定性不稳定(可能跳过前面的重复元素),故表格以更典型的链地址法为例。

  3. BST 与 AVL 对比: BST 最坏情况退化为链表(时间 \(O(n)\)),AVL 通过旋转保持平衡,确保时间始终 \(O(\log n)\),但插入 / 删除操作更复杂。

八、排序算法

算法 平均时间复杂度 空间复杂度 稳定性 备注
冒泡排序 O(n²) O(1) 稳定 相邻交换
快速排序 O(n log n) O(log n) 不稳定 分治 + 基准划分
归并排序 O(n log n) O(n) 稳定 分治 + 合并有序数组
堆排序 O(n log n) O(1) 不稳定 建堆 + 交换堆顶
插入排序 O(n²) O(1) 稳定 适合小规模数据
  • 插入排序(直接插入、希尔排序)、交换排序(冒泡、快速排序)、选择排序、归并排序、堆排序。
  • 时间复杂度对比:
    • 稳定排序:冒泡、插入、归并、基数排序。
    • 不稳定排序:快速、选择、堆排序。
    • 最优时间复杂度:O (n)(插入、冒泡在最好情况下)。
    • 最坏时间复杂度:O (n²)(插入、冒泡、选择)、O (n logn)(快速、归并、堆排序)。

高频考点

一、线性结构高频考点

考点1:链表翻转(LeetCode 206)
#include 
#include 

struct ListNode {
    int val;
    struct ListNode *next;
};

// 链表翻转函数
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode *prev = NULL;
    struct ListNode *curr = head;
    
    while (curr != NULL) {
        struct ListNode *nextTemp = curr->next; // 暂存下一节点
        curr->next = prev;  // 反转指针
        prev = curr;       // prev前移
        curr = nextTemp;    // curr后移
    }
    return prev; // 新链表头
}

// 测试代码
int main() {
    // 创建链表 1->2->3->4->5
    struct ListNode nodes[5];
    for (int i = 0; i < 5; i++) {
        nodes[i].val = i + 1;
        nodes[i].next = (i < 4) ? &nodes[i+1] : NULL;
    }
    
    struct ListNode* newHead = reverseList(&nodes[0]);
    
    // 打印翻转结果:5->4->3->2->1
    struct ListNode* p = newHead;
    while (p != NULL) {
        printf("%d->", p->val);
        p = p->next;
    }
    printf("NULL\n");
    return 0;
}

关键点

  1. 使用三指针法:prev, curr, nextTemp

  2. 时间复杂度:O(n),空间复杂度:O(1)

  3. 注意指针操作顺序,避免链表断裂


二、树形结构高频考点

考点1:二叉树层序遍历(LeetCode 102)
#include 
#include 

#define MAX_SIZE 100

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

// 队列结构
struct Queue {
    struct TreeNode* data[MAX_SIZE];
    int front;
    int rear;
};

// 初始化队列
void initQueue(struct Queue *q) {
    q->front = 0;
    q->rear = 0;
}

// 入队
void enqueue(struct Queue *q, struct TreeNode* node) {
    if ((q->rear + 1) % MAX_SIZE == q->front) return; // 队满
    q->data[q->rear] = node;
    q->rear = (q->rear + 1) % MAX_SIZE;
}

// 出队
struct TreeNode* dequeue(struct Queue *q) {
    if (q->front == q->rear) return NULL; // 队空
    struct TreeNode* node = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return node;
}

// 层序遍历
void levelOrder(struct TreeNode* root) {
    if (root == NULL) return;
    
    struct Queue q;
    initQueue(&q);
    enqueue(&q, root);
    
    while (q.front != q.rear) {
        int levelSize = (q.rear - q.front + MAX_SIZE) % MAX_SIZE;
        
        for (int i = 0; i < levelSize; i++) {
            struct TreeNode* node = dequeue(&q);
            printf("%d ", node->val);
            
            if (node->left != NULL) enqueue(&q, node->left);
            if (node->right != NULL) enqueue(&q, node->right);
        }
        printf("\n"); // 换行表示新的一层
    }
}

// 测试代码
int main() {
    /* 构建二叉树:
          1
         / \
        2   3
       / \   \
      4   5   6
    */
    struct TreeNode n1 = {1, NULL, NULL};
    struct TreeNode n2 = {2, NULL, NULL};
    struct TreeNode n3 = {3, NULL, NULL};
    struct TreeNode n4 = {4, NULL, NULL};
    struct TreeNode n5 = {5, NULL, NULL};
    struct TreeNode n6 = {6, NULL, NULL};
    
    n1.left = &n2; n1.right = &n3;
    n2.left = &n4; n2.right = &n5;
    n3.right = &n6;
    
    printf("层序遍历结果:\n");
    levelOrder(&n1);
    return 0;
}

输出

层序遍历结果:
1 
2 3 
4 5 6 

三、排序高频考点

考点1:快速排序(手写实现)
#include 

// 交换两个元素
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 分区函数
int partition(int arr[], int low, int high) {
    int pivot = arr[high];  // 选择最后一个元素为基准
    int i = (low - 1);      // 小于基准的边界索引
    
    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

// 快速排序主函数
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

// 测试代码
int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("排序前: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    
    quickSort(arr, 0, n - 1);
    
    printf("\n排序后: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    
    return 0;
}

关键点

  1. 分区函数是核心,时间复杂度平均O(n log n)

  2. 最坏情况(已排序数组)时间复杂度O(n²)

  3. 优化方法:随机选择基准点 int pivot = arr[low + rand() % (high - low + 1)];


四、图论高频考点

考点1:图的DFS遍历

#include 
#include 
#define V 5

// 邻接矩阵表示图
int graph[V][V] = {
    {0, 1, 1, 0, 0},
    {1, 0, 1, 1, 0},
    {1, 1, 0, 0, 1},
    {0, 1, 0, 0, 1},
    {0, 0, 1, 1, 0}
};

// DFS递归函数
void DFS(int vertex, int visited[]) {
    visited[vertex] = 1;
    printf("%d ", vertex);
    
    for (int i = 0; i < V; i++) {
        if (graph[vertex][i] && !visited[i]) {
            DFS(i, visited);
        }
    }
}

// DFS主函数
void DFSTraversal() {
    int visited[V];
    for (int i = 0; i < V; i++) visited[i] = 0;
    
    printf("DFS遍历结果: ");
    for (int i = 0; i < V; i++) {
        if (!visited[i]) {
            DFS(i, visited);
        }
    }
    printf("\n");
}

int main() {
    DFSTraversal();
    return 0;
}

五、哈希表高频考点

考点1:两数之和(LeetCode 1)
#include 
#include 

// 哈希表节点
struct HashNode {
    int key;
    int value;
    struct HashNode* next;
};

// 哈希表
struct HashTable {
    int size;
    struct HashNode** table;
};

// 创建哈希表
struct HashTable* createHashTable(int size) {
    struct HashTable* ht = malloc(sizeof(struct HashTable));
    ht->size = size;
    ht->table = malloc(sizeof(struct HashNode*) * size);
    for (int i = 0; i < size; i++) ht->table[i] = NULL;
    return ht;
}

// 哈希函数
int hash(int key, int size) {
    return abs(key) % size;
}

// 插入哈希表
void insert(struct HashTable* ht, int key, int value) {
    int index = hash(key, ht->size);
    struct HashNode* newNode = malloc(sizeof(struct HashNode));
    newNode->key = key;
    newNode->value = value;
    newNode->next = ht->table[index];
    ht->table[index] = newNode;
}

// 查找哈希表
int find(struct HashTable* ht, int key) {
    int index = hash(key, ht->size);
    struct HashNode* node = ht->table[index];
    while (node) {
        if (node->key == key) return node->value;
        node = node->next;
    }
    return -1; // 未找到
}

// 两数之和解法
int* twoSum(int* nums, int numsSize, int target) {
    struct HashTable* ht = createHashTable(numsSize);
    int* result = malloc(2 * sizeof(int));
    
    for (int i = 0; i < numsSize; i++) {
        int complement = target - nums[i];
        int complementIndex = find(ht, complement);
        
        if (complementIndex != -1) {
            result[0] = complementIndex;
            result[1] = i;
            return result;
        }
        insert(ht, nums[i], i);
    }
    return NULL; // 无解
}

// 测试
int main() {
    int nums[] = {2, 7, 11, 15};
    int target = 9;
    int* result = twoSum(nums, 4, target);
    
    if (result) {
        printf("索引: [%d, %d]\n", result[0], result[1]);
        printf("值: %d + %d = %d\n", nums[result[0]], nums[result[1]], target);
    } else {
        printf("无解\n");
    }
    return 0;
}
 
  

六、高级数据结构考点

考点1:最小栈设计(LeetCode 155)
#include 
#include 
#include 

// 最小栈节点
struct MinStackNode {
    int val;
    int min; // 记录当前最小值
    struct MinStackNode* next;
};

// 最小栈结构
typedef struct {
    struct MinStackNode* top;
} MinStack;

// 创建栈
MinStack* minStackCreate() {
    MinStack* stack = malloc(sizeof(MinStack));
    stack->top = NULL;
    return stack;
}

// 入栈
void minStackPush(MinStack* obj, int val) {
    struct MinStackNode* node = malloc(sizeof(struct MinStackNode));
    node->val = val;
    
    // 计算当前最小值
    if (obj->top == NULL) {
        node->min = val;
    } else {
        node->min = (val < obj->top->min) ? val : obj->top->min;
    }
    
    node->next = obj->top;
    obj->top = node;
}

// 出栈
void minStackPop(MinStack* obj) {
    if (obj->top == NULL) return;
    struct MinStackNode* temp = obj->top;
    obj->top = obj->top->next;
    free(temp);
}

// 获取栈顶元素
int minStackTop(MinStack* obj) {
    if (obj->top == NULL) return INT_MIN;
    return obj->top->val;
}

// 获取最小值
int minStackGetMin(MinStack* obj) {
    if (obj->top == NULL) return INT_MIN;
    return obj->top->min;
}

// 测试
int main() {
    MinStack* stack = minStackCreate();
    
    minStackPush(stack, -2);
    minStackPush(stack, 0);
    minStackPush(stack, -3);
    
    printf("最小值: %d\n", minStackGetMin(stack)); // -3
    minStackPop(stack);
    printf("栈顶: %d\n", minStackTop(stack));      // 0
    printf("最小值: %d\n", minStackGetMin(stack)); // -2
    
    return 0;
}

七、C语言数据结构实现要点

  1. 内存管理

    • 手动分配/释放内存(malloc/free)

    • 防止内存泄漏(确保每个malloc都有free)

    • 检查分配是否成功 if (ptr == NULL) { /* 处理错误 */ }

  2. 指针操作

    • 理解指针的指针(如链表操作)

    • 结构体指针访问成员:node->next 替代 node.next

  3. 常用技巧

    // 动态数组
    int* arr = malloc(size * sizeof(int));
    
    // 二维数组
    int** matrix = malloc(rows * sizeof(int*));
    for (int i = 0; i < rows; i++) 
        matrix[i] = malloc(cols * sizeof(int));
    
    // 位运算优化
    #define SET_BIT(arr, n) (arr[n/32] |= (1 << (n%32)))
    #define GET_BIT(arr, n) (arr[n/32] & (1 << (n%32)))
    
  4. 调试技巧

    • 使用Valgrind检测内存泄漏

    • 打印指针地址:printf("%p", ptr)

    • 断言检查:#include

建议练习

  1. 实现循环队列(数组版)

  2. 手写AVL树旋转函数

  3. 实现LRU缓存(哈希表+双向链表)

  4. 完成Dijkstra最短路径算法(邻接矩阵版)

你可能感兴趣的:(数据结构)