Stack详解(含动画演示)

目录

  • Stack详解
    • 1、栈数据结构动画演示
    • 2、Stack的继承体系
    • 3、Stack的push (入栈)方法
    • 4、Stack的pop (出栈)方法
    • 5、Stack是如何利用Vector实现栈数据结构的?
    • 6、自己实现栈(不借助JDK提供的集合)
    • 7、自己实现栈(借助JDK提供的集合)
      • 利用 ArrayDeque 实现高性能的非线程安全的栈。
      • 利用 ConcurrentLinkedDeque实现高性能的线程安全的栈。
    • 8、栈的应用场景
      • ①、浏览器的前进、后退功能实现
      • ②、表达式求值 (实现简单计算器功能)
      • ③、语法解析
      • ④、栈排序
      • ⑤、实现特殊算法

Stack详解

基于JDK8

1、栈数据结构动画演示

在这里插入图片描述

从动画中可以看出,栈这种数据结构 只能从同一头进,从同一头出。
比较正式的说法叫: 后进先出(LIFO, Last In First Out)。

2、Stack的继承体系

public class Stack<E> extends Vector<E>

在这里插入图片描述
可以看到JDK提供的 Stack 是利用 Vector实现的。所以Stack 类也是线程安全的。

3、Stack的push (入栈)方法

public E push(E item) {
    // 调用 addElement 方法将 item 添加到 Vector 的末尾
    addElement(item);

    // 返回被添加的元素 item
    return item;
}
public synchronized void addElement(E obj) {
    // 修改计数器增加,用于记录 Vector 被修改的次数
    modCount++;

    // 确保 Vector 有足够的容量来容纳新元素
    ensureCapacityHelper(elementCount + 1);

    // 将新元素添加到 elementData 数组的 elementCount 位置
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
    // 检查是否需要扩容,如果当前容量小于 minCapacity 则进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}


protected Object[] elementData;

private void grow(int minCapacity) {
    // 当前数组的容量
    int oldCapacity = elementData.length;

    // 新的容量,如果 capacityIncrement 大于 0 则按此增量扩容,否则容量翻倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

    // 确保新的容量不小于最小所需的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    // 检查新的容量是否超过最大允许的数组大小
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // 将元素数组扩容到新的容量
    elementData = Arrays.copyOf(elementData, newCapacity);
}

上面只有push方法是Stack提供的,其余方法都是Vector类提供的。

4、Stack的pop (出栈)方法

public synchronized E pop() {
    E obj;
    int len = size();

    // 获取栈顶元素
    obj = peek();

    // 移除栈顶元素
    removeElementAt(len - 1);

    // 返回被移除的栈顶元素
    return obj;
}

public synchronized E peek() {
    int len = size();

    // 如果栈为空,抛出 EmptyStackException 异常
    if (len == 0)
        throw new EmptyStackException();

    // 返回栈顶元素(位于 elementData 数组的最后一个有效位置)
    return elementAt(len - 1);
}

public synchronized void removeElementAt(int index) {
    // 增加修改计数器
    modCount++;

    // 如果索引超出当前元素数量,抛出 ArrayIndexOutOfBoundsException 异常
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    }
    // 如果索引为负数,抛出 ArrayIndexOutOfBoundsException 异常
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }

    // 计算需要移动的元素个数
    int j = elementCount - index - 1;

    // 如果有需要移动的元素
    if (j > 0) {
        // 将 elementData 数组中 index + 1 到 elementCount - 1 的元素左移一位
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }

    // 减少元素数量
    elementCount--;

    // 将最后一个元素设为 null 以便垃圾回收
    elementData[elementCount] = null;
}

pop (出栈)方法利用到了peek(获取栈顶元素)方法。

5、Stack是如何利用Vector实现栈数据结构的?

上面已经用动画演示了栈数据结构的特点:栈这种数据结构 只能从同一头进,从同一头出。

栈的主要操作包括以下几种:

  • 压栈(push):将元素添加到栈的顶部。
  • 出栈(pop):移除并返回栈顶元素。
  • 查看栈顶元素(peek):返回栈顶元素但不移除它。

Stack 类通过继承 Vector,并添加自己的方法,实现了这些操作。
push 方法
push 方法将元素压入栈顶。它实际上调用了 Vector 的 addElement 方法,将元素添加到 Vector 的末尾

pop 方法
pop 方法移除并返回栈顶元素。它先调用 peek 方法获取栈顶元素,然后调用 removeElementAt 方法移除该元素。

peek 方法
peek 方法返回栈顶元素但不移除它。它直接访问 Vector 的最后一个元素。

Vector和ArrayList类似,底层都是使用Object数组来存储元素的。Stack通过操作数组的末尾,比如push只把元素添加到数组的末尾,pop只移除数组末尾的元素。

在这里插入图片描述

6、自己实现栈(不借助JDK提供的集合)

主要利用Object 数组和Arrays.copyOf方法

class MyStack<T>{

    // 默认容量
    private final Integer defaultCapacity = 10;
    // 存储元素的数组
    private Object[] elementData;
    // 真正存储的元素个数
    private int elementCount;

    public MyStack() {
        this.elementData = new Object[10];
    }


    public void push(T element){
        // 元素个数大于默认容量就复制数组
        if(gtDefaultCapacity(elementCount + 1)){
            // 创建一个新的数组,长度为当前数组长度加1
            Object[] newElements = Arrays.copyOf(elementData, elementCount + 1);
            newElements[elementCount++] = element;
            elementData = newElements;
        }else {
            // 否则就直接添加元素到数组末尾
            this.elementData[elementCount++] = element;
        }
    }

    // 获取栈顶元素
    @SuppressWarnings(value = "unchecked")
    public T peek(){
        if(elementCount<=0){
            throw new RuntimeException("当前栈为空!");
        }
        return (T)elementData[elementCount-1];
    }

    // 删除栈顶元素
    @SuppressWarnings(value = "unchecked")
    public T pop(){
        if(elementCount<=0){
            throw new RuntimeException("当前栈为空!");
        }
        Object temp = elementData[elementCount - 1];
        elementData = Arrays.copyOf(elementData, elementCount - 1);
        elementCount--;
        return (T) temp;
    }


    // 判断是否大于默认容量
    private boolean gtDefaultCapacity(int length){
        return length > defaultCapacity;
    }

    // 获取栈大小
    public int size(){
        return elementCount;
    }

    // 判断栈是否为空
    public boolean isEmpty(){
        return elementCount <= 0;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (int i =elementCount - 1 ; i >= 0; i--) {
            builder.append(elementData[i].toString()).append("  ");
        }
        return builder.toString();
    }

}

测试:

public static void main(String[] args) throws Exception {
        MyStack<String> myStack = new MyStack<>();
        myStack.push("秀逗");
        myStack.push("四眼");
        myStack.push("大黄");

        System.out.println(myStack.peek());  //  大黄
        System.out.println(myStack.pop());  // 大黄
        System.out.println(myStack.size()); // 2
        System.out.println(myStack.isEmpty()); // false
		System.out.println(myStack);  // 四眼  秀逗

        System.out.println(myStack.pop()); //  四眼  
        System.out.println(myStack.pop());//  秀逗 
        System.out.println(myStack.pop()); // java.lang.RuntimeException: 当前栈为空!
       
    }

我们利用Object 数组和Arrays.copyOf方法实现了一个简单的栈,这个栈包含基本的 push、peek、pop、size、isEmpty方法,并模拟出栈顺序重写了 toString。

7、自己实现栈(借助JDK提供的集合)

上面我们自己利用Object 数组和Arrays.copyOf方法实现了简单的栈,我们默认设置了10的容量,如果超过10个元素,我们利用Arrays.copyOf完成数组的复制。 这样做是非常耗费性能的,并且我们自己实现的栈是非线程安全的。
下面我们利用JDK现有的集合来实现栈。

利用 ArrayDeque 实现高性能的非线程安全的栈。

class MyStack<T>{
	// 使用 ArrayDeque 作为底层数据结构来存储栈元素
    private ArrayDeque<T> arrayDeque;

	// 构造方法,初始化 ArrayDeque
    public MyStack() {
        this.arrayDeque = new ArrayDeque<T>();
    }
    
	// 将元素压入栈顶,使用 addFirst 方法将元素添加到双端队列的头部
    public void push(T elementData){
        arrayDeque.addFirst(elementData);
    }


    public void peek(){
        arrayDeque.getFirst();
    }

    public T pop(){
       return arrayDeque.removeFirst();
    }

    public int size(){
        return arrayDeque.size();
    }

    public boolean isEmpty(){
        return arrayDeque.isEmpty();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        // 这里不需要反序迭代  因为我们的push是往头部添加元素 直接正序迭代就是出栈顺序
        Iterator<T> iterator = arrayDeque.iterator();
        while (iterator.hasNext()){
            builder.append(iterator.next().toString()).append("  ");
        }
        return builder.toString();
    }
}

利用 ConcurrentLinkedDeque实现高性能的线程安全的栈。

class MyStack<T>{

    private ConcurrentLinkedDeque<T> concurrentLinkedDeque;

    public MyStack() {
        this.concurrentLinkedDeque = new ConcurrentLinkedDeque<T>();
    }

    public void push(T elementData){
        concurrentLinkedDeque.addFirst(elementData);
    }

    public void peek(){
        concurrentLinkedDeque.getFirst();
    }

    public T pop(){
       return concurrentLinkedDeque.removeFirst();
    }

    public int size(){
        return concurrentLinkedDeque.size();
    }

    public boolean isEmpty(){
        return concurrentLinkedDeque.isEmpty();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        // 这里不需要反序迭代  因为我们的push是往头部添加元素 直接正序迭代就是出栈顺序
        Iterator<T> iterator = concurrentLinkedDeque.iterator();
        while (iterator.hasNext()){
            builder.append(iterator.next().toString()).append("  ");
        }
        return builder.toString();
    }
}

ConcurrentLinkedDeque 的高性能线程安全设计,涉及到乐观并发控制策略比如CAS,延迟回收,分段锁等机制。
后面总结并发编程知识的时候再详细分析下ConcurrentLinkedDeque 的设计。

8、栈的应用场景

栈虽然是一种后进先出(LIFO, Last In First Out)的数据结构,但是却有广泛的应用场景。
下面是几个常见的例子:

①、浏览器的前进、后退功能实现

在这里插入图片描述
可以用两个栈分别存储前进和后退的页面
代码示例:

import java.util.Stack;

public class TestA {
    public static void main(String[] args) {
        Browser browser = new Browser();
        browser.visit("百度");
        browser.visit("谷歌");

        browser.back();  //  百度
        browser.forward();  // 谷歌
    }
}


class Browser {
    // 保存前进页面的栈,用于实现前进功能
    private Stack<String> forwardStack;
    // 保存后退页面的栈,用于实现后退功能
    private Stack<String> backStack;
    // 当前浏览的页面
    private String currentPage;

    // 初始化前进栈、后退栈和当前页面
    public Browser() {
        this.forwardStack = new Stack<>();
        this.backStack = new Stack<>();
        // 初始化默认页面为 "Home"
        this.currentPage = "Home";
    }

    /**
     * 访问新页面
     * @param page 新访问的页面
     */
    public void visit(String page) {
        if (page == null) {
            throw new RuntimeException("页面不能为空");
        }
        // 将当前页面加入后退栈,因为我们将访问新的页面
        backStack.push(currentPage);
        // 更新当前页面为新访问的页面
        currentPage = page;
        // 打印当前浏览页面
        System.out.println("当前浏览页面:" + currentPage);
        // 清空前进栈,因为访问新页面时前进栈失效
        forwardStack.clear();
    }

    /**
     * 前进到下一页面
     */
    public void forward() {
        System.out.println("前进");
        // 如果前进栈不为空
        if (!forwardStack.isEmpty()) {
            // 将当前页面加入后退栈
            backStack.push(currentPage);
            // 从前进栈弹出页面作为当前页面
            currentPage = forwardStack.pop();
            // 打印当前浏览页面
            System.out.println("当前浏览页面:" + currentPage);
        } else {
            // 前进栈为空,无法前进
            System.out.println("当前无前进页面");
        }
    }

    /**
     * 后退到上一页面
     */
    public void back() {
        System.out.println("后退");
        // 如果后退栈不为空
        if (!backStack.isEmpty()) {
            // 将当前页面加入前进栈
            forwardStack.push(currentPage);
            // 从后退栈弹出页面作为当前页面
            currentPage = backStack.pop();
            // 打印当前浏览页面
            System.out.println("当前浏览页面:" + currentPage);
        } else {
            // 后退栈为空,无法后退
            System.out.println("当前无后退页面");
        }
    }

    /**
     * 获取当前浏览的页面
     * @return 当前页面
     */
    public String getCurrentPage() {
        return currentPage;
    }
}

运行结果:

当前浏览页面:百度
当前浏览页面:谷歌
后退
当前浏览页面:百度
前进
当前浏览页面:谷歌

动画示例
在这里插入图片描述

②、表达式求值 (实现简单计算器功能)

栈常用于表达式求值,如中缀表达式转后缀表达式(逆波兰表达式)和逆波兰表达式求值。

先了解几个表达式的基本概念:
一般来说,数学表达式的表示方法主要有以下三种:

中缀表达式 (Infix Notation)
前缀表达式 (Prefix Notation) 又叫 波兰式 (Polish Notation)
后缀表达式 (Postfix Notation) 又叫 逆波兰式 (Reverse Polish Notation)

表达式类型示例直观性优先级标识计算机实现别名
中缀表达式(A + B) * C需要括号复杂
前缀表达式* + A B C不需要简单波兰式
后缀表达式A B + C *不需要简单逆波兰式

特点比较:

  • 中缀表达式 是我们日常使用的表达方式,运算符位于操作数之间,直观但需要括号来明确优先级,计算机实现较复杂。
  • 前缀表达式(波兰式)运算符位于操作数之前,不需要括号来确定优先级,适合树结构计算,计算机实现简单但不直观。
  • 后缀表达式(逆波兰式)运算符位于操作数之后,同样不需要括号来确定优先级,适合栈结构计算,计算机实现简单但不直观。

我们可以利用栈实现计算器功能 (实际上就是中缀表达式转后缀表达式,再利用栈实现后缀表达式的计算):

import java.util.Scanner;
import java.util.Stack;

public class TestA {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("请输入一个中缀表达式 (输入 'exit' 退出): ");
            String infixExpression = scanner.nextLine();

            if (infixExpression.equalsIgnoreCase("exit")) {
                break; // 输入 'exit' 退出循环
            }

            try {
                String postfixExpression = infixToPostfix(infixExpression);
                System.out.println("后缀表达式: " + postfixExpression);
                double result = evaluatePostfix(postfixExpression);
                if (result % 1 == 0) { // 判断结果是否为整数
                    System.out.println("计算结果: " + (int) result); // 整数结果去掉小数部分
                } else {
                    System.out.println("计算结果: " + result);
                }
            } catch (Exception e) {
                System.out.println("表达式无效: " + e.getMessage());
            }
        }
        scanner.close();
    }

    // 中缀表达式转后缀表达式
    public static String infixToPostfix(String infix) {
        Stack<Character> stack = new Stack<>();
        StringBuilder postfix = new StringBuilder();

        for (int i = 0; i < infix.length(); i++) {
            char ch = infix.charAt(i);

            if (Character.isDigit(ch)) {
                // 处理多位数字和小数
                while (i < infix.length() && (Character.isDigit(infix.charAt(i)) || infix.charAt(i) == '.')) {
                    postfix.append(infix.charAt(i++));
                }
                postfix.append(' ');
                i--; // 调整索引,以便下一次循环从正确位置开始
            } else if (ch == '(') {
                stack.push(ch);
            } else if (ch == ')') {
                // 将栈中的元素弹出直到遇到 '('
                while (!stack.isEmpty() && stack.peek() != '(') {
                    postfix.append(stack.pop()).append(' ');
                }
                stack.pop(); // 弹出 '('
            } else if (isOperator(ch)) {
                // 当栈不为空且当前运算符的优先级小于等于栈顶运算符的优先级时,弹出栈顶运算符并加入后缀表达式
                while (!stack.isEmpty() && precedence(stack.peek()) >= precedence(ch)) {
                    postfix.append(stack.pop()).append(' ');
                }
                stack.push(ch); // 将当前运算符入栈
            }
        }

        // 将栈中剩余的运算符全部加入后缀表达式
        while (!stack.isEmpty()) {
            postfix.append(stack.pop()).append(' ');
        }

        return postfix.toString().trim(); // 返回后缀表达式
    }

    // 计算后缀表达式
    public static double evaluatePostfix(String postfix) {
        Stack<Double> stack = new Stack<>();

        for (int i = 0; i < postfix.length(); i++) {
            char ch = postfix.charAt(i);

            if (Character.isDigit(ch)) {
                // 处理多位数字和小数
                StringBuilder number = new StringBuilder();
                while (i < postfix.length() && (Character.isDigit(postfix.charAt(i)) || postfix.charAt(i) == '.')) {
                    number.append(postfix.charAt(i++));
                }
                stack.push(Double.parseDouble(number.toString())); // 将数字入栈
                i--; // 调整索引,以便下一次循环从正确位置开始
            } else if (isOperator(ch)) {
                double b = stack.pop();
                double a = stack.pop();
                // 根据运算符进行计算并将结果入栈
                switch (ch) {
                    case '+': stack.push(a + b); break;
                    case '-': stack.push(a - b); break;
                    case '*': stack.push(a * b); break;
                    case '/': stack.push(a / b); break;
                }
            }
        }

        return stack.pop(); // 返回计算结果
    }

    // 判断是否是运算符
    public static boolean isOperator(char ch) {
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    // 返回运算符优先级
    public static int precedence(char ch) {
        switch (ch) {
            case '+':
            case '-': return 1;
            case '*':
            case '/': return 2;
        }
        return -1; // 如果不是运算符,则返回 -1
    }
}

输入:3*(1+2)
输出:

后缀表达式: 3 1 2 + *
计算结果: 9

栈在计算后缀表达式结果时的步骤:

  • 1.创建一个空栈,用于存储操作数。
  • 2.从左到右遍历后缀表达式的每个字符。
  • 3.如果当前字符是操作数(数字),则将其转换为对应的数值,并压入栈中。
  • 4.如果当前字符是运算符(+、-、*、/):
          从栈中弹出两个操作数,分别记为 a 和 b(注意:b 是栈顶元素,a 是次顶元素)。
          根据当前运算符进行计算:根据后缀表达式的特性,b 是操作符 a 的右操作数,a 是左操作数。
  • 5.将计算结果压入栈中。
  • 6.遍历完后缀表达式后,栈中剩余的唯一元素即为最终的计算结果。

计算过程动画演示:
在这里插入图片描述

③、语法解析

括号匹配

import java.util.Stack;

public class TestA {

    public static void main(String[] args) {
        String s = "{[(1234)]}";
        // 调用isValid方法并输出结果,应该输出true,因为输入的括号字符串是有效的
        System.out.println(isValid(s)); // 输出: true
    }

    // 判断括号字符串是否有效的方法
    public static boolean isValid(String s) {
        // 创建一个栈来存放左括号
        Stack<Character> stack = new Stack<>();

        // 遍历输入的字符串中的每一个字符
        for (char c : s.toCharArray()) {
            // 如果是左括号,则压入栈中
            if (c == '(' || c == '{' || c == '[') {
                stack.push(c);
            } else if (c == ')' || c == '}' || c == ']') { // 只处理右括号
                // 如果是右括号且栈为空,说明没有匹配的左括号,返回false
                if (stack.isEmpty()) return false;

                // 弹出栈顶的左括号
                char top = stack.pop();
                // 判断栈顶的左括号和当前的右括号是否匹配,如果不匹配,返回false
                if (c == ')' && top != '(' || c == '}' && top != '{' || c == ']' && top != '[') {
                    return false;
                }
            }
        }

        // 如果栈为空,说明所有的括号都匹配,返回true;否则返回false
        return stack.isEmpty();
    }
}

④、栈排序

用一个栈对数据进行排序

import java.util.*;

public class TestA {

    public static void main(String[] args) {
        // 创建一个LinkedList并添加一些整数元素
        List<Integer> intCollection = new ArrayList<>();
        intCollection.add(5);
        intCollection.add(6);
        intCollection.add(3);
        intCollection.add(1);
        intCollection.add(2);
        intCollection.add(4);

        // 输出排序前的整数集合
        System.out.println("整数集合排序前: " + intCollection);
        // 调用sortCollection方法对集合进行排序
        Collection<Integer> sortedIntCollection = sortCollection(intCollection);
        // 输出排序后的整数集合
        System.out.println("整数集合排序后: " + sortedIntCollection);

    }

    // 对集合进行排序的方法,使用泛型并要求元素实现Comparable接口
    public static <T extends Comparable<T>> Collection<T> sortCollection(Collection<T> collection) {
        // 创建一个栈用于排序
        Stack<T> stack = new Stack<>();
        // 将集合中的元素压入栈中
        for (T element : collection) {
            stack.push(element);
        }

        // 创建一个临时栈用于辅助排序
        Stack<T> tempStack = new Stack<>();
        // 当输入栈不为空时进行排序
        while (!stack.isEmpty()) {
            // 弹出输入栈的栈顶元素
            T temp = stack.pop();
            // 将临时栈中所有大于temp的元素弹出并压入输入栈
            while (!tempStack.isEmpty() && tempStack.peek().compareTo(temp) < 0) {
                stack.push(tempStack.pop());
            }
            // 将temp压入临时栈
            tempStack.push(temp);
        }

        // 将临时栈中的元素全部弹出并存入一个新集合
        Collection<T> sortedCollection = new LinkedList<>();
        while (!tempStack.isEmpty()) {
            sortedCollection.add(tempStack.pop());
        }

        return sortedCollection;
    }
}

⑤、实现特殊算法

深度优先搜索(DFS)
二叉树的非递归前序遍历

回溯算法
例如求解迷宫问题、N皇后问题等。
算法类的暂时先不在这边篇文章内写了。后续会单独再写一些相关的内容。

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

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

相关文章

SyntaxError: Non-UTF-8 code starting with ‘\xbd‘ in file错误解决

在运用python的pandas和numpy的内容环境下&#xff0c;运行代码时发生以下错误&#xff1a; C:\ProgramData\Anaconda3\python.exe D:/zafile/py数据分析与应用/数据分析代码/14.2、紧急电话数据分析.pyFile "D:/zafile/py数据分析与应用/数据分析代码/14.2、紧急电话数据…

7.枚举和模式匹配

一、enum枚举 1.1 定义枚举类型和对应的数据 //定义枚举 #[derive(Debug)] enum IpAddrKind{IPv4,IPv6, }struct Ipaddr{kind: IpAddrKind, //设置kind为IpAddrKind的枚举类型address: String, }fn route(ip_addr: &Ipaddr){println!("ip_type {:#?}", ip_a…

如何基于Nginx配置代理服务器实现邮件告警

当代企业信息化系统建设中&#xff0c;将内网与公网进行隔离是一种非常常见的措施&#xff0c;它可以有效保护企业内部数据不被外泄&#xff0c;有助于企业构建一个更加安全的网络环境&#xff0c;保护企业资产和用户隐私。但另一方面&#xff0c;内网与公网隔离也会带来一些问…

【STM32】输入捕获应用-测量脉宽或者频率(方法2)

链接&#xff1a;https://blog.csdn.net/gy3509/article/details/139629893?spm1001.2014.3001.5502&#xff0c;讲述了只使用一个捕获寄存器测量脉宽和频率的方法&#xff0c;其实测量脉宽和频率还有一个更简单的方法就是使用PWM输入模式&#xff0c;PWM输入模式需要占用两个…

推挽式B类功率放大器的基本原理

单晶体管 B 类放大器&#xff08;图 1&#xff09;使用高 Q 值储能电路作为负载来抑制高次谐波分量。通过采用高 Q 谐振电路&#xff0c;输出电压仅包含基波分量&#xff0c;使放大器能够忠实地再现输入信号。尽管集电极电流是半波整流正弦波&#xff0c;但高 Q 值储能电路将谐…

Python基础教程(十):装饰器

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

Non-aligned Supervision for Real Image Dehazing

原文链接&#xff1a;https://www.semanticscholar.org/paper/Non-aligned-supervision-for-Real-Image-Dehazing-Fan-Guo/7595d39e71ae58343e8728fc1af0e18ffe38218b 数据集&#xff1a;https://www.cityscapes-dataset.com/ 真实的图像去雾的非对准监督 摘要 由于天气条件…

淘宝评论数据信息接口

淘宝评论API接口是一种用于获取淘宝用户评论信息的接口&#xff0c;联讯数据可以帮助商家和消费者获取到商品的评价信息&#xff0c;以便更好地了解商品的质量和用户体验。以下是关于淘宝评论API接口的一些内容&#xff1a; 一、接口功能 淘宝评论API接口提供了商品评价信息的…

doc 和 docx 文件的区别

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

第32章-SDN概述

1. SDN的由来 2. SDN的基本概念 1. SDN的由来 计算机产业的发展&#xff1a; 从 1946 年第一代电子管计算机面世到如今&#xff0c;计算机的形态也发生了翻天覆地的变化。从大型机到个人 PC&#xff0c;计算机在不断地推陈出新&#xff0c;且创新发展的势头越加猛烈。究其原因…

信号处理中的相位

相位 用来描述波动或振动状态。 在信号处理和通信领域&#xff0c;相位通常指的是信号相对于某一参考信号的延迟。 在周期性信号中&#xff0c;相位通常以角度&#xff08;弧度或度&#xff09;来表示&#xff0c;表示信号的周期性变化相对于参考信号的位置。 在非周期性信号中…

教你一招,告警恢复时如何拿到恢复时的值?

Prometheus 生态的原生做法&#xff0c;由于阈值是放在 promql 中的&#xff0c;恢复时的消息中难以拿到恢复时的值&#xff0c;夜莺 v7.0.0.beta10 版本开始&#xff0c;提供了一种较为简单的内置方式&#xff0c;解决这个问题。下面我们就来看一下如何实现这个能力。 升级方…

医疗设备维修培训服务的安全性和可靠性

当前&#xff0c;医疗设备维修服务行业市场已经形成了一个庞大的产业链&#xff0c;涵盖了设备检测、故障诊断、维修维护等多个环节。随着医疗设备的日益复杂和高端&#xff0c;对专业维修服务的需求也在不断增加。因此&#xff0c;市场上涌现出了一批专业的医疗设备维修服务提…

Anaconda3 下载安装卸载

1、下载 官网链接&#xff1a;Download Now | Anaconda Step1&#xff1a;进入官网 Anaconda | The Operating System for AI Step2&#xff1a;进入下载页面&#xff0c;选择要Anaconda软件安装包 2、安装 Step1: 点击 Anaconda3-2024.02-1-Windows-x86_64.exe 安装包进行安…

Windows 11中查找和删除旧文件的几种方法,总有一种适合你

序言 如果你的电脑存储空间不足,最好的办法就是找到并删除旧的、不需要的文件。Windows 11提供了多种方法来查找这些占用存储空间的项目,我们将在本指南中向你展示这些方法以及如何使用它们。 使用存储感知 存储感知是Windows 11的内置功能,可帮助自动清理旧文件。你可以…

适合营销的叙事可视化

背景 数据可视化与数据故事化的差异和相似点&#xff0c;以及它们如何协同工作&#xff0c;将你的数据转化为清晰、简洁、可操作的信息&#xff0c;以便您的组织使用。 什么是数据可视化&#xff1f; 数据可视化通过图像传达信息——这是你所收集数据的视觉表示。通过提供原…

(六)React组件通信

理解组件通信 概念&#xff1a;组件通信就是组件之间的数据传递&#xff0c;根据组件嵌套关系不同&#xff0c;有不同的通信方式。 A - B 父子通信B - C 兄弟通信A - E 跨层通信 1. 父传子 – 基础实现 实现步骤&#xff1a; 父组件传递数据 - 在子组件标签上绑定属性子组…

eNSP学习——配置高级的访问控制列表

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建OSPF网络 3、配置Telnet 4、配置高级ACL控制访问 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NSP各种设备配置命令大全PDF版_ensp配置命令大全资源-…

使用Stream实现Web应用,使用YOLOv8模型对图像进行目标检测为例。

Streamlit是一个开源的Python框架&#xff0c;专门设计用于快速构建和共享数据应用程序。它使数据科学家和机器学习工程师能够通过编写简单的Python脚本&#xff0c;轻松创建美观、功能强大的Web应用程序&#xff0c;而无需具备前端开发的经验。 其他框架或web应用可以看下面两…

【全开源】Java无人共享棋牌室茶室台球室系统JAVA版本支持微信小程序+微信公众号

无人共享棋牌室系统——棋牌娱乐新体验 &#x1f3b2;引言 随着科技的不断发展&#xff0c;传统棋牌室正逐渐迈向智能化、无人化。今天&#xff0c;我要为大家介绍的就是这款引领潮流的“无人共享棋牌室系统”。它不仅为棋牌爱好者提供了全新的娱乐体验&#xff0c;更在便捷性…