数据结构与算法-Rust 版读书笔记-2线性数据结构-栈

数据结构与算法-Rust 版读书笔记-2线性数据结构-栈

一、线性数据结构概念

数组、栈、队列、双端队列、链表这类数据结构都是保存数据的容器,数据项之间的顺序由添加或删除时的顺序决定,数据项一旦被添加,其相对于前后元素就会一直保持位置不变,诸如此类的数据结构被称为线性数据结构。

线性数据结构有两端,称为“左”和“右”,在某些情况下也称为“前”和“后”,当然也可以称为顶部和底部,名称不重要,重要的是这种命名展现出的位置关系表明了数据的组织方式是线性的。这种线性特性和内存紧密相关,因为内存就是一种线性硬件,由此也可以看出软件和硬件是如何关联在一起的。

线性数据结构说的并非数据的保存方式,而是数据的访问方式。

线性数据结构不一定代表数据项在内存中相邻。以链表为例,虽然其中的数据项可能在内存的各个位置,但访问是线性的。

区分不同线性数据结构的方法是查看它们添加和移除数据项的方式,特别是添加和移除数据项的位置。例如,一些数据结构只允许从一端添加数据项,另一些则允许从另一端移除数据项,还有的允许从两端操作数据项。这些变种及其组合形式产生了许多在计算机科学领域非常有用的数据结构,它们出现在各种算法中,用于执行各种实际且重要的任务。

1、栈:后进先出

栈是数据项的有序集合,其中,新项的添加和移除总发生在同一端,这一端称为顶部,与之相对的另一端称为底部。栈的底部很重要,因为栈中靠近底部的项是存储时间最长的,最近添加的项最先被移除。这种排序原则有时被称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out,FILO),所以较新的项靠近顶部,较旧的项靠近底部。

2、Rust 预备知识

1、trait

trait类似于Java中的接口,TS 的 interface,C++中的纯虚类,但却又不完全相同。

trait这个单词,本意为特征,在代码中的含义就是,让某个结构体拥有某个特征。

trait Shape {
    fn area(&self) -> f32{
        return 0.0; 
    } //该函数是实现可写可不写,如果不写,那么实现该Trait的结构就必须写,如果这里写了,那么后面实现该trait的结构就可以不写

    fn test(){
        println!("不写self参数,则只能通过 :: 的方式进行调用");
    }
}

struct triangle{ //为了简单,假设其是直角三角形,存放两个直角边
    a: f32,
    b: f32,
}

impl Shape for triangle {
    fn area(&self) -> f32 {
        return (self.a*self.b)/2.0;
    }
}

struct square{
    a: f32
}

impl Shape for square {
    fn area(&self) -> f32 {
        return self.a*self.a;
    }
}

通过 trait Shape,使得 triangle、square 都具有了 area 方法。

调用方式:

fn main() {
    let t=triangle{a: 1.0, b: 2.0};
    let s=square{a:4.0};
    //调用带有self参数的函数
    t.area();
    s.area();
    //调用没有self参数的函数
    triangle::test();
    square::test();
}

其中area函数的参数带有self,也就是要与具体的结构体对应,调用的时候要用.的方式。

另一种调用方式:

fn main() {
	let t=triangle{a: 1.0, b: 2.0};
    let s=square{a:4.0};
    //调用带有self参数的函数
    test_area(&t);
    test_area(&s);
}

fn test_area(shape: &impl Shape){
    shape.area();
}

用 test_area 函数俩输出,这个函数的参数为 &impl Shape,意思是:接受实现了这个 Shape trait 的结构体的引用。

这是不是就和println!宏非常像了!现在只要你的任意形状结构体实现了这个Shape的trait,那么我就能用一个统一的方法(test_area)来输出你的内容!

所以:

只要你自定义了一个结构体,你想要让他可以被println!打印出来,你就得为其实现这个trait

如果要拷贝,那就请你实现Clone这个trait,并且显式的调用clone这个函数,让你自己清楚的认识到此刻你是在完成一个拷贝数据的工作

#[derive(Clone)]
struct Stu{
    name: String,
    age:u32
}


fn main() {
    let s1=Stu{
        name:String::from("yushi-"),
        age:100
    };

    let s2=s1.clone(); //让你能清醒的认识到自己在完成一个拷贝的工作
    println!("{}:{}", s1.name,s1.age); //可用,因为是将内容拷贝给了s2一份
    println!("{}:{}", s2.name,s2.age);
}

最常用的trait,除了CopyClone,还有三个:DebugDefaultPartialEq

其中,Debug是方便我们调试用的:

#[derive(Debug)]
struct Stu{
    name: String,
    age:u32
}
fn main() {
    let s1=Stu{/*省略代码*/};
    println!("{:?}", s1);
}

只要你用了Debug这个trait,那么你就无需实现Display这个trait,也可以方便的打印出相关信息

唯一需要注意的点就是,打印Debug信息,你需要在{}中添加:?

如果你还想要打印格式化后的格式信息,让结构更好看,还可以这样写:

println!("{:#?}", s1);
2、Vec

Vec 是一种动态数组,它可以在运行时自动调整大小。

Vec是Rust标准库的一部分,提供了一种高效、安全的方式来处理大量数据。

基于堆内存申请的连续动态数据类型,其索引、压入(push)、弹出(pop) 操作的时间复杂度为 O(1) 。

Vec 是 vector 的缩写。

Vec的底层实现是基于数组的,因此它的性能非常高。Vec可以存储任何类型的数据,包括整数、浮点数、字符串等。

Vec其实是一个智能指针,用于在堆上分配内存的动态数组。它提供了一些方法来操作数组,如添加、删除和访问元素。与C或Python中的数组不同,Vec会自动处理内存分配和释放,从而避免了常见的内存泄漏和悬挂指针错误。

Vec的本质就是一个三元组,指针、长度、容量,在rust标准库中的定义如下:

pub struct Vec<T, A: Allocator = Global> {
    buf: RawVec<T, A>,
    len: usize,
}
impl<T> Vec<T> {
    #[inline]
    pub const fn new() -> Self {
        Vec { buf: RawVec::NEW, len: 0 }
    }
//...略...
}

Vec的核心功能之一是动态增长和收缩。当向Vec中添加元素时,如果堆上的内存不足,Vec会自动分配更多的内存来容纳元素。这个过程称为“扩容”。同样,当从Vec中删除元素时,如果堆上的内存过多,Vec会自动收缩以释放内存。这个过程称为“缩容”。这种自动内存管理机制使得使用Vec变得非常方便,同时也避免了手动管理内存的错误。

除了基本的添加、删除和访问元素操作之外,Vec还提供了许多其他功能。例如,它们可以按索引访问元素,可以使用迭代器遍历元素,并且支持多种方法(如push()、pop()、insert()和remove())来修改Vec的内容。Vec还提供了一些有用的静态方法(如capacity()、len()和is_empty()),可以用来获取Vec的属性。

虽然Vec是一个非常强大的数据结构,但它们也有一些限制。例如,Vec在堆上分配内存,这意味着访问元素的速度可能会比在栈上分配内存的数组慢。此外,由于Vec是智能指针,因此它们的大小不是固定的,这可能会导致一些编程错误。例如,如果尝试将Vec赋值给一个固定大小的数组或另一个Vec,则会发生编译时错误。

在这里插入图片描述

Vec::new()方法

只创建一个空列表时,必须注明类型(否则通不过编译)。

fn main() {
    let vec: Vec<i32> = Vec::new();
    println!("{:?}", vec);
}
Vec::from()方法
 let vec = Vec::from([1,2,3]);
vec! 宏

用于判断是否相等

fn main() {
    let vec1 = Vec::from([1,2,3]);
    println!("{:?}", vec1);
    let vec2 = vec![1,2,3];
    println!("{:?}", vec2);
    assert_eq!(vec1, vec2);
    assert_eq!(vec1, [1,2,3]);
    assert_eq!(vec2, [1,2,3]);
    println!("{}", vec1 == vec2); // 输出 true
}

创建相同元素 n 的 vec

fn main() {
    let vec = vec![0; 5];
    assert_eq!(vec, [0, 0, 0, 0, 0]);
    println!("{:?}", vec);
    let vec = vec![1; 3];
    assert_eq!(vec, [1, 1, 1]);
    println!("{:?}", vec);
    let vec = vec![1; 0];
}

因为是数组,所以还有 pop、splice、sort 等等数组具有的方法。

3、impl

**impl是一个关键字,用于在类型上实现方法。它是将函数与特定类型(结构体或枚举)关联起来的一种方式。impl**主要有两种用途:

1、实现方法:你可以为特定类型定义方法。然后可以在该类型的实例上调用这些方法。

struct Rectangle {
    width: u32,
    height: u32,
}
 
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

在这个示例中,为**Rectangle结构体实现了一个名为area**的方法,用于计算矩形的面积。

2、实现特质(Traits):Rust中的特质(Trait)类似于其他语言中的接口。它们定义了类型必须提供的功能。使用**impl**,你可以为特定类型实现一个特质,提供特质中定义的必要方法。

trait Describable {
    fn describe(&self) -> String;
}
 
impl Describable for Rectangle {
    fn describe(&self) -> String {
        format!("Rectangle of width {} and height {}", self.width, self.height)
    }
}

在这里,为**Rectangle实现了Describable**特质,提供了描述矩形的具体方式。

impl 块中定义的函数可以是独立的,这意味着将其称为 Foo::bar()。 如果函数以 self&self&mut self 作为它的第一个参数,那么也可以使用方法调用语法调用它,这是任何面向对象的程序员都熟悉的特性,比如 foo.bar ()

4、Self

通常在 Rust 的 trait 和 associated function 中使用 Self 来指代实现该 trait 或调用该 associated function 的类型。

struct Point {
    x: f32,
    y: f32,
}
 
impl Point {
    //关联函数
    fn origin() -> Self {
        Point { x: 0.0, y: 0.0 }
    }
}
 
fn main() {
    let p = Point::origin();
}
 
5、self

self 是一个代表**类型实例(或者是类型的引用或者是值)**的关键字,在 Rust 的方法中使用 self 可以引用当前类型的实例或者类型本身。

具体来说,当我们定义一个方法时,使用 self 关键字作为方法的第一个参数可以让我们在调用该方法时直接访问类型实例本身

struct Point {
    x: f32,
    y: f32,
}
 
impl Point {
    fn distance(&self, other: &Point) -> f32 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy).sqrt()
    }
}
6、 . 和 ::

在Rust中,.::操作符都可以用来调用方法,但它们的用法有所不同。

.操作符用于调用实例方法。实例方法是定义在类型上的方法,它需要一个类型的实例作为第一个参数(通常称为self)。**而实例方法(instance methods)与其他语言中的动态方法(dynamic methods)类似。都需要先声明一个实例后,才可以用的方法。**例如,下面是一个简单的结构体和一个实例方法的示例:

在这里插入图片描述

上面的代码定义了一个名为Point的结构体,它有两个字段xy。然后,我们在impl Point块中定义了一个名为distance_from_origin的实例方法。这个方法接受一个名为self的参数,它表示调用该方法的实例。在这个方法中,我们使用了self.xself.y来访问实例的字段。

main函数中,我们创建了一个名为pPoint实例,并使用.操作符来调用它的实例方法。也就是说,我们使用了语句p.distance_from_origin()来调用该方法。

::操作符则用于调用关联函数。**关联函数也是定义在类型上的函数,但它不需要一个类型的实例作为第一个参数。Rust中的关联函数(associated functions)与其他语言中的静态方法(static methods)类似。**例如,下面是一个简单的结构体和一个关联函数的示例:

在这里插入图片描述

上面的代码定义了一个名为Point的结构体,它有两个字段xy。然后,我们在impl Point块中定义了一个名为new的关联函数。这个函数接受两个参数:x和y,并返回一个新创建的Point实例。

在main函数中,我们使用::操作符来调用Point类型上的关联函数。也就是说,我们使用了语句Point::new(3, 4)来调用该函数。

实例方法通常用于操作类型的实例。例如,您可以定义一个Point结构体,它有两个字段xy,然后定义一个实例方法来计算点到原点的距离。这个方法需要一个Point类型的实例作为第一个参数,然后使用这个实例的字段来进行计算。

关联函数通常用于执行与类型相关但不依赖于类型实例的操作。例如,您可以定义一个关联函数来创建一个新的Point实例。这个函数不需要一个Point类型的实例作为第一个参数,而是接受一些参数来初始化新创建的实例。

在选择使用实例方法还是关联函数时,您应该考虑您要执行的操作是否依赖于类型的实例。如果是,则应该使用实例方法;否则,应该使用关联函数。

7、self 和 &self、mut 和 &mut

&self,表示向函数传递的是一个引用,不会发生对象所有权的转移;

self,表示向函数传递的是一个对象,会发生所有权的转移,对象的所有权会传递到函数中。

let b = a;
含义:a绑定的资源A转移给b,b拥有这个资源A

let b = &a;  
含义:a绑定的资源A借给b使用,b只有资源A的读权限

let b = &mut a;  
含义:a绑定的资源A借给b使用,b有资源A的读写权限

let mut b = &mut a;  
含义:a绑定的资源A借给b使用,b有资源A的读写权限。同时,b可绑定到新的资源上面去(更新绑定的能力)

fn do(c: String) {}  
含义:传参的时候,实参d绑定的资源D的所有权转移给c

fn do(c: &String) {}  
含义:传参的时候,实参d将绑定的资源D借给c使用,c对资源D只读

fn do(c: &mut String) {}  
含义:传参的时候,实参d将绑定的资源D借给c使用,c对资源D可读写

fn do(mut c: &mut String) {}  
含义:传参的时候,实参d将绑定的资源D借给c使用,c对资源D可读写。同时,c可绑定到新的资源上面去(更新绑定的能力)

8、Option<T>

Option<T> 是 Rust 中的类型系统,来传播和处理错误的类型。

pub enum Option<T> {
    None,
    Some(T),
}

Option<T>是一个枚举类型,要么是Some<T>,要么是None。这能很好地表达有值和无值两种情况,避免出现Java中的NullPointerException

9、’ 生命周期标记

生命周期用单引号’加字母表示,置于&后,如&'a、&mut 't

10、unwrap

有的时候我们不想处理或者让程序自己处理 Err, 有时候我们只要 OK 的具体值就可以了。

针对这两种处女座诉求, Rust 语言的开发者们在标准库中定义了两个帮助函数 unwrap()expect()

方法原型说明
unwrapunwrap(self):T如果 selfOkSome 则返回包含的值。 否则会调用宏 panic!() 并立即退出程序
expectexpect(self,msg:&str):T如果 selfOkSome 则返回包含的值。 否则调用panic!() 输出自定义的错误并退出

expect() 函数用于简化不希望事情失败的错误情况。而 unwrap() 函数则在返回 OK 成功的情况下,提取返回的实际结果。

unwrap()expect() 不仅能够处理 Result <T,E> 枚举,还可以用于处理 Option <T> 枚举。

fn main(){
   let result = is_even(10).unwrap();
   println!("result is {}",result);
   println!("end of main");
}
fn is_even(no:i32)->Result<bool,String> {
   if no%2==0 {
      return Ok(true);
   } else {
      return Err("NOT_AN_EVEN".to_string());
   }
}

编译运行以上 Rust 代码,输出结果如下

thread 'main' panicked at 'called `Result::unwrap()` on 
an `Err` value: "NOT_AN_EVEN"', libcore\result.rs:945:5
note: Run with `RUST_BACKTRACE=1` for a backtrace
11、'_ 匿名生命周期

Rust 2018 允许你明确标记生命周期被省略的地方,对于此省略可能不清楚的类型。 要做到这一点,你可以使用特殊的生命周期'_,就像你可以用语法 let x:_ = ..;明确标记一个类型一样。

要我们说的话,无论出于什么原因,我们在 &'a str 周围有一个简单的封装:

struct StrWrap<'a>(&'a str);

Rust 版本指南 中文版

3、栈的 Rust 代码实现、运行结果

stack.rs

/*
 * @Description: 
 * @Author: tianyw
 * @Date: 2023-12-10 17:43:34
 * @LastEditTime: 2023-12-10 21:28:31
 * @LastEditors: tianyw
 */
#[derive(Debug)] // Debug 是派生宏的名称,此语句为 Stack 结构体实现了 Debug trait

pub struct Stack<T> { // pub 表示公开的
    size: usize, // 栈大小
    data: Vec<T>, // 栈数据 泛型数组
}

impl<T> Stack<T> { // impl 用于定义类型的实现,如实现 new 方法、is_empty 方法等
    // 初始化空栈
    pub fn new() -> Self { // 指代 Stack 类型
        Self {
            size: 0,
            data: Vec::new() // 初始化空数组
        }
    }

    pub fn is_empty(&self) -> bool {
        0 == self.size // 结尾没有分号,表示返回当前值
    }

    pub fn len(&self) -> usize { // &self 只可读
        self.size // 结尾没有分号 表示返回当前值
    }

    // 清空栈
    pub fn clear(&mut self) { // &mut self 可读、可写
        self.size = 0;
        self.data.clear();
    }

    // 将数据保存在 Vec 的末尾
    pub fn push(&mut self, val:T) {
        self.data.push(val);
        self.size +=1;
    }

    // 在将栈顶减1后,弹出数据
    pub fn pop(&mut self) -> Option<T> {
        if 0 == self.size { return None; }
        self.size -= 1;
        self.data.pop()
    }

    // 返回栈顶数据引用和可变引用
    pub fn peek(&self) -> Option<&T> {
        if 0 == self.size {
            return None;
        }
        self.data.get(self.size - 1) // 不带分号 获取值并返回
    }

    pub fn peek_mut(&mut self) -> Option<&mut T> {
        if 0 == self.size {
            return None;
        }
        self.data.get_mut(self.size - 1)
    }

    // 以下是为栈实现的迭代功能
    // into_iter:栈改变,成为迭代器
    // iter: 栈不变,得到不可变迭代器
    // iter_mut: 栈不变,得到可变迭代器
    pub fn into_iter(self) -> IntoIter<T> {
        IntoIter(self)
    }

    pub fn iter(&self) -> Iter<T> {
        let mut iterator = Iter { stack: Vec::new() };
        for item in self.data.iter() {
            iterator.stack.push(item);
        }
        iterator
    }

    pub fn iter_mut(&mut self) -> IterMut<T> {
        let mut iterator = IterMut { stack: Vec::new() };
        for item in self.data.iter_mut() {
            iterator.stack.push(item);
        }
        iterator
    }

    
}

// 实现三种迭代功能
pub struct IntoIter<T>(Stack<T>);
impl<T:Clone> Iterator for IntoIter<T> {
    type Item = T;
    fn next(&mut self) -> Option<Self::Item> {
        if !self.0.is_empty() {
            self.0.size -= 1;
            self.0.data.pop()
        } else {
            None
        }
    }
}

pub struct Iter<'a,T:'a> { stack: Vec<&'a T>, }
impl<'a,T> Iterator for Iter<'a,T> {
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        self.stack.pop()
    }
}

pub struct IterMut<'a,T:'a> { stack: Vec<&'a mut T> }
impl<'a,T> Iterator for IterMut<'a,T> {
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        self.stack.pop()
    }
}    
  

main.rs

mod stack;
fn main() {
    basic();
    peek();
    iter();

    fn basic() {
        let mut s= stack::Stack::new();
        s.push(1);
        s.push(2);
        s.push(3);
        println!("size:{},{:?}", s.len(), s);
        println!("pop {:?},size {}", s.pop().unwrap(), s.len());
        println!("empty: {}, {:?}", s.is_empty(), s);

        s.clear();
        println!("{:?}", s);
    }

    fn peek() {
        let mut s = stack::Stack::new();
        s.push(1);
        s.push(2);
        s.push(3);

        println!("{:?}", s);
        let peek_mut = s.peek_mut();
        if let Some(top) = peek_mut {
            *top = 4;
        }

        println!("top {:?}", s.peek().unwrap());
        println!("{:?}", s);
    }

    fn iter() {
        let mut s = stack::Stack::new();
        s.push(1);
        s.push(2);
        s.push(3);

        let sum1 = s.iter().sum::<i32>();
        let mut addend = 0;
        for item in s.iter_mut() {
            *item += 1;
            addend += 1;
        }

        let sum2 = s.iter().sum::<i32>();
        println!("{sum1} + {addend} = {sum2}");
        assert_eq!(9, s.into_iter().sum::<i32>());
    }
}

cargo run 运行结果

在这里插入图片描述

这里使用集合容器Vec作为栈的底层实现,因为Rust中的Vec提供了有序集合机制和一组操作方法,只需要选定Vec的哪一端是栈顶就可以实现其他操作了。以下栈实现假定Vec的尾部保存了栈的顶部元素,随着栈不断增长,新项将被添加到Vec的末尾。因为不知道所插入数据的类型,所以采用泛型数据类型T。此外,为了实现迭代功能,这里添加了IntoIter、Iter、IterMut三个结构体,以分别完成三种迭代功能。

应用:括号匹配、加减乘除优先级匹配

// par_checker3.rs
 
fn par_checker3(par: &str) -> bool {
    let mut char_list = Vec::new();
    for c in par.chars() { char_list.push(c); }
 
    let mut index = 0;
    let mut balance = true;
    let mut stack = Stack::new();
    while index < char_list.len() && balance {
        let c = char_list[index];
        // 将开始符号入栈
        if '(' == c || '[' == c || '{' == c {
            stack.push(c);
        }
        // 如果是结束符号,则判断是否平衡
        if ')' == c || ']' == c || '}' == c {
            if stack.is_empty() {
                balance = false;
            } else {
                let top = stack.pop().unwrap();
                if !par_match(top, c) { balance = false; }
            }
        }
        // 非括号字符直接跳过
        index += 1;
    }
    balance && stack.is_empty()
}
 
fn main() {
    let sa = "(2+3){func}[abc]"; let sb = "(2+3)*(3-1";
    let res1 = par_checker3(sa); let res2 = par_checker3(sb);
    println!("sa balanced:{res1}, sb balanced:{res2}");
 // (2+3){func}[abc] balanced:true, (2+3)*(3-1 balanced:false
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[ABAP] Selection Screen 按钮管理

1. 隐藏执行按钮 initialization.data btab type table of sy-ucomm.append ONLI to btab.call function RS_SET_SELSCREEN_STATUSexportingp_status sy-pfkeytablesp_exclude btab.2.添加按钮(Tool Bar) tables: sscrfields.selection-screen begin of line.selection-scre…

Leetcode704二分查找、折半查找(Java实现)

好久没有更新算法题&#xff0c;今天来写一道二分查找的题目。题目要求如下&#xff0c; 那么这道题的解题思路如下&#xff0c;我们寻找的过程是首先去访问数组的中间位置mid&#xff0c;如果nums[mid]大于了targe那么说明&#xff0c;我们要找的数在mid的左半边&#xff0c;…

外包干了3个月,技术退步明显。。。

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

awt中文乱码-Intellij IDEA

乱码的根本原因在于秦始皇嘎太早了&#xff08;bushi 解决方法&#xff1a;肉眼可见的编码设置统一为GBK 1.打开设置找到文件编码 2.肉眼可见的编码统统改成GBK 有人该问了&#xff0c;为什么不改成utf-8&#xff0c;因为awt的编码由操作系统决定&#xff0c;我的是win家庭中…

Faster R-CNN

Faster R-CNN是作者Ross Girshick继Fast R-CNN后的又一力作。同样使用VGG16作推理速度在GPU上达到5fps(包括候选区域的生成)&#xff0c;准确率为网络的backbone&#xff0c;也有进一步的提升。在2015年的ILSVRC以及COCO竞赛中获得多个项目的第一名。 算法流程 右边这部分和Fa…

人体关键点检测3:Android实现人体关键点检测(人体姿势估计)含源码 可实时检测

目录 1. 前言 2.人体关键点检测方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下而上)方法&#xff1a; 3.人体关键点检测模型训练 4.人体关键点检测模型Android部署 &#xff08;1&#xff09; 将Pytorch模型转换ONNX模型 &#xff08;2&#xff09; 将ONNX模型转换…

(纯原创)基于JavaWeb的宠物领养商城(详细源码以及开发设计报告)

摘要 本宠物领养系统以MVC分层为原则&#xff0c;数据持久化使用Mybatis&#xff0c;数据库使用MySQL&#xff0c;这些技术目前相对比较成熟&#xff0c;方便系统的维护与扩展 商城系统包括了宠物领养、用户注册、用户登录、商品查询、商品添加到购物车、删除商品等几大功能…

云贝教育 | 分享课:12月12日周二晚Oracle分享课享来了

Oracle 19c OCM分享课分享主题: Introduction to Clusterware 讲师&#xff1a;郭一军 直播分享平台&#xff1a;云贝教育视频号 时间&#xff1a;12月12日 周二晚 19: 30

广东佛山开房屋租赁发票

我是20223年12月办理的&#xff0c;给大家做个参考。 一、准备材料 &#xff08;如果非房东本人办理&#xff0c;还需要房东签份授权书&#xff0c;多复印几份或者直接签多份&#xff0c;不然会被税务局收走&#xff09; 废话不多说&#xff0c;直接上图。 二、线上预约 附个…

H264帧内预测介绍

4x4 luma宏块的预测模式 4x4 luma宏块有9种预测模式 16x16 luma宏块的预测模式 16 x16 luma宏块有四种预测模式 帧内预测模式信令(Signalling intra prediction modes) 4x4 或者8x8 luma prediction 对4x4或者8x8 luma因为每一个宏块都要指明预测模式,且有9种预测模式可…

孩子还是有一颗网安梦——Bandit通关教程:Level0

&#x1f575;️‍♂️ 专栏《解密游戏-Bandit》 &#x1f310; 游戏官网&#xff1a; Bandit游戏 &#x1f3ae; 游戏简介&#xff1a; Bandit游戏专为网络安全初学者设计&#xff0c;通过一系列级别挑战玩家&#xff0c;从Level0开始&#xff0c;逐步学习基础命令行和安全概念…

java+springboot+ssm学生社团管理系统76c2e

本系统包括前台和后台两个部分。前台主要是展示社团列表、社团风采、社团活动、新闻列表等&#xff0c;前台登录后进入个人中心&#xff0c;在个人中心能申请加入社团、查看参加的社团活动等&#xff1b;后台为管理员与社团负责人使用&#xff0c;应用于对社团的管理及内容发布…

西工大网络空间安全学院计算机系统基础实验二(清楚实验框架及phase_1)

首先&#xff0c;将自己的实验包从Windows系统中使用scp命令传到Linux虚拟机中。而要想传到Linux虚拟机中&#xff0c;第一步就是要确定Linux虚拟机的IP地址&#xff0c;如 图1&#xff1a;确定Linux虚拟机的IP地址 所示。接着使用scp命令将实验包从Windows系统传送到Linux虚拟…

栈(深入理解栈是什么)

这里写目录标题 栈概念栈的初始化栈的溢出函数的栈帧函数的返回 栈 概念 英文&#xff1a;stack&#xff0c;也叫做堆栈。 特点&#xff1a;先进后出。 栈的两个基本操作&#xff0c;也就是入栈和出栈。都是通过SP指针来维护。C语言中的函数的局部变量&#xff0c;传递的实参…

计算机二级Python基本操作题-序号46

Python 函数查询 1. 《卖火柴的小女孩》是丹麦童话故事作家安徒生的一篇童话故事&#xff0c;发表于1846年。主要讲了一个卖火柴的小女孩在富人阖家欢乐、举杯共庆的大年夜冻死在街头的故事。这里给出《卖火柴的小女孩》的一个网络版本文件&#xff0c;文件名为“小女孩.txt”…

IOday8作业

使用消息队列完成两个进程之间相互通信(多进程) #include<myhead.h>//定义结构体 struct buf {long mtype;char mtest[1024]; };#define SIZE (sizeof(struct buf)-sizeof(long))//进程 int main(int argc, const char *argv[]) {//创建keykey_t key1 ftok("/&quo…

系统韧性研究(7)| 韧性系统的16大指导原则

不良事件和条件可能会中断系统&#xff0c;导致系统无法提供必要的功能和服务。正如我在本系列的前几篇文章中所概述的那样&#xff0c;韧性是大多数系统的一个基本质量属性&#xff0c;因为它们提供了关键的能力和服务&#xff0c;尽管存在着不可避免的困难&#xff0c;但这些…

前沿重器[39] | 对话式推荐系统——概念和技术点

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

Java设计模式-单例(Singleton)设计模式的概述及实现

目录 &#x1f436;1 设计模式概述 &#x1f436;2 何为单例模式 &#x1f436;3 实现思路 &#x1f436;4 饿汉式实现代码 &#x1f436;5 懒汉式实现代码 &#x1f436;6 对比两种模式&#xff08;特点、优缺点&#xff09; &#x1f436;7 单例模式的优点及应用场景 &…

Python中的魔力编程:掌握面向对象之道

Python中的面向对象编程 背景&#xff1a; ​ 最近在看一些代码的时候&#xff0c;对类中的一些内置方法不是很懂&#xff0c;因此出一篇文章来细说一下&#xff0c;希望大家看完后对Python中类有一个清楚的认识。 基础铺垫&#xff1a; ​ 面向对象的三个特点&#xff1a;…