python 数据结构与算法1

python 数据结构与算法

1 python常见数据结构性能

1.1 List

1.1.1 安索引取值和赋值

list最常用的操作是:按索引取值和赋值(v=a[i],a[i]=v),这两个操作执行时间与列表大小无关,均为O(1)。

1.1.2 列表append和__add__()

list.addend(v),执行时间是O(1)。
list0 = list1 + [v],执行时间是O(n+k),其中k是被加的列表长度。

1.1.3 使用timeit模块测试执行时间

在这里插入图片描述
使用timeit模块对函数计时:
在这里插入图片描述
在这里插入图片描述

1.1.4 List基本操作的大O数量级

在这里插入图片描述

1.2 Dict

1.2.1 dict数据类型

字典是根据key找到对应的value,最常用的取值get和赋值set,其性能都是O(1)。另外一个常用的操作判断字典中是否存在某个key (in),这个性能也是O(1)。
在这里插入图片描述

2 线性结构 Linear Structure

线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继(除了第一个和最后一个)。

2.1 栈Stack

栈是一种有次序的数据项集合,在栈中,数据项的加入和移除都仅发生在同一端,这一端叫做“栈顶top”,另一端叫做“栈底base”。

栈是一种后进先出LIFO:Last in First out,这是一种基于数据项保存时间的次序,时间越短的离栈顶越近,而时间越长的离栈底越近。

在这里插入图片描述

2.1.1 抽象数据类型Stack

抽象数据类型“栈”是一个有次序的数据集,每个数据项仅从“栈顶”一端加入到数据集中、从数据集中移除,栈具有后进先出LIFO的特性。
在这里插入图片描述

2.1.2 Stack的操作如下

stack():创建一个空栈,不包含任何数据项。
push(item):将item加入栈顶,无返回值。
pop():将栈顶的数据项移除,并返回,栈被修改。
peek():“窥视”栈顶数据项,返回栈顶的数据。
isEmpty():返回是否是空栈。
size():返回栈中有多少个数据项。

在这里插入图片描述

class Stack(object):
    def __init__(self):
        self.stack = []

    def push(self, item):
        self.stack.append(item)

    def pop(self):
        if not self.is_empty():
            return self.stack.pop()

    def peek(self):
        if not self.is_empty():
            return self.stack[self.size()-1]

    def is_empty(self):
        return len(self.stack) == 0

    def size(self):
        return len(self.stack)

2.1.3 栈的应用1:简单括号匹配

2.1.3.1 圆括号匹配

括号必须遵循“平衡”原则,即每个开括号必须有一个比括号对应。
从左到右扫描括号,最新打开的左括号,应该匹配最先遇到的右括号。这样,第一个左括号就应该匹配最后一个右括号,这种次序反转的识别,正好符合栈的特性。

在这里插入图片描述
流程图如下:
在这里插入图片描述

from data_structure.Stack.stack import Stack


def brackets_valid(expression):
    stack = Stack()

    for item in expression:
        if item == "(":
            stack.push(item)
        elif item == ")":
            if stack.is_empty():
                return False
            else:
                stack.pop()

    return stack.is_empty()


if __name__ == '__main__':
    print(brackets_valid("()()()"))
    print(brackets_valid("(()())(()"))
    print(brackets_valid("((5+6)*(4+3))+((10-9)"))

2.1.3.2 通用括号匹配

实际应用中,会遇到更多种括号,比如:[], {},()。
在这里插入图片描述

from data_structure.Stack.stack import Stack


def brackets_valid(expression):
    stack = Stack()
    mapping = {
        ")": "(",
        "]": "[",
        "}": "{"
    }

    for item in expression:
        if item in "([{":
            stack.push(item)
        elif item in ")]}":
            if stack.is_empty():
                return False
            else:
                if stack.peek() != mapping[item]:
                    return False
                else:
                    stack.pop()

    return stack.is_empty()


if __name__ == '__main__':
    print(brackets_valid("()()()"))
    print(brackets_valid("(()())(()"))
    print(brackets_valid("((5+6)*(4+3))+((10-9)"))

    print(brackets_valid("{{([][])}()}"))
    print(brackets_valid("[[{{(())}}]]"))
    print(brackets_valid("[][][](){}"))

    print(brackets_valid("([)}"))
    print(brackets_valid("((()]))"))
    print(brackets_valid("[{()]"))

2.1.4 栈的应用2:进制转换

进制:指用多少字符来表示整数。十进制是0-9十个数字字符,二进制是0、1两个字符。

例如:十进制233对应的二进制是11101001,具体算法如下:
233 = 2102 + 3101 + 3100
11101001 = 1
27 + 126 + 125 + 024 + 123 + 022 +021 + 1*20

2.1.4.1 十进制转换二进制

常见的十进制转换二进制采用的是**“除2求余数”的算法。如下:35的二进制是100011。在除2求余的过程中,得到的余数是从低到高的次序,而输出则是从高到低,所以需要一个栈来反转次序**。
在这里插入图片描述

from data_structure.Stack.stack import Stack


def convert(num):
    if not isinstance(num, int):
        return False

    stack = Stack()
    while num:
        num, remainder = divmod(num, 2)
        stack.push(remainder)

    result = ""
    while not stack.is_empty():
        result += str(stack.pop())

    return result


if __name__ == '__main__':
    print(f"35的二进制数是{convert(35)}")
2.1.4.2 十进制转换任意进制

将“除2求余”改为“除N求余”就可以将上面的算法扩展为任意进制转换。
十进制233对应八进制351,十六进制是E9。
主要区别是:
二进制:0-1
十进制:0-9
八进制:0-7
十六进制:0-9,A-F

from data_structure.Stack.stack import Stack


def convert(num, unit):
    if not isinstance(num, int):
        return False

    dights = "0123456789ABCDEF"
    stack = Stack()
    while num:
        num, remainder = divmod(num, unit)
        stack.push(remainder)

    result = ""
    while not stack.is_empty():
        result += str(dights[stack.pop()])

    return result


if __name__ == '__main__':
    print(f"35的二进制数是{convert(35, 2)}")
    print(f"233的八进制数是{convert(233, 8)}")
    print(f"233的十六进制数是{convert(233, 16)}")

2.1.5 栈的应用3:表达式转换

2.1.5.1 中缀表达式

操作符位于两个操作数之间的表示法,称为“中缀”表达式。例如:A+B。

2.1.5.2 全括号中缀表达式

计算机处理时最好避免复杂的优先级(乘除优先于加减、括号优先级)规则,能明确规定所有的计算顺序是最好的。因此,引入全括号表达式: ((A+B*C)+D)

2.1.5.3 前缀和后缀表达式

前缀表达式:将操作符移到操作数前面,即:+AB。
后缀表达式:将操作符移到操作数后面,即:AB+。

在前缀和后缀表达式中,操作符次序完全决定了运算的次序,不再混淆。

在这里插入图片描述

2.1.5.4 中缀表达式转换为前缀和后缀形式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.5.5 通用的中缀转后缀算法

中缀表达式A+BC,对应的后缀表达式是 ABC+。其中,操作数ABC的顺序没有改变,操作符的出现顺序在后缀表达式中反转了。由于*的优先级比+高,所以后缀表达式中操作符的出现顺序与运算次序一致。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
算法流程:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

import string

from data_structure.Stack.stack import Stack


def convert_postfix(expression):
    result = ""
    oper_stack = Stack()
    priority = {
        "(": 0,
        "+": 1,
        "-": 1,
        "*": 2,
        "/": 2
    }

    for item in expression:
        if item in string.ascii_letters or item in string.digits:
            result += item
        elif item == "(":
            oper_stack.push(item)
        elif item == ")":
            while oper_stack.peek() != "(":
                result += oper_stack.pop()
            else:
                oper_stack.pop()
        elif item in "+-*/":
            while oper_stack.peek() and \
                    priority[oper_stack.peek()] >= priority[item]:
                result += oper_stack.pop()
            else:
                oper_stack.push(item)

    while not oper_stack.is_empty():
        result += oper_stack.pop()

    return result


if __name__ == '__main__':
    print(convert_postfix("A+B*C"))
    print(convert_postfix("(A+B)*C"))

2.1.5.6 后缀表达式求值

对于后缀表达式从左到右扫描,由于操作符在操作数后面,所以要暂存操作数,在遇到操作符的时候再将暂存的两个操作数进行实际的计算
例如: ABC*+,先扫描到了ABC暂存到栈中,扫描到*的时候,从栈中弹出2个操作数做乘法,做完之后再存入栈中;继续扫描到+,从栈中弹出2个操作数做加法。
在这里插入图片描述
注意:对于-/操作符,弹出的两个操作数顺序很重要,先弹出的是右操作数,后弹出的是左操作数。

算法流程:
在这里插入图片描述

import string

from data_structure.Stack.stack import Stack
from data_structure.Stack.infix_to_postfix import convert_postfix


def calculate(expression):
    stack = Stack()

    for item in expression:
        if item in string.digits:
            stack.push(item)
        elif item in "+-*/":
            first = stack.pop()
            second = stack.pop()

            stack.push(str(eval(second + item + first)))

    return stack.pop()


if __name__ == '__main__':
    print(calculate(convert_postfix("1+2*3")))
    print(calculate(convert_postfix("(1+2)*3")))

2.2 队列

队列是一种有次序的数据集合,其特征是新数据项的添加总发生在一端(尾端rear),数据项的移除总发生在另一端(首端front),这种次序原则称为FIFO:first in first out。
当数据项加入对列,首先出现在队尾,随着队首数据项的移除,它主键接近队首。
在这里插入图片描述

2.2.1 抽象数据类型Queue

抽象数据类型Queue是一个有次序的数据集合。 数据仅允许在尾端添加,在首端删除,满足FIFO的操作次序。

2.2.1.1 Queue的操作

Queue():创建一个空队列对象,返回值为Queue对象;
enqueue(item):将数据项item添加到队尾,无返回值;
dequeue(item):从队首移除数据项,返回值为队首数据项,队列被修改;
isEmpty():返回是否空队列;
size():返回对了中数据项的个数。

在这里插入图片描述

class Queue(object):
    def __init__(self):
        self.queue = []

    def enqueue(self, item):
    	# enqueue 复杂度为O(n)
        self.queue.insert(0, item)

    def dequeue(self):
    	# dequeue 复杂度为O(1)
        if not self.is_empty():
            return self.queue.pop()

    def is_empty(self):
        return len(self.queue) == 0

    def size(self):
        return len(self.queue)
2.2.1.2 队列应用1:热土豆问题(约瑟夫问题)

在这里插入图片描述
约瑟夫问题可以通过队列来解决:

  1. 将所有的人名放入队列中;
  2. 确定报数num,循环num次将队首的人出队再入队;循环结束后,移除队首的人;
  3. 直到最终队列中只剩1个人。
    在这里插入图片描述
from data_structure.Queue.queue import Queue


def josef_quest(names, num):
    if not isinstance(names, list) or len(names) == 0:
        return

    queue = Queue()

    # 1. 所有人入队
    for name in names:
        queue.enqueue(name)

    # 2. 循环num次,将队首人移出再添加到队尾。 循环结束后,将队首的人移除
    while queue.size() > 1:
        for i in range(num):
            queue.enqueue(queue.dequeue())
        queue.dequeue()

    # 3. 返回剩余的1个人
    return queue.dequeue()


if __name__ == '__main__':

    print(josef_quest(["A", "B", "C", "D", "E", "F", "G"], 7))

2.3 双端队列

双端队列Deque是一种有次序的数据集。跟队列相似,其两端可以称作“首”“尾”端;但是双端队列首尾都可以移除或添加数据项。
在这里插入图片描述

2.3.1 抽象数据类型Deque

Deque():创建一个空双端队列;
add_front(item):将item加入队首;
add_rear(item):将item加入队尾;
remove_front():从队首移除数据项,返回值移除的数据项;
remove_rear():从队尾移除数据项,返回值为移除的数据项;
is_empty():返回deque是否为空;
size():返回deque中包含数据项的个数。
在这里插入图片描述

class Deque(object):
    def __init__(self):
        self.deque = []

    def add_front(self, item):
        # 时间复杂度O(n)
        self.deque.insert(0, item)

    def add_rear(self, item):
        # 时间复杂度O(1)
        self.deque.append(item)

    def remove_front(self):
        # 时间复杂度O(n)
        if not self.is_empty():
            return self.deque.pop(0)

    def remove_rear(self):
        # 时间复杂度O(1)
        if not self.is_empty():
            return self.deque.pop()

    def is_empty(self):
        return self.size() == 0

    def size(self):
        return len(self.deque)


if __name__ == '__main__':
    d = Deque()
    print(d.is_empty())
    print(d.add_rear(4))
    print(d.add_rear("dog"))
    print(d.add_front("cat"))
    print(d.add_front(True))
    print(d.size())
    print(d.is_empty())
    print(d.add_rear(8.4))
    print(d.remove_rear())
    print(d.remove_front())

2.3.2 双端队列应用1:回文词

在这里插入图片描述
使用双端队列很容易解决回文词问题:

  1. 将词加入双端队列;
  2. 从双端队列两边分别移除字符,并判断是否相等。直到最红deque中剩下0个或1个字符。
from data_structure.Deque.deque import Deque


def pal_check(expression):
    deque = Deque()
    for i in expression:
        deque.add_rear(i)

    while deque.size() not in [0, 1]:
        if deque.remove_front() != deque.remove_rear():
            return False
    else:
        return True


if __name__ == '__main__':
    print(pal_check("abccba"))
    print(pal_check("abcba"))
    print(pal_check("abcdba"))

2.4 链表

2.4.1 无序表抽象数据类型

2.4.1.1 列表List

一种数据项按照相对位置存放的数据集。数据项指按照存放位置来索引,没有其他关系。

2.4.1.2 无序表List的操作:

List():创建一个空列表;
add(item):添加一个数据项到列表中,假设item原先不存在于列表中;
remove(item):从列表中移除item;
search(item):在列表中查找item,返回布尔值;
is_empty():返回列表是否为空;
size():返回列表包含了多少数据项;
append(item):添加item到末尾,假设原先不存在item;
index(item):返回item的索引。
insert(pos, item):将数据项插入到位置pos,假设item原先不存在,并且列表有足够多的数据项能够插入;
pop():移除末尾的数据项,并返回数据
pop(pos):移除位置为pos的数据项,假设存在pos位置的数据项。

2.4.1.3 采用链表实现无序表

在这里插入图片描述
在这里插入图片描述

class Node(object):
    def __init__(self, init_data):
        self.data = init_data
        self.next = None

    def get_data(self):
        return self.data

    def get_next(self):
        return self.next

    def set_data(self, data):
        self.data = data

    def set_next(self, node):
        self.next = node

在这里插入图片描述
在这里插入图片描述a

from data_structure.LinkedList.node import Node


class UnsortedLinkedList(object):
    def __init__(self):
        self.head = None

    # 队首添加,添加在队首最简单,如果添加在队尾需要遍历整个链表
    def add(self, item):
        node = Node(item)
        # 1. 先将新node的下一个节点设置为self.head
        node.set_next(self.head)
        # 2. 再将self.head指向新加入的节点
        self.head = node

    def remove(self, data):
        """
        根据传入的data,找到具体的node并删除。
        :param data: 要删除的data
        :return:
        """
        current = self.head
        previous = self.head

        while current is not None:
            if current.get_data() == data:
                previous.set_next(current.get_next())
                break
            previous = current
            current = current.get_next()

    def search(self, data):
        """
        根据传入的data,遍历链表查找是否有节点data等于传入的data,并返回是否找到。
        :param data:
        :return: Boolean
        """
        current = self.head

        while current is not None:
            if current.get_data() == data:
                return True
            current = current.get_next()
        else:
            return False

    def is_empty(self):
        return self.size() == 0

    def size(self):
        count = 0
        current = self.head
        while current is not None:
            count += 1
            current = current.get_next()
        return count

    def append(self, item):
        """
        链表尾部添加node
        :param item: 要新增的节点data
        :return:
        """
        node = Node(item)
        current = self.head
        # 1.空链表
        if self.is_empty():
            self.head = node

        # 2.非空链表
        # 这里要拿到最后一个node,所以判断条件是 current.get_next() is not None
        while current.get_next() is not None:
            current = current.get_next()
        else:
            current.set_next(node)

    def index(self, data):
        """
        传入data,遍历链表,查找是否有node的data等于传入的data,
        如果有返回下标,没有返回-1
        :param data:
        :return:
        """
        index = 0
        current = self.head

        while current is not None:
            if current.get_data() == data:
                return index
            current = current.get_next()

        return -1

    def insert(self, pos, data):
        """
        :param pos: pos大于等于链表长度时添加在尾部,小于0时添加在首部
        :param data: 要插入的data
        :return:
        """
        # 1. pos 大于等于列表长度时添加到对尾
        if pos >= self.size():
            self.append(data)
        # 2. pos 小于等于0时,添加到队首
        elif pos <= 0:
            self.add(data)
        else:
            index = 0
            current = self.head
            node = Node(data)
            while current is not None:
                if index == pos:
                    node.set_next(current.get_next())
                    current.set_next(node)
                    break
                index += 1
                current = current.get_next()

    def pop(self, pos=None):
        """
        :param pos: 指定位置删除,pos大于等于链表长度或None代表删除最后一个节点;
        pos小于等于0代表删除第一个节点
        :return: 返回被删除的节点data
        """
        previous = self.head
        current = self.head
        index = 0
        data = None

        if self.is_empty():
            return None

        if pos < 0:
            data = self.head.get_data()
            self.head.set_next(self.head.get_next())
            return data

        while current.get_next() is not None:
            if index == pos:
                previous.set_next(current.get_next())
                data = current.get_data()
                break
            index += 1
            previous = current
            current = current.get_next()
        else:
            # 走到这里时,current是最后一个node,previous是倒数第二个node,
            # index指向最后一个node下标;如果这时 pos>=index,就删除最后一个node
            if pos >= index:
                previous.set_next(current.get_next())
                data = current.get_data()

        return data

2.4.2 有序表抽象数据类型

有序表是一种数据项依照某些可比性质来决定在列表中的位置,按照顺序排列。
在这里插入图片描述

2.4.2.1 有序表操作如下

在这里插入图片描述

2.4.2.2 有序表实现

在这里插入图片描述

from data_structure.LinkedList.node import Node


class SortedLinkedList(object):
    def __init__(self):
        self.head = None

    def add(self, data):
        """
        :param data: 要插入的data
        :return:
        """

        node = Node(data)
        if self.head is None:
            self.head = node
            return
        elif data < self.head.get_data():
            node.set_next(self.head)
            self.head = node
        else:
            current = self.head
            previous = self.head
            while current is not None:
                if data < current.get_data():
                    previous.set_next(node)
                    node.set_next(current)
                    break

                previous = current
                current = current.get_next()

    def remove(self, data):
        """
        根据传入的data,找到具体的node并删除。
        :param data: 要删除的data
        :return:
        """
        current = self.head
        previous = self.head

        while current is not None:
            if current.get_data() == data:
                previous.set_next(current.get_next())
                break
            previous = current
            current = current.get_next()

    def search(self, data):
        """
        根据传入的data,遍历链表查找是否有节点data等于传入的data,并返回是否找到。
        :param data:
        :return: Boolean
        """
        current = self.head

        while current is not None:
            if current.get_data() == data:
                return True
            elif current.get_data() > data:
                return False
            else:
                current = current.get_next()
        else:
            return False

    def is_empty(self):
        return self.size() == 0

    def size(self):
        count = 0
        current = self.head
        while current is not None:
            count += 1
            current = current.get_next()
        return count

    def index(self, data):
        """
        传入data,遍历链表,查找是否有node的data等于传入的data,
        如果有返回下标,没有返回-1
        :param data:
        :return:
        """
        index = 0
        current = self.head

        while current is not None:
            if current.get_data() == data:
                return index
            elif current.get_data() > data:
                return -1
            else:
                current = current.get_next()

        return -1

    def pop(self, pos=None):
        """
        :param pos: 指定位置删除,pos大于等于链表长度或None代表删除最后一个节点;
        pos小于等于0代表删除第一个节点
        :return: 返回被删除的节点data
        """
        previous = self.head
        current = self.head
        index = 0
        data = None

        if self.is_empty():
            return None

        if pos < 0:
            data = self.head.get_data()
            self.head.set_next(self.head.get_next())
            return data

        while current.get_next() is not None:
            if index == pos:
                previous.set_next(current.get_next())
                data = current.get_data()
                break
            index += 1
            previous = current
            current = current.get_next()
        else:
            # 走到这里时,current是最后一个node,previous是倒数第二个node,
            # index指向最后一个node下标;如果这时 pos>=index,就删除最后一个node
            if pos >= index:
                previous.set_next(current.get_next())
                data = current.get_data()

        return data

2.4.2.3 链表实现的算法分析

在这里插入图片描述
在这里插入图片描述

3 递归

3.1 什么是递归 Recursion

在这里插入图片描述

3.2 初识递归

问题:给定一个列表,返回所有数的和。要求不能用for或while循环。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 递归程序如何被执行

在这里插入图片描述

3.4 递归三定律 ※※※

在这里插入图片描述
前面数列求和问题套用递归三定律如下:
在这里插入图片描述

3.5 递归应用

3.5.1 整数转换为任意进制

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

def convert(num, unit):
    convert_str = "0123456789ABCDEF"

    if num < unit:
        return convert_str[num]

    num, remainder = divmod(num, unit)

    return convert(num, unit) + str(remainder)


if __name__ == '__main__':
    print(f"35的二进制数是{convert(35, 2)}")
    print(f"233的八进制数是{convert(233, 8)}")
    print(f"233的十六进制数是{convert(233, 16)}")

3.6 递归调用的实现

在这里插入图片描述

3.6.1 python递归深度限制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 分治策略与递归

在这里插入图片描述
在这里插入图片描述

4.1 优化问题和贪心策略

在这里插入图片描述

4.1.1 贪心策略Greedy Method

在这里插入图片描述

4.1.2 贪心策略解决找零兑换问题

在这里插入图片描述
在这里插入图片描述

def change(change_money, coin_value_list):
    for unit in coin_value_list:
        if change_money >= unit:
            consult, remainder = divmod(change_money, unit)
            if remainder != 0:
                # 减小规模 + 递归调用
                return "%s*%s+" % (consult, unit) + change(remainder, coin_value_list)
            else:
                # 递归结束条件
                return "%s*%s" % (consult, unit)


if __name__ == '__main__':
    print(change(63, [25, 21, 10, 5, 1]))

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值