数据结构小结

最近在复习,所以就在这写下所有数据结构的理解

1.线性表:
零个或多个数据元素的有限序列;
元素之间是有序的,若有多个元素,则第一个元素只有后继,最后一个只有一个前驱,中间元素有一个前驱和一个后继。
线性表是有限的;

线性存储的要素:
1.存储空间的起始位置
2.线性表的最大容量
3.线性表的当前长度

它的存取性能是O(1)时间内的,是随机存储结构
顺序表的插入和删除操作是很不方便的。性能是O(n)
比较适合于数据的存取,和数据量比较小的场合;

线性表的链式存储: 解决了上述删除和插入的问题。
不仅要存储自身的数据,还要加入一个指针域来存储直接后继的地址
为了方便我们对指针进行操作,会在链表的头部设置一个头结点,数据域可以不存储东西,指针域指向第一个结点的地址。

静态链表 : 用数组描述
还是数据域,还有一个游标(相当于next指针,存放的是该元素的后继在数组下标)

我们把未被使用的数组称为:备用链表
数组的第一个元素,存放备用链表的下标,而数组的最后一个元素存放第一个有数值的元素的下标(相当于头结点的作用)
1.将所有未被使用过的和已经被删除的分量用游标链成一个备用的链表,每当进行插入操作时,就能从备用链表上取得第一个节点作为带插入的新节点。

循环链表 : 将尾指针的NULL,改为指向头结点的

双链表: 数据域和两个指针域,分别指向前驱和后继。
在插入时注意赋值的顺序 (先搞定插入节点的前驱和后继。再搞定后结点的前驱,最后是前结点的后继)

栈:仅在尾部进行插入和删除的操作的 线性表
队列:在一端进行插入操作,在另一端进行删除操作 的线性表

栈顶:是允许进行插入和删除的地方。 后进先出的线性表(LIFO)
当栈存在一个元素时,top=0,通常判断空栈是(top= -1)
两个栈共享空间:(数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的起始端,令一个栈为数组的末端,这样的话,两个栈如果增加元素,就是从两端向中间延伸)

这样的话,不仅要有入栈的元素,还要有栈的标号(表示入哪个栈)
判断空栈,top1=-1 , top2=n 、、
两个栈相遇,栈满 top1+1 = top2

栈的链式存储:把栈顶放在单链表的头部。(对于链栈来说头结点是没有意义的因为已经有了top)
空栈的话现在就是 top=NULL

后缀表达式:是栈的一个应用 。

从左到右遍历表达式的每一个数字和符号,遇到数字就进栈,遇到符号就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,一直到最终结果
中缀表达式转后缀表达式:
从左到右遍历表达式,若是数字就输出,成为后缀表达式的一部分,若是符号,就判断其与栈顶符号的优先级,是右括号或优先级低的,则栈顶元素出栈并输出,并将当前符号进栈,

队列:FIFO先进先出的结构。(比如:客服就是这样,打电话的人多就进行等待)
队列插入,为O(1),而删除的话为O(n)。因为要保证队头不为空,就要移动后面所有元素。

为了解决O(n)。队头的下标不一定非要设置为0,为了避免只有一个元素时,队头和队尾只有一个元素不方便,就让队尾指针指向尾元素的下一个位置。
初始状态:front和rear(队尾指针)都在下标为0处,

循环队列:解决前面空闲位置的利用问题。
把头尾相接。循环利用,

这样的话,当队列是空front==rear,队列是满front==rear
区分的话就设置flag,当flag=0时,队列为空,为1时,队列满

队列的链式存储:队头指针指向头结点。空队列,front和rear都指向头结点。

串:由0个或多个字符组成的有限序列。

**树:**n(n>=0)个结点的有限集,任何一个非空树,有且仅有一个根结点,n>1时,其余结点可分为m个互不相交的有限集T1,T2,TN,每一个结合本身又是一棵树,是子树

结点拥有的子树是:结点的
度为0的节点成为叶节点。终端节点
树的度是树内各节点的最大度
节点的子树称为该结点的孩子
同一个双亲的孩子之间互为兄弟
节点的祖先是从跟节点到该结点所径分支上的所有节点
节点的层次从根开始:第一层
其双亲在同一层的节点互为堂兄弟
树种最大层次称为树的深度或高度
森林是m棵互不相交的树的集合

树的双亲表示法: 除了跟节点外,其余每个节点不一定有孩子但一定有双亲

节点结构: data(数据),指向双亲的指针(该结点双亲在数组中的下标)

typedef struct PTNODE  //节点结构
{
    TypeElem data;   //数据类型
    int parent;
}Ptnode;
typedef struct   //树结构
{
    ptnode nodes[max_size];
    int r,n;   //根的位置,节点数
}Ptree;

由于跟节点是没有双亲的,设置为-1
这里写图片描述

这样的结构很容易就找到各个节点的双亲在哪,但是找孩子的话就得遍历整个树

改进方法:在后面加一个左边孩子的域,称为长子域(没有孩子节点就为-1)

孩子表示法:每个节点有多个指针域,每个指针指向一颗子树的跟节点,(多重链表表示法)
把每个节点的孩子节点都排列起来,以单链表作为存储结构,叶子节点此链表为空,n个头指针又成为一个线性表,放在一维数组中
这里写图片描述

二叉树 :n个节点的有限集合,该集合或者为空集,或者由一个跟节点和两棵互不相交的、分别称为跟节点的左子树和右子树的二叉树组成

每个结点最多有两个子树
左子树和右子树是有顺序的
即使树中只有一颗子树,也是要区分是左子树还是右子树。

二叉树的五种基本形态:

空二叉树
只有一个跟节点
跟节点只有左子树
跟结点只有右子树
跟节点既有左子树,还有右子树

斜树:所有节点只有左子树的二叉树叫左斜树。相反有右斜树。(线性表形式)
满二叉树:所有的节点都有左子树和右子树,并且叶子节点都在同一层上。(非叶子节点的度为2,同样深度的二叉树中此结构的节点数最多)
完全二叉树 :对一个n个结点的二叉树按层序编号,如果编号i的节点和同样深度的满二叉树中编号为i节点在树中的位置相同,就称为完全二叉树

叶子节点只能出现在最下面两层
最下层叶子节点只能出现在“”左部连续位置“”
倒数两层若有叶子节点,只能在右部连续位置
如果节点度为1,只能是存在左子树
同样节点数的二叉树,完全二叉树的深度最小

判断一个二叉树是不是完全二叉树:按照在满二叉树的编号,看此树编号有没有空挡,没有就是完全二叉树

二叉树的性质:二叉树的第i层上至多有2^(i-1)个节点
深度为k的二叉树至多有 2^k -1个节点
对于任何一棵二叉树,终端节点数为n0,度为2的节点数为n2,则n0=n2+1;
具有n个结点的完全二叉树的深度为log2n向下取整+1;
对n个结点的完全二叉树进行层序编号:对任意节点i
有i=1;就是根,若i>1.则其双亲为i/2
若2i>n;则i无左孩子。否则其左孩子为2i
若2i+1 >n;i无右孩子。否则右孩子是2i+1

二叉树 的顺序存储结构:一般只用于完全二叉树(否则造成资源空间浪费)
二叉树的二叉链表 :左孩子右孩子 指针,data域
二叉树的遍历 :指的是从跟节点出发,按照某种次序依次访问二叉树中的节点,使得每个节点被访问一次并且仅被访问一次
已知前序和 中序可以唯一确定一个二叉树
后序和中序可唯一确定一个二叉树
线索二叉树: 我们把指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,
其实线索二叉树就是把二叉树转换为了双向链表(把空指针域利用起来,指向前驱和后继)但是这样做的话我们无法区分左指针指向的是左孩子还是前驱。就要曾加两个标志位lflag和rflag存放的只是0和1,代表o(是孩子)1是前驱或后继

typedef enum{link,Thread} pointerTag; //link=0;是左右孩子指针
//Thread=1;是前驱和后继的线索
typedef struct Bitnode
{
    type data;
    struct Bitnode* lchild,*rchild;
    poniterTag LTAG;
    pointerTag RTAG;
}*Bitree;

线索化的过程就是在遍历的过程中修改空指针的过程

//中序遍历线索化’
Bitree pre;   //始终指向刚访问过的节点
void init(Bitree p)
{
    if(p)
    {
        init(p->lchild);  //递归左子树线索化
        if(!p->lchild)
        {
            p->LTAG=Thread;  //前驱线索
            p->lchild=pre;   //左孩子指针指向前驱
        }
        if(!p->rchild)
        {
            pre->RTAG=Thread;
            pre->rchild=p;  //前驱右孩子指针指向后继(当前p)
        }
        pre=p;
        init(p->rchild);
    }
}

借助二叉链表,树和二叉树可以相互转换。

树转换为二叉树:1.在所有兄弟节点之间加一条线。2.去线(对树中的每个节点只保留与第一个孩子节点的线)3.层次调整。
森林转化为二叉树:1.把每个树转换为二叉树。2.第一课二叉树不动,从第二棵树开始,依次把后一棵二叉树的跟节点作为前一棵树的跟节点的右孩子,用线连接起来,
二叉树转换为树:是其逆过程1.加线(若某节点的左孩子存在,左孩子的n个右孩子节点作为此节点的孩子,并连接起来)2.去线(删除原二叉树中节点与右孩子的连线)3.层次调整
二叉树转换为森林(看这棵二叉树跟节点有没有右孩子)1.从跟节点开始,把与右孩子的连线去除,再看分离后的二叉树,右孩子存在就继续删除2.将每棵分离的二叉树转换为树

树的先根遍历和后根遍历完全可以用二叉树的前序和后序实现(二叉链表作为存储结构)

哈夫曼树:
从树中一个节点到另一个节点之间的分支结构构成两个节点之间的路径,分支数目称为路径长度
树的路径长度就是:从树根到每一个节点的路径长度之和
带权路径长度最小的二叉树称为WPL:哈夫曼树

构造步骤:
1.先把有权值的叶子节点从小到大排列
2.取头两个最小权值的节点作为新节点N1的两个子节点,相对较小的是左孩子,新节点的权值为两者之和
3.将N1代替这两个节点插入排列中,按序
4.重复步骤2,将N1与下一个节点作为子节点构造新的N2
一直重复下去,最后的到最大的节点T,就是跟节点,构造完成

哈夫曼编码:设需要编码的字符集为{d1,d2…dm},各个字符在电文中出现的次数或评率{w1,w2,…wn},以d1,d2,dn,作为叶子节点,以’w1,w2,..wn作为权值构造哈夫曼树,规定左分支为0,右分支为1,则从跟节点到叶子节点的路径分支组成的1,0,就是对应字符编码

图:由顶点的有穷非空集合,和顶点之间的边的集合组成,通常称为G:(V,E), G:表示一个图,V、;是顶点的集合,E:是边的集合

边集可以为空,但V不能为空
图中的元素称之为:顶点,。任意两个顶点之间都可能有关系
若顶点到顶点之间没有方向:无向边,图中任意两个顶点之间无方向用()无序偶对表示,就是无向图
有方向的两条边:有向边,弧;用<>有序偶对表示。
在图中不存在顶点到自身的边,且一条边不重复出现,就称为”简单图”
在无向图中,任意两个顶点都存在边,称为无向完全图。有n个顶点的无向完全图有(n*(n-1))/2条边
在有向图中,任意两个顶点之间存在互为相反的两条弧,称为有向完全图n*(n-1)条边
带权的图称为“网”
对于无向图:和顶点v相关联的边称为v的度,TD(v),边数,其实就是各顶点度和的一半
对于有向图:以v为头的弧数,称为入度。以v为尾的是出度。
第一个顶点到最后一个顶点相同的路径称为回路或环

在无向图中,从v到v’有路径,就叫两者连通,对于图中任意两个顶点是联通的则是连通图
无向图中极大连通子图称为连通分量。(连通子图含有极大顶点数。包含依附于这些顶点的所有边)
有向图:强连通图,强连通分量
连通图的生成树:极小连通子图。含有图中全部n个顶点,却只有构成一棵树的n-1条边,
一个有向图有一个顶点入度为0,其余顶点入度均为1,是一颗有向树
图的存储结构:
1.邻接矩阵:是用两个数组来表示图。一个一维数组来表示图的顶点信息。一个二维数组存储图的边信息
有n个顶点的图,就有n*n的矩阵。
一般无向图的边矩阵为对称的。(aij=aji)
要知道某顶点的度(第i行或第i列的元素之和)
有向图的矩阵不是对称的,入度为第i列,出度为第i行
要是加上权值的话(必须用一个极大值来表示没有权值)
n个顶点e条边的无向网图的创建O(n+n*n+e),邻接矩阵的初始化O(n*n )
对于边数相对顶点较少的图来说太浪费
2.邻接表:
数组与链表结合(相当于树的孩子表示法):顶点用一维数组存储,并且有一个指向第一个邻接点的指针
关于每个顶点vi的邻接点构成的单链表,有两个域,一个存储某顶点的邻接点在顶点数组中的下标
O(n+e)
对于有向图来说关心了出度问题,但是要了解入度就必须遍历整个图才能知道
3.十字链表
把邻接表和逆邻接表结合在一起:
顶点表结构:data,firstin(入边表头指针,指向该顶点入边表的第一个节点),firstout(出边表头指针,指向出边表的第一个节点)
边表结构:tailvex(弧起点在顶点表的下标),headvex(弧终点在顶点表的下标),headlink()入边表指针域,指向终点相同的下一条边,taillink(指向起点相同的下一条边)
4.邻接多重表:
要是想要做删除操作的话,在邻接表中是很麻烦的事情
边表结构:ivex,jvex;是与某条边依附的两个顶点在顶点表的下标,ilink(指向依附顶点ivex的下一条边),jlink(指向依附顶点jvex的下一条边)

边集数组:两个一维数组构成,一个存储点的信息,另一个存储边的信息,边数组的每个数据元素由一条边的起点下标,终点下标和权值组成,
适合对边依次进行处理的操作,不适合对顶点进行处理的操作。

图的遍历:从某个顶点出发,访遍其余顶点,且每个顶点只访问一次,
深度优先遍历: DFS;从图中某个顶点v出发,访问此顶点,然后从未被访问过的邻接点出发深度优先遍历,直到图中所有和v有路径相通的顶点被访问到(对于连通图来说)
对于非连通图,只需要对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历之后,若图中尚有顶点未被访问,则另选一个未被访问的顶点作为起始点重复上述过程,直到图中所有顶点被访问。

typedef int boolean;
boolean visited[MAX];   //数组访问标志
void DFS(MGraph G,int i) {
    int j;
    visited[i]=TRUE;
    printf("%c",G.vex[i]);
    for(j=0;j<G.numVetexes;j++)
        if(G.arc[i][j]==1&& !visited[j])
            DFS(G,j);
}

void DFStraver(MGraph G)
{
    int i;
    for(i=0;i<G.numvertexes;i++)
        visited[i]=FALSE;
    for(i=0;i<G.numvertexes;i++)
        if(!visited[i])
            DFS(G,i);
}

广度优先遍历:BFS;类似于树的层序遍历。而深度优先遍历类似于树的前序遍历

用一个辅助队列,
对每一个顶点做循环,未被访问过就处理。
将此顶点入队列。若当前队列不为空,将队中元素出队列,赋值给i
判断其他顶点与当前顶点存在边且为访问过
找到的顶点诶队列

深度优先比较适合于目标比较明确的找点

最小生成树:所谓的最小生成树,就是n个顶点的图,用n-1条线连接。把构造连通网的的最小代价生成树叫做最小生成树
两个算法:
1.普里姆算法:构造邻接矩阵。

adjvex[MAX]:保存相关顶点下标
lowcast[MAX]:保存相关顶点间边的权值 首先lowcast[0]=0;第一个顶点加入生成树,权值为0。(只要是lowcast【】的值为0,就代表加入了生成树)
adjvex[0]=0;初始化第一个顶点下标为0;
循环0除外的所有顶点,将v0与之有边的权值存入数组lowcast【】(其实就是邻接矩阵的第一行数据)
设置一个j用来循环顶点下标,k用来存储最小权值的顶点下标
找到最小权值点,打印此边。并将lowcast【k】=0;
j从1开始循环,查看vk行的权值,与lowcast【】的对应值比较,若更小就修改lowcast的值,并将k值存入adjvex中,

时间复杂度为O(n*n);
2.克鲁斯卡尔算法:
上面的算法是以顶点开始,逐步找最下权值的边构成最小生成树。
而这个算法直接以便为开始,直接去找最小权值的边,但有可能造成回路。就要用到图的边集数组。并对此升序排列

typedef struct {
int begin;
int end;
int weight;}Edge;

定义边集数组Edges edgws[MAXEDGE];
int parent[MAXVEX];用来判断边与边是否形成环路;初始化都为0
for(i=0;i<numEdges;i++)    循环每一条边
{
    n=find(parent,edges[i].begin);
    m=find(parent,edges[i].end);
    if(n!=m)   说明此边没有与现有生成树形成环路
        parent[n] = m;   将此边的结尾顶点放入下标为起点的parent中;说明此顶点在生成树集合中
        printf("(%d,%d)%d",edges[i].begin,edges[i].end,edges[i].weigth);

}
int find(int *parent,int f)     查找连线顶点的尾部下标
{
    while(parent[f]>0)
        f = parent[f];
    return f;
}

图的最短路径:对于网图来说,两顶点之间经过的边上权值之和最少的路径。第一个点是源点,最后一个是终点。

1.迪杰斯特拉算法:按路径长度递增的方法,在求得前一个最短的基础上求下一个; 最终的结果是求得了顶点与所有点的最短路径

一个最短路径下标的数组p,一个最短路径和的数组D
final[MAXVEX]; 数组值为1代表求得v0到vw的最短路径;初始化全部为0
D先初始化为与v0有边的顶点的权值。 p初始化为0
v0到v0路径为0,则final【v0】=1
开始主循环,从1开始。寻找离v0最近的顶点。
把得到的最近的顶点final【】置为1
然后修正当前最短路劲和距离

2.佛洛依德算法:
网图中v起点到其余各顶点w的最短路径p[V][W]和带权长度D[v][w]

初始化D即为个点之间的权值。p[v][w] = w(从0开始循环)
for(k=0;k< G.numvertx;++k)
for(v=0;v< G.numvertex;++v)
for(w =0; w < G.numvertex;++w)
if((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
//如果经过下标为k顶点路径比源两点间路径更短,将当前两点间权值设为更小的一个
(*D)[V][W]=(*D)[v][k]+(*D)[k][w];
(*p)[v][w]=(*p)[v][k]; 路径设置经过下标为k的顶点

k是中专顶点下标,v是起始顶点,w代表结束顶点。
当k=1时就是所有顶点都经过v1中转,
EG:当v=0时,原本D【0】【2】=5,现在由于D[0][1]+D[1][2]=4;所以要设置为二者的下值。即D【0】【2】=4;

它算的是所有.顶点到所有顶点的最短路径;

拓扑排序:
针对无环图的应用,就是没有回路。
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系。我们叫它AOV网。 弧表示活动之间存在制约关系。
设G=(V,E)是一个有n个顶点的有向图,v中的顶点序列V1….Vn满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前,那我们称这样的顶点序列是拓扑排序。
如果网的全部顶点被输出,那么是不存在回路的AVO。 若哪怕是少了一个顶点,都是存在回路的,不是AOV网。

算法:1.从AOV网中选择一个入度为0的顶点输出,删除此顶点。并删除以此顶点为尾的弧。继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点。

需要使用邻接表,顶点表结构in(入度域),data(数据域),firstedge(指向第一个邻接点)还要用到栈,来存储处理过程中入度为0的顶点,避免每个查找都要去遍历入度为0的顶点。

将入度为0的顶点入栈。
while(top!=0)
出栈,打印。count++(统计输出顶点数) 在最后if count< numverttex 就是存在环,否则全部输出了
对此顶点的弧表遍历(边表),将邻接点的入度-1,若为0就入栈 。

用来解决一个工程是否能顺利进行的问题。
2.关键路径:
解决工程完成的最短时间问题;
找到最关键的流程,就是最短的时间。
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动持续时间,这种有向图的边表示活动的网,我们称为AOE网。
没有入边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。
一个AOE网正常情况下只有一个始点,一个终点。
路径上各个活动所持续的时间之和称为路径长度;
从源点到汇点具有最大长度的路径叫关键路径。在关键路径上的活动称为关键活动。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值