A B C…
B直接前驱元素: A
B直接后驱元素:C
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域
双向链表的插入和删除:
插入:
s -> prior = p;
s -> next = p -> next;
p -> next -> prior = s;
p -> next = s;
删除:
p -> prior -> next = p -> next;
p -> next -> prior = p ->prior;
free(p);
线性表
栈的插入操作称为入栈,反之称为出栈.
栈的一些基本操作:
ADT 栈(stack)
Data
同线性表.元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitStack(*S)
DestroyStack(*S)
ClearStack(*S)
StackEmpty(*S)判断堆栈是否为空,返回值是ture和false
GetTop(*S)
Push(*S, e)插入e值
POp(*S, *e)删除栈顶元素并用e返回其值
StackLength(S)
利用下标为0的数组一端作为栈的栈底
判断空栈的条件是top=-1
**销毁一个栈: **释放内存
**清空一个栈: **只改变地址
两栈共享空间结构:使用数组同时实现两个栈,即栈1和栈2;栈1为空时,栈1的栈顶指针指向-1;栈2为空时,栈2 的栈顶指针指向MAXSIZE;栈1和栈2添加元素时,都会向数据中间靠拢,当栈1的指针+1等于栈2的指针的时候,栈满。
栈顶指针和单链表的头指针合二为一.
9+(3-1)*3+10/2 这是中缀表达式
9 3 1 - 3 * + 10 2 / + 这是后缀表达式
栈的四则运算最主要的就是让中缀表达式转换为后缀表达式,然后再进行一些其他的操作.
队列是只允许在一端进行插入操作,在另一端进行删除操作的线性表
如何判断队列是否已满?利用下面共指来判断
( r e a r − f r o n t + Q u e n e S i z e ) % Q u e n e S i z e = = f r o n t (rear-front+QueneSize) \% QueneSize == front (rear−front+QueneSize)%QueneSize==front
串是由零个或者多个字符组成的有限序列,又名叫字符串.
串是由零个或者多个字符组成的有限序列,又名叫字符串
一般记作:
s = " a 1 a 2 a 3...... a n " s = "a1a2a3......an" s="a1a2a3......an"
所谓序列,是指相邻的字符串之间具有前驱和后继的关系
ADT 串 (String)
Data
串中的元素仅由一个字符组成,相邻元素具有前驱和后继关系.
Operation
StrAssign (&T, chars)
初始条件:chars是字符串常量。
操作结果:生成一个其值等于chars的串T。
StrCopy (&T, S)
初始条件:串S存在。
操作结果:由串S复制得串T。
StrEmpty(S)
初始条件:串S存在。
操作结果:若S为空串,则返回TRUE,否则返回FALSE。
StrCompare(S, T)
初始条件:串S和T存在。
操作结果:若S>T,则返回值>0;若S=T,则返回值=0;若S < T,则返回值 < 0。
StrLength(S)
初始条件:串S存在。
操作结果:返回S的元素个数,称为串的长度。
ClearString (&S)
初始条件:串S存在。
操作结果:将S清为空串。
Concat (&T, S1, S2)
初始条件:串S1和S2存在。
操作结果:用T返回由S1和S2联接而成的新串。
SubString(&Sub, S, pos, len)
初始条件:串S存在,1≤pos≤StrLength(S)且0≤len≤StrLength(S)-pos+1
操作结果:用Sub返回串S的第pos个字符长度为len的子串。
Index(S, T, pos)
初始条件:串S和T存在,T是非空串,1≤pos≤StrLength(S)。
操作结果:若主串S中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置;否则函数值为0。
Replace (&S, T, V)
初始条件:串S, T和V存在,T是非空串。
操作结果:用V替换主串S中出现的所有与T相等的不重叠的子串。
StrInsert (&S, pos, T)
初始条件:串S和T存在, 1≤pos≤StrLength(S)+1。
操作结果:在串S的第pos个字符之前插入串T。
StrDelete (&S, pos, len)
初始条件:串S存在, 1≤pos≤StrLength(S)-len+1。
操作结果:从串S中删除第pos个字符起长度为len的子串。
DestroyString (&S)
初始条件:串S存在。
操作结果:串S被销毁。
endADT
给定一个主串S及一个模式串P,判断模式串是否为主串的子串;若是,返回匹配的第一个元素的位置(序号从1开始),否则返回0;如S=“abcd”,P=“bcd”,则返回2;S=“abcd”,P=“acb”,返回0。
https://blog.csdn.net/qq_37969433/article/details/82947411
朴素算法理解简单,但两个串都有依次遍历,时间复杂度为O(n*m),效率不高。由此有了KMP算法。
一般的,在一次匹配中,我们是不知道主串的内容的,而模式串是我们自己定义的。
朴素算法中,P的第j位失配,默认的把P串后移一位。
但在前一轮的比较中,我们已经知道了P的前(j-1)位与S中间对应的某(j-1)个元素已经匹配成功了。这就意味着,在一轮的尝试匹配中,我们get到了主串的部分内容,我们能否利用这些内容,让P多移几位(我认为这就是KMP算法最根本的东西),减少遍历的趟数呢?
树是n个结点的有限集合.n=0时称为空树.在任意一棵非空树中:
http://data.biancheng.net/view/30.html
将树中的每个结点的孩子结点排列成一个线性表,用链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在一个线性表中,这样的表示方法就是孩子表示法。
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点。可以将两种表示方法合二为一,存储效果如图 :
#define TElemType int
#define Tree_Size 100
//孩子表示法
typedef struct CTNode{
int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode * next;
}*ChildPtr;
typedef struct {
TElemType data;//结点的数据类型
ChildPtr firstchild;//孩子链表的头指针
}CTBox;
typedef struct{
CTBox nodes[Tree_Size];//存储结点的数组
int n,r;//结点数量和树根的位置
}CTree;
任意一棵树, 他的结点的第一个孩子如果存在就是唯一的, 他的右兄弟如果存在也是唯一的.因此我们设置两个指针,分别指向该结点的第一个孩子和此节点的右兄弟
data | firstchild | rightsib |
---|---|---|
数据域 | 孩子域 | 兄弟域 |
上下两个表格一个意思
这个表示方法就是把一个复杂的树变成了二叉树
https://www.jianshu.com/p/bf73c8d50dc2
二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树), 或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成
二叉树的特点:
斜树: 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
满二叉树: 在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树
完全二叉树: 对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树
特点:
1)叶子结点只能出现在最下层和次下层。
2)最下层的叶子结点集中在树的左部。
3)倒数第二层若存在叶子结点,一定在右部连续位置。
4)如果结点度为1,则该结点只有左孩子,即没有右子树。
5)同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。
1.二叉排序树
二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
2.平衡二叉树
(AVL)平衡二叉树是一种二叉排序树,其中每个结点的左子树和右子树的高度差至多等于1。它是一种高度平衡的二叉排序树。意思是说,要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。
平衡二叉树是一种二叉排序树。也就是二叉排序树包含了平衡二叉排序树,其次它要满足后面的约束条件,就是每个结点的左子树和右子树的高度差不超过1
https://blog.csdn.net/why_still_confused/article/details/51532222
链表每个结点包含一个数据域和两个指针域:
其中data是数据域,lchild和rchild都是指针域,分别指向左孩子和右孩子。
/*二叉树的二叉链表结点结构定义*/
typedef struct BiNode
{
char data; /*结点数据*/
struct BiNode *lchild, *rchild; /*左右孩子指针*/
}BiNode,*BiTree;
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的所有结点, 使得每个结点被访问一次且仅被访问一次。
https://blog.csdn.net/gatieme/article/details/51163010(二叉树建立和遍历的C++代码实现)
根结点 —> 左子树 —> 右子树
左子树 —> 根节点 —> 右子树
左子树 —> 右子树—> 根节点
从根节点开始访问,从上而下逐层遍历,在同一层中,按照从左到右的顺序对结点逐个访问
为什么存在线索二叉树:
二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者用栈辅助实现非递归的遍历。用二叉树作为存储结构时,取到一个节点,只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的前驱或者后继。为了保存这种在遍历中需要的信息,我们利用二叉树中指向左右子树的空指针来存放节点的前驱和后继信息
n个节点的二叉树中含有n+1个空指针域。利用二叉树中的空指针域来存放在某种遍历次序下的前驱和后继,这种指针叫“线索”。这种加上了线索的二叉树称为线索二叉树。
https://blog.csdn.net/linraise/article/details/11745559
1. 将树转换成二叉树:
(1)加线。就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;
(3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。
2. 森林转换为二叉树
森林是由若干棵树组成,可以将森林中的每棵树的根结点看作是兄弟,由于每棵树都可以转换为二叉树,所以森林也可以转换为二叉树。
将森林转换为二叉树的步骤是:
(1)先把每棵树转换为二叉树;
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子结点,用线连接起来。当所有的二叉树连接起来后得到的二叉树就是由森林转换得到的二叉树。
3. 二叉树转化为森林
二叉树转换为树是树转换为二叉树的逆过程,其步骤是:
(1)若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
(2)删除原二叉树中所有结点与其右孩子结点的连线;
(3)整理(1)和(2)两步得到的树,使之结构层次分明。
4. 二叉树转换为森林
二叉树转换为森林比较简单,其步骤如下:
(1)先把每个结点与右孩子结点的连线删除,得到分离的二叉树;
(2)把分离后的每棵二叉树转换为树;
(3)整理第(2)步得到的树,使之规范,这样得到森林。
根据树与二叉树的转换关系以及二叉树的遍历定义可以推知,树的先序遍历与其转换的相应的二叉树的先序遍历的结果序列相同;树的后序遍历与其转换的二叉树的中序遍历的结果序列相同;树的层序遍历与其转换的二叉树的后序遍历的结果序列相同。由森林与二叉树的转换关系以及森林与二叉树的遍历定义可知,森林的先序遍历和中序遍历与所转换得到的二叉树的先序遍历和中序遍历的结果序列相同。
构建哈夫曼树的步骤:
哈夫曼编码:
https://baozouai.com/2019/03/08/%E5%A4%A7%E8%AF%9D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AC%AC%E4%B8%83%E7%AB%A0%20%20%E5%9B%BE/
图(Graph)是由顶点的有穷非空集合和顶点之间的边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
无向图:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边(Edge),用序偶对(Vi,Vj)表示。如果图的任意两个顶点之间的边都是无向边,则称该图是无向图。
上述无向图G1来说,G1=(V1, {E1}),其中顶点集合V1={A,B,C,D};边集合E1={(A,B),(B,C),(C,D),(D,A),(A,C)};
有向图:若从顶点Vi到Vj的边是有方向的,则成这条边为有向边,也称为弧(Arc)。用有序对(Vi,Vj)标示,Vi称为弧尾,Vj称为弧头。如果任意两条边之间都是有向的,则称该图为有向图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEpVicYr-1573180530985)(https://baozou.gitbooks.io/-data-structure/content/assets/152import.png)]
有向图G2中,G2=(V2,{E2}),顶点集合(A,B,C,D),弧集合E2={,,
无向完全图:在一个无向图中,任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有 n ∗ ( n + 1 ) / 2 n*(n+1)/2 n∗(n+1)/2 条边
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzB97B8u-1573180530986)(https://baozou.gitbooks.io/-data-structure/content/assets/155import.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8L4L0N2g-1573180530986)(https://baozou.gitbooks.io/-data-structure/content/assets/157import.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOuSOE7Z-1573180530987)(https://baozou.gitbooks.io/-data-structure/content/assets/160import.png)]
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二位数组(称为邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
网图的邻接表
设图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
数组与邻接表相结合的存储方法称为邻接表
无相图的邻接表
有向图的邻接表
网图(带权值)的邻接表
十字链表 = 邻接表 + 逆邻接表
针对无向图(存储空间压缩时用)
边集数组是由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成
begin是存储起点下标,end是存储终点下标,weight是存储权值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zrNe533V-1573180530987)(https://baozou.gitbooks.io/-data-structure/content/assets/import.png23)]
边集数组关注的是边的集合,在边集数组中要查找一个顶点的度需要扫描整个边数组,效率并不高。因此它更适合对边依次进行处理的操作,而不适合对顶点相关的操作
定义:从图的某一顶点出发访遍图中的其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历
从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到,以上说的只是连通图,对于非连通图,只需要对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDuS4knY-1573180530987)(https://baozou.gitbooks.io/-data-structure/content/assets/import.png24)]
图的深度优先遍历和广度优先遍历算法在时间复杂度上一样,不同之处仅在于对顶点的访问次序不同深度优先算法更适合目标比较明确的。以找到目标为主要目的的情况,而广度优先更适合在不断扩大遍历范围时找到相对最优解的情况
生成树的特点:
最小成本,就是n个顶点,用n-1条边把一个连通图连接起来,并且使得权值的和最小
最小生成树:把构造联通网的最小代价生成树称为最小生成树。最小生成树不唯一,但是最小生成树的权值和一定唯一。
https://www.bilibili.com/video/av36885391/?spm_id_from=333.788.videocard.0(B站)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQVfGhpK-1573180530987)(https://baozou.gitbooks.io/-data-structure/content/assets/import.png25)]
算法实现(C++)
#include
#define MAXINT 6
using namespace std;
//声明一个二维数组,C[i][j]存储的是点i到点j的边的权值,如果不可达,则用1000表示
//借此二维数组来表示一个连通带权图
int c[MAXINT][MAXINT]={{1000,6,1,5,1000,1000},{6,1000,5,1000,3,1000},{1,5,1000,5,6,4},{5,1000,5,1000,1000,2},{1000,3,6,1000,1000,6},{1000,1000,4,2,6,1000}};
void Prim(int n)
{
int lowcost[MAXINT];//存储S中到达对应的其它各点的最小权值分别是多少
int closest[MAXINT];//closest[]数组保存的是未在S中的点所到达S中包含的最近的点是哪一个,如:closest[i]=1表示i最靠近的S中的点是1
bool s[MAXINT];//bool型变量的S数组表示i是否已经包括在S中
int i,k;
s[0]=true;//从第一个结点开始寻找,扩展
for(i=1;i<=n;i++)//简单初始化
{
lowcost[i]=c[0][i];
closest[i]=0;//现在所有的点对应的已经在S中的最近的点是1
s[i]=false;
}
cout<<"0->";
for(i=0;i";
s[j]=true;//添加点j到集合S中
for(k=1;k<=n;k++)//因为新加入了j点,所以要查找新加入的j点到未在S中的点K中的权值是不是可以因此更小
{
if((c[j][k]>c[i][j];
cout<
https://www.bilibili.com/video/av36885495/?spm_id_from=333.788.videocard.0(B站)
本质就是一种贪心算法
算法思想:
两种算法的比较
算法名 | Prime算法 | Kruskal算法 |
---|---|---|
算法思想 | 选择点 | 选择边 |
时间复杂度 | O(n^2) | O(eloge) |
适应范围 | 稠密图 | 稀疏图 |
最短路径:指两个顶点之间经过边上的权值之和最少的路径,并称第一个顶点是源点, 最后一个顶点是终点.
要解决的两类问题:
单源最短路径
https://www.bilibili.com/video/av36886088/?spm_id_from=333.788.videocard.0(B站)
按路径长度递增次序产生最短路径
https://www.bilibili.com/video/av36886554/?spm_id_from=333.788.videocard.0(B站)
所有顶点之间的最短路径
算法思想:
https://www.bilibili.com/video/av36886991/?spm_id_from=333.788.videocard.0(B站)
1.AOV网
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网.
2.AOE网
用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束时间,称这种有向图为边表示活动的网,简称AOE网.
3.AOV网的特点:
4.什么是拓扑排序?:
在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧存在, 则在这个序列中, i一定排在j的前面, 具有这种性质的线性序列称为拓扑有序序列, 相应的拓扑有序排序的算法称为拓扑排序.
5.拓扑排序的方法
一个拓扑序列并不唯一
上述拓扑序列也可以是C9, C10, C11, C6, C1, C12, C4, C2, C3, C5, C7, C8
6.拓扑排序的一个重要应用
检测AOV网中是否存在环
对有向图构造其顶点的拓扑有序序列, 若网中所有顶点都在他的拓扑有序序列中,则该AOV网必定不存在环.
https://www.bilibili.com/video/av36907591/?spm_id_from=trigger_reload(B站)
1.AOE网
用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束时间,称这种有向图为边表示活动的网,简称AOE网.
2.关键路径
把工程计划表示为边表示的活动的网络, 即AOE网, 用顶点表示事件, 弧表示活动, 弧的权表示活动持续时间