[学习笔记] 2. 数据结构

news2024/9/25 19:24:36
数据结构

视频地址:https://www.bilibili.com/video/BV1uA411N7c5

数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中

比如:列表、集合与字典等都是一种数据结构。

N.Wirth: “程序 = 数据结构 + 算法”

数据结构按照其逻辑结构可以分为:①线性结构;②树结构;③图结构。

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

1. 列表/数组

列表(其他语言成为数组)是一种基本数据类型。

关于列表的问题:

  1. 列表中的元素是如何存储的?
  2. 列表的基本操作:按下标(索引)查找、插入元素、删除元素…这些操作的时间复杂度是多少?
    • 查找: O ( 1 ) O(1) O(1)
    • 插入: O ( n ) O(n) O(n)
    • 删除: O ( n ) O(n) O(n)
    • append O ( 1 ) O(1) O(1)

扩展:Python的列表是如何实现的?

数组与列表有两点不同:

  1. 数组元素类型相同
  2. 数组长度固定

2. 栈(stack)

栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。

  • 栈的特点:先进后出,后进先出LIFO(Last-In, First-Out)
  • 栈的概念:栈顶、栈底
  • 栈的基本操作:
    • 进栈(压栈):push
    • 出栈:pop
    • 取栈顶:gettop

2.1 栈的实现

使用一般的列表结构(list)即可实现栈。

  • 进栈: ls.append
  • 出栈: ls.pop
  • 取栈顶: ls[-1]

2.2 栈的基本实现代码

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

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

    def pop(self):
        if self.stack:
            return self.stack.pop()
        else:
            raise "Empty Value"

    def gettop(self):
        if self.stack:
            return self.stack[-1]
        else:
            return None


if __name__ == "__main__":
    stack = Stack()
    stack.push(1)
    stack.push(2)
    stack.push(3)
    print(stack.pop())
    print(stack.gettop())

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

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

例如:

()()[]{}    匹配
([{()}])    匹配
[](         不匹配
[(])        不匹配

思路:有左括号先放到栈中,看下一个;如果是右括号,那么看和栈顶是否匹配,如果匹配,出栈。看完所有元素后,如果栈是空栈,那么说明是合法的,如果不为空,那么是非法的。

特殊情况:如果直接来了一个右括号,直接非法!


代码:

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

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

    def pop(self):
        if self.stack:
            return self.stack.pop()
        else:
            raise "Empty Value"

    def gettop(self):
        if self.stack:
            return self.stack[-1]
        else:
            raise "Empty Value"

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


def brace_match(s):
    match_dict = {')': '(',
                  ']': '[',
                  '}': '{'}
    stack = Stack()

    for char in s:
        if char in "([{":
            stack.push(char)
        elif char in ")]}":
            if stack.is_empty():
                return False
            elif stack.gettop() == match_dict[char]:
                stack.pop()
            else:  # 不匹配
                return False
        else:
            raise "Illegal Value"

    # 看一下栈里是否有元素
    if stack.is_empty():
        return True
    else:
        return False


if __name__ == "__main__":
    test_1 = "()()[]{}"
    test_2 = "([{()}])"
    test_3 = "[]("
    test_4 = "[(])"

    print(brace_match(test_1))
    print(brace_match(test_2))
    print(brace_match(test_3))
    print(brace_match(test_4))

"""
    True
    True
    False
    False
"""

3. 队列(Queue)

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

在这里插入图片描述

  • 进行插入的一端称为队尾(rear),插入动作称为进队或入队
  • 进行删除的一端称为对头(front),删除动作称为出队
  • 队列的性质:先进先出FIFO(First-In, First-Out)

3.1 队列的实现

队列是否能用列表简单实现?为什么?

在这里插入图片描述

列表不好实现,出队3次以后,再入队就没法入队了。但如果把这个队列的头和尾连起来,这个问题就解决了!

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

在这里插入图片描述

Q:队满的时候为什么有一个位置是空的?
A:为了判断这个队是空队还是满队:

  • 空队列:front == rear
  • 满队列:front == rear + 1

Q:rear进队到11时,怎么变为0
A:使用% -> rear = rear % len(queue) -> rear %= 12

Q:那么front呢?
A:也是一样的,也是front %= len(queue)


环形队列:当队尾指针front = MaxSize - 1时,再前进一个位置就自动到0

  • 队首指针前进1:front = (front + 1) % MaxSize
  • 队尾指针前进1:rear = (rear + 1) % MaxSize
  • 队空条件:rear == front
  • 队满条件:(rear + 1) % MaxSize == front

MaxSize为队列的长度

3.3 队列的代码实现

class Queue:
    def __init__(self, size=100):
        # 队列的长度需要固定
        self.size = size  # 队列的长度
        self.queue = [0 for i in range(size)]
        self.rear = 0  # 队尾(进队)
        self.front = 0  # 队首(出队)

    # 入队
    def push(self, elem):
        if not self.is_filled():
            self.rear = (self.rear + 1) % self.size
            self.queue[self.rear] = elem  # 进队
        else:
            raise "Filled Queue Error"

    # 出队
    def pop(self):
        if not self.is_empty():
            self.front = (self.front + 1) % self.size
            # 这里不删除旧的元素了,因为环形的,有值后会覆盖它!
            return self.queue[self.front]
        else:
            raise "No Value Error"

    # 判断队空
    def is_empty(self):
        if self.rear == self.front:
            return True
        else:
            return False

    # 判断队满
    def is_filled(self):
        if (self.rear + 1) % self.size == self.front:
            return True
        else:
            return False

    # 获取队尾
    def getrear(self):
        return self.queue[self.rear]

    # 获取队首
    def getfront(self):
        return self.queue[(self.front + 1) % 12]


if __name__ == "__main__":
    q = Queue(5)
    for i in range(4):
        q.push(i)

    print(q.is_filled())

    print(q.pop())
    print(q.is_empty())
    print(q.getfront())
    print(q.getrear())

3.4 双向队列

双向队列的两端都支持进队和出队操作,其基本操作如下:

  • 队首进队
  • 队首出队
  • 队尾进队
  • 队尾出队

在这里插入图片描述

3.5 Python队列内置模块

使用方法from collections import deque,具体操作如下:

  • 创建队列:queue = deque()
  • 进队:append()
  • 出队:popleft()
  • 双向队列队首进队:appendleft()
  • 双向队列队尾出队:pop()

注意:这里并不是import queue模块,因为queue模块一般用在多线程、多进程中,用来保持线程安全的。如果我们想要解题,那么我们就使用from collections import deque
deque: 全称dequeue,de表示双向,即双向队列
deque是从左到右队首和堆尾!
一般情况下,单向队列用的比较多

from collections import deque

q = deque()

# 单向队列
q.append(1)  # 队尾进队
print(q.popleft())  # 队首出队
# print(q.popleft())  # IndexError: pop from an empty deque

# 双向队列
q.appendleft(2)  # 队首进队
print(q.pop())

值得注意的是,deque()在创建的时候是可以传入值的,如下:

from collections import deque

# 不传入值
q1 = deque()
q1.append(1)
q1.append((1, 2, 3))
q1.append([1, 2, 3])
print(q1)  # deque([1, (1, 2, 3), [1, 2, 3]])
q1.popleft()
q1.popleft()
print(q1)  # deque([[1, 2, 3]])

# 传入值
q2 = deque([1, 2, 3])
print(q2)  # deque([1, 2, 3])
q2.append(4)
print(q2)  # deque([1, 2, 3, 4])
q2.append([5, 6, 7])  # deque([1, 2, 3, 4, [5, 6, 7]])
print(q2)

# 传入值和最大值
q3 = deque([1, 2, 3], maxlen=5)
print(q3)  # deque([1, 2, 3], maxlen=5)
q3.append(4)
q3.append(5)
print(q3)  # deque([1, 2, 3, 4, 5], maxlen=5)
q3.append(6)
print(q3)  # deque([2, 3, 4, 5, 6], maxlen=5) -> 自动将1pop掉了

3.6 使用deque实现Linux的tail命令

Linux的tail命令即在terminal中输出文本文件的后五行。

from collections import deque


def tail(n):
    with open('test.txt', 'r') as f:
        q = deque(f, n)
        return q


if __name__ == "__main__":
    # print(tail(5))  # deque(['gdfsfdgh567u65g\n', 'dsfgfdgh456t24t\n', 'sdgfstg453rt234t2\n', 'gdfs344534y45\n', 'qqqfdu6574'], maxlen=5)
    for line in tail(5):
        print(line, end="")

"""
    gdfsfdgh567u65g
    dsfgfdgh456t24t
    sdgfstg453rt234t2
    gdfs344534y45
    qqqfdu6574
"""

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

给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。

在这里插入图片描述

maze: 英[meɪz] 美[meɪz]
n. 迷宫; 纷繁复杂的规则; 复杂难懂的细节; 迷宫图;
vt. 使困惑; 使混乱; 迷失;

3.7.1 方法1:栈 —— 深度优先搜索

  • 回溯法
  • 思路:从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。
  • 使用栈存储当前路径。
def maze_path(maze, x1, y1, x2, y2):
    # 创建栈
    stack = []
    # 将起点放到栈中
    stack.append((x1, y1))

    # 定义四种寻找方式
    directions = [
        lambda x, y: (x + 1, y),  # 上
        lambda x, y: (x - 1, y),  # 下
        lambda x, y: (x, y - 1),  # 左
        lambda x, y: (x, y + 1),  # 右
    ]

    # 当栈不为空,说明能继续找
    while len(stack) > 0:
        # 设定当前节点
        curNode = stack[-1]

        # 判断当前节点是否为终点
        if curNode[0] == x2 and curNode[1] == y2:
            print("Find Solution!")
            for path in stack:
                print(path, end="->")
            return True
        else:  # 如果不是终点,按照四个方向依次寻找
            for direction in directions:
                # 得到下一个节点
                nextNode = direction(curNode[0], curNode[1])

                # 判断下一个节点是否为0
                if maze[nextNode[0]][nextNode[1]] == 0:  # 说明是下一个节点
                    # 压栈
                    stack.append(nextNode)
                    # 进行标记以防止回退
                    maze[nextNode[0]][nextNode[1]] = 2
                    # 已经找到下一个节点,停止该节点的搜索
                    break
                else:  # 如果不是,继续搜索下一个方向
                    pass
            else:  # 如果四个方向都没有找到下一个节点
                stack.pop()

    else:  # 栈为空
        print("No Solution!")
        return False


if __name__ == "__main__":
    maze = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],  # 1
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],  # 2
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],  # 3
        [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],  # 4
        [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],  # 5
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],  # 6
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],  # 7
        [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],  # 8
        [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],  # 9
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],  # 10
    ]
    maze_path(maze, 1, 1, 8, 8)

"""
Find Solution!
(1, 1)->(2, 1)->(3, 1)->(4, 1)->(5, 1)->(5, 2)->(5, 3)->(6, 3)->(6, 4)->(6, 5)->(7, 5)->(8, 5)->(8, 6)->(8, 7)->(8, 8)->
"""

3.7.2 方法2:队列 —— 广度优先搜索

思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口。

多种可能同时进行

  • 使用队列存储当前正在考虑的节点。
from collections import deque


def print_path(path):
    curNode = path[-1]  # path的最后一个元素就是终点

    real_path = []  # 存放真正的路径

    # 找到其他节点
    while curNode[2] != -1:
        real_path.append((curNode[0], curNode[1]))
        curNode = path[curNode[2]]
    else:
        real_path.append((curNode[0], curNode[1]))  # 起点

    real_path.reverse()
    for node in real_path:
        print(node, end="->")


def maze_path_queue(maze, x1, y1, x2, y2):
    # 创建队列
    queue = deque()

    # 存放起点
    queue.append((x1, y1, -1))  # x坐标,y坐标,哪个位置让它来的
    path = []  # 存放出队的节点

    # 定义四种寻找方式
    directions = [
        lambda x, y: (x + 1, y),  # 上
        lambda x, y: (x - 1, y),  # 下
        lambda x, y: (x, y - 1),  # 左
        lambda x, y: (x, y + 1),  # 右
    ]

    while len(queue) > 0:
        curNode = queue.popleft()  # 队首出栈
        path.append(curNode)

        # 判断curNode是否为终点
        if curNode[0] == x2 and curNode[1] == y2:
            print("Found Solution: ")
            print_path(path)
            return True

        # 搜索四个方向
        for direction in directions:
            nextNode = direction(curNode[0], curNode[1])
            # 判断nextNode是否能走通
            if maze[nextNode[0]][nextNode[1]] == 0:
                # 能走,进队,并记录哪个节点带它来的
                queue.append((nextNode[0], nextNode[1], len(path) - 1))
                # 标记为已走过
                maze[nextNode[0]][nextNode[1]] = 2

    else:
        print("No Solution")
        return False


if __name__ == "__main__":
    maze = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],  # 1
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],  # 2
        [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],  # 3
        [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],  # 4
        [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],  # 5
        [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],  # 6
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],  # 7
        [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],  # 8
        [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],  # 9
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],  # 10
    ]
    maze_path_queue(maze, 1, 1, 8, 8)

"""
Found Solution: 
(1, 1)->(2, 1)->(3, 1)->(4, 1)->(5, 1)->(5, 2)->(5, 3)->(6, 3)->(6, 4)->(6, 5)->(7, 5)->(8, 5)->(8, 6)->(8, 7)->(8, 8)->
"""

4. 链表(Linked List)

链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表。

在这里插入图片描述

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

定义链表

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


if __name__ == "__main__":
    # 创建节点
    a = Node(1)
    b = Node(2)
    c = Node(3)

    # 将节点连起来
    a.next = b
    b.next = c

    # 通过a这个节点找到b和c
    print(a.next.item)  # 2
    print(a.next.next.item)  # 3

4.1 链表的创建与遍历

上面创建链表的方式是手动的,这样太慢了。创建链表的方式有:头插法和尾插法。

  • 头插法:在head前面插入,插入完毕后,head指向新的头
  • 尾插法:在tail后面插入,插入完毕后,tail指向新的尾
class Node():
    def __init__(self, item):
        self.item = item
        self.next = None


def create_linkedlist_head(ls):
    # 新建头节点
    head = Node(ls[0])

    for elem in ls[1:]:
        node = Node(elem)
        node.next = head
        head = node
    return head


def create_linkedlist_tail(ls):
    head = Node(ls[0])
    # 此时头节点和尾节点是同一指向
    tail = head

    for elem in ls[1:]:
        # 为不同元素创建节点
        node = Node(elem)
        tail.next = node
        tail = node

    return head


def print_linklist(lk):
    while lk:
        print(lk.item, end=", ")
        lk = lk.next


if __name__ == '__main__':
    lk_head = create_linkedlist_head([1, 2, 3])
    print_linklist(lk_head)  # 3, 2, 1,
    print("")

    lk_tail = create_linkedlist_tail([1, 2, 3, 4, 5])
    print_linklist(lk_tail)  # 1, 2, 3, 4, 5,

4.2 链表节点的插入

数组插入需要挪动,再插入,这样的时间复杂度比较高( O ( n ) O(n) O(n)),而对于一个链表而言,先把需要插入的地方的链子解开,插入后连起来就行了。

在这里插入图片描述

p.next = curNode.next
curNode.next = p

4.3 链表节点的删除

在这里插入图片描述

p = curNode.next

curNode.next = curNode.next.next  # 写成curNode.next = p.next也行
del p  # 其实删不删都可以

5. 双链表

双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。

在这里插入图片描述

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

5.1 双链表节点的插入

在这里插入图片描述

p.next = curNode.next
curNode.next.prior = p
p.prior = curNode
curNode.next = p

在这里插入图片描述

5.2 双链表节点的删除

在这里插入图片描述

p = curNode.next
curNode.next = p.next
p.next.prior = curNode
del p

在这里插入图片描述

6. 链表 —— 复杂度分析

顺序表(列表/数组)与链表对比:

Item顺序表链表
按元素值查找 O ( n ) O(n) O(n) O ( n ) O(n) O(n)
按下标查找 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
在某个元素后插入 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
删除某个元素 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
  • 链表在插入和删除的操作上明显快与顺序表
  • 链表的内存可以更灵活地分配:
    • 试利用链表重新实现栈和队列
  • 链表这种链式存储的数据结构对树和图的结构有很大的启发性

7. 哈希表

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

  • insert(key, value): 插入键值对(key, value)
  • get(key): 如果存在键为key的键值对则返回其value,否则返回空值
  • delete(key): 删除键为key的键值对

哈希表其实就是一个dict

7.1 直接寻址表

当关键字的全域 U U U 比较小时,直接寻址是一种简单而有效的方法。

在这里插入图片描述

U U U是一个集合,里面包含了所有可能出现的关键词
K K K是一个集合,里面包含了所有实际出现的关键词
T T T是一个列表,它和 U U U一一对应

直接寻址技术的缺点:

  1. 当域 U U U很大时,需要消耗大量内存,很不实际
  2. 如果域 U U U很大而实际出现的key很少,则大量空间被浪费
  3. 无法处理关键字不是数字的情况

Q:那我们应该怎么解决这些问题呢?
A:在直接寻址表上加一个哈希函数就形成了哈希表


7.2 哈希

直接寻址表:keyk的元素放到k位置上

改进的直接寻址表:哈希(Hashing)

  • 构建大小为 m m m的寻址表 T T T
  • keyk的元素放到 h ( k ) h(k) h(k)位置上
  • h ( k ) h(k) h(k)是一个函数,其将域 U U U映射到表 T [ 0 , 1 , . . . , m − 1 ] T[0, 1, ..., m-1] T[0,1,...,m1]

7.3 哈希表

哈希表(Hash Table,又称为散列表),是一种线性表的存储结构。哈希表由一个直接寻址表一个哈希函数组成。哈希函数 h ( k ) h(k) h(k)将元素关键字 k k k作为自变量,返回元素的存储下标。


假设有一个长度为7的哈希表,哈希函数$h(k) = k % 7 。元素集合 。元素集合 。元素集合{14, 22, 3, 5}$的存储方式如下图:

在这里插入图片描述

元素集合 { 14 , 22 , 3 , 5 } \{14, 22, 3, 5\} {14,22,3,5}是指key,没有value
h ( k ) = k % 7 ∈ [ 0 , 6 ] h(k) = k \% 7 \in [0, 6] h(k)=k%7[0,6]

Q: 如果有一个key=0,那么上面的表中key=0已经有key了,怎么办?
A:这么情况称为哈希冲突

7.4 哈希冲突

由于哈希表的大小是有限的,而要存储的值的数量是无限的,因此对于任何哈希函数而言,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。

比如 h ( k ) = k % 7 h(k) = k \% 7 h(k)=k%7, h ( 0 ) = h ( 7 ) = h ( 14 ) = . . . h(0) = h(7) = h(14) = ... h(0)=h(7)=h(14)=...

7.4.1 解决哈希冲突1 —— 开放寻址法

开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。

  • 线性探查:如果位置 i i i 被占用,则探查 i + 1 , i + 2 , . . . i + 1, i + 2, ... i+1,i+2,...
  • 二次探查:如果位置 i i i 被占用,则探查 i + 1 2 , i − 1 2 , i + 2 1 , i − 2 2 , . . . i + 1^2, i - 1^2, i + 2^1, i - 2^2, ... i+12,i12,i+21,i22,...
  • 二度哈希(Double Hash):有 n n n 个哈希函数,当使用第1个哈希函数 h 1 h_1 h1发生冲突时,则尝试使用 h 2 , h 3 , . . . h_2, h_3, ... h2,h3,...

7.4.2 解决哈希冲突2 —— 拉链法

拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。

在这里插入图片描述

之前是每个位置存放一个元素,现在不是元素而是链表

7.5 常见的哈希函数

  • 除法哈希法:
    • h ( k ) = k % m h(k) = k \% m h(k)=k%m
    • -> lambda k: k % m
  • 乘法哈希法:
    • h ( k ) = f l o o r ( m × ( A × k e y % 1 ) ) h(k) = \rm{floor}(m\times (A \times \rm{key} \% 1)) h(k)=floor(m×(A×key%1))
    • -> lambda k: floor(m*(A*key%1))
    • %1: 取小数部分
  • 全域哈希法:
    • h a , b ( k ) = ( ( a × k e y + b ) % p ) % m h_{a, b}(k) = ((a\times \rm{key} + b) \% p) \% m ha,b(k)=((a×key+b)%p)%m
    • -> lambda a, b, k: ((a * key + b) % p) % m
    • a , b = 1 , 2 , . . . , p − 1 a, b = 1, 2, ..., p-1 a,b=1,2,...,p1

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

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

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

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

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

7.7 哈希表的应用 —— MD5算法

MD5(Message-Digest Algorithm 5)曾经是密码学中常用的哈希函数,可以把任意长度的数据映射为128位的哈希值,其曾经包含如下特征:

  1. 同样的消息,其MD5值必定相同;
  2. 可以快速计算出给定消息的MD5值;
  3. 除非暴力的枚举所有可能的消息,否则不可能从哈希值反推出消息本身;
  4. 两条消息之间即使只有微小的差别,其对应的MD5值也应该是完全不同、完全不相关区的;
  5. 不能在有意义的时间内人工构造两个不同的消息使其具有相同的MD5值。

Message-Digest: 消息摘要

哈希只有加密,没有解密

MD5值相同的两个文件,但二者也有可能是不同的,因为有哈希冲突的存在,而且128位的哈希值是有限的(虽然有限,但是人工制造两个哈希值相同的不同文件基本上是不可能的)

现在MD5已经被破解了,不再安全了,但仍然在大规模使用

7.7.1 应用举例 —— 文件的哈希值

算出文件的哈希值,若两个文件的哈希值相同,则可以认为这两个文件是相同的,因此:

  1. 用户可以利用它来验证下载的文件是否完整
  2. 云存储服务商可以利用它来判断用户要上传的文件是否已经存在于服务器上,从而实现秒传的功能,同时也可以避免存储过多相同的文件副本

7.8 哈希表的应用 —— SHA-2算法

历史上MD5和SHA-1曾经是使用最为广泛的 Cryptographic Hash Function,但是随着密码学的发展,这两个哈希函数的安全性相继受到了各种挑战。因此现在安全性较重要的场合推荐使用SHA-2等新的、更安全的哈希函数。

  • SHA-2包含了一系列的哈希函数:SHA-224,SHA-256,SHA-384,SHA-512,SHA-512/224,SHA-512/256,其对应的哈希长度分别为224,256,384或512位。
  • SHA-2具有和MD5类似的性质(参见MD5算法的特征)。

SHA: Secure Hash Algorithm
SHA-2目前还没有被破解,因此更加安全

7.8.1 应用举例 —— SHA-2算法

在比特币(Bitcoin)系统中,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的目标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于一个给定值D。此时,只能通过暴力枚举V来猜测。

首先计算出结果的人可以获得一定奖金,而某人首先计算成功的概率与其拥有的计算量成正比,所以其获得奖金的期望值与其拥有的计算量成正比。

8. 树与二叉树

树是一种数据结构,比如目录结构。树是一种可以递归定义的数据结构,是由 n n n个节点组成的集合:

  • 如果 n = 0 n=0 n=0,那这是一颗空树;
  • 如果 n > 0 n > 0 n>0,那存在1个节点作为树的根节点,其他节点可以分为 m m m个集合,每个集合本身又是一棵树。

在这里插入图片描述

8.1 一些概念

  • 根节点:A
  • 叶子节点:没有孩子节点的节点(到头了,没有分叉了),比如 B,C,P,Q,K,L,M,N
  • 树的深度(高度):最深的
  • 节点的度:指定节点对应的分叉数,E的度=2
  • 树的度:哪个节点分的叉最多,那树的就是几 -> max(节点的度)=6
  • 孩子节点/父节点:相对的概念
  • 子树:EIJPQ是一颗以E为根节点的树

8.2 用树创建一个文件夹系统

# 树是由一堆节点构成,所以节点是树的核心
class Node:
    def __init__(self, name, node_type='dir'):
        # 链式存储的方式
        self.name = name
        self.type = node_type  # "dir" or "file"
        self.children = []
        self.parent = None  # 指向父节点

    # 让Node可以被打印
    def __repr__(self):
        return self.name


class FileSystemTree:
    def __init__(self):
        self.root = Node("/")  # 根节点: 这里仿照的是Linux系统的root
        self.now = self.root  # 在哪个文件夹中

    def mkdir(self, name):
        # name必须以/结尾
        if name[-1] != "/":
            name += "/"

        # 创建Node
        node = Node(name)

        # 和现在的文件夹相连
        self.now.children.append(node)
        node.parent = self.now

    # 展现当前目录下的所有目录
    def ls(self):
        print(self.now.children)
        # return self.now.children

    # change directory(只支持相对路径)
    def cd(self, name):
        if name in ["..", "../"]:  # 返回上一级目录
            self.now = self.now.parent
            return True

        # name必须以/结尾
        if name[-1] != "/":
            name += "/"

        for child in self.now.children:
            if child.name == name:  # 如果有这个目录
                self.now = child  # 切换目录
                return True
        else:
            raise ValueError("invalid directory")


if __name__ == "__main__":
    n = Node('hello')
    n2 = Node('world')
    n.children.append(n2)
    n2.parent = n

    tree = FileSystemTree()
    tree.mkdir("var/")
    tree.mkdir("bin/")
    tree.mkdir("usr/")
    tree.ls()  # [var/, bin/, usr/]

    tree.cd("bin/")
    tree.mkdir("python/")
    tree.ls()  # [python/]

    tree.cd("../")
    tree.ls()  # [var/, bin/, usr/]

8.3 二叉树

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

在这里插入图片描述

8.3.1 节点定义:

class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None

二叉树的度是2

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


if __name__ == "__main__":
    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.lchild = f

    root = e

    print(root.lchild.rchild.data)  # C

8.3.2 二叉树的遍历

在这里插入图片描述

二叉树的遍历方式:

  1. 前序遍历:EACBDGF —— 自己 -> 左 -> 右
  2. 中序遍历:ABCDEGF —— 左 -> 自己 -> 右
  3. 后序遍历:BDCAFGE —— 左 -> 右 -> 自己
  4. 层次遍历:EAGCFBD —— 每一层,从左到右

前序、中序、后序都是指遍历自己的位置
给出三者的任意两者都可以推出这棵树
中序遍历后一定是升序的!(有序!)

from collections import deque


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


def pre_order(root):
    # 前序遍历: 自己 -> 左 -> 右
    if root:  # 如果root不为空
        print(root.data, end=" ")  # 自己
        pre_order(root.lchild)  # 左
        pre_order(root.rchild)  # 右


def in_order(root):
    # 中序遍历: 左 -> 自己 -> 右
    if root:
        in_order(root.lchild)
        print(root.data, end=" ")
        in_order(root.rchild)


def post_order(root):
    # 后序遍历: 左 -> 右 -> 自己
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end=" ")


def level_order(root):
    # 层次遍历

    # 先创建队列,并添加根节点
    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)


if __name__ == "__main__":
    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.lchild = f

    root = e

    print(root.lchild.rchild.data)  # C

    pre_order(root)  # E A C B D G F
    print("")
    in_order(root)  # A B C D E F G 
    print("")
    post_order(root)  # B D C A F G E 
    print("")
    level_order(root)  # E A G C F B D

8.4 二叉搜索树(Binary Search Tree, BST)

在这里插入图片描述

二叉搜索树是一颗二叉树,且满足以下性质:设 x 是二叉树的一个节点,如果 yx 左子树的任意一个节点,那么 y.key ≤ x.key;如果 yx 右子树的一个节点,那么 y.key ≥ x.key

简单理解:任意一个节点的左侧的值都比该节点小,右边都比该节点大

二叉搜索树的操作:

  1. 查询: O ( log ⁡ n ) O(\log^n) O(logn)
  2. 插入: O ( log ⁡ n ) O(\log^n) O(logn)
  3. 删除
import random


class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
        self.parent = None


class BinarySearchTree:
    def __init__(self, ls=None):
        self.root = None  # 根节点
        if ls:
            for val in ls:
                self.insert_no_rec(val)  # 非递归的方法(更快)
                # self.root = self.insert(self.root, val)  # 递归的方法

    def insert(self, node, val):
        """
            node: 递归时遍历的所有节点
            val: 插入的值
        """
        if node is None:  # 如果node为空(写成 if not node也可以)
            node = BiTreeNode(val)  # node为空则需要创建一个节点
        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
        else:  # 相等的情况
            pass

        return node

    def insert_no_rec(self, val):
        p = self.root
        if not p:  # 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 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 query(self, node, val):
        """
            递归版本需要node
        """
        if not node:  # 如果node为空(递归的终止条件)
            return None

        elif node.data > val:
            return self.query(node.lchild, val)
        elif node.data < val:
            return self.query(node.rchild, val)
        else:
            return node

    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data > val:
                p = p.lchild
            elif p.data < val:
                p = p.rchild
            else:
                return p

        else:
            return None


if __name__ == '__main__':
    tree = BinarySearchTree([4, 6, 7, 9, 2, 1, 3, 5, 8])
    tree.pre_order(tree.root)  # 4 2 1 3 6 5 7 9 8
    print("")
    tree.in_order(tree.root)  # 1 2 3 4 5 6 7 8 9
    print("")
    tree.post_order(tree.root)  # 1 3 2 5 8 9 7 6 4
    print("")

    ls = list(range(500))
    random.shuffle(ls)
    tree = BinarySearchTree(ls)
    tree.in_order(tree.root)  # 0 1 2 3 4 5 ... 99 (排好序了!)
    print()

    ls = list(range(0, 500, 2))
    random.shuffle(ls)
    tree = BinarySearchTree(ls)
    print(tree.query(tree.root, 4).data)  # 4
    print(tree.query_no_rec(3))  # None

8.4.1 二叉搜索树 —— 删除操作

  1. 如果要删除的节点是叶子节点:直接删除

在这里插入图片描述

  1. 如果要删除的节点只有一个孩子:将此节点的父亲与孩子连接,然后删除该节点。

在这里插入图片描述

如果要删除的节点是根节点,且这个根只有一个孩子节点,那么直接更新根就行。

  1. 如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点。

在这里插入图片描述

在右子树中,往左走到头就是最小节点

这部分的代码如下:

min_node = node.rchild

while min_node.lchild:  # 如果min_node有左孩子
    min_node = min_node.lchild  # 让左孩子替换min_node

下面是整体的代码:

import random


class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
        self.parent = None


class BinarySearchTree:
    def __init__(self, ls=None):
        self.root = None  # 根节点
        if ls:
            for val in ls:
                self.insert_no_rec(val)  # 非递归的方法(更快)
                # self.root = self.insert(self.root, val)  # 递归的方法

    def insert(self, node, val):
        """
            node: 递归时遍历的所有节点
            val: 插入的值
        """
        if node is None:  # 如果node为空(写成 if not node也可以)
            node = BiTreeNode(val)  # node为空则需要创建一个节点
        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
        else:  # 相等的情况
            pass

        return node

    def insert_no_rec(self, val):
        p = self.root
        if not p:  # 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 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 query(self, node, val):
        """
            递归版本需要node
        """
        if not node:  # 如果node为空(递归的终止条件)
            return None

        elif node.data > val:
            return self.query(node.lchild, val)
        elif node.data < val:
            return self.query(node.rchild, val)
        else:
            return node

    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data > val:
                p = p.lchild
            elif p.data < val:
                p = p.rchild
            else:
                return p

        else:
            return None

    def __remove_node_1(self, node):
        # 情况1:node是叶子结点(没有孩子)
        if not node.parent:  # 如果节点的父节点为空(那就是root节点)
            self.root = None  # 删除根节点

        if node == node.parent.lchild:  # node是它父亲的左孩子
            node.parent.lchild = None  # 删除左孩子
        else:  # 右孩子
            node.parent.rchild = None  # 删除右孩子

    def __remove_node_2_1(self, node):
        # 情况2-1:node只有一个左孩子节点
        if not node.parent:  # 根节点
            # 左孩子就是新的root
            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只有左孩子,没有右孩子
            node.lchild.parent = node.parent

    def __remove_node_2_2(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)  # 找到val对应的node
            if not node:  # 如果val对应的node不存在
                raise ValueError(f"数值{val}不存在!")

            if not node.lchild and not node.rchild:  # node是叶子节点(没有孩子)
                self.__remove_node_1(node)
            elif not node.rchild:  # 只有一个孩子(只有左孩子)-> 2-1
                self.__remove_node_2_1(node)
            elif not node.lchild:  # 只有一个右孩子 -> 2-2
                self.__remove_node_2_2(node)
            else:  # 两个孩子都有
                # 找右子树最小的节点
                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_2_2(min_node)
                else:  # 是叶子节点
                    self.__remove_node_1(min_node)


if __name__ == '__main__':
    tree = BinarySearchTree([4, 6, 7, 9, 2, 1, 3, 5, 8])
    tree.pre_order(tree.root)  # 4 2 1 3 6 5 7 9 8
    print("")
    tree.in_order(tree.root)  # 1 2 3 4 5 6 7 8 9
    print("")
    tree.post_order(tree.root)  # 1 3 2 5 8 9 7 6 4
    print("")

    ls = list(range(500))
    random.shuffle(ls)
    tree = BinarySearchTree(ls)
    tree.in_order(tree.root)  # 0 1 2 3 4 5 ... 99 (排好序了!)
    print()

    ls = list(range(0, 500, 2))
    random.shuffle(ls)
    tree = BinarySearchTree(ls)
    print(tree.query(tree.root, 4).data)  # 4
    print(tree.query_no_rec(3))  # None

    tree = BinarySearchTree([1, 4, 2, 5, 3, 8, 6, 9, 7])
    tree.in_order(tree.root)  # 1 2 3 4 5 6 7 8 9
    print()

    tree.delete(4)
    tree.delete(1)
    tree.delete(3)
    tree.delete(8)
    tree.in_order(tree.root)  # 2 5 6 7 9

二叉搜索树的效率

平均情况下,二叉搜索树进行搜索的时间复杂度为 O ( lg ⁡ n ) O(\lg^n) O(lgn)

在这里插入图片描述

但是在最坏情况下,二叉搜索树可能非常偏斜(极端情况下退化到 O ( n ) O(n) O(n))。解决方案有:

  1. 随机化插入
  2. AVL树

8.5 AVL树

AVL树:AVL树是一棵自平衡的二叉搜索树。

在这里插入图片描述

AVL树具有以下性质:

  1. 根的左右子树的高度差的绝对值不能超过1
  2. 根的左右子树都是平衡二叉树

高度差:针对每个节点的左右孩子数之差(有孩子为1,没孩子为0)
balance factor,平衡因子:记录了左右子树的高度之差。对于10而言,左=0,右=1,所以balance factor = 0 - 1 = -1
平衡二叉树:简单理解为树的深度一致

8.6 AVL树 —— 插入

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

可视化VAL

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

不平衡的情况有4种。

8.6.1 左旋

不平衡是由于对K的右孩子的右子树插入导致的:左旋。

在这里插入图片描述

8.6.2 右旋

不平衡是由于对K的左孩子的左子树插入导致的:右旋。

在这里插入图片描述

8.6.3 右旋-左旋

不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋。

在这里插入图片描述

8.6.4 左旋-右旋

不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋。

在这里插入图片描述

  • 左左:右旋
  • 右右:左旋
  • 左右:左右
  • 右左:右左

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

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

在这里插入图片描述

比17和35小的走左边,在范围内的走中间,大的走右边。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399744.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“小霞”黄绮珊绮望三十巡回演唱会将于3月18日杭州大剧院震撼开唱!

•中年成名 四十余载静待盛放 歌手黄绮珊&#xff0c;1991年正式进入歌坛&#xff0c;至今已满三十周年。黄绮珊前半生的歌手之路好像并不是那么顺畅。虽然一直有着华语乐坛的顶尖歌唱实力&#xff0c;但在45岁之前&#xff0c;黄绮珊一直处于“歌红人不红”的状态。不少人听过…

postgresql 自动备份 bat实现

postgres数据据备分,用cmd命令有些烦,写了个bat实现 BAT脚本中常用的注释命令有rem、@rem和:: rem、@rem和::用法都很简单,直接在命令后加上要注释的语句即可。例如下图,语言前加了rem,运行BAT时就会自动忽略这个句子。需要注释多行时,每行前面都要加上rem、@rem和::。…

Kalman Filter in SLAM (3) ——Extended Kalman Filter (EKF, 扩展卡尔曼滤波)

文章目录1. 线性系统的 Kalman Filter 回顾2. Extended Kalman Filter 之 DR_CAN讲解笔记2.1. 非线性系统2.2. 非线性系统线性化2.2.1. 状态方程f(xk)f(x_k)f(xk​)在上一次的最优估计状态x^k−1\hat{x}_{k-1}x^k−1​处线性化2.2.2. 观测方程h(xk)h(x_k)h(xk​)在这一次的预测…

企业知识管理应该怎么做?

企业知识管理是指企业利用各种信息技术手段&#xff0c;对企业内部的知识进行收集、整理、分析和传递&#xff0c;以提高企业的知识水平和竞争力。企业知识管理涉及到企业内部的各种知识&#xff0c;包括技术知识、专业知识、经验知识、市场知识等。 企业知识管理的方式可以分为…

DETR目标检测算法学习记录

引言 无论是One Stage中的YOLO还是Two-Stage中的Faster-RCNN&#xff0c;其虽然都在目标检测领域有着一席之地&#xff0c;但无一例外都是基于Anchor的模型算法&#xff0c;这就导致其在输出结果时不可避免的进行一些如非极大值抑制等操作来进一步选择最优解&#xff0c;这会带…

uniCloud基础使用

获取openID云函数use strict; exports.main async (event, context) > {//event为客户端上传的参数console.log(event : , event)// jscode2session 微信小程序登录接口&#xff0c;获取openidconst {code} event;// 云函数中如需要请求其他http服务&#xff0c;则使用uni…

Day912.多环境配置隔离 -SpringBoot与K8s云原生微服务实践

多环境配置隔离 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于多环境配置隔离的内容。 多环境支持&#xff0c;是现在互联网开发研发和交付的主流基本需求。通过规范多环境配置可以规范开发流程&#xff0c;并同时提示项目的开发质量和效率等。 一个公司应该规范…

网络使用情况监控

您的网络是否经常成为网络紧张或带宽瓶颈的牺牲品&#xff1f;瓶颈并不一定意味着带宽不足&#xff1b;它们可能是由占用带宽到严重网络威胁等任何因素造成的。密切监控您的网络并分析带宽使用情况和网络流量对于找到问题的根源非常重要。NetFlow Analyzer是最先进的网络使用情…

Simulink自动化-Matlab脚本自动创建Runnable及mapping

文章目录前言设计Excel模板编写matlab脚本自动添加Function到Simulink模型自动mapping Function与Runnable总结前言 在之前的一篇文章中&#xff0c;介绍了Autosar S/R接口的自动创建及mapping,传送门&#xff1a;Simulink自动化-Matlab脚本自动生成Autosar RTE S/R接口及mapp…

mujoco安装及urdf转xml方法记录

参考 mujoco210及mujoco-py安装 下载适用于Linux或 OSX的 MuJoCo 2.1 版二进制文件 。 将mujoco210的下载的目录解压到~/.mujoco/mujoco210路径下. 注意&#xff1a;如果要为包指定非标准位置&#xff0c;请使用环境变量MUJOCO_PY_MUJOCO_PATH。 验证是否安装成功&#xff08…

springcloud3 fegin服务超时的配置和日志级别的配置2

一 fegin的概述 1.1 fegin的默认超时时间 默认fegin客户端只等待1秒钟&#xff0c;超过1秒钟&#xff0c;直接会返回错误。 1.2 架构图 1.2.1 说明 1.2.2 启动操作 1.先启动9001,9002 eureka 2.启动9003 服务提供者 3.启动9006消费者 1.3 情况验证 1.3.1 正常默认情…

云端IDE:TitanIDE v2.6.0 正式发布

原文作者&#xff1a;行云创新技术总监 邓冰寒 引言 云原生集成开发环境 TitanIDE v2.6.0 正式发布了&#xff0c;一起来看看都有那些全新的体验吧&#xff01; TitanIDE 是一款云IDE, 也称 CloudIDE&#xff0c;作为数字化时代研发体系不可或缺的一环&#xff0c;和企业建设…

C++网络编程(一)本地socket通信

C网络编程(一) socket通信 前言 本次内容简单描述C网络通信中&#xff0c;采用socket连接客户端与服务器端的方法&#xff0c;以及过程中所涉及的函数概要与部分函数使用细节。记录本人C网络学习的过程。 网络通信的Socket socket,即“插座”,在网络中译作中文“套接字”,应…

项目管理必备:如何绘制一份优秀的甘特图?

本文一共分为两部分—— 分享60Excel甘特图模板&#xff0c;简单省事儿分享两种甘特图制作教程&#xff0c;高效快捷 第一部分——60甘特图模板 分享一些项目管理甘特图的模板&#xff0c;省事儿&#xff01;高效&#xff01;简单&#xff01; Excel甘特图表模板自取&#xf…

Laya小游戏开发,laya3D美术篇——1——关于laya自带的几个shader的基础运用讲解。

最近三年&#xff0c;基本上做的都是laya小游戏项目。也就是微信小程序&#xff0c;很多业内同行都觉得laya做小游戏不好用&#xff0c;去用了其他平台&#xff0c;甚至还有些做app游戏的&#xff0c;都不来趟laya这个坑。原因有那么以下几点。laya对于unity的辅助开发&#xf…

STM32的USART发送数据时如何使用TXE和TC标志

8人从A出发去D旅游的故事 STM32的USART发送数据时如何使用TXE和TC标志 TXE为1&#xff1a; TDR里的数据全部移到了移位寄存器&#xff0c;且没有新的数据TDR。 TXE为0&#xff1a; TDR里有数据&#xff0c;未空&#xff0c;则TXE0。 TC为1&#xff1a; 从TDR过来的数据全部被移…

一文上手图数据备份恢复工具 NebulaGraph BR

作者&#xff1a;NebulaGraph 工程师 Kenshin NebulaGraph BR 开源已经有一段时间了&#xff0c;为了给社区用户提供一个更稳、更快、更易用的备份恢复工具&#xff0c;去年对其进行了比较大的重构。NebulaGraph BR&#xff08;以下简称 BR&#xff09;分为社区版和企业版两个版…

Git学习笔记(五)——分支

一、创建与合并分支创建分支&#xff1a;Git创建一个分支很快&#xff0c;因为除了增加一个指针&#xff0c;改改HEAD的指向&#xff0c;工作区的文件都没有任何变化。合并分支&#xff1a;就是直接把master&#xff08;其中一条分支&#xff09;指向dev&#xff08;另一条分支…

python基础条件循环语句

1、编写代码完成一个名片显示,要求使用取消换行和格式化操作符 # 编写代码完成一个名片显示,要求使用取消换行和格式化操作符name 张三 qq 1234567 phone_num 1234567 com_address 北京print(\n 姓名:%s\n qq:%d\n 手机号:%d\n 公司地址:%s\n %(name ,qq ,phone_num, com_…