数据结构:七大线性数据结构从结构体定义到函数实现的的区别

数据结构概述

在程序设计中,数据结构的选择直接影响算法的效率和代码的可维护性。本文将深入解析顺序表、单链表、双向链表、栈、链栈、队列、循环队列这七种线性数据结构,通过对比它们的结构体定义和函数实现,帮助大家理解各自的特点和适用场景。

1. 顺序表(Sequence List)

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef struct {
    int data[MAXSIZE];  // 静态数组存储元素
    int length;         // 当前长度
} SeqList;

结构特点

使用连续内存空间 通过数组下标直接访问元素 需要预先分配固定大小

核心函数实现对比

复制代码
// 初始化顺序表
void initSeqList(SeqList* list) {
    list->length = 0;  // 只需设置长度为0
}
// 插入元素
int insertSeqList(SeqList* list, int pos, int elem) {
    if (pos < 1 || pos > list->length + 1) return 0;
    if (list->length >= MAXSIZE) return 0;
    for (int i = list->length; i >= pos; i--) {
        list->data[i] = list->data[i - 1];  // 移动后续元素
    }
    list->data[pos - 1] = elem;
    list->length++;
    return 1;
}
// 删除元素
int deleteSeqList(SeqList* list, int pos) {
    if (pos < 1 || pos > list->length) return 0;
    for (int i = pos; i < list->length; i++) {
        list->data[i - 1] = list->data[i];  // 前移覆盖
    }
    list->length--;
    return 1;
}

函数说明表

函数名 头文件 参数 返回值 参数示例 示例含义
initSeqList stdio.h, stdlib.h list: 顺序表指针 &list 初始化空顺序表
insertSeqList stdio.h, stdlib.h list: 顺序表指针 pos: 位置 elem: 元素 成功1,失败0 &list, 2, 10 在位置2插入10
deleteSeqList stdio.h, stdlib.h list: 顺序表指针 pos: 位置 成功1,失败0 &list, 3 删除位置3元素

2. 单链表(Singly Linked List)

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
    int data;           // 数据域
    struct Node* next;  // 指针域
} ListNode;

结构特点

节点包含数据和指针 内存非连续,通过指针链接 动态内存分配

核心函数实现对比

复制代码
// 创建新节点
ListNode* createNode(int elem) {
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = elem;
    newNode->next = NULL;  // 新节点next初始为NULL
    return newNode;
}

// 插入节点
void insertListNode(ListNode** head, int pos, int elem) {
    ListNode* newNode = createNode(elem);
    if (pos == 1) {  // 头插法
        newNode->next = *head;
        *head = newNode;
        return;
    }
    ListNode* current = *head;
    for (int i = 1; i < pos - 1 && current != NULL; i++) {
        current = current->next;  // 遍历找到插入位置
    }
    if (current == NULL) return;
    newNode->next = current->next;  // 新节点指向原后继
    current->next = newNode;        // 原前驱指向新节点
}

// 删除节点
void deleteListNode(ListNode** head, int pos) {
    if (*head == NULL) return;
    ListNode* temp = *head;
    if (pos == 1) {  // 删除头节点
        *head = temp->next;
        free(temp);
        return;
    }
    for (int i = 1; temp != NULL && i < pos - 1; i++) {
        temp = temp->next;  // 找到删除位置的前驱
    }
    if (temp == NULL || temp->next == NULL) return;
    ListNode* toDelete = temp->next;    // 要删除的节点
    temp->next = toDelete->next;        // 前驱指向后继
    free(toDelete);                     // 释放内存
}

函数说明表

函数名 头文件 参数 返回值 参数示例 示例含义
createNode stdio.h, stdlib.h elem: 节点数据 新节点指针 5 创建数据为5的节点
insertListNode stdio.h, stdlib.h head: 头指针地址 pos: 位置 elem: 元素 &head, 1, 8 在头部插入8
deleteListNode stdio.h, stdlib.h head: 头指针地址 pos: 位置 &head, 2 删除第2个节点

3. 双向链表(Doubly Linked List)

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
typedef struct DNode {
    int data;               // 数据域
    struct DNode* prev;     // 前驱指针
    struct DNode* next;     // 后继指针
} DListNode;

结构特点

每个节点有两个指针 可以双向遍历 插入删除操作更复杂但更灵活

核心函数实现对比

复制代码
// 创建双向节点
DListNode* createDNode(int elem) {
    DListNode* newNode = (DListNode*)malloc(sizeof(DListNode));
    newNode->data = elem;
    newNode->prev = NULL;  // 两个指针都初始化为NULL
    newNode->next = NULL;
    return newNode;
}

// 插入节点(双向链表特有)
void insertDListNode(DListNode** head, int pos, int elem) {
    DListNode* newNode = createDNode(elem);
    if (*head == NULL) {  // 空链表
        *head = newNode;
        return;
    }
    if (pos == 1) {  // 头插
        newNode->next = *head;
        (*head)->prev = newNode;  // 设置原头节点的前驱
        *head = newNode;
        return;
    }
    DListNode* current = *head;
    for (int i = 1; i < pos - 1 && current != NULL; i++) {
        current = current->next;
    }
    if (current == NULL) return;
    newNode->next = current->next;
    newNode->prev = current;           // 设置新节点前驱
    if (current->next != NULL) {
        current->next->prev = newNode; // 设置后继节点的前驱
    }
    current->next = newNode;           // 设置前驱节点的后继
}

// 删除节点(双向链表特有)
void deleteDListNode(DListNode** head, int pos) {
    if (*head == NULL) return;
    DListNode* current = *head;
    if (pos == 1) {  // 删除头节点
        *head = current->next;
        if (*head != NULL) {
            (*head)->prev = NULL;  // 新头节点前驱置空
        }
        free(current);
        return;
    }
    for (int i = 1; current != NULL && i < pos; i++) {
        current = current->next;  // 直接找到要删除的节点
    }
    if (current == NULL) return;
    if (current->next != NULL) {
        current->next->prev = current->prev;  // 更新后继的前驱
    }
    if (current->prev != NULL) {
        current->prev->next = current->next;  // 更新前驱的后继
    }
    free(current);
}

4. 栈(Stack)- 顺序实现

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
#define STACK_SIZE 100
typedef struct {
    int data[STACK_SIZE];  // 数组存储
    int top;               // 栈顶指针
} SeqStack;

结构特点

LIFO(后进先出) 只能在栈顶操作 顺序存储实现

核心函数实现对比

复制代码
// 初始化栈
void initStack(SeqStack* stack) {
    stack->top = -1;  // 栈空标志
}

// 入栈
int pushStack(SeqStack* stack, int elem) {
    if (stack->top >= STACK_SIZE - 1) return 0;
    stack->data[++stack->top] = elem;  // 栈顶上移再赋值
    return 1;
}

// 出栈
int popStack(SeqStack* stack) {
    if (stack->top < 0) return -1;
    return stack->data[stack->top--];  // 返回栈顶元素再下移
}

// 获取栈顶(顺序栈特有)
int peekStack(SeqStack* stack) {
    if (stack->top < 0) return -1;
    return stack->data[stack->top];  // 只读不修改top
}

函数说明表

函数名 头文件 参数 返回值 参数示例 示例含义
initStack stdio.h, stdlib.h stack: 栈指针 &stack 初始化空栈
pushStack stdio.h, stdlib.h stack: 栈指针 elem: 元素 成功1,失败0 &stack, 5 5入栈
popStack stdio.h, stdlib.h stack: 栈指针 栈顶元素 &stack 弹出栈顶
peekStack stdio.h, stdlib.h stack: 栈指针 栈顶元素 &stack 查看栈顶

5. 链栈(Linked Stack)

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
typedef struct StackNode {
    int data;                   // 数据域
    struct StackNode* next;     // 指针域
} LinkStack;

结构特点

栈顶是链表头 动态内存管理 理论上容量无限

核心函数实现对比

复制代码
// 入栈(链栈特有)
void pushLinkStack(LinkStack** top, int elem) {
    LinkStack* newNode = (LinkStack*)malloc(sizeof(LinkStack));
    newNode->data = elem;
    newNode->next = *top;  // 新节点指向原栈顶
    *top = newNode;        // 更新栈顶指针
}

// 出栈(链栈特有)
int popLinkStack(LinkStack** top) {
    if (*top == NULL) return -1;
    LinkStack* temp = *top;
    int elem = temp->data;
    *top = (*top)->next;  // 栈顶下移
    free(temp);           // 释放原栈顶
    return elem;
}

// 判断空栈
int isEmptyLinkStack(LinkStack* top) {
    return top == NULL;  // 栈空条件
}

6. 队列(Queue)- 顺序实现

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
#define QUEUE_SIZE 100
typedef struct {
    int data[QUEUE_SIZE];  // 数组存储
    int front;             // 队头指针
    int rear;              // 队尾指针
} SeqQueue;

结构特点

FIFO(先进先出) 队尾插入,队头删除 存在"假溢出"问题

核心函数实现对比

复制代码
// 初始化队列
void initQueue(SeqQueue* queue) {
    queue->front = 0;
    queue->rear = 0;  // 队空条件:front == rear
}

// 入队
int enQueue(SeqQueue* queue, int elem) {
    if (queue->rear >= QUEUE_SIZE) return 0;
    queue->data[queue->rear++] = elem;  // 队尾插入,rear后移
    return 1;
}

// 出队
int deQueue(SeqQueue* queue) {
    if (queue->front == queue->rear) return -1;
    return queue->data[queue->front++];  // 队头取出,front后移
}

// 判断队空
int isEmptyQueue(SeqQueue* queue) {
    return queue->front == queue->rear;  // 队空条件
}

7. 循环队列(Circular Queue)

结构体定义与特点

复制代码
#include <stdio.h>
#include <stdlib.h>
#define CQ_SIZE 5  // 实际可用CQ_SIZE-1
typedef struct {
    int data[CQ_SIZE];  // 循环数组
    int front;          // 队头指针
    int rear;           // 队尾指针
} CircularQueue;

结构特点

数组首尾相接 解决"假溢出"问题 队空和队满的判断条件特殊

核心函数实现对比

复制代码
// 初始化循环队列
void initCQueue(CircularQueue* queue) {
    queue->front = 0;
    queue->rear = 0;  // 循环队列空条件
}

// 入队(循环队列特有)
int enCQueue(CircularQueue* queue, int elem) {
    if ((queue->rear + 1) % CQ_SIZE == queue->front) return 0;  // 队满
    queue->data[queue->rear] = elem;
    queue->rear = (queue->rear + 1) % CQ_SIZE;  // 循环后移
    return 1;
}

// 出队(循环队列特有)
int deCQueue(CircularQueue* queue) {
    if (queue->front == queue->rear) return -1;  // 队空
    int elem = queue->data[queue->front];
    queue->front = (queue->front + 1) % CQ_SIZE;  // 循环后移
    return elem;
}

// 判断队满(循环队列特有)
int isCQueueFull(CircularQueue* queue) {
    return (queue->rear + 1) % CQ_SIZE == queue->front;  // 队满条件
}

函数说明表

函数名 头文件 参数 返回值 参数示例 示例含义
initCQueue stdio.h, stdlib.h queue: 队列指针 &queue 初始化循环队列
enCQueue stdio.h, stdlib.h queue: 队列指针 elem: 元素 成功1,失败0 &queue, 3 元素3入队
deCQueue stdio.h, stdlib.h queue: 队列指针 队头元素 &queue 队头出队
isCQueueFull stdio.h, stdlib.h queue: 队列指针 队满1,否0 &queue 判断队满

实际应用示例

浏览器历史记录(栈的应用)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_HISTORY 10

typedef struct {
    char* pages[MAX_HISTORY];
    int top;
} BrowserHistory;

void initBrowser(BrowserHistory* browser) {
    browser->top = -1;
}

void visitPage(BrowserHistory* browser, const char* url) {
    if (browser->top >= MAX_HISTORY - 1) {
        free(browser->pages[0]);
        for (int i = 0; i < MAX_HISTORY - 1; i++) {
            browser->pages[i] = browser->pages[i + 1];
        }
        browser->top--;
    }
    browser->pages[++browser->top] = strdup(url);
}

char* goBack(BrowserHistory* browser) {
    if (browser->top <= 0) return NULL;
    return browser->pages[--browser->top];
}

char* goForward(BrowserHistory* browser) {
    if (browser->top >= MAX_HISTORY - 1) return NULL;
    return browser->pages[++browser->top];
}

数据结构对比总结

内存分配方式

数据结构 存储方式 内存连续性 容量限制
顺序表 静态数组 连续 固定大小
单链表 动态节点 不连续 理论无限
双向链表 动态节点 不连续 理论无限
顺序栈 静态数组 连续 固定大小
链栈 动态节点 不连续 理论无限
顺序队列 静态数组 连续 固定大小
循环队列 静态数组 连续 固定大小

操作复杂度对比

操作 顺序表 单链表 双向链表 队列
访问 O(1) O(n) O(n) O(1) O(1)
插入 O(n) O(1) O(1) O(1) O(1)
删除 O(n) O(1) O(1) O(1) O(1)
查找 O(n) O(n) O(n) O(n) O(n)

常见面试题

1. 基础概念题

Q1:顺序表和链表的区别是什么?

存储方式:顺序表连续,链表离散 访问效率:顺序表O(1),链表O(n)

插入删除:顺序表O(n),链表O(1) 空间效率:顺序表更优,链表有指针开销

Q2:栈和队列的主要区别?

栈:LIFO(后进先出),只能在栈顶操作

队列:FIFO(先进先出),队尾插入,队头删除

2. 算法实现题

Q3:用两个栈实现队列

复制代码
typedef struct {
    SeqStack stack1;  // 用于入队
    SeqStack stack2;  // 用于出队
} StackQueue;

void enStackQueue(StackQueue* sq, int elem) {
    pushStack(&sq->stack1, elem);
}

int deStackQueue(StackQueue* sq) {
    if (isEmptyStack(&sq->stack2)) {
        while (!isEmptyStack(&sq->stack1)) {
            pushStack(&sq->stack2, popStack(&sq->stack1));
        }
    }
    return popStack(&sq->stack2);
}

Q4:判断链表是否有环

复制代码
int hasCycle(ListNode* head) {
    if (head == NULL) return 0;
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) return 1;
    }
    return 0;
}

3. 综合应用题

Q5:设计LRU缓存机制

使用哈希表+双向链表实现 哈希表提供O(1)访问 双向链表维护访问顺序

Q6:用队列实现栈

复制代码
typedef struct {
    SeqQueue queue1;
    SeqQueue queue2;
} QueueStack;

void pushQueueStack(QueueStack* qs, int elem) {
    enQueue(&qs->queue1, elem);
}

int popQueueStack(QueueStack* qs) {
    while (qs->queue1.front != qs->queue1.rear - 1) {
        enQueue(&qs->queue2, deQueue(&qs->queue1));
    }
    int elem = deQueue(&qs->queue1);
    SeqQueue temp = qs->queue1;
    qs->queue1 = qs->queue2;
    qs->queue2 = temp;
    return elem;
}

性能优化建议

  1. 顺序结构:适合查询多、增删少的场景

  2. 链式结构:适合频繁插入删除的场景

  3. 栈的应用:函数调用、表达式求值、括号匹配

  4. 队列的应用:消息队列、任务调度、广度优先搜索

总结

通过对比七种线性数据结构的结构体定义和函数实现,我们可以清楚地看到:

  1. 顺序表适合随机访问,但插入删除效率低

  2. 链表适合频繁插入删除,但访问效率低

  3. 适合LIFO场景,操作受限但效率高

  4. 队列适合FIFO场景,解决顺序处理问题

  5. 循环队列解决了顺序队列的假溢出问题

理解这些数据结构的本质区别和适用场景,对于设计高效算法和解决实际问题具有重要意义。在实际开发中,大家根据具体需求选择合适的数据结构,平衡时间效率和空间效率。

相关推荐
wangwangmoon_light6 小时前
0.0 编码基础模板
java·数据结构·算法
小年糕是糕手7 小时前
【数据结构】算法复杂度
c语言·开发语言·数据结构·学习·算法·leetcode·排序算法
小杨勇敢飞9 小时前
拼图小游戏开发日记 | Day3(已完结)
java·数据结构·算法
Guan jie9 小时前
10.6作业
数据结构·算法·排序算法
_bong12 小时前
python评估算法性能
数据结构·python·算法
如意猴13 小时前
数据结构初阶(第六讲)单链表的功能实现
数据结构
2401_8414956414 小时前
【数据结构】链栈的基本操作
java·数据结构·c++·python·算法·链表·链栈
Archie_IT14 小时前
「深入浅出」嵌入式八股文—P2 内存篇
c语言·开发语言·数据结构·数据库·c++·算法
是那盏灯塔14 小时前
【算法】——动态规划算法及实践应用
数据结构·c++·算法·动态规划