数据结构

数据结构

定义

数据结构就是设计数据以何种方式组织并存放在计算机中

eg:列表,字典,元组,堆,栈,队列

程序 = 数据结构(静态的数据) + 算法(动态的操作)

分类

  • 逻辑结构
    • 线性(一对一)
    • 非线性
      • 树结构(一对多)
      • 图结构(多对多)
      • 集合结构(除属于同一集合,别无其它关系)
  • 存储结构(物理结构)
    • 顺序存储结构(列表)
    • 链式存储结构

注:逻辑上为非线性结构,存储结构可以以线性结构存储,eg:堆(在逻辑上试属于非线性结构中的树,但可以以线性存储结构中的列表存储)

c语言数组与python中列表的区别

  1. c语言需设置存储空间大小
  2. 两者空间存储内容范围不同
    image

c语言数组只能存储同一类型元素,python可存储不同类型元素,由于python不同类型元素所占的字节不同,无法定义数组容量,因此,python数组存储元素的地址,占用一片连续的空间。

插入删除的时间复杂度为:O(n)

特点

后进先出(last in first out:LIFO)

基本操作

  • 进栈:push
  • 出栈:pop
  • 取栈顶(不拿走):gettop

image

栈的实现

  • 进栈:li.append
  • 出栈:li.pop
  • 取栈顶:li[len(li)-1]
# 栈
# 访问该类属性格式:self+.+属性
class Stack:        # 定义一个栈类            
    def __init__(self):     # 初始化栈
        self.stack = []
    def push(self,element):     # 进栈方法
        self.stack.append(element)
    def pop(self):              # 出栈方法
        if len(self.stack) > 0:     # 判断栈类是否有元素
            return self.stack.pop()
        else:
            return None
    def get_top(self):      # 获取栈顶元素方法
        if len(self.stack) > 0:     # 判断是否存在栈顶元素
            return self.stack[-1]
        else:
            return None
stack = Stack()
stack.push(9)
stack.push(6)
stack.push(6)
print(stack.pop())
print(stack.pop())
print(stack.get_top())

栈的应用

# 栈的括号匹配问题
class Stack:
    def __init__(self):
        self.stack = []
    def push(self,element):
        self.stack.append(element)
    def pop(self):
        if len(self.stack) > 0:
            return self.stack.pop()
        else:
            return None
    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):
    stack = Stack()
    catch = {"]":"[","}":"{",")":"("}
    for i in s:
        if i in "([{":
            stack.push(i)
        else:
            if stack.is_empty():
                stack.push(i)
            elif catch[i] == stack.get_top():
                stack.pop()
            else:
                stack.push(i)
    if stack.is_empty():
        print("匹配")
    else:
        print("不匹配")
str = str(input())
brace_match(str)

队列

特点

先进先出(first in first out:FIFO)
image

队列的实现——环形队列

原理

image

队首指针往下一格:(front+1)%MaxSize

队尾指针往下一格:(rear+1)%MaxSize

队满条件:(rear+1)%MaxSize == front

队空条件:font == rear

实现

# 堆的底层实现
class Queue:
    def __init__(self,size):     # 指定列表空间大小
        self.size = size
        self.queue = [0 for _ in range(size)]
        self.rear = 0       # rear表示队尾元素
        self.front = 0          # front表示队头元素
    def push(self,element):
        if not self.is_filled():
            self.rear = (self.rear + 1) % self.size
            self.queue[self.rear] = element
        else:
            raise Exception("队满了!")
    def pop(self):
        if not self.is_empty():
            self.front = (self.front + 1) % self.size
            return self.queue[self.front]
        else:
            raise Exception("队空了!")
    # 队空情况
    def is_empty(self):
        return self.rear == self.front
    # 队满情况
    def is_filled(self):
        return (self.rear + 1) % self.size == self.front
queue = Queue(7)
for i in range(6):
    queue.push(i)
print(queue.pop())

双端队列

同时具有队列和栈的特点:元素可以从两端进行删除和插入操作

image

python队列内置模块的实现

from collections import deque
# 单向队列
queue = deque()
for j in range(10):
    queue.append(j)  # 在队尾插入元素
print(queue.popleft())  # 在队首移除元素

# 多向队列
for i in range(20,30):
    queue.appendleft(i)  # 在队首插入元素
print(queue.pop())   # 在队尾移除元素

队列的应用

# 队列的应用(从文件中读取后n行)
from collections import deque
def tail(n):
    with open(r"C:\Users\hby\Desktop\1.txt","r") as f:
        queue = deque(f,n)          
        # f表示初始化一个队列,n表示队列所能存放的最大空间,若超过,dequeue会将前面的元素移除,最后保留后加入的n给元素
        return queue
for i in tail(5):
    print(i,end="")

栈和队列的应用

背景

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

栈(深度优先搜索:一条路走到黑)

回溯法

  1. 原理:通过一定法则,进行搜索,并将路径保存在栈内存中,当元素走到头时,将该元素出栈,找到栈内还有可走路径的元素为止,停止出栈,最后到终点,则该栈中的元素则为到达终点的一条路径(并不一定是最短路径)

image

  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,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]
]
direction = [                   # 用于存储给出参数为(x,y)的上下左右坐标位置
    lambda x,y:(x+1,y),
    lambda x,y:(x,y+1),
    lambda x,y:(x-1,y),
    lambda x,y:(x,y-1)
]
def path(x1,y1,x2,y2):          # x1,y1表示起始点;x2,y2表示终点
    stuck = []                  # 将路径点存放在列表(可进行增加和删除操作)stuck中
    stuck.append((x1,y1))
    while(len(stuck)>0):        # 当列表还存在元素时(表示可能还有通路)
        curNode = stuck[-1]     # 记录已走到最末的当前点位置
        if curNode == (x2,y2):      # 当走到终点时,输出终点
            for i in stuck:
                print(i)
            return True
        for i in direction:
            newNode = i(curNode[0],curNode[1])      # 找到末点的后面的点位置,得到一个坐标(x,y)
            if maze[newNode[0]][newNode[1]] == 0:       # 判断该点是否是通路
                stuck.append(newNode)                  # 是通路则存放列表中
                maze[newNode[0]][newNode[1]] = 2        # 改变该点的标志为2,说明该点已走过
                break                               # 以新的点进行重新while循环,进行深度优先搜索
        else:
            # 若当前点都没有下一个可走位置,则去除该点,找到前一个点,如此循环,直到找到有可走路径的点为止
            stuck.pop()
            maze[newNode[0]][newNode[1]] = 2        # 并设该点为已走过的点
    else:           # 若列表内元素为空,则表明无通路
        print("无通路!")
        return False
path(1,1,8,8)

队列(广度优先搜索)

  1. 原理:考虑一个位置所有可走的位置,分叉多条路,最后得到的路径一定是最短路径,需要一个额外的数组来存储当前位置的源头下标。

image

  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,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]
]
# 方向
directions = [
    lambda x,y:(x+1,y),
    lambda x,y:(x,y+1),
    lambda x,y:(x-1,y),
    lambda x,y:(x,y-1)
]
# 寻回队列前的元素
def maze_back(path):
    Node = path[-1]                     # 从终点开始往回找
    realPath = []                       # 存放正确的路径
    while Node[2] != -1:                # 等于-1时即为起始点
        realPath.append(Node[0:2])
        Node = path[Node[2]]
    path.append(Node[0:2])
    realPath.reverse()                  # reverse能够使得列表本身发生颠倒
    for i in realPath:
        print(i)
# 广度优先搜索
from collections import deque           # python内置队列
def maze_path_queue(x1,y1,x2,y2):
    queue = deque()
    path = []           # 存放移除队列的元素
    queue.append((x1,y1,-1))            # -1表示该元素在path里源头的下标
    while len(queue) > 0:               # 判断找到终点元素前队列是否为空
        curNode = queue.pop()
        path.append(curNode)            # 保存元素,以便后面从终点找起点
        if curNode[0] == x2 and curNode[1] == y2:
            maze_back(path)
            return True
        for i in directions:
            newNode = i(curNode[0],curNode[1])
            if maze[newNode[0]][newNode[1]] == 0:
                queue.append((newNode[0],newNode[1],len(path)-1))
                maze[newNode[0]][newNode[1]] = 2
    else:
        print("无通路!")
        return False
maze_path_queue(1,1,8,8)

链表

定义

由一系列结点组成的元素集合

包含两部分:数据域(item)& 指针域(next)

数据域:存放数据

指针域:存放下一个结点的地址,指向下一个结点的位置

结点与结点之间不是顺序存放,结点内部之间则是顺序存放

链表的创建和遍历

  1. 链表的创建
    • 头插法:需要指向头节点的指针
    • 尾插法:需要指向头节点和尾节点的指针

image

# 链表的创建
class Node:
    def __init__(self,element):        # 初始化结点(包括数据域和指针域)
        self.element = element
        self.next = None
# 头插法
def head_insert(li):
    head = Node(li[0])
    for i in li[1:]:
        node = Node(i)        # 创建新的结点
        node.next = head
        head = node
    return head
head = head_insert([1,2,3,4,5,6])
print(head.element)
# 尾插法
def tail_insert(li):
    head = Node(li[0])
    tail = head
    for i in li[1:]:
        node = Node(i)
        tail.next = node
        tail = node
    return head,tail
head,tail = tail_insert([1,2,3,4,5,6])
print(head.next.element,tail.element)

链表的插入和删除

链表的插入和删除操作的效率要高于列表

  1. 链表的插入:

image

p.next = curNode.next
curNode.next = p
  1. 链表的删除:
    image
p=curNode.next
curNode.next = curNode.next.next
del p

双链表

双链表有两个指针(指向前一个结点的指针prior和指向后一个结点的指针next)

双链表的插入和删除

  1. 插入
    image
curNode.next.prior = p
p.next = curNode.next
curNode.next = p
p.prior = curNode
  1. 删除

image

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

复杂度分析

顺序表(数组/列表)与链表

  • 链表在插入和删除操作的时间效率要高于顺序表,顺序表在按下标查找元素的时间效率要高于链表
  • 链表的内存可以更灵活的分配(不需要连续的且既定大小的空间)

哈希表(散列表)

通过哈希函数计算数据存储位置的数据结构,属于线性存储结构,通常支持如下操作:

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

构成

①直接寻址表 ②哈希函数

哈希冲突

当有两个值通过哈希函数计算到了同一个位置上

python中的字典和集合以哈希表的形式实现的

哈希冲突解决方法

开放寻址法

通过哈希函数找到原本该存放的位置,当位置已有元素,接着通过以下方式进行空位的查找与存放;查找元素方法与存放元素方法必须一样。

  • 线性探查法:如果位置i被占用,则探查i+1,i+2,……
  • 二次探查法:如果位置i被占用,则探查i+12,i-12,i+22,i-22,……
  • 二度哈希法:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,则尝试使用h2,h3,……

拉链法

image

通过链表,将存放在同一位置的元素进行串联起来

直接寻址表(k的key为k)

  1. 原理

将可能的关键字范围建成一个长度为该范围的列表,关键字存放位置的下标即为该关键字的值(eg:2存放在下标为2的列表中)

image

  1. 评价

优点:能够快速进行插入和删除操作

缺点:当关键字的域很大时,浪费空间;当域很多,而关键字较集中时(实际出现的key很少),浪费空间;无法处理关键字不为数字的情况

改进直接寻指标(哈希)(k的key为h(k))

  1. 思想
  • 构建一个大小为m的列表
  • 设置哈希函数h(k)使得关键域U映射在范围为[0,m-1]的列表中

哈希表的内部实现

#  链表功能的定义
class LinkList:
    # 定义结点(包括数据域和指针域)
    class Node:
        def __init__(self,element=None):
            self.element = element
            self.next = None
    # 迭代器的定义(必须有iter和next)
    class LinkListIterator:
        def __init__(self,head):
            self.head = head
        def __iter__(self):
            return self
        def __next__(self):
            if self.head:
                cur_node = self.head
                self.head = cur_node.next
                return cur_node.element
            else:
                raise StopIteration
    # 链表的元素添加
    def __init__(self,iterable=None):
        self.head = None
        self.tail = None
        if iterable:
            self.extend(iterable)
    # 将元素进行拆分成单个元素
    def extend(self,iterable):
        for obj in iterable:
            self.append(obj)
    # 对单个元素进行添加
    def append(self,obj):
        s = LinkList.Node(obj)
        if self.head:
            self.tail.next = s
            self.tail = s
        else:
            self.head = s
            self.tail = s
    # 对单个元素进行寻找
    def find(self,obj):
        for i in self:
            if i == obj:
                return True
        else:
            return False
    # 设置链表迭代器
    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 h(self,k):
        return k % self.size
    # 元素的插入
    def insert(self,k):
        key = self.h(k)
        if self.T[key].find(k):
            print("Duplicated Insert.")
        else:
            self.T[key].append(k)
hash = HashTable()
hash.insert(0)
hash.insert(1)
hash.insert(3)
hash.insert(102)
print(hash.T)

哈希表的应用

  • 字典和集合都是通过哈希表来实现的

  • 安全性上(将数据映射到128位的哈希值)

    • md5算法

    • SHA2算法(无法反推)

定义

树是一种非线性(一对多)且可以递归定义的数据结构

概念

  • 根结点、叶子结点

    • 根结点:第一个结点(无父结点)
    • 叶子结点:无孩子的结点(无度的结点)
  • 树的深度(高度)

  • 树的度

    整个结点中有最大度结点的度(结点的度:一个结点的分叉数)

树的实例

# 模拟文件系统(linus)
# 定义结点
class Node:
    def __init__(self,name):
        self.name = name
        self.type = "dir"
        self.childen = []     # 存放该结点的孩子结点
        self.parent = None      # 存放该结点的父结点
    def __repr__(self):
        return self.name
# 树结构
class FileSystemTree:
    def __init__(self):				# 初始化根目录
        self.root = Node("/")
        self.now = self.root
    def mkdir(self,name):			# 文件的创建
        if name[-1] != "/":
            name += "/"
        newNode = Node(name)
        self.now.childen.append(newNode)
        newNode.parent = self.now
    def ls(self):					# 对当前文件的字文件进行查询
        return self.now.childen
    def cd(self,name):				# 跳转文件
        if name[-1] != "/":
            name += "/"
        for Name in self.now.childen:
            if Name.name == name:
                self.now = Name
                return
        raise ValueError("invalid dir")
file = FileSystemTree()
file.mkdir("bin")
file.mkdir("user")
file.cd("user")
file.mkdir("python")
print(file.ls())

二叉树

定义

  • 每个结点的度都大于大于等于2
  • 有左右结点孩子区分

存储形式

  • 若为完全二叉树可采用顺序存储方式(如排序算法中的堆排序)
  • 若不为完全二叉树可采用链式存储方式

结点的定义

class Node:
    def __init__(self,name):
        self.name = name
        self.lchild = None      # 左孩子
        self.rchild = None      # 右孩子
    def __repr__(self):         # 定义输出形式,默认输出地址
        return self.name
a = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
e = Node("e")
root = a
a.lchild = c
a.rchild = b
b.rchild = d
print(root.rchild.rchild)

二叉树的遍历

遍历方式(主要思想:前中后序采用递归,层次采用队列)

  • 前序遍历:EACBDGF
  • 中序遍历:ABCDEGF
  • 后续遍历:BDCAFGE
  • 层次遍历:EAGCFBD

例子:
image

# 定义结点
class Node:
    def __init__(self,element):
        self.element = element
        self.lchild = None
        self.rchild = None
# 初始化各结点
e = Node("E")
a = Node("A")
g = Node("G")
c = Node("C")
f = Node("F")
b = Node("B")
d = Node("D")
# 结点关系存放
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e
# 递归遍历
# 前序遍历
def pre_order(root):
    if root:                            # 遍历条件
        print(root.element,end="")      # 前序先打印
        pre_order(root.lchild)          # 遍历左孩子
        pre_order(root.rchild)          # 遍历右孩子
print("前序遍历为:",end="")
pre_order(root)
print()
# 中序遍历
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.element,end="")
        in_order(root.rchild)
print("中序遍历为:",end="")
in_order(root)
print()
# 后序遍历
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.element,end="")
print("后序遍历为:",end="")
post_order(root)
print()
# 层次遍历(队列思想)
from collections import deque
def level_order(root):
    queue = deque()                         # 创建一个队列
    if root:                                # 若存在根节点
        queue.append(root)                  # 将根节点先入队
        while len(queue)>0:                 # 判断队列是否还存在结点(不存在则所有元素全输出完毕)
            node = queue.popleft()          # 存在出队列,保存该结点(用以寻找其左右孩子)
            print(node.element,end="")      # 出队列的元素打印
            if node.lchild:                 # 判断出队列元素是否存在左孩子
                queue.append(node.lchild)   # 存在则入队
            if node.rchild:                 # 判断出队列元素是否存在右孩子
                queue.append(node.rchild)   # 存在则入队
print("层次遍历为:",end="")
level_order(root)

二叉搜索树(BST)

定义

所有左子树的值小于等于根节点,所有右子树的值大于等于根节点

操作

  • 查询
# 二叉树插入算法
# 定义二叉树结点
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):      # li为待插入元素的数组
        self.root = None
        if li:                  # 内部进行插入比较方便
            for i in li:
                self.insert_no_rec(i)
    # 递归思想插入结点(效率会慢)
    def insert(self,node,val):      # node判断所插入的位置是否为空,val为待插入结点的值
        if not node:
            node = BiTreeNode(val)          # 当找到的位置为空时,为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   # 判断该二叉树是否存在根节点,p为指针,指向每个结点的位置
        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 pre_order(self,node):
        if node:
            self.pre_order(node.lchild)
            print(node.data, end="")
            self.pre_order(node.rchild)
            
            
            
    # 二叉树的查询
    # 递归思想
    def query(self,node,val):
        if not node:
            return None
        else:
            if node.data > val:
                return self.query(node.lchild,val)  # 只需要进入递归函数,递归函数执行完成后无需再往下执行原本的代码行(有return)
            elif node.data < val:
                return self.query(node.rchild,val)  # 没有return返回不了True or None
            else:
                return True
    # 非递归思想
    def query_no_rec(self,val):
        p = self.root
        while p:
            if p.data > val:
                p = p.lchild
            elif p.data < val:
                p = p.rchild
            else:
                return True
        return None

    
    
import random
li = list(range(0,500,2))
random.shuffle(li)
tree = BST(li)
# tree.pre_order(tree.root)
print(tree.query_no_rec(6))
print(tree.query(tree.root,6))
  • 插入
# 二叉树插入算法
# 定义二叉树结点
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):      # li为待插入元素的数组
        self.root = None
        if li:                  # 内部进行插入比较方便
            for i in li:
                self.insert_no_rec(i)
    # 递归思想插入结点(效率会慢)
    def insert(self,node,val):      # node(用来递归的)判断所插入的位置是否为空,val为待插入结点的值
        if not node:
            node = BiTreeNode(val)          # 当找到的位置为空时,为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   # 判断该二叉树是否存在根节点,p为指针,指向每个结点的位置
        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 pre_order(self,node):
        if node:
            self.pre_order(node.lchild)
            print(node.data, end="")
            self.pre_order(node.rchild)
import random
li = list(range(50))
random.shuffle(li)
tree = BST(li)
tree.pre_order(tree.root)
  • 删除
    • 三种情况(都要判断删除的结点是否为根节点)
      • 删除的对象为叶子结点:直接删除
      • 删除的对象有一个孩子结点:将孩子给删除对象的父亲结点,再对对象进行删除(注意,当删除的是根结点时,要重新赋值根节点root)
      • 删除的对象有两个孩子结点:找到左孩子的最大结点/右孩子的最小结点替换删除的结点,转化为删除找到的那个结点
# 二叉树插入算法
# 定义二叉树结点
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):      # li为待插入元素的数组
        self.root = None
        if li:                  # 内部进行插入比较方便
            for i in li:
                self.insert_no_rec(i)
    # 递归思想插入结点(效率会慢)
    def insert(self,node,val):      # node判断所插入的位置是否为空,val为待插入结点的值
        if not node:
            node = BiTreeNode(val)          # 当找到的位置为空时,为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   # 判断该二叉树是否存在根节点,p为指针,指向每个结点的位置
        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 pre_order(self,node):
        if node:
            self.pre_order(node.lchild)
            print(node.data, end="")
            self.pre_order(node.rchild)
    # 二叉树的查询
    # 递归思想
    def query(self,node,val):
        if not node:
            return None
        else:
            if node.data > val:
                return self.query(node.lchild,val)  # 只需要进入递归函数,递归函数执行完成后无需再往下执行原本的代码行(有return)
            elif node.data < val:
                return self.query(node.rchild,val)  # 没有return返回不了True or None
            else:
                return node
    # 非递归思想
    def query_no_rec(self,val):
        p = self.root
        while p:
            if p.data > val:
                p = p.lchild
            elif p.data < val:
                p = p.rchild
            else:
                return p
        return None
    # 删除算法
    def __delete_condition_1_(self,node):   # 情况一:当删除的是叶子节点时
        if not node.parent:                 # 当该叶子结点是根节点时
            self.root = None
        elif node == node.parent.lchild:    # 判断该叶子结点是该结点父亲的左孩子还是右孩子
            node.parent.lchild = None
        else:
            node.parent.rchild = None
    def __delete_condition_21_(self,node):    # 情况二1:删除的结点结点只有一个孩子(左孩子)
        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 __delete_condition_22_(self,node):     # 情况二2:删除的结点结点只有一个孩子(右孩子)
        if not node.parent:
            self.root = node.rchild
            node.rchild.parent = None
        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):
        node = self.query_no_rec(val)
        if not self.root and not node:          # 判断是否存在树以及是否有删除的该结点
            return False
        if not node.lchild and not node.rchild: # 情况一:当为叶子节点时
            self.__delete_condition_1_(node)
        elif not node.rchild:                   # 情况二1:当只有一个左孩子时
            self.__delete_condition_21_(node)
        elif not node.lchild:                   # 情况二2:当只有一个右孩子时
            self.__delete_condition_22_(node)
        else:                                   # 情况三:左右孩子都有时
            min_lchild = node.rchild            # 找到删除结点右子树的最小结点
            while min_lchild.lchild:
                min_lchild = min_lchild.lchild
            node.data = min_lchild.data         # 直接将删除部分的数据域换成替换的数据域(好处,不用再重新指回左右孩子)
            if min_lchild.rchild:               # 当替换的结点有右孩子时
                self.__delete_condition_22_(min_lchild)
            else:                               # 当替换的结点为叶子节点时
                self.__delete_condition_1_(min_lchild)

bst = BST([2,5,3,4,1,9,7,8,6])
bst.pre_order(bst.root)
print()
bst.delete(4)
bst.delete(8)
bst.pre_order(bst.root)

AVL树(不是算法考察重点)

二叉树平均时间效率为O(logn),有时会出现极端情况(即形状呈现线性结构)。解决措施:随机化插入、AVL树
image

定义

是一棵自平衡的二叉搜索树

性质

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

旋转

  • 插入一个新的结点时,可能会破坏AVL树的平衡,可通过旋转操作来进行修正
  • 插入的结点只可能导致其中树的一半造成不平衡想象,且插入后结点的高度差不超过3

不平衡的四种情况

  • 左左:右旋
    image

  • 右右:左旋
    image

  • 左右:左右旋
    image

  • 右左:右左旋
    image

旋转的实现

from main import BiTreeNode,BST  # 导入先前写的二叉树(结点的生成和二叉树的遍历等方法)
class AVLNode(BiTreeNode):
    def __init__(self,data):
        BiTreeNode.__init__(self,data)
        self.bf = 0                 # 在二叉树结点属性的基础上加上一个结点左右孩子的高度差变量
class AVLTree(BST):
    def __init__(self,li = None):
        BST.__init__(li)

    def rotate_right(self,p,c):      # 不平衡情况一根左左:右转(p为由下往上找第一个bf大于1的结点,c则为该分支中p的下一个结点)
        s2 = c.rchild                   # 若c(代替根节点,成为根节点的结点)存在右孩子,则把右孩子给根节点,根节点变为c的右孩子
        if s2:
            p.lchild = s2
            s2.parent = p
        c.rchild = p
        p.parent = c
        p.bf = 0                    # 更改变化后各节点的高度差
        c.bf = 0
    def rotate_left(self,p,c):      # 不平衡情况二根右右:左转(与情况一类似)
        s2 = c.lchild
        if s2:
            p.rchild = s2
            s2.parent = p
        c.lchild = p
        p.parent = c
        p.bf = 0
        c.bf = 0
    def rotate_left_right_(self,p,c):   # 不平衡情况三根左右:左右转(p为由下往上找第一个bf大于1的结点,c则为该分支中p的下一个结点)
        g = c.rchild                    # 找到g(替换根节点,成为根节点的结点)
        # 判断待成为根节点的结点是否存在左右孩子(左孩子成为其父结点的右孩子,父结点成为其左孩子;右孩子成为根节点的左孩子,根结点成为其右孩子)
        if g.lchild:
            g.lchild.parent = c
            c.rchild = g.lchild
        g.lchild = c
        c.parent = g
        if g.rchild:
            g.rchild.parent = p
            p.lchild = g.rchild
        g.rchild = p
        p.parent = g
        # 判断待成为根节点的结点原始的高度差,若为1,则右孩子与根节点的右孩子高度差一样,左孩子比父结点左孩子的高度差小1;若为-1,则相反;
        # 若为零,则在插入过程中,插入的是g(待成为根节点的结点),各结点的左右孩子高度差为0
        if g.bp > 0:
            c.bp = -1
            p.bp = 0
        elif g.bp < 0:
            c.bp = 0
            p.bp = 1
        else:
            c.bp = 0
            p.bp = 0
    def rotate_right_left(self,p,c):        # 不平衡情况四根右左:右左转(与情况三类似)
        g = c.lchild
        if g.lchild:
            g.lchild.parent = p
            p.rchild = g.lchild
        g.lchild = p
        p.parent = g
        if g.rchild:
            g.rchild.parent = c
            c.lchild = g.rchild
        g.rchild = c
        c.parent = g
        if g.bp > 0:
            p.bp = -1
            c.bp = 0
        elif g.bp < 0:
            p.bp = 0
            c.bp = 1
        else:
            p.bp = 0
            c.bp = 0
        # g.bp为原本的数,不改变

插入操作

  1. 原理思想:当插入在左(右)子树上,该结点的父结点的balance factor的值-(+)1,若从左(右)子树上到(子)树的根节点,则该根节点-(+)1,当中间balance factor的值有超过1时,进行旋转操作,当旋转后的根节点不为0时,进行进行向上的balance factor值的变化,直到达到整棵树的根节点或者当有个结点-(+)1为0时,停止向上更改balance factor。每插入一个结点都要更改其向上分支的balance factor值

B树(B-Tree)(二叉搜索树扩展应用)

image

定义

B树是一棵自平衡的多路搜索树。常用于数据库的索引

原理

如图:一块地方存放两个数据域,37,25,(分2+1个分支),当新加入的数据域小于17,则存放在左边,若大于17小于35时,存放在中间,若大于35时,则存放在右边。

posted @ 2023-10-30 20:31  byyya  阅读(75)  评论(0)    收藏  举报