📚 一、栈
栈是只允许从一端进行插入和删除数据的线性存储结构 。
可以操作增删的这一端叫栈顶 ,另一端叫栈底。
- 压栈(入栈):向栈存入数据
- 出栈(弹栈):从栈取出数据
- 核心特性:先进后出(FILO)
栈的实际应用
- 解决递归回溯类问题
- 软件撤销功能、网页回退缓存
- 字符串符号匹配,判断成对符号是否匹配、有无丢失
顺序栈
顺序栈基于数组实现,分为4种状态:满增栈、空增栈、满减栈、空减栈。
|----|------------------|--------------|
| 名词 | 说明 | 判断依据 |
| 满栈 | 栈顶位置一直存有元素 | 看栈顶位置是否存有元素 |
| 空栈 | 栈顶位置一直没有元素 | 看栈顶位置是否存有元素 |
| 增栈 | 栈顶移动方向:低地址 → 高地址 | 栈顶移动方向(生长方向) |
| 减栈 | 栈顶移动方向:高地址 → 低地址 | 栈顶移动方向(生长方向) |
链式栈实战代码
底层使用单链表实现栈,链表头部作为栈顶,入栈出栈都操作表头,效率O(1)。文件分为头文件 stack.h 、实现文件 stack.c 、测试 main.c
stack.h
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #ifndef STACK_H #define STACK_H typedef int Data_t; typedef struct node { Data_t data; struct node *pnext; }Node_s; typedef struct stack { Node_s *ptop; int clen; }Stack_s; Stack_s *create_stack(); int is_empty_stack(Stack_s *pstack); int push_stack(Stack_s *pstack, Data_t data); int pop_stack(Stack_s *pstack, Data_t *pdata); int get_stack_top(Stack_s *pstack,Data_t *ptop); void show_stack(Stack_s *pstack); void clear_stack(Stack_s *pstack); void destroy_stack(Stack_s *pstack); #endif |
stack.c
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include <stdlib.h> #include "stack.h" Stack_s *create_stack() { Stack_s *pstack=malloc(sizeof(Stack_s)); if(NULL==pstack) { printf("malloc error\n"); return NULL; } pstack->ptop=NULL; pstack->clen=0; return pstack; } int is_empty_stack(Stack_s *pstack) { return NULL==pstack->ptop; } int push_stack(Stack_s *pstack, Data_t data) { Node_s *pinsert=malloc(sizeof(Node_s)); if(pinsert==NULL) { printf("malloc error\n"); return -1; } pinsert->data=data; pinsert->pnext=NULL; pinsert->pnext=pstack->ptop; pstack->ptop=pinsert; pstack->clen++; return 0; } int pop_stack(Stack_s *pstack, Data_t *pdata) { if(is_empty_stack(pstack)) { return -1; } Node_s *pfree=pstack->ptop; pstack->ptop=pfree->pnext; if(pdata!=NULL) { *pdata=pfree->data; } free(pfree); pstack->clen--; return 0; } int get_stack_top(Stack_s *pstack,Data_t *ptopdata) { if(is_empty_stack(pstack)) { return -1; } else { *ptopdata=pstack->ptop->data; return 0; } } void show_stack(Stack_s *pstack) { Node_s *ptmp=pstack->ptop; while(ptmp) { printf("%d ",ptmp->data); ptmp=ptmp->pnext; } printf("\n"); } void clear_stack(Stack_s *pstack) { while(!is_empty_stack(pstack)) { pop_stack(pstack,NULL); } } void destroy_stack(Stack_s *pstack) { clear_stack(pstack); free(pstack); } |
main.c(链式栈测试)
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include "stack.h" int main(void) { Stack_s *pstack=NULL; pstack=create_stack(); if(NULL==pstack) { return -1; } for(int i=0;i<5;++i) { push_stack(pstack, i); } show_stack(pstack); printf("clen = %d\n",pstack->clen); Data_t pdata; pop_stack(pstack, &pdata); show_stack(pstack); printf("pop : %d\n",pdata); printf("clen = %d\n",pstack->clen); Data_t ptopdata; int ret=0; ret=get_stack_top(pstack,&ptopdata); if(ret==0) { printf("top : %d\n",ptopdata); } destroy_stack(pstack); return 0; } |
代码关键点:
- ptop永远指向链表第一个节点(栈顶)
- push:新节点插在链表头部
- pop:释放栈顶节点,更新ptop
- clear_stack循环pop清空全部节点,destroy_stack先清空再释放栈管理结构体,防止内存泄漏
🌳 二、二叉树(一对多树形结构)
树:由根节点和若干子节点构成,具备一对多关系的集合。
空树:没有任何节点。
基础名词释义
- 根节点:树最顶层节点
- 叶子节点(终端节点):没有子节点,节点度为0
- 分支节点:拥有子节点的节点
- 节点的度:节点拥有的子节点数量
- 树的度(广度):树内节点的最大度
- 树的深度:描述树的层数
二叉树、满二叉树、完全二叉树
二叉树:树的度为2的树形结构,左右子节点不能互换。

小提示:删除完全二叉树节点,需要从下到上、从右往左删除。
二叉树四种遍历方式
- 前序遍历:根 → 左子树 → 右子树
- 中序遍历:左子树 → 根 → 右子树
- 后序遍历:左子树 → 右子树 → 根
- 层序遍历:从上到下,从左至右逐层访问(需要借助队列实现)
|-----------------------------------------------------------------------------|
|
|
链式二叉树完整工程代码
工程包含:tree.h、tree.c、linkque.h、linkque.c(层序遍历依赖链式队列)、main.c测试文件。
tree.h
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #ifndef tree_h #define tree_h typedef char Data_t; typedef struct tr_node { Data_t data; struct tr_node *pl; struct tr_node *pr; }Tnode_s; extern Tnode_s *create_bin_tree(); extern void pre_show_bin_tree(Tnode_s *proot); extern void mid_show_bin_tree(Tnode_s *proot); extern void tail_show_bin_tree(Tnode_s *proot); extern void floor_show_bin_tree(Tnode_s *proot); extern int get_node_cnt(Tnode_s *proot); extern int get_tree_deep(Tnode_s *proot); extern void destroy_tree(Tnode_s *proot); #endif |
linkque.h 层序遍历依赖队列头文件
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #ifndef LINKQUE_H #define LINKQUE_H #include "tree.h" typedef Tnode_s * Data_s; typedef struct node { Data_s data; struct node *pnext; }Node_s; typedef struct linkque { Node_s *phead; Node_s *ptail; int clen; }Linkque_s; extern Linkque_s *creat_linkque(); extern int is_empty_linkque(Linkque_s *pque); extern int insert_linkque_tail(Linkque_s *pque,Data_s data); extern int pop_link_queue(Linkque_s *pque, Data_s *data); extern int get_link_queue_head(Linkque_s *pque,Data_s *pdata); extern void show_linkque(Linkque_s *pque); extern void destory_linkque(Linkque_s *pque); #endif |
linkque.c 链式队列实现
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include <stdlib.h> #include "linkque.h" Linkque_s *creat_linkque() { Linkque_s *pque=malloc(sizeof(Linkque_s)); if(NULL==pque) { printf("malloc error\n"); return NULL; } pque->phead=NULL; pque->ptail=NULL; pque->clen=0; return pque; } int is_empty_linkque(Linkque_s *pque) { return NULL==pque->phead; } int insert_linkque_tail(Linkque_s *pque,Data_s data) { Node_s *pinsert=malloc(sizeof(Node_s)); if(NULL==pinsert) { printf("malloc error\n"); return -1; } pinsert->data=data; pinsert->pnext=NULL; if(is_empty_linkque(pque)) { pque->phead=pinsert; pque->ptail=pinsert; } else { pque->ptail->pnext=pinsert; pque->ptail=pinsert; } pque->clen++; return 0; } int pop_link_queue(Linkque_s *pque, Data_s *data) { if(is_empty_linkque(pque)) { return -1; } Node_s *pfree=pque->phead; pque->phead=pfree->pnext; if(data!=NULL) { *data=pfree->data; } free(pfree); if(NULL==pque->phead) { pque->ptail=NULL; } pque->clen--; return 0; } int get_link_queue_head(Linkque_s *pque,Data_s *pdata) { if(is_empty_linkque(pque)) { return -1; } *pdata=pque->phead->data; return 0; } void show_linkque(Linkque_s *pque) { if(is_empty_linkque(pque)) { return ; } Node_s *ptmp=pque->phead; while (NULL!=ptmp) { ptmp=ptmp->pnext; } printf("\n"); } void destory_linkque(Linkque_s *pque) { while(!is_empty_linkque(pque)) { pop_link_queue(pque,NULL); } free(pque); return ; } |
tree.c 二叉树核心逻辑
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include <stdlib.h> #include "tree.h" #include "linkque.h" Data_t tree\[\]="ABF##GC###DH#I##E##"; int idx=0; Tnode_s *create_bin_tree() { Data_t data=treeidx++; if('#' == data) { return NULL; } Tnode_s *ptnode=malloc(sizeof(Tnode_s)); if(NULL == ptnode) { printf("malloc error\n"); return NULL; } ptnode->data=data; ptnode->pl=create_bin_tree(); ptnode->pr=create_bin_tree(); return ptnode; } void pre_show_bin_tree(Tnode_s *proot) { if(NULL == proot) { return ; } printf("%c",proot->data); pre_show_bin_tree(proot->pl); pre_show_bin_tree(proot->pr); } void mid_show_bin_tree(Tnode_s *proot) { if(NULL == proot) { return ; } mid_show_bin_tree(proot->pl); printf("%c",proot->data); mid_show_bin_tree(proot->pr); } void tail_show_bin_tree(Tnode_s *proot) { if(NULL == proot) { return ; } tail_show_bin_tree(proot->pl); tail_show_bin_tree(proot->pr); printf("%c",proot->data); } void floor_show_bin_tree(Tnode_s *proot) { Linkque_s *plque=NULL; plque=creat_linkque(); if(NULL == plque) { return ; } Data_s outdata; insert_linkque_tail(plque,proot); while (!is_empty_linkque(plque)) { pop_link_queue(plque,&outdata); printf("%c", outdata->data); if (outdata->pl != NULL) { insert_linkque_tail(plque, outdata->pl); } if (outdata->pr != NULL) { insert_linkque_tail(plque, outdata->pr); } } destory_linkque(plque); } int get_node_cnt(Tnode_s *proot) { if(NULL == proot) { return 0; } return 1+get_node_cnt(proot->pl)+get_node_cnt(proot->pr); } int get_tree_deep(Tnode_s *proot) { if(NULL == proot) { return 0; } int left_floor=get_tree_deep(proot->pl); int right_floor=get_tree_deep(proot->pr); return left_floor>right_floor?left_floor+1:right_floor+1; } void destroy_tree(Tnode_s *proot) { if(NULL == proot) { return ; } destroy_tree(proot->pl); destroy_tree(proot->pr); free(proot); } |
main.c二叉树测试代码
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include "tree.h" #include "linkque.h" int main(void) { Tnode_s *ptnode=NULL; ptnode=create_bin_tree(); if(ptnode == NULL) { return -1; } pre_show_bin_tree(ptnode); printf("\n"); mid_show_bin_tree(ptnode); printf("\n"); tail_show_bin_tree(ptnode); printf("\n"); floor_show_bin_tree(ptnode); printf("\n"); int node_cnt=0; node_cnt=get_node_cnt(ptnode); printf("node_cnt = %d\n",node_cnt); int tree_deep=0; tree_deep=get_tree_deep(ptnode); printf("tree_deep = %d\n",tree_deep); destroy_tree(ptnode); return 0; } |
代码关键点总结
- create_bin_tree:使用带#标记的前序字符串递归创建二叉树,#代表空节点
- 前、中、后序全部依靠递归实现;层序遍历必须借助队列,先入队根节点,出队打印,再依次入队左右孩子
- get_node_cnt:递归,当前节点+左子树节点数+右子树节点数
- get_tree_deep:取左右子树深度较大值+1
- destroy_tree后序释放,先释放左右子树,再释放自己,杜绝内存泄漏
🔑 三、哈希存储
哈希存储:在数据关键字与存储位置之间建立映射关系,依据映射快速存、取数据。这个映射函数叫做哈希函数(散列函数)。
核心目的:实现快速检索数据。
流程:数据key → 哈希函数 → 得到存储位置。
常见哈希函数
- 求余法:f(key) = key % 10
- 一次函数法:f(key) = a*key + b
哈希冲突
条件: key1≠key2 ,但是 f(key1)=f(key2)
不同关键字,经过哈希计算得到同一个存储位置,就产生哈希冲突。
解决哈希冲突两种方案
- 开放定址法:哈希表本身提供连续内存空间,冲突就向后寻找下一个空位。
- 链地址法:数组每一个位置挂载一条链表,冲突的数据挂接到同一个链表上。本次代码就是链地址法实现哈希表。
链地址法哈希表实战代码
功能:存储人名和电话号码,以名字首字符作为key计算哈希地址,冲突则挂在对应位置的单链表。文件包含 hash.h 、 hash.c 、 main.c 。
hash.h
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #ifndef HASH_H #define HASH_H #define HASH_SIZE 27 typedef struct per { char name32; char tel16; }Data_s; typedef struct node { Data_s data; struct node *pnext; }Node_s; extern int hash_function(char key); extern int insert_hash_table(Node_s **hash_table,Data_s data); extern void show_hash_table(Node_s **hash_table); #endif |
hash.c
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include <stdlib.h> #include "hash.h" int hash_function(char key) { if('a' <= key && key <= 'z') { return key-'a'; } else if('A' <= key && key <= 'Z') { return key-'A'; } else { return HASH_SIZE-1; } } int insert_hash_table(Node_s **hash_table,Data_s data) { int addr = hash_function(data.name0); Node_s *pnode = malloc(sizeof(Node_s)); if(NULL == pnode) { printf("malloc error\n"); return -1; } pnode->data=data; pnode->pnext=NULL; pnode->pnext=hash_tableaddr; hash_tableaddr=pnode; return 0; } void show_hash_table(Node_s **hash_table) { for(int i=0;i<HASH_SIZE;++i) { if(hash_tablei != NULL) { Node_s *pnode = malloc(sizeof(Node_s)); if(NULL == pnode) { printf("malloc error\n"); return ; } pnode=hash_tablei; while(pnode != NULL) { printf("%s %s\n",pnode->data.name,pnode->data.tel); pnode=pnode->pnext; } } } } |
main.c(哈希表测试)
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| C #include <stdio.h> #include "hash.h" int main(void) { Node_s *hash_tableHASH_SIZE={NULL}; Data_s per5={{"zhangsan","110"},{"lisi","120"}, {"wanger","119"},{"zhaowu","114"}, {"tianqi","100"}}; for(int i=0;i<5;++i) { insert_hash_table(hash_table,peri); } show_hash_table(hash_table); return 0; } |
哈希代码关键点
- hash_function:取名字首字母,小写a‑z映射0‑25,非字母放到下标26位置;
- 插入采用头插法,新节点直接挂载到数组对应下标链表头部;
- 链地址法:哈希地址相同的全部挂在同一条链表,以此解决哈希冲突;
- 注意:当前代码缺少哈希表销毁释放逻辑,实际项目中需要补充遍历释放每一个链表节点,避免内存泄漏。
✍️今日学习小结
- 栈记住核心规则:先进后出。手写链式栈,熟悉创建、入栈、出栈、取栈顶、清空销毁整套接口,注意内存释放,防止内存泄漏。
- 二叉树分清满二叉树、完全二叉树概念;前中后序递归遍历,层序遍历需要队列辅助;递归求节点总数、树深度;递归字符串生成二叉树、后序销毁树,笔试常考遍历序列还原树。
- 哈希存储重点理解哈希函数、哈希冲突,两种冲突解决方法是考试高频考点;完成链地址法哈希表代码实现,理解头插法挂载冲突节点。
今日踩坑点:
①忘记销毁栈/二叉树/队列,出现内存泄漏;
②层序遍历队列用完没有销毁;
③二叉树递归创建的时候索引全局变量容易出错;
④本次哈希示例代码没有写释放接口,实际开发必须手动释放链表节点。