学了 C 语言语法,一碰到数据结构就懵?想用数组存数据,结果数据太多存不下;想删个中间的元素,数组得挪半天;更别说处理括号匹配、消息排队这些问题,根本不知道用啥结构 —— 你是不是也觉得数据结构比纯语法难多了?其实啊,链表、栈、队列这三个 “老熟人”,学会了能解决大半问题。它们就像不同的容器,各有各的用法,用对了能省老多事。兔子哥当年学这些的时候,对着指针绕了好几天,后来结合实例才搞明白,今天就用大白话讲讲怎么用 C 语言实现,再附个实战项目,一起往下看吧!
一、为啥非得学这些结构?数组不够用吗?
可能有人会说,我用数组挺好的,为啥还要学链表、栈、队列?这就得说说数组的 “短板” 了。
数组得先确定大小,比如
int a[100],最多存 100 个数据,万一不够了,改起来特别麻烦;想在中间插个数据,得把后面的全往后挪,费时费力。而链表、栈、队列这些结构,就灵活多了 —— 链表能随便加数据,栈和队列能按特定顺序处理数据,各有各的神通。就像装东西,数组是固定大小的箱子,满了就塞不下;链表是能无限接长的链条,想加多少加多少;栈是只能从顶上放、顶上拿的抽屉;队列是排队买票,先来的先处理。不同场景用不同的 “容器”,这就是数据结构的门道。
二、链表:像串珠子一样存数据,增删改特别方便
链表是最灵活的,由一个个 “节点” 串起来,每个节点分两部分:存数据的 “数据域”,和指向下一个节点的 “指针域”。
1. 先定义节点结构,这是基础
c运行
// 定义一个存整数的链表节点struct Node {int data; // 数据域,存具体数值struct Node *next; // 指针域,指向下一个节点};就像一颗珠子,data 是珠子里的东西,next 是穿珠子的线,指向后面那颗珠子。
2. 怎么往链表加数据?头插法最简单
头插法就是往链表的最前面加节点,步骤大概是:
- 新造一个节点,给 data 赋值;
- 让新节点的 next 指向原来的头节点;
- 把头节点换成新节点。
代码大概这样(带注释):
c运行
// 头插法添加节点struct Node* addHead(struct Node *head, int num) {// 1. 申请一块内存给新节点struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));if(newNode == NULL) { // 万一内存不够,得处理下printf("内存不够啦");return head;}// 2. 给新节点存数据newNode->data = num;// 3. 新节点的next指向原来的头newNode->next = head;// 4. 头变成新节点head = newNode;return head;}刚开始写的时候,总搞反 newNode->next 和 head 的关系,画个图就清楚了 —— 新节点插在最前面,自然要指向原来的头。
3. 删节点、查节点,关键是找对位置
删节点得先找到要删的节点的前一个,让前一个的 next 跳过要删的,直接指向下一个;查节点就从头往后遍历,一个个比 data。这些操作看着麻烦,多练两次就顺了。
三、栈:只能从 “栈顶” 进出,像叠盘子一样
栈有个特点:先进后出,就像叠盘子,最后放的那个得先拿下来。
1. 用数组实现栈,简单好懂
定义一个数组存数据,再用个变量 top 记着栈顶位置:
c运行
#define MAX_SIZE 100int stack[MAX_SIZE];int top = -1; // 栈顶索引,-1表示空栈2. 入栈和出栈,就俩核心操作
- 入栈:top 加 1,然后把数据放进去,注意别超过 MAX_SIZE;
c运行
void push(int data) {if(top >= MAX_SIZE - 1) { // 栈满了printf("栈满啦,放不下");return;}stack[++top] = data; // 先加top,再存数据}- 出栈:先拿 top 位置的数据,再把 top 减 1;
c运行
int pop() {if(top == -1) { // 栈空了printf("栈空啦,没数据");return -1; // 用-1表示出错}return stack[top--]; // 先拿数据,再减top}当年我总搞反 push 里的
++top和top++,后来发现++top是先移栈顶再存,才对,不然第一个数据会存在索引 0 的位置。四、队列:先进先出,像排队买票
队列跟栈正好相反,先进先出,就像排队,先来的先买票。
1. 用数组实现队列,得记头和尾
需要两个变量:front(队头,拿数据的地方)和 rear(队尾,放数据的地方):
c运行
#define MAX_QUEUE 100int queue[MAX_QUEUE];int front = 0;int rear = 0;2. 入队和出队,注意 “循环” 问题
- 入队:数据放 rear 位置,rear 加 1,超过最大长度就绕回 0(循环队列);
c运行
void enqueue(int data) {int nextRear = (rear + 1) % MAX_QUEUE;if(nextRear == front) { // 队满了printf("队满啦,进不来");return;}queue[rear] = data;rear = nextRear;}- 出队:拿 front 位置的数据,front 加 1,同样绕回 0;
c运行
int dequeue() {if(front == rear) { // 队空了printf("队空啦,没数据");return -1;}int data = queue[front];front = (front + 1) % MAX_QUEUE;return data;}循环队列刚开始理解起来有点绕,其实就是让队列 “首尾相连”,不浪费空间,画个圈就能看明白。
| 数据结构 | 特点 | 最适合的场景 | C 语言实现难度 |
|---|---|---|---|
| 链表 | 可动态增减,灵活 | 数据量不确定,常增删 | 稍难(指针多) |
| 栈 | 先进后出 | 括号匹配、函数调用、撤销操作 | 简单 |
| 队列 | 先进先出 | 消息处理、排队系统 | 中等 |
五、实战项目:简易计算器(用栈处理表达式)
用栈来做个能算加减乘除的计算器,核心是用栈存数字和运算符,处理运算顺序。
1. 思路:
- 遇到数字直接入栈;
- 遇到运算符,跟栈顶的运算符比优先级,优先级高就入栈,低就把栈顶的运算符弹出来算;
- 最后把栈里剩下的运算符弹出来算。
2. 核心代码片段(处理运算符部分):
c运行
// 简化版:比较运算符优先级int priority(char op) {if(op == '+' || op == '-') return 1;if(op == '*' || op == '/') return 2;return 0;}// 处理运算符入栈void handleOp(char op, int *numStack, int *numTop, char *opStack, int *opTop) {// 栈顶运算符优先级高,先算while(*opTop != -1 && priority(opStack[*opTop]) >= priority(op)) {char topOp = opStack[(*opTop)--]; // 弹出运算符int b = numStack[(*numTop)--]; // 弹出两个数字int a = numStack[(*numTop)--];int res;if(topOp == '+') res = a + b;else if(topOp == '-') res = a - b;else if(topOp == '*') res = a * b;else res = a / b;numStack[++(*numTop)] = res; // 结果入栈}opStack[++(*opTop)] = op; // 当前运算符入栈}这个项目能把栈的用法练透,刚开始可能算不对,多调几次就好了,我当年改了五遍才让它正确算 “3+5*2” 这种带优先级的表达式。
可能有人会问,这些结构在实际项目中用得多吗?太多了!操作系统里的进程调度用队列,编译器查括号用栈,链表更是到处都是。
兔子哥觉得,学这些别光看代码,得多画图 —— 链表怎么串的,栈怎么进出的,一画就明白。刚开始写指针总出错很正常,我当年写链表删除节点,总忘了释放内存,导致内存泄漏,后来才记住 free () 函数。多敲代码,从简单的增删改查做起,慢慢就找到感觉了。希望这些能帮到你,赶紧动手试试吧!
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。
还木有评论哦,快来抢沙发吧~