目录
第一章 数据结构绪论
1.1 数据结构的基本概念
1.2 数据结构的三要素
1.2.1. 数据的逻辑结构
1.2.2. 数据的存储结构(物理结构)
1.2.3. 数据的运算
1.2.4. 数据类型和抽线数据类型
1.3 算法的基本概念
1.4 算法的时间复杂度
1.5 算法的空间复杂度
第二章 线性表
2.1 线性表的定义和基本操作
2.1.1 线性表的定义
2.1.2 线性表的基础操作
2.2 顺序表
2.2.1 顺序表的概念
2.2.2. 顺序表的实现编辑
2.2.3 顺序表的基本操作
2.3 线性表的链式表示
2.3.1. 单链表的基本概念
2.3.2. 单链表的实现
2.3.3. 单链表的插入
2.3.4. 单链表的删除
2.3.5. 单链表的查找
2.3.6. 单链表的建立
2.3.7. 双链表
2.3.8. 循环链表
2.3.9. 静态链表
2.3.10. 顺序表和链表的比较
第三章 栈和队列
3.1. 栈
3.1.1. 栈的基本概念
3.1.2. 栈的基本操作
3.1.3. 栈的顺序存储实现
3.1.4. 栈的链式存储
3.2. 队列
3.2.1. 队列的基本概念
3.2.2. 队列的基本操作
3.2.3. 队列的顺序存储实现
3.2.4. 队列的链式存储实现
3.2.5. 双端队列
3.3. 栈与队列的应用
3.3.1 栈在括号匹配中的应用
3.3.2. 栈在表达式求值中的应用
3.3.3. 栈在递归中的应用
3.3.4. 队列的应用
3.4. 特殊矩阵的压缩存储
3.4.1 数组的存储
3.4.2 对称矩阵的压缩存储
3.4.3 三角矩阵的压缩存储
3.4.4 三对角矩阵的压缩存储
3.4.5 稀疏矩阵的压缩存储
第四章 串
4.1. 串的基本概念
4.2. 串的基本操作
4.3. 串的存储实现
4.3.1 静态数组实现
4.3.2 基本操作的实现
4.4. 串的朴素模式匹配
4.5. KPM算法
第五章 图
5.1. 树的概念
5.1.1. 树的基本定义
5.1.2. 树的常考性质
5.2. 二叉树
5.2.1. 二叉树的定义
5.2.2. 特殊二叉树
5.2.3. 二叉树的性质
5.2.4. 二叉树存储实现
5.3. 二叉树的遍历和线索二叉树
5.3.1. 二叉树的先中后序遍历
5.3.2. 二叉树的层序遍历
5.3.3. 由遍历序列构造二叉树
5.3.4. 线索二叉树的概念
5.3.5. 二叉树的线索化
5.3.6. 在线索二叉树中找前驱/后继
5.4. 树和森林
5.4.1. 树的存储结构
5.4.2. 树和森林的遍历
5.5. 应用
5.5.1. 二叉排序树
5.5.2. 平衡二叉树
5.5.3. 哈夫曼树
第六章 图
6.1. 图的基本概念
6.2. 图的存储
6.2.1. 邻接矩阵
6.2.2. 邻接表
6.2.3. 十字链表、临接多重表
6.2.4. 图的基本操作
6.3. 图的遍历
6.3.1. 广度优先遍历
6.3.2. 深度优先遍历
6.4. 图的应用
6.4.1. 最小生成树
6.4.2. 无权图的单源最短路径问题——BFS算法
6.4.3. 单源最短路径问题——Dijkstra算法
6.4.4. 各顶点间的最短路径问题——Floyd算法
6.4.5. 有向⽆环图描述表达式
6.4.6. 拓扑排序
6.4.7. 关键路径
第七章 查找
7.1 查找概念
7.2 顺序查找
7.3 折半查找
7.4 分块查找
7.5 红黑树
7.5.1 为什么要发明红黑树?
7.5.2 红黑树的定义
7.5.3 红黑树的插入
7.6 B树和B+树
7.6.1 B树
7.6.2 B树的基本操作
7.6.3 B+树
7.6.4 B树和B+树的比较
7.7 散列查找及其性能分析
7.7.1 散列表的基本概念
7.7.2 散列查找及性能分析
第八章 排序
8.1. 排序的基本概念
8.2. 插入排序
8.2.1. 直接插入排序
8.2.2. 折半插入排序
8.2.3. 希尔排序
8.3. 交换排序
8.3.1. 冒泡排序
8.3.2. 快速排序
8.4. 选择排序
8.4.1. 简单选择排序
8.4.2. 堆排序
8.5. 归并排序
8.6. 基数排序
8.7. 内部排序算法总结
8.7.1. 内部排序算法比较
8.7.2. 内部排序算法的应用
8.8. 外部排序
8.8.1. 外部排序的基本概念和方法
8.8.2. 败者树
8.8.3. 置换-选择排序(生成初始归并段)
8.8.4. 最佳归并树


举例需要理解几点:
逻辑结构是指数据元素之间的逻辑关系,即从逻辑关系上描述数据。
逻辑结构包括:
如何用计算机表示数据元素的逻关系?
存储结构是指数据结构在计算机中的表示(又称映像),也称物理结构。
存储结构包括:
需要理解几点:
针对于某种逻辑结构,结合实际需求,定义基本运算。
例如:逻辑结构->线性结构
基本运算: 1.查找第i个数据元素 2.在第i个位置插入新的数据元素 3.删除第i个位置的数据元素...... 数据类型是一个值的集合和定义在此集合上的一组操作的总称。例如:定义int整形,我们就可以把他们加减乘除等操作。
抽象数据类型(Abstract Data Type,ADT)是抽象数据组织及与之相关的操作。ADT 用数学化的语言定义数据的逻辑结构、定义运算。与具体的实现无关。
在探讨一种数据结构时理解几点:

程序 = 数据结构+算法
数据结构:如何用数据正确地描述现实世界的问题,并存入计算机。
算法:如何高效地处理这些数据,以解决实际问题
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。
算法的特性:
我们可以类比:y = f(x)函数,其中x就是输出,y就是输出,这个函数就是算法。
好的算法达到的目标:





什么时候要传入参数的引用“&“-- 对参数的修改结果需要“带回来”看下面举例:
#include void test(int x) //形参是实参的临时拷贝 { x = 1024; printf("test函数内部 x=%d\n",x); } int main() { int x = 1; printf("调用test前 x=%d\n",x); test(x); //这里的x改变了并没有传回来 printf("调用test后 x=%d\n",x); return 0; } //输出为: //调用test前 x=1 //test函数内部 x=1024 //调用test后 x=1 //请按任意键继续. . . #include void test(int &x) //把x的地址传到函数 { x = 1024; printf("test函数内部 x=%d\n",x); } int main() { int x = 1; printf("调用test前 x=%d\n",x); test(x); //这里的x通过函数传回来值改变了 printf("调用test后 x=%d\n",x); return 0; } //输出为: //调用test前 x=1 //test函数内部 x=1024 //调用test后 x=1024 //请按任意键继续. . . 
我们看完线性表的逻辑结构和基本运算,现在继续学习物理结构:顺序表



//顺序表的实现--静态分配 #include #define MaxSize 10 //定义表的最大长度 typedef struct{ int data[MaxSize]; //用静态的"数组"存放数据元素 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义(静态分配方式) void InitList(SqList &L){ for(int i=0;i //顺序表的实现——动态分配 #include #include //malloc、free函数的头文件 #define InitSize 10 //默认的初始值 typedef struct{ int *data; //指示动态分配数组的指针 int MaxSize; //顺序表的最大容量 int length; //顺序表的当前长度 }SeqList; void InitList(SeqList &L){ //初始化 //用malloc 函数申请一片连续的存储空间 L.data =(int*)malloc(InitSize*sizeof(int)) ; L.length=0; L.MaxSize=InitSize; } void IncreaseSize(SeqList &L,int len){ //增加动态数组的长度 int *p=L.data; L.data=(int*)malloc((L.MaxSize+len)*sizeof(int)); for(int i=0;i 
#define MaxSize 10 //定义最大长度 typedef struct{ int data[MaxSize]; //用静态的数组存放数据 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义 bool ListInsert(SqList &L, int i, int e){ if(i<1||i>L.length+1) //判断i的范围是否有效 return false; if(L.length>=MaxSize) //当前存储空间已满,不能插入 return false; for(int j=L.length; j>=i; j--){ //将第i个元素及其之后的元素后移 L.data[j]=L.data[j-1]; } L.data[i-1]=e; //在位置i处放入e L.length++; //长度加1 return true; } int main(){ SqList L; //声明一个顺序表 InitList(L);//初始化顺序表 //...此处省略一些代码;插入几个元素 ListInsert(L,3,3); //再顺序表L的第三行插入3 return 0; } #define MaxSize 10 typedef struct { int data[MaxSize]; int length; } SqList; // 删除顺序表i位置的数据并存入e bool ListDelete(SqList &L, int i, int &e) { if (i < 1 || i > L.length) // 判断i的范围是否有效 return false; e = L.data[i-1]; // 将被删除的元素赋值给e for (int j = i; j < L.length; j++) //将第i个位置后的元素前移 L.data[j-1] = L.data[j]; L.length--; return true; } int main() { SqList L; InitList(L); int e = -1; if (ListDelete(L, 3, e)) printf("已删除第3个元素,删除元素值为%d\n", e); else printf("位序i不合法,删除失败\n"); return 0; } 
// 静态分配的按位查找 #define MaxSize 10 typedef struct { ElemType data[MaxSize]; int length; }SqList; ElemType GetElem(SqList L, int i) { return L.data[i-1]; } // 动态分配的按位查找 #define InitSize 10 typedef struct { ElemType *data; int MaxSize; int length; }SeqList; ElemType GetElem(SeqList L, int i) { return L.data[i-1]; } #define InitSize 10 //定义最大长度 typedef struct{ ElemTyp *data; //用静态的“数组”存放数据元素 int Length; //顺序表的当前长度 }SqList; //在顺序表L中查找第一个元素值等于e的元素,并返回其位序 int LocateElem(SqList L, ElemType e){ for(int i=0; i 
以上我们看完顺序表的物理存储了,然后我们学习单链表

typedef struct LNode { //定义单链表结点类型 ElemType data; //数据域 struct LNode *next;//指针域 }LNode, *LinkList; typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; //初始化一个空的单链表 bool InitList(LinkList &L){ L = NULL; //空表,暂时还没有任何结点 return true; } void test(){ LinkList L; //声明一个指向单链表的头指针 //初始化一个空表 InitList(L); ... } //判断单链表是否为空 bool Empty(LinkList L){ return (L==NULL) } typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; //初始化一个单链表(带头结点) bool InitList(LinkList &L) { L = (LNode*) malloc(sizeof(LNode)); //头指针指向的结点——分配一个头结点(不存储数据) if (L == NULL) //内存不足,分配失败 return false; L -> next = NULL; //头结点之后暂时还没有结点 return true; } void test() { LinkList L; //声明一个指向单链表的指针: 头指针 //初始化一个空表 InitList(L); //... } //判断单链表是否为空(带头结点) bool Empty(LinkList L) { if (L->next == NULL) return true; else return false; } 带头结点和不带头结点的比较:

typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; //在第i个位置插入元素e(带头结点) bool ListInsert(LinkList &L, int i, ElemType e) { //判断i的合法性, i是位序号(从1开始) if(i<1) return False; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && jlengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if (p==NULL) //如果p指针知道最后再往后就是NULL return false; //在第i-1个结点后插入新结点 LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点 s->data = e; s->next = p->next; p->next = s; //将结点s连到p后,后两步千万不能颠倒qwq return true; } typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; bool ListInsert(LinkList &L, int i, ElemType e) { if(i<1) return false; //插入到第1个位置时的操作有所不同! if(i==1){ LNode *s = (LNode *)malloc(size of(LNode)); s->data =e; s->next =L; L=s; //头指针指向新结点 return true; } //i>1的情况与带头结点一样!唯一区别是j的初始值为1 LNode *p; //指针p指向当前扫描到的结点 int j=1; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && jlengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if (p==NULL) //i值不合法 return false; //在第i-1个结点后插入新结点 LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个结点 s->data = e; s->next = p->next; p->next = s; return true; } typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; bool InsertNextNode(LNode *p, ElemType e) { if(p==NULL){ return false; } LNode *s = (LNode *)malloc(sizeof(LNode)); //某些情况下分配失败,比如内存不足 if(s==NULL) return false; s->data = e; //用结点s保存数据元素e s->next = p->next; p->next = s; //将结点s连到p之后 return true; } //平均时间复杂度 = O(1) //有了后插操作,那么在第i个位置上插入指定元素e的代码可以改成: bool ListInsert(LinkList &L, int i, ElemType e) { if(i<1) return False; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && jlengh, p最后4鸟会等于NULL p = p->next; //p指向下一个结点 j++; } return InsertNextNode(p, e) } //前插操作:在p结点之前插入元素e bool InsertPriorNode(LNode *p, ElenType e){ if(p==NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); if(s==NULL) //内存分配失败 return false; //重点来了! s->next = p->next; p->next = s; //新结点s连到p之后 s->data = p->data; //将p中元素复制到s p->data = e; //p中元素覆盖为e return true; } typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; bool ListDelete(LinkList &L, int i, ElenType &e){ if(i<1) return false; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //循环找到第i-1个结点 while(p!=NULL && jlengh, p最后会等于NULL p = p->next; //p指向下一个结点 j++; } if(p==NULL) return false; if(p->next == NULL) //第i-1个结点之后已无其他结点 return false; LNode *q = p->next; //令q指向被删除的结点 e = q->data; //用e返回被删除元素的值 p->next = q->next; //将*q结点从链中“断开” free(q) //释放结点的存储空间 return true; } bool DeleteNode(LNode *p){ if(p==NULL) return false; LNode *q = p->next; //令q指向*p的后继结点 p->data = p->next->data; //让p和后继结点交换数据域 p->next = q->next; //将*q结点从链中“断开” free(q); return true; } //时间复杂度 = O(1) 
LNode * GetElem(LinkList L, int i){ if(i<0) return NULL; LNode *p; //指针p指向当前扫描到的结点 int j=0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) while(p!=NULL && jnext; j++; } return p; //返回p指针指向的值 } LNode * LocateElem(LinkList L, ElemType e){ LNode *P = L->next; //p指向第一个结点 //从第一个结点开始查找数据域为e的结点 while(p!=NULL && p->data != e){ p = p->next; } return p; //找到后返回该结点指针,否则返回NULL } int Length(LinkList L){ int len=0; //统计表长 LNode *p = L; while(p->next != NULL){ p = p->next; len++; } return len; } 
// 使用尾插法建立单链表L LinkList List_TailInsert(LinkList &L){ int x; //设ElemType为整型int L = (LinkList)malloc(sizeof(LNode)); //建立头结点(初始化空表) LNode *s, *r = L; //r为表尾指针 scanf("%d", &x); //输入要插入的结点的值 while(x!=9999){ //输入9999表示结束 s = (LNode *)malloc(sizeof(LNode)); s->data = x; r->next = s; r = s; //r指针指向新的表尾结点 scanf("%d", &x); } r->next = NULL; //尾结点指针置空 return L; } LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表 LNode *s; int x; L = (LinkList)malloc(sizeof(LNode)); //建立头结点 L->next = NULL; //初始为空链表,这步不能少! scanf("%d", &x); //输入要插入的结点的值 while(x!=9999){ //输入9999表结束 s = (LNode *)malloc(sizeof(LNode)); //创建新结点 s->data = x; s->next = L->next; L->next = s; //将新结点插入表中,L为头指针 scanf("%d", &x); } return L; } LNode *Inverse(LNode *L) { LNode *p, *q; p = L->next; //p指针指向第一个结点 L->next = NULL; //头结点指向NULL while (p != NULL){ q = p; p = p->next; q->next = L->next; L->next = q; } return L; 
typedef struct DNode{ //定义双链表结点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针 }DNode, *DLinklist; typedef struct DNode{ //定义双链表结点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针 }DNode, *DLinklist; //初始化双链表 bool InitDLinkList(Dlinklist &L){ L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->prior = NULL; //头结点的prior指针永远指向NULL L->next = NULL; //头结点之后暂时还没有结点 return true; } void testDLinkList(){ //初始化双链表 DLinklist L; // 定义指向头结点的指针L InitDLinkList(L); //申请一片空间用于存放头结点,指针L指向这个头结点 //... } //判断双链表是否为空 bool Empty(DLinklist L){ if(L->next == NULL) //判断头结点的next指针是否为空 return true; else return false; } bool InsertNextDNode(DNode *p, DNode *s){ //将结点 *s 插入到结点 *p之后 if(p==NULL || s==NULL) //非法参数 return false; s->next = p->next; if (p->next != NULL) //p不是最后一个结点=p有后继结点 p->next->prior = s; s->prior = p; p->next = s; return true; } //删除p结点的后继结点 bool DeletNextDNode(DNode *p){ if(p==NULL) return false; DNode *q =p->next; //找到p的后继结点q if(q==NULL) return false; //p没有后继结点; p->next = q->next; if(q->next != NULL) //q结点不是最后一个结点 q->next->prior=p; free(q); return true; } //销毁一个双链表 bool DestoryList(DLinklist &L){ //循环释放各个数据结点 while(L->next != NULL){ DeletNextDNode(L); //删除头结点的后继结点 free(L); //释放头结点 L=NULL; //头指针指向NULL } } while(p!=NULL){ //对结点p做相应处理,eg打印 p = p->prior; } 后向遍历
while(p!=NULL){ //对结点p做相应处理,eg打印 p = p->next; } 注意:双链表不可随机存取,按位查找和按值查找操作都只能用遍历的方式实现,时间复杂度为

typedef struct LNode{ ElemType data; struct LNode *next; }DNode, *Linklist; /初始化一个循环单链表 bool InitList(LinkList &L){ L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->next = L; //头结点next指针指向头结点 return true; } //判断循环单链表是否为空(终止条件为p或p->next是否等于头指针) bool Empty(LinkList L){ if(L->next == L) return true; //为空 else return false; } //判断结点p是否为循环单链表的表尾结点 bool isTail(LinkList L, LNode *p){ if(p->next == L) return true; else return false; } 单链表和循环单链表的比较:单链表:从一个结点出发只能找到该结点后续的各个结点;对链表的操作大多都在头部或者尾部;设立头指针,从头结点找到尾部的时间复杂度=O(n),即对表尾进行操作需要O(n)的时间复杂度;
循环单链表:从一个结点出发,可以找到其他任何一个结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾进行操作都只需要O(1)的时间复杂度;
循环单链表优点:从表中任一节点出发均可找到表中其他结点。
typedef struct DNode{ ElemType data; struct DNode *prior, *next; }DNode, *DLinklist; //初始化空的循环双链表 bool InitDLinkList(DLinklist &L){ L = (DNode *) malloc(sizeof(DNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->prior = L; //头结点的prior指向头结点 L->next = L; //头结点的next指向头结点 } void testDLinkList(){ //初始化循环单链表 DLinklist L; InitDLinkList(L); //... } //判断循环双链表是否为空 bool Empty(DLinklist L){ if(L->next == L) return true; else return false; } //判断结点p是否为循环双链表的表尾结点 bool isTail(DLinklist L, DNode *p){ if(p->next == L) return true; else return false; } bool InsertNextDNode(DNode *p, DNode *s){ s->next = p->next; p->next->prior = s; s->prior = p; p->next = s; //删除p的后继结点q p->next = q->next; q->next->prior = p; free(q); 
单链表:各个结点散落在内存中的各个角落,每个结点有指向下一个节点的指针(下一个结点在内存中的地址);
静态链表:用数组的方式来描述线性表的链式存储结构: 分配一整片连续的内存空间,各个结点集中安置,包括了——数据元素and下一个结点的数组下标(游标)
#define MaxSize 10 //静态链表的最大长度 struct Node{ //静态链表结构类型的定义 ElemType data; //存储数据元素 int next; //下一个元素的数组下标(游标) }; //用数组定义多个连续存放的结点 void testSLinkList(){ struct Node a[MaxSize]; //数组a作为静态链表, 每一个数组元素的类型都是struct Node //... } 或者是:
#define MaxSize 10 //静态链表的最大长度 typedef struct{ //静态链表结构类型的定义 ELemType data; //存储数据元素 int next; //下一个元素的数组下标 }SLinkList[MaxSize]; void testSLinkList(){ SLinkList a; } 相当于:
#define MaxSize 10 //静态链表的最大长度 struct Node{ //静态链表结构类型的定义 ElemType data; //存储数据元素 int next; //下一个元素的数组下标(游标) }; typedef struct Node SLinkList[MaxSize]; //重命名struct Node,用SLinkList定义“一个长度为MaxSize的Node型数组; 【逻辑结构】
【存储结构】
顺序表:顺序存储
链表:链式存储
【基本操作 - 创建】
【基本操作 - 销毁】
【基本操作-增/删】
顺序表:插入/删除元素要将后续元素后移/前移;时间复杂度=O(n),时间开销主要来自于移动元素;
链表:插入/删除元素只需要修改指针;时间复杂度=O(n),时间开销主要来自查找目标元素
【基本操作-查】
顺序表
链表
顺序、链式、静态、动态四种存储方式的比较
顺序存储的固有特点:
链式存储的固有特点:
静态存储的固有特点:
动态存储的固有特点:



【顺序栈的定义】
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //静态数组存放栈中元素 int top; //栈顶元素 }SqStack; void testStack(){ SqStack S; //声明一个顺序栈(分配空间) //连续的存储空间大小为 MaxSize*sizeof(ElemType) } 【顺序栈的初始化】
#define MaxSize 10 typedef struct{ ElemType data[MaxSize]; int top; }SqStack; // 初始化栈 void InitStack(SqStack &S){ S.top = -1; //初始化栈顶指针 } // 判断栈是否为空 bool StackEmpty(SqStack S){ if(S.top == -1) return true; else return false; } 【顺序栈的入栈出栈】
// 新元素进栈 bool Push(SqStack &S, ElemType x){ // 判断栈是否已满 if(S.top == MaxSize - 1) return false; S.data[++S.top] = x; return true; } // 出栈 bool Pop(SqStack &x, ElemType &x){ // 判断栈是否为空 if(S.top == -1) return false; x = S.data[S.top--]; return true; } 【读取栈顶元素】
// 读栈顶元素 bool GetTop(SqStack S, ElemType &x){ if(S.top == -1) return false; x = S.data[S.top]; return true; } S.data[++S.top] = x【共享栈(两个栈共享同一片空间)】
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //静态数组存放栈中元素 int top0; //0号栈栈顶指针 int top1; //1号栈栈顶指针 }ShStack; //初始化栈 void InitSqStack(ShStack &S){ S.top0 = -1; //初始化栈顶指针 S.top1 = MaxSize; } 【链栈的定义】
链表的头部作为栈顶,意味着:
因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;
栈的链式存储结构可描述为:
【链栈的定义】
typedef struct Linknode{ ElemType data; //数据域 Linknode *next; //指针域 }Linknode,*LiStack; void testStack(){ LiStack L; //声明一个链栈 } 【链栈的初始化】
typedef struct Linknode{ ElemType data; Linknode *next; }Linknode,*LiStack; // 初始化栈 bool InitStack(LiStack &L){ L = (Linknode *)malloc(sizeof(Linknode)); if(L == NULL) return false; L->next = NULL; return true; } // 判断栈是否为空 bool isEmpty(LiStack &L){ if(L->next == NULL) return true; else return false; } 【入栈出栈】
// 新元素入栈 bool pushStack(LiStack &L,ElemType x){ Linknode *s = (Linknode *)malloc(sizeof(Linknode)); if(s == NULL) return false; s->data = x; // 头插法 s->next = L->next; L->next = s; return true; } // 出栈 bool popStack(LiStack &L, int &x){ // 栈空不能出栈 if(L->next == NULL) return false; Linknode *s = L->next; x = s->data; L->next = s->next; free(s); return true; } 

【顺序队列的定义】
#define MaxSize 10; //定义队列中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //用静态数组存放队列元素 int front, rear; //队头指针和队尾指针 }SqQueue; void test{ SqQueue Q; //声明一个队列 } 【顺序队列的初始化】
#define MaxSize 10; typedef struct{ ElemType data[MaxSize]; int front, rear; }SqQueue; // 初始化队列 void InitQueue(SqQueue &Q){ // 初始化时,队头、队尾指针指向0 // 队尾指针指向的是即将插入数据的数组下标 // 队头指针指向的是队头元素的数组下标 Q.rear = Q.front = 0; } // 判断队列是否为空 bool QueueEmpty(SqQueue Q){ if(Q.rear == Q.front) return true; else return false; } 【入队出队(循环队列)】
// 新元素入队 bool EnQueue(SqQueue &Q, ElemType x){ // 如果队列已满直接返回 if((Q.rear+1)%MaxSize == Q.front) //牺牲一个单元区分队空和队满 return false; Q.data[Q.rear] = x; Q.rear = (Q.rear+1)%MaxSize; return true; } // 出队 bool DeQueue(SqQueue &Q, ElemType &x){ // 如果队列为空直接返回 if(Q.rear == Q.front) return false; x = Q.data[Q.front]; Q.front = (Q.front+1)%MaxSize; return true; } 【获得队头元素】
// 获取队头元素并存入x bool GetHead(SqQueue &Q, ElemType &x){ if(Q.rear == Q.front) return false; x = Q.data[Q.front]; return true; } 解决方法一:牺牲一个单元来区分队空和队满,即将(Q.rear+1)%MaxSize == Q.front作为判断队列是否已满的条件。(主流方法)
解决方法二:设置 size 变量记录队列长度。
#define MaxSize 10; typedef struct{ ElemType data[MaxSize]; int front, rear; int size; }SqQueue; // 初始化队列 void InitQueue(SqQueue &Q){ Q.rear = Q.front = 0; Q.size = 0; } // 判断队列是否为空 bool QueueEmpty(SqQueue 0){ if(Q.size == 0) return true; else return false; } // 新元素入队 bool EnQueue(SqQueue &Q, ElemType x){ if(Q.size == MaxSize) return false; Q.size++; Q.data[Q.rear] = x; Q.rear = (Q.rear+1)%MaxSize; return true; } // 出队 bool DeQueue(SqQueue &Q, ElemType &x){ if(Q.size == 0) return false; Q.size--; x = Q.data[Q.front]; Q.front = (Q.front+1)%MaxSize; return true; } 解决方法三:设置 tag 变量记录队列最近的操作。(tag=0:最近进行的是删除操作;tag=1 :最近进行的是插入操作)
#define MaxSize 10; typedef struct{ ElemType data[MaxSize]; int front, rear; int tag; }SqQueue; // 初始化队列 void InitQueue(SqQueue &Q){ Q.rear = Q.front = 0; Q.tag = 0; } // 判断队列是否为空,只有tag==0即出队的时候才可能为空 bool QueueEmpty(SqQueue 0){ if(Q.front == Q.rear && Q.tag == 0) return true; else return false; } // 新元素入队 bool EnQueue(SqQueue &Q, ElemType x){ if(Q.rear == Q.front && tag == 1) return false; Q.data[Q.rear] = x; Q.rear = (Q.rear+1)%MaxSize; Q.tag = 1; return true; } // 出队 bool DeQueue(SqQueue &Q, ElemType &x){ if(Q.rear == Q.front && tag == 0) return false; x = Q.data[Q.front]; Q.front = (Q.front+1)%MaxSize; Q.tag = 0; return true; } 【链队列的定义】
// 链式队列结点 typedef struct LinkNode{ ElemType data; struct LinkNode *next; } // 链式队列 typedef struct{ // 头指针和尾指针 LinkNode *front, *rear; }LinkQueue; 【 链队列的初始化(带头结点)】
typedef struct LinkNode{ ElemType data; struct LinkNode *next; }LinkNode; typedef struct{ LinkNode *front, *rear; }LinkQueue; // 初始化队列 void InitQueue(LinkQueue &Q){ // 初始化时,front、rear都指向头结点 Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode)); Q.front -> next = NULL; } // 判断队列是否为空 bool IsEmpty(LinkQueue Q){ if(Q.front == Q.rear) return true; else return false; } 【入队出队(带头结点)】
// 新元素入队 void EnQueue(LinkQueue &Q, ElemType x){ LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL; Q.rear->next = s; Q.rear = s; } // 队头元素出队 bool DeQueue(LinkQueue &Q, ElemType &x){ if(Q.front == Q.rear) return false; LinkNode *p = Q.front->next; x = p->data; Q.front->next = p->next; // 如果p是最后一个结点,则将队头指针也指向NULL if(Q.rear == p) Q.rear = Q.front; free(p); return true; } 【不带头结点的链队列操作】
typedef struct LinkNode{ ElemType data; struct LinkNode *next; }LinkNode; typedef struct{ LinkNode *front, *rear; }LinkQueue; // 初始化队列 void InitQueue(LinkQueue &Q){ // 不带头结点的链队列初始化,头指针和尾指针都指向NULL Q.front = NULL; Q.rear = NULL; } // 判断队列是否为空 bool IsEmpty(LinkQueue Q){ if(Q.front == NULL) return true; else return false; } // 新元素入队 void EnQueue(LinkQueue &Q, ElemType x){ LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL; // 第一个元素入队时需要特别处理 if(Q.front == NULL){ Q.front = s; Q.rear = s; }else{ Q.rear->next = s; Q.rear = s; } } //队头元素出队 bool DeQueue(LinkQueue &Q, ElemType &x){ if(Q.front == NULL) return false; LinkNode *s = Q.front; x = s->data; if(Q.front == Q.rear){ Q.front = Q.rear = NULL; }else{ Q.front = Q.front->next; } free(s); return true; } 双端队列定义

双端队列考点:判断输出序列的合法化
#define MaxSize 10 typedef struct{ char data[MaxSize]; int top; }SqStack; void InitStack(SqStack &S); bool StackEmpty(SqStack &S); bool Push(SqStack &S, char x); bool Pop(SqStack &S, char &x); // 判断长度为length的字符串str中的括号是否匹配 bool bracketCheck(char str[], int length){ SqStack S; InitStack(S); // 遍历str for(int i=0; i 中缀表达式转后缀表达式-手算
步骤1: 确定中缀表达式中各个运算符的运算顺序
步骤2: 选择下一个运算符,按照[左操作数 右操作数 运算符]的方式组合成一个新的操作数
步骤3: 如果还有运算符没被处理,继续步骤2
“左优先”原则: 只要左边的运算符能先计算,就优先算左边的 (保证运算顺序唯一);
中缀:A + B - C * D / E + F ① ④ ② ③ ⑤ 后缀:A B + C D * E / - F + 后缀表达式的计算—手算:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应的运算,合体为一个操作数
后缀表达式的计算—机算
用栈实现后缀表达式的计算(栈用来存放当前暂时不能确定运算次序的操作数)
步骤1: 从左往后扫描下一个元素,直到处理完所有元素;
步骤2: 若扫描到操作数,则压入栈,并回到步骤1;否则执行步骤3;
步骤3: 若扫描到运算符,则弹出两个栈顶元素,执行相应的运算,运算结果压回栈顶,回到步骤1;
中缀表达式转后缀表达式(机算)
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符从左到右处理各个元素,直到末尾。可能遇到三种情况:
1.遇到操作数:直接加入后缀表达式。
2.遇到界限符:遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到 弹出“(”为止。注意:“(”不加入后缀表达式。
3.遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式, 若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
#define MaxSize 40 typedef struct{ char data[MaxSize]; int top; }SqStack; typedef struct{ char data[MaxSize]; int front,rear; }SqQueue; void InitStack(SqStack &S); bool StackEmpty(SqStack S); bool Push(SqStack &S, char x); bool Pop(SqStack &S, char &x); void InitQueue(SqQueue &Q); bool EnQueue(LQueue &Q, char x); bool DeQueue(LQueue &Q, char &x); bool QueueEmpty(SqQueue Q); // 判断元素ch是否入栈 int JudgeEnStack(SqStack &S, char ch){ char tp = S.data[S->top]; // 如果ch是a~z则返回-1 if(ch >= 'a' && ch <= 'z') return -1; // 如果ch是+、-、*、/且栈顶元素优先级大于等于ch则返回0 else if(ch == '+' && (tp == '+' || tp == '-' || tp == '*' || tp == '/')) return 0; else if(ch == '-' && (tp == '+' || tp == '-' || tp == '*' || tp == '/')) return 0; else if(ch == '*' && (tp == '*' || tp == '/')) return 0; else if(ch == '/' && (tp == '*' || tp == '/')) return 0; // 如果ch是右括号则返回2 else if(ch == ')') return 2; // 其他情况ch入栈,返回1 else return 1; } // 中缀表达式转后缀表达式 int main(int argc, char const *argv[]) { SqStack S; SqQueue Q; InitStack(S); InitQueue(Q); char ch; printf("请输入表达式,以“#”结束:"); scanf("%c", &ch); while (ch != '#'){ // 当栈为空时 if(StackEmpty(&S)){ // 如果输入的是数即a~z,直接入队 if(ch >= 'a' && ch <= 'z') EnQueue(Q, ch); // 如果输入的是运算符,直接入栈 else Puch(S, ch); }else{ // 当栈非空时,判断ch是否需要入栈 int n = JudgeEnStack(S, ch); // 当输入是数字时直接入队 if(n == -1){ EnQueue(Q, ch); }else if(n == 0){ // 当输入是运算符且运算符优先级不高于栈顶元素时 while (1){ // 取栈顶元素入队 char tp; Pop(S, tp); EnQueue(Q, tp); // 再次判断是否需要入栈 n = JudgeEnStack(S, ch); // 当栈头优先级低于输入运算符或者栈头为‘)’时,入栈并跳出循环 if(n != 0){ EnStack(S, ch); break; } } }else if(n == 2){ // 当出现‘)’时 将()中间的运算符全部出栈入队 while(1){ char tp; Pop(S, tp); if(tp == '(') break; else EnQueue(Q, tp); } }else{ // 当运算符优先级高于栈顶元素或出现‘(’时直接入栈 Push(S, ch); } } scanf("%c", &ch); } // 将最后栈中剩余的运算符出栈入队 while (!StackEmpty(S)){ char tp; Pop(S, tp); EnQueue(Q, tp); } // 输出队中元素 while (!QueueEmpety(Q)){ printf("%c ", DeQueue(Q)); } return 0; } 用栈实现中缀表达式的计算:
1.初始化两个栈,操作数栈和运算符栈;
2.若扫描到操作数,压入操作数栈;
3.若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:
递归调用时,函数调用栈称为 “递归工作栈”:
缺点:太多层递归可能回导致栈溢出;适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题

一维数组的存储:各数组元素大小相同,且物理上连续存放。设起始地址为LOC,则数组元素
的存放地址 = LOC + i * sizeof(ElemType) (0≤i<10)

二维数组的存储 :
1. M行N列的二维数组中,设起始地址为LOC,若按行优先存储,则
的存储地址 =
2. M行N列的二维数组中,设起始地址为 LOC,若按列优先存储,则
的存储地址 =


对称矩阵的压缩存储:若n阶方阵中任意一个元素
,都有
则该矩阵为对称矩阵,对于对称矩阵,只需存储主对角线+下三角区。若按照行优先原则将各元素存入一维数组中,即
存入到数组
中,那么数组
共有
个元素。对于k,有:


下三角矩阵:除了主对角线和下三角区,其余的元素都相同。
上三角矩阵:除了主对角线和上三角区,其余的元素都相同。
压缩存储策略:按行优先原则将主对角线+下三角区存入一维数组中,并在最后一个位置存储常量。即
存入到数组
中,那么数组
共有
个元素。对于k,有:


三对角矩阵,又称带状矩阵: 当
时,有
。对于三对角矩阵,按行优先原则,只存储带状部分,即
存入到数组
中,那么
。若已知数组下标k,则
。

稀疏矩阵的非零元素远远少于矩阵元素的个数。压缩存储策略:



例:
S="HelloWorld!" T='iPhone 11 Pro Max?' 假设有串 T = '', S = 'iPhone 11 Pro Max?', W = 'Pro'

静态数组实现(定长顺序存储)
#define MAXLEN 255 //预定义最大串长为255 typedef struct{ char ch[MAXLEN]; //静态数组实现(定长顺序存储) //每个分量存储一个字符 //每个char字符占1B int length; //串的实际长度 }SString; 动态数组实现( 堆分配存储)
typedef struct{ char *ch; //按串长分配存储区,ch指向串的基地址 int length; //串的实际长度 }HString; HString S; S.ch = (char*)malloc(MAXLEN *sizeof(char)); S.length = 0; #define MAXLEN 255 typedef struct{ char ch[MAXLEN]; int length; %7