计算机科学中的数据结构是算法设计的基础。本文将详细介绍链表、栈和队列这三种常见的数据结构,并重点分析链表的各种形式及其操作方法。
链表是一种线性数据结构,与数组不同的是,它不需要连续的内存空间来存储数据。链表中的每个元素都是一个独立的节点,这些节点通过指针相互连接起来。
链表可以根据节点之间的连接方式分为以下几种:
NULL
,而是返回链表的第一个节点。struct Node { // 单向链表节点定义
int data; // 数据域,例如整数
struct Node *next; // 指向下一个节点的指针
};
/*
* @Author: ZPY
* @TODO: 单向链表的定义和使用
*/
#include
#include
// 节点
typedef struct node
{
int data;
struct node *next;
} Node;
// 两种定义链表的方式
// 1
typedef struct link
{
struct node *head;
int size;
} Link;
// 2
// typedef struct node *Link;
// 操作
void init(Link *link);
void add(Link *link, int value);
void display(Link *link);
void insert(Link *link, int index, int value);
void delete(Link *link, int index);
void clear(Link *link);
int find(Link *link, int value);
int size(Link *link);
int main(int argc, char const *argv[])
{
// 创建
Link *link = malloc(sizeof(Link));
// 初始化
init(link);
// 添加元素
add(link, 1);
add(link, 2);
add(link, 3);
// 显示
display(link);
// 删除
delete(link, 1);
// 显示
display(link);
// 插入
insert(link, 1, 4);
// 显示
display(link);
return 0;
}
void init(Link *link)
{
link->size = 0;
link->head = malloc(sizeof(Node));
link->head->next = NULL;
}
void add(Link *link, int value)
{
// Node node = {value, NULL}; // 两种创建头节点的方式
Node *node = malloc(sizeof(Node));
node->data = value;
node->next = NULL;
if (link->head->next == NULL) // 链表为空
{
link->head->next = node;
}
else
{
// 头插法
// node->next = link->head->next;
// link->head->next = node;
// 尾插法
Node *n = link->head;
for (int i = 0; i < link->size; i++)
{
n = n->next;
}
n->next = node; // 找到最后一个节点后将新节点添加到链表的末尾
}
link->size++;
}
void display(Link *link)
{
Node *node = link->head->next;
while (node)
{
printf("data = %d, ", node->data);
node = node->next;
}
printf("\n");
}
void insert(Link *link, int index, int value)
{
if (index < 0 || index > link->size)
{
printf("请输入正确索引\n");
return;
}
Node *pre = link->head;
while (index--)
{
pre = pre->next;
}
Node *node = malloc(sizeof(Node));
node->data = value;
node->next = pre->next;
pre->next = node;
link->size++;
free(node);
}
void delete(Link *link, int index)
{
if (index < 0 || index >= link->size)
{
printf("请输入正确索引\n");
return;
}
// 前驱节点
Node *pre = link->head;
for (int i = 0; i < index; i++)
{
pre = pre->next;
}
// 获取要删除的节点
Node *node = pre->next;
pre->next = node->next;
link->size--; // 大小减一
free(node); // 释放删除节点
}
int find(Link *link, int value)
{
Node *node = link->head->next;
int index = 0;
while (node)
{
if (node->data == value)
{
return index;
}
node = node->next;
index++;
}
return -1;
}
void clear(Link *link)
{
Node *node = link->head;
Node *f = node;
while (node)
{
node = node->next;
f = node;
free(f); // 遍历元素并释放空间
}
link->head->next = NULL;
link->size = 0;
}
int size(Link *link)
{
return link->size;
}
struct node // 节点
{
int data; // 数据域
struct node *prev; // 前驱
struct node *next; // 后继
};
/*
* @Author: ZPY
* @TODO: 双向链表的定义和使用,双向链表使用尾插法更方便
*/
#include
#include
typedef struct node // 节点
{
int data; // 数据域
struct node *prev; // 前驱
struct node *next; // 后继
} Node;
// typedef struct node Link; // 一个指针
typedef struct link // 两个指针
{
Node *head; // 头结点
Node *tail; // 尾结点
int size; // 链表长度
} Link;
// 操作
void init(Link *link);
void add(Link *link, int value);
void display(Link *link);
void insert(Link *link, int index, int value);
void delete(Link *link, int index);
void clear(Link *link);
int find(Link *link, int value);
int size(Link *link);
int get(Link *link, int index);
int main(int argc, char const *argv[])
{
Link *link = (Link *)malloc(sizeof(Link)); // C++ 不允许隐式转换,因此在 C++ 中申请空间需要强转,而 C 则不用
// 初始化
init(link);
// 添加
add(link, 1);
add(link, 2);
add(link, 3);
// 显示
display(link);
// 插入
insert(link, 1, 4);
// 显示
display(link);
return 0;
}
void init(Link *link)
{
link->size = 0;
link->head = malloc(sizeof(Node));
link->tail = malloc(sizeof(Node));
link->head->next = link->tail;
link->tail->prev = link->head;
}
void add(Link *link, int value) // 时间复杂度 O(1)
{
Node *node = malloc(sizeof(Node));
node->data = value;
link->tail->prev->next = node; // 尾结点前驱的后继指向新节点
node->prev = link->tail->prev; // 新节点的前驱指向尾结点的前驱
node->next = link->tail; // 新节点的后继指向尾结点
link->tail->prev = node; // 尾结点的前驱指向新节点
link->size++;
}
void display(Link *link)
{
Node *node = link->head->next;
for (int i = 0; i < link->size; i++)
{
printf("list[%d] = %d, ", i, node->data);
node = node->next;
}
printf("\n");
}
void insert(Link *link, int index, int value)
{
if (index < 0 || index > link->size)
{
printf("请输入正确索引\n");
return;
}
Node *node = malloc(sizeof(Node));
node->data = value;
Node *n = link->head->next;
while (index--)
{
n = n->next;
}
n->prev->next = node; // 新节点的前驱指向插入位置的前一个节点
node->prev = n->prev; // 新节点的后继指向插入位置的节点
node->next = n; // 新节点的后继指向插入位置的节点
n->prev = node; // 插入位置的前一个节点的后继指向新节点
link->size++;
}
void delete(Link *link, int index)
{
if (index < 0 || index > link->size)
{
printf("请输入正确索引\n");
return;
}
Node *node = link->head->next;
while (index--)
{
node = node->next;
}
node->prev->next = node->next; // 删除节点前驱的后继指向删除节点的后继
node->next->prev = node->prev; // 删除节点后继的前驱指向删除节点的前驱
free(node); // 释放删除节点内存
link->size--;
}
int find(Link *link, int value)
{
Node *node = link->head->next;
int index = 0;
while (node != link->tail)
{
if (node->data == value)
{
return index;
}
node = node->next;
index++;
}
return -1;
}
void clear(Link *link)
{
Node *node = link->head;
Node *f = node;
while (node != link->tail)
{
node = node->next;
f = node;
free(f); // 遍历元素并释放空间
}
link->head->next = link->tail;
link->tail->prev = link->head;
link->size = 0;
}
int size(Link *link)
{
return link->size;
}
int get(Link *link, int index)
{
Node *node = link->head->next;
while (index--)
{
node = node->next;
}
return node->data;
}
概念:一种操作受限的表,只能从一端操作。LIFO:后进先出
操作:
应用场景:
实现:
struct stack
{
int *data; // 数组:在使用时若栈满需要扩容,链表则不需要
int capacity; // 容量
int top; // 栈顶
};
/*
* @Author: ZPY
* @TODO: 栈的定义和使用
*/
#include
#include
typedef struct stack
{
int *data; // 数组
int capacity; // 容量
int top; // 栈顶
} Stack;
// 栈操作
void init(Stack *s); // 初始化
void push(Stack *s, int v); // 压栈
int pop(Stack *s); // 出栈,删除栈顶元素并返回
int peek(Stack *s); // 读取栈顶元素
int size(Stack *s); // 栈大小
void increase(Stack *s); // 扩容(数组需要,链表则无需)
int isEmpty(Stack *s);
int main(int argc, char const *argv[])
{
Stack *s= malloc(sizeof(Stack));
init(s);
push(s, 1);
push(s, 2);
push(s, 3);
printf("size = %d\n", size(s));
printf("%d\n", pop(s));
printf("%d\n", pop(s));
printf("%d\n", pop(s));
printf("size = %d\n", size(s));
return 0;
}
void init(Stack *s)
{
s->top = -1;
s->capacity = 10;
s->data = malloc(sizeof(int) * s->capacity);
}
void push(Stack *s, int v)
{
if (s->top == s->capacity - 1)
{
increase(s);
}
s->top++;
s->data[s->top] = v;
}
int pop(Stack *s)
{
if (s->top == -1)
{
printf("栈空无法出栈\n");
return -1;
}
return s->data[s->top--];
}
int peek(Stack *s)
{
if (s->top == -1)
{
printf("栈空无法读取\n");
return -1;
}
return s->data[s->top];
}
int size(Stack *s)
{
return s->top + 1;
}
void increase(Stack *s)
{
s->capacity << 1; // 扩容为2倍
s->data = realloc(s->data, sizeof(int) * s->capacity);
}
int isEmpty(Stack *s)
{
if (s->top == -1)
{
return 1;
}
return 0;
}
例如:1 + 2 * 3 - 4 / 2
中缀表达式存在缺陷:
方法:
转换过程:
原中缀表达式 | 数组(后缀表达式) | 运算符栈 |
---|---|---|
1 + 2 * 3 - 4 / 2 | NULL | NULL |
+ 2 * 3 - 4 / 2 | 1 | + |
2 * 3 - 4 / 2 | 1 | + |
* 3 - 4 / 2 | 1, 2 | + |
3 - 4 / 2 | 1, 2 | +, *(乘法优先级高于加法,‘+’ 不出栈) |
- 4 / 2 | 1, 2, 3 | +, * |
4 / 2 | 1, 2, 3, +, * | -(减法优先级低于乘法且等于加法,栈内元素出栈两次) |
/ 2 | 1, 2, 3, +, *, 4 | - |
2 | 1, 2, 3, +, *, 4 | -, /(除法优先级高于减法,‘-’ 不出栈) |
NULL | 1, 2, 3, +, *, 4, 2 | -, / |
NULL | 1, 2, 3, +, *, 4, 2, -, / | NULL(中缀表达式遍历完成,栈内元素全部出栈) |
运算符压栈操作:每次压栈前判断
括号不用加入队列(后缀表达式)中
例:23 - (5 + 3 * 2) * 3 + 7
栈:
队列:23, 5, 3, 2, *, +, 3, *, -, 7, +
后缀表达式求值:
队列 | 数字栈 | 运算符栈 |
---|---|---|
23, 5, 3, 2, *, +, 3, *, -, 7, + | NULL | NULL |
+, 3, *, -, 7, + | 23, 5, 3, 2 | *(数字栈出栈两个元素进行运算后再压栈) |
3, *, -, 7, + | 23, 5, 6 | + |
-, 7, + | 23, 11, 3 | * |
7, + | 23, 33 | - |
NULL | -10, 7 | + |
NULL | -3 | NULL |
概念:操作受限的表,两端操作,队列头出队,队列尾入队。FIFO,先入先出。
应用场景:
定义:
操作:
轮询:每次出队一个元素
struct Queue {
int *arr; // 数据
int front; // 队首
int rear; // 队尾
int capacity; // 容量
}
/*
* @Author: ZPY
* @TODO: 栈的定义和使用
*/
#include
#include
// 定义队列结构体
typedef struct Queue {
int *arr;
int front;
int rear;
int capacity;
} Queue;
// 初始化队列
Queue* initQueue(int capacity) {
Queue* queue = (Queue*)malloc(sizeof(Queue));
queue->capacity = capacity;
queue->front = queue->rear = -1;
queue->arr = (int*)malloc(queue->capacity * sizeof(int));
return queue;
}
// 判断队列是否为空
int isEmpty(Queue* queue) {
return queue->front == -1;
}
// 入队操作
void enqueue(Queue* queue, int data) {
if (queue->rear == queue->capacity - 1) {
printf("Queue Overflow\n");
return;
}
queue->arr[++queue->rear] = data;
if (queue->front == -1) { // 如果队列之前是空的
queue->front = 0;
}
}
// 出队操作
int dequeue(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue Underflow\n");
return INT_MIN;
}
int data = queue->arr[queue->front];
queue->front++;
if (queue->front > queue->rear) { // 如果队列变为空
queue->front = queue->rear = -1;
}
return data;
}
// 获取队列大小
int size(Queue* queue) {
if (isEmpty(queue)) {
return 0;
}
return (queue->rear - queue->front + 1);
}
int main() {
Queue* q = initQueue(5);
enqueue(q, 1);
enqueue(q, 2);
enqueue(q, 3);
printf("Size = %d\n", size(q));
printf("%d\n", dequeue(q));
printf("%d\n", dequeue(q));
printf("%d\n", dequeue(q));
printf("Size = %d\n", size(q));
return 0;
}