Python数据结构

数据结构是指相互之间具有(存在)一定联系(关系)的数据元素的集合

一、数据结构的存储方式

顺序存储结构: 用数据元素在存储器中的相对位置来表示数据元素之间的逻辑结 构(关系)

链式存储结构: 在每一个数据元素中增加一个存放另一个元素地址的指针 (pointer ),用该指针来表示数据元素之间的逻辑结构(关系)

二、逻辑结构和物理结构

逻辑结构: 元素之间的相互联系(关系)

四种基本类型

集合: 结构中的数据元素除了“同属于一个集合”外,没有其它关系

线性结构: 结构中的数据元素之间存在一对一的关系

树型结构: 结构中的数据元素之间存在一对多的关系

图状结构或网状结构: 结构中的数据元素之间存在多对多的关系

三、数据结构的运算

数据结构的主要运算包括: 建立(Create); 销毁(Destroy); 插入(Insert); 删除(Delete); 修改(Modify); 查找(Search); 访问(Access);排序(Sort);

四、线性表

1、线性结构

是最常用、最简单的一种数据结构

基本特点是线性表中的数据元素是有序且有限的

在这种结构中:

① 存在一个唯一的被称为“第一个”的数据元素;

② 存在一个唯一的被称为“最后一个”的数据元素;

③ 除第一个元素外,每个元素均有唯一一个直接前驱;

④ 除最后一个元素外,每个元素均有唯一一个直接后继

2、线性表的顺序存储

使用数组来描述顺序表

3、线性表的链式存储

用一组任意的存储单元存储线性表中的数据元素,用这种方法存储的线性表简称线性链表

存储链表中结点的一组任意的存储单元以是连续的或不连续的,甚至是零散分布 在内存中的任意位置

为了正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其 直接后继结点的地址(或位置),称为指针(pointer)或链(link),这两部分组成了链表中的结点结构

链表是通过每个结点的指针域将线性表的 n 个结点按其逻辑次序链接在一起的。 每一个结点只包含一个指针域的链表,称为单链表

为操作方便,总是在链表的第一个结点之前附设一个头指针 head 指向第一个结点

4、双向链表

双向链表(Double Linked List) 指的是构成链表的每个结点中设立两个指针域,一 个指向其直接前趋的指针域 prior,一个指向其直接后继的指针域 next,这样形 成的链表中有两个方向不同的链,故称为双向链表

和单链表类似,双向链表一般增加头指针也能使双链表的某些运算变得方便

将头结点和尾结点链接起来也能构成循环链表,并称之为双向循环链表

双向链表是为了克服单链表的单向性的缺陷而引入的

5、双向链表的结点及其类型定义

五、队列和栈

栈和队列是两种应用非常广泛的数据结构,它们都来自线性表数据结构,都是“操作受限”的线性表

栈在计算机的实现有多种方式:

硬堆栈: 利用 CPU 中的某些寄存器组或类似的硬件或使用内存的特殊区域来实现,这类堆栈容量有限,但速度很快

软堆栈: 这类堆栈主要在内存中实现。实现方式又有动态方式和静态方式两种

1、栈的基本概念

栈(Stack): 是限制在表的一端进行插入和删除操作的线性表,又称为后进先出LIFO (Last In First Out)或先进后出 FILO (First In Last Out)线性表

栈顶(Top): 允许进行插入、删除操作的一端,又称为表尾。 用栈顶指针(top)来指
示栈顶元素

栈底(Bottom): 是固定端,又称为表头

空栈: 当表中没有元素时称为空栈

2、栈的顺序存储表示

栈的顺序存储结构简称为顺序栈,和线性表相类似,用一维数组来存储栈,根据数组是否根据需要增大,又分为静态顺序栈和动态顺序栈

静态顺序栈实现简单,但不能根据需要增大栈的存储空间

动态顺序栈根据需要增大栈的存储空间,但实现稍为复杂

3、栈的链式存储表示

栈的链式存储结构称为链栈,是运算受限的单链表,其插入和删除操作只能在表 头位置进行,因此,链栈没有必要像单链表那样附加头结点,栈顶指针 top 就是 链表的头指针

4、栈的应用

由于栈具有的“后进先出”的固有特性,因此,栈成为程序设计中常用的工具和数据结构,中缀表达式变后缀表达式就是栈应用的例子

后缀表达式计算

中缀表达式变后缀表达式

栈与递归调用的实现

5、队列

队列(Queue): 也是运算受限的线性表。是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在表的一端进行插入,而在另一端进行删除

队首(front) : 允许进行删除的一端称为队首

队尾(rear) : 允许进行插入的一端称为队尾

队列中没有元素时称为空队列。在空队列中依次加入元素 a1, a2, …, an 之后,a1 是队首元素,an 是队尾元素。显然退出队列的次序也只能是 a1, a2, …, an ,即队列的修改是依先进先出的原则进行的

6、循环队列

为充分利用向量空间,克服“假溢出”现象,将队列分配的向量空间看成为一个首 尾相接的圆环,并称这种队列为循环队列(Circular Queue)

7、队列的链式表示和实现

队列的链式存储结构简称为链队列,它是限制仅在表头进行删除操作和表尾进行插入操作的单链表

需要两类不同的结点: 数据元素结点,队列的队首指针和队尾指针的结点

六、树

树型结构是一类非常重要的非线性结构,直观地,树型结构是以分支关系定义的层次结构

树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,用树来组织信息;在分析算法的行为时,用树来描述其执行过程等等

1、树的定义

树(Tree)是 n(n≧0)个结点的有限集合 T,若 n=0 时称为空树,否则: 有且只有一个特殊的称为树的根(Root)结点

若 n>1 时,其余的结点被分为 m(m>0)个互不相交的子集 T1, T2, T3…Tm,其中每 个子集本身又是一棵树,称其为根的子树(Subtree)

这是树的递归定义,即用树来定义树,而只有一个结点的树必定仅由根组成

2、树的基本术语

结点(node): 一个数据元素及其若干指向其子树的分支

结点的度(degree) 、树的度: 结点所拥有的子树的棵数称为结点的度,树中结 点度的最大值称为树的度

叶子(left)结点、非叶子结点

树中度为 0 的结点称为叶子结点(或终端结点),相对应地,度不为 0 的结点称为 非叶子结点(或非终端结点或分支结点),除根结点外,分支结点又称为内部结点

孩子结点、双亲结点、兄弟结点

一个结点的子树的根称为该结点的孩子结点(child)或子结点,相应地,该结点是其孩子结点的双亲结点(parent)或父结点

森林(forest): 是 m(m≧0)棵互不相交的树的集合,显然,若将一棵树的根结点删除,剩余的子树就构成了森林

3、树的表示形式

倒悬树是最常用的表示形式

嵌套集合: 是一些集合的集体,对于任何两个集合,或者不相交,或者一个集合包含另一个集合

广义表形式

4、二叉树

二叉树(Binary tree) 是 n(n≥0)个结点的有限集合,若 n=0 时称为空树,否则: 有且只有一个特殊的称为树的根(Root)结点

若 n>1 时,其余的结点被分成为二个互不相交的子集 T1,T2,分别称之为左、右 子树,并且左、右子树又都是二叉树

由此可知,二叉树的定义是递归的

二叉树在树结构中起着非常重要的作用,因为二叉树结构简单,存储效率高,树 的操作算法相对简单,且任何树都很容易转化成二叉树结构,上节中引入的有关 树的术语也都适用于二叉树

5、二叉树的基本形态

6、二叉树的本质

性质 1:在非空二叉树中,第 i 层上至多有 2i 个结点(i≧1)

性质 2:深度为 k 的二叉树至多有 2k-1 个结点(k≧1)

性质 3:对任何一棵二叉树,若其叶子结点数为 n0,度为 2 的结点数为 n2,则 n0=n2+1

7、满二叉树

一棵深度为 k 且有 2k-1 个结点的二叉树称为满二叉树(Full Binary Tree)

满二叉树的特点:

(1)每一层上的结点数总是最大结点数

(2)所有的支结点都有左、右子树

(3)可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左 至右”的原则进行

8、完全二叉树

完全二叉树(Complete Binary Tree):如果深度为 k,由 n 个结点的二叉树,当且仅 当其每一个结点都与深度为 k 的满二叉树中编号从 1 到 n 的结点一一对应,该二叉树称为完全二叉树

或深度为 k 的满二叉树中编号从 1 到 n 的前 n 个结点构成了一棵深度为 k 的完全二叉树

其中 2k-1 ≦ n≦2k-1

完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例

完全二叉树的特点是,若完全二叉树的深度为 k ,则所有的叶子结点都出现在 第 k 层或 k-1 层,对于任一结点,如果其右子树的最大层次为 l,则其左子树的 最大层次为 l 或 l+1

9、二叉树的存储结构

顺序存储结构

用一组地址连续的存储单元依次“自上而下、自左至右”存储完全二叉树的数据元素

对于完全二叉树上编号为 i 的结点元素存储在一维数组的下标值为 i-1 的分量中 对于一般的二叉树,将其每个结点与完全二叉树上的结点相对照,存储在一维数 组中

10、链式存储结构

设计不同的结点结构可构成不同的链式存储结构

二叉链表结点,有三个域:一个数据域,两个分别指向左右子结点的指针域

三叉链表结点,除二叉链表的三个域外,再增加一个指针域,用来指向结点的父结点

11、二叉树的链式存储形式

有一棵一般的二叉树,以二叉链表和三叉链表方式存储的结构图

12、遍历二叉树及其应用

遍历二叉树(Traversing Binary Tree)是指按指定的规律对二叉树中的每个结点访问 一次且仅访问一次

所谓访问是指对结点做某种处理,如:输出信息、修改结点的值等

二叉树是一种非线性结构,每个结点都可能有左、右两棵子树,因此,需要寻找 一种规律,使二叉树上的结点能排列在一个线性队列上,从而便于遍历

二叉树的基本组成:根结点、左子树、右子树。若能依次遍历这三部分,就是遍 历了二叉树

若以 L、D、R 分别表示遍历左子树、遍历根结点和遍历右子树,则有六种遍历方案:DLR、LDR、LRD、DRL、RDL、RLD,若规定先左后右,则只有前三种情况三种情况,分别是:

DLR——先(根)序遍历

LDR——中(根)序遍历

LRD——后(根)序遍历

13、构建二叉搜索树

又名二叉排序树

用递归的方法构建树

实现对树的增删改查功能

七、查找算法

1、顺序查找

从表中的第一个元素开始,将给定的值与表中逐个元素的关键字进行比较,直到两者相符,查到所要找的元素为止,否则就是表中没有要找的元素,查找不成功,平均要与表中一半以上元素进行比较,最坏情况下需要比较 n 次

如果线性表为无序表,则不管是顺序存储结构还是链式存储结构,都只能用顺序查找

即使是有序线性表,如果采用链式存储结构,也只能用顺序查找

2、二分法查找

二分查找又称折半查找,它是一种效率较高的查找方法

算法思想: 首先,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位
置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表

重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功

优缺点: 折半查找法的优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难,因此,折半查找方法适用于不经常变
动而查找频繁的有序列表

代码实现:

def binary_chop(alist, data):
    """
    递归解决二分查找
    :param alist:
    :return:
    """
    n = len(alist)
    if n < 1:
        return False
    mid = n // 2  # 
    if alist[mid] > data:
        return binary_chop(alist[0:mid], data)
    elif alist[mid] < data:
        return binary_chop(alist[mid + 1:], data)
    else:
        return True
八、排序算法

排序是将一批(组)任意次序的记录重新排列成按关键字有序的记录序列的过程,其定义为:

给定一组记录序列:{R1 , R2 ,…, Rn},其相应的关键字序列是{K1 , K2 ,…, Kn},确定 1, 2, … n 的一个排列 p1 , p2 ,…, pn,使其相应的关键字满足如下非递减(或非递增)关系: Kp1≤Kp2 ≤…≤Kpn 的序列{Kp1 ,Kp2 , …,Kpn} ,这种操作称为
排序

关键字 Ki 可以是记录 Ri 的主关键字,也可以是次关键字或若干数据项的组合

Ki 是主关键字:排序后得到的结果是唯一的

Ki 是次关键字:排序后得到的结果是不唯一的

1、冒泡排序

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成,这个算
法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端

比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这步做完后,最后的元素会是最大的数

针对所有的元素重复以上的步骤,除了最后一个

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

交换过程图示(第一次):

那么我们需要进行 n-1 次冒泡过程,每次对应的比较次数如下图所示:

代码实现:

# 冒泡排序

def bubble_sort(alist):
    for j in range(len(alist) - 1, 0, -1):
        # j 表示每次遍历需要比较的次数,是逐渐减小的
        for i in range(j):
            if alist[i] > alist[i + 1]:
                alist[i], alist[i + 1] = alist[i + 1], alist[i]
                
                
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
bubble_sort(li)
print(li)

2、插入排序

插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作 原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描, 找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复 把已排序元素逐步向后挪位,为最新元素提供插入空间

插入排序分析:

代码实现:

# 选择排序
def insert_sort(alist):
    # 从第二个位置,即下标为 1 的元素开始向前插入 
    for i in range(1, len(alist)):
        # 从第 i 个元素开始向前比较,如果小于前一个元素,交换位置 
        for j in range(i, 0, -1):
            if alist[j] < alist[j - 1]:
                alist[j], alist[j - 1] = alist[j - 1], alist[j]


alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]

insert_sort(alist)
print(alist)

3、选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未 排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序 元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元 素均排序完毕

选择排序的主要优点与数据移动有关,如果某个元素位于正确的最终位置上,则它不会 被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对 n 个元素的表进行排序总共进行至多 n-1 次交换。在所有的完全依靠交换去移动元 素的排序方法中,选择排序属于非常好的一种

选择排序分析

排序过程:

代码实现:

# 选择排序
def selection_sort(alist):
    n = len(alist)
    # 需要进行 n-1 次选择操作
    for i in range(n - 1):
        # 记录最小位置
        min_index = i
        # 从 i+1 位置到末尾选择出最小数据
        for j in range(i + 1, n):
            if alist[j] < alist[min_index]:
                min_index = j
            # 如果选择出的数据不在正确位置,进行交换
        if min_index != i:
            alist[i], alist[min_index] = alist[min_index], alist[i]


alist = [54, 226, 93, 17, 77, 31, 44, 55, 20]
selection_sort(alist)
print(alist)

4、快速排序

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通 过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部 分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过 程可以递归进行,以此达到整个数据变成有序序列

步骤为:

(1)从数列中挑出一个元素,称为"基准"(pivot),

(2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆 在基准的后面(相同的数可以到任一边),在这个分区结束之后,该基准就处于数列 的中间位置,这个称为分区(partition)操作

(3)递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了,虽然一 直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一 个元素摆到它最后的位置去

快速排序的分析

代码实现:

# # 快速排序
@print_execute_time
def quick_sort(alist, start, end):
    """快速排序"""
    # 递归的退出条件
    if start >= end:
        return
    # 设定起始元素为要寻找位置的基准元素
    mid = alist[start]
    # low 为序列左边的由左向右移动的游标
    low = start
    # high 为序列右边的由右向左移动的游标
    high = end
    while low < high:
        # 如果 low 与 high 未重合,high 指向的元素不比基准元素小,则 high 向左移动
        while low < high and alist[high] >= mid:
            high -= 1
            # 将 high 指向的元素放到 low 的位置上
        alist[low] = alist[high]
        # 如果 low 与 high 未重合,low 指向的元素比基准元素小,则 low 向右移动
        while low < high and alist[low] < mid:
            low += 1
        # 将 low 指向的元素放到 high 的位置上
        alist[high] = alist[low]
    # 退出循环后,low 与 high 重合,此时所指位置为基准元素的正确位置
    # 将基准元素放到该位置
    alist[low] = mid
    # 对基准元素左边的子序列进行快速排序
    quick_sort(alist, start, low - 1)
    # 对基准元素右边的子序列进行快速排序
    quick_sort(alist, low + 1, end)


alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
quick_sort(alist, 0, len(alist) - 1)
print(alist)
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值