当前位置:   article > 正文

【详解】数据结构及算法详解_数据结构与算法分析

数据结构与算法分析

1 算法的衡量标准

1.1 算法

解决问题的办法,是一种独立的存在的解决问题的方法和思想,它不依赖于代码。代码只不过是对算法的一种表达和实现。

1.2 数据结构

存储和组织数据的方式

1.3 时间复杂度

在规模量级上对算法的衡量,表示一个算法随着问题规模不断变化的最主要趋势

  • 计算规则(6条)
    • 基本操作:只有常数项,时间复杂度为O(1)
    • 顺序结构:时间复杂度按加法进行计算
    • 循环结构、递归结构:时间复杂度按乘法进行计算
    • 分支结构:时间复杂度取最大值
    • 判断一个算法的效率时,往往只需要关注操作数量的最高次项,忽略系数、低阶及常数项
    • 在没有特殊说明的情况下,我们所分析的算法的时间复杂度都是指最坏时间复杂度(提供了一种保证,表明算法在此种程度的基本操作中一定能完成工作。)
    • 函数嵌套,时间复杂度按乘法计算

O(1) 执行次数恒定的计算

1.4 空间复杂度

一个算法在运行过程中临时消耗内存的大小的度量

1.5 算法的复杂度

算法的时间复杂度和空间复杂度的合称

1.6 算法五大特性

①输入: 算法具有0个或多个输入

②输出: 算法至少有1个或多个输出

③有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成

④确定性:算法中的每一步都有确定的含义,不会出现二义性

⑤可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成

1.7 常见时间复杂度

执行次数函数举例非正式术语
12O(1)常数阶
2n+3O(n)线性阶
3n2+2n+1O(n2)平方阶
5log2n+20O(logn)对数阶
6n3+2n2+3n+4O(n3)立方阶
  • 时间复杂度曲线

    在这里插入图片描述

    • 所消耗的时间从小到大:
      • O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3)
      • 时间复杂度越低,效率越高

2 数据结构

2.1 数据结构

数据对象中数据元素之间的关系-

  • 内置数据结构:list、元组、字典等
  • 扩展数据结构:栈、队列等

2.1.1 程序 = 数据结构(问题载体) + 算法

高效的程序需要在数据结构基础上设计和选择算法

2.1.2 元素外置:地址和数据分开存储

  • 地址存储占用的内存空间
    • 32位操作系统:4位
    • 64位操作系统:8位

2.1.3 数据结构

1 线性结构

1.1 顺序表

  • 一体式顺序表
  • 分离式顺序表

1.2 链表

  • 单链表
    • 元素域: 存放具体数据
    • 链接域: 存放下一个节点的位置

1.3 栈

在这里插入图片描述
  • 1.3.1 特点:

    ① 一种运算受限的线性表,仅允许在表的一端进行插入和删除运算,这一端被称为栈顶,相对地,把另一端称为栈底.

    ② 处理数据的时候符合先进后出(FILO)特点

  • 1.3.2 作用:函数里面有可能要使用到局部变量, 不能总是用全局变量. 而局部变量在函数使用完毕之后就销毁了, 那么将局部变量存储在栈中既能不浪费空间又能及时销毁.

  • 1.3.3 代码实现

    • 1 链表实现栈

      class Node:
          def __init__(self, data, next=None):
              self.data = data
              self.next = next
      
      class LinkedStack:
          def __init__(self):
              # 最上面的元素
              self.top = None
      
          def push(self, value: int):
              newtop = Node(value)
              newtop.next = self.top
              self.top = newtop
      
          def pop(self):
              if self.top:
                  value = self.top.data
                  self.top = self.top.next
                  return value
      
      
      if __name__ == "__main__":
          stack = LinkedStack()
          for i in range(9):
              stack.push(i)
      
          for i in range(9):
              ele = stack.pop()
              print(ele)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

1.4 队列

在这里插入图片描述
  • 特点:

    ① 一种操作受限的线性表,只允许在表的头部(front)(队头)进行删除操作,而在表的尾部(rear)(队尾)进行插入操作。

    ② 处理数据的时候符合先进先出(FIFO)特点

  • 作用:

    在任务处理类的系统中,先把用户发起的任务请求接收过来存到队列中,然后后端开启多个应用程序从队列中取任务进行处理,队列可以起到了缓冲压力的作用

  • 代码实现

    • 列表队列优化

      # 尾部入  头部出
      
      class ArrayQueue:
          def __init__(self, capacity):# capacity 容量
              # 申请固定长度空间
              self.__items = [0]*capacity
              self.__capacity = capacity
              # 头部指针
              self.__head = 0
              # 尾部指针(下一个元素添加到这个位置)
              self.__tail = 0
      
          def getItmes(self):
              return self.__items
      
          def enqueue(self, item):
              # 如果存满了,返回False
              if self.__head==0 and self.__tail==self.__capacity:
                  return False
              # 没有存满,指针到最后了  需要先把元素往前挪
              if self.__tail==self.__capacity:
                  # 指针到最后了,容量没有满
                  for i in range(0, self.__tail - self.__head):
                      self.__items[i] = self.__items[i + self.__head]
                  # 修改尾部指针
                  self.__tail = self.__tail - self.__head
                  # 修改头部指针
                  self.__head = 0
              # 元素添加
              self.__items[self.__tail] = item
              self.__tail += 1
              return True
      
          def dequeue(self):
              # 非空
              if self.__head != self.__tail:
                  item = self.__items[self.__head]
                  # 把头部往后移动1位
                  self.__head += 1
                  return item
              else:
                  return None
      
      arrayQ = ArrayQueue(6)
      arrayQ.enqueue(10)
      arrayQ.enqueue(20)
      print(arrayQ.getItmes())
      print(arrayQ.dequeue())
      print(arrayQ.getItmes())
      arrayQ.enqueue(30)
      arrayQ.enqueue(40)
      arrayQ.enqueue(50)
      arrayQ.enqueue(60)
      arrayQ.enqueue(70)
      print(arrayQ.getItmes())
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
    • 链表实现队列

      # 节点
      class Node:
          def __init__(self, data, next=None):
              # 数据
              self.data = data
              # 链接域
              self.next = next
      
      class LinkedQueue:
          def __init__(self):
              self.__head = None
              self.__tail = None
      
          def enqueue(self, value):
              '''入队列'''
              new_node = Node(value)
              if self.__tail:
                  # 非空
                  self.__tail.next = new_node
              else:
                  # 空链表
                  self.__head = new_node
              # 尾部指向新的节点
              self.__tail = new_node
      
          def dequeue(self):
              if self.__head:
                  # 头部不为空
                  # 获取头部元素
                  value = self.__head.data
                  # 头部指向原来的下一个节点
                  self.__head = self.__head.next
                  if not self.__head:
                      # 如果头部指向None 链表为空
                      self.__tail = None
                  return value
      
      
      if __name__ == "__main__":
          linkQ = LinkedQueue()
          linkQ.enqueue(10)
          linkQ.enqueue(20)
          linkQ.enqueue(30)
      
          print(linkQ.dequeue())
          print(linkQ.dequeue())
          print(linkQ.dequeue())
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
    • 双端队列

在这里插入图片描述

​ 特点:

  • 一种具有队列和栈的性质的数据结构
  • 双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行
  • 双端队列可以在队列任意一端入队和出队

2 非线性结构

3 排序算法

3.1 排序

使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

3.2 算法稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的, 否则称为不稳定的。

3.3 排序算法
  • 不稳定算法: 选择排序快速排序、希尔排序、堆排序

  • 稳定算法: 冒泡排序插入排序、归并排序和基数排序

3.3.1 代码实现

1 冒泡排序

  • 特点:

    重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成

    名字由来:因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”

  • 复杂度分析:

    • 最差时间复杂度 : O(n^2)

    • 最优时间复杂度 : O(n) # 遍历一遍发现没有任何元素发生了位置交换,终止排序

    • 算法稳定性 : 稳定算法

    • 原地排序:是

# n-1+n-2+...+1
def bubble_sort(alist):
    '''冒泡排序'''
    length = len(alist)
    count= 0
    for i in range(length-1):
        flag = False
        for j in range(length-1-i):
            count+=1
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
                flag = True

        if not flag:
            break

    print(count)
# 4+3+2+1
if __name__ == '__main__':
    # l = [5,3,4,7,2]
    l = [2,3,4,5,7]
    bubble_sort(l)
    print(l)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2 选择排序

  • 特点:

    第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾. 以此类推,直到全部待排序的数据元素的个数为零.

  • 复杂度分析:

    • 最差时间复杂度 : O(n^2)

    • 最优时间复杂度 : O(n^2)

    • 算法稳定性 : 不稳定算法

    • 原地排序:是

def select_sort(alist):
    # 5
    length = len(alist)
    for i in range(length-1):
        # 设置目标
        # targetIndex = i
        # 设置最小值索引
        minIndex = i
        for j in range(i+1,length):
            if alist[j] < alist[minIndex]:
                minIndex = j
        # 如果目标和最小值索引不同 交换
        if minIndex!=i:
            alist[i], alist[minIndex] = alist[minIndex], alist[i]

if __name__ == '__main__':
    alist = [1,3,4,10,0,1000,88]
    select_sort(alist)
    print(alist)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3 插入排序

  • 特点:

    将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。(每步将一个待排序的记录,按排序要求插入到前面已经排序的数据中适当位置上,直到全部插入完为止)

  • 复杂度分析:

    • 最差时间复杂度:O(n^2)
    • 最优时间复杂度:O(n)
    • 算法稳定性: 稳定的排序方法
    • 原地排序:是
def insert_sort(alist):
    length = len(alist)
    for i in range(1,length):
        # i每一次需要排序的结束索引
        # 0 - 1
        for j in range(i,0,-1):
            if alist[j] < alist[j-1]:
                alist[j], alist[j-1] = alist[j-1], alist[j]
            else:
                break

if __name__ == '__main__':
    alist = [1, 100, 99, 20, 5, 1000]
    insert_sort(alist)
    print(alist)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

4 快速排序

  • 特点:

    通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

  • 排序流程:

    (1) 首先设定一个分界值,通过该分界值将数组分成左右两部分

    (2) 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边,此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值

    (3) 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也做类似处理

    (4) 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序,当左、右两个部分各数据排序完成后,整个数组的排序也就完成了

  • 复杂度分析:

    • 最优时间复杂度: O(nlogn)

    • 最差时间复杂度: O(n^2)

    • 算法稳定性: 不稳定

    • 原地排序:是

def quick_sort(alist,start,end):
    if start>=end:
        return
    # 界限值
    mid = alist[start]
    # 左右游标
    left = start
    right = end
    while left < right:
        # 从右边开始找寻小于mid的值 归类到左边
        while alist[right] >= mid and left < right:
            right -= 1
        alist[left] = alist[right]
        # 从左边开始找寻大于mid的值 归类到右边
        while alist[left] < mid and left < right:
            left += 1
        alist[right] = alist[left]
    # left=right
    alist[left] = mid
    # 排序左边
    quick_sort(alist,start,left-1)
    # 排序右边
    quick_sort(alist,left+1,end)


if __name__ == '__main__':
    alist = [5,9,8,7,6,4,3,2,1]
    quick_sort(alist,0,len(alist)-1)
    print(alist)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

4 二分查找

4.1 二分查找
  • 特点

    分查找又称折半查找,它是一种效率较高的查找方法,①必须采用顺序存储结构 ②必须按关键字大小有序排列。

  • 基本思想

    数组分为三部分,依次是中值前,中值,中值后,将要查找的值与中值进行比较,若小于中值则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回。

  • 复杂度分析

    • 最差时间复杂度: O(logn)

    • 最优时间复杂度: O(1)

4.2 代码实现
4.2.1 递归版本
def binary_search(alist, item):
    # 数列的长度
    n = len(alist)
    if n==0:
        return False
    # 递归的结束条件
    # 中间值索引
    mid = n // 2
    if item == alist[mid]:
        return True
    elif item < alist[mid]:
        # 左边
        return binary_search(alist[:mid],item)
    elif item > alist[mid]:
        # 右边
        return binary_search(alist[mid+1:],item)

if __name__ == '__main__':
    alist = [1,2,3,4,5,6,7,8,9]
    print(binary_search(alist, 5))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
4.2.2 递归优化版本
def binary_search_achieve(alist, item, start, end):
    """二分查找"""
    if start > end:
        return -1
    # 中间值索引
    mid = (start + end) // 2

    if item == alist[mid]:
        return mid
    elif item < alist[mid]:
        return binary_search_achieve(alist, item, start, mid - 1)
    elif item > alist[mid]:
        return binary_search_achieve(alist, item, mid + 1, end)


def binary_search(alist, item):
    return binary_search_achieve(alist, item, 0, len(alist) - 1)


# 40亿 32  2^32
if __name__ == '__main__':
    alist = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(binary_search(alist, 8))
    print(binary_search(alist, 100))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
4.2.3 非递归版本
def binary_search(alist, item):
    """二分查找"""

    # 设置起始位置 获取中间值
    start = 0
    end = len(alist) - 1
    # 必须要=  需要比较最后的一个元素是否是需要的数据
    while start <= end:
        # 获取中间值
        # mid = (start+end)//2
        # end//2+start//2
        mid = start+(end-start)//2
        # start+(end-start)//2
        if item == alist[mid]:
            return mid
        elif item < alist[mid]:
            end = mid - 1
        elif item > alist[mid]:
            start = mid + 1

    # 没有找到想要找的数字
    return -1

# 最好 O(1) 最坏 O(logn)
if __name__ == '__main__':
    alist = [1,2,3,4,5,6,7,8,9]
    print(binary_search(alist, 1))
    print(binary_search(alist, 5))
    print(binary_search(alist, 100))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
4.2.4 二分查找-位置
def binary_search_first(alist, item):
    """二分查找 如果找到返回索引  没有找到返回-1"""

    # 设置起始位置 获取中间值
    start = 0
    end = len(alist) - 1
    # 必须要=  需要比较最后的一个元素是否是需要的数据
    while start <= end:
        # 获取中间值
        mid = (start + end)//2

        if item == alist[mid]:
            # 索引为0 不需要继续查找
            if mid==0 or alist[mid-1]!=item:
                return mid
            else:
                end = mid - 1
        elif item < alist[mid]:
            end = mid - 1
        elif item > alist[mid]:
            start = mid + 1

    # 没有找到想要找的数字
    return -1
# 最好 O(1) 最坏 O(logn)
if __name__ == '__main__':
    alist = [1,2,3,4,5,5,5,6,7,8,9]
    # print(binary_search(alist, 1))
    # print(binary_search(alist, 1))
    # 获取最后一个5
    # print(binary_search_last(alist, 5))
    print(binary_search_first(alist, 5))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
4.2.5 第一个位置
def binary_search(alist, item):
      """二分查找 如果找到返回索引  没有找到返回-1"""
  
      # 设置起始位置 获取中间值
      start = 0
      end = len(alist) - 1
      # 必须要=  需要比较最后的一个元素是否是需要的数据
      while start <= end:
          # 获取中间值
          mid = (start + end)//2
  
          if item == alist[mid]:
              # 判断是否是第一个3
              if mid==0 or alist[mid-1]!=item:
                  return mid
              else:
                  end = mid - 1
          elif item < alist[mid]:
              end = mid - 1
          elif item > alist[mid]:
              start = mid + 1
  
      # 没有找到想要找的数字
      return -1
  # 最好 O(1) 最坏 O(logn)
  if __name__ == '__main__':
      alist = [1,2,3,3,3,4,5]
      print(binary_search(alist, 3))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
4.2.6 最后一个位置
def binary_search(alist, item):
    """二分查找 如果找到返回索引  没有找到返回-1"""

    # 设置起始位置 获取中间值
    start = 0
    maxIndex = len(alist) - 1
    end = maxIndex
    # 必须要=  需要比较最后的一个元素是否是需要的数据
    while start <= end:
        # 获取中间值
        mid = (start + end)//2

        if item == alist[mid]:
            if mid == maxIndex or alist[mid+1]!=item:
                return mid
            else:
                start = mid + 1
        elif item < alist[mid]:
            end = mid - 1
        elif item > alist[mid]:
            start = mid + 1

    # 没有找到想要找的数字
    return -1
# 最好 O(1) 最坏 O(logn)
if __name__ == '__main__':
    alist = [1,2,3,3,3,4,5]
    print(binary_search(alist, 3))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

5 非线性数据结构-树

5.1 特点

一种非线性结构,用来模拟具有树状结构性质的数据集合. 它是由n(n>=1)个有限节点组成一个具有层次关系的集合。

①每个节点有零个或多个子节点

②没有父节点的节点称为根节点

③每一个非根节点有且只有一个父节点

④除了根节点外,每个子节点可以分为多个不相交的子树

名字由来:因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的.

  • 拓展:线性结构和非线性结构

    • 线性结构:数据元素之间存在着“一对一”的线性关系的数据结构

      • 特点

        ①集合中必存在唯一的一个"第一个元素”

        ②集合中必存在唯一的一个"最后的元素"

        ③除最后元素之外,其它数据元素均有唯一的"后继"

        ④除第一元素之外,其它数据元素均有唯一的"前驱”

    • 非线性结构:一个结点元素可能对应多个直接前驱和多个后继的数据结构

5.2 树的术语

  • 节点的度:一个节点含有的子树的个数称为该节点的度
  • 树的度:一棵树中,最大的节点的度称为树的度
  • 叶节点或终端节点:度为零的节点
  • 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
  • 树的高度或深度:树中节点的最大层次
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟
  • 节点的祖先:从根到该节点所经分支上的所有节点
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林

5.3 树的分类

5.3.1 无序树

树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树

5.3.2 有序树

树中任意节点的子节点之间有顺序关系,这种树称为有序树
- 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树
- B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个的子树

1 二叉树

  • 特点:每个节点最多含有两个子树的树,通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)

  • 分类

    • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列的二叉树。

    • 满二叉树:所有叶节点都在最底层的完全二叉树

    • 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树

    • 排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉树)

  • 基本要求

    (1)若左子树不空,则左子树上所有节点的值均小于它的根节点的值

    (2)若右子树不空,则右子树上所有节点的值均大于它的根节点的值

    (3)左、右子树也分别为二叉排序树

    排序二叉树包含空树

  • 存储方式

    • 顺序存储:将数据结构存储在固定的数组中,虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树存储方式。
    • 链式存储:由于对节点的个数无法掌握,常见树的存储表示都转换成二叉树进行处理,子节点个数最多为2
  • 应用场景

    ①xml,html等,那么编写这些东西的解析器的时候,不可避免用到树
    ②路由协议就是使用了树的算法
    ③mysql数据库索引
    ④文件系统的目录结构
    ⑤所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构

  • 性质:

    性质1: 在二叉树的第i层上至多有2^(i-1)个结点(i>0)
    性质2: 深度为k的二叉树至多有2^k-1个结点(k>0)
    性质3: 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1
    性质4: 最多有n个结点的完全二叉树的深度必为 log2(n+1)
    性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1 , 其父节点的编号必为i//2(i=1 时为根,除外)

  • 代码实现

    • 完全二叉树

      import queue
      
      class Node(object):
          """节点类"""
          def __init__(self, item):
              # 数据类型  10
              self.item = item
              # Node
              self.lchild = None
              # Node
              self.rchild = None
      
      class BinaryTree(object):
          """二叉树"""
          def __init__(self, node=None):
              # Node类型  根节点
              self.root = node
      
          def add(self, item):
              add_node = Node(item)
              # 空二叉树
              if self.root == None:
                  self.root = add_node
                  return
      
              my_queue =  queue.Queue()
              # 把root放入到队列中
              my_queue.put(self.root)
              while True:
                  # 获取元素
                  node = my_queue.get()
                  # 左右节点是否为空
                  if node.lchild == None:
                      node.lchild = add_node
                      return
                  if node.rchild == None:
                      node.rchild = add_node
                      return
                  # 放入左右节点
                  my_queue.put(node.lchild)
                  my_queue.put(node.rchild)
      
          def breadh_travel(self):
              """广度优先遍历"""
              if not self.root:
                  return
              # 定义队列
              my_queue = queue.Queue()
              # 放入根节点
              my_queue.put(self.root)
              while not my_queue.empty():
                  node = my_queue.get()
                  # 当前节点数据打印
                  print(node.item,end='')
                  # 左右节点添加到队列中
                  if node.lchild:
                      my_queue.put(node.lchild)
                  if node.rchild:
                      my_queue.put(node.rchild)
      
          def preorder_travel_out(self):
              self.__preorder_travel(self.root)
      
          def __preorder_travel(self, root):
              '''先序遍历: 给根节点 可以把这个节点按照根左右的方式遍历出来'''
              if root:
                  # 根
                  print(root.item,end='')# 0
                  # 左子树
                  self.__preorder_travel(root.lchild)
                  # 右子树
                  self.__preorder_travel(root.rchild)
      
          # 给一个节点 就可以对这个节点以及这个节点一下的节点进行中序遍历
          def inorder_travel(self, root):
              """中序遍历 左 根 右"""
              if root is not None:
                  self.inorder_travel(root.lchild)
                  print(root.item, end="")
                  self.inorder_travel(root.rchild)
      
          # 给一个节点 就可以对这个节点以及这个节点一下的节点进行后序遍历
          def postorder_travel(self, root):
              """后序遍历 根 左 右"""
              if root is not None:
                  self.postorder_travel(root.lchild)
                  self.postorder_travel(root.rchild)
                  print(root.item, end="")
      
      
      if __name__ == '__main__':
          tree = BinaryTree()
          tree.add("0")
          tree.add("1")
          tree.add("2")
          tree.add("3")
          tree.add("4")
          tree.add("5")
          tree.add("6")
          tree.add("7")
          tree.add("8")
          tree.add("9")
          tree.postorder_travel(tree.root)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
    • 完全二叉树添加节点

      import queue
      
      class Node(object):
          """节点类"""
          def  __init__(self, item):
              self.item = item
              self.lchild = None
              self.rchild = None
      
      class BinaryTree(object):
          """完全二叉树"""
          def __init__(self, node=None):
              self.root = node
      
          def add(self, item):
              """添加节点"""
              if self.root == None:
                  self.root = Node(item)
                  return
      
              # 队列
              que = queue.Queue()
              # 从尾部添加数据
              que.put(self.root)
      
              while True:
                  # 从头部取出数据
                  node = que.get()
                  # 判断左节点是否为空
                  if node.lchild == None:
                      node.lchild = Node(item)
                      return
                  else:
                      que.put(node.lchild)
      
                  if node.rchild == None:
                      node.rchild = Node(item)
                      return
                  else:
                      que.put(node.rchild)
      
          def breadh_travel(self):
              """广度优先遍历"""
      
              if self.root == None:
                  return
      
              # 队列
              que = queue.Queue()
              # 添加数据
              que.put(self.root)
      
              while not que.empty():
              # while queue:
                  # 取出数据
                  node = que.get()
                  # 当前节点数据
                  print(node.item, end="")
      
                  # 判断左右子节点是否为空
                  if node.lchild is not None:
                      que.put(node.lchild)
                  if node.rchild is not None:
                      que.put(node.rchild)
      
      if __name__ == '__main__':
          tree = BinaryTree()
          tree.add("A")
          tree.add("B")
          tree.add("C")
          tree.add("D")
          tree.add("E")
          tree.add("F")
          tree.add("G")
          tree.add("H")
          tree.add("I")
          tree.breadh_travel()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
    • 二叉树深度优先遍历

在这里插入图片描述
  • 广度优先可以找到最短路径

  • 深度优先往往可以很快找到搜索路径

在这里插入图片描述

在这里插入图片描述

class Node(object):
    """节点类"""
    def  __init__(self, item):
        self.item = item
        self.lchild = None
        self.rchild = None

class BinaryTree(object):
    """完全二叉树"""
    def __init__(self, node=None):
        self.root = node

    def add(self, item):
        """添加节点"""

        if self.root == None:
            self.root = Node(item)
            return

        # 队列
        queue = []
        # 从尾部添加数据
        queue.append(self.root)

        while True:
            # 从头部取出数据
            node = queue.pop(0)
            # 判断左节点是否为空
            if node.lchild == None:
                node.lchild = Node(item)
                return
            else:
                queue.append(node.lchild)

            if node.rchild == None:
                node.rchild = Node(item)
                return
            else:
                queue.append(node.rchild)

    def breadh_travel(self):
        """广度优先遍历"""

        if self.root == None:
            return

        # 队列
        queue = []
        # 添加数据
        queue.append(self.root)

        while len(queue)>0:
            # 取出数据
            node = queue.pop(0)
            print(node.item, end="")

            # 判断左右子节点是否为空
            if node.lchild is not None:
                queue.append(node.lchild)
            if node.rchild is not None:
                queue.append(node.rchild)

    # 给一个节点 就可以对这个节点以及这个节点一下的节点进行先序遍历
    def preorder_travel(self, root):
        """先序遍历 根 左 右"""
        if root is not None:
            print(root.item, end="")
            # 需要对1和1以下的节点先序遍历
            self.preorder_travel(root.lchild)
            self.preorder_travel(root.rchild)

    # 给一个节点 就可以对这个节点以及这个节点一下的节点进行中序遍历
    def inorder_travel(self, root):
        """中序遍历 左 根 右"""
        if root is not None:
            self.inorder_travel(root.lchild)
            print(root.item, end="")
            self.inorder_travel(root.rchild)

    # 给一个节点 就可以对这个节点以及这个节点一下的节点进行后序遍历
    def postorder_travel(self, root):
        """后序遍历 根 左 右"""
        if root is not None:
            self.postorder_travel(root.lchild)
            self.postorder_travel(root.rchild)
            print(root.item, end="")

if __name__ == '__main__':
    tree = BinaryTree()
    tree.add("0")
    tree.add("1")
    tree.add("2")
    tree.add("3")
    tree.add("4")
    tree.add("5")
    tree.add("6")
    tree.add("7")
    tree.add("8")
    tree.add("9")
    tree.preorder_travel(tree.root)
    print()
    tree.inorder_travel(tree.root)
    print()
    tree.postorder_travel(tree.root)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 根据遍历结果反推二叉树

  • 知道中序遍历先序遍历 或者 后序遍历 就可以推出二叉树的结构

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/388447
推荐阅读
相关标签
  

闽ICP备14008679号