(计算机考研复试)数据结构重点知识

数据结构

1.绪论

​ 数据结构是相互有一定联系的数据元素的集合,数据结构作用包括数据的组织和存储

​ 算法是问题的解决方案,是能获得正确答案的指令,是对特定问题求解的一种描述

​ 好的算法:“证件课酬高“:正确性,健壮性,可理解性,抽象性,高效性

​ 抽象:抽出问题本质特征,而忽略非本质特征

​ 时间复杂度:算法中基本语句执行次数在渐近意义下的阶

​ 空间复杂度:算法临时开辟的存储空间

2.线性表

​ 随机存储:只要确定了存储顺序表的起始地址,计算任意一个存储单元的存储地址的时间是相等的

​ 单链表:区别头结点和头指针,每个单链表都含有头指针,这是用于找到单链表位置的(类似于数组名),但是头结点的作用在于使插入操作中插入头和插入中间的操作变得一样,若没有头结点,插入操作就要分两种情况,代码坑长,所以需要头结点。故:头指针是指向头结点的指针,头结点是一个没有data的结点。

​ 双链表的插入:若在p后插入s,先完成s所含有的指针的指向,再执行s前后指针使其指向s(四条语句)

​ s->pre=p; s->next=p->next; p->next->pre=s; p->next=s;

​ 双链表删除:删除p,则将p前后指针连接即可(二条语句)

​ p->pre->next=p->next; p->next->pre=p->pre;

​ 循环链表:仍然有头结点,是一个头尾相接的循环单链表

顺序表与链表的优劣

​ 时间性能:顺序表适合查询,链表适合插入,删除

​ 空间性能:一方面链表有指针,占用了内存;但另一方面顺序表在可能分配存储空间过大,导致存储空间没有得到充分利用。

​ 总结:事先知道大致长度,可使用顺序表; 而元素个数变化大或未知,则用链表

3.栈和队列

​ 顺序栈:也就是通过数组来实现栈,一个变量top来代表栈顶,当空栈top=-1,满栈top=size-1。要注意数组的哪一边是底部。

​ 链栈:各人认为是比较好理解的,无论是插入元素还是删除元素都在栈顶操作,所以就不用像单链表一样设立头结点了,且时间复杂度都为o(1)

​ 链栈的入栈操作:假设s是要插入的节点,执行s->next=top;top=s;(top是指针)

​ 链栈的出栈操作:要先判断是否是空栈–if(top==null) 然后再记录top的data和指针,最后top=top->next

​ 队列:

​ 顺序队列:存放在数组中,入队直接写在后面o(1)出队是o(n)因为要全部向前移动;也可以放宽约定通过front和rear两个指针指向队列的头和尾,那么就都是o(1)(双指针法:这在队列中可以说非常常见)

​ 循环队列:解决顺序队列的单向移动性:假溢出。而循环队列:当元素到达数组最大时,再增加元素该元素坐标就执行rear=(rear+1)%quenesize,通过取余操作来判定是否超出了总体长度(其实每次rear都是+1的,但是多个取余保证了不会溢出)。

​ 循环队列队满与队空:这是一个约定,当front=rear队空,当front在rear的前一格视为队满

​ 链队列:链队列其实就是通过单链表制作而成,只是前后两端多了front和rear指针,从而实现在前面出队,只在后面入队(记住这个规则),为了与特殊空链表插入一致,设立头结点。队头指向头结点。

​ 链队列入队:先初始化节点:s->data=x; s->next=null;再插入到尾部:rear->next=s;rear=s;

​ 链队列出队:先判断是不是空节点:if(rear==front) throw"下溢"

​ 删除前保存一下删除节点信息:p=front->next;x=p->data;

​ 再删除:front->next=p->next;

​ 最后再判断一下是不是空链表了:if(p->next==null){rear=front}//如果是就要让rear=front而不是跟着删掉的p节点了

​ 循环队列和链队列的比较:首先他们时间复杂度都是o(1)循环队列需要提前设定存储空间,而链队列则不需要,但是会有指针这种多出来的空间消耗,所以当频繁需要变化时使用链队列,若没有则使用循环队列,在保证不会出现假溢出的情况下也可以使用顺序队列。

综上会发现:链适合变化,顺序表适合存储

4.字符串和多维数组

​ 字符串位置:通过字符串首字母来定位

​ 模式匹配:在主串中寻找子串

​ BF算法:就是暴力法,从一开始每个字符串匹配,直到匹配所有或者中途匹配失败回溯,如果全部匹配完找不到子串返回0,匹配成功,返回本趟匹配起始地址。

​ 最佳情况是每次失败都在第一个字母失败:那么时间复杂度o(n+m);

​ 最坏情况是每次失败都在倒数第二个字母失败:那么时间复杂度o(n*m)

​ KMP算法:是一种字符串匹配的高效办法,不建议理解他的本质,比较复杂。他是通过对子串进行研究,然后生成一个next[j],next数组中每个元素对应子串一个字符串。然后当后面子串与主串匹配时,就不会全部都回溯,而是根据next数组来操作。

​ next的获取:1.当j=0时,next[j]=-1;

​ 2.当j=1时,next[j]=0;

​ 3.当j>1时,看从2到j-1的小串中互相有几个相等的;比如子串T[]=“ababc”,当j=2,T[0]!=T[1],所以next[2]=0; 当j=3,T[0]=T[2],T[0]T[1]!=T[1]T[2]所以next[3]=1;同理next[4]=2;(注意:j为单数比较到中间那个的两边分组,双数比较到两边分组)

​ 当获得next数组以后,也是从s[i]和T[j]开始比较,一开始i=0,j=0,匹配成功则继续匹配i++,j++若匹配错误,j=next[j] (若next[j]=-1,则i++,j++),当j等于T的长度时,全部比较完毕。

​ 多维数组:

​ 对称矩阵的压缩存储:将上三角或者下三角记录下来即可

​ 三角矩阵的压缩存储:将上三角或者下三角记录下来,然后还要加多一个常数

5.树和二叉树

​ 树:有限集合,他的子树是互不相交的

​ 度:1.节点的度:就是有多少个子树 2.树的度:该树中最大的节点的度称为树的度

​ 叶子节点:终端节点;

​ 孩子节点:子树的根节点; 双亲节点:子树根节点的父节点; 兄弟节点:拥有同一个父节点

​ 路径:指从哪个节点到哪个节点经过的节点名字; 路径长度:该路径经过多少条边(在树中路径唯一)

​ 祖先,子孙:这是个相对概念,从上往下只要有路径,上面的就是下面的祖先,下面的就是上面的子孙

​ 层数:就是第几层,规定根节点为第一层

​ 深度(高度):最深有几层

​ 宽度:这棵树最宽的一层的节点数

​ 前序遍历:先跟再左右 ; 后续遍历:先左右再根 ; 层序遍历:自上而下自左到右;

5.1树的存储结构

​ 双亲表示法:通过建立一个有data和parent两个属性的结构体数组,来存放树的信息,data是节点本身信息,parent则存放这个节点的双亲结点,(注意:根节点的parent是-1)

​ 孩子表示法:通过线性表存放data和firstchild两个属性的结构体数组,然后firstchild指向一个有child和next两个属性的单链表,单链表第一个用于存放表头节点的最左边孩子节点,单链表上的节点互相是兄弟节点,如果表头中有叶子节点,他的firstchild指向空表。

​ 孩子兄弟表示法(常用,因为更好理解,便于实现树的操作):是一个链表,链表中的节点是一个含有两个指针和自己data的结构体,一个指针指向该节点第一个孩子,一个指针指向该节点的右兄弟节点。通过这种方式,要找到某节点的第x个孩子,只要在该节点往下一步,然后往右x步即可找到。

5.2二叉树逻辑结构

​ 二叉树是最简单的树,他与树不同在于,他是有方向的,即使是一个节点只有一棵子树,也要区分左子树和右子树,这两个树是不同的。

​ 斜树:只有左子树或者只有右子树,直观来看就是一条向左(或者向右)斜的斜棒

​ 满二叉树:顾名思义,全满的–叶子节点在最后一层且分支节点度都为2

​ 完全二叉树:n-1层是满二叉树,最后一层第n层叶子节点全都在最左边。

二叉树性质

​ 叶子节点个数=度为2节点个数+1

​ 二叉树第k层上最多有2^(k-1)个节点

​ 深度为k的二叉树,最多2^k -1个节点

​ 有n个节点的完全二叉树深度为(log2n)取整 +1层

​ 对于从1开始的完全二叉树中的节点i:1.其父节点必为i/2取整,否则为根节点; 2.其左孩子必为2i(否则无左孩子) 3.其右孩子必为2i+1(否则无右孩子)

​ 前序遍历,后序遍历刚刚提到一样的

​ 中序遍历:先左,然后根节点,然后右节点

5.3二叉树存储结构

​ 顺序存储结构:根据刚刚的左子节点2i,右子节点2i+1来作为每个节点的下标,但是这种仅适合存放完全二叉树,否则将会浪费非常多的空间。因为k层的二叉树就要分配2^k-1个存储单元。

​ 二叉链表存储结构:通过一个结构体有两个左右指针,分别指向左孩子和右孩子

​ 无论是前序遍历中序遍历后序遍历,都是使用递归操作,记住先判定是否是空节点,如果不是,再考虑执行操作; 而层序遍历:需要通过队列进行,队列先放入根节点,当根节点出队,根节点的所有孩子节点都要入队。

​ 二叉树的构造:通过输入#来代表空树,然后递归实现。

​ 三叉链表存储结构:不仅存储左右孩子,还存储双亲节点。

5.4森林

​ 森林:就是多个树的集合,这多棵树加上根节点就变成了一棵树。一棵树删掉根节点,就变成了森林

​ 森林的遍历就是从左到右一颗颗树的遍历

树,森林,二叉树转换:

​ 树变为二叉树:所有兄弟节点之间加线,然后除了第一个孩子节点其余去除线,调整层次(根节点无右子树):连兄弟除孩子

​ 森林转二叉树:森林中的子树变为二叉树,然后根节点相连即可(可看出右分支节点个数就是森林中的子树数量)

​ 二叉树转树或森林:某x节点是双亲节点y的左节点,就将x的右孩子,右孩子的右孩子,。。。。都与y相连;然后将所有 本来有的 双亲结点与右孩子的连线去掉;调整层次 给别人孩子,除自己孩子

5.5最优二叉树

​ 叶子节点给定权值:也就是叶子节点上面写个数字叫权值

​ 二叉树的带权路径长度:根节点到该叶子节点的路径长度乘该叶子节点的权值 之和(就是所有叶子节点这个的和)

​ 哈夫曼树:同样的权值集,构造的二叉树带权路径长度最小(所以需要权值最大的二叉树靠近根节点,权值最小的二叉树远离根节点)

​ 哈夫曼算法就是将权值最小的合并然后他们的和是双亲节点的权值,然后这两个节点合并成一个节点,继续按照权值最小的合并,最后构造的就是哈夫曼树了。

​ 等长编码:就是每个字符都用同样长的二进制位串来表示

​ 不等长编码:使用频率高的较短编码,使用频率低的较长编码,这就是压缩的思想(但是容易引起歧义,所以产生了前缀编码:任意一个编码都不会是其他编码的前缀)

​ 哈夫曼编码用于构造最短的不等长编码方案。先通过哈夫曼算法构造一棵哈夫曼树,然后根据使用频率把每个字符分配给叶子节点,记住规定:左边路径为0,右边路径为1,这样每条路径代表的01字符串就是每个字符的编码了。由于每个字符都是叶子节点,他们不可能到其他节点有路径,所以保证了唯一性。

6.图

​ 图的定义:由顶点的集合和边的集合组成 G=(V,E) V代表顶点集,E代表边集

​ 无向图:任意两个顶点为无向边 (vi,vj)

​ 有向图:只要有一条为有向边 <vi,vj>

​ 权:是边的值 (与二叉树中叶子节点的权值区别开来)

​ 邻接:两个点相邻 ; 依附:两个点中间的那条边依附于两个点

​ 度:依附于该顶点的边数 入度:依附于该顶点并指向该顶点的边数 出度:依附于该顶点并且指向不是该顶点的。

​ 无向完全图:任意两个顶点间都有一条没有方向的边:数量是n*(n-1)/2

​ 有向完全图:任意 两个顶点间都有两条相反方向的边:数量是n*(n-1)

​ 路径:一个顶点到另一个顶点所经过的边; 路径长度:经过的边的数量(如果是带权图就是计算权值的和)

​ 以下是在 无向图 中

​ 连通:一个顶点有路径到另一个顶点,则这两个顶点是连通的

​ 连通图:任意顶点之间都有路径

​ 连通分量:非连通图的极大连通子图,也就是分开来多少个连通子图(注意每个都是)

​ 以下是在 有向图 中

​ 强连通图:任何顶点间都有路径

​ 强连通分量:极大强连通子图

6.1图的遍历

​ 图的遍历:为了解决确定哪个顶点已经被访问过,设立一个对应于顶点数组的visit[]数组,初始化全为零,当访问过后visit[i]=1;

​ 深度优先搜索:类似前序遍历,先第一个顶点v,然后从v的未被访问邻接点中选择顶点w,再对w进行深度优先搜索。很明显是一个递归的思想。

​ 广度优先搜索:先访问顶点v,然后依次访问顶点v的所有邻接点wjq,然后再依次访问wjq的所有邻接点,很明显使用队列来实现,类似层序遍历。

6.2图的存储

​ 邻接矩阵:通过一个一维数组存储图的顶点,然后通过二维数组存储图的边,二维数组中每一行都代表一个顶点到其余顶点的是否邻接:若是,则为1(或者是权值); 若是自己则为0;若不是邻接,则为0(有权图则为无穷)

​ 可以看出:无向图必定是对称矩阵;所以判断某个顶点多少个邻接点,只要看顶点所代表的行有多少个1(权值)即可。

​ 邻接表:非常类似孩子表示法,就是由一个表头数组构成,装每一个顶点,下标代表顶点的位置,而顶点是一个有两个元素的结构体,分别是顶点的内容和指针,这个指针指向一串单链表,单链表是所有该顶点的邻接点。

​ 所以通过邻接表:无向图中,该顶点所有邻接点都在右侧链表中。

​ 邻接矩阵和邻接表的比较:

​ 空间性能:邻接矩阵是o(n^2) 而邻接表是o(n+e),e代表边数。邻接矩阵要存储所有可能的邻接边,而邻接表则有指针这种结构性开销。所以当比较稠密的图用邻接矩阵比较好。比较稀疏的图用邻接表比较好。

​ 时间性能:搜索一个顶点的邻接边时,邻接矩阵要o(n)邻接表要o(e/n) 所以邻接表是更快的

​ 唯一性:邻接矩阵唯一,但是邻接表不一定,取决于输入

​ 对应性:邻接矩阵每一行对应邻接表顶点对应的那行。

6.3最小生成树(扑克)

​ 生成树:无向连通图的一个极小连通子图,这个子图多一条原来的边就会产生回路,少一条边就不是连通的。

​ 最小生成树:生成树有很多个,但是边权值之和最小称为最小生成树,这会运用在“如何用最少的经费建桥”这种问题上。

​ 求最小生成树:

​ Prim普里姆算法(放入):用邻接矩阵存储的图,此算法适合比较稠密的图来寻找生成树,时间复杂度为o(n^2),其思想是将整个图分为两个点集,一个是一开始的U,一个是V-U(V指所有的点),然后每一步将V-U中离U最近的点放入U中。 实现方法是:每个点都有对应的两个数组:lowcost[]和adjvex[] ,adjvex[i]=j代表i点离U中的j点最近,lowcost[i]代表i点离j点的权值,(注意i点是V-U中的点,j点是U中的点),每一次操作都将最小的lowcost的i放入j中,然后记录下lowcost[i]。重复上述步骤直到U=V,这样就找到了最小生成树了。

​ Kruskal克鲁斯卡尔算法(组合):此算法适用于比较稀疏的图,时间复杂度O(elog2e)。其思想是先用数组将所有边的权值从小到大存好,然后按照从小到大来连接,连接的前提是这两个顶点在刚刚的操作后的此时此刻必须是不连通的,若连通则跳过,直到所有顶点都在同一个连通图里。 实现方法:通过边集数组存储,边集数组的元素是一个有三个属性的结构体,分别是from,to,weight(代表起点终点和权值),判断是否连通,则通过并查集(也就是数)来实现,合并两个集合就是将一个集合的根节点作为另一个集合根节点的孩子,若要判断是否是同一个集合(是否连通),则看是否有同一个根节点。

6.4最短路径(德福)

​ Dijkstra迪杰斯特拉算法(放入一次更新):两次循环,所以时间复杂度为O(n^2),用于查找单个点到其余所有点的最短路径。思想是将顶点分为一开始的U集合用于存放起点,V-U为其余点。首先图的存储用edge[] []邻接矩阵来存储,通过一个一维数组dist[i]来记录某点到其余i个点的最短距离,开始循环,第一轮查找dist[k]的最短路径的顶点,并将该顶点k存入U集合,然后遍历一遍其余点,如果其余点的dist大于dist[k]+edge[k] [i],就将dist[i]=dist[k]+edge[k],也就是起点到i点的最短距离变成了起点到k点再到i点。然后循环刚刚的步骤直到U=V。也就是每一轮都会有一个新的点进入U,然后更新所有的dist。

​ Floyd佛洛依德算法():用于查找所有点之间的最短路径,他的实现比较简单,思路就是将所有点都加进起点和中点之间与原来的路径比较一下,如果更小,它就成为新的最短路径。实现:所以就三层循环,k,i,j然后内部当dist[i] [j]>dist[i] [k]+dist[k] [j],则dist[i] [j]=dist[i] [k]+dist[k] [j],path[i] [j]>path[i] [k]+path[k] [j] 注意path是用来存放最后的最短路径的。

6.5有向无环图
6.5.1 AOV网和拓扑排序

​ AOV网是一种用顶点表示活动,而有指向的边表示先后顺序,a指向b表示a完成了b才可以执行,所以AOV网不能有回路。

​ 所以必须使用拓扑排序来测试一个图是否有回路:1.选择一个没有前驱顶点并输出。2.删掉这个顶点以及所以以该顶点为尾的边 3.重复上述两步,直到只剩下单个的顶点则没有回路; 如果最后剩下还有前驱的顶点,那么就是有回路了。(注意拓扑排序使用邻接表,而且顶点数组还要多个属性入度)

6.5.2 AOE网和关键路径

​ AOE网:顶点代表事件,边代表活动,只有当所有前面的活动完成,事件才能开始

​ 事件最早发生时间:就是该事件的所有上一事件中的 最晚的 “最早开始事件的时间+上一这个事件到当前事件的距离”(例外:源点最早发生事件是0) 巧计:不能影响上一最早事件 的最大值

​ 事件最晚发生时间:下一最晚事件事件中的 最早的时间-这一事件到下一事件的长度(例外,倒数第二个事件最早与最晚一样) 巧计:不影响下一最晚事件的 最早值

​ 活动最早开始时间:等于事件最早开始时间

​ 活动最晚开始时间:就是该活动指向的事件的最晚开始时间-活动长度 (因为不能影响下一事件最晚开始时间,与事件不同就是不用再取最小值因为是特定的)

7.查找

​ 记录:数据元素

​ 关键码:标识一个记录的某个数据项,就类似名字,学号这种

​ 主关键码:唯一标识一个记录; 此关键码:就是不能唯一标识

​ 查找:在相同类型记录构成的集合中,找出满足给定条件的记录

​ 静态查找:只查找

​ 动态查找:还涉及插入和删除操作

7.1顺序查找

​ 设置哨兵:问题:当我们比较的时候,用while循环时每个循环内部都要加上判断下标是否越界,所以通过在data[0] 放置我们的给定值k,这样从大到小比较,就算找不到最后data[0]==k循环也会结束,就不用再判断是否越界了。

7.2折半查找

​ 前提是升序或者降序的顺序表,(此处假设升序)时间复杂度为O(log2n),其思想是有两个指针front和rear指向顺序表的头和尾,mid为(front+rear)/2。若mid指向值<给定值,说明在右边,那么front=mid+1。若mid指向值>给定值,说明在左边,rear=mid-1。

​ 算法分为两种类型,递归和非递归,非递归就是用循环实现,找到停止循环,或者rear<=front代表查找失败。 递归则是每次传送新的front rear和给定值k,(注意先判断是否rear<=front)

7.3二叉排序树

​ 一棵通过 中序遍历 可以获得有序数列的每个节点有特定权值的二叉树。

​ 特点:1.左子树不空则左子树所有节点都必定小于根节点;

​ 2.右子树不空则右子树所有节点都必定大于根节点

​ 3.左右子树都是二叉排序树

​ 二叉排序树的思想就是递归

​ 查找:通过递归实现,给定值大于当前值就递归调用本函数参数为右子树,给定值小于当前值就递归调用本函数参数为左子树。

​ 插入:递归直到找到叶子节点然后插入,注意递归调用的参数源于当前值大于还是小于给定值

​ 构造就是不断调用插入算法执行

​ 删除:叶子节点直接删除; 有一颗子树的删除然后连接子树;

​ 比较复杂的是删除有两棵子树的节点p,此时要用一个节点s来代替删除节点,规定这个节点选择大于删除节点的最小节点(也就是右子树的最左边叶子节点) 或者 小于删除节点的最大节点(也就是左子树的最右节点) 删除这个节点s要遵循删除原则,然后用s节点代替p节点。

​ 查找性能:O(log2n)到O(n)

7.4平衡二叉树

​ 比较平衡有两个特点:根节点左子树和右子树深度相差最多为1;左子树和右子树都为平衡二叉树

7.5 B树

​ 是一种平衡的多路查找树:m阶B树:每个节点最多m个子树; 每个节点最少m/2棵子树; 根节点至少两棵子树; ;所有叶子节点在同一层;而且他也遵循二叉排序树的类似规律,左子树小于跟,右子树大于跟,中间子树有大有小;每一个节点中有多个关键码,关键码数量最多为m,(关键码类似权值,只是此时一个节点可以有多个关键码)

​ 查找:通过树的关键码来查找,比如我们要找47,c节点关键码为43,78;因为47大于43,小于78,所以查询c节点的中间子树。

​ 插入:当插入的节点,插入后节点关键码数量小于等于m,那么就直接插入。

​ 当插入的节点,插入后节点关键码数量大于m,发生溢出,此时要“分裂–提升”,也就是把插入后的节点的中间关键码提到上一层,然后另外两个关键码分裂,分别构成一个节点。若提升的那个节点在上一层的节点仍然关键码数量大于m,则继续“分裂–提升”。

​ 删除:首先如果删除的关键码不在叶子节点,那么就把他删除,并且用他的子树的最小关键码来代替。

​ 那么删除问题就全部变为删除叶子节点的情况了。规定:如果删除后叶子节点关键码个数大于m/2-1,则可直接删除,否则需要以下:

​ 兄弟够借:从足够的兄弟节点那里借来一个关键码上移到双亲结点,然后双亲结点的最右边的关键码下移到被删节点处。

​ 兄弟不够借:双亲节点下移到被删除节点,被删除节点和就近的兄弟节点合并。双亲节点也会出现关键码消失的情况,解决办法也是以上,直到根节点下移合并,整个B树减少一层。

7.6散列表

​ 散列表:关键码通过散列函数来定位到散列表,根据这个对应关系,每个记录对应一个关键码,而用户通过关键码对应的映射地址,获得这个地址,而这个地址之上就是存放记录的。

​ 散列函数:计算简单;散列地址分布均匀(为了减小冲突)

​ 1.直接定址法:H=a* key+b(a,b为常数)

​ 2.除留余数法:H=key%p 通过取余获取,p一般采用接近m的最小素数 ; 这也是最常用的方法。

​ 3.平方取中法:将关键码平方后,取中间几位作为散列地址

​ 处理冲突:

​ 开放定址法:(闭散列表)就是将"散列地址加一个常数%m"(m是散列表的长度,也就是存储散列地址的长度,n是记录个数,也就是关键码的个数),获得新的散列地址看看是否和别的冲突。容易造成堆积

​ 拉链法:(开散列表)就是将所有散列地址相同的也就是冲突的记录存储在一个单链表中

​ 对比:拉链法不产生堆积现像,但是有指针的开销,而闭散列表,存储效率高,但是容易产生堆积现像。

​ 所以开散列表无需事先知道表的容量,但是闭散列表必须事先估计存储空间。

8.排序

​ 先说明:插入和交换是不一样的,插入是两个数组,右边的数组中的数字插入到左边的数组中;而交换是在一个数组里面进行的操作

8.1插入排序
8.1.1直接插入排序:

​ 就是把数据分为两数组,前面是已排序,后面是未排序,然后通过循环,每次将后面未排序的一个数字插入到前面已排序的合适位置。时间复杂度为O(n^2)

8.1.1希尔排序

​ 在O(nlog2n)与O(n^2)之间

​ 是在直接插入排序的一种优化,他先将总数组分割成一个个小数组,然后再对小数组进行直接插入排序,循环直到最后一次对总体大数组进行直接插入排序,此时数组基本有序了,那么直接插入排序的插入会比较少。

​ 而关键是如何分组:分组方法是根据间隔j来分组,每间隔j个单位分成一组,而j=n/2;每次都除以2,j越大分的组越多,每个组数字越少;j越小分的组越少,每个组数字越多,最后j=0,也就是最后一次对总体进行直接插入排序。

8.2交换排序
8.2.1起泡排序:

​ O(n^2)

8.2.2快速排序

​ 最好情况O(log2n) 最坏情况O(n)

​ 是对起泡排序的一种优化,因为起泡排序永远只能交换相邻的节点,这其实效率较低,所以快速排序实现了很远的节点相互之间也可以交换,从而提高效率。

​ 思路:它通过设置一个轴值,每一次的循环都会将所有比轴值小的放在轴值左边,比轴值大的放在轴值右边,然后递归再对轴值左边的数组进行快速排序,递归对轴值右边的数组进行快速排序,那么当最后对一个数字快排以后,递归返回就完成排序了。

​ 实现方式是,用i下标指向数组第一个数代表他是轴值,j为最后一个下标,然后重复以下直到i==j停止:右侧扫描就是j–,当data[i]>data[j]则交换他们两个的值;

​ 左侧扫描就是i++,当data[i]<data[j]交换他们两个的值;然后又开始右侧扫描,直到i==j

8.3选择排序
8.3.1简单选择排序

​ 最简单的选择排序,就是在一个数组里面,选择未排序中最小的和未排序中的那部分的第一个数字交换。O(n^2) (区别于直接插入排序,直接插入排序是两个数组,依次插入到另外那个数组;而简单选择排序是选择最小的放到前面)

8.3.2堆排序

​ O(nlog2n) 推排序基本思想是先把数据建成堆,以大根堆为例,每次把堆顶拿出就是数据最大值,然后重新建堆。循环就可以排序了。

​ 堆:以大根堆为例,每个节点都大于他的左右孩子的完全二叉树

​ 建堆:也就是把一棵完全二叉树变为每个节点都大于他左右孩子的完全二叉树,本质就是将每个分支节点与他的左右孩子作比较,然后把比根节点大的更大的孩子节点与根节点交换。从编号最大的分支节点开始,直到跟节点为止为建堆。

​ 而堆排序就是建堆,然后取根节点,此时将编号最大的叶子节点取出来代替根节点。再重新堆排序,再取出根节点。通过这种方式,每一次堆排序都会让整个堆更加有序。

8.4归并排序

​ 二路归并:O(nlog2n):最好最坏也是平均的时间性能

​ 其思路就是将一个数组分成两个组,把这两个小组排序好,然后再将这两个小组合并,合并通过两个指针分别指向两个小组的头结点,然后一次比较放入另一个新的数组,直到全部放进去

​ 递归实现:就是自己调用自己直到调到最后每个小组只有一个数

​ 非递归实现:就是从一开始就把总数组分成length个长度为1的小数组,然后开始将他们合并,循环直到有序子序列长度为length。

查找:

顺序查找O(n)

折半查找O(log2n)

img

希尔排序是O(nlog2n)到 O(n^2)之间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值