1.数据结构与算法 基础知识

数据结构与算法 (Python 版)

知识点一 : 算法 概念


1.概述:
  1.1 算法 (Algorithm)即一个计算过程, 即用来解决问题的方法.
  
  1.2 著名 IT 科学家尼古拉斯·沃斯 (Niklaus Wirth) 认为 " 程序 = 数据结构 + 算法 "

图片/数据结构与算法1.png


  
  
  

知识点二 : 时间复杂度


1.概述:
  1.1 时间复杂度: 用来评估算法运行效率的一个式子 ( 即用一个类似于公式的东西能够形象的比较两个算法的快慢 )
  
  1.2 为什么不使用时间来体现算法的运行快慢?
    - 电脑本身内存的运行效率有差异.
    - 执行代码的次数有差异.(如for循环1次,10次,100次都有所不同)

2.案例:
  2.1 打印一行代码
    我们将一个print定义为叫O(1),我们将O理解为一个约等号,大约的意思,括号中的东西就类似于单位,1就是一个单位,类似于秒这个单位,但不是1秒,而就是1,它没有后缀.

# 只执行了一次,时间消耗大约为1,即O(1)
print("Hello World")

  
  2.2 打印一个for循环

# i执行n次, 时间消耗大约为 1×n, 即O(n)
for i in range(n):
	print("Hello World")

  
  2.3 打印两个for循环

# i执行1次时,j执行n次 时间消耗大约为 1×n,即O(n)
# 然而i本身还要执行n次,所以是 n×(1×n), 最终的执行时间为 O(n^2)
for i in range(n):
    for j in range(n):
        print("Hello World")

  
  2.4 打印三个for循环

# 同两个for循环,for循环执行n次即时间消耗O(n),三次循环即 n×n×n,故最终的消耗时间为O(n^3)
for i in range(n):
    for j in range(n):
        for k in range(n):
            print("Hello World")

  
  2.5 打印三行代码

# 执行三次,因为执行三次print和执行一次print的的差距几乎忽略不计,时间复杂度 O(1)
# 因为按正常逻辑来说,执行三次print的时间复杂度本来应是3×1 即O(3)
# 但是因为时间复杂度的低精度性,就直接被约算成了O(1)
print("Hello World")	
print("Hello Python")	
print("Hello Algorithm")	

注意点1 : 执行基本操作,只要它的问题规模不上升到n这么大的时候,它的时间复杂度就是O(1)
    (就像是我们睡觉只会说大约睡了几个小时,而不是说睡了几个小时几分几秒.)

注意点2 : 时间复杂度是一个低精度的计算方式, 只算一个大致的内容即可.

  
  2.6 打印双重for-print循环

# 同理两次for循环应该是n^2,又因为多打印了n次Hello World
# 正常来说,时间复杂度应该是O(n^2+n)
# 但是同样是因为时间复杂度的低精度性,故而这里的时间复杂度应该是O(n^2)
for i in range(n):
    print("Hello World")
    for j in range(n):
        print("Hello World")	

  
  2.7 打印减半while循环

# 当n=64时输出:64,32,16,8,4,2
# 代码每执行一次,n的值都比之前少一半
# 2^6 = 64 → 1og~2~64 = 6
# 所以将时间复杂度记为O(log~2~n) 或 O(logn)
# 循环减半logn
while n > 1:
    print(n)
    n = n // 2

  
3.总结:
  3.1 时间复杂度是用来估计算法运行时间的一个式子 (单位).
  
  3.2 一般来说 (即在基本上相同条件下), 时间复杂度高的算法比复杂度低的算法慢.
  
  3.3 常见的时间复杂度 (按效率排序):
    O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n2logn) < O(n3 )
  
  3.4 复杂问题的时间复杂度:
    O(n!) O(2n) O(nn) …
  
  3.5 快速判断算法复杂度 (适用于绝大多数简单情况,复杂情况需要根据算法执行过程判断):
    - 确定问题规模 n → 如列表排序,需要先确定列表的长度
    - 循环减半过程 → logn
    - k 层关于 n 的循环 → nk (几层循环就是n的几次方)


  
  
  

知识点三 : 空间复杂度


1.概述:
  1.1 空间复杂度: 用来评估算法内存占用大小的式子
    (小电影100MB能看就不要去下300MB的看,代码同理,能用10MB解决的问题就不要用30MB存储去多占用空间.)
图片/数据结构与算法2.png

  
  1.2 空间复杂度表示:
    - 算法使用了几个变量: O(1)
    - 算法使用了长度为 n 的一维列表: O(n)
    - 算法使用了 m 行 n 列的二维列表: O(mn)
     (空间复杂度一般来说有O(1)、O(n)、O(n2)、O(logn)、O(NlogN)几种.但是其实最常用的也就是O(1) 或者 O(n),用到O(n2)的情况都不多)
  
  1.3 空间换时间:
    在我们写算法时,时间复杂度和空间复杂度只能二选一进行优化,但最好是优化时间复杂度,毕竟在这个学习资料都要放3个T的时代,存储空间并不算昂贵,但是时间真的很宝贵。故而优化时:时间复杂度>空间复杂度.


  
  
  

知识点四 : 递归


1.概述:
  1.1 递归的两个特点:
    - 调用自身
    - 结束条件
  
2.实例:
  2.1 先打印后递归

# 标准递归函数:先打印后递归
def func1(x):
    if x > 0:	# 1.有结束条件
        print(x)
        func1(x-1)	# 2.调用自身

        
func1(3)

图片/数据结构与算法3.png

  
  2.2 先递归后打印

# 标准递归函数: 先递归后打印
def func2(x):
    if x > 0:	# 1.有结束条件
        func2(x-1)	# 2.调用自身
        print(x)
  

func2(3)

图片/数据结构与算法4.png


  
  
  

知识点五 : 递归 汉诺塔问题

1.题目

  原版: 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞n片黄金圆盘。大梵天命令婆罗门把圆盘从下自上开始、按大小顺序重新摆放在另一根柱子上。并且规定,小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,如图所示。问应该怎样移动,才能将圆盘移动到另一根柱子上。
  
  数学版: 从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数。

  
  

2.思路

(1) 假设 n = 2 时:
  第一步: 把 圆盘1 从 A 移动到 B
  第二步: 把 圆盘2 从 A 移动到 C
  第三步: 把圆盘1 从 B 移动到 C

图片/数据结构与算法5.png

  
  
(2) 假设有n个盘子时:
  第一步: 把 n-1 个圆盘 从 A 经过 C 移动到 B
  第二步: 把 第n个圆盘 从 A 移到 C
  第三步: 把 n-1 个圆盘 从 B 经过 A 移到到 C

图片/数据结构与算法6.png

  
  

3.代码

# -*- coding: utf-8 -*-

# 定义一个有参无返回值函数,n是圆盘个数, abc分别对应ABC三柱
def HanNuoTa(n, a, b, c):

    if n > 0:
        # 1.把 n-1 个圆盘 从 A 经过 C 移动到 B
        HanNuoTa(n - 1, a, c, b)

        # 2.把 第n个圆盘 从 A 移到 C
        print(f"moving from {a} to {c}")

        # 3.把 n-1 个圆盘 从 B 经过 A 移到到 C
        HanNuoTa(n - 1, b, a, c)


HanNuoTa(3, 'A', 'B', 'C')

图片/数据结构与算法7.png

  
  

4.小结

  • 汉诺塔移动次数的递推式: h(x) = 2h(x-1) + 1
    • h (64) =18446744073709551615
    • 假设婆罗门每秒钟搬一个盘子,则总计需要5800亿年

  
  
  

知识点六 : 查找


  • 查找 : 在一些数据元素中, 通过一定的方法找出与给定关键字相同的数据元素的过程.
  • 列表查找 ( 线性表查找 ) : 从列表中查找指定元素
    • 输入 : 列表、待查找元素
    • 输出 : 元素下标 ( 未找到元素时一般返回None或-1 )
  • 内置列表查找函数 : index( )

  
  
  

1.顺序查找


  • 顺序查找 ( Linear Search ) : 也称线性查找, 从列表第一个元素开始顺序进行搜索, 直到找到元素或搜索到列表最后一个元素为止.
  • 顺序查找时间复杂度 : O(n)

# -*- coding: utf-8 -*-

def linear_search(li, val):

    # enumerate(teration, start): 枚举函数, 默认包含两个参数.
    #   1.iteration参数:需要遍历的参数 (如字典、列表、元组等)
    #   2.start参数:开始的参数,默认为0(不写start那就是从0开始)
    #   3.enumerate函数有两个返回值,第一个返回值为从start参数开始的数,第二个参数为iteration参数中的值。
    for index, value in enumerate(li):
        if value == val:
            return index
        else:
            return None

  
  
  

2.二分查找


  • 二分查找 (Binary Search) : 也称折半查找, 针对一个有序数据集合, 每次都通过跟区间的中间元素对比, 将待查找的区间缩小为之前的一半, 直到找到要查找的元素, 或者区间被缩小为 0.

  • 二分查找图解 : 从列表中查找元素3.

    图片/数据结构与算法9.png

  • 二分查找时间复杂度 : O(logn)

  • 二分查找与顺序查找比较 : 二分查找的效率远高于顺序查找.

    图片/数据结构与算法8.gif

  • 二分查找的局限性 :

    • 二分查找针对的是有序数据, 也就是说我们使用二分查找前必须对数据进行排序.
    • 数据量太小和太大都不适用于二分查找, 太小可以直接用顺序查找, 太大电脑内存撑不住.

# -*- coding: utf-8 -*-

def binary_search(li, val):
    left = 0
    right = len(li) - 1

    # 候选区有值
    while left <= right:
        mid = (left + right) // 2
        if li[mid] == val:
            return mid
        # 待查找的值在mid左侧
        elif li[mid] > val:
            right = mid - 1
        # 待查找的值在mid右侧
        else:
            left = mid + 1
    else:
        return None

  
  

知识点七 : 排序


  • 排序 : 将一组 “无序” 的记录序列调整为 “有序” 的记录序列

  • 列表排序 : 将无序列表变为有序列表

    • 输入 : 列表
    • 输出 : 有序列表
  • 升序与降序

    • 升序 : 按从小到大的顺序排列 (如1、3、5、6、7、9)
    • 降序 : 按从大到小的顺序排列 (如9、8、6、4、3、1)
  • 内置排序函数 : sort( )

  • 常见排序算法 :

    排序 Low B 三人组排序NB三人组其他排序
    1.冒泡排序1.快速排序1.希尔排序
    2.选择排序2.堆排序2.计数排序
    3.插入排序3.归并排序3.基数排序

    图片/数据结构与算法38.png


  
  

1.冒泡排序


  • 冒泡排序 (Bubble Sort):

    • 列表每两个相邻的数, 如果前面比后面大, 则交换这两个数
    • 一趟排序完成后, 则无序区减少一个数, 有序区增加一个数
    • 代码关键点 : 趟、无序区范围
    • 执行 n-1 趟 ( n是列表的长度 )
  • 冒泡排序的时间复杂度 : O(n2)

  • 冒泡排序实例 :

    初始数据:[ 30,13,25,16,47,26,19,29,10 ]

    第一趟执行过程(先从30依次开始比较):

    012345678
    133025164726192910
    132530164726192910
    132516304726192910
    132516304726192910
    132516302647192910
    132516302619472910
    132516302619294710
    132516302619291047

    第一趟 : [13 25 16 30 26 19 29 10 47]

    第二趟:[13 16 25 26 19 29 10 30 47]

    第八趟: [10 13 16 19 25 26 29 30 47]

    注意点1 : 冒泡排序相当于是一个接力,从最左侧的一个 记录(30)开始从左到右开始比较,遇到比自己小的数就将其甩在身后(放到30的左边),遇到比自己大的数就把接力棒交给它(40),直到这一趟中的最大记录到达最右侧,然后开始新一轮接力跑(上一趟的最大记录不再参与此趟)


# -*- coding: utf-8 -*-

import random


def bubble_sort(li):
    # 第i趟
    for i in range(len(li) - 1):
        for j in range(len(li) - i - 1):
            exchange = False
            # >是执行升序排序,<是执行降序排序
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]
                exchange = True

        # print(f"第{i+1}趟:{li}")

        # if not exchange 的意思是 if not False,因为我们之前将False这个值赋给了exchange这个变量
        if not exchange:
            return


li = [random.randint(0, 100) for i in range(10)]

print(f"执行冒泡排序前:{li}")
bubble_sort(li)
print(f"执行冒泡排序后:{li}")

  
  

2.选择排序


  • 选择排序 (Select Sort) : 一趟排序记录最小的数, 将其放到第一个位置; 再一趟排序记录列表无序区最小的数, 放到第二个位置.
  • 选择排序算法关键点 : 有序区和无序区、无序区最小数的位置
  • 选择排序的时间复杂度 : O(n2)

# -*- coding: utf-8 -*-

# 选择排序简单版
# 缺陷:开辟了新的存储空间,算法的利用率低
import random


def select_sort_simple(li):
    li_new = []
    for i in range(len(li)):
        min_val = min(li)
        li_new.append(min_val)
        li.remove(min_val)
    return li_new


li = [random.randint(0,100) for i in range(10)]
print(f"执行简单选择排序前:{li}")
print(f"执行简单选择排序后:{select_sort_simple(li)}")
# -*- coding: utf-8 -*-

import random

# 选择排序完整版
def select_sort(li):
    # i = 0,1,2,3,4,5,6,7,8
    for i in range(len(li)-1):
        min_loc = i
        # i+1少比一次,因为我们设的最开始的最小值为i,相当于减少了它自己跟自己比的那一趟
        # j = 1,2,3,4,5,6,7,8,9
        for j in range(i+1, len(li)):
            # 现在在li列表中j就是i的后一个下标,只要j对应的那个元素比i现在对应的那个元素小,两者就交换位置
            if li[j] < li[min_loc]:
                min_loc = j
        li[i], li[min_loc] = li[min_loc], li[i]
        # print(li)


li = [random.randint(0,100) for i in range(10)]
print(f"执行选择排序前:{li}")
select_sort(li)
print(f"执行选择排序后:{li}")

  
  

3.插入排序


  • 插入排序 (InsertionSort) : 每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止.

  • 插入排序的时间复杂度 : O(n2)

  • 插入排序图解 :

    图片/数据结构与算法10.gif

    图片/数据结构与算法11.png


# -*- coding: utf-8 -*-
import random


def insert_sort(li):
    # i表示摸到的牌的下标
    for i in range(1, len(li)):
        tmp = li[i]
        # j表示手里的牌的下标
        j = i - 1
        while j >= 0 and li[j] > tmp:
            li[j + 1] = li[j]
            j -= 1
        li[j + 1] = tmp
        print(li)


li = [random.randint(0, 100) for i in range(10)]

print(f"执行插入排序前:{li}")
insert_sort(li)
print(f"执行插入排序后:{li}")

  
  

4.快速排序


  • 快速排序 (Quick sort) : 是对冒泡排序的一种改进.

  • 快速排序思路:

    • 取一个元素 p (第一个元素), 使元素p归位
    • 列表被p分成两部分, 左边都比p小, 右边都比p大
    • 递归完成排序
  • 快速排序的时间复杂度: O(nlogn)

  • 快速排序思路图解:

    图片/数据结构与算法12.png

  • 快速排序框架图解:

    假设对以下10个数进行快速排序:

    图片/数据结构与算法13.png

    我们先模拟快速排序的过程:首先,在这个序列中随便找一个数作为基准数,通常为了方便,以第一个数作为基准数。

    图片/数据结构与算法14.png

    在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都≤ 6,右边的数都≥ 6。那么如何找到这个位置k呢?

    我们要知道,快速排序其实是冒泡排序的一种改进,冒泡排序每次对相邻的两个数进行比较,这显然是一种比较浪费时间的。

    而快速排序是分别从两端开始”探测”的,先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i 指向序列的最左边,指向数字6。让哨兵j指向序列的最右边,指向数字8。

    图片/数据结构与算法15.png

    首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要。哨兵j一步一步地向左挪动(即j = j − 1),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i = i + 1),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i ii停在了数字7面前。

    图片/数据结构与算法16.png

    现在交换哨兵 i i i和哨兵 j j j所指向的元素的值。交换之后的序列如下。

    图片/数据结构与算法17.png

    到此,第一次交换结束。接下来开始哨兵j 继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4 < 6,停下来。哨兵i ii也继续向右挪动的,他发现了9 > 6,停下来。此时再次进行交换,交换之后的序列如下。

    图片/数据结构与算法18.png

    第二次交换结束。哨兵j jj继续向左挪动,他发现了3 < 6,又停下来。哨兵i 继续向右移动,此时哨兵i 和哨兵j 相遇了,哨兵i 和哨兵j 都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

    图片/数据结构与算法19.png

    到此第一轮“探测”真正结束。现在基准数6已经归位,此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i ii和j jj碰头为止。

    现在我们将第一轮“探测"结束后的序列,以6为分界点拆分成两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

    图片/数据结构与算法20.png

    重复第一轮的过程,应该得到如下序列:

    图片/数据结构与算法21.png

    OK,现在3已经归位。接下来需要处理3左边的序列:

    图片/数据结构与算法22.png

    处理之后,2已经归位,序列“1”只有一个数,也不需要进行任何处理,因此“1”也归位。

    图片/数据结构与算法23.png

    对于基数右边的序列,采用和左边相同的过程;最终将会得到这样的序列,如下。

    图片/数据结构与算法24.png

  ​ 快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序结束。
  
  快速排序之所以比较快,是因为与冒泡排序相比,每次的交换时跳跃式的,每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是 O(n2),它的平均时间复杂度为O(n log2n)。


# -*- coding: utf-8 -*-
import random

# 第一步:partition函数
def partition(li, left, right):
    tmp = li[left]
    while left < right:
        # 从右边找比tmp小的数
        while left < right and li[right] >= tmp:
            # 往左走一步
            right -= 1
        # 把右边的值写到左边空位上
        li[left] = li[right]
        # print(li, "right")
        while left < right and li[left] <= tmp:
            left += 1
        # 把左边的值写到右边的空位上
        li[right] = li[left]
        # print(li, "left")
    # tmp归位
    li[left] = tmp

    return left

# 第二步:快速排序框架
def quick_sort(li, left, right):
    # 至少要两个元素
    if left < right:
        mid = partition(li, left, right)
        quick_sort(li, left, mid-1)
        quick_sort(li, mid+1, right)

li = [random.randint(0, 100) for i in range(10)]

print(f"执行插入排序前:{li}")
quick_sort(li, 0, len(li) - 1)
print(f"执行插入排序后:{li}")

  
  

5.堆排序


  • 5.1 堆排序前置知识点1: 树

    • 树是一种数据结构 (如目录结构) ,可以递归定义.

    • 树是由 n 个节点组成的集合

      • 如果 n = 0, 那么这是一棵空树 ;
      • 如果 n > 0, 那么存在1个节点作为树的根节点, 其他节点可以分为 m 个集合, 每个集合本身又是一棵树
    • 树的基本概念1: 节点

      • 根节点 : 树最顶部的一个节点, 如节点A.
      • 叶子节点: 树最末端的节点, 如节点B, C, H, I, P, Q, K, L, M, N
      • 子节点: 除根节点之外, 并且本身下面还连接有节点的节点, 如节点D, E, J, F, G

      图片/数据结构与算法25.png

    • 树的基本概念2: 高度 / 深度

      图片/数据结构与算法26.png

    • 树的基本概念3: 树的度

      图片/数据结构与算法27.png

    • 树的基本概念4: 孩子节点 / 父节点

      图片/数据结构与算法28.png

    • 树的基本概念5: 子树

      图片/数据结构与算法29.png

  • 5.2 堆排序前置知识点2: 二叉树

    • 二叉树: 度不超过2的树 (即分支不能多于两条)

      • 二叉树每个节点资源的有两个孩子节点
      • 二叉树有两个孩子节点时, 严格区分左孩子节点和右孩子节点

      图片/数据结构与算法30.png

    • 特殊形态的二叉树:

      • 满二叉树: 一个二叉树如果每一个层的节点都达到最大值, 则这个二叉树就是满二叉树.
      • 完全二叉树: 叶节点只能出现在最下层和次下层, 并且最下面一层的节点都集中在该层最左边的若干位置的二叉树.

      图片/数据结构与算法31.png

    • 二叉树的存储方法 (表示方式):

      • 链式存储方式
      • 顺序存储方式

      图片/数据结构与算法32.png

  • 5.3 堆 定义: 一种特殊的完全二叉树结构

    • 大根堆: 一棵完全二叉树, 满足任一节点都比其他孩子节点大

    • 小根堆: 一棵完全二叉树, 满足任一节点都比其他孩子节点小

      图片/数据结构与算法33.png

  • 5.4 堆 向下调整:

    • 假设根节点的左右子树都是堆,但根节点不满足堆的性质

    • 可以通过一次向下的调整来将其变成一个堆

      图片/数据结构与算法34.png

  • 5.5 堆 排序过程:

    图片/数据结构与算法35.gif

    • 1.建立堆

    • 2.得到堆顶元素,为最大元素

    • 3.去掉堆顶,将堆最后一个元素放到堆顶, 此时可通过一次向下调整重新使堆有序

    • 4.堆顶元素为第二大元素

    • 5.重复步骤3,直到堆变空

      # -*- coding: utf-8 -*-
      
      import random
      
      def sift(li, low, high):
          """
          :param li: 列表
          :param row: 堆的根节点位置
          :param high: 堆的最后一个元素的位置
          :return:
          """
      
          i = low     # i最开始指向根节点
          j = 2 * i + 1      # j最开始是左孩子
          tmp = li[low]      # 把堆顶存起来
          while j <= high:   # 只要j位置有数
              if j+1 <= high and li[j+1] > li[j]:    # 如果右孩子有且比较大
                  i = j + 1  # j指向右孩子
              if li[j] > tmp:
                  li[i] = li[j]
                  i = j
                  j = 2 * i + 1
              else:         # tmp更大,把tmp放到i的位置上
                  li[i] = tmp     # 把tmp放到某一级领导的位置上
                  break
          else:
              li[i] = tmp     # 把tmp放到叶子节点上
      
      def heap_sort(li):
          n = len(li)
          for i in range((n-2)//2, -1, -1):
              # i表示建堆的时候调整的部分的根的下标
              sift(li, i, n-1)
          # 建堆完成
          for i in range(n-1, -1, -1):
              # i指向当前堆的最后一个元素
              li[0], li[i] = li[i], li[0]
              # i-1是新的high
              sift(li, 0, i-1)
      
      li = [i for i in range(10)]
      
      # random.shuffle方法:对元素进行重新排序,打乱原有的顺序,返回一个随机序列(当然此处随机序列属于伪随机,即可重现), 该方法的作用类似洗牌
      random.shuffle(li)
      
      print(li)
      
      heap_sort(li)
      
      print(li)
      

    图片/数据结构与算法35.gif

  • 5.6 堆排序时间复杂度: O(nlogn)

  • 5.7 Python内置的堆排序模块heapq:

    • heapify ( x ) : 建堆

    • heappush ( heap, item ) : 建立大根堆、小根堆

    • heappop ( heap ) : 从堆中弹出并返回最小的值

      # -*- coding: utf-8 -*-
      
      import heapq
      import random
      
      li = list(range(100))
      print(li)
      random.shuffle(li)
      print(li)
      
      # 建堆
      heapq.heapify(li)
      
      n = len(li)
      for i in range(n):
          # 执行一次heappop,则弹出一个元素,这个元素是列表中最小的元素
          print(heapq.heappop(li), end=",")
      
  • 5.8 堆排序 Topk问题

    • 题目: 现在有n个数, 设计算法得到前K大的数. ( k < n )

    • 思路:

      • 1.取列表前k个元素建立一个小根堆. 堆顶就是目前第k大的数
      • 2.依次向后变量原列表. 对于列表中的元素, 如果小于堆顶, 则忽略该元素; 如果大于堆顶, 则将堆顶更换为该元素, 并且堆堆进行一次调整;
      • 3.遍历列表所有元素后,倒序弹出堆顶.
    • 代码:

      # -*- coding: utf-8 -*-
      
      import random
      
      def sift(li, low, high):
      
          i = low     # i最开始指向根节点
          j = 2 * i + 1      # j最开始是左孩子
          tmp = li[low]      # 把堆顶存起来
          while j <= high:   # 只要j位置有数
              # li[j+1] < li[j] 小根堆
              if j+1 <= high and li[j+1] < li[j]:    # 如果右孩子有且比较大
                  i = j + 1  # j指向右孩子
              if li[j] < tmp:     # li[j] < tmp 小根堆
                  li[i] = li[j]
                  i = j
                  j = 2 * i + 1
              else:         # tmp更大,把tmp放到i的位置上
                  li[i] = tmp     # 把tmp放到某一级领导的位置上
                  break
          else:
              li[i] = tmp     # 把tmp放到叶子节点上
      
      
      def topk(li, k):
          heap = li[0:k]
          for i in range((k-2)//2, -1, -1):
              sift(heap, i, k-1)
          # 1.建堆
          for i in range(k, len(li)-1):
              if li[i] > heap[0]:
                  heap[0] = li[i]
                  sift(heap, 0, k-1)
      
          # 2.遍历
          for i in range(k-1, -1, -1):
              heap[0], heap[i] = heap[i], heap[0]
              sift(heap, 0, i-1)
      
          # 3.出数
          return heap
      
      
      li = list(range(100))
      random.shuffle(li)
      print(topk(li, 10))
      

  
  

6.归并排序


  • 6.1 归并定义 : 将一个分两段有序的列表合成一个有序列表的操作叫做归并

  • 6.2 归并原理图解 :

    图片/数据结构与算法36.gif

  • 6.3 归并排序思路 :

    • 分解 : 将列表越分约小, 直至分成一个元素

    • 终止条件 : 一个元素是有序的

    • 合并 : 将两个有序列表归并, 列表越来越大

      图片/数据结构与算法37.png

  • 6.4 归并排序时间复杂度 : O(nlogn)

  • 6.5 归并排序空间复杂度 : O(n)

  • 6.6 归并排序代码实现 :

    # -*- coding: utf-8 -*-
    
    import random
    
    # 归并代码
    def merge(li, low, mid, high):
        i = low
        j = mid + 1
        ltmp = []
        while i <= mid and j <= high:  # 只要左右两边都有数
            if li[i] < li[j]:
                ltmp.append(li[i])
                i += 1
            else:
                ltmp.append(li[j])
                j += 1
        # while执行完毕,肯定是有一部分没数了
        while i <= mid:
            ltmp.append(li[i])
            i += 1
        while j <= high:
            ltmp.append(li[j])
            j += 1
        li[low:high + 1] = ltmp
    
    
        
    # 归并排序代码
    def merge_sort(li, low, high):
        # 至少有两个元素,递归思想
        if low < high:
            mid = (low + high) // 2
            merge_sort(li, low, mid)
            merge_sort(li, mid+1, high)
            merge(li, low, mid, high)
    
    
    li = list(range(10))
    random.shuffle(li)
    print(li)
    merge_sort(li, 0, len(li)-1)
    print(li)
    
  • 6.7 NB三人组小结 :

    • 1.三种排序算法的时间复杂度都是 O(nlogn)
    • 2.一般情况下就运行时间而言 :
      • 快速排序 < 归并排序 < 堆排序
    • 3.三种排序算法的缺点 :
      • 快速排序 : 极端情况下排序效率低
      • 归并排序 : 需要额外的内存开销
      • 堆排序 : 在快的排序算法中相对较慢

  
  

7.希尔排序


  • 7.1 希尔排序 (Shell Sort) : 希尔排序是插入排序的一种, 是直接插入排序算法的进阶版本.

  • 7.2 希尔排序图解:

    图片/数据结构与算法39.png

  • 7.3 希尔排序的时间复杂度 : O(n1.3) ~ O(n2)

    • (较为复杂, 与选取的gap序列有关)
  • 7.4 希尔排序思路 :

    • 首先取一个整数 d1=n/2 ,将元素分为d1个组, 每组相邻量元素直接的距离为d1, 在各组内进行直接插入排序;
    • 取第二个整数d2=d1/2, 重复上述分组排序过程, 直到d1=1, 即所有元素在同一组内进行直接插入排序.
    • 希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序; 最后一趟排序使得所有数据有序.
  • 7.5 希尔排序代码实现 :

    # -*- coding: utf-8 -*-
    
    import random
    
    
    def insert_sort_gap(li, gap):
        for i in range(gap, len(li)):   # i表示摸到的牌的下标
            tmp = li[i]
            j = i - gap  # j指的是手里的牌的下标
            while j >= 0 and li[j] > tmp:
                li[j+gap] = li[j]
                j -= gap
            li[j+gap] = tmp
    
    
    def shell_sort(li):
        d = len(li) // 2
        while d >= 1:
            insert_sort_gap(li, d)
            d //= 2
    
    
    li = list(range(10))
    random.shuffle(li)
    print(li)
    shell_sort(li)
    print(li)
    

  
  

8.计数排序


  • 计数排序 (Count sort) : 对列表进行排序, 已知列表中的数的范围都在0到100之间, 设计时间复杂度为O(n)的算法

  • 计数排序图解 :

    图片/数据结构与算法40.png

  • 计数排序代码实现 :

    # -*- coding: utf-8 -*-
    
    import random
    
    
    def count_sort(li, max_count=100):
        count = [0 for _ in range(max_count+1)]
        for val in li:
            count[val] += 1
        li.clear()
        for ind, val in enumerate(count):
            for i in range(val):
                li.append(ind)
    
    
    li = [random.randint(0, 100) for _ in range(10)]
    print(li)
    count_sort(li)
    print(li)
    

  
  

9.桶排序


  • 桶排序 (Bucket Sort) : 首先将元素分在不同的桶中, 再对每个桶中的元素进行排序. 一般用来解决范围较大的元素排序(如1到1亿之间)

  • 桶排序图解 :

    图片/数据结构与算法41.gif

  • 桶排序的时间复杂度 :

    • 平均情况时间复杂度: O(n+k)
    • 最坏情况时间复杂度: O(n2k)
  • 桶排序的空间复杂度 : O(nk)

  • 桶排序代码实现 :

    # -*- coding: utf-8 -*-
    
    import random
    
    def bucket_sort(i, n=100, max_num=10000):
        # 创建桶
        buckets = [[] for _ in range(n)]
    
        for var in li:
    
            # i 表示放到几号桶中
            i = min(var // (max_num // n), n-1)
            # 将var添加到桶中
            buckets[i].append(var)
    
            # 保持桶内的顺序
            for j in range(len(buckets[i])-1):
                if buckets[i][j] < buckets[i][j-1]:
                    buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]
                else:
                    break
    
        sorted_li = []
        for buc in buckets:
            
            # extend函数: 用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
            sorted_li.extend(buc)
    
        return sorted_li
    
    
    li = [random.randint(0,10000) for i in range(100000)]
    print(li)
    li = bucket_sort(li)
    print(li)
    

  
  

10.基数排序


  • 基数排序 (Radix Sort) : 基数排序是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较

  • 基数排序时间复杂度 : O(kn) k表示数字位数

  • 基数排序空间复杂度 : O(k+n)

  • 基数排序实例 :

    数据:[275,104,66,930,569,154,502,219,7,76]

    将数据进行第一趟分配(按个位):

    0123456789
    930502104275066007569
    154076219

    数据变更为:[930,502,104,154,275,66,76,7,569,219]

    将变更后的数据进行第二趟分配(按十位,只有个位数则其十位以上视为0):

    0123456789
    502219930154066275
    104569076
    007

    数据二次变更为:[502,104,7,219,930,154,66,569,275,76]

    将二次变更后的数据进行第三趟分配(按百位):

    0123456789
    007104219502930
    066154275569
    076

    收集得到最终结果:[7,66,76,104,154,219,275,502,569,930]

    注意点1 : 基数排序先按个位排序,再按十位排序,再按百位排序,其是一个多关键字的排序

  • 基数排序代码实现 :

    # -*- coding: utf-8 -*-
    
    import random
    
    def radix_sort(li):
        # 最大值 9->1, 99->2, 888->3, 10000->5
        max_num = max(li)
        it = 0
        while 10 ** it <= max_num:
            buckets = [[] for _ in range(10)]
            for var in li:
                # 987  it=1  987//10->98 98%10->8;    it=2    987//100->9  9%10=9
                digit = (var // 10 ** it) % 10
                buckets[digit].append(var)
    
            # 分桶完成
            li.clear()
            for buc in buckets:
                li.extend(buc)
    
            # 把数重新写回li
            it += 1
    
    
    li = list(range(100000))
    random.shuffle(li)
    radix_sort(li)
    print(li)
    

  
  

知识点八 : 排序 练习题

练习题一


  • 题目 : 给两个字符串 s 和 t , 判断 t 是否为 s 的重新排列后组成的单词

    • s = “anagram”, t = “nagaram”, return true
    • s = “rat”, t = “car”, return false
  • 代码 :

    # -*- coding: utf-8 -*-
    
    class Solution:
        def isAnagram(self, s, t):
            """
    
            :param s: str
            :param t: str
            :return: bool
            """
    
            # 方案一
            return sorted(list(s)) == sorted((list(t)))
    
    
            # 方案二
            dict1 = {}      # { "a":1, "b":2 }
            dict2 = {}
            for ch in s:
                dict1[ch] = dict1.get(ch, 0) + 1
            for ch in t:
                dict2[ch] = dict2.get(ch, 0) + 1
            return dict1 == dict2
    

  
  

练习题二


  • 题目 : 给定一个 m*n 的二维列表, 查找一个数是否存在. 列表有下列特性:

    • 每行的列表从左到右已经排序好

    • 每行第一个数比上一行最后一个数大

      [
       [ 1, 3, 5, 7],
       [10, 11, 16, 20],
      ​ [23, 30, 34, 50]
      ]

  • 代码 :

    # -*- coding: utf-8 -*-
    
    class Solution:
        def searchMatrix(self, matrix, target):
            """
    
            :param matrix: List[List[int]]
            :param target: int
            :return: bool
            """
    
            h = len(matrix)
            if h == 0:
                return False
            w = len(matrix[0])
            if w == 0:
                return False
            left = 0
            right = w * h - 1
            while left <= right:
                mid = (left + right) // 2
                i = mid // w
                j = mid % w
                if matrix[i][j] == target:
                    return True
                elif matrix[i][j] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                return False
    

  
  

练习题三


  • 题目 : 给定一个列表和一个整数, 设计算法找到两个数的下标, 使得两个数之和为给定的整数.保证肯定仅有一个结果

    • 例如, 列表 [1, 2, 5, 4] 与目标整数 3, 1+2=3, 结果为 (0, 1).
  • 代码 :

    # -*- coding: utf-8 -*-
    
    class Solution:
        def binary_search(self, li, left, right, val):
            while left <= right:  # 候选区有值
                mid = (left + right) // 2
                if li[mid][0] == val:
                    return mid
                elif li[mid][0] > val:  # 带查找的值在mid左侧
                    right = mid - 1
                else:  # li[mid] < val 带查找的值在mid右侧
                    left = mid + 1
            else:
                return None
    
        def twoSum(self, nums, target):
            """
    
            :param nums: List[int]
            :param target: int
            :return: List[int]
            """
            new_nums = [[num, i] for i, num in enumerate(nums)]
            new_nums.sort(key=lambda x: x[0])
    
            for i in range(len(new_nums)):
                a = new_nums[i][0]
                b = target - a
                if b >= a:
                    j = self.binary_search(new_nums, i + 1, len(new_nums) - 1, b)
                else:
                    j = self.binary_search(new_nums, 0, i - 1, b)
                if j:
                    break
    
            return sorted([new_nums[i][1], new_nums[j][1]])
    
    

  
  

知识点九 : 数据结构 概念

1.数据结构定义

  • 数据结构指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成
    • 简单来说, 数据结构就是设计数据以何种方式组织并存储在计算机中, 如列表、集合与字典等都是一种数据结构.

2.数据结构分类

  • 按照其逻辑结构可分为三类
    • 线性结构 : 数据结构中的元素存在一对一的相互关系
    • 树结构 : 数据结构中的元素存在一对多的相互关系
    • 图结构 : 数据结构中的元素存在多对多的相互关系

  
  

知识点十 : 列表

1.列表定义

  • 最简单的线性结构,也叫做顺序表,是一种基本的数据结构, 其他语言中称为数组 (列表和数组有一定区别).

2.列表存储

  • 按照顺序存储, 是一块连续的内存,在32位机器上, 一个整数占4个字节 ( 1字节 (Byte) = 8 比特 (bit))

    图片/数据结构与算法41.png

  
  

知识点十一 : 栈

1.栈的定义

栈 (stack) 是一个数据集合, 可以理解为只能在一段进行插入或删除操作的列表。

2.栈的特点

“先进后出”, 即 LIFO (last-in, first-out)

3.栈的基本操作

  • 3.1 进栈 (压栈) : push

    图片/数据结构与算法42.gif

  • 3.2 出栈 : pop

    图片/数据结构与算法43.gif

  • 3.3 取栈顶 : gettop

4.栈的实现

  • 4.1 进栈 : li.append
  • 4.2 出栈 : li.pop
  • 4.3 取栈顶 : li[-1]

5.栈的应用

  • 括号匹配问题 : 给一个字符串, 其中包括小括号、中括号、大括号, 求该字符串中的括号是否匹配。

  • 例如:

    ()()[]{} 匹配

    ([{()}]) 匹配

    []( 不匹配

    [(]) 不匹配

# -*- coding: utf-8 -*-

class Stack:

    def __init__(self):
        self.stack = []

    def push(self, element):
        self.stack.append(element)

    def pop(self):
        return self.stack.pop()

    def get_top(self):
        if len(self.stack) > 0:
            return self.stack[-1]
        else:
            return None

    def is_empty(self):
        return len(self.stack) == 0

def brace_match(s):

    match = {
        '}': '{',
        ']': '[',
        ')': '('
    }

    stack = Stack()
    for ch in s:
        if ch in {'(', '[', '{'}:
            stack.push(ch)
        else:   # ch in {'(', '[', '{'}
            if stack.is_empty():
                return False
            elif stack.get_top() == match[ch]:
                stack.pop()
            else:   # stack.get_top() != match[ch]
                return False
    if stack.is_empty():
        return True
    else:
        return False

print(brace_match('[{()}(){()}[]({}){}]'))
print(brace_match('[({}])]'))

  
  

知识点十二 : 队列

1.队列定义

队列 (Queue) 即一个数据组合, 仅允许在列表的一端进行插入, 另一端进行删除。进行插入的一端称为队尾 (rear), 插入动作称为进队或入队; 进行删除的一端称为队头 (front), 删除动作称为出队

2.队列的性质

先进先出 (First-in, First-out)

图片/数据结构与算法42.png

3.队列的实现

  • 环形队列 : 当队尾指针front == Maxsize + 1 时, 再前进一个位置就自动到0.
    • 队首指针前进1: front = (front+1) % MaxSize
    • 队尾指针前进1: rear = (rear+1) % MaxSize
    • 队空条件: rear == front
    • 队满条件: (rear+1) % MaxSize == front

图片/数据结构与算法43.png

4.队列的内置模块

  • 双向队列的两端都支持进队和出队操作

  • 双向队列的基本操作

    • 队首进队

    • 队首出队

    • 队尾进队

    • 队尾出队

图片/数据结构与算法44.png

  • 使用方法: from collections import deque

    • 创建队列: queue = deque()
    • 进队: append()
    • 出队: popleft()
    • 双向队列队首进队: appendleft()
    • 双向队列队尾出队: pop

知识点十三 : 栈和队列的应用 迷宫问题

1.题目

给一个二维列表, 表示迷宫 (0表示通道,1表示围墙)。给出算法, 求一条走出迷宫的路径。

2.解法

  • 栈 深度优先搜索

    • 回溯法

      思路: 从一个节点开始, 任意找下一个能走的点, 当找不到能走的点时, 退回上一个点寻找算法有其他方向的点。即使用栈存储当前路径

      图片/数据结构与算法45.png

    • 代码实现

      # -*- coding: utf-8 -*-
      
      
      maze = [
          [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
          [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
          [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
          [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
          [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
          [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
          [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
          [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
          [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
          [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
      ]
      
      dirs = [
          lambda x, y: (x + 1, y),
          lambda x, y: (x - 1, y),
          lambda x, y: (x, y - 1),
          lambda x, y: (x, y + 1)
      ]
      
      
      def maze_path(x1, y1, x2, y2):
          stack = []
          stack.append((x1, y1))
          while (len(stack) > 0):
              curNone = stack[-1]  # 当前的节点
              if curNone[0] == x2 and curNone[1] == y2:
                  # 走到终点了
                  for p in stack:
                      print(p)
                  return True
      
              # x,y 四个方向: x-1, y; x+1,y; x,y-1; x,y+1
              for dir in dirs:
                  nextNode = dir(curNone[0], curNone[1])
                  # 如果下一个节点能走
                  if maze[nextNode[0]][nextNode[1]] == 0:
                      stack.append(nextNode)
                      maze[nextNode[0]][nextNode[1]] = 2  # 2表示已经走过
                      break
              else:
                  maze[nextNode[0]][nextNode[1]] = 2
                  stack.pop()
          else:
              print("没有路")
              return False
      
      
      maze_path(1, 1, 8, 8)
      
  • 队列 广度优先搜索

    • 思路: 从一个节点开始, 寻找所有接下来能继续走的点, 继续不断寻找, 直到找到出口。

      图片/数据结构与算法46.png

    • 代码实现

      # -*- coding: utf-8 -*-
      
      from collections import deque
      
      maze = [
          [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
          [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
          [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
          [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
          [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
          [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
          [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
          [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
          [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
          [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
      ]
      
      dirs = [
          lambda x, y: (x + 1, y),
          lambda x, y: (x - 1, y),
          lambda x, y: (x, y - 1),
          lambda x, y: (x, y + 1)
      ]
      
      def print_r(path):
          curNode = path[-1]
          realpath = []
          while curNode[2] != -1:
              realpath.append(curNode[0:2])
              curNode = path[curNode[2]]
          realpath.append(curNode[0:2])   # 起点
          realpath.reverse()
          for node in realpath:
              print(node)
      
      
      def maze_path_queue(x1, y1, x2, y2):
          queue = deque()
          queue.append((x1, y1, -1))
          path = []
          while len(queue) > 0:
              curNode = queue.popleft()
              path.append(curNode)
              if curNode[0] == x2 and curNode[1] == y2:
                  # 终点
                  print_r(path)
                  return True
              for dir in dirs:
                  nextNode = dir(curNode[0], curNode[1])
                  if maze[nextNode[0]][nextNode[1]] == 0:
                      queue.append((nextNode[0], nextNode[1], len(path) - 1)) # 后续节点进队, 记录哪个节点带他来的
                      maze[nextNode[0]][nextNode[1]] = 2  # 标记为已经走过
          else:
              print("没有路")
              return False
      
      maze_path_queue(1, 1, 8, 8)
      

知识点十四 : 链表

1.链表介绍

  • 链表是由一系列节点组成的元素集合。每个节点包含两个部分, 数据域 item 和指向下一个节点的指针next。 通过节点之间的相互连接, 最终串联成一个链表。

    图片/数据结构与算法47.png

2.创建链表

  • 头插法

    图片/数据结构与算法48.gif

    # -*- coding: utf-8 -*-
    
    class Node:
        def __init__(self, item):
            self.item = item
            self.next = None
    
    
    # 头插法
    def create_linklist_head(li):
        head = Node(li[0])
        for element in li[1:]:
            node = Node(element)
            node.next = head
            head = node
        return head
    
    
    def print_linklist(lk):
        while lk:
            print(lk.item, end=",")
            lk = lk.next
    
    
    lk = create_linklist_head([1, 2, 3])
    print_linklist(lk)
    
    
  • 尾插法

    图片/数据结构与算法49.gif

    # -*- coding: utf-8 -*-
    
    class Node:
        def __init__(self, item):
            self.item = item
            self.next = None
    
    
    # 尾插法
    def create_linklist_tail(li):
        head = Node(li[0])
        tail = head
        for element in li[1:]:
            node = Node(element)
            tail.next = node
            tail = node
        return head
    
    
    def print_linklist(lk):
        while lk:
            print(lk.item, end=",")
            lk = lk.next
    
    
    lk = create_linklist_tail([1, 2, 3, 6, 8])
    print_linklist(lk)
    
    

3.链表节点的插入

  • p.next = curNode.next

  • curNode.next = p

    图片/数据结构与算法50.png

4.链表节点的删除

  • p = curNode.next

  • curNode.next = curNode.next.next

  • del p

    图片/数据结构与算法51.png

5.双链表

  • 双链表的每个节点有两个指针: 一个指向后一个节点, 另一个指向前一个节点

    图片/数据结构与算法52.png

6.双链表节点的插入

  • p.next = curNode.next

  • curNode.next.prior = p

  • p.prior = curNode

  • curNode.next = p

    图片/数据结构与算法53.png

  • 7.双链表节点的删除

    • p = curNode.next

    • curNode.next = p.next

    • p.next.prior = curNode

    • del p

      图片/数据结构与算法54.png

  • 8.链表复杂度解析

    • 顺序表 (列表/数组) 与链表
      • 按元素值查找
      • 按下标查找
      • 在某元素后插入
      • 删除某元素
  • 9.链表与顺序表

    • 链表在插入和删除的操作上明显快于顺序表
    • 链表的内存快于更灵活的分配
      • 可利用链表重新实现栈和队列
    • 链表这种链式存储的数据结构对树和图的结构有很大的启发性

知识点十五 : 哈希表

1.哈希表原理

  • 哈希表是一个通过哈希函数来计算数据存储位置的数据结构, 通常支持如下操作:
    • insert(key, value) : 插入键值对 (key, value)
    • get(key) : 如果存在键为 key 的键值对则返回其value, 否则返回空值
    • delete(key) : 删除键为key的键值对

2.哈希表前身 直接寻址表

  • 当关键字的全域U比较小时, 直接寻址是一种简单而有效的方法。

    图片/数据结构与算法55.png

  • 直接寻址技术的缺点:

    • 当域U很大时, 需要消耗大量内存, 很不实际
    • 如果域U很大而实际出现的key很少, 则大量空间被浪费
    • 无法处理关键字不是数字的情况
  • 直接寻址表

    • key为k的元素放到k位置上
  • 改进直接寻址表 : 哈希 (Hashing)

    • 构建大小为m的寻址表T
    • key为k的元素放到h(k)位置上
    • h(k)是一个函数, 其将域U映射到表T[0,1,…,m-1]

3.哈希表定义

  • 哈希表 (Hash Table, 又称散列表), 是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量, 返回元素的存储下标。

  • 假设有一个长度为7的哈希表, 哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下图。

    图片/数据结构与算法56.png

4.哈希冲突

  • 由于哈希表的大小是有限的, 而要存储的值的总数量是无限的, 因此对于任何哈希函数, 都会出现两个不用元素映射到同一个位置上的情况, 这种情况就叫做哈希冲突。

  • 比如 h(k) = k%7, h(0)=h(7)=h(14)=…

    图片/数据结构与算法56.png

5.解决哈希冲突

  • 5.1 开放寻址法

    • 如果哈希函数返回的位置已经有值, 则可以向后探索新的位置来存储这个值。
      • 线性探查 : 如果位置 i 被占用, 则探查 i+1, i+2
      • 二次探查 : 如果位置 i 被占用, 则探查 i+12, i-12, i+22, i-22, …
      • 二度哈希 : 有 n 个哈希函数, 当使用第1个哈希函数 h1 发生冲突时, 则尝试使用 h2, h3, …
  • 5.2 拉链法

    • 哈希表每个位置都连接一个链表, 当冲突发生时, 冲突的元素将被加到该位置链表的最后。

      图片/数据结构与算法57.png

6.常见哈希函数

  • 6.1 除法哈希法
    • h(k) = k % m
  • 6.2 乘法哈希法
    • h(k) = floor(m*(A*key%1))
  • 6.3 全域哈希法
    • ha,b(k) = ((a*key + b) mod p) mod m a,b=1,2,…,p-1

7.哈希表实现

# -*- coding: utf-8 -*-

class LinkList:
    class Node:
        def __init__(self, item=None):
            self.item = item
            self.next = None

    class LinkListIterator:
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:
                cur_node = self.node
                self.node = cur_node.next
                return cur_node.item
            else:
                raise StopIteration

        def __iter__(self):
            return self

    def __init__(self, iterable=None):
        self.head = None
        self.tail = None
        if iterable:
            self.extend(iterable)

    def append(self, obj):
        s = LinkList.Node(obj)
        if not self.head:
            self.head = s
            self.tail = s
        else:
            self.tail.next = s
            self.tail = s

    def extend(self, iterable):
        for obj in iterable:
            self.append(obj)

    def find(self, obj):
        for n in self:
            if n == obj:
                return True
        else:
            return False

    def __iter__(self):
        return self.LinkListIterator(self.head)

    def __repr__(self):
        return "<<" + ",".join(map(str, self)) + ">>"


# lk = LinkList([1, 2, 3, 4, 5])
# for element in lk:
#     print(element)
# print(lk)

# 类似于集合的结构
class HashTable:
    def __init__(self, size=101):
        self.size = size
        self.T = [LinkList() for i in range(self.size)]

    def h(self, k):
        return k % self.size

    def insert(self, k):
        i = self.h(k)
        if self.find(k):
            print("Duplicated Insert.")
        else:
            self.T[i].append(k)

    def find(self, k):
        i = self.h(k)
        return self.T[i].find(k)

ht = HashTable()

ht.insert(0)
ht.insert(1)
ht.insert(3)
ht.insert(102)
ht.insert(508)

print(",".join(map(str, ht.T)))
# print(ht.find(203))

8.哈希表应用

  • 8.1 集合与字典

    • 字典与集合都是通过哈希表来实现的。
      • a = {‘name’:‘Alex’, ‘age’:18, ‘gender’:‘Man’}
    • 使用哈希表存储字典, 通过哈希函数将字典的键映射为下标。假设 h(‘name’) = 3, h(‘age’) = 1, h(‘gender’) = 4, 则哈希表存储为[None, 18, None, ‘Alex’, ‘Man’]
    • 如果发送哈希冲突, 则通过拉链法或开发寻址法解决
  • 8.2 md5算法

    • MD5 (Message-Digest Algorithm 5) 曾经是密码学中常用的哈希函数, 可以把任意长度的数据映射为 128位的哈希值, 其曾经包含如下特征:
      • 1.同样的消息, 其MD5值必定相同;
      • 2.可以快速计算出任意给定消息的MD5值;
      • 3.除非暴力的枚举所有可能的消息,否则不可能从哈希值反推出消息本身;
      • 4.两条消息之间即使只有微小的差别,其对应的MD5值也应该是完全不同、完全不相关的;
      • 5.不能在有意义的时间内人工的构造两个不同的消息使其具有相同的MD5值。
    • 应用举例 : 文件的哈希值
      • 算出文件的哈希值,若两个文件的哈希值相同,则可认为这两个文件是相同的。因此:
        • 1.用户可以利用它来验证下载的文件是否完整。
        • 2.云存储服务商可以利用它来判断用户要上传的文件是否已经存在于服务器上,从而实现秒传的功能,同时避免存储过多相同的文件副本。
  • 8.3 SHA2算法

    • 历史上MD5和SHA-1曾经是使用最为广泛的cryptographic hash function,但是随着密码学的发展,这两个哈希函数的安全性相继受到了各种挑战。

    • 因此现在安全性较重要的场合推荐使用SHA-2等新的更安全的哈希函数。

    • SHA-2包含了一系列的哈希函数: SHA-224,SHA-256,SHA-384,SHA-512,
      SHA-512/224,SHA- 512/256,其对应的哈希值长度分别为224,256,384 or 512位。

    • SHA-2具有和MD5类似的性质 (参见MD5算法的特征) 。

    • 应用举例:

      ​ 例如,在比特币系统中,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的目标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于—个给定值D。此时,只能通过暴力枚举V来进行猜测。首先计算出结果的人可获得一定奖金。而某人首先计算成功的概率与其拥有的计算量成正比,所以其获得的奖金的期望值与其拥有的计算量成正比。

知识点十六 : 树

1.树的概念

  • 1.1 树与二叉树

    • 树是一种数据结构 (如目录结构)
    • 树是一种可以递归定义的数据结构
    • 树是由n个节点组成的集合
      • 如果n=0,那这是一棵空树;
      • 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是—棵树。

    图片/数据结构与算法58.png

    注意 : 树的相关具体概念见 知识点七排序-堆排序前置知识点

2.树的实例 模拟文件系统

# -*- coding: utf-8 -*-
class Node:
    def __init__(self, name, type='dir'):
        self.name = name
        self.type = type  # "dir" or "file"
        self.children = []
        self.parent = None


class FileSystemTree:
    def __init__(self):
        self.root = Node(" /")
        self.now = self.root

    def __repr__(self):
        return self.name

    def mkdir(self, name):
        # name 以 / 结尾
        if name[-1] != "/":
            name += "/"
        node = Node(name)
        self.now.children.append(node)
        node.parent = self.now

    def ls(self):
        return self.now.children

    def cd(self, name):
        # "../var/python"
        if name[-1] != "/":
            name += "/"

        if name == "../":
            self.now = self.now.parent
            return

        for child in self.now.children:
            if child.name == name:
                self.now = child
                return
        raise ValueError("invalid dir")


tree = FileSystemTree()
tree.mkdir("var/")
tree.mkdir("bin/")
tree.mkdir("usr/")

tree.cd("bin/")
tree.mkdir("python/")

tree.cd("../")

print(tree.ls())

3.二叉树

  • 3.1 二叉树的链式存储

    • 将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。

    图片/数据结构与算法59.png

  • 3.2 二叉树节点定义

    class BiTreeNode:
        def __init__(self, data):
            self.data = data
            self.lchild = None  # 左孩子
            self.rchild = None  # 右孩子
    
    
    a = BiTreeNode("A")
    b = BiTreeNode("B")
    c = BiTreeNode("C")
    d = BiTreeNode("D")
    e = BiTreeNode("E")
    f = BiTreeNode("F")
    g = BiTreeNode("G")
    
    e.lchild = a
    e.rchild = g
    a.rchild = c
    c.lchild = b
    c.rchild = d
    g.rchild = f
    
    root = e
    
    print(root.lchild.rchild.data)
    
  • 3.3 二叉树的遍历

    • 二叉树的遍历方式:

      • 前序遍历 : EACBDGF
      • 中序遍历 : ABCDEGF
      • 后序遍历 : BDCAFGE
      • 层次遍历 : EAGCFBD
    • 代码实现:

      from collections import deque
      
      class BiTreeNode:
          def __init__(self, data):
              self.data = data
              self.lchild = None  # 左孩子
              self.rchild = None  # 右孩子
      
      
      a = BiTreeNode("A")
      b = BiTreeNode("B")
      c = BiTreeNode("C")
      d = BiTreeNode("D")
      e = BiTreeNode("E")
      f = BiTreeNode("F")
      g = BiTreeNode("G")
      
      e.lchild = a
      e.rchild = g
      a.rchild = c
      c.lchild = b
      c.rchild = d
      g.rchild = f
      
      root = e
      
      # print(root.lchild.rchild.data)
      
      
      # 1.前序遍历
      def pre_order(root):
          if root:
              print(root.data, end=',')
              pre_order(root. lchild)
              pre_order(root. rchild)
      # pre_order(root)
      
      
      # 2.中序遍历
      def in_order(root):
          if root:
              in_order(root.lchild)
              print(root.data, end=',')
              in_order(root.rchild)
      # in_order(root)
      
      
      # 3.后序遍历
      def post_order(root):
          if root:
              post_order(root.lchild)
              post_order(root.rchild)
              print(root.data, end=',')
      # post_order(root)
      
      
      # 4.层次遍历
      def level_order(root):
          queue = deque()
          queue.append(root)
          while len(queue) > 0:   # 只要队不空
              node = queue.popleft()
              print(node.data, end=",")
              if node.lchild:
                  queue.append(node.lchild)
              if node.rchild:
                  queue.append(node.rchild)
      level_order(root)
      
  • 3.4 二叉搜索树的概念

    • 二叉搜索树是一颗二叉树且满足性质 : 设x是二叉树的一个节点。如果 y 是x 左子树的—个节点,那么 y.key ≤ x.key ;如果 y 是 x 右子树的一个节点,那么 y.key ≥ x.key。
    • 二叉搜索树的操作:查询、插入、删除

    图片/数据结构与算法60.png

  • 3.5 二叉搜索树: 插入

    • 代码实现

      # -*- coding: utf-8 -*-
      import random
      
      
      class BiTreeNode:
          def __init__(self, data):
              self.data = data
              self.lchild = None  # 左孩子
              self.rchild = None  # 右孩子
              self.parent = None
      
      
      class BST:
          def __init__(self, li=None):
              self.root = None
              if li:
                  for val in li:
                      self.insert_no_rec(val)
      
          def insert(self, node, val):
              if not node:
                  node = BiTreeNode(val)
              elif val < node.data:
                  node.lchild - self.insert(node.lchild, val)
                  node.lchild.parent = node
              elif val > node.data:
                  node.rchild = self.insert(node.rchild, val)
              return node
      
          def insert_no_rec(self, val):
              p = self.root
              if not p:  # 空树
                  self.root = BiTreeNode(val)
                  return
              while True:
                  if val < p.data:
                      if p.lchild:
                          p = p.lchild
                      else:  # 左孩子不存在
                          p.lchild = BiTreeNode(val)
                          p.lchild.parent = p
                          return
                  elif val > p.data:
                      if p.rchild:
                          p = p.rchild
                      else:
                          p.rchild = BiTreeNode(val)
                          p.rchild.parent = p
                          return
                  else:
                      return
      
          # 1.前序遍历
          def pre_order(self, root):
              if root:
                  print(root.data, end=',')
                  self.pre_order(root.lchild)
                  self.pre_order(root.rchild)
      
          # 2.中序遍历
          def in_order(self, root):
              if root:
                  self.in_order(root.lchild)
                  print(root.data, end=',')
                  self.in_order(root.rchild)
      
          # 3.后序遍历
          def post_order(self, root):
              if root:
                  self.post_order(root.lchild)
                  self.post_order(root.rchild)
                  print(root.data, end=',')
      
      
      li = list(range(10))
      random.shuffle(li)
      
      tree = BST(li)
      tree.pre_order(tree.root)
      print("")
      tree.in_order(tree.root)
      print("")
      tree.post_order(tree.root)
      
      
  • 3.6二叉搜索树 : 查询

    • 代码实现

      # -*- coding: utf-8 -*-
      import random
      
      
      class BiTreeNode:
          def __init__(self, data):
              self.data = data
              self.lchild = None  # 左孩子
              self.rchild = None  # 右孩子
              self.parent = None
      
      class BST:
          def __init__(self, li=None):
              self.root = None
              if li:
                  for val in li:
                      self.insert_no_rec(val)
      
          def insert(self, node, val):
              if not node:
                  node = BiTreeNode(val)
              elif val < node.data:
                  node.lchild - self.insert(node.lchild, val)
                  node.lchild.parent = node
              elif val > node.data:
                  node.rchild = self.insert(node.rchild, val)
              return node
      
          def insert_no_rec(self, val):
              p = self.root
              if not p:  # 空树
                  self.root = BiTreeNode(val)
                  return
              while True:
                  if val < p.data:
                      if p.lchild:
                          p = p.lchild
                      else:  # 左孩子不存在
                          p.lchild = BiTreeNode(val)
                          p.lchild.parent = p
                          return
                  elif val > p.data:
                      if p.rchild:
                          p = p.rchild
                      else:
                          p.rchild = BiTreeNode(val)
                          p.rchild.parent = p
                          return
                  else:
                      return
      
          def query(self, node, val):
              if not node:
                  return None
              if node.data < val:
                  return self.query(node.rchild, val)
              elif node.data > val:
                  return self.query(node.lchild,val)
              else:
                  return node
      
      
          def query_no_rec(self, val):
              p = self.root
              while p:
                  if p.data < val:
                      p = p.rchild
                  elif p.data > val:
                      p = p.lchild
                  else:
                      return p
              return None
      
          # 1.前序遍历
          def pre_order(self, root):
              if root:
                  print(root.data, end=',')
                  self.pre_order(root.lchild)
                  self.pre_order(root.rchild)
      
          # 2.中序遍历
          def in_order(self, root):
              if root:
                  self.in_order(root.lchild)
                  print(root.data, end=',')
                  self.in_order(root.rchild)
      
          # 3.后序遍历
          def post_order(self, root):
              if root:
                  self.post_order(root.lchild)
                  self.post_order(root.rchild)
                  print(root.data, end=',')
      
      
      li = list(range(0, 100, 2))
      random.shuffle(li)
      
      tree = BST(li)
      print(tree.query_no_rec(4).data)
      
  • 3.7 二叉搜索树 : 删除

    • 如果要删除的节点是叶子节点: 直接删除

      图片/数据结构与算法61.png

    • 如果要删除的节点只有一个孩子: 将次节点的父亲与孩子连接, 然后删除该节点。

      图片/数据结构与算法62.png

    • 如果要删除的节点有两个孩子 : 将其右子树的最小节点 (该节点最多有一个右孩子) 删除, 并替换当前节点。

      图片/数据结构与算法63.png

    • 代码实现:

      # -*- coding: utf-8 -*-
      
      # -*- coding: utf-8 -*-
      import random
      
      
      class BiTreeNode:
          def __init__(self, data):
              self.data = data
              self.lchild = None  # 左孩子
              self.rchild = None  # 右孩子
              self.parent = None
      
      
      class BST:
          def __init__(self, li=None):
              self.root = None
              if li:
                  for val in li:
                      self.insert_no_rec(val)
      
          def insert(self, node, val):
              if not node:
                  node = BiTreeNode(val)
              elif val < node.data:
                  node.lchild - self.insert(node.lchild, val)
                  node.lchild.parent = node
              elif val > node.data:
                  node.rchild = self.insert(node.rchild, val)
              return node
      
          def insert_no_rec(self, val):
              p = self.root
              if not p:  # 空树
                  self.root = BiTreeNode(val)
                  return
              while True:
                  if val < p.data:
                      if p.lchild:
                          p = p.lchild
                      else:  # 左孩子不存在
                          p.lchild = BiTreeNode(val)
                          p.lchild.parent = p
                          return
                  elif val > p.data:
                      if p.rchild:
                          p = p.rchild
                      else:
                          p.rchild = BiTreeNode(val)
                          p.rchild.parent = p
                          return
                  else:
                      return
      
          def query(self, node, val):
              if not node:
                  return None
              if node.data < val:
                  return self.query(node.rchild, val)
              elif node.data > val:
                  return self.query(node.lchild, val)
              else:
                  return node
      
          def query_no_rec(self, val):
              p = self.root
              while p:
                  if p.data < val:
                      p = p.rchild
                  elif p.data > val:
                      p = p.lchild
                  else:
                      return p
              return None
      
          # 1.前序遍历
          def pre_order(self, root):
              if root:
                  print(root.data, end=',')
                  self.pre_order(root.lchild)
                  self.pre_order(root.rchild)
      
          # 2.中序遍历
          def in_order(self, root):
              if root:
                  self.in_order(root.lchild)
                  print(root.data, end=',')
                  self.in_order(root.rchild)
      
          # 3.后序遍历
          def post_order(self, root):
              if root:
                  self.post_order(root.lchild)
                  self.post_order(root.rchild)
                  print(root.data, end=',')
      
          def __remove_node_1(self, node):
              # 情况1: node是叶子节点
              if not node.parent:
                  self.root = None
              if node == node.parent.lchild:  # node是它父亲的左孩子
                  node.parent.lchild = None
              else:  # 右孩子
                  node.parent.rchild = None
      
          def _remove_node_21(self, node):
              # 情况2.1: node只有一个左孩子
              if not node.parent:  # 根节点
                  self.root = node.lchild
                  node.lchild.parent = None
              elif node == node.parent.lchild:
                  node.parent.lchild = node.lchild
                  node.lchild.parent = node.parent
              else:
                  node.parent.rchild = node.lchild
                  node.lchild.parent = node.parent
      
          def __remove_node_22(self, node):
              # 情况2.2: node只有一个右孩子
              if not node.parent:
                  self.root = node.rchild
              elif node == node.parent.lchild:
                  node.parent.lchild = node.rchild
                  node.rchild.parent = node.parent
              else:
                  node.parent.rchild = node.rchild
                  node.rchild.parent = node.parent
      
          def delete(self, val):
              if self.root:  # 不是空树
                  node = self.query_no_rec(val)
                  if not node:  # 不存在
                      return False
                  if not node.lchild and not node.rchild:  # 叶子节点
                      self.__remove_node_1(node)
                  elif not node.rchild:  # 2.1只有一个左孩子
                      self.__remove_node_21(node)
                  elif not node.lchild:  # 2.2只有一个右孩子
                      self.__remove_node_22(node)
                  else:  # 3.两个孩子都有
                      min_mode = node.rchild
                      while min_mode.lchild:
                          min_mode = min_mode.lchild
                      node.data = min_mode.data
                      # 删除min_node
                      if min_mode.rchild:
                          self.__remove_node_22(min_mode)
                      else:
                          self.__remove_node_1(min_mode)
      
      
      tree = BST([1, 4, 2, 5, 3, 8, 6, 9, 7])
      tree.in_order(tree.root)
      print("")
      
      tree.delete(4)
      tree.delete(1)
      tree.delete(8)
      
      tree.in_order(tree.root)
      
      
  • 3.8 二叉搜索树的效率

    • 平均情况下,二叉搜索树进行搜索的时间复杂度为O(Ign)。

    • 但是最坏情况下,二叉搜索树可能非常偏斜。

      图片/数据结构与算法64.png

    • 解决方案

      • 1.随机化插入
      • 2.AVL树

4.AVL树

  • 4.1 AVL树的概念

    • AVL树 : AVL树是—棵自平衡的二叉搜索树。

    • AVL树具有以下性质:

      • 根的左右子树的高度之差的绝对值不能超过1
      • 根的左右子树都是平衡二叉树

      图片/数据结构与算法65.png

  • 4.2 AVL树 : 旋转

    • 插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。

    • 插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树的高度差2。

    • 不平衡的出现可能有4种情况

      • 1.不平衡是由于对 K 的左孩子的左子树插入导致的 : 右旋

        图片/数据结构与算法66.png

      • 2.不平衡是由于对 K 的右孩子的右子树插入导致的 : 左旋

        图片/数据结构与算法67.png

      • 3.不平衡是由于对 K 的右孩子的左子树插入导致的 : 右旋-左旋

        图片/数据结构与算法68.png

      • 4.不平衡是由于对 K 的左孩子的右子树插入导致的 : 左旋-右旋

        图片/数据结构与算法69.png

    • 代码实现 (有bug待处理)

      	class BiTreeNode:
          def __init__(self, data):
              self.data = data
              self.lchild = None  # 左孩子
              self.rchild = None  # 右孩子
              self.parent = None
      
      
      class BST:
          def __init__(self, li=None):
              self.root = None
              if li:
                  for val in li:
                      self.insert_no_rec(val)
      
          def insert(self, node, val):
              if not node:
                  node = BiTreeNode(val)
              elif val < node.data:
                  node.lchild - self.insert(node.lchild, val)
                  node.lchild.parent = node
              elif val > node.data:
                  node.rchild = self.insert(node.rchild, val)
              return node
      
          def insert_no_rec(self, val):
              p = self.root
              if not p:  # 空树
                  self.root = BiTreeNode(val)
                  return
              while True:
                  if val < p.data:
                      if p.lchild:
                          p = p.lchild
                      else:  # 左孩子不存在
                          p.lchild = BiTreeNode(val)
                          p.lchild.parent = p
                          return
                  elif val > p.data:
                      if p.rchild:
                          p = p.rchild
                      else:
                          p.rchild = BiTreeNode(val)
                          p.rchild.parent = p
                          return
                  else:
                      return
      
          def query(self, node, val):
              if not node:
                  return None
              if node.data < val:
                  return self.query(node.rchild, val)
              elif node.data > val:
                  return self.query(node.lchild, val)
              else:
                  return node
      
          def query_no_rec(self, val):
              p = self.root
              while p:
                  if p.data < val:
                      p = p.rchild
                  elif p.data > val:
                      p = p.lchild
                  else:
                      return p
              return None
      
          # 1.前序遍历
          def pre_order(self, root):
              if root:
                  print(root.data, end=',')
                  self.pre_order(root.lchild)
                  self.pre_order(root.rchild)
      
          # 2.中序遍历
          def in_order(self, root):
              if root:
                  self.in_order(root.lchild)
                  print(root.data, end=',')
                  self.in_order(root.rchild)
      
          # 3.后序遍历
          def post_order(self, root):
              if root:
                  self.post_order(root.lchild)
                  self.post_order(root.rchild)
                  print(root.data, end=',')
      
          def __remove_node_1(self, node):
              # 情况1: node是叶子节点
              if not node.parent:
                  self.root = None
              if node == node.parent.lchild:  # node是它父亲的左孩子
                  node.parent.lchild = None
              else:  # 右孩子
                  node.parent.rchild = None
      
          def _remove_node_21(self, node):
              # 情况2.1: node只有一个左孩子
              if not node.parent:  # 根节点
                  self.root = node.lchild
                  node.lchild.parent = None
              elif node == node.parent.lchild:
                  node.parent.lchild = node.lchild
                  node.lchild.parent = node.parent
              else:
                  node.parent.rchild = node.lchild
                  node.lchild.parent = node.parent
      
          def __remove_node_22(self, node):
              # 情况2.2: node只有一个右孩子
              if not node.parent:
                  self.root = node.rchild
              elif node == node.parent.lchild:
                  node.parent.lchild = node.rchild
                  node.rchild.parent = node.parent
              else:
                  node.parent.rchild = node.rchild
                  node.rchild.parent = node.parent
      
          def delete(self, val):
              if self.root:  # 不是空树
                  node = self.query_no_rec(val)
                  if not node:  # 不存在
                      return False
                  if not node.lchild and not node.rchild:  # 叶子节点
                      self.__remove_node_1(node)
                  elif not node.rchild:  # 2.1只有一个左孩子
                      self.__remove_node_21(node)
                  elif not node.lchild:  # 2.2只有一个右孩子
                      self.__remove_node_22(node)
                  else:  # 3.两个孩子都有
                      min_mode = node.rchild
                      while min_mode.lchild:
                          min_mode = min_mode.lchild
                      node.data = min_mode.data
                      # 删除min_node
                      if min_mode.rchild:
                          self.__remove_node_22(min_mode)
                      else:
                          self.__remove_node_1(min_mode)
      
      
      class AVLNode(BiTreeNode):
          def __init__(self, data):
              BiTreeNode.__init__(self, data)
              self.bf = 0
      
      
      class AVLTree(BST):
          def __init__(self, li=None):
              BST.__init__(self, li)
      
          def rotate_left(self, p, c):
              s2 = c.lchild
              p.rchild = s2
              if s2:
                  s2.parent = p
      
              c.lchild = p
              p.parent = c
      
              p.bf = 0
              c.bf = 0
              return c
      
          def rotate_right(self, p, c):
              s2 = c.rchild
              p.lchild = s2
              if s2:
                  s2.parent = p
      
              c.rchild = p
              p.parent = c
      
              p.bf = 0
              c.bf = 0
              return c
      
          def rotate_right_left(self, p, c):
              g = c.lchild
      
              s3 = g.rchild
              c.lchild = s3
              if s3:
                  s3.parent = c
              g.rchild = c
              c.parent = g
      
              s2 = g.lchild
              p.rchild = s2
      
              if s2:
                  s2.parent = p
              g.lchild = p
              p.parent = g
      
              if g.bf > 0:  # g.bf ==1
                  p.bf = -1
                  c.bf = 0
              else:  # g.bf < 0 或者说 == -1:
                  p.bf = 0
                  c.bf = 1
              g.bf = 0
              return g
      
          def rotate_left_right(self, p, c):
              g = c.rchild
      
              s2 = g.lchild
              c.rchild = s2
              if s2:
                  s2.parent = c
              g.lchild = c
              c.parent = g
      
              s3 = g.rchild
              p.lchild = s3
              if s3:
                  s3.parent = p
              g.rchild = p
              p.parent = g
      
              if g.bf < 0:
                  p.bf = 0
                  c.bf = 1
              else:
                  p.bf = 0
                  c.bf = -1
              g.bf = 0
              return g
      
          def insert_no_rec(self, val):
              # 1.和BST一样,插入
              p = self.root
              if not p:  # 空树
                  self.root = BiTreeNode(val)
                  return
              while True:
                  if val < p.data:
                      if p.lchild:
                          p = p.lchild
                      else:  # 左孩子不存在
                          p.lchild = BiTreeNode(val)
                          p.lchild.parent = p
                          node = p.lchild  # 存储的就是插入的节点
                          break
      
                  elif val > p.data:
                      if p.rchild:
                          p = p.rchild
                      else:
                          p.rchild = BiTreeNode(val)
                          p.rchild.parent = p
                          node = p.rchild
                          break
      
                  else:  # val == p.data
                      return
      
              # 2.更新balance factor
              while node.parent:  # node . parent不空
                  if node.parent.lchild == node:  # 传递是从左子树来的,左子树更沉了
                      # 更新node.parent的bf -= 1
                      if node.parent.bf < 0:  # 原来node.parent.bf ==.-1,更新后变成-2
                          # 做旋转
                          # 看node哪边沉
                          g = node.parent.parent  # 为了连接旋转之后的子树
      
                          if node.bf > 0:
                              n = self.rotate_left_right(node.parent, node)
                          else:
                              n = self.rotate_right(node.parent, node)
                          # 记得:把n和g连起来
                      elif node.parent.bf > 0:  # 原来node.parent.bf = 1,更新之后变成0
                          node.parent.bf = 0
                          break
                      else:  # 原来node . parent.bf = 0,更新之后变成-1
                          node.parent.bf = -1
                          node = node.parent
                          continue
                  else:  # 传递是从右子树来的。右子树更沉了
                      # 更新node.parent.bf +1
                      if node.parent.bf > 0:  # 原来node.parent.bf == 1,更新后变成2
                          # 做旋转
                          # 看node即边沉
                          g = node.parent.parent  # 为了连接旋转之后的子树
                          x = node.parent  # 旋转前的子树的根
                          if node.bf < 0:  # node.bf = 1
                              n = self.rotate_right_left(node.parent, node)
                          else:  # node.bf = -1
                              n = self.rotate_left(node.parent, node)
                          # 记得连起来
                      elif node.parent.bf < 0:  # 原来node.parent.bf = -1,更新之后变成0
                          node.parent.bf = 0
                          break
                      else:  # 原来node.parent.bf = 0, 更新之后变成1
                          node.parent.bf = 1
                          node = node.parent
                          continue
      
                  # 链接旋转后的子树
                  n.parent = g
                  if g:  # g不是空
                      if x == g.lchild:
                          g.lchild = n
                      else:
                          g.rchild = n
                      break
                  else:
                      self.root = n
                      break
      
      
      tree = AVLTree([9, 8, 7, 6, 5, 4, 3, 2, 1])
      
      tree.pre_order(tree.root)
      print("")
      tree.in_order(tree.root)
      
      
  • 4.3 二叉搜索树拓展 B树

    • B树(B-Tree) : B树是一棵自平衡的多路搜索树。常用于数据库的索引。

      图片/数据结构与算法70.png

  • 0
    点赞
  • 2
    收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值