python实现数据结构2

重要的数据结构2

Posted by QQL on October 7, 2018

数据结构

链表

链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
定义链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串成一个链表。
单向链表图示.png
创建链表
头插法
尾插法

class Node:
    """
    创建链表
    """
    def __init__(self, item):
        self.item = item
        self.next = None

def create_linklist_head(li):
    """
    头插法
    :param li:
    :return:
    """
    head =Node(li[0])  # 列表第一个值为头
    for element in li[1:]:  # 列表除了头之外的元素
        node = Node(element)  # 新的链表元素
        node.next = head  # 新的链表插到头部,
        head = node  # 头部为新的链表元素
    return head

def create_linklist_tail(li):
    head = Node(li[0])  # 初始头结点
    tail = head  # 开始尾结点和头结点相同
    for element in li[1:]:
        node = Node(element)
        tail.next = node  # 新的链表插到尾部
        tail = node  # 尾部位新的链表元素
    return head


def print_linklist(lk):
    """
    遍历链表
    :param lk:
    :return:
    """
    while lk:  # 当lk不为None时进行循环
        print(lk.item, end=',')
        lk = lk.next


lk_head = create_linklist_head([1,2,3,6,8,15])
lk_tail = create_linklist_tail([1,2,3,6,8,15])
print_linklist(lk_head)
print_linklist(lk_tail)

双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
双链表.png

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

链表在插入和删除的操作上明显快于顺序表
链表的内存可以更灵活的分配
链表这种链式存储的数据结构对树和图结构有很大启发

哈希表(散列表)

哈希表是通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
 * insert(key, value):插入键值对
 * get(key):如果存在键为key的键值对则返回value,否则返回空值
 * delete(key):删除键为key的键值对
哈希表是一种线性的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
hash表demo.png
注意:由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一位置上的情况,这种情况叫做哈希冲突
hash冲突.png

解决hash冲突

1.开放寻址法
开放寻址法.png
2.拉链法
拉链法.png
hash表的实现

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:  # 如果node不是空
                cur_node = self.node
                self.node = cur_node.next  # 更新node指向下一个链表
                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  # 接上原本尾巴的next
            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):
        # print调用__repr__。map(str, self)把可迭代对象转换成字符串
        return "<<" + ", ".join(map(str, self)) + ">>"  # map(func,iterable)通过函数映射可迭代对象


# 类似于集合的结构
class HashTable:
    def __init__(self, size=101):
        self.size = size  # hash表长度
        self.T = [LinkList() for i in range(self.size)]  # 存放下标的地方

    def h(self, k):
        # hash函数
        return k % self.size

    def insert(self, k):
        i = self.h(k)  # hash值
        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))

hash表的应用:
1.字典与集合都是通过hash表实现的
2.MD5曾经是密码学中常用的hash函数,把任意长度数据映射成128位的hash值
应用
 * 文件的hash值,用来验证下载的文件是否完整
 * 云存储服务商利用它判断用户要上传的文件是否已经存在到服务器上,从而实现秒传的功能,同时避免存储过多的相同文件
 * 如今安全性重要的场合推荐使用SHA-2等新的更安全的hash函数。应用:SHA-2比特币

树的概念python算法–堆排序学前必备知识
二叉树的性质(特性)
性质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 时为根,除外)
二叉树的链式存储
将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来链接
二叉树节点

class Node(object):
    """节点类"""
    def __init__(self, elem=-1, lchild=None, rchild=None):
        self.elem = elem  # 本身
        self.lchild = lchild  # 左孩子
        self.rchild = rchild  # 右孩子

二叉树的遍历(重点)

四种主要的遍历思想为
深度优先遍历
1.前序遍历:根结点 —> 左子树 —> 右子树

2.中序遍历:左子树—> 根结点 —> 右子树

3.后序遍历:左子树 —> 右子树 —> 根结点

广度优先遍历:  4.层次遍历:只需按层次遍历即可
遍历.png
面试题:通过二叉树前序遍历和中序遍历写出二叉树和它的后序遍历
1.前序遍历可以确认第一个元素为根
2.再到中序遍历中找到根的左右两侧子树
3.再通过前序遍历分出左右子树,找到首个左右子树,再重复1、2操作,检查每一个元素带入中序遍历中,找到左右子树的左边和右边得到对应的子树
4.得到二叉树,写出后序遍历,后序遍历最后一个元素为根元素,每一个子树的根为最后一个元素 左右子树不断划分
二叉树遍历(递归实现)

from collections import deque


class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None   # 左孩子
        self.rchild = None # 右孩子

a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")

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

root = e

def pre_order(root):
    """
    前序遍历
    :param root:
    :return:
    """
    if root:  # 是否有root值
        print(root.data, end=',')  # 先打印自己
        pre_order(root.lchild)  # 递归左子树
        pre_order(root.rchild)  # 递归右子树

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

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

def level_order(root):
    """
    层次遍历
    :param root:
    :return:
    """
    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)

###二叉搜索树
二叉搜索树是一颗二叉树且满足:左边的所有节点比父节点小,右边的所有节点比父节点大
二叉搜索树的操作:查询、插入、删除
      二叉搜索树.png

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)

二叉搜索树进行搜索的时间复杂度为O(logn)
最坏情况下,二叉搜索树可能非常偏斜,比如:所有节点都比根节点大,就会退化成线性结构
解决方案:1.随机化插入 2.AVL树

AVL树

AVL树:一颗自平衡二叉搜索树
AVL树性质:
 根的左右子树的高度之差的绝对值不能超过1
 根的左右子树都是平衡二叉树
AVL树.png
插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作进行修正
旋转操作:插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能改变。我们需要找到第一个破坏平衡条件的节点,称之为KK的两颗子树的高度差2
不平衡的出现可能有4中情况:
1.右旋: 左左
右旋.png
2.左旋: 右右
左旋.png
3.右旋-左旋: 右左
左右.png
3.左旋-右旋: 左右
右左.png

from bst 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__(self, li)

    def rotate_left(self, p, c):
        s2 = c.lchild
        p.rchild = s2
        if s2:
            s2.parent = p

        c.lchild = p
        p.parent = c

        p.bf = 0
        c.bf = 0
        return c

    def rotate_right(self, p, c):
        s2 = c.rchild
        p.lchild = s2
        if s2:
            s2.parent = p

        c.rchild = p
        p.parent = c

        p.bf = 0
        c.bf = 0
        return c

    def rotate_right_left(self, p, c):
        g = c.lchild

        s3 = g.rchild
        c.lchild = s3
        if s3:
            s3.parent = c
        g.rchild = c
        c.parent = g

        s2 = g.lchild
        p.rchild = s2
        if s2:
            s2.parent = p
        g.lchild = p
        p.parent = g

        # 更新bf
        if g.bf > 0:
            p.bf = -1
            c.bf = 0
        elif g.bf < 0:
            p.bf = 0
            c.bf = 1
        else:  # 插入的是g
            p.bf = 0
            c.bf = 0
        return g

    def rotate_left_right(self, p, c):
        g = c.rchild

        s2 = g.lchild
        c.rchild = s2
        if s2:
            s2.parent = c
        g.lchild = c
        c.parent = g

        s3 = g.rchild
        p.lchild = s3
        if s3:
            s3.parent = p
        g.rchild = p
        p.parent = g

        # 更新bf
        if g.bf < 0:
            p.bf = 1
            c.bf = 0
        elif g.bf > 0:
            p.bf = 0
            c.bf = -1
        else:
            p.bf = 0
            c.bf = 0
        return g

    def insert_no_rec(self, val):
        # 1. 和BST一样,插入
        p = self.root
        if not p:  # 空树
            self.root = AVLNode(val)
            return
        while True:
            if val < p.data:
                if p.lchild:
                    p = p.lchild
                else:  # 左孩子不存在
                    p.lchild = AVLNode(val)
                    p.lchild.parent = p
                    node = p.lchild  # node 存储的就是插入的节点
                    break
            elif val > p.data:
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = AVLNode(val)
                    p.rchild.parent = p
                    node = p.rchild
                    break
            else:  # val == p.data
                return

        # 2. 更新balance factor
        while node.parent:  # node.parent不空
            if node.parent.lchild == node:  # 传递是从左子树来的,左子树更沉了
                # 更新node.parent的bf -= 1
                if node.parent.bf < 0:  # 原来node.parent.bf == -1, 更新后变成-2
                    # 做旋转
                    # 看node哪边沉
                    g = node.parent.parent  # 为了连接旋转之后的子树
                    x = node.parent  # 旋转前的子树的根
                    if node.bf > 0:
                        n = self.rotate_left_right(node.parent, node)
                    else:
                        n = self.rotate_right(node.parent, node)
                        # 记得:把n和g连起来
                elif node.parent.bf > 0:  # 原来node.parent.bf = 1,更新之后变成0
                    node.parent.bf = 0
                    break
                else:  # 原来node.parent.bf = 0,更新之后变成-1
                    node.parent.bf = -1
                    node = node.parent
                    continue
            else:  # 传递是从右子树来的,右子树更沉了
                # 更新node.parent.bf += 1
                if node.parent.bf > 0:  # 原来node.parent.bf == 1, 更新后变成2
                    # 做旋转
                    # 看node哪边沉
                    g = node.parent.parent  # 为了连接旋转之后的子树
                    x = node.parent  # 旋转前的子树的根
                    if node.bf < 0:  # node.bf = 1
                        n = self.rotate_right_left(node.parent, node)
                    else:  # node.bf = -1
                        n = self.rotate_left(node.parent, node)
                        # 记得连起来
                elif node.parent.bf < 0:  # 原来node.parent.bf = -1,更新之后变成0
                    node.parent.bf = 0
                    break
                else:  # 原来node.parent.bf = 0,更新之后变成1
                    node.parent.bf = 1
                    node = node.parent
                    continue

            # 链接旋转后的子树
            n.parent = g
            if g:  # g不是空
                if x == g.lchild:
                    g.lchild = n
                else:
                    g.rchild = n
                break
            else:
                self.root = n
                break


tree = AVLTree([9, 8, 7, 6, 5, 4, 3, 2, 1])

tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)

AVL数的应用B树
B树是一颗自平衡的多路搜索树。常用于数据库的索引
B-Tree.png

发展路线

二叉树—->二叉搜索树(小的左/大的右,实现类似二分的效果)—-遇到偏斜严重的问题—->AVL树(保证自平衡,在插入或删除中需要维护平衡进行旋转)