C++学习记录004——C语言数据结构(下)

C/C++知识总结

`
第一章 C语言基础知识
第二章 C语言高级编程
第三章 C语言数据结构
第四章 C语言高级数据结构


一、图

1.1 图的相关概念

  • 图的定义和术语:图是由顶点的有穷非空集合和顶点之间的边的集合组成,通常表示为:G = (V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
	无向边:若顶点Vi 到Vj 的边没有方向,则称这条边为无向边,用无序偶对(Vi ,Vj)来表示。
	有向边:若从顶点Vi 到Vj的边有方向,则称这条边为有向边,也称为弧(Arc)。用有序偶对<Vi ,Vj>来表示。Vi称为弧尾(Tail)或初始点,Vj称为弧头(Head)或终端点。
	无向图: 如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
	有向图: 如果图中任意顶点之间的边都是有向边,则称该图为有向图。
	无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n(n-1)/2条边。
	有向完全图:在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n(n-1)条边。
	稀疏图: 有很少条边或弧的图。
	稠密图: 有很多条边或弧的图。
	权: 有时图的边或弧具有与它相关的数,这种与图的边或弧相关的数叫做权。
	网:带权的图通常称为网。
	度:顶点的度是指和该顶点关联的边的数目。
	入度:有向图中以顶点(v)为头的弧的数目,称为(v)的入度。
	出度:有向图中以顶点(v)为尾的弧的数目,称为(v)的出度。
	邻接点:对于无向图,同一边上的两个顶点称为邻接点。
	子图: 假设两个图G=(V,E)和G1=(V1,E1),如果V1⊆V且E1⊆E则G1为G的子图
	路径的长度: 路径上的边或弧的数目。
	连通图:在无向图G=(V,E)中,如果从顶点v到顶点w有路径,则称v和w是相通的。如果对图中任意两个顶点Vi和Vj 属于E,则两个顶点是连通的,则称G是连通图。
	极小连通子图:连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。极小连通子图是相对于连通图来说的。
  • 如果一个图有n个顶点和小于n-1条边,则是非连通图,如果它多于n-1条边,必定构成一个环,因为这条边使得它依附的那两个顶点之间有了第二条路径。不过有n-1条边并不一定是生成树。例如:图1是一个普通图,但显然不是生成树,当去掉两条构成环的边后,比如图2或图3,就满足n个顶点n-1条边且连通的定义了,它们都是一棵生成树。而图4有n-1条边但不是生成树。
  • 极小连通子图

1.2 图的存储结构

  • 图的邻接矩阵存储方式是用两个数组来表示图。
      一个一维数组存储图中顶点信息,
      一个二维数组(邻接矩阵)存储图中的边或弧的信息。
  • 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
    邻接矩阵
  • 无向图的邻接矩阵:无向图的邻接矩阵是一个对称矩阵。
    无向图邻接矩阵
    从这个矩阵中,很容易知道图中的信息。
      (1)判断任意两顶点是否有边无边;
      (2)某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
      (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;
  • 有向图的邻接矩阵:有向图讲究入度和出度,顶点v2的入度为2,正好是第i列各数之和。顶点v2的出度为1,即第i行的各数之和。
    有向图的邻接矩阵
  • 网的邻接矩阵:wij表示(vi,vj)上的权值。无穷大表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值。
    网的邻接矩阵
    在这里插入图片描述
  • 邻接表:对于边数相对顶点较少的图,邻接矩阵结构对存储空间极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
      (1) 图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
      (2) 图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
  • 邻接表的数据类型定义:顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
    邻接表的数据类型
  • 无向图的邻接表的结构:
    无向图的邻接表
  • 网的邻接表:对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。
    带权值的网图

1.3 图的遍历

  • 深度优先遍历:简称DFS,就像是一棵树的前序遍历。从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。
  • 深度优先搜索是通过栈来实现的。下图中的数字显示了深度优先搜索顶点被访问的顺序。
    深度优先搜索
  • 实现深度优先搜索:
      重建一个数组和vertex顶点数组形成对应关系,标记该顶点是否被访问过。
      取一个没访问的顶点入栈,并且标记为访问,开始遍历。
      取栈顶元素邻接点,入栈,标记访问。
      没有邻接点了栈顶出栈,找新的栈顶元素邻接点入栈,标记访问。
  • 广度优先遍历:简称BFS,类似于树的层序遍历。在深度优先搜索中,算法表现得好像要尽快地远离起始点似的。相反,在广度优先搜索中,算法好像要尽可能地靠近起始点。它首先访问起始顶点的所有邻接点,然后再访问较远的区域。
  • 广度优先遍历用队列来实现的。下面图中的数字显示了广度优先搜索顶点被访问的顺序。
     广度优先遍历
  • 实现广度优先搜索:
      访问下一个未来访问的邻接点,这个顶点必须是当前顶点的邻接点,标记它,并把它插入到队列中。
      如果因为已经没有未访问顶点而不能执行规则1时,那么从队列头取一个顶点,并使其成为当前顶点。
      如果因为队列为空而不能执行规则2,则搜索结束。

1.4 最小生成树

  • 生成树:一个连通图的生成树是它的极小连通子图,在n个顶点的情形下,有n-1条边。生成树是对连通图而言的,是连同图的极小连通子图,包含图中的所有顶点,有且仅有n-1条边。
  • 最小生成树:在图论中,常常将树定义为一个无回路连通图。对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树称为图的最小生成树。造价最优问题就是一个最小生成树问题。
  • 普里姆(Prim)算法:设G=(V, E)是具有n个顶点的连通网,T=(U, TE)是G的最小生成树,T的初始状态为U={u0}(u0∈V),TE={},重复执行下述操作:在所有u∈U,v∈V-U的边中找一条代价最小的边(u, v)并入集合TE,同时v并入U,直至U=V。即:
      从连通网络 G = { V, E }中的某一顶点 u0 出发,选择与它关联的具有最小权值的边(u0, v),将其顶点加入到生成树的顶点集合U中。
      以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u, v),把它的顶点加入到集合U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集合U中为止。
    此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。Prim算法的核心:始终保持TE中的边集构成一棵生成树。
    Prim算法
  • 克鲁斯卡尔(Kruskal)算法:设无向连通网为G=(V, E),令G的最小生成树为T=(U, TE),其初态为U=V,TE={ },然后,按照边的权值由小到大的顺序,考察G的边集E中的各条边。若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。
    克鲁斯卡尔(Kruskal)算法
  • prim算法适合稠密图,其时间复杂度为O(n2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(nlogn)跟边的数目有关,适合稀疏图。

1.5 最短路径

  • 最短路径:从图中某一顶点(源点)到达另一顶点(终点)找到一条路径,沿此路径上各边的权值总和(称为路径长度)达到最小。
  • 单源最短路径:已知有向带权图(简称有向网)G=(V,E),找出从某个源点s∈V到V中其余各顶点的最短路径。
  • 最短路径的最优子结构性质:如果P(i,j)={Vi…Vk…Vs…Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。反证法证明。
  • 迪杰斯特拉(Dijkstra)算法:由上述性质,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根据这种思路,假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点:
      从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;
      更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})
      直到U=V,停止。
  • 在下面图中找出 A 城到 D 城最近的一条公路。注:在带权图中求最短路径问题,此时路径的长度不是路径上边的数目,而是路径上的边所带权值的总和。
    求最短路径
  • 分析:可看出A到D的最短路径ABED,倒推出最短路径,从D往回推,程序中也一样。dist[]数组:从出发点到各个点最短的距离。path[]数组:到当前点的最近的邻接点。先初始化所有数据,开始进入循环,遍历所有点的关系,更新最小点。
  • Prim算法和Dijkstra算法的异同:
      Prim是计算最小生成树的算法,比如为N个村庄修路,怎么修花销最少。Dijkstra是计算最短路径的算法,比如从a村庄走到其他任意村庄的距离。
      Prim算法中有一个统计总len的变量,每次都要把到下一点的距离加到len中;Dijkstra算法中却没有,只需要把到下一点的距离加到cls数组中即可。
      Prim算法的更新操作更新的low是已访问集合到未访问集合中各点的距离。Dijkstra算法的更新操作更新的dist是源点到未访问集合中各点的距离,已经访问过的相当于已经找到源点到它的最短距离了。

二、查找

2.1 二叉排序树

  • 二叉排序树又称二叉查找(搜索)树:它是一颗空树,或者是满足如下性质的二叉树:
      ①若它的左子树非空,则左子树上所有结点的值均小于等于根结点的值;
      ②若它的右子树非空,则右子树上所有结点的值均大于等于根结点的值;
      ③左、右子树本身又各是一棵二叉排序树。
  • 二叉排序树特点:二叉排序树中任一结点x,其左(右)子树中任一结点y(若存在)的关键字必小(大)于x的关键字。各结点关键字是惟一的。 按中序遍历该树所得到的中序序列是一个递增有序序列。
  • 下图所示两棵树均是二叉排序树,他们的中序序列均为有序序列:2,3,4,5,6,7,8
    二叉排序树
  • 二叉排序树操作:一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即是对无序序列进行排序的过程。每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。
  • 搜索、插入、删除的时间复杂度等于树高,期望O(logn),最坏O(n)(数列有序,树退化成线性表,如右斜树)。虽然二叉排序树的最坏效率是O(n),但它支持动态查找,且有很多改进版的二叉排序树可以使树高为O(logn),如AVL、红黑树等。
  • 插入算法:
	若二叉排序树T为空,则为待插入的关键字key申请一个新结点,并令其为根;
	若二叉排序树T不为空,则将key和根的关键字比较:
		若二者相等,则说明树中已有此关键字key,无须插入。
		若key<T→key,则将key插入根的左子树中。
		若key>T→key,则将它插入根的右子树中。

  子树中的插入过程与上述的树中插入过程相同。如此进行下去,直到将key作为一个新的叶结点的关键字插入到二叉排序树中,或者直到发现树中已有此关键字为止。

  • 查找算法:
	若二叉树T为空树,则搜索失败,否则:
	若查找的数x等于T根节点的数据域的值,则查找成功,否则:
	若查找的数x小于T根节点的数据域的值,则搜索左子树,否则:
	查找右子树
  • 删除算法:
	1.	若p结点为叶子结点,即该节点左子树PL和右子树PR均为空树。由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针即可。
	2.	若p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点f的左子树(当p是左子树)或右子树(当p是右子树)即可,
	作此修改也不破坏二叉排序树的特性。
	3.  若p结点的左子树和右子树均不空。在删去p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整。
	比较好的做法是,找到p的直接前驱(或直接后继)s,用s来替换结点p,然后再删除结点s。
  • 二叉排序树性能分析:每个结点的Ci为该结点的层次数。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和logn成正比(O(log2(n)))。最坏情况下,当先后插入的关键字有序时,构成的二叉排序树为一棵斜树,树的深度为n,其平均查找长度为(n + 1) / 2。也就是时间复杂度为O(n),等同于顺序查找。因此,如果希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树(平衡二叉树)。

2.2 平衡二叉树

  • 平衡二叉树:要求对于每一个节点来说,它的左右子树的高度之差不能超过1,如果插入或者删除一个节点使得高度之差大于1,就要进行节点之间的旋转,将二叉树重新维持在一个平衡状态。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
  • 平衡二叉树实现的大部分过程和二叉查找树是一样的,区别就在于插入和删除之后要写一个旋转算法去维持平衡,维持平衡需要借助一个节点高度的属性。
  • 满足平衡二叉树的条件:一棵空树是平衡二叉树;若 T 是一棵非空二叉树,其左、右子树为 TL 和 TR ,令 hl 和 hr 分别为左、右子树的深度。当且仅当TL 、 TR 都是平衡二叉树;| hl - hr |≤ 1时,则 T 是平衡二叉树。定义 hl - hr 为二叉平衡树的平衡因子 (balance factor) 。因此,平衡二叉树上所有结点的平衡因子可能是 -1,0 ,1 。若一棵二叉树上任一结点的平衡因子的绝对值都不大于 1 ,则该树是就平衡二叉树。
  • 最小不平衡子树:以离插入结点最近、且平衡因子绝对值大于 1 的结点作根结点的子树。假设二叉排序树的最小不平衡子树的根结点为 A,调整该子树的规律可归纳为下列四种情况:LL型、RR型、LR型和RL型。
  • LL型:新结点 X 插在 A 的左孩子的左子树里,以 B 为轴心,将 A 结点从 B 的右上方转到 B 的右下侧,使 A 成为 B 的右孩子。
  • RR型:新结点 X 插在 A 的右孩子的右子树里。以 B 为轴心,将 A 结点从 B 的右上方转到 B 的右下侧,使 A 成为 B 的右孩子。
  • LR型:新结点 X 插在 A 的左孩子的右子树里。分为两步进行:第一步以 X 为轴心,将 B 从 X 的左上方转到 X 的左下侧,使 B 成为 X 的左孩子, X 成为 A 的左孩子。第二步跟 LL 型一样处理 ( 应以 X 为轴心 ) 。
  • RL型:新结点 X 插在 A 的左孩子的右子树里。分为两步进行:第一步以 X 为轴心,将 B 从 X 的左上方转到 X 的左下侧,使 B 成为 X 的左孩子, X 成为 A 的左孩子。第二步跟 LL 型一样处理 ( 应以 X 为轴心 ) 。
  • 平衡二叉树的创建:平衡二叉树上插入节点采用递归的方式进行。递归算法如下:
	1	若该树为一空树,那么插入一个数据元素为e的新节点作为平衡二叉树的根节点,树的高度增加12	若待插入的数据元素e和平衡二叉树的根节点的关键字相等,那么就不需要进行插入操作。
	3	若待插入的元素e比平衡二叉树的根节点的关键字小,而且在平衡二叉树的左子树中也不存在和e有相同关键字的节点,则将e插入在平衡二叉树的左子树上,并且当插入之后的左子树深度增加1时,分别就下列情况处理之:
		1 平衡二叉树的根节点的平衡因子为-1(右子树的深度大于左子树的深度):则将根节点的平衡因子更改为0,平衡二叉树的深度不变;
		2 平衡二叉树的根节点的平衡因子为0(左右子树的深度相等):则将根节点的平衡因子修改为1,平衡二叉树的深度增加13 平衡二叉树的根节点的平衡因子为1(左子树的深度大于右子树的深度):若平衡二叉树的左子树根节点的平衡因子为1,则需要进行单向右旋转平衡处理,并且在右旋处理后,将根节点和其右子树根节点的平衡因子更改为0,树的深度不变;
		4 若平衡二叉树的左子树根节点的平衡因子为-1,则需进行先向左,后向右的双向旋转平衡处理,并且在旋转处理之后,修改根节点和其左,右子树根节点的平衡因子,树的深度不变;
	4	若e的关键字大于平衡二叉树的根节点的关键字,而且在平衡二叉树的右子树中不存在和e有相同关键字的节点,则将e插入到平衡二叉树的右子树上,并且当插入之后的右子树深度加1时,分别就不同的情况处理之:
		1 平衡二叉树的根节点的平衡因子是1(左子树的深度大于右子树的深度):则将根节点的平衡因子修改为0,平衡二叉树的深度不变;
		2 平衡二叉树的根节点的平衡因子是0(左右子树的深度相等):则将根节点的平衡因子修改为-1,树的深度加13 平衡二叉树的根节点的平衡因子为-1(右子树的深度大于左子树的深度):若平衡二叉树的右子树根节点的平衡因子为1,则需要进行两次选择,第一次先向右旋转,再向左旋转处理,并且在旋转处理之后,修改根节点和其左,右子树根节点的平衡因子,树的深度不变;
		4 若平衡二叉树的右子树根节点的平衡因子为1,则需要进行一次向左的旋转处理,并且在左旋之后,更新根节点和其左,右子树根节点的平衡因子,树的深度不变;
		
  • 举例:设一组记录的关键字按以下次序进行插入: 4 、 5 、 7 , 2 、 1 、 3 、 6 ,创建平衡二叉树。
    创建平衡二叉树
  • 红黑树:红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
  • 红黑树的性质:红黑树上每个结点内含五个域,color,key,left,right,parent。如果相应的指针域没有,则设为NULL。只有满足以下全部性质的树,我们才称之为红黑树:
      1 每个结点要么是红的,要么是黑的。
      2 根结点是黑的。
      3 每个叶结点,即空结点(NULL)是黑的。
      4 如果一个结点是红的,那么它的俩个儿子都是黑的。
      5 从根到叶节点的每条路径,必须包含相同数目的黑色节点。
  • 红黑树操作:当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质。为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树种某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质。

三、排序

  • 排序的稳定性:如果在序列中有两个数据元素r[i]和r[j],它们的关键字k[i] == k [j],且在排序之前,对象r[i]排在r[j]前面。如果在排序之后,对象r[i]仍在r[j]前面,则称这个排序方法是稳定的,否则称这个排序方法是不稳定的。
  • 排序的时间性能是区分排序算法好坏的主要因素。
  • 选择排序:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。是不稳定的排序方法。
  • 冒泡排序:设数组长度为N。比较相邻的前后两个数据,如果前面数据大于后面的数据,就将二个数据交换。这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“升”到数组第N-1个位置。N=N-1,如果N不为0就重复前面二步,否则排序完成。冒泡排序是一种效率低下的排序方法,在数据规模很小时,可以采用。可以对冒泡进行优化,添加flag作为标记,记录序列是否已经有序,减少循环次数。是一种稳定的排序算法。
  • 插入排序:每一次从无序序列中拿出一个数据,将它放到已排序的序序列的正确位置,如此重复,直到所有的无序序列中的数据都找到了正确位置。设数组为a[0…n-1]。 初始时,a[0]自成1个有序区,无序区为a[1…n-1]。令i=1;将a[i]并入当前的有序区a[0…i-1]中形成a[0…i]的有序区间;i++并重复第二步直到i==n-1。排序完成。它是一种稳定的排序算法,对局部有序的数据具有较高的效率。
  • 希尔排序:希尔排序的实质就是分组插入排序,又称缩小增量排序。先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前三种方法有较大提高。是不稳定的排序算法。
  • 快速排序:一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。先从数列中取出一个数作为基准数(枢轴)。分区过程将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。再对左右区间重复第二步,直到各区间只有一个数。是一种不稳定的排序算法。
  • 归并排序:采用分治法,基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。是一种稳定的排序算法。如何合并两个有序序列?只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
  • 堆排序:二叉堆是完全二叉树或者是近似完全二叉树。满足二个特性:
      1 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
      2 每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
      当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。
  • 一般都用数组来表示堆,从数组的1号位置开始存储树的根节点,i结点的父结点下标就为i / 2。它的左右子结点下标分别为2 * i 和2 * i + 1。如第1个结点左右子结点下标分别为2和3。数组的零号位置一般空闲不用。
    堆的存储
  • 堆的插入(向上渗透):每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中。
  • 堆的删除(向下渗透):按定义,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。

排序算法总结


总结

在这里插入图片描述
本文章涉及代码:https://github.com/wangchengyongnevergiveup/C3_data_structure.git
笔记来源:黑马程序员C语言课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值