数据结构:七大线性数据结构从结构体定义到函数实现的的区别
数据结构概述
在程序设计中,数据结构的选择直接影响算法的效率和代码的可维护性。本文将深入解析顺序表、单链表、双向链表、栈、链栈、队列、循环队列这七种线性数据结构,通过对比它们的结构体定义和函数实现,帮助大家理解各自的特点和适用场景。
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初始为NULLreturn 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; // 两个指针都初始化为NULLnewNode->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 10typedef 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;
}
性能优化建议
顺序结构:适合查询多、增删少的场景
链式结构:适合频繁插入删除的场景
栈的应用:函数调用、表达式求值、括号匹配
队列的应用:消息队列、任务调度、广度优先搜索
总结
通过对比七种线性数据结构的结构体定义和函数实现,我们可以清楚地看到:
顺序表适合随机访问,但插入删除效率低
链表适合频繁插入删除,但访问效率低
栈适合LIFO场景,操作受限但效率高
队列适合FIFO场景,解决顺序处理问题
循环队列解决了顺序队列的假溢出问题
理解这些数据结构的本质区别和适用场景,对于设计高效算法和解决实际问题具有重要意义。在实际开发中,大家根据具体需求选择合适的数据结构,平衡时间效率和空间效率。