数据结构与算法总结——Python版

目录

  • 前言
  • 一.算法
    • 1. 哈希表是什么?
    • 2. 什么是时间复杂度?
    • 3. 空间复杂度
    • 4. 递归
    • 4. 查找
      • 4.1、顺序查找
      • 4.2. 二分查找
    • 5. 排序
      • 5.1. 冒泡排序
      • 5.2. 选择排序
      • 5.3. 插入排序
      • 5.4. 快速排序
      • 5.5. 堆排序
        • 5.5.1.树
        • 5.5.2. 堆
      • 5.6. 归并排序
      • 5.7. 希尔排序
      • 5.8. 计数排序
      • 5.9.桶排序
      • 5.10. 基数排序
  • 二.数据结构
    • 2.1.列表/数组
    • 2.2.栈
    • 2.3. 队列
    • 2.4. 链表
    • 2.5. 哈希表
    • 2.6. 树
      • 2.6.1. 二叉树
      • 2.6.2. 二叉搜索树
      • 2.6.3. AVL树

前言

最近在学习数据结构与算法,写个笔记记录一下。

一.算法

1. 哈希表是什么?

先来个开胃菜,我们经常听到哈希,那么哈希是什么,我们来浅浅的看一下,后面会详细介绍。

哈希表(Hash Table),也称为哈希映射或字典,是一种常见的数据结构,用于存储键-值对(key-value pairs)。它通过使用哈希函数(hash function)将键映射到存储桶(buckets)或槽位(slots)中,以实现快速的插入、查找和删除操作。哈希表的核心思想是使用键的哈希值作为索引来访问和存储值。

2. 什么是时间复杂度?

时间复杂度是算法运行时间与输入规模之间的关系,用来衡量算法的效率。它描述了算法执行所需的操作数量,通常以大O符号(O)表示。计算时间复杂度时,主要关注算法中执行次数最多的那部分代码。

  • 常数时间复杂度(O(1)):无论输入规模大小,算法的执行时间都是固定的常量。例如,简单的赋值语句、访问数组元素等操作都是常数时间复杂度。
  • 线性时间复杂度(O(n)):算法的执行时间与输入规模呈线性关系。例如,对一个包含n个元素的数组进行遍历操作的时间复杂度就是O(n)。
  • 对数时间复杂度(O(log n)):算法的执行时间与输入规模的对数关系。通常出现在使用分治或二分搜索等算法时。例如,二分查找的时间复杂度就是O(log n)。
  • 平方时间复杂度(O(n2))、立方时间复杂度(O(n3))等:算法的执行时间与输入规模的平方、立方等次方关系。例如,嵌套循环操作通常会导致平方时间复杂度。
  • 指数时间复杂度(O(2^n)):算法的执行时间与输入规模的指数关系。通常出现在使用穷举搜索等指数级算法时,因为它的执行时间随着输入规模的增加而急剧增长。

快速判断算法复杂度(适用于绝大多数简单情况)

  • 确定问题规模n
  • 循环减半过程一logn
  • k层关于n的循环一> n k n^{k} nk
  • 复杂情况:根据算法执行过程判断

3. 空间复杂度

空间复杂度:用来评估算法内存占用大小的式子,空间复杂度的表示方式与时间复杂度完全一样

  • 算法使用了几个变量: O(1)
  • 算法使用了长度为n的一维列表: O(n)
  • 算法使用了m行n列的二维列表: O(mn)
  • 空间换时间

4. 递归

两个限制条件:调用自身,结束条件

def fun1(x):
    if x > 0:
        print(x)
        fun1(x - 1)


def fun2(x):
    if x > 0:
        fun1(x - 1)
        print(x)


if __name__ == "__main__":
    fun1(3)
    fun2(3)

optput:

3
2
1
1
2
3

为什么下面一个是1,2,3?看下下面的图:函数的执行是从上往下,左边一个是递归完就打印,右边是递归完再打印。
数据结构与算法总结——Python版_第1张图片
看个汉诺塔的问题:

def han(n, a, b, c):

    if n > 0:
        han(n - 1, a, c, b)
        print("moving from %s to %s" % (a, c))
        han(n - 1, b, a, c)


if __name__ == "__main__":
    han(10 , "A", "B", "C")

4. 查找

查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。

  • 列表查找(线性表查找) : 从列表中查找指定元素
  • 输入:列表、待查找元素
  • 输出: 元素下标 (未找到元素时一般返回None或-1)
  • 内置列表查找函数: index()

4.1、顺序查找

for循环

def linear_search(data_set, value):
    for i in range(len(data_set)):
        if data_set[i] == value:
            return i
    return

4.2. 二分查找

要求列表必须是有序列表,先排序,left和right记录两边的变量,找出中间元素mid,更新left,right,mid。
下面代码是假设已经排序好的。排序直接用sorted()排序即可。

def binary_search(data_list, value):
    left = 0
    right = len(data_list)
    
    while left <= right:
        mid = (left + right) //2
        if data_list[mid] == value:
            return mid
        elif data_list[mid] > value:
            right = mid -1
        else:
            left = mid + 1

5. 排序

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

  • 输入:列表
  • 输出:有序列表
  • 升序与降序
  • 内置排序函数: sorted()

5.1. 冒泡排序

大的数下沉或者上浮,两两比较进行交换。
数据结构与算法总结——Python版_第2张图片

def bubble_sort(array):
    for i in range(len(array)):
        for j in range(len(array) - 1 - i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]
    return array

上面的代码有个不足之处,如果一个列表不需要走完所有的n-1趟,是不是可以改进一下?

def bubble_sort(array):
    for i in range(len(array)):
        exchange = False
        for j in range(len(array) - 1 - i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]
                exchange = True
    return array

5.2. 选择排序

选择最小的或者最大的依次放到前面。

def select_sort(li):
    for i in range(len(li)-1):
        min_loc = i
        for j in range(i+1, len(li)):
            if li[j] < li[min_loc]:
                min_loc = j
            li[i], li[min_loc] = li[min_loc], li[i]

5.3. 插入排序

类似摸牌的时候把牌按照插入的方法排序。

def insertion_sort(arr):
    n = len(arr)
    for i in range(1, n):
        key = arr[i]  # 待插入的元素
        j = i - 1  # 已排序序列的最后一个元素的索引

        # 将比待插入元素大的元素向右移动
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]
            j -= 1

        # 插入待排序元素到正确位置
        arr[j + 1] = key

总结:

冒泡排序,选择排序,插入排序都是远低排序,复杂度都是 O ( n 2 ) O(n^{2}) O(n2),实现简单,缺点就是

5.4. 快速排序

快速排序:快
快速排序思路(有点类似二分法)
数据结构与算法总结——Python版_第3张图片

  1. 取一个元素p(第一个元素),使元素p归位(放到应该放的位置);
  2. 列表被p分成两部分,左边都比p小,右边都比p大;
  3. 递归完成排序
def partition(li, left, right):
    tmp = li[left]
    while left < right:
        while left < right and li[right] >= tmp:  # 从右面找比tmp小的数
            # 往左走一步
            right -= 1
        li[left] = li[right]
        # 把右边的值写到左边空位上
        while left < right and li[left] <= tmp:
            left += 1
        li[right] = li[left]
    # 把左边的值写到右边空位上
    li[left] = tmp  # 把tmp归位
    return left


def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        quick_sort(data, left, mid - 1)
        quick_sort(data, mid + 1, right)
    return data

快速排序的时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
缺点:递归,会有最坏情况(初试列表是倒序的复杂度还是 O ( n 2 ) ) O(n^{2})) O(n2)),打乱列表可以缓解这种情况出现。

5.5. 堆排序

5.5.1.树

堆排序要先知道下什么是树。
数据结构与算法总结——Python版_第4张图片
数据结构与算法总结——Python版_第5张图片
数据结构与算法总结——Python版_第6张图片
数据结构与算法总结——Python版_第7张图片
堆就是一个特殊的完全二叉树。
二叉树在计算机中怎么实现,即怎么存储?
两种方式:链式存储(后面介绍链表再讲),顺序存储
顺序存储就是放到列表中处理。
下面以完全二叉树为例:
数据结构与算法总结——Python版_第8张图片

5.5.2. 堆

堆:一种特殊的完全二叉树结构
两种结构:

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

数据结构与算法总结——Python版_第9张图片
堆具有向下调整型:

假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整来将其变成一个堆

数据结构与算法总结——Python版_第10张图片
调整后:
数据结构与算法总结——Python版_第11张图片

堆怎么构造?
从最先开始一级一级往上调整,农村包围城市。
过程:

  1. 建立堆,以大根堆为例
  2. 得到堆顶元素,为最大元素
  3. 去掉堆顶,将堆最后一个元素放到堆顶,此时可通过仅需一次向下调整即可重新使堆有序
  4. 堆顶元素为第二大元素
  5. 重复步骤3,直到堆变空

堆排序的核心就是构建堆+一次向下调整,想要构建堆,核心是让下级先有序,只要下级满足堆了,逐级通过一次向下调整即可构建出完整的堆,最后就能逐个遍历堆顶元素进行排序整个列表

# 只是一次调整堆
def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low  # i最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    while j <= high:  # 只要j位置有数
        if j + 1 <= high and li[j + 1] > li[j]:  # 如果右孩子有并且比较大
            j = j + 1  # 指向右孩子
        if li[j] > tmp:
            li[i] = li[j]
            i = j  # 往下看一层
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上li[i] = tmp#把tmp放到某一级领导位置上break
            li[i] = tmp  # 把tmp放到叶子节点上
            break
    else:
        li[i] = tmp


def heap_sort(li):
    n = len(li)
    # 从最下面的左边的叶子结点开始调整建堆
    for i in range((n - 2) // 2, -1, -1):
        sift(li, i, n - 1)
    # print(li)
    # 排序
    for i in range(n - 1, -1, -1):
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i - 1)
    return li

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

堆的内置模块:

  • heapify(x)
  • heappush(heap,item)
  • heappop(heap)

现在有n个数,设计算法得到前k大的数。 (k 解决思路:

  • 排序后切片:O(nlogn)
  • 排序LowB三人组:O(kn)
  • 堆排序思路:O(nlogk),小根堆实现

堆排序实现方法:两组动图演示一下。

数据结构与算法总结——Python版_第12张图片

def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low  # i最开始指向根节点
    j = 2 * i + 1  # 开始是左孩子
    tmp = li[low]  # 把堆顶存起来
    while j <= high:  # 只要j位置有数
        if j + 1 <= high and li[j + 1] < li[j]:  # 如果右孩子有并且比较大
            j = j + 1  # 指向右孩子
        if li[j] < tmp:
            li[i] = li[j]
            i = j  # 往下看一层
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上li[i] = tmp#把tmp放到某一级领导位置上break
            li[i] = tmp  # 把tmp放到叶子节点上
            break
    else:
        li[i] = tmp

def topk(li, k):
	# 列表前k个取出来建堆
    heap = li[0:k]
    for i in range((k - 2) // 2, -1, -1):
        sift(heap, i, k - 1)
    # 建堆
    for i in range(k, len(li)-1):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap, 0, k-1)
    # 遍历
    for i in range(k - 1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        sift(heap, 0, i - 1)
    # 输出
    return heap

5.6. 归并排序

一个列表可以分为两部分,两边都是排序好的。如list = [1, 2, 3, 4, 2, 3, 7, 9]

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 i <= mid:
        ltmp.append((li[i]))
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp
    return li

实际使用中列表不是一分为二排序好的,怎么处理?

  • 分解: 将列表越分越小,直至分成一个元素
  • 终止条件:一个元素是有序的。
  • 合并:将两个有序列表归并,列表越来越大。

归并图解:
数据结构与算法总结——Python版_第13张图片
数据结构与算法总结——Python版_第14张图片

完整代码应该怎么实现?

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
    ltmp.extend(li[i:mid + 1])
    ltmp.extend(li[j:high + 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)
    return li

python内置的sorted的排序方法就是基于归并排序优化的。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
总结:
数据结构与算法总结——Python版_第15张图片挨个移动的都是稳定的,不是挨个换的都是不稳定的。

5.7. 希尔排序

希尔排序(Shell Sort)是一种分组插入排序算法,插入排序算法的改进版本,非常稳定的算法。

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

数据结构与算法总结——Python版_第16张图片

def insert_sort_gap(li, gap):
    for i in range(gap, len(li)):
        tmp = li[i]
        j = i -gap
        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
    return li

5.8. 计数排序

直接统计某个数出现的次数。
数据结构与算法总结——Python版_第17张图片

def count_sort(li, max_count):
    count = [0 for _ in range(max_count + 1)]
    for val in li:
        count[val] += 1
    li.clear()
    for index, val in enumerate(count):
        for i in range(val):
            li.append(index)

    return li


li = [3, 2, 6, 1, 7, 9, 6, 8]
# 注意这里传的是最大值
result = count_sort(li, max(li))
print(result)
print(sorted(li))

遇到字典的时候可以放到列表中。

缺点:列表短,数值大的时候不太友好。怎么解决,引出桶排序。

5.9.桶排序

对计数排序的改进,把某个范围的元素放到一起。 不是很重要,知道原理代码能写出了就行了。
桶排序(Bucket Sort):

  • 首先将元素分在不同的桶中,再对每个桶中的元素排序 29 25 3 49 9 37 21 43

数据结构与算法总结——Python版_第18张图片

def bucket_sort(li, n, max_num):
    # 创建桶
    buckets = [[] for _ in range(n)]
    for var in li:
        # 去最小值是因为当值为最后一位的时候数组下标会越界,如10000分为100组,10000的时候数应该放在99下标的数组中,而不是100
        i = min(var // (max_num // n), n - 1)
        buckets[i].append(var)
        # 装进桶的同时进行排序,只需要给前面一个数比较看谁大就行了,保证了每个桶的数按照顺序放入
        for j in range(len(buckets[i]) - 1, 0, -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:
        sorted_li.extend(buc)
    return sorted_li

桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。

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

5.10. 基数排序

桶排序的改进,先按照个位数进行排序,输出,再按照10位进行排序。

数据结构与算法总结——Python版_第19张图片

def radix_sort(li):
    max_num = max(li)
    # 求最大数的位数
    it = 0
    while 10 ** it <= max_num:
        # 固定10个桶
        buckets = [[] for _ in range(10)]
        for var in li:
            # 从前往后依次取数,如987,->7,8,9
            digit = (var // 10 ** it) % 10
            buckets[digit].append(var)
        # 按照对应位数粪桶完成
        li.clear()
        for buc in buckets:
            li.extend(buc)
        it += 1
    return li

时间复杂度: O ( k n ) O(kn) O(kn),k以10为底的数
数据结构与算法总结——Python版_第20张图片

二.数据结构

数据结构大致可以分为三类:线性结构,树结构,图结构。

2.1.列表/数组

列表(其他语言称数组) 是一种基本数据类型。
数组跟列表有两点不一样:数组元素必须一样,并且长度固定,Python中则不需要。那么python中的列表是如何实现元素可以不一样,长度固定呢?python列表里面存放的地址不是值了,把元素的地址放到列表中,就是C中的指针。长度不够的时候列表会自己开辟。
列表的一些问题

  • 列表中的元素是如何存储的?
  • 列表的基本操作:按下标查找、插入元素、删除元素…
  • 这些操作的时间复杂度是多少?
  • Python的列表是如何实现的?

列表的一些操作可参考:一文详解列表,元组,字典,集合,生成器,迭代器,可迭代对象,zip,enumerate。

2.2.栈

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

  • 栈的特点: 先进后出 栈的概念:栈顶、栈底
  • 栈的基本操作: 进栈(压栈): push 出栈: pop 取栈顶: gettop
  • 使用一般的列表结构即可实现栈:进栈: li.append;出栈: li.pop;取栈顶: li[-1]

栈的简单实现:

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

stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop())

应用:栈能用在哪?
看一个括号匹配问题。检查括号写法是否正确。

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:
            if stack.is_empty():
                return False
            elif stack.get_top() == match[ch]:
                stack.pop()
            else:
                return False
    if stack.is_empty():
        return True
    else:
        False


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

2.3. 队列

  队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。进行插入的一端称为队尾(rear),插入动作称为进队或入队进行删除的一端称为队头(front),删除动作称为出队队列的
性质: 先进先出(First-in,First-out)
队列实现方式:环形队列
环形队列:当队尾指针front == Maxsize - 1时,再前进一个位置就自动到0.
队指针前进1: front =(front +1)% MaxSize
队尾指针前进1: rear =(rear + 1)% MaxSize
队空条件: rear == front
队满条件:(rear +1)% MaxSize == front


自己实现环形队列:

class Queue:
    def __init__(self, size=100):
        self.queue = [0 for _ in range(size)]
        self.size = size
        # 队首指针
        self.rear = 0
        # 队尾指针
        self.front = 0

    def push(self, element):
        if not self.is_filled():
            self.rear = (self.rear + 1) % self.size
            self.queue[self.rear] = element
        else:
            raise IndexError("队列已满")

    def pop(self):
        if not self.is_empty():
            self.front = (self.front + 1) % self.size
            return self.queue[self.front]
        else:
            raise IndexError("队列为空")

    def is_empty(self):
        return self.rear == self.front

    def is_filled(self):
        return (self.rear + 1) % self.size == self.front

q = Queue(5)
for i in range(4):
    q.push(i)
print(q.pop())
q.push(4)

上面的队列是单向队列,只能先进先出,不能双向进出。于是就有了双向队列,双向队列两头都可以进出的队列。
python内置队列模块:
python中的queue包并不是队列包,只是保证线程安全的包。队列包通过from collections import deque引入deque表示双向队列,collections中中存放了一些数据结构包。

使用方法: from collections import deque
# 创建的是个双向队列
创建队列: queue = deque()
# 单向队列
进队: append() # 队尾进队
出队: popleft() # 队首出队
# 双向队列时的操作,一般用的不多
双向队列队首进队: appendleft()
双向队列队尾出队: pop()
from collections import deque
# 队满自动出列
q = deque([1, 2, 3, 4, 5], 3)
print(q)

输出:

deque([3, 4, 5], maxlen=3)

下面再看一个用队列实现linux的tell函数的问题:

# file.txt文件
1223
rwqer
qewf
vdsg
q3wr
xczv
24141fswf
wqeq
fsgfd

from collections import deque
# 用队列实现linux的tell函数
def tell(n):
    with open("file.txt", "r") as f:
        # deque可以接收一个可迭代对象,deque会自动迭代文件对象f并将其行存储在队列中。
        q = deque(f, n)
        return q
for line in tell(5):
    print(line, end="")

输出:

q3wr
xczv
24141fswf
wqeq
fsgfd

上面的问题如果改成前几行呢?直接用户for循环遍历前几个就行了。
再来看个迷宫的问题:

# 0表示路,1表示墙
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, 0],
    [1, 0, 0, 0, 0, 0, 1, 1, 0, 1],
    [1, 1, 1, 0, 1, 1, 1, 1, 1, 0],
    [1, 1, 1, 0, 0, 1, 0, 1, 0, 1],
    [1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
    [1, 1, 0, 0, 0, 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:
        curNode = stack[-1]
        if curNode[0] == x2 and curNode[1] == y2:
            for p in stack:
                print(p)
            return True
        # 遍历四个方向
        for dir in dirs:
            nextNode = dir(curNode[0], curNode[1])
            # 检查下一个节点是否越界
            if nextNode[0] < 0 or nextNode[0] >= len(maze) or nextNode[1] < 0 or nextNode[1] >= len(maze[0]):
                continue
            # 如果下一个节点能走且未访问过
            if maze[nextNode[0]][nextNode[1]] == 0:
                stack.append(nextNode)
                # 标记已经走过
                maze[nextNode[0]][nextNode[1]] = 2
                break
        else:
            # 四个方向都无法前进,回溯
            stack.pop()
    else:
        print("没有路")
        return False

maze_path(1, 1, 8, 8)

输出:

(1, 1)
(2, 1)
(3, 1)
(4, 1)
(5, 1)
(5, 2)
(5, 3)
(6, 3)
(7, 3)
(7, 4)
(8, 4)
(8, 5)
(8, 6)
(8, 7)
(8, 8)

链表队列可以解决环形队列固定长度问题。

2.4. 链表

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

看一个简单的链表演示

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

a = Node(1)
b = Node(2)
c = Node(3)

a.next = b
b.next = c

print(a.next.next.item)

怎么创建链表?头插法,尾插法。

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

def creat_linklist_head(li):
    head = Node(li[0])
    for element in li[1:]:
        node = Node(element)
        node.next = head
        head = node
    return head

def creat_linklist_tail(li):
    head = Node(li[0])
    tail = head
    for element in li[1:]:
        node = Node(element)
        tail.next = node
        tail = node
    return head

链表怎么插入删除?

插入的时候先跟后面一个连接起来在和前面节点链接,删除的时候需要前面一个节点和后面一个结点先连接起来在删除中间的节点。
数据结构与算法总结——Python版_第21张图片
数据结构与算法总结——Python版_第22张图片

双链表怎么实现?

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

数据结构与算法总结——Python版_第23张图片

数据结构与算法总结——Python版_第24张图片

p.next = curNode.next
curNode.next.prior =p
p.prior = curNode
curNode.next = p

2.5. 哈希表

python中的字典和集合都是通过哈希表实现的。哈希表通过一个哈希函数来计算数据存储位置的数据结构,通常支持如下操作:

  • insert(key,value): 插入键值对(key,value)
  • get(key): 如果存在键为key的键值对则返回其value,否则返回空值
  • delete(key):删除键为key的键值对

哈希表从直接寻址表演化而来,直接寻址表是吧key为k的元素放到k位置上。
改进直接寻址表:->哈希

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

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

哈希冲突怎么解决?
哈希寻址法(用的很少),拉链法

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

拉链法:

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

数据结构与算法总结——Python版_第25张图片
常见的哈希函数:

  • 除法哈希法:h(k)=k%m
  • 乘法哈希法:h(k)= foor(m*(A*key%1))
  • 全域哈希法:hab(k)=((a*key + b)mod p) mod m a,b=1,2,…,P-1

自己实现拉链法哈希表:

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

    '''
    下面的三个函数可以完成插入一个列表
    '''

    # iterable表示要传入的列表
    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):
        # self是可迭代对象,调用这个函数的本身就是个可迭代对象。
        for n in self:
            if n == obj:
                return True
        else:
            return False

    # 让这个链表成为一个可迭代对象(返回的是迭代器,迭代器上面创建好了),支持for循环
    def __iter__(self):
        return self.LinkListIterator(self.head)

    # 直接打印对象转为字符串
    def __repr__(self):
        return "<<" + ",".join(map(str, self)) + ">>"


# 哈希表的目的是做一个类似集合的东西
class HashTable:
    def __init__(self, size=101):
        self.size = size
        # 拉链法,每个位置是个链表,每个位置初始化一个空链表
        self.T = [LinkList() for i in range(self.size)]

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

    def find(self, k):
        # 先找打哈希值
        i = self.hash(k)
        return self.T[i].find(k)

    def insert(self, k):
        i = self.hash(k)
        if self.find(k):
            print("重复插入")
        else:
            self.T[i].append(k)


ha = HashTable()
ha.insert(1)
ha.insert(2)
ha.insert(3)
ha.insert(103)
print(",".join(map(str, ha.T)))

输出:

<<>>,<<1>>,<<2,103>>,<<3>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>

上面的函数用到了自定义迭代器和可迭代对象,对于这一点不懂的小伙伴可以看下我的另一篇博文:
一文详解列表,元组,字典,集合,生成器,迭代器,可迭代对象,zip,enumerate

哈希表的应用:

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

2.6. 树

之前在将堆的时候简单介绍了下树。树是一种数据结构,比如目录结构树是一种可以递归定义的数据结构树是由n个节点组成的集合,如果n=0,那这是一棵空树;如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。

2.6.1. 二叉树

一个节点最多分为两个树枝。
简单实现下下面的二叉树:
数据结构与算法总结——Python版_第26张图片

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.rchild = g
e.lchild = a
a.rchild = c
g.rchild = f
c.rchild = d
c.lchild = b
root = e
print(root.lchild.rchild.data)

二叉树遍历:
四种方式,前序遍历,中序遍历,后序遍历,层次遍历。可以根据遍历结果反推出树的结构。

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.rchild = g
e.lchild = a
a.rchild = c
g.rchild = f
c.rchild = d
c.lchild = b

root = e


# 前序遍历
def pre_order(root):
    if root:
        print(root.data, end=",")
        pre_order(root.lchild)
        pre_order(root.rchild)


# 中序遍历
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data, end=",")
        in_order(root.rchild)


# 后序遍历
def post_order(root):
    if root:
        in_order(root.lchild)
        in_order(root.rchild)
        print(root.data, end=",")


# 层次遍历
def layer_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)


pre_order(root)
print("\n")
in_order(root)
print("\n")
post_order(root)
print("\n")
layer_order(root)

2.6.2. 二叉搜索树

二又搜索树是一颗二叉树且满足性质: 设x是二叉树的一个节点。如果y是x左子树的一个节点,那么y.key ≤ x.key; 如果y是x右子树的一个节点,那么y.key > x.key

二叉搜索树的操作:查询,插入,删除

插入和查询的实现:

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)
            node.rchild.parent = node
        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

    # 非递归查询
    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data < val:
                p = p.lchild
            elif p.data > val:
                p = p.lchild
            else:
                return p
        return None

    # 前序遍历
    def pre_order(self, root):
        if root:
            print(root.data, end=",")
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    # 中序遍历
    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=",")
            self.in_order(root.rchild)

    # 后序遍历
    def post_order(self, root):
        if root:
            self.in_order(root.lchild)
            self.in_order(root.rchild)
            print(root.data, end=",")


tree = BST([4, 5, 3, 7, 8, 1, 2, 9])
tree.pre_order(tree.root)
print("")
# 一定是升序序列
tree.in_order(tree.root)
print("")
tree.post_order(tree.root)
print("")
print(tree.query_no_rec(4).data)

删除:

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)
            node.rchild.parent = node
        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

    # 非递归查询
    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data < val:
                p = p.lchild
            elif p.data > val:
                p = p.lchild
            else:
                return p
        return None

    # 情况一:node是叶子结点
    def __remove_mode_1(self, node):
        if not node.parent:
            self.root = None
        if node == node.parent.lchild:
            node.parent.lchild = None
        else:
            node.parent.rchild = None

    # 情况2.1:node只有一个左孩子
    def __remove_node_21(self, node):
        if not node.parent:
            self.root = node.lchild
            node.lchild.parent = None
        elif node == node.parent.lchild:
            node.parent.lchild = node.parent
            node.lchild.parent = node.parent
        else:
            node.parent.rchild = node.lchild
            node.lchild.parent = node.parent

    # 情况2.2:node只有一个右孩子
    def __remove_node_22(self, 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_mode_1(node)
            elif not node.rchild:  # 2.1只有一个左孩子
                self.__remove_node_21(node)
            elif not node.lchild:  # 2.1只有一个右孩子
                self.__remove_node_22(node)
            else:  # 3.两个孩子都有
                min_node = node.rchild
                while min_node.lchild:
                    min_node = min_node.lchild
                node.data = min_node.data
                if min_node.rchild:
                    self.__remove_node_22(node)
                else:
                    self.__remove_mode_1(min_node)

    # 前序遍历
    def pre_order(self, root):
        if root:
            print(root.data, end=",")
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    # 中序遍历
    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=",")
            self.in_order(root.rchild)

    # 后序遍历
    def post_order(self, root):
        if root:
            self.in_order(root.lchild)
            self.in_order(root.rchild)
            print(root.data, end=",")


tree = BST([4, 5, 3, 7, 8, 1, 2, 9])
# 一定是升序序列
tree.in_order(tree.root)
print("")
tree.delete(4)
tree.in_order(tree.root)
print("")

2.6.3. AVL树

二叉树的时间复杂度是 O ( l o n g n ) O(longn) O(longn),但是会出现最坏的情况,二叉树非常的偏斜,一直往一边分支。从而引出了AVL树。
AVL树: AVL树是一棵自平衡(任何两边的树的高度差不会超过1)的二叉搜索树。
AVL树具有以下性质:

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

数据结构与算法总结——Python版_第27张图片

你可能感兴趣的:(python,数据结构)