最全数据结构学习笔记(考研 笔记 完结 西电)_西电数据结构笔记,2024年最新软件测试篇

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

	return NULL;
LNode \*q=p->next;
h=q->data;
p->next=q->next;
free(q);
return L;

}
int main(){
LinkList L=InsertTailH();
// char e=‘a’;
// find(L,3,e);
// cout<<e<<endl;
// find2(L,‘w’);
InsertElem(L,2,‘Z’);
find2(L,‘Z’);
char h;
Delete(L,2,h);
cout<<h<<“已被删除”<<endl;
return 0;
}


###### 3.约瑟夫环(Joseph Circle)(可运行)



> 
> 问题描述: 编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。现在给定一个随机数m>0,从编号为1的人开始,按顺时针方向1开始顺序报数,报到m时停止。报m的人出圈,同时留下他的密码作为新的m值,从他在顺时针方向上的下一个人开始,重新从1开始报数,如此下去,直至所有的人全部出列为止。请编写程序求出圈的顺序。
> 
> 
> 


分析:


1. 每一个人代表一个数据元素,每个人有两个属性(数据项):编号和密码;
2. 需要一个计数器k表示报到几了,当k与m相等时,删除该数据元素;
3. 可以考虑顺序表或循环链表,因为要进行删除操作,选择循环链表简单一点;
4. 考虑带**尾指针**的循环单链表:每次删除当前结点时,要通过当前结点的前一位来删除它,如果是头指针,那么就需要先循环到链尾再进行删除。
5. 不带头结点的尾插法(显然不带头结点)



#include
#include
#include

using namespace std;

//Joseph Circle

//定义单链表
typedef struct LNode{
int No;//编号
unsigned int Pwd;//密码
struct LNode *next;
}LNode,*LinkList;

//用尾插法创建一个结点数为n的不带头结点的单循环链表,返回值为尾结点的指针
LinkList Create_list(int n){
LinkList r=NULL,p;//p:头指针
//创建循环单链表的第一个结点
p=(LinkList)malloc(sizeof(LinkList));
if(!p){
cout<<“memory allocation error!”<<endl;
return NULL;
}
cout<<“input the first password:”<<endl;
cin>>p->Pwd;
p->No=1;
p->next=p;
r=p;
//循环创建其余n-1个结点
for(int i=2;i<=n;i++){
LNode *s=(LNode*)malloc(sizeof(LNode));
if(!s){
cout<<“memory allocation error!”<<endl;
return NULL;
}
s->No=i;
cout<<"input the "<No<<“的password:”<<endl;
cin>>s->Pwd;
s->next=r->next;
r->next=s;
r=s;
}
r->next=p;
return r;

}

//游戏玩法
void playing(LinkList tail,int n,int m){
LinkList pre,p;//pre指向当前结点的前驱结点,p指向当前结点
int k=1;//计数器
//取余 毕竟每次都是一个循环 按最小的算
m=(m%n)?(m%n):n;
pre=tail;//前驱结点指向链尾
p=tail->next;//p指向第一个结点
while(n>1){//圈中人数多于1时,循环
if(k==m){//循环到需要出圈的人时
cout<<m<<" 当前m的值"<<endl;
printf(“%4d即将出圈\n”,p->No);
pre->next=p->next;//删除
n–;
m=p->Pwd;
m=(m%n)?(m%n):n;
free§;//释放当前结点(注意:不是释放p指针啊)
p=pre->next;
k=1;//下一轮循环仍然从1开始
}
else{
pre=p;
p=pre->next;
k++;
}
}

printf("%4d是最后一个出圈的结点",p->No);

}

int main(){
LinkList tail;//带尾结点的单链表
int n,ip;//玩家人数和初始密码
cout<<“please input the number of players and initial password:” <<endl;
cin>>n>>ip;
//创建循环单链表
tail=Create_list(n);
if(tail){
playing(tail,n,ip);
}
return 0;
}


###### (二)双链表


结点中有两个指针,prior和next,分别指向前驱和后继;



typedef struct DLnode{
ElemType data;
struct DLnode *prior,*next;
}DLnode,*DLinkList;


###### (三)循环链表


**①循环单链表**  
 最后一个结点的next指针指向头结点;


**带尾指针的循环单链表的特点:**


* 通过表尾指针可以一步得到表头指针;
* 对于简单的两个表的合并可以通过简单操作完成(T(n)=O(1));


**②循环双链表**  
 头结点的prior指针还要指表尾结点(即,某结点\*p为尾结点时,p->next==L);  
 循环双链表为空表时,头结点的prior和next域都等于L(即,指向自身);


###### (四)静态链表


静态链表借助数组来描述线性表的链式存储结构,结点也有数据域和指针域,这里的指针是结点的相对地址(数组下标),又称游标。静态链表和顺序表一样需要预先分配一块连续的内存空间。


#### CHAPTER2 栈和队列


##### 一、栈


栈:只允许在一端进行插入或删除操作的线性表;(操作受限的线性表)  
 后进先出(LIFO)


卡特兰数:n个不同元素进栈,出栈元素不同排列的个数为![在这里插入图片描述](https://img-blog.csdnimg.cn/20200829100428501.png#pic_center)


###### (一)顺序栈


**栈采用顺序存储结构**  
 此时栈顶指针默认指向栈顶元素的下一位;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200828214717313.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)



#include
#include
#inclide

#define STACKINITIALSIZE 100
#define STACKINCRECEMENT 10

using namespace std;

//栈的顺序存储表示
typedef struct{
ElemType *base;//栈底指针,栈构造前和销毁后为空
ElemType *top;//栈顶指针,指向栈顶元素的下一个位置
int stacksize;//当前分配的栈的存储空间数
}SqStack;

//构造空栈(初始化栈)
bool InitStack(SqStack &s){
s.base=(ElemType*)malloc(STACKINITIALSIZE*sizeof(ElemType));
if(!s.base)
return false;
s.base=s.top;
s.stacksize=STACKINITIALSIZE;
return true;
}

//入栈
bool Push(SqStack &s,ElemType e){
//判断是否栈满,栈满重新申请空间
//top-base==stacksize(容量),说明栈已满
if((s.top-s.base)==s.stacksize){
s.base=(ElemType*)realloc(s.base,(s.stacksize+STACKINCRECEMENT)*sizeof(ElemType));
if(!s.base)
return false;
s.top=s.base+s.stacksize;
s.stacksize+=STACKINCRECEMENT;
}
//元素入栈
*s.top=e;
s.top++;
return true;
}

int main(){

return 0;

}


###### (二)链栈


**栈采用单链表存储**  
 规定所有的操作都是在单链表的表头进行的,毕竟栈只能在一端进行操作


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200829091711737.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **Q:是否需要另外设置尾指针?**  
 A:否,尾结点的next------>NULL;可以判断尾结点在哪里,不需要另设;  
 **Q:是否需要另设头结点?**  
 A:否,此时栈顶指针top指向的是栈顶元素,入栈出栈操作可以在O(1)时间内完成,不需要设置头结点;  
 **Q:建立链栈适合采用哪种插入法?**  
 A:头插法;(头插法逆序)



typedef struct LinkStack{
ElemType data;//数据域
struct LinkStack *next;//指针域
}*LinkStack;


**注意链栈元素出栈是e=top→data;**


###### (三)栈的应用


**主要是满足后进先出的特性**


**1.数制转换**  
 43(10) = 101011(2)  
 **思想**:先求出来的余数放在后边


**2.括号匹配**  
 **思想**:自左至右扫描表达式,若遇左括号,则将左括号入栈,若遇右括号,则将其与栈顶的左括号进行匹配,若配对,则栈顶的左括号出栈,否则出现括号不匹配错误。


**3.表达式求值**(中缀表达式求值)  
 #优先级最低  
 **思想**:例如:4+2×3-10/5  
 按照运算法则,我们应当先算2×3然后算10/5 ,再算加法,最后算减法。  
 我们两个栈,一个用于存储运算符称之为**运算符栈**,另一个用于存储操作数称之为**操作数栈**。  
 (1)首先置操作数栈为空,表达式起始符“#”为运算符栈的栈低元素。  
 (2)依次读入表达式中每个字符,若是操作数则进操作数栈,若是运算符则和运算符栈栈顶元素比较优先级,**若栈顶元素优先级高于即将入栈的元素,则栈顶元素出栈**(优先级高的先出栈,再把优先级低的放进来),操作数栈pop出两个操作数和运算符一起进行运算,将运算后的结果放入操作数栈,直至整个表达式求值完毕(即运算符栈顶元素和要放入元素均为“#”)


**4.迷宫问题**  
 **思想**:以栈S记录当前路径,则栈顶中存放的是“当前路径上最后一个位置信息”


* 若当前位置“可通”,则纳入路径(入栈),继续前进;
* 若当前位置“不可通”,则后退(出栈),换方向继续探索;
* 若四周“均无通路”,则将当前位置从路径中删除出去。


**5.递归调用**


**5.程序运行时的函数调用**


##### 二、队列


**队列:**  
 队列是仅限定在表尾进行插入和表头进行删除操作的线性表;  
 先进先出(FIFIO)


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830113737726.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


###### (一)链队列


队列的链式存储结构  
 默认带头结点;  
 实际上是一个同时带有队头指针和队尾指针的单链表;  
 适合于数据元素变动比较大的情况,不存在队满溢出的问题;  
 ![](https://img-blog.csdnimg.cn/20200830142007696.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **插入元素只动rear,删除元素只动front;**



#include
#include
#include

using namespace std;

//队列的链式存储类型
//先定义链式队列结点,再定义链式队列
//先搞一颗珍珠
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;

//把珍珠串起来
typedef struct{
QueuePtr front;//队头指针
QueuePtr rear;//队尾指针
}LinkQueue;

//初始化;构造一个空队列Q ;带头结点哦~
bool InitQueue(LinkQueue &Q){
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
return false;
Q.front->next=NULL;
return true;
}

//判空
bool IsEmpty(LinkQueue Q){
if(Q.front==Q.rear)
return true;
return false;
}

//入队
bool EnQueue(LinkQueue &Q,QElemType e){
//表面上插的是元素,实际上插入的是结点
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p)
return false;
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;

return true;

}

//出队
//若队列非空,对头元素出队并用e返回其值
bool DeQueue(LinkQueue &Q,QElemType &e){
if(Q.frontQ.rear)
return false;
QueuePtr p=Q.front->next;
Q.front->next = p->next;
e=p->data;
//若原队列中只有一个结点,删除后变空
if(Q.rear
p)
Q.rear=Q.front;
//Q:这里写成Q.rear=NULL;也行吧
//A:不行!!!队列空的条件是front和rear都指向头结点
return true;
}

int main(){

return 0;

}


###### (二)顺序队列


分配一块连续的存储单元存放队列中的元素,并附设两个指针,队头指针和队尾指针,队头指针指向队头元素,**队尾指针指向队尾元素的下一个位置**;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830155159591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **空队列的条件:Q.front == Q.rear==0;**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830160210132.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **队满的条件:Q.rear==MaxSize**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020083016052030.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 队不满但元素插入完毕:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830160923145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)



> 
> 队列长度:Q.rear-Q.front  
>  对头元素:Q.base[Q.front]  
>  队尾元素:Q.base[Q.rear-1]
> 
> 
> 


顺序队列的假溢出:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830161756708.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)队列中有空闲单元,但新元素进入队列无法在O(1)时间复杂度完成(需要移动元素)  
 解决办法:循环队列


###### (三)循环队列


循环队列:队列的顺序存储结构;  
 把存储队列元素的表从逻辑上视为一个环;


**当Q.rear== Q.front时,如何区分队列空和队列满:**  
 默认处理方式:  
 令队列空间中的一个单元闲置,使得在任何时刻,保持Q.rear和Q.front之间至少间隔一个空闲单元;实际上就是让判满的公式改变了一下,Q.rear+1 == Q.front;而队空是Q.rear==Q.front;



> 
> 队列满: (Q.rear+1)%MAXSIZE == Q.front  
>  队列空: Q.rear==Q.front  
>  队列长度: (Q.rear - Q.front+ MAXSIZE)% MAXSIZE
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200830163115680.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 循环队列



#include
#include
#include

#define MAXSIZE 100

using namespace std;

//循环队列的存储结构
typedef struct{
QElemType *base;
int front;
int rear;
}SqQueue;

//初始化循环队列
bool InitQueue(SqQueue &Q){
Q.base=(QElemType *)malloc(MAXSIZE*sizeof(QElemType));
if(!Q.base)
return false;
Q.front=Q.rear=0;
return true;
}

//入队
bool EnQueue(SqQueue &Q,QElemType e){
//将元素e插入队列Q的队尾
if((Q.rear+1)%MAXSIZE==Q.front)
return false;
Q.base[Q.rear]=e;
Q.rear=(Q.rear+1)%MAXSIZE;
return true;
}

//出队
bool DeQueue(SqQueue &Q,QElemType &e){
//删除队列Q的队头元素并用e带回
if(Q.front==Q.rear)
return false;
e=Q.base[Q.front];
Q.front=(Q.front+1)%MAXSIZE;
return true;
}

int main(){
return 0;
}


###### (四)队列的应用


**1.层次遍历二叉树**  
 **2.解决主机与外设之间速度不匹配的问题:缓冲区**  
 **3.多用户资源竞争问题:CPU的分时**


#### CHAPTER3 串


串:零个或多个字符组成的有限序列;  
 空串:长度为**零**的串;  
 空白串:仅由一个或多个**空格**组成的串;  
 空串是任意串的子串,任意串是其自身的子串;


子串的定位运算成为模式匹配或串匹配;


串默认用顺序存储;



#include
#include
#include

#define MaxStrLen 256; //预定义最大串长

using namespace std;

//串的定长顺序存储表示
typedef struct{
char ch[MaxStrLen];//每个分量存储一个字符
int length;//串的实际长度
}Sstring;

//顺序串,求子串
//求串S从第POS个位置起,长度为len的子串sub
bool SubString(Sstring &sub,Sstring s,int pos,int len){
//健壮性
if(pos<1||pos>s.length-len+1||len<0)
return false;
//把主串的值赋给子串
for(int i=pos;i<(pos+2);i++){
sub.ch[i]=s.ch[i];
}
return true;
}

int main(){

return 0;

}


顺序串存储存在的问题:  
 空间大小固定,运算结果截断;


**子串的定位运算又称为模式匹配或串匹配;**


##### 一、朴素模式匹配算法


思想:从主串、模式串(子串)的第一个位置开始比较(i=1,j=1),若相等,则 i,j 各自+1,然后比较下一个字符。若不等,主串指针回溯到上一轮比较位置的下一个位置,子串回溯到1,再进行下一次比较。**(i=i-(j-1)+1)**



#include
#include
#include

#define MaxStrLen 256; //预定义最大串长

using namespace std;

//串的定长顺序存储表示
typedef struct{
char ch[MaxStrLen];//每个分量存储一个字符
int length;//串的实际长度
}Sstring;

//朴素模式匹配 S:主串 T:子串
int Index(Sstring S,Sstring T){
int i=j=0;
while(i<=S.length&&j<=T.length){//在主、子串有效长度内
if(S.ch[i]==T.ch[j]){
i++;
j++;//继续比较后续字符
}
else{//指针回溯
i=i-j+2;
j=1;
}
}
if(j>T.length)
return i-T.length;
//为啥是i-T.length而不是i-T.length+1;
//因为最后一个相等之后,i,j还会执行一次自增操作
else return 0;//匹配失败
}

int main(){

return 0;

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200903150430635.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 匹配成功的最好时间复杂度:**O(m)**


* 刚好第一个就匹配上了,总对比次数为子串长度


匹配失败的最好时间复杂度:**O(n-m+1)=O(n-m)=O(n)**


* 匹配成功之前,每一个与第一个字符都匹配失败;


最坏时间复杂度:**O(nm-m^2+m)= O(nm)**


* 子串除了最后一个对不上,其余的都能对上,则每次遍历完一边后,又要走回头路;
* 直到匹配成功/失败一共需要比较



> 
> m\*(n-m+1)  
>  m:每次需要移动m次  
>  i需要移动n-m+1次
> 
> 
> 


##### 二、KMP算法


思想:失配时,只有模式串指针回溯,主串指针不变;


next数组求法(手动模拟):


* 前1~j-1个组成串s
* next[j]=s的最长相等前后缀长度+1
* next[1]=0;
* 若位序从0开始,next[j[整体-1


next[j]的含义:实际上是子串的下一个需要比较的位置;



//KMP算法
int Index_KMP(Sstring S,Sstring T,int next[]){
int i=1,j=1;
while(i<=S.length&&j<=T.length){
if(j==0||S.ch[i]==T.ch[j]){
i++;
j++;
}else{
j=next[j];//发生失配时,模式串指针回溯
}
if(j>T.length)
return i-T.length;
else return 0;
}
}


求next[]的算法



//求next数组
//求模式串T的函数值,并存入数组next
int Get_Index(Sstring P,int next[]){
int i=1,j=0;
next[1]=0;
while(i<=P.length){
if((j==0)||P[i]==P[j]){
i++;
j++;
if(P[i]!=P[j])
next[i]=j;
else
next[i]=next[j];
}
else
j=next[j];
}
}


#### CHAPTER4 数组和广义表


##### 一、数组


这部分看王道就行


###### (一)数组


* 数组是有n(>=1)个相同类型的数据元素构成的有限序列; 是线性表的推广;
* 一维数组可以看作一个线性表,二维数组可以看作“数据元素是一维数组”的一维数组;
* 三维数组可以看作“数据元素是二维数组”的一维数组;


###### (二)数组的顺序表示


###### (三)矩阵的压缩存储


**1.特殊矩阵**


**2.稀疏矩阵**  
 找规律算就行


##### 二、广义表(重点)


###### (一)广义表的定义


广义表是线性表的推广。  
 L=(a1,a2,…,an ),n≥0,ai可以是单元素,也可以是一个表。


例如:  
 A = ( ):A是一个空表。  
 B = (e):B只有一个原子。  
 C = (a, (b,c,d) ):C有一个原子和一个子表。  
 D = (A, B, C):D有3个子表。  
 E = (a, E) = (a, (a, (a,…… , ) ):E是一个递归的表。



> 
> 广义表 LS = ( a1, a2, …, an )的结构特点:  
>  广义表中的数据元素有相对次序;  
>  广义表的长度定义为表中的元素个数;  
>  广义表的深度定义为表的嵌套层数;  
>  注意:“原子”的深度为 0 ;  
>  “空表”的深度为 1 。  
>  广义表可以共享;  
>  广义表可以是一个递归的表;  
>  递归表的深度是无穷值,长度是有限值。
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/20201013114456709.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 A = ( ):长度为0,深度为1。  
 B = (e):长度为1,深度为1 。  
 C = (a, (b,c,d) ):长度为2,深度为2 。  
 D = (A, B, C):长度为3,深度为3 。  
 E = (a, E) = (a, (a, (a, …… , ) ):长度为2,深度为∞ 。


任何一个非空广义表 LS = ( a1, a2, …, an) 均可分解表头和表尾两部分:  
 表头(Head):第一个**元素**  
 Head(LS) = a1  
 表尾(Tail):除第一个元素外其余元素构成的**表**  
 Tail(LS) = ( a2, …, an)


D = ( E, F ) = ((a, (b, c)), F )  
 **Head( D ) = E** ; **Tail( D ) = ( F )**  
 Head( E ) = a ; Tail( E ) = ( ( b, c) )  
 Head( (( b, c)) ) = ( b, c) ; Tail( (( b, c)) ) = ( )  
 Head( ( b, c) ) = b ; Tail( ( b, c) ) = ( c )


###### (二)广义表的存储结构


#### CHAPTER5 树


##### 一、基本概念


###### (一)定义


* 树:n个结点的有限集(树是一种递归的数据结构,适合于表示具有层次的数据结构)
* 结点的度:一个结点的孩子个数
* 树的度:树中节点的最大度数
* 两结点之间的路径:由两个结点之间所经过的结点序列构成
* 两结点之间的路径长度:路径上所经过的边的个数
* 树的路径长度是指树根到每个结点的路径长的总和,根到每个结点的路径长度的最大值是树的高度减1


##### 二、二叉树(必考)


###### (一)定义


每个结点至多有两棵子树,且二叉树的子树有左右之分,顺序不能颠倒;


###### (二)性质


* 一个有n个结点的完全二叉树的高度H=[log(n)]+1  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200911124539640.png#pic_center)


###### (三)存储结构


**1.顺序存储**  
 非完全二叉树不适合顺序存储  
 **2.链式存储**  
 **二叉链表:** 每个结点两个指针域;  
 **三叉链表:** 就多了个指向双亲结点的指针域;


###### (四)用二叉树表示表达式


按运算顺序构造二叉树,然后进行先中后序遍历即可;  
 用**栈**对后缀表达式求值:遇到操作数就进栈,遇到操作符就从栈顶弹出两个操作数进行运算,最后运算结果入栈,循环至栈空;


##### 三、遍历二叉树和线索二叉树


###### (一)层序遍历—队列


先根,后子树,先左子树,后右子树;某一结点出队后,将该结点的子树的根节点入队后,再将队头元素出队;


###### (二)先中后序遍历(必考)



#include
#include
#include

using namespace std;
//二叉树链式存储
typedef struct BiTNode{
ElemType data;
struct BiTNode *Lchild,*Rchild;
}BiTNode,*BiTree;//BiTree指向结构体的指针 BiTNode指向结构体的变量

//层序遍历
void Cengxu(BiTNode *root){
InitQueue(Q);
EnQueue(Q,root);
while(!EmptyQueue){
DeQueue(Q,p);//队头元素出栈
visit§;
EnQueue(Q,p->lChild);
EnQueue(Q,p->rChild);
}

}

//二叉树先序遍历(递归)
void PreOrder(BiTNode *root){
if(root != NULL){
cout<data;
PreOrder(root->Lchild);
PreOrder(root->Rchild);
}
}

//中序遍历二叉树(递归)
void InOrder(BiTNode *root){//root指向根
if(root!=NULL){
InOrder(root->Lchild);
cout<data;
InOrder(root->Rchild);
}
}

//后序遍历二叉树(递归)
void PostOrder(BiTNode *root){
if(root!=NULL){
PostOrder(root->Lchild);
PostOrder(root->Rchild);
cout<data;
}
}

//先序遍历(非递归) 这里的node就是BiTNode 懒得改了 非重点
void preOrder(Node root) {
if(root==NULL) return null;
Node* p=root;
Stack<Node* > s; //建立一个栈 存储node类型
while(!s.empty() || p){
if§{
cout<data;
s.push§;
p=p->lchild;
}
else{
p=s.top();
s.pop();
p=p->rchild;
}
}

}

//中序遍历(非递归)
void InOrder_1(BiTNode *root){
InitStack(S);//初始化栈
BiTNode *p;
Push(&S,root);//根指针入栈
While(!StackEmpty(S)){
while(GetTop(S,p)&&p){
Push(S,p->Lchild);//向左走到头
}
Pop(S,p);//空指针退栈
if(!StackEmpty(S)){
Pop(S,p);
cout<data;//访问结点
Push(S,p->Rchild);//向右
}
}
}

int main(){
return 0;
}


###### (三)线索二叉树(低概率考点)


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200917224817603.png#pic_center)  
 tag == 0:指向子树的根节点;  
 tag == 1:指向前驱或后继;  
 **1.中序线索二叉树(非重点)**  
 思路:在有左子树的情况下一直往左走,直到走到最左下的结点;



//中序线索二叉树上找指定结点的后继:
BiThrTree inordernext(BiThrTree p)
{
if (p->rtag1) return(p->Rchild);
else {
q=p->Rchild;
while (q->ltag
0) q=q->Lchild;
return(q);
}
}


**2.后序线索二叉树(掌握算法)**


思路:


* 若p所指结点是整棵二叉树的根结点,则无后继结点;
* 若p->Rtag=1,则p->Rchild指向其后继结点;
* 若p->Rtag=0://P所指结点有右子树  
 1.若p所指结点是其父结点f的右孩子,则其父结点f是其后继;  
 2.若p所指结点是其父结点f的左孩子:  
 ⅰ 若p所指结点没有右兄弟,则其父结点f是其后继;  
 ⅱ 若P有右兄弟,则其后继结点是其父的右子树上后序遍历得到的第一个结点。



//在后序线索二叉树上查找指定结点的后继;
BiThrTree postorder_next(BiThrTree p)
{
if (p->Rtag == 1) return(p->Rchild);
else {
查找p所指节点的父结点f;
if (p == f->Rchild) return f;
if (p == f->Lchild && f->Rtag == 1) return f;
q = f->Rchild;
while (q->Ltag == 0 || q ->Rtag == 0) {
if (q->Ltag == 0)
q = q->Lchild;
else
q = q->Rchild;
}
return(q);
}
}


##### 四、树、森林


###### (一)树的存储结构


1.**双亲表示法:**  
 采用一组地址连续的存储单元存储树的结点,通过保存每个结点的双亲结点的位置,表示树中结点之间的结构关系。(eg.a为根节点)




| data | parent |
| --- | --- |
| a | 0 |


2.**孩子表示法**  
 通过保存每个结点的孩子结点的位置,表示树中结点之间的结构关系。  
 (类似于链表)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200920195620673.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


3.**孩子兄弟表示法**  
 用二叉链表存储树。链表的两个指针域分别指向该结点的第一个孩子结点和其右边的下一个兄弟结点。**(左孩子,右兄弟)**


###### (二)树、森林、二叉树的相互转换(必考)


工具:孩子兄弟表示法


###### (三)树、森林遍历


树的**先根**遍历等同于对转换所得的二叉树进行**先序遍历**;  
 树的**后根**遍历等同于对转换所得的二叉树进行**中序遍历**;


森林的**先序**遍历等于对转换所得的二叉树进行**先序遍历**;  
 森林的**中序**遍历等于对转换所得的二叉树进行**中序遍历**;


##### 五、哈夫曼树及应用


###### (一)定义


* 哈夫曼树(最优二叉树):带权路径长度最短的树;
* 路径和路径长度:从树中的一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称作路径长度;
* 结点的带权路径长度:从根到该结点的路径长度与该结点权的乘积称为结点的带权路径长度;
* 树的带权路径长度:树中所有叶子的带权路径长度之和称为树的带权路径长度(WPL);


note:构建哈夫曼树时,都是两个两个合在一起的,所以没有度为一的结点,即n1=0;


###### (二)哈夫曼算法构造最优二叉树


1. 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和;
2. 在F中删除这两棵树,同时将新得到的二叉树加入F中;
3. 重复1和2,直到F中只含一棵树为止。这棵树便是最优二叉树;


哈夫曼树适合采用顺序结构:已知叶子结点数n0,且n1=0,则总结点数为2n2+1(或2n0-1),且哈夫曼树构造过程需要不停地修改指针,用链式存储的话很容易造成指针偏移;


构造哈弗曼树的算法,会填空就行;


#### CHAPTER6 图


##### 一、定义


1. 图:顶点集和边集构成的二元组
2. 分为无向图和有向图
3. 无向完全图: 把能连起来的边都连起来:1+2+3+·····+n-1=n(n-1)/2
4. 有向完全图:有来有回:n(n-1)
5. 邻接点:边的两个顶点互为领接点
6. 顶点V的度=与V相关联的边的数目(有向图中:度=入度+出度);图的所有顶点度数之和:2\*e(e为边数)
7. 路径:从一个点到另一个点所经过的顶点序列
8. 网络(网):若图中的每条边都有权,这个**带权图**被称为**网**;
9. 长度(无权图):沿路径所经过的边数成为该路径的长度;
10. 长度(有权图):取沿路径各边的权之和作为此路径的长度;
11. 简单路径:路径中的顶点不重复出现;
12. 简单回路:由简单路径组成的回路;
13. 连通图:无向图(有向图)中任意两个顶点之间都是连通的,称为连通图(强连通图);
14. 连通分量:无向图G中的**极大连通子图**称为G的连通分量;对任何连通图而言,连通分量就是其自身;
15. 强连通分量:针对于有向图;
16. 生成树:一个连通图的生成树是一个**极小连通子图**,它含有图中**全部顶点**,但只有足以构成一棵树的**n-1条边**;(多加一条边就会形成一个环)



> 
> 重要区分:  
>  **极大连通子图**:**无向图**的连通分量,极大即要求该连通子图包含其所有的边;非连通图(无向图)有多个极大连通子图,即多个连通分量;连通图(无向图)只有一个极大连通子图,即他本身;  
>  **极小连通子图**:既要保持图连通,又要使得边数最少的子图;例如,生成树;
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200921202315256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


##### 二、图的存储结构


图的存储结构至少要保存两类信息:顶点的数据和顶点间的关系;


###### (一)图的数组表示法


在数组表示法中,用邻接矩阵表示顶点间的关系



#include
#include
#include

#define MaxVnum 50

using namespace std;

//图的数组表示法定义
//定义一个二维数组来表示邻接矩阵
typedef double AdjMatrix[MaxVnum] [MaxVnum];

typedef struct{
int vexnum,arcnum;//顶点数和边数
AdjMatrix arcs;//邻接矩阵
}Graph;

int main(){
Graph G;
return 0;
}


**数组表示法的特点:**  
 //无向图


1. 无向图的邻接矩阵是对称矩阵,同一条边表示了两次;
2. 顶点v的度:等于二维数组对应行(或列)中值为1的元素个数;
3. 判断两顶点v、u是否为邻接点:只需判二维数组对应分量是否为1;
4. 顶点不变,在图中增加、删除边:只需对二维数组对应分量赋值1或清0;
5. 设图的顶点数为 n ,用有n个元素的一维数组存储图的顶点,用邻接矩阵表示边,则G占用的存储空间为:n+n2;图的存储空间占用量只与它的顶点数有关,与边数无关;适用于边稠密的图;


//有向图


1. 有向图的邻接矩阵不一定是对称的;
2. 顶点v的**出度**:等于二维数组对应**行**中值为1的元素个数;
3. 顶点v的**入度**:等于二维数组对应**列**中值为1的元素个数;


###### (二)邻接表


顶点:通常按编号顺序将顶点数据存储在一维数组中  
 关联同一顶点的边:用线性链表存储


**网的邻接表表示:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200922165458127.png#pic_center)


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200922165742473.png#pic_center)



#include
#include
#include

#define MaxVnum 50

using namespace std;

//表结点结构
typedef struct ArcNode{
int adjvex;
double weight;
struct ArcNode *nextarc;
}ArcNode;

//头结点结构
typedef struct{
VertexType data;
ArcNode *firstarc;
}AdjList[MaxVnum];

//图
typedef struct{
int vexnum,arcnum;
AdjList vertexes;
}AGraph;

int main(){
AGraph G;
return 0;
}


##### 三、图的遍历(重要)


图的遍历:从图的某个顶点出发,访问图中的**所有**顶点,且使每个顶点**仅被访问一次**;  
 深度优先搜索遍历(DFS)、广度优先搜索遍历(BFS);


###### (一)DFS


key points:递归、栈;类似于树的先序遍历


**基本思想:**


1. 访问顶点v;
2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。


**手动模拟:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200927211435465.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)**伪代码:**



#include
#include
#include

using namespace std;

bool visited[MaxVnum];//访问标记数组

//邻接表或者邻接矩阵存储图

//DFS
void DFSTraverse(Graph G){
for(int v=0;v<G.vexnum;++v)//vexnum:顶点数
visited[v]==false;//第一个for循环,初始化访问标记数组
for(int v=0;v<vexnum;++v)//第二个for循环,从v=0开始对图进行DFS
if(visited[v]==false)//此条件判断语句可计算图的连通分量个数
DFS(G,v);
}
void DFS(Graph G,int v){
visit(v);//访问顶点v;
visited[v]=true;//设已访问标记
for(w为v的第一个领接点;w存在;w取v的下一个领接点)
if(visited[w]==false)
DFS(G,w);
}

int main(){

return 0;

}


**算法执行过程图解:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200928184218105.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **复杂度分析:**  
 **遍历图**的过程实质上是**对每个顶点查找其邻接顶点**的过程,所以耗费的时间取决于所采用的存储结构;  
 **邻接链表表示**:查找每个顶点的邻接点所需时间为O(e),e为边(弧)数,算法时间复杂度为O(n+e);  
 **数组表示**:查找每个顶点的邻接点所需时间为O(n2),n为顶点数,算法时间复杂度为O(n2);


###### (二)BFS


key points:队列,类似于树的层次遍历;


基本思想:从图中某顶点vi出发:


1. 访问顶点vi ;
2. 访问vi 的所有未被访问的邻接点w1 ,w2 , …wk ;
3. 依次从这些邻接点(在步骤②中访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问;


tips:  
 为实现3,需要保存在步骤2中访问的顶点,而且访问这些顶点的邻接点的顺序为:先保存的顶点,其邻接点先被访问。


**手动模拟:**  
 首先从v1开始,v1入队,访问v1,visited[v1]=T;v1出队,v1的邻接点v2、v3入队,对v2、v3进行同等操作;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200928195952161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 **伪代码:**



#include

using namespace std;

void BFSTraverse(Graph G){
for(v=0;v<G.vexnum;v++)
visited[v]=false;//初始化标记数组
InitQueue(Q);//初始化队列
for(v=0;v<G.vexnum;v++){
if(visited[v]==false){
EnQueue(Q,v);
visited[v]=true;
while(!Empty(Q)){
DeQueue(Q,u);//队头元素出队给Q
for(w取u的第一个邻接点;w存在;w取u的下一个邻接点){
if(visited[w]==false){
EnQueue(Q,w);
visited[w]=true;
}
}
}
}
}
}

int main(){
return 0;
}


**复杂度分析:**  
 同DFS


##### 四、图的应用(手动模拟)


迪杰斯特拉、弗洛伊德掌握算法  
 prim、克鲁斯卡尔、破圈法,都是贪心的思想


###### (一)最小代价生成树-手动模拟


最小生成树的形式不是唯一的,但权值的和总是相同的;


为啥要求最小生成树:最小生成树是代价最小的,例如要在多个村庄之间修路,怎样使路径想通且代价最小,就应该考虑最小生成树;


求最小生成树所用到的性质:



> 
> 最小生成树的MST性质:  
>  假设G=(V,E)是一个连通网络,U是V中的一个真子集,若存在顶点u∈U和顶点v∈V-U 的边(u,v)是一条具有最小权的边,则必存在G的一棵最小生成树包括这条边(u,v)。
> 
> 
> 


###### 1.**Prim算法求最小代价生成树**——不断加点的过程


**算法思想:**  
 普里姆算法构造最小生成树的过程是从一个顶点U={u0}作初态,不断寻找与U中顶点相邻且代价最小的边的另一个顶点,扩充到U集合直至U=V为止。


**注:**


1.“与U之外的顶点 ”就保证了在构造最小生成树的过程中,不会有环形成;


1. “扩充到U集合直至U=V为止”就保证了图中的所有顶点均被包含进来了;  
 **手动模拟:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200928213320300.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)**算法实现**(顶多出填空题)  
 要解决的问题:
2. 顶点集合如何表示?–closedge
3. 最小边如何选择?–lowcost里边非0 的最小边
4. 一个顶点加入U集合如何表示?–令lowcost=0



//定义数组
/*
1.adjvex:V-U中的顶点,也是i的邻接点;
2.lowcost:当前顶点相连的最小代价的边
3.closedge[i].adjvex=k:表示从U和V-U中各选一个点,组成边(i,k)
4.顶点i加入U集合时:closedge[i].lowcost=0
*/
struct {
int adjvex;
double lowcost;
}closedge[MAX_VERTEX_NUM];

void MiniSpanTree_PRIM (Graph G, VertexType u){
//用普里姆算法从顶点u出发构造G的最小生成树
for(j = 0; j < G.vexnum; ++j) //辅助数组初始化
if ( j != u ) closedge[j] = {u, G.arcs[u][j]};
closedge[u].lowcost = 0; //初始,U={u}
for(i = 1; i < G.vexnum; ++i) {
k = minimum(closedge); //求生成树的下一个顶点k
cout << closedge[k].adjvex << G.vexs[k]; //输出生成树的边
closedge[k].lowcost = 0; //顶点k并入U集合
for(j = 0; j < G.vexnum; ++j)
if (G.arcs[k][j] < closedge[j].lowcost)
closedge[j] = {k, G.arcs[k][j]};
}


时间复杂度O(n2)


###### 2.**克鲁斯卡尔求解最小生成树:**——不断加边的过程


**算法思想:**


1. 假设连通网N=(V,E),则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量
2. 在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。


**手动模拟:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200929091944448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


###### 3.破圈法求解最小生成树


——思想简单,实现复杂  
 算法思想:每次选择最长的边进行删除,删除之后,保证图依然是连通的;


补:




| AOV网 | AOE网 |
| --- | --- |
| 顶点——活动 | 顶点——事件 |
| 有向边——活动的先后关系 | 有向边——活动,边的权值表示完成该活动的开销 |
| 侧重于表示活动的前后次序 | 除表示活动的先后次序外,还表示活动的持续时间 |
| 求解工程流程是否合理 | 解决工程所需最短时间及哪些子工程拖延会影响整个工程按时完成等问题 |


###### (二)拓扑排序


###### 1.AOV网


**AOV网:** 用**顶点**表示**活动**,**边**表示**活动的顺序关系**的有向图称为AOV网;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200929101433757.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 特点:若在有向图中有弧<v,u>,则称顶点v是顶点u 的前趋,那么施工计划中顶点v 也排在u之前。也称u是v的后继。


**一个AOV网不应该存在环**,因为存在环意味着某项活动的进行应该以本活动的完成作为先决条件,会死锁。


**手动快速输出拓扑序列:**  
 每次都输出入度为0 的点,然后去掉这个点继续按这个规则输出;


###### 2.拓扑排序-手动模拟


1. 拓扑排序:将有向图中的顶点排成一个序列。
2. 拓扑序列:有向图D的一个顶点序列称作一个拓扑序列。如果该序列中任两顶点v 、u ,若在D中v是u前趋,则在序列中v也是u前趋。


**拓扑排序方法:**


1. 在有向图中选一个无前趋的顶点v,输出之;
2. 从有向图中删除v及以v为尾的弧;
3. 重复1、2,直接全部输出全部顶点或有向图中不存在无前趋的结点时为止;


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200929102146444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)**算法不必掌握**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200929102305512.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


###### (三)关键路径-手动模拟


**AOE网**:顶点表示事件,有向边表示活动,边上权值表示完成该活动的开销,称为用边表示活动的网络,即AOE网;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930101714295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)注:  
 **事件Vi的最早发生时间是是源事件V1到Vi的最长路径长度;**  
 活动的最早开始时间=弧尾事件的最早发生时间  
 活动的最晚开始时间=弧头事件的最晚发生时间-边的权值  
 边的权值即活动的持续时间


**手动模拟:** 掌握表格  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020093010192666.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930102012299.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


###### (四)最短路径-算法


路径长度:路径上的边数、路径上边的权值之和


最短路径:两结点间权值之和最小的路径


求解最短路径的算法所依赖的性质:



> 
> 两点之间的最短路径,也包含了路径上其他顶点间的最短路径;  
>  (毕竟每一段最短,加起来才最短)
> 
> 
> 


###### 1.单源最短路径


即求图中某顶点到其他各顶点的最短路径:DJIKSTRA算法  
 算法思想:按路径长度递增顺序求解最短路径;本质:贪心  
 算法步骤:设V0是起始源点,S是已求得最短路径的终点集合


1. V-S = 未确定最短路径的顶点的集合, 初始时 S={V0},长度最短的路径是**边数为1**且权值最小的路径
2. 下一条长度最短的路径:  
 ① Vi ∈ V - S ,先求出V0 到Vi 中间只经 S 中顶点的最短路径;  
 ② 上述最短路径中长度最小者即为下一条长度最短的路径;  
 ③ 将所求最短路径的终点加入S 中;
3. 重复2直到求出所有终点的最短路径;


**手动模拟:** 会写表格  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200930104505279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)**算法:**——尽量掌握  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001104250621.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001104355984.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/2020100110441056.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


###### 2.每对顶点间的最短路径


方法一:每次以一个顶点为源点,重复执行迪杰斯特拉算法n次,求得每一对顶点之间的最短路径。


方法二:**FLOYD算法**——以邻接矩阵作为图的存储结构


**手动模拟:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001140219850.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001140233784.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)**算法:**——尽量掌握  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001140452224.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001140505718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNjU4NTE1,size_16,color_FFFFFF,t_70#pic_center)


#### CHAPTER7查找


##### (一)静态查找


###### 平均查找长度ASL


![在这里插入图片描述](https://img-blog.csdnimg.cn/2020100316024389.png#pic_center)  
 p:查找到该元素的概率  
 c:比较次数


###### 1.顺序查找


一般选择从后往前查找


###### 1.1无序表的静态查找


**将0号单元设置为“监视哨”** 的目的是使得代码内的循环不必判断数组是否会越界,因为满足i==0时,循环一定会跳出,从而减少不必要的循环语句,进而提高程序效率;



#include
#include
#include

using namespace std;

typedef struct{
KeyType key;
OtherInfoType info;
}ElemType;

typedef struct{
ElemType *elem;//数据元素存储空间基址,建表时,按实际长度分配,0号单元留空
int length;//表长
}SSTable;

//在无序表中查找元素key所在的位置,查找成功则返回元素在表中的位置,否则返回0
int Sq_search(SSTable ST,KeyType key){
int i=ST.length;
ST.elem[0].key=key;//监视哨:下标为0的位置存放待查找的元素
while(ST.elem[i].key!=key) i–;
return i;
}

int main(){

return 0;

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20201003161826855.png#pic_center)——查找失败只有一种可能性:走到监视哨了;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201003161835735.png#pic_center)


###### 1.2有序表的静态查找



#include
#include
#include

using namespace std;

typedef struct{
KeyType key;
OtherInfoType info;
}ElemType;

typedef struct{
ElemType *elem;//数据元素存储空间基址,建表时,按实际长度分配,0号单元留空
int length;//表长
}SSTable;

//假设表中元素按递增排序;查找成功时返回下标;失败时返回0
int Sq_search(SSTable ST,KeyType key){
int i=n;
ST.elem[0].key=key;//监视哨:下标为0的位置存放待查找的元素
while(ST.elem[i].key>key) i–;
if(ST.elem[i]==key){
return i;
}
return 0;
}

int main(){

return 0;

}


ASL(成功)和无序表一样;  
 查找失败:n个元素,就由n+1个空隙,即n+1种出错的可能;这里从代码就可看出,若key大于最大值或小于最小值,都是查找失败,所以就有n+1种出错的可能;在n+1处查找失败要比较1次,在第n个和第n-1个元素之间失败要比较两次;  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201003165635614.png#pic_center)



![img](https://img-blog.csdnimg.cn/img_convert/f0acb0b6a13f7046609391d89ae3eee5.png)
![img](https://img-blog.csdnimg.cn/img_convert/02e10d1a2ca40010449ef443d95e6b94.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

lude<cstdio>
#include<cstdlib>

using namespace std;

typedef struct{
	KeyType key;
	OtherInfoType info;
}ElemType;

typedef struct{
	ElemType \*elem;//数据元素存储空间基址,建表时,按实际长度分配,0号单元留空
	int length;//表长 
}SSTable;

//在无序表中查找元素key所在的位置,查找成功则返回元素在表中的位置,否则返回0 
int Sq\_search(SSTable ST,KeyType key){
	int i=ST.length;
	ST.elem[0].key=key;//监视哨:下标为0的位置存放待查找的元素
	while(ST.elem[i].key!=key) i--;
	return i; 
}

int main(){
	
	return 0;
}

在这里插入图片描述——查找失败只有一种可能性:走到监视哨了;
在这里插入图片描述

1.2有序表的静态查找
#include<iostream>
#include<cstdio>
#include<cstdlib>

using namespace std;

typedef struct{
	KeyType key;
	OtherInfoType info;
}ElemType;

typedef struct{
	ElemType \*elem;//数据元素存储空间基址,建表时,按实际长度分配,0号单元留空
	int length;//表长 
}SSTable;

//假设表中元素按递增排序;查找成功时返回下标;失败时返回0 
int Sq\_search(SSTable ST,KeyType key){
	int i=n;
	ST.elem[0].key=key;//监视哨:下标为0的位置存放待查找的元素
	while(ST.elem[i].key>key) i--;
	if(ST.elem[i]==key){
		return i;
	} 
	return 0; 
}

int main(){
	
	return 0;
}

ASL(成功)和无序表一样;
查找失败:n个元素,就由n+1个空隙,即n+1种出错的可能;这里从代码就可看出,若key大于最大值或小于最小值,都是查找失败,所以就有n+1种出错的可能;在n+1处查找失败要比较1次,在第n个和第n-1个元素之间失败要比较两次;
在这里插入图片描述

[外链图片转存中…(img-AKyut8KI-1715395112522)]
[外链图片转存中…(img-dmJYfENy-1715395112523)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值