第4章 数据结构算法

py内置数据结构算法常考

常用内置的算法和数据结构

sorted
list/set/dict/tuple

常用内置数据结构和算法

数据结构/算法 语言内置 内置库
线性结构 list(列表)/tuple(元组) array(数组, 不常用)/collections.namedtuple
链式结构 collections.deque(双端队列)
字典结构 dict(集合) collections.Counte(计数器)/OrderedDict(有序字典)
集合结构 set(集合)/frozenset(不可变集合)
排序算法 sorted
二分算法 bisect模块
堆算法 heapq模块
缓存算法 functools.lru_cached(Least Recent Used, py3)

有用过collections模块吗?
collections模块提供了一些内置数据结构的扩展
namedtuple
deque
Counter
OrderedDict
defaultdict

import collections

Point = collections.namedtuple('Point', 'x, y')

p = Point(1, 2)
print(p.x)
print(p.y)
print(p[0])
print(p[1])
#namedtuple让tuple属性可读

#deque可以方便地实现queue/stack
de = collections.deque()
de.append(1)
de.appendleft(0)
deque([0, 1])
de.pop()
de.popleft()
import collections

c = collections.Counter()
c = collections.Counter('abcab')

print(c)
print(c['a'])
print(c.most_common())
#需要计数器的地方使用Counter  

# !< out:
    Counter({'a': 2, 'b': 2, 'c': 1})
    2
    [('a', 2), ('b', 2), ('c', 1)]
import collections

od = collections.OrderedDict()

od['c'] = 'c'
od['a'] = 'a'
od['b'] = 'b'

print(list(od.keys()))
# !< out:
    ['c', 'a', 'b']
#LRU cache
#带有默认值的字典
import collections
dd = collections.defaultdict(int)

print(dd['a'])
dd['b'] += 1

print(dd)
# !< out:
    0
    defaultdict(<class 'int'>, {'a': 0, 'b': 1})

底层原理实现
py dict 底层结构

  • dict底层使用的哈希表
    为了支持快速查找使用了哈希表作为底层结构
    哈希表平均查找时间复杂度O(1)
    CPython解释器使用二次探查解决哈希冲突问题

哈希冲突和扩容是常考题

py list/tuple 区别

list vs tuple
都是线性结构 支持下标访问
list是可变对象, tuple保存的引用不可变
list没法作为字典的key, tuple可以(可变对象不可hash)

t = ([1], 2, 3)
t[0].append(1)

#保存的引用不可变指的是你没法替换掉这个对象
#但是如果对本身是一个可变对象,是可以修改
#这个引用指向的可变对象的

什么是LRUCache?
Least-Recently-Used替换掉最近最少使用的对象
缓存剔除策略,当缓存空间不够用的时候需要一种方式剔除key
常见的有 LRU,LFU等
LRU通过使用一个循环双端队列不断把最新访问的key放到表头实现

如何实现LRUCache?
字典用来缓存,循环双端链表用来记录访问顺序
利用py内置的dict+collections.OrderedDict实现

​ dict用来当做k/v键值对的缓存

​ OrderedDict用来实现更新最近访问的key

from collections import OrderedDict

class LRUCache:
    
    def __init__(self, capacity=128):
        self.od = OrderedDict()
        self.capacity = capacity
    
    def get(self, key): #每次访问更新最新使用的key
        if key in self.od:
            val = self.od[key]
            self.od.move_to_end(key)
            return val
        else:
            return -1
        
    def put(self, key, value): #更新k/v
        if key in self.od:
            del self.od[key]
            self.od[key] = value #更新key到表头
        else: #insert
            self.od[key] = value
            #判断当前容量是否已经满了
            if len(self.od) > self.capacity:
                self.od.popitem(last=False)
                
    #请实现LRUCache并编写单元测试

算法常考点

排序+查找 重中之重
常考排序算法:冒泡排序, 快速排序, 归并排序, 堆排序
线性查找, 二分查找等
能独立实现代码(手写) 能够分析时间空间复杂度
常用排序算法的时空复杂度

排序算法 最差时间分析 平均时间复杂度 稳定度 空间复杂度
冒泡排序 O(n^2) O(n^2) 稳定 O(1)
选择排序 O(n^2) O(n^2) 不稳定 O(1)
插入排序 O(n^2) O(n^2) 稳定 O(1)
快速排序 O(n^2) O(n*log2n) 不稳定 O(log2n) - O(n)
堆排序 O(n*log2n) O(n*log2n) 不稳定 O(1)

py数据结构常考题

常考题型
py web后端常考数据结构
常见的数据结构链表, 队列, 栈, 二叉树, 堆
使用内置结构实现高级数据结构, 比如内置的list/deque实现栈

​ Leetcode或者 <剑指offer> 上的常见题

常考数据结构之链表
链表有单链表, 双链表, 循环双端链表
如何使用Py来表示链表结构
实现链表常见操作, 比如插入节点, 反转链表, 合并多个链表等
Leetcode练习常见链表题目

数据结构之链表

#leetcode
# Definition for singly-linked list.
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while cur:
            nextnode = cur.next
            cur.next = pre
            pre = cur
            cur = nextnode
        return pre

常考数据结构之队列
队列(queue)是先进先出结构
如何使用py实现队列?

​ 实现队列的apend和pop操作,如何做到先进先出

​ 使用py的list或者collections.deque实现队列

Collections.deque 双端队列

#实现队列, 使用deque
from collections import deque

class Queue:
    def __init__(self):
        self.items = deque()

    def append(self, val):
        return self.items.append(val)

    def pop(self):
        return self.items.popleft()

    def empty(self):
        return len(self.items) == 0

def test_queue():
    q = Queue()
    q.append(0)
    q.append(1)
    q.append(2)
    print(q.pop())
    print(q.pop())
    print(q.pop())

test_queue()

常考数据结构之栈
栈是后进先出结构
如何使用py实现栈?
实现栈的push和pop操作,如何做到后进先出
同样可以用py list或者collections.deque实现栈

#请实现一个Stack
#借助内置的数据结构非常容易实现一个栈Stack, 后进先出
from collections import deque
class Stack(object):
    def __init__(self):
        self.deque = deque()    #或者用list
    
    def push(self, value):
        self.deque.append(value)
    
    def pop(self):
        return self.deque.pop()
   
#如何使用两个stack 实现队列 先进先出
#实现获取最小值栈minstack

常考数据结构之字典与集合
py dict/set底层都是哈希表
哈希表的实现原理, 底层其实就是一个数组
根据哈希函数快速定位一个元素, 平均查找O(1), 非常快
不断加入元素会引起哈希表重新开辟空间, 拷贝之前元素到新数组

哈希表如何解决冲突
链接法和开放寻址法
元素key冲突之后使用一个链表填充相同key的元素
开放寻址法是冲突之后根据一种方式(二次探查)寻找下一个可用的槽
cpython使用的是二次探查

常考数据结构之二叉树
先序, 中序, 后序遍历
先(根)序:先处理根, 之后是左子树, 然后是右子树
中(根)序:先处理左子树, 然后是根, 然后是右子树
后(根)序:先处理左子树, 然后是右子树, 最后是根

#数据结构之二叉树:
#树的遍历方式
#先序遍历 递归代码里先处理根
class BinTreeNode(object):
    def __init__(self, data, left=None, right=None):
        self.data, self.left, self.right = data, left, right
        
class BinTree(object):
    def __init__(self, root=None):
        self.root = root
    
    def preorder_trav(self, subtree):
        """先(根)序遍历"""
        if subtree is not None:
            print(subtree.data) #递归函数里先处理根
            self.preorder_trav(subtree.left)    #递归处理左子树
            self.preorder_trav(subtree.right)   #递归处理右子树
     #中序遍历 调整下把print(subtree.data)放中间就好       
    def inorder_trav(self, subtree):
        if subtree is not None:
            self.preorder_trav(subtree.left)
            print(subtree.data) #中序遍历放到中间就好
            self.preorder_trav(subtree.right)

常考数据结构之堆
堆其实是完全二叉树, 有最大堆和最小堆
最大堆:对于每个非叶子节点V, V的值都比它的孩子大
最大堆支持每次pop操作获取最大的元素, 最小堆获取最小元素

最大堆支持每次pop操作获取最大的元素, 最小堆获取最小元素

常见问题:用堆来完成topk问题, 从海量数字找寻找最大的k个
"""
class Topk:

    def __init__(self, iterable, k):
        self.minheap = []
        self.capacity = k
        self.iterable = iterable

    def push(self, val):
        if len(self.minheap) >= self.capacity:
            min_val = self.minheap[0]
            if val < min_val:   #可以直接>操作, 这里只显示跳过这个元素
                pass
            else:
                heapq.heapreplace(self.minheap, val) #返回并且pop堆顶最小值,
                                                     #推入新的val值并调整堆
        else:
            heapq.heappush(self.minheap, val)   #前面k个元素直接放入minheap

    def get_topk(self):
        for val in self.iterable:
            self.push(val)
        return self.minheap

def test():
    import random
    i = list(range(1000))   #这里可以是一个可迭代的元素, 节省内存
    random.shuffle(i)
    _ = Topk(i, 10)
    print(_.get_topk())

test()
# !< out:
[990, 991, 992, 995, 993, 998, 994, 996, 997, 999]

使用堆解决TOPK问题

py白板编程(手写代码)
手写算法题
刷题, Leetcode <剑指offer> github题解
如何准备
刷题
面试之前系统整理之前做过的题目,不要靠记忆而是真正理解掌握
打好基础是重点,面试可以刷常见题突击, 保存手感

面试前练习
刷题(leetcode + 剑指offer + 面经)
<剑指offer> 上常见题目用py是实现
把leetcode上常见分类题目刷一遍(github搜索leetcode分类)
常见排序算法和数据结构能手写

常考数据结构题--链表
链表涉及到指针操作较为复杂,容易出错, 经常用作考题
熟悉链表的定义和常见操作
常考题:删除一个链表节点 leetcode 237
合并两个有序链表 leetcode 21

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        root = ListNode(None)
        cur = root
        while l1 and l2:
            if l1.val < l2.val:
                node = ListNode(l1.val)
                l1 = l1.next
            else:
                node = ListNode(l2.val)
                l2 = l2.next
            cur.next = node
            cur = node
        if l1 is None:
            cur.next = l2
        else:
            cur.next = l1
        return root.next

二叉树
二叉树涉及到递归和指针操作, 常结合递归考察
二叉树的操作涉及到递归和指针操作
二叉树的镜像(反转二叉树)
如何层序遍历二叉树(广度优先) 102题
输出左右视角的二叉树结果

class TreeNode:
    def __init__(self, val, left, right):
        self.val, self.left, self.right = val, left, right

#leetcode 237   

栈与队列

后进先出 vs 先进先出

​ 熟练掌握用py的list 或者 collections.deque()实现栈和队列
​ 常考题:用栈实现队列(使用两个栈实现)
​ leetcode:implement - queue - using - stacks 232题

     |__|   |__|
     |__|   |__|
     |__|   |__|
     |__|   |__|
      S1     S2
栈2不为空直接pop 否则把栈1的所有元素放到栈2
然后执行栈2 pop操作

deque() 双端队列

from collections import deque

class Stack:
    def __init__(self):
        self.items = deque()

    def push(self, val):
        return self.items.append(val)

    def pop(self):
        return self.items.pop()

    def top(self): #return stack top value
        return self.items[-1]

    def empty(self):
        return len(self.items) == 0


class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.s1 = Stack()
        self.s2 = Stack()

    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        self.s1.push(x)

    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if not self.s2.empty():
            return self.s2.pop()
        while not self.s1.empty():
            val = self.s1.pop()
            self.s2.push(val)
        return self.s2.pop()

    def peek(self) -> int:
        """
        Get the front element.
        """
        if not self.s2.empty():
            return self.s2.top()
        while not self.s1.empty():
            val = self.s1.pop()
            self.s2.push(val)
        return self.s2.top()

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        return self.s1.empty() and self.s2.empty()

def test():
    q = MyQueue()
    q.push(1)
    q.push(2)
    q.push(3)
    print(q.pop())
    print(q.pop())
    print(q.pop())

test()

数据结构之堆

堆的常考题基本围绕在合并多个有序(数组/链表) Topk问题
|-理解堆的概念 堆是完全二叉树 有最大堆和最小堆
|-会使用py内置的heapq模块实现堆的操作
|-常考题:合并k个有序链表leetcode merge-k-sorted-list 23 title
合并两个有序链表..

​ |--1.读取所有链表值

​ |--2.构造一个最小堆heapq实现

​ |--3.根据最小堆构造链表

# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

from heapq import heapify,heappop
class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        h = []
        for node in lists:
            while node:
                h.append(node.val)
                node = node.next
        if not h:
            return None
        heapify(h)

        root = ListNode(heappop(h))
        curnode = root
        while h:
            nextnode = ListNode(heappop(h))
            curnode.next = nextnode
            curnode = nextnode
        return root

字符串常考题

字符串题
了解常用的字符串操作
翻转一个字符串 334
判断是否是回文串 9

​ py内置字符串操作split, upper, replace etc

s.reverse()

def reverseString(self, s):
beg = 0
end = len(s) - 1
while beg < end:
    s[beg], s[end] = s[end], s[beg]
    beg += 1
    end -= 1
def isPalindrome(x):
    if x < 0:
        return False
    s = str(x)
    beg, end = 0, len(s) - 1
    while beg < end:
        if s[beg] == s[end]:
            beg += 1
            end -= 1
        else:
            return False
    return True

字符串的双端遍历方法,可以解决类似问题

练习题
|--链表, 字符串,
如何反转一个单链表
使用循环的方式实现
使用递归地方式实现

猜你喜欢

转载自www.cnblogs.com/xuzhaoping/p/11616745.html