Python数据结构与算法(十、集合——基于两种不同的底层实现及其性能比较)

保证一周更两篇吧,以此来督促自己好好的学习!代码的很多地方我都给予了详细的解释,帮助理解。好了,干就完了~加油!
声明:本python数据结构与算法是imooc上liuyubobobo老师java数据结构的python改写,并添加了一些自己的理解和新的东西,liuyubobobo老师真的是一位很棒的老师!超级喜欢他~
如有错误,还请小伙伴们不吝指出,一起学习~
No fears, No distractions.


一、集合简述

  1. 回忆我们上一小节实现的二分搜索树,它不能盛放重复元素,因此是一个非常好的实现“集合”的底层数据结构。
  2. 本节实现一个基于二分搜索树的集合和一个基于链表的集合,并对两者的性能进行比较。
  3. 接口:
    为充分展现函数功能,这里我用C++的函数表示法来写。
    void add(E) 添加元素E(注意不能添加重复元素,也就是说添加重复元素的时候什么都没发生就可以了)
    void remove(E) 删除元素E(若根本不包含E,什么也不做)
    bool contains(E) 判断是否包含元素E
    int getSize() 获取集合的大小(有效元素个数)
    bool isEmpty() 判断集合是否为空
  4. 典型应用: 客户统计,IP统计,一本书的词汇量统计
  5. 基于二分搜索树的集合是有顺序性的,而基于链表实现的集合是无顺序性的,但是一般不用链表来实现无顺序性的集合,而用哈希表,哈希表后面会讲到。

二、基于二分搜索树的集合

1. 实现

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data:             2018-10-13   15:19 pm
# Python version:   3.6

from binarysearchTree.bst import BST   # 上一节我们写的二分搜索树在bst.py文件中


class BstSet:
    def __init__(self):
        """用二分搜索树作为底层实现的集合的构造函数"""
        self._data = BST()   # 一个空的二分搜索树

    def getSize(self):
        """
        Description: 返回集合的大小
        Retures:
        集合大小
        """
        return self._data.getSize()  # 直接调用二分搜索树的getSize方法

    def isEmpty(self):
        """
        Description: 判断集合是否为空
        Returns:
        bool值,空为True
        """ 
        return self._data.isEmpty()  # 直接调用二分搜索树的isEmpty方法

    def add(self, elem):
        """
        Description: 向集合中添加元素elem,时间复杂度:O(logn)
        Params:
        - elem: 待添加的元素
        """
        self._data.add(elem)         # 直接调用二分搜索树的add方法,注意我们实现的二分搜索树是不包含重复元素的哦,满足集合的概念。不清楚的话回去看一下二分搜索树的add方法就好了

    def contains(self, elem):
        """
        Description: 查询集合中是否存在元素elem,时间复杂度:O(logn)
        Params:
        - elem: 待查询元素
        Returns:
        bool值,存在为True
        """
        return self._data.contains(elem)  # 直接调用二分搜索树的contains方法

    def remove(self, elem):
        """
        Description: 将elem从集合中删除,注意若elem不存在,我们什么也不做。时间复杂度:O(logn)
        Params:
        - elem: 待删除元素
        """
        self._data.remove(elem)    # 直接调用二分搜索树的remove方法

    def printSet(self):
        """
        Description: 打印集合,这里采用中序遍历的方法来打印集合元素。随便选啦,前中后以及层序都可以的。
        """
        self._data.inOrder()    # 直接调用二分搜索树的inOrder方法,这样打印出来的元素是从小到达排列的!

2. 测试

from SET.bstSet import BstSet  # 我们的以二叉搜索树为底层的集合写在了bstSet.py文件中
import numpy as np
np.random.seed(7)

test_set = BstSet()   # 创建一个空的集合
print('初始化时是否为空?', test_set.isEmpty())
for i in range(10):   # 10次添加操作
    test_set.add(np.random.randint(100))
print('10次添加操作后集合元素为:')
test_set.printSet()
print('Size: ', test_set.getSize())
print('此时集合中是否含有元素47?', test_set.contains(47))
print('-----------------------------------------------------')
print('添加一个重复元素47后,集合所包含的全部元素以及集合大小为:')
test_set.add(47)
test_set.printSet()
print()
print('Size: ', test_set.getSize())
print('-----------------------------------------------------')
print('删除元素47后,集合所包含的全部元素以及集合大小为:')
test_set.remove(47)
test_set.printSet()
print()
print('Size: ', test_set.getSize())
print('-----------------------------------------------------')

3. 输出

初始化时是否为空? True
10次添加操作后集合元素为:
14 23 25 47 57 67 68 83 92 Size:  9
此时集合中是否含有元素47True
-----------------------------------------------------
添加一个重复元素47后,集合所包含的全部元素以及集合大小为:
14 23 25 47 57 67 68 83 92
Size:  9
-----------------------------------------------------
删除元素47后,集合所包含的全部元素以及集合大小为:
14 23 25 57 67 68 83 92
Size:  8
-----------------------------------------------------

三、基于链表的集合

1. 实现

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data:             2018-10-13   15:19 pm
# Python version:   3.6

from linklist.linkedlist import LinkedList # 我们以前实现的链表类写在linkedlist.py文件中

class LinkedListSet:
    def __init__(self):
        """用链表作为底层数据结构实现的集合的构造函数"""
        self._data = LinkedList()   # 空链表

    def getSize(self):
        """
        Description: 返回集合的大小
        Retures:
        集合大小
        """
        return self._data.getSize()  # 调用链表的getSize方法

    def isEmpty(self):
        """
        Description: 判断集合是否为空
        Returns:
        bool值,空为True
        """ 
        return self._data.isEmpty()

    def add(self, elem):
        """
        Description: 向集合中添加元素elem,时间复杂度:O(n)
        Params:
        - elem: 待添加的元素
        """
        if not self._data.contains(elem):  # 注意以前实现的链表类是可以包含重复元素的,所以在这里要做一个判断,这步的时间复杂度是O(n)的
            self._data.addFirst(elem)      # 在链表头部添加元素,因为时间复杂度是O(1)的!

    def contains(self, elem):
        """
        Description: 查询集合中是否存在元素elem,时间复杂度:O(n)
        Params:
        - elem: 待查询元素
        Returns:
        bool值
        """
        return self._data.contains(elem)  # 直接调用链表的contains方法

    def remove(self, elem):
        """
        Description: 将elem从集合中删除,注意若elem不存在,我们什么也不做。时间复杂度:O(n)
        Params:
        - elem: 待删除元素
        """
        self._data.removeElement(elem)  # 直接调用链表的remove方法

    def printSet(self):
        """
        Description: 打印集合,这里采用中序遍历的方法来打印集合元素。随便选啦,前中后以及层序都可以的。
        """
        self._data.printLinkedList()  # 直接调用链表的printLinkedList方法。

2. 测试

from SET.linklistset import LinkedListSet  # 我们的以链表为底层的集合写在了linklistset.py文件中
import numpy as np 
np.random.seed(7)

test_set = LinkedListSet()   # 创建一个空的集合
print('初始化时是否为空?', test_set.isEmpty())
for i in range(10):   # 10次添加操作
    test_set.add(np.random.randint(100))
print('10次添加操作后集合元素为:')
test_set.printSet()
print('此时集合中是否含有元素47?', test_set.contains(47))
print('-----------------------------------------------------')
print('添加一个重复元素47后,集合所包含的全部元素以及集合大小为:')
test_set.add(47)
test_set.printSet()
print('-----------------------------------------------------')
print('删除元素47后,集合所包含的全部元素以及集合大小为:')
test_set.remove(47)
test_set.printSet()
print('-----------------------------------------------------')

3. 输出

初始化时是否为空? True
10次添加操作后集合元素为:
表头: 14  57  92  23  83  67  25  68  47
Size:  9
此时集合中是否含有元素47True
-----------------------------------------------------
添加一个重复元素47后,集合所包含的全部元素以及集合大小为:
表头: 14  57  92  23  83  67  25  68  47
Size:  9
-----------------------------------------------------
删除元素47后,集合所包含的全部元素以及集合大小为:
表头: 14  57  92  23  83  67  25  68
Size:  8
-----------------------------------------------------

四、两个不同底层实现的集合的性能对比

1. 测试代码设计

from queue.testQueueAndLoopqueue import count_time   # 记录操作所用时间的装饰器
from SET.bstSet import BstSet  # 我们的以二叉搜索树为底层的集合写在了bstSet.py文件中
from SET.linklistset import LinkedListSet  # 我们的以链表为底层的集合写在了linklistset.py文件中
import numpy as np 

np.random.seed(7)
bst_set = BstSet() 
linkedlinst_set = LinkedListSet()
nums = 500000   # 操作次数设为500000

@count_time
def compute_bst_set():
    global nums, bst_set
    for i in range(nums):
        bst_set.add(np.random.randint(2000))
    for i in range(nums):
        bst_set.remove(np.random.randint(2000))

@count_time
def compute_linkedlist_set():
    global nums, linkedlinst_set
    for i in range(nums):
        linkedlinst_set.add(np.random.randint(2000))
    for i in range(nums):
        linkedlinst_set.remove(np.random.randint(2000))


if __name__ == '__main__':
    print('基于二分搜索树所实现的Set: ')
    compute_bst_set()
    print('基于链表所实现的Set: ')
    compute_linkedlist_set()

2. 输出

基于二分搜索树所实现的Set:
共用时: 4.507374 秒
基于链表所实现的Set:
共用时: 54.803042

五、总结

当操作数增加到一定程度后,O(n)的时间复杂度要比O(logn)的时间复杂度慢很多!所以说树是一种时间性能很好的数据结构,必须要掌握!

若有还可以改进、优化的地方,还请小伙伴们批评指正!

你可能感兴趣的:(集合,数据结构,python,算法,python数据结构与算法,Python数据结构与算法)