Python算法概述(3)

七、树形结构及其算法

1、若二叉树的高度为h,树的节点数为2^h-1,h≧0,就称此树为“满二叉树”。

2、完全二叉树
若二叉树的高度为h,所含的节点数小于2^h-1,但其节点的编号方式如同高度为h的满二叉树一样,从左到右,从上到下的顺序一一对应。
对于完全二叉树而言,假设有N个节点,那么此二叉树的层数h为[log(N+1)]
在这里插入图片描述
3、斜二叉树
当一个二叉树完全没有右节点或者左节点时,就称为左斜二叉树或右斜二叉树

4、严格二叉树
二叉树中的每一个非终端节点均有非空的左右子树

7.1 用数组实现二叉树

使用有序的一组数组来表示二叉树,先将二叉树假想成一棵满二叉树,而且第k层具有2^(k-1)个节点,按序存放在一维数组中。
首先来看使用一维数组建立二叉树的表示方法以及数组索引值的设置
在这里插入图片描述
从上图可看出一维数组中的索引值有以下关系:
① 左子树的索引值是父节点的索引值乘以2
② 右子树的索引值是父子树索引值乘以2加1
二叉查找树具有以下特点:
(1)可空集合,若不是空集合,则节点上一定要有一个键值
(2)每一个树根的值需要大于左子树的值
(3)每一个树根的值需要小于右子树的值
(4)左右子树也是二叉查找树
(5)树的每个节点值都不相同

7.2 用链表实现二叉树

所谓链表实现二叉树,就是使用链表来存储二叉树。使用链表来表示二叉树的好处是对于节点的增加和删除相当容易,缺点是难找到父节点,除非在每一个节点多增加一个父字段。
二叉树的类声明:

class tree:
def __init__(self):
   self.data=0
   self.lelf=None
   self.right=none

链表实现二叉树的示意图:
在这里插入图片描述

7.3 二叉树遍历

二叉树的遍历,最简单的说法就是“访问树中所有的节点各一次”,并且在遍历后,将树中的数据转化为线性关系。
在这里插入图片描述

7.3.1 中序遍历

中序遍历是“左中右”的遍历顺序,也就是从树的左侧逐步向下方移动,直到无法移动,再访问此节点,并向右移动一节点。若无法再向右移动时,可以返回上层的父节点,并重复左、中、右的步骤:

  • 遍历左子树
  • 遍历树根
  • 遍历右子树
7.3.2 后序遍历

后序遍历时“左右中”的遍历顺序,就是先遍历左子树,再遍历右子树,最后遍历根节点,反复执行此步骤:

  • 遍历左子树
  • 遍历右子树
  • 遍历树根
7.3.3 前序遍历

前序遍历是“中左右”的遍历顺序,也就是从根节点遍历,再往左方移动,当无法继续时,继续向右方移动,接着再重复执行此步骤:

  • 遍历树根
  • 遍历左子树
  • 遍历右子树

7.4 二叉树节点的查找

二叉树在建立的过程中,是树根左子树<树根<右子树的原则建立的,因此只需从树根出发比较键值,若比树根大就往右,否则往左而下,直到相等就找到了要查找的值,如果比到None,无法再前进就代表查找不到此值。

7.5 二叉树节点的插入

二叉树的插入和查找相似,重点是插入之后仍要保持二叉查找树的特性。如果插入的节点已经在二叉树中,就没有插入的必要了。如果插入的值不在二叉树中,就出现查找失败的情况,相当于找到了要插入的位置。

7.6二叉树节点删除

二叉树节点的删除操作稍为复杂,可分为以下三种情况。
① 删除的节点为树叶,只要将其相连的父节点指向None即可;
② 删除的节点只有一棵子树。删除节点4,就将其右指针字段放到父节点的右指针字段。
在这里插入图片描述

③ 删除的节点有两棵子树,删除节点1,方式有两种,虽然结果不同,但都符合二叉树的特性。

  • 找出中序立即先行者(inorder immediate predecessor),就是将欲删除节点的左子树中最大者向上提。即是在该节点地左子树,往右寻找,直到右指针为None,这个节点就是中序立即先行者。

  • 找出中序立即后续者(inorder immediate successor),就是把要删除节点地右子树中最小者向上提。即是在该节点地右子树,往左寻找,直到左指针为None,这个节点就是中序立即后继者。

7.7堆积树排序法

堆积排序法算是选择排序法地改进版,它可减少在选择排序法中地比较此树,进而减少排序时间。堆积排序法用到二叉树地技巧,它是利用堆积树来完成排序的。堆积树是一种特殊的二叉树,可分为最大堆积树和最小堆积树两种。

7.7.1 最大堆积树满足以下三个条件:
  • 它是一个完全二叉树
  • 所有节点的值都大于或等于它左右子节点的值
  • 树根是堆积树中最大的
7.7.2 最小堆积树满足以下三个条件:
  • 它是一个完全二叉树
  • 所有节点的值都小于或等于它左右子节点的值
  • 树根是堆积树中最小的

八、图的数据结构及其算法

8.1图的遍历

一个图G=(V,E),存在某一顶点v Ɛ V,我们希望从v开始,通过此节点相邻的节点去访问图G中的其他节点,这就被称为“图的遍历”。也就是从某一个顶点V1开始,遍历可以经过V1到达顶点,接着遍历下一个顶点直到全部顶点遍历完毕为止。
在遍历的过程中可能会重复经过某些顶点和边。通过图的遍历可以判断该图是否连通,并找出连通分至和路径。图遍历的方法有两种,即“深度优先遍历”和“广度优先遍历”,也称“深度优先搜索”和“广度优先搜索”。

8.1.1 深度优先遍历法

深度优先遍历的方式有点类似于前序遍历,从图的某一顶点开始遍历,被访问过的顶点就做上已访问的记号,接着遍历此顶点所有相邻且未访问过的顶点,并做上已访问的标记,再以该点为新的起点,继续进行深度优先的搜索。这种图的遍历方法结合了递归和堆栈两种数据结构的技巧,由于此方法会造成无限循环,因此加入一个变量,判断该节点是否已遍历完毕。

8.1.2 广度优先遍历法

广度优先遍历法则是使用队列和递归技巧来遍历,也是从图的某一顶点开始遍历,被访问过的顶点就做上已访问的记号。接着遍历此顶点的所有相邻且未访问的顶点中的任意一个顶点,并做上已访问的记号,再以该点未新起点继续进行广度优先遍历。

8.2 最小生成树

在这里插入图片描述

8.2.1 Kruskal算法

Kruskal算法是一种构造最小生成树的简单算法,其中的思想比较简单。

基本思想
设G=(V,E)是一个网络,其中|V|=n。Kruskal算法构造最小生成树的过程是:

  1. 初始时取包含G中所有n个顶点但没有任何边的孤立点子图T=(V,{}),T里的每个顶点自成一个连通分量。下面将通过不断扩充T的方式构造G的最小生成树。
  2. 将边集E中的边按权值递增的顺序排序,在构造中的每一步顺序地检查这个边序列,找到下一条(最短的)两端点位于T的两个不同连通分量的边e,把e加人T。这导致两个连通分量由于边e的连接而变成了一个连通分量。
  3. 每次操作使T减少一个连通分量。不断重复这个动作加人新边,直到T中所有顶点都包含在一个连通分量里为止,这个连通分量就是G的一棵最小生成树。
    在这里插入图片描述
    可以先用一个优先队列存储所有的边,这样可保证每次取到的都是最短边。为每个连通分量确定一个代表元,如果两个顶点的代表元相同,它们就互相连通,属于同一连通分量。。加人一条边减少了连通分量,这时需要选一个顶点,让被合并的两个连通分量里的顶点都以它为代表元。完成这件事的简单方法是从原来的两个代表元中任选一个,而后更新另一连通分量中顶点的代表元。

算法的python实现

下面给出Kruskal算法的一个实现。除表reps和mgt外,这个算法还使用了另一个表edges,在其中存储图graph所有的边,并调用Python表的sort操作将这些边按权值从小到大排好序。随后的操作就是逐个选择最短的有效边。
边表的形式是(w,vi,vj),其中w是这条边的权值,vi和vj是其两个端点。在主循环里顺序检查edges里的边(按权值从小到大),如果一条边的两端点代表元不同,就将其加人mst,并更新一个连通分量的代表元。这样反复做到mst里积累了n-1条边(成功得到最小生成树),或者所有边都已检查完毕(没有最小生成树),循环结束。这时在mst里保存的是graph的最小生成树,或最小生成树林。

#Kruskal算法
def Kruskal(graph):
    vnum=graph.vertex_num()
    reps=[i for i in range(vnum)]
    mst,edges=[],[]
    for vi in range(vnum):
        for v,w in graph.out_edges(vi):
            edges.append((w,vi,v))
    edges.sort()
    for w,vi,vj in edges:
        if reps[vi]!=reps[vj]:
            mst.append(((vi,vj),w))
            if len(mst)==vnum-1:
                break
            rep,orep=reps[vi],reps[vj]
            for i in range(vnum):
                if reps[i]==orep:
                    reps[i]=rep
     return mst
8.2.1 Prim算法

prim算法是MST性质的直接应用,其基本思想是:从一个顶点出发,利用MST性质选择最短连接边,扩充已连接的顶点集并加入所选的边,直至结点集合里包含了图中的所有顶点。

算法细节

  • 从图G的顶点集V中任取一顶点(例如顶点v0)放人集合U中,这时U={v0},令边集合ET={},显然T=(U,ET)是一棵树(只包含一个顶点且没有边)。
  • 检查所有一个端点在集合U里而另一个端点在集合V-U的边,找出其中权最小的边e=(u,v)(假设),将顶点v加人顶点集合U,并将e加人边集合。易见,扩充之后的T=(U,ET)仍是一棵树。
  • 重复上面步骤直到U=V(所构造的树已经包含了所有顶点)。这时集合ET里有n-1条边,子图T=(U,ET),就是G的一棵最小生成树。
    在这里插入图片描述
    下面算法里用了一个优先队列cands记录候选边,mst的作用与前面算法相同。开始将边(0,0,0)压人队列,表示从顶点0到自身的长度为0的边,第一个元素是权值。然后执行算法的主循环,直到mst记录了”个顶点(成功构造出最小生成树)或优先队列空(说明图graph不连通,没有最小生成树)时结束。
#prim算法
def Prim(graph):
    vnum=graph.vertex_num()
    mst=[None]*vnum
    cands=PrioQue([(0,0,0)])
    count=0
    while count<vnum and not cands.is_empty():
        w,u,v=cands.dequeue()
        if mst[v]:
            continue
        mst[v]=((u,v),w)
        count+=1
        for vi,w in graph.out_edges(v):
            if not mst[vi]:
                cands.enqueue((w,v,vi))
     return mst

8.3图的最短路径法(简略介绍)

最短路径问题(The Short Path Problem):在有向图G=(V,E)中,每一条边都有一个比例常数W(权重,weight)与之对应,若想求G图中V0到Vi的最少W总和值。
最小成本生成树(MST)就是计算连通网络中每个顶点所需的最少花费,但连通树中任意两顶点的路径不一定是一条花费最少的路径。

8.3.1 Dijkstra 算法于A*算法
1. Dijkstra 算法

一个顶点到多个顶点通常使用Dijkstra算法求得,Dijkstra的算法如下:
假设S={Vi | Vi ∈ V},且Vi在已发现的最短路径中,其中V0 ∈ S是起点。
假设w ∉S,定义Dist(w)是从V0到w的最短路径,这条路径除了w外必属于S,且有下列几点特性:
(1)如果u是当前找到最短路径的下一个节点,则u必属于V-S集合中最小成本的边。
(2)若u被选中,将u加入S集合中,则会产生当前从V0到u的最短路径,对于w ∉S, DIST(w)被改变称DIST(w)=Min{DIST(w),DIST(u) + COST(u,w)}

2.A*算法

A* 算法会预先设置一个“推测权重”,并在查找最短路径的过程中,将“推测权重”一并纳入决定最短路径的考虑因素。所谓”权重推测“就是根据事先知道的信息来给定一个预估值,结合这个预估值,A*算法可以更有效地查找最短路径。

8.3.2 Floyd算法

求出图中任意两点甚至所有顶点间最短路径,就必须使用Floyd算法。
在这里插入图片描述

  1. 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
  2. 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。

优缺点
Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法,也要高于执行|V|次SPFA算法。

优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高,不适合计算大量数据。

数据结构和算法就介绍到这里了,欢迎各位朋友评论,收藏,转发。

python的算法概述是本人的总结的读书笔记,可能存在欠缺,还望各位朋友多多指教。

我为什么要学习数据结构和算法?
原因是本人觉得数据结构和算法的逻辑思想很有意思!

其实所有编程语言的数据结构和算法几乎都一样的,只要掌握了思想和原理,可以融汇贯通。

目前是 大数据时代 DT(Big Data Term)和 人工智能时代 IT (Artificial Intelligence Term)的融合阶段,我们应保持学习,乐于知识共享,为创造充满活力的环境贡献点滴力量。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值