Day 75 数据结构

数据结构

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

数据结构的分类

数据结构按照其逻辑结构可分为线性结构、树结构、图结构

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

树结构:数据结构中的元素存在一对多的相互关系

图结构:数据结构中的元素存在多对多的相互关系

使用一般的列表结构即可实现栈

进栈:li.append

出栈:li.pop

取栈顶:li[-1]

 

 

 

栈的应用——括号匹配问题

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

()()[]{} 匹配

([{()}]) 匹配

[]( 不匹配

[(]) 不匹配

def match(s):
    match = {'}': '{', ']': '[', ')': '('}
    li = []
    for ch in s:
        if ch in {'{', '(', '['}:
            li.append(ch)
        else:  # ch in {'}',')',']'}
            if len(li) == 0:
                return False
            elif len(li) > 0 and li[-1] == match[ch]:
                li.pop()
            else:  # li[-1] not in match
                return False
    if len(li):
        return False
    else:
        return True


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

队列

队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。

进行插入的一端称为队尾(rear),插入动作称为进队或入队

进行删除的一端称为队头(front),删除动作称为出队

队列的性质:先进先出(First-in, First-out)

 

class Queue:
    def __init__(self, size=100):
        self.size = size
        self.queue = [0 for _ in range(100)]
        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 ImportError('队列已满')

    def pop(self):
        if not self.is_empth():
            self.front = (self.front + 1) % self.size
            return self.queue[self.front]
        else:
            raise ImportError('队列已空')

    # 判断队空
    def is_empth(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.is_filled())
print(q.pop())
print(q.pop())
print(q.pop())
print(q.pop())
print(q.push(15))
print(q.pop())
View Code

 

双向队列

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

双向队列的基本操作: 队首进队、队首出队、队尾进队、队尾出队

队列的实现

from collections import deque
q = deque([1,2,3,4,5],5) # 非空对列 长度为 5,长度大于 5 时,会自动八队首移出
q.append(7)         # 队尾进队
print(q.popleft())  # 队首出队

q.appendleft(1)     # 队首进队
print(q.pop())      # 队尾出对

# 读取文件中后 5 行数据
def tail(num):
    with open('./info.txt','r',encoding='utf-8')as f:
        q = deque(f,num)
        return q

for i in tail(5):
    print(i,end='')
View Code

 

队列的实现方式——环形队列

 

 

 

 栈和队列的应用——迷宫问题

 

栈——深度优先搜索

 

# 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, 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:
        curCode = stack[-1]  # 当前节点
        if curCode[0] == x2 and curCode[1] == y2:
            # 走到了终点
            for p in stack:
                print(p)
            return True
        # 查找 4 个方向
        for dir in dirs:
            nextCode = dir(curCode[0], curCode[1])
            if maze[nextCode[0]][nextCode[1]] == 0:
                stack.append(nextCode)
                maze[nextCode[0]][nextCode[1]] = 2  # 表示已经走过了
                break
        else:
            maze[nextCode[0]][nextCode[1]] = 2
            stack.pop()

    else:
        print('没有路')
        return False

maze_path(1,1,8,8)
View Code

 

队列——广度优先搜索

 

from collections import deque

# 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, 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):
    real_path = []
    i = len(path) - 1
    while i >= 0:
        real_path.append(path[i][0:2])
        i = path[i][2]
    real_path.reverse()
    for node in real_path:
        print(node)


def maze_path_duque(x1, y1, x2, y2):
    queue = deque()
    path = []
    queue.append((x1,y1,-1))    # 设置起点坐标
    while len(queue) > 0:        # 当队列不为空循环
        cur_node = queue.popleft()
        path.append(cur_node)
        if cur_node[0] == x2 and cur_node[1] == y2:
            print_r(path)     # 到达终点 打印坐标
            return True

        for dir in dirs:
            next_node = dir(cur_node[0],cur_node[1])
            if maze[next_node[0]][next_node[1]] == 0:
                queue.append((next_node[0],next_node[1],len(path) - 1))
                maze[next_node[0]][next_node[1]] = 2  # 走过的路径

    return False

maze_path_duque(1,1,8,8)
View Code

链表

 

 

 

创建与遍历链表

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


# a = Node(1)
# b = Node(2)
# a.next = b
# print(a.next.item)

# 头插法
def create_linklist_head(li):
    head = Node(li[0])      # 创建空列表,设置头节点
    for val in li[1:]:
        node = Node(val)
        node.next = head
        head = node
    return head

# 尾插法
def create_linklist_tail(li):
    head = Node(li[0])
    tail = head
    for val in li[1:]:
        node = Node(val)
        tail.next = node
        tail = node
    return head


def look(lk):
    while lk:
        print(lk.item,end=',')
        lk = lk.next

# lk_head = create_linklist_head([1,2,3,4,5])
lk_tail = create_linklist_tail([1,2,3,4,5])
print(look(lk_tail))
View Code

链表的插入和删除

 

 

 

 

 

 

双链表

 

 

双链表的插入与删除

 

 

 

 

 

 

链表在插入和删除的操作上明显快于顺序表

链表的内存可以更灵活的分配,试利用链表重新实现栈和队列

链表这种链式存储的数据结构对树和图的结构有很大的启发性

 

哈希表

哈希表一个通过哈希函数来计算数据存 储位置的数据结构,通常支持如下操作:

insert(key, value):插入键值对

(key,value) get(key):如果存在键为key的键值对则返回其value,否则返回空值

delete(key):删除键为key的键值对

直接寻址表

 

 

哈希

 

 

 

 

哈希冲突

 

 

 

 

 

 

 

 

 哈希表——常见哈希函数

除法哈希法: h(k) = k % m

乘法哈希法: h(k) = floor(m*(A*key%1))

全域哈希法: ha,b(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

    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))+">>"

# 类似于集合的结构
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))
View Code

哈希表的应用——集合与字典

字典与集合都是通过哈希表来实现的。

a = {'name': 'Alex', 'age': 18, 'gender': 'Man'}

使用哈希表存储字典,通过哈希函数将字典的键映射为下标。假设h('name') = 3, h('age') = 1, h('gender') = 4,则哈希表存储为[None, 18, None, 'Alex', 'Man']

如果发生哈希冲突,则通过拉链法或开发寻址法解决

 

Python对字典的关键字有什么要求?如何使一个自定义的类的对象成为字典的键?

Python对集合中的元素有什么要求?如何利用集合对大量的对象去重?

 

树的实例:模拟文件系统

class Node:
    def __init__(self, name, type='dir'):
        self.name = name
        self.type = type   #"dir" or "file"
        self.children = []
        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):
        # 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())
View Code

 

二叉树

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

二叉树遍历

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')

root = e
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

# print(root.rchild.rchild.data)


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

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

# middle_order(root)

# 后序遍历
def back_order(root):
    if root:
        middle_order(root.lchild)
        print(root.data, end=',')
        middle_order(root.rchild)

# middle_order(root)

# 层级遍历
def level_order(root):
    qu = deque()
    qu.append(root)
    while len(qu)>0:
        node = qu.popleft()
        print(node.data,end=',')
        if node.lchild:
            qu.append(node.lchild)
        if node.rchild:
            qu.append(node.rchild)


level_order(root)
View Code

 

二叉树:插入、查询、删除

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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)
            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 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

    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.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: #1. 叶子节点
                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_node = node.rchild
                while min_node.lchild:
                    min_node = min_node.lchild
                node.data = min_node.data
                # 删除min_node
                if min_node.rchild:
                    self.__remove_node_22(min_node)
                else:
                    self.__remove_node_1(min_node)




#
#
# 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)
View Code

 

AVL树

 

 

 

AVL树——插入

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

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

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

AVL插入——右旋

 

 

 

AVL插入——左旋

AVL插入——右旋-左旋

 

 

AVL插入——左旋-右旋

 

 

 二叉搜索树扩展应用——B树

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

 

posted @ 2020-05-19 18:22  亦双弓  阅读(180)  评论(0编辑  收藏  举报