一、第一章 绪论
1.1 什么是数据结构
1.2 基本概念和术语
1.3 抽象数据
2.1 类型的表示与实现
1.4 算法和算法分析
1.4.1 算法
- 一个有限指令集
- 接受一些输入(有些情况下,不需要输入)
- 产生输出一定的有限步骤后终止每一条指令
- 必须有充分明确的目标,
- 不可以有歧义计算机能处理的范围之内
- 描述应该不能依赖任何一种计算机语言以及具体的实现手段
1.4.2 算法设计的要求
1.4.3 算法效率的度量
1.4.4 算法的存储空间要求
二、第二章 线性表
2.1 线性表的类型定义
- 定义: 有序
- 线性表的特征:
- 前驱
- 后既
- 唯一
2.2 线性表示的顺序的链式表示和实现
-
创建链表
-
平均时间复杂度
T(n)=∑n=1nan∗pi T(n)= \sum_{n=1}^na_n *p_i T(n)=n=1∑nan∗pi
**如果无法找到的平均时间复杂度的话,用最坏的时间来表示 ** -
顺序表的插入:
- 插入次数
- 插入的实现
- 注意的错误
- 时间复杂度
- 顺序表的删除
- 注意事项
2.3 链表
2.3.1 链表定义
-
数据域:
-
指针域:
-
链表:n个结点由指针链存储映像,
-
头指针:
-
头结点:
-
首元结点:
-
设置头结点的好处:
-
便于首元结点的处理
-
便于空表和非空表的统一处理
头结点的数据域内装的是什么?
数据可以为空,也可以存放线性表长度等附加信息,但此结点不能计入链表长度值
-
-
空表:
无头结点时,头指针为空
有头结点时,当头节点的指针域为空时表示空表
链表的特点:
- 结点在存存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
- 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
2.3.2 创建链表
单链表的定义
typedef struct londe {
ElemType date; // 结点的数据域
struct Londe * next; // 结点的指针域
}Londe,*LinkList; //LinKList为指向结构体Lnode的指针类型
变量的定义
LinkList L; //LinkList 本身就是指针类型,通常指向头结点的,代表链表,定义链表
LNode *p,*s; //指向结点的指针,不用LinKList p;
重要操作
P=L; //p指向头结点
s=L->next; //s指向首元结点
p=p->next; //P指向下一个结点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXcGRmFI-1657001168832)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220306204542583.png)]
例如,以下为通常操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vWv0qoU-1657001168833)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220309002101408.png)]
主要区别就是,将数据域单独放在一起作为一个类型。
1. 单链表的初始化(⟶\longrightarrow⟶构造一个空的单链表)
-
算法步骤(带头结点的单链表)
- 生成新结点作为头结点,用头结点指针L指向头结点。
- 将头结点的指针域置空。
Status InitList_L(LinkList &L){ L=new LNode;//或L=(LinKList)malloc(sizeof(LNode)); L->next=NULL; return OK; } -
补充算法1⟶\longrightarrow⟶(判断链表是否为空)
空表:链表中无元素,称为空链表(头指针和头结点仍然存在)
【算法思路】
2.判断头结点指针域是否为空
int ListEmpty(LinkList L){ //若L为空表,则返回1,否则返回0
if(L->next)//非空
return 0;
else
return 1;
}
3. 单链表的销毁:销毁后不存在
【算法思路】从头指针开始,依次释放所有的结点,需要另外一个变量
P=L;
L=L->next;
delete P;//free(P);
结束条件 : L==NULL
循环条件: L!=NULL 或:L
//销毁单链表的算法
Status DestroList_L(LinkList &L)
{
Londe *p;//或者 LinkList P;
while (L){
P=L;
L=L->next;
delete p;
}
return Ok;
}
//清空链表,链表仍然存在了,但是链表中无元素,成为空链表(头指针和头结点仍然在)
【算法思路】
依次释放所有结点。并将头结点指针域设置为空
//思路
p=L->Next;
P=L;
q=p->Next;
delelte p;
反复执行;
p=q;
q=q->Next;
结束条件:p==NULL
循环条件: p!=NULL
//完整的算法过程思路
Status ClearList(LinkList &L) {
Londe *p,*q;
p=L->Next;
while(p){
q=p->Next;
delete p;
p=q;
}
L->Next=NULL;
return OK;
}
4. 求单链表的表长
【算法思路】从首元结点开始,依次计数所有结点
p=p->next;
i++;
//完整的算法
int ListLength_L(LinkList L){ //返回L中数据元素个数
LinkList p;
p=L->next; / p指向第一个结点
i=0;
while(p){ //遍历单链表,统计结点数
i++;
p=p->next;
}
return i;
}
2.3.3 单链表基本操作的实现
1.查找操作
取值(取单链表中第i个元素的内容)
注意:从链表的头指针出发,顺着链表域next逐个往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取的结构
【算法步骤】
- 从第一个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点 ,p初值 p=L->Next.
- j作为计数器,累加当前扫描过的结点数,j初值为1。
- 当p指向下一个扫描到的下一个节点时,计数器加1
- 当 j==i 时,p所指的结点就是要找的第 i 个结点。
【算法分析】
该算法的基本操作是比较j和i并后移指针p,while循环体中的语句频度与位置有关。
- 若 1≤i≤21\leq i \leq21≤i≤2 ,则频度为i−1i-1i−1 ,一定能取值成功
- 若i>ni>ni>n,则频度为n,则频度为n,取值失败。
ps:最坏的时间复杂度为O(n)O(n)O(n)
假设每个位置上元素的取值概率相等,即
pi=1/n
p_i=1/n
pi=1/n
则
ASL=1n∑i=1N(i−1)=n−12
ASL= \frac{1}{n}\sum_{i=1}^N(i-1)= \frac{n-1}{2}
ASL=n1i=1∑N(i−1)=2n−1
由此可见,单链表取值算法的平均时间复杂度为O(n)O(n)O(n)
【算法描述】
Status GetElment L(LinkList L,int i,ElemType &e){//获取线性表L中的某个元素的内容,通过变量e返回
p=L->Next;j=1; //初始化
while(p && j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next;
j++;
}
if(!p || j>i) //第i个元素不存在
return EROOR;
e=p->data; //取出第i个元素
return OK;
}//GetElem_L
2.取值(根据指定数据获取该数据所在的位置(地址))
【算法步骤】
- 从第一个结点起,依次和e相比较。p=L->next;
- 如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或“地址”;p=p->next;
- 如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”。 p-date!=e
Londe *LocateElem_L (LinkList L,Elemtype e){
//在线性表L中查找值为e的数据元素
//找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
p=L->next;
while(p && p->date!=e)
p=p->next;
return p;
}
2.插入操作
//在第i个结点前插入值为e的新结点
【算法步骤】
-
首先找到 ai−1 a_{i-1}ai−1 的存储位置p
-
生成一个数据域为e的新结点s
-
插入新结点:①新结点的指针域指向aia_{i}ai
②结点$a_{i-1}$的指针域指向新结点 -
s->next=p->next; p->next =s
//在L中第i个元素之前插入数据元素e
Status Listinsert_L(LinkList &L ,int i,ElemType e){
p=L;
j=0;
while(p && j<i-1){
p=p->next;
j++;
}
if(!p||j>i-1)
return ERROR;//大于表长+1或者小于1,插入位置非法
s=new LNode; //生成结点s,将结点s的数据域置为e
s->date=e;
s->next=p->next;
p->next =s;
return OK;
}//ListInsert_L
【算法分析】
为了在第iii个结点之前插入一个新结点,必须先找到第i−1i-1i−1个结点,时间复杂度为o(n)o(n)o(n)
3.删除操作
【算法步骤】//删除第i个结点
- 首先找到ai−1a_{i-1}ai−1的存储位置p,保存要删除的aia_iai 的值
- 另p->next指向ai+1a_{i+1}ai+1。
- 释放结点aia_{i}ai的空间
Status ListDlete_L(LinkList &L,int i,ElemType &e){
p=L;
j=0;
if (k == 1) {
if (L != NULL)
L = L->next;
else
cout << "这是个空链表,删除失败" << endl;
delete p;
return;
}
while((p->next) && (j<i-1)){
p=p->next;
j++;
}
// if ((!p->next) || (j->i-1)) return ERROR;//删除ERROR;
q=p->next;
p->next=q->next;
e=q->date;
delete q;
return Ok;
}// ListDelte_L
4.头插法
【算法描述】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8Xbh4EB-1657001168834)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220313101723524.png)]
【代码的实现】
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL;//先建立一个带头结点的单链表
for(i=n;i>0;--i){
p=new LNode;//生成新结点p=(LNode*)malloc(sizeof(LNode));
cin>>p->date;//输入元素值scanf(&p->date);
p->next=L->next;//插入到表头
L->next=p;
}//CreateList_H
【时间复杂度】O(n)O(n)O(n)
5.尾插法
【算法描述】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGlozTPC-1657001168834)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220313103258247.png)]
【代码实现】
//正位序输入n个元素的值,建立带头结点的单链表
void Greatlist_R(Linklist &L,int n){
L=new Lnode;
L->next=NULL;
r=L;//使尾指针指向头结点
for(i=0;i<n;i++){
p=new LNode;
cin>>p->date; //生成新结点,输入元素值
p->next=NULL;
r->next=p; //插入到表尾
r=p; //指向尾指针
}//CreateList_R
2.4 静态链表
2.5 循环链表
带尾指针的循环链表的合并
【算法描述】
LinkList Connext(LinkList Ta,Linklist Tb){
//假设Ta、Tb都是非空的单循环链表
p=Ta->next;//(1)pc
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nXWVvm1J-1657001168835)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220313152947010.png)]
2.5-1 单循环链表
删除头结点
2.5-2 两个有序链表的合并
2.5-3 双向链表的插入
【算法思路】
【代码】
void ListInsert_Dul(DuLinkList &L,Int i,ElemeType e){
if(!(p=GetElemP_Dul(L,i))) return ERROR; //插入的位置不合法
S=new DulNode;
s->date=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
}
2.5-4 双向链表的删除
2.5-5 双向链表的插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6V6pX9l-1657001168836)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220313202019262.png)]
线性表的合并
2.4 链表的常见操作
2.4-1 单链表的逆置
1.头插法
开始时未逆转:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOwiJiw8-1657001168836)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220322213808211.png)]
**初始化:**使用p和q分别记录待转置链表的首位结点和次首位结点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rg9umFkS-1657001168836)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220322213935339.png)]
**第一次:**从链表中一次“删除”,头插到逆置表中(循环开始,条件while§)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AcSaDRmm-1657001168837)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220322214544960.png)]
第二次:q=q->next
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w4Uxd1Lu-1657001168837)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220322215038520.png)]
第三次:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tq4CPpOs-1657001168838)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220322215428326.png)]
void reverse(node*head)
{
node*p;
p=L->next;
head->next=NULL;
while(p)
{
q=p;
p=p->next;
q->next=head->next;
head->next=q;
}
}
ps:对于不含头结点的单链表的头指针原地逆置
ListNode* converse(ListNode *head)
{
ListNode* dumyOfInsertedList = new ListNode(-1);//一个哑结点,用于创建新的链表
if(head == NULL) return NULL;
ListNode* unInsertedHead = head;//使用unInsertedHead和inInsertHead_next作为待逆置链表的首结点和次首结点的便签
ListNode* unInsertedHead_next = head->next;
while(unInsertedHead)// 头插直到原链表为空
{
unInsertedHead->next = dumyOfInsertedList->next;
dumyOfInsertedList->next = unInsertedHead;
unInsertedHead = unInsertedHead_next;
if(unInsertedHead_next!=NULL)// 注意:最后一个结点时,unInsertedHead_next为NULL没有next成员变量
{
//cout<<"head:"<< unInsertedHead->val<<"next:"<<unInsertedHead_next->val<<endl;
unInsertedHead_next = unInsertedHead_next->next;
}
}
return dumyOfInsertedList->next;
}
2.递归的方法
第三章 栈和队列
1.有关栈操作的栈名称
InitStack(&S) //初始化操作
操作结果:构造一个空栈S
DestroyStack(&S)//销毁栈操作
初始条件:栈S已经存在
操作结果:栈S被销毁。
StackEmpty(&S) //判段S是否为空栈
初始条件:栈S已经存在
操作结果:若栈S为空栈,则返回True,否则False
StackLength(S) //求栈的长度
初始条件:栈S已经存在
操作结果:返回S的元素个数,及栈的长度
GetTop(S,&e) //取栈顶元素
初始条件:栈S已经存在且非空
操作结果:用e返回S的栈顶元素
ClearStack(&S)//栈制空操作
初始操作:栈S已经存在
操作结果:将S清为空栈
Push(&s,e) //入栈操作
初始条件:栈S已经存在
操作结果:插入元素e为新的栈顶元素
Pop(&s,e)//出栈操作
初始条件:栈已经存在且非空
操作结果:删除S的栈顶元素,并用e返回值
StackTraverse(S,visit())
初始条件:栈S已经存在且非空
操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit()。一旦visit()则操作失效
栈的顺序存储–顺序栈
栈的链式存储–链栈
2.顺序栈的相关的操作
2.1栈满时的处理方法
- 报错,返回操作系统T
- 分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈
上溢:栈已经满了,又要压入元素
下溢:栈已经空,还要弹出元素
2.2 顺序表的表示和实现
**存储方式:**通一般线性表的存储结构完全相同,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
注意: 一般来说,在初始化设栈时不应限定栈的最大容量。比较合理的做法是:先为栈分配一个基本容量,然后在应用过程中,当栈的空间不够使用时再逐段扩大。
为此设定两个常量:STACK_INIT_SIZE(存储空间初始分配量)和STACKINCREMENT(存储空间分配增量,并以下述类型说明作为顺序栈的定义。
//顺序栈的表示方式
#define STACK_INIT_SIZE 100;//存储空间初始分配量
#define STACKINCREMENT 10;//存储空间分配增量
typedef struct {
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //表示栈可使用的最大容量
}SqStack;
2.3 顺序栈的初始化
Status InitStack(SqStack &S) { //构造一个空表
S.base = new SElemType[MAXsize];
//或 S.base = (SElemType*)malloc(MAXSIZE *sizeof(SELem Type));
if(!S.base) exit (OvERFLOW); //存储分配失败
S.top = S.base; //栈顶指针等于栈底指针
S.stacksize = MAXsize;
return Ok;
}
2.4 返回栈顶元素
//若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR
Status GetTop(SqStack S,SElemType &e)
{
if(S.top == S.b)
return ERROR;
e=*(S.top-1);
return OK;
}
2.5 求顺序栈长度
int StackLength(SqStack S){
return S.top-S.base;
}
2.6 清空顺序栈
Status ClearStack(SqStack S){
if(S.base)
S.top=S.base;
return ok;
}
2.7 销毁顺序栈
Status DestroyStack(SqStack &S){
if(S.base){
delete S.base;
S.base = S.top =NULL;
}
return Ok;
}
2.8 顺序表的入栈操作
- 顺序栈的入栈
- 判断是否栈满,若满则出现(上溢)
- 元素e压入栈顶
- 栈顶指针加1
Status Push(Satack &S,SELemType e){
if(S.top-S.base >= S.stacksize) //栈满,追加存储空间
{
S.base = (SELemType *)realloc(S.base,(S.stacksize + STACKINCREMNT)*sizeof(SElemType));
if(!S.base)
exit(OVERFLOW);//存储分配失败
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMNT;
}
*S.top++=e// =>*S.top =e; S.top++;
return Ok;
}
【常规做法】
//插入元素 e为新的栈顶元素
Status Push(SqStack &s,SElemType e)
{
if(S.top-S.base == S.stacksize)
return ERROR;
*S.top++=e;
return ok;
}
2.9 顺序表的出栈操作
- 判断是否栈空。若空则 出错(下溢)
- 获取栈顶元素e
- 栈顶指针减1
Status Pop(SqStack &S,SElemType &e){
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回Ok;否则返回ERROR
if(S.top ==S.base) //等价于if(StackEmpty(S))
return ERROR;
e= *(--S.top); //--S.top; e=*S.top
return Ok;
}
3. 栈的应用举例
3.1 数制转换
算法原理:N=N div d+ N mod dN= N\;div\;d+\; N\;mod\;dN=Ndivd+Nmodd(其中:div为整除运算,mod 为求余运算)
例如:$(1348){10}=(2504){8} $,运算过程如下:
| NNN | N div 8N\;div\;8Ndiv8 | N mod 8N\;mod\;8Nmod8 |
|---|---|---|
| 1348 | 168 | 4 |
| 168 | 21 | 0 |
| 21 | 2 | 5 |
| 2 | 0 | 2 |
//打印出输入任意一个非负十进制整数,打印出与之等值的八进制数
void conversion()
{
InitStack(S);//构造空栈
scanf("%d",N);
while(N)
{
Push(S,N % 8);
N = N/8;
}
while(!StackEmpty(S))
{
Pop(S,e);
printf("%d",e);
}
}
3.2 括号匹配的检验
自主学习
3.3 行编辑程序
【算法思想】
- 将输入缓冲区为一个栈结构
- 每当从终端接受了一个字符后先做如下的判别。若它既不是退格符也不是换行符,则将该字符压入栈顶。如果是一个退格符符,则从栈顶删去一个字符;如果它是一个换行符,则将字符栈清为空栈
//利用字符栈S,从终端接收一行并传送至调用过程的数据区
void LineEdit()
{
InitStack(S);//构造空栈S
ch=getchar();//从终端接收第一个字符
while(ch != EOF)
{
while(ch!=EOF && ch!='\n')
{
switch(ch)
{
case '#':Pop(S,c);
break;//且当栈非空时退栈
case '@':ClearStack(S);
break;
default:
Push(S,ch);//有效字符进栈,未考虑栈满情况
}
ch = getchar();//从终端接收下一个字符
}
将从栈底到栈顶的栈内字符传送到至调用过程的数据区;
ClearStack(S);//重置S为空栈
if(ch != EOF)
ch = getchar();
}
DestroyStack(S);
}
3.4 迷宫求解
3.5 表达式求值
【算法思想】
- 用两个工作栈,OPTR(运算符栈),OPND(操作数栈)
- 首先置空两个运算符栈
- 将"#"压入运算符栈中,
- 重复扫描条件,表达式没有扫描完毕至“#”,执行循环
- 判断输入的是字符,还是操作数
- 如果是操作数,直接压入OPND栈。如果是运算符号 ->比较运算符
- 如果小于,则将ch压入栈OPTR栈,读入下一个字符ch
- 如果大于,则弹出OPTR栈顶的运算符,从OPND栈弹出两个数,进行相应运算,将计算结果压入OPND中
- 如果等于,则OPTR的栈顶元素是“(”且ch是“)”,这时弹出OPTR栈顶的“(”,相当于括号匹配成功,然后读入下一个字符ch。
OPerandType EvaluateExpression()
{
InitStack(OPTR);
Push(OPTR,'#');//将#压入栈
InitStack(OPND);
c=getchar();//cin>>ch;
while(c!='#' || GetTop(OPTR) != '#')
{
if(!In(c,OP))//不是操作符,压入栈
{
Push(OPND,c);
}
else
switch(Precede(GetTop(OPTR),c))//比较栈顶元素和c的运算符的优先级
case '<'://栈顶元素优先权低
Push(OPTR,c);
c=getchar();
break;
case '>':
Pop(OPTR,theta);//弹出OPTR栈顶的运算符
Pop(OPND,B);//弹出OPND栈顶的两个运算符
Pop(OPND,A);
Push(OPND,oprate(a,thete,b));
break;
case '=':
Pop(OPTR,c);
c=getchar();
break;
}
return GetTop(OPND);//返回OPND栈顶元素,即为表达式结果。
}
4.栈的链式存储
**链栈:**链栈是运算受限的单链表,只能在链表头部进行操作
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
LinkStack S;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0dmJmhjw-1657001168838)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220316220839208.png)]
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头指针指向空
- 插入
3.0 类型的定义
typedef struct StackNode
{
ElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
3.1 初始化
直接将栈顶指针置空即可
Status InitStack(LinkStack &s)
{
S=NULL;
return OK;
}
3.2 入栈
入栈不需要判断栈是否满。只需要为入栈元素动态分配一个结点空间
【算法步骤】
- 为入栈元素e分配空间,用指针p指向。
- 将新结点数据域置为e。
- 将新结点插入栈顶。
- 修改栈顶指针为p。
Status Push(LinkStack &s,SElemType e)
{
p=new StackNode; //生成新结点
p->date=e; //将新结点的数据域置为e
p->next =s;//将新结点插入栈顶
s=p;
return ok;
}
3.3 出栈
和顺序表一样,栈链在出栈前也需要判断栈是否为空,不同的时,链栈在出栈后需要释放出栈元素的栈顶空间
【算法步骤】
- 判断栈是否为空,若空则返回ERROR。
- 将栈顶元素赋给e。
- 临时保存栈顶元素的空间,以备释放。
- 修改栈顶指针,指向新的栈顶元素。
- 释放原栈顶元素的空间。
Status Pop(LinkStack &S,SElemType &e)
{
//删除s的栈顶元素e,用e返回值
if(S==NULL)
return ERROR;//栈空
e=S->date;//将栈顶元素赋给e
p=S;//用p临时保存栈顶元素空间,以备释放
S=S->next;//修改栈顶元素
delete p;
return OK;
}
3.4 取栈顶元素
与顺序栈一样,当栈非空时,此操作返回当前栈顶元素的值,栈顶指针S保持不变。
SElemType GetTop(LinkStack S)
{
if(S!=NULL)
return S->date;//返回栈顶元素的值,栈顶指针不变
}
4. 几个常见的栈的操作
4.1 递归操作
5.队列的表示和操作的实现
5.1队列的抽象数据类型定义
InitQueue(&Q)
操作结果:构造空队列
De
5.2 队列的存储结构
#define MaxQsize 100
typedef struct{
QElemType *base;//存储空间的基地址
int front; //头指针
int rear;//尾指针
}SqQueue;
第四章 串
4.1 串的数据类型
数组:相同数据类型的数据的集合
二维数组:若一维数组中的数据元素又是一维数------------------------
二维数组的逻辑结构-------------
等价于:公式====
推广:n维数组
结论:线性表结构是数组的一个特例,数组结构又是线性表结构的扩展
数组的特点
n维数组的数据类型:
数据对象:
数据关系:N个前驱
抽象数据类型的定义:
R={ROW,COL}
ROW=
COL=
分成行上的前驱和列上的前驱分别是什么
推广:n维的n之间的关系
2.1 基本操作
InitArry(&A,n,bound1,boundn)//构造数组
DestroyArray(&A)//销毁数组A
Value(A,&e,index1,..,indexn)//取数组元素值
Assign(A,&e,index1,..indexn)//给数组元素赋值
数组的顺序存储
数组特点:结构固定(维数和维界不变)
数组基本操作:初始化、销毁、取元素、修改元素值。一般不做插入和删除操作
注意:数组可以是多维的,但是存储数据元素的内存单元地址是一维的,因此,在存储数组结构之前,需要解决将多维关系映射到一维关系的问题。
二维数组⟶\longrightarrow⟶ 一维数组
行序为主序:
列序为主序:
存储位置:
三维数组:
下表
串的模式匹配算法
【普通的匹配】
int Index(SString S,SString T,int pos)
{
//返回子串在主串中第pos个字符之后的位置。不存在返回0.
//T非空,1 <= pos <=StrLength(s)
int i=pos;//用于主串s中当前位置下标,若pos不为1,则从pos位置开始匹配
int j=1;//j用于子串T中的当前位置下标值
while(i <= S[0] && j <=T[0]) //若i小于S长度且j小于T的长度时循环
{
if(S[i] == S[j]) //两字母相等则继续
{
i++;
j++;
}
else
{
i=i-j+2;//i回退到上次匹配首位的下一位
j =1;
}
}
if(j > T[0])
return i-T[0];
else
return 0;
}
【算法分析】
- 将T串各个位置的j值的变化定义为一个数组next,next的长度就是T长度就是T串的长度
【KMP算法】
void get_next (SString T , int next[])
{
int i,j;
i=1;
j=0;
next[1]=0;
while (i < T[0])
{
if (j == 0 || T[i] == T[j]) //T[i]表示后缀的单个字符,T[j]表示前缀的单个字符
{
i++;
j++;
next[i] = j;
}
else
j = next[j];//若字符不相等,则回溯,//发生了失配,查Next数组移动模式串指针
}
}
int Index_KMP (SString S, SString T, int pos)
{
i=pos;
j=1;
int next[255];
get_next (T,next);
while (i <= S[0] && j <= T[0])
{
if (j == 0 || S[i] == T[j])
{
i++;
j++;
}
else
j = next[j];
}
if (j > T[0])
return i-T[0];
else
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7zhtB2FM-1657001168839)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220401151921138.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVrvX34k-1657001168839)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220409105805342.png)]
第五章 查找
第六章 内部排序操作
5.1 排序的概论
#define MAXSIZE 20
typedef int KeyType;//定义一个关键字类型为整数类型
typedef struct {
KeyType key; //关键字项
InfoType otherinfo;//其它数据项
}RedType;//记录了类型
typedef struct {
RedType r[MAXSIZE + 1];//r[0]闲置或用作哨兵单元
int length;//顺序表长度
}SqList;//顺序表类型
5.2 直接插入排序
void Insertsort ( SqList &L ){
for ( i=2; i <= L.length; i++){
if (LT(L.r[i].key, L.r[i-1].key)) {
L.r[0] = L.r[i];//复制元素为哨兵
L.r[i] = L.r[i-1];
for (j = i - 2; LT(L.r[0].key, L.r[j].key); j--)
L.r[j+1] = L.r[j]; //记录后移
L.r[j+1] = L.r[0]; // 插入到正确的位置
}
}//InsertSort
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqmnwGDX-1657001168839)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220415112743134.png)]
5.3 折半插入排序
void BInsertSort (SqList &L){
for (i=2; i <= L.Length;i++){
L.r[0] = L.r[i]; //将要排的序暂存到L.r[0](哨兵)中
low = 1;
high = i-1;
while (low <= high){
m = (low + high)/2;
if (LT(L.r[0].key,L.r[m].key))
high = m - 1;
else
low = m + 1;
}
for (j = i - 1; j >= high + 1; j--){
L.r[j+1] = L.r[j];
}
L.r[high + 1] = L.r[0];
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oNaQsUe3-1657001168840)(C:\Users\31930\AppData\Roaming\Typora\typora-user-images\image-20220415115735540.png)]
6.4 希尔排序
6.5 快速排序
6.6 选择排序
6.6.1 简单选择排序
每一趟
【算法思想】
void SelectSort (SqList &L){
for (i=1; i <L.length; i++){
k = i;
for( j = i + 1;j < L.length - k; j++)
if (L.r[j+1].key > L.r[k].key)
k = j;
if (k != i)
{
temp = L.r[i];
L.r[i] = L.r[k];
L.r[k] = temp
}
}
}
6.7 堆排序
6.8 归并排序
6.9 基数排序
第七章 树
7.1 树的定义
二叉树的定义
满二叉树vs完全二叉树
- 编号对应
- 连续的去掉
特点:
链表的存储
三叉链表
先中后遍历的方法
先序遍历算法(二叉链表)
二叉树的层次遍历算法
1.看成一层层的,从上到下,从左到右的顺序
2.用队列
二叉树的应用
1.二叉树的建立(限序遍历)
1.从键盘(补充空结点)
2.按先序方式建立
读入一串字符//ABC##DE#G##F###
Status CreateBiTree(BiTree &T){
scanf(&ch);//cin >>ch;
if(ch == "#")
T =NULL;
else{
if(!T = (BiTNode *)malloc(sizeof(BiTNode)))
exit(OverFLOW);//T = new BiTNode;
T->date = ch;//生成根结点
CreateBiTree(T->Ichild);//构造左子树
CreteBiTree(T->rchild);//构造右子树
}
return Ok;
}
2.复制二叉树
【算法思想】
- 如果是空树,递归结束
- 否则,申请新结点空间,复制根结点
- 递归复制左子树
- 递归复制右子树
int Copy(BiTree T,BiTree &NewT){
if(T==NUll){//如果是空树返回0;
NewT=NUll;
return 0;
}
else{
NewT=new BiTNode;
NewT->data=T->data;
copy(T->lChild,NewT->lchild);
Copy(T->rChild,NewT->rchild);
}
}
3.计算二叉树的深度
【算法思想】
- 如果是空树,则深度为0;
- 否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1;
int Depth(Bitree T){
if(T==NuLL){
return 0;//如果是空树则返回0;
}
else{
m=Depth(T->lChild);
n=Depth(T->rChild);
if(m>n)
return (m+1);
else
return (n+1);
}
}
4.计算结点的总数
【算法思想】
- 如果是空树,则结点树个数为0;
- 否则,结点个数为左子树的结点个数+右子树的结点个数+1.
int NodeCount(BiTree T)
{
if(T==NULL)
return 0;
else
return (NodeCount(T->lchild)+NodeCount(T->rchild)+1);
}
备注:
计算叶子结点总数
【算法思想】
- 如果是空树,则结点树个数为0;
- 否则,结点个数为左子树的结点个数+右子树的结点个数。
int LeadCount(BiTree T){
if(T==NULL)//如果是空树返回0;
return 0;
if(T->lchild == NULL && T->rchild == NULL)
return 1;//如果是叶子结点返回1
else
return LeadfCount(T->lchild)+LeadfCount(T->rchild);
}
线索二叉树
缘由:一般情况下,不好找结点的前驱后继
二叉树链表中空指针域的数量:n个结点的二叉链表中,一共有2n个指针域。n-1个用来指示结点的左右孩子,其余n+1个指针域为空。
【算法思想】
- if左孩子指针域空,指向其前驱结点
- if右孩子指针域空,指向其后继结点
线索化 :将二叉树按某种遍历次序使其变为线索二叉树的过程
线索二叉树的结点形式:
| lchild | LTag | data | RTag | rchild |
|---|
LTag:{0 lchild域指示结点的左孩子}{1 lchild域指示结点的前驱}
RTag:{0 Rchild域指示结点的左孩子}{1 rchild域指示结点的后继}
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *rchild,*lchild;//左右孩子
int LTag,RTag;//左右标记
}BiThrNode,*BiThrTree;
构造线索二叉树
【算法步骤】
二叉树的深度
【算法思想】
- 如果是空树,则深度为0;
- 否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1;
int Depth(Bitree T){
if(T==NuLL){
return 0;//如果是空树则返回0;
}
else{
m=Depth(T->lChild);
n=Depth(T->rChild);
if(m>n)
return (m+1);
else
return (n+1);
}
}
4.计算结点的总数
【算法思想】
- 如果是空树,则结点树个数为0;
- 否则,结点个数为左子树的结点个数+右子树的结点个数+1.
int NodeCount(BiTree T)
{
if(T==NULL)
return 0;
else
return (NodeCount(T->lchild)+NodeCount(T->rchild)+1);
}
备注:
计算叶子结点总数
【算法思想】
- 如果是空树,则结点树个数为0;
- 否则,结点个数为左子树的结点个数+右子树的结点个数。
int LeadCount(BiTree T){
if(T==NULL)//如果是空树返回0;
return 0;
if(T->lchild == NULL && T->rchild == NULL)
return 1;//如果是叶子结点返回1
else
return LeadfCount(T->lchild)+LeadfCount(T->rchild);
}
线索二叉树
缘由:一般情况下,不好找结点的前驱后继
二叉树链表中空指针域的数量:n个结点的二叉链表中,一共有2n个指针域。n-1个用来指示结点的左右孩子,其余n+1个指针域为空。
【算法思想】
- if左孩子指针域空,指向其前驱结点
- if右孩子指针域空,指向其后继结点
线索化 :将二叉树按某种遍历次序使其变为线索二叉树的过程
线索二叉树的结点形式:
| lchild | LTag | data | RTag | rchild |
|---|
LTag:{0 lchild域指示结点的左孩子}{1 lchild域指示结点的前驱}
RTag:{0 Rchild域指示结点的左孩子}{1 rchild域指示结点的后继}
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *rchild,*lchild;//左右孩子
int LTag,RTag;//左右标记
}BiThrNode,*BiThrTree;
构造线索二叉树
【算法步骤】
本文详细介绍了数据结构的基础知识,包括链表的定义、操作和实现,如线性表、单链表、静态链表和循环链表,以及栈和队列的操作,如初始化、入栈、出栈、取栈顶元素。此外,还涉及了栈和队列在实际问题中的应用,如数制转换和表达式求值。接着,文章讨论了串的模式匹配算法和几种排序方法,如插入排序、快速排序和堆排序。最后,提到了树的概念,特别是二叉树的性质和操作,如深度计算、结点计数,以及线索二叉树的构造。
3887

被折叠的 条评论
为什么被折叠?



