数据结构
侧边栏壁纸
  • 累计撰写 16 篇文章
  • 累计收到 2 条评论

数据结构

晨旭不想写程序
2024-06-02 / 0 评论 / 13 阅读 / 正在检测是否收录...

树的前中后序遍历

前序遍历 根左右 每次左右可以展开时进行替换

img点击并拖拽以移动

中序遍历 左根右

后序遍历 左右根

前中后序转化

前中后序转化看遍历方式决定的顺序结构

例如前序遍历的第一个元素一定是根节点 中序遍历的根节点的两边就是左右子树

树与二叉树的转化

树要变成二叉树,那就将树中的所有兄弟结点进行链接,然后每一层与上一层的连接只留下第一个结点的连接

二叉树要变成树,那就反方向来一次,将除了第一个结点的其他结点与根节点连接上,然后将兄弟结点连接,这时候二叉树就变回了原来的树

森林与二叉树的转化

森林转化为二叉树,森林是由若干个树组成的,可以理解为森林中的每棵树都是兄弟,我们先把森林中的每棵树转化成二叉树,然后将从第二个树起的每个结点作为上一个结点的右孩子

二叉树想转化成森林,先要看他可不可以转化,直接看根节点有没有右孩子,有就可以转化,先递归的将每个拥有右节点的根节点都断开 然后将二叉树再转化成树就成了森林

树与森林的遍历

树的遍历

树的遍历很简单,分为先根遍历与后根遍历

森林的遍历

森林的遍历也分为两种,分别是前序遍历与后序遍历,森林的前序遍历与二叉树的中序遍历相同,森林的后序遍历与二叉树的中序遍历相同

图的表示

邻接矩阵

typedef struct
{
    Vertextype vexs[MAXVEX];
    EdgeType arc[MAXVEX][MAXVEX];
    int numNodes, numEdges;
}MGraph;

邻接表

int EdgeType;
typedef struct
{
    int adjvex;
    EdgeType info;
    struct EdgeNode *next;
}EdgeNode;
typedef struct
{
    VertexType data;
    EdgeNode *firstedge;
}VertexNode,AdjList[MAXVEX];
typedef struct
{
    Adjlist adjList;
    int numNodes,numEdges;
}AdjList;

深度优先搜索

深度优先搜索也叫DFS,这种搜索如其名,深度优先,在走之前先确定一个方向,比如先访问最左边的,那就持续往前走,在未遇到过的结点的路中选择最左边的即可

//邻接矩阵
#define MAXVEX 9
Boolean visited[MAXVEX]
void DFS(MGraph G,int i)
{
    int j;
    visited[i] = True;
    printf("%c",G.vexs[i])
    for(j = 0;j < MAXVEX;j++)
    {
        if(G.arc[i][j] == 1 && visited[i] == False)
        {
            DFS(G,j);
        }       
    }
}
void DFSTraverse(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);
        }
    }
}
//邻接表
void DFS(GraphAdjList GL,int i)
{
    EdgeNode *p;
    visited[i] = True;
    printf("%c",GL->adjlist[i].data);
    p = GL->adjlist[i].firstedge;
    while(p)
    {
        if(!visted[p->adjvex])
        {
            DFS(GL,p->adjvex);
        }
        p = p->next;
    }
}
void DFSTraverse(GraphAdjlist GL)
{
    int i;
    for(i = 0;i < GL->numvexes;i++)
    {
        visited[i] = False;
    }
    for(i = 0;i < GL->numvexes;i++)
    {
        if(!visited[i])
        {
            DFS(GL,i);
        }
    }
}

广度优先搜索

广度优先搜索,也叫BFS

核心思想是一层一层访问结点,使用的是一个栈来作为辅助存储,从入栈第一个节点开始,每次出栈一个结点,就将这个结点邻接的所有未访问顶点入栈,由此来遍历所有顶点

void BFSTraverse(MGraph G)
{
    int i;
    Queue Q;
    for(i = 0;i < G->numvexes;i++)
    {
        visited[i] = False;
    }
    InitQueue(Q);
    for(i = 0;i < G->numvexes;i++)
    {
        if(!visited[i])
        {
            visited[i] = True;
            printf("%c",G->vex[i])
            EnQueue(&Q,i);
            while(!EmptyQueue(Q))
            {
                DeQueue(&Q,&i);
                for(j = 0;j < G->numvexes;j++)
                {
                    if(G.arc[i][j]&&!visited[j])
                    {
                        printf("%c",G->vex[j]);
                        EnQueue(&Q,j);
                    }
                }
                
            }
        }
    }
}

最小生成树

prim算法

简介

prim算法的核心就是迭代,从一个顶点开始构建生成树,每次讲代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。

实现思想

创建两个数组,一个是标记是否加入的数组isjoin,一个是计算各节点加入最小生成树的最低代价的数组lowcost

在此之前先选取第一个结点,对此结点的相邻边进行遍历,将有权边加入到lowcost中供选择

第一轮循环遍历各个结点,找出lowcost最低的并且未加入树的顶点,将相邻结点加入isjoin数组中并开启下一轮遍历,更新还未加入的各个顶点的lowcost值

不断循环直至所有顶点都纳入为止

因为要进行n-1轮的循环,每次循环2n次

总时间复杂度是O($n^2$)即O($|V|^2$)

kruskal算法

简介

kruskal算法的核心就是全局里去找,每次选择一条权值最小的边,使这条边的两头连通(若原本已经连通则不选)直到所有顶点都连通

实现思想

初始时先将各条边按照权值进行排序

然后使用并查集方法检查是否已连通,若未连通则将新节点加入,一共要执行e轮,美伦判断两结点是否属于同一集合,需要O($log_2e$)

最短路径

BFS求无权图的单源最短路径

简介

直接进行广度优先遍历

使用两个数组,一个记录最短路径值,一个记录到这个顶点的直接前驱

只能用无权图

迪杰斯特拉算法

简介

dijkstra算法是一种一步一步找出最短路径的方法,核心思路就是从初始点开始,一步一步从已确定路径中选取最短的路径作为新的最短路径,并加入新已确定顶点,然后执行多次

实现

我们选用三个数组,分别是标记各顶点是否已找到最短路径的finals,最短路径长度的dist,以及记录路径上的前驱的path

img点击并拖拽以移动

也就是我们每次将可到达的结点找出来,从可获取路径中找到最短路径,并将其前驱记录,标记出结点

时间复杂度为O($n^2$)即O($|V|^2$)

如果用于负权值带权图,则迪杰斯特拉算法可能会失效

弗洛伊德算法

简介

Floyd算法是求出每一对顶点之间的最短路径
使用动态规划思想,将问题的求解分为多个阶段

对于个顶点的图G,求任意一对顶点Vi一>Vj之间的最短路径可分为如下几个阶段:

初始:不允许在其他顶点中转,最短路径是?

0:若允许在Vo中转,最短路径是?

1:若允许在Vo、V1中转,最短路径是?

2:若允许在Vo、V1、V2中转,最短路径是?

......

n-1:若允许在Vo、V1、V2.Vn-1中转,最短路径是?

img点击并拖拽以移动

例如这样,左边的矩阵就是初始时,不中转获得的个顶点建最短路径长度,右边的矩阵是初始时中转点的记录,因为不中转,所以是-1

若允许在V0中转,则新加一

img点击并拖拽以移动编辑

如此经历n轮递推

woc,大道至简,本身以为是只有一个节点做中转的情况,但是仔细一想,它并不是单源的算法,而是点到点的算法,并且也从来不是每次加一个这么简单,他是考虑了所有的结点

就好比是需要经过 0 2 4 6才能到这个点,在查找时0->2是最小值不需要中转,0->4是经过2的中转,0到6是经过4的中转,但是到4的中转前已经中转过2了,所以这种算法已经考虑了所有的情况

DAG

简介

有向无环图简称DAG 图

DAG描述表达式

img点击并拖拽以移动

相同部分可以合并

img点击并拖拽以移动

节省存储空间

顶点中不能出现重复的操作数,标出来各个运算符的生效顺序,注意分层

拓扑排序

简介

拓扑排序就是找到做事的先后顺序

每个AOV网可能有一个或者多个拓扑排序

实现

①从AOV网中选择一个没有前驱(入度为0)的顶点并输出。
②从网中删除该顶点和所有以它为起点的有向边。
③重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。

使用三个数组进行实现

分别是 记录当前顶点入度的数组indegree 记录拓扑序列的数组print 保存度为零的顶点栈s

逆拓扑排序

将拓扑排序中的入度更换成出度即可,使用邻接表不适合实现逆拓扑排序,应该使用逆邻接表或者邻接矩阵

查找

查找的衡量方法为平均查找长度

顺序查找

基本思想

是从线性表的一端开始,逐个检查关键字是否满足给定的条件。若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置。若查找到表的另一端,仍未找到符合给定条件的元素,则返回查找失败的信息。

折半查找

基本思想

就是二分法

散列表

基本概念

散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这些情况为冲突,这些发生碰撞的不同关键字称为同义词。
散列表建立了关键字和存储地址之间的一种直接映射关系。

散列函数不同,散列表不同

直接定址法

就是直接使用线性函数确定地址,一般不常见

除留余数法

确定一个数m,所有的数对设定的数m进行取余,取余后进行散列表的排序

解决冲突办法

如果有重复则将其使用向后推移并记录查找次数的方法进行储存,成为开放定址法,也可使用链表进行存储,称为拉链法

查找长度

散列表的查找长度取决于三个因素:散列函数、处理冲突的方法和装填因子。
装填因子a=表中记录数n/散列表长度m。
散列表的平均查找长度依赖于散列表的装填因子α,不直接依赖于n或m。
α越大,表示装填的记录越“满”,发生冲突的可能性就越大。

排序

插入排序

直接排序实际上就是进行比较后一步步替换

空间复杂度为O(1)

时间复杂度为O($n^2$)-->两个嵌套for循环(平均)

稳定性 稳定 (遇到相同数字,相对位置保持不变)

每次向后移动一次即一趟排序

希尔排序

希尔排序是通过一个常数d作为增量,然后对于相隔d个增量的记录作为子表进行排序,经过几次排序,使得整个表格基本有序后,对全体进行一次排序即可

因为同样使用常数个辅助单元,所以空间复杂度为o(1)

时间复杂度依赖于增量d,一般来说是不确定的,所以一般我们不去考虑

最后两个相同数字的相对位置也不能保证,所以稳定性也是不稳定的

交换排序

冒泡排序

不做解释,一一交换

空间 O(1)

时间O($n^2$)

快速排序

快速排序是选取一个固定数,通常为第一个数,将小于这个数的跟不小于这个数的值进行排序,然后依次进行,是一个递归的过程

所以

空间复杂度是O($log_2n$)

时1间复杂度是O($nlog_2n$)

稳定性为不稳定

快速排序是所有内部排序算法中平均性能最优的算法

但是并不适用于本身就已经有了一定顺序的序列进行排序

选择排序

简单选择排序

就是遍历每个元素,在遍历到第i个元素时,选择从i到n的所有元素中最小的一个,将其与第i个元素进行交换

空间复杂度为O(1)

时间复杂度为O($n^2$)

稳定性为不稳定

堆排序

对于二叉树的排序结果,我们可以根据根节点存放的是最大结点还是最小结点将堆分为大根堆与小根堆

对于大根堆小根堆的构造,都是从$n/2$开始进行的

对于堆的删除操作,就是将栈顶元素输出后再次进行构造

对于堆的插入操作,我们将其放入栈尾,再次进行构造

空间复杂度O(1)

时间复杂度O($nlog_2n$)

稳定性 不稳定

归并排序

归并排序是将两个或两个以上的有序表组成一个新的有序表

例如二路归并排序,就是将元素两两组合并进行排序

空间复杂度是O(n)

时间复杂度是O($nlog_2n$)

稳定性 为稳定

基数排序

不基于比较与移动进行排序,而是基于关键字各位的大小进行排序

空间效率 O(r)

时间复杂度O(d(n+r))

稳定性 稳定

各种排序算法的比较

img点击并拖拽以移动

插帽龟跟统计鸡是很稳定的

插帽龟在选冒插的时候,恩慌了

恩老说快归堆

问题

树的度为树中最大的度,例如二叉树的度为2
img点击并拖拽以移动树中的指针域,看图理解即可

含有n个结点的树含有n+1个空链域,n-1个非空链域,可以从画图理解,从第一个结点为2个空域,每增加一个结点,空域增加一个

前后缀表达式
前缀表达式

img点击并拖拽以移动首先先看,前缀表达式是从后往前算,遇到数字一个个放入栈中,遇到符号则拿出栈顶的元素进行计算,后进先算

img点击并拖拽以移动

后缀表达式

先入先出,从前往后进行计算,也就是通过队列进行实现

二叉搜索树

(BST,Binary Search Tree),也称二叉排序树或二叉查找树。
二叉搜索树:一棵二叉树,可以为空;如果不为空,满足以下性质:

  1. 非空左子树的所有键值小于其根结点的键值。
  2. 非空右子树的所有键值大于其根结点的键值。
  3. 左、右子树都是二叉搜索树。
二叉链表

用二叉链表存储哈夫曼树,有m个叶子结点,问哈夫曼树中总共多少个空指针域:

2m,叶子节点数*2

数据的物理存储结构

主要包括链式存储与顺序存储

二叉排序树插入新节点

时间复杂度为O(n),因为最差情况为单链

以链表为栈的存储结构出栈时

以链表为栈的存储结构出栈时必须判空,不需要判定满栈

顺序线性表插入脑残img点击并拖拽以移动
数据的最小单位

是数据项

归并排序落单

丢掉

substr(str,int,int)

意思是str的第int开始的int个字符

层次遍历初始堆

无法保证得到一个有序的序列,因为堆的兄弟结点之间无序

创建邻接表的时间复杂度

无向图中有n个结点e条边,建立该图邻接表的平均时间复杂度为O(n+e)

深度为k的完全二叉树中最少有$2^{k-1}$个结点

如上

一趟排序结束后不一定能选出一个元素在其最终位置上的排序算法

希尔排序,可能没有元素在最终位置上

连通图是无向图

连通图一定是无向图,所以深度优先遍历连通图一定能够访问到所有的顶点

链式栈的栈顶元素删除

删除栈顶元素操作序列 top = top->next

初始化堆

筛选法建初始堆必须从第$\frac{n}{2}$个元素开始进行筛选,因为第$\frac{n}{2}$个元素都有孩子结点(对于所有的完全二叉树来讲都是这样)

新建元素

x = (类型)malloc(sizeof(元素))

0

评论 (0)

取消