Day32-数据结构与算法-树进阶


title: Day32-数据结构与算法-树进阶
date: 2020-11-18 14:46:02
author: 子陌


常用的经典数据结构

  • 如果设计一种数据结构,用来存放整数,要求提供三个接口
    • 获取元素
    • 获取最大值
    • 删除最大值
获取最大值删除最大值添加元素
动态数组/双向链表O(n)O(n)O(1)
有序动态数组/双向链表O(1)O(1)O(n)全排序有点浪费
BBST(平衡二叉树)O(logn)O(logn)O(logn)杀鸡用了牛刀
  • 有没有更优的数据结构来实现:

    堆:获取最大值 O(1)、删除最大值 O(logn)、添加元素 O(logn)。

Top K问题

从海量数据N中找出前K个数据,例如从100w整数中找出最大的100个整数

  • 如果使用排序算法进行全排序,需要O(nlogn)的时间复杂度
  • Top K问题的解法之一:可以利用数据结构“堆”来解决(O(nlogk) k远远小于n)

堆(Heap)

  • 堆也是一种树状的数据结构(不要跟内存模型中的“堆空间”混淆),常见的堆实现有
    • 二叉堆(Binary Heap,完全二叉堆
    • 多叉堆(D-heap、D-ary Heap)
    • 索引堆(Index Heap)
    • 二项堆(Binomial Heap)
    • 斐波那契堆(Fibonacci Heap)
    • 左倾堆(Leftist Heap,左式堆
    • 斜堆(Skew Heap)
  • 堆的一个重要性质:任意节点的值总是 ≥(≤)子节点的值
    • 如果任意节点的值总是 ≥子节点的值,称为:最大堆、大根堆、大顶堆
    • 如果任意节点的值总是≤子节点的值,称为:最小堆、小根堆、小顶堆
  • 由此可见,堆中的元素必须具备可比较性(跟二叉搜索树一样)

最大堆和最小堆

堆的接口设计

package com.zimo.;

/**
 * 堆的公共接口设计
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/11/18 15:14:33
 */
public interface Heap<E> {
    int size();             //元素的数量
    boolean isEmpty();      //是否为空
    void clear();           //清空
    void add(E element);    //添加元素
    E get();                //获得堆顶元素
    E remove();             //删除堆顶元素
    E replace(E element);   //删除堆顶元素的同时插入一个新元素
}

堆的公共抽象类

package com.zimo.;

import java.util.Comparator;

/**
 * 抽象堆 - 基类
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/11/19 10:37
 */
public abstract class AbstractHeap<E> implements Heap<E> {
    protected int size;
    protected Comparator<E> comparator;

    public AbstractHeap() {
        this(null);
    }

    public AbstractHeap(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    // 比较接口
    protected int compare(E e1, E e2){
        return comparator != null ?comparator.compare(e1, e2) : ((Comparable<E>)e1).compareTo(e1);
    }
}

二叉堆

二叉堆的逻辑结构就是一棵完全二叉树,所以也叫完全二叉堆

  • 鉴于完全二叉树的一些特性,二叉堆的底层(物理结构)一般用数组实现即可
  • 索引i的规律(n为元素数量)
    • 如果 i = 0,它是节点
    • 如果 i >0,它的节点编号为floor((i - 1) / 2)
    • 如果 2i + 1 ≤ n - 1,它的子节点索引为2i + 1
    • 如果 2i + 1 > n - 1,它无左子节点
    • 如果 2i + 2 ≤ n - 1,它的子节点索引为2i + 2
    • 如果 2i + 2 > n - 1,它无右子节点
最大堆 - 添加
  • 循环执行以下操作(图中的80简称为node)

    • 如果node > 父节点

      与父节点交换位置

    • 如果node ≤ 父节点,或者node没有父节点

      退出循环

  • 这个过程,叫做上滤(Sift Up)

    • 时间复杂度为:O(logn)

最大堆添加

最大堆 - 删除
  1. 用最后一个节点覆盖根节点

  2. 删除最后一个节点

  3. 循环执行以下操作(图中的43简称为node)

    • 如果node < 子节点

      与最大子节点交换位置

    • 如果node ≥ 子节点,或者node没有子节点

      退出循环

  4. 这个过程,叫做下滤(Sift Down)

    • 时间复杂度为:O(logn)

最大堆删除

最大堆 - 批量建堆(Heapify)
  • 批量建堆,有两种做法
    1. 自上而下的上滤(本质是添加)
    2. 自下而上的下滤(本质是删除)
  • 所有节点的深度之和
    • (完全二叉树)仅仅是叶子节点,就有近n/2个,而且每个叶子节点的深度都是O(logn)级别的
    • 因此,在叶子节点这一块,就达到了O(nlogn)级别
    • O(nlogn)的时间复杂度足以利用排序算法对所有节点进行全排序
  • 所有节点的高度之和
    • 假设是满二叉树,节点总个数为n,树高为h,那么n = 2h - 1
    • 所有节点的树高之和H(n) = 20 * (h - 0) + 21 * (h - 1) + 22 + … +2h - 1 * [h - (h - 1)]
    • H(n) = h * (20+21+22+…+2h-1)-[1 * 21+2 * 22+3 * 23+…+(h - 1) * 2h-1]
    • H(n) = h * (2h - 1) - [(h - 2) * 2h + 2]
    • H(n) = h * 2h - h - h * 2h + 2h+1 - 2
    • H(n) = 2h+1 - h - 2 = 2 * (2h - 1) - h = 2n - h = 2n - log2(n + 1) = O(n)

批量建堆

package com.zimo.;

import java.util.Comparator;
import java.util.Objects;

/**
 * 二叉堆 - 最大堆优化
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/11/19 10:42
 */
public class BinaryHeap_1<E> extends AbstractHeap<E> {
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;

    public BinaryHeap_1() {
        this(null,null);
    }

    public BinaryHeap_1(Comparator<E> comparator) {
        this(null, comparator);
    }

    public BinaryHeap_1(E[] elements,Comparator<E> comparator) {
        super(comparator);
        if (elements == null || elements.length == 0){
            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
        }else {
            size = elements.length;
            int capacity = Math.max(elements.length, DEFAULT_CAPACITY);
            this.elements = (E[]) new Object[capacity];
            for (int i = 0; i < elements.length; i++) {
                this.elements[i] = elements[i];
            }
            heapify();
        }
    }

    public BinaryHeap_1(E[] elements) {
        this(elements, null);
    }

    @Override
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    @Override
    public void add(E element) {
        elementNotNullCheck(element);
        ensuerCapacity(size + 1);
        elements[size++] = element;
        siftUp(size - 1);
    }

    @Override
    public E get() {
        emptyCheck();
        return elements[0];
    }

    @Override
    public E remove() {
        emptyCheck();
        int lastIndex = --size;
        E root = elements[0];
        elements[0] = elements[lastIndex];
        elements[lastIndex] = null;
        siftDown(0);
        return root;
    }

    @Override
    public E replace(E element) {
        elementNotNullCheck(element);
        E root = null;
        if (size == 0) {
            elements[0] = element;  // 如果添加根节点
            size = 1;
        }else {
            root = elements[0];
            elements[0] = element;  // 不是根节点
            siftDown(0);   // 下滤
        }
        return root;
    }

    /**
     * 批量建堆:自上而下的上滤、自下而上的下滤
     */
    private void heapify() {
        // 自上而下的上滤
//        for (int i= 1; i < size; i++){
//            siftUp(i);
//        }

        // 自下而上的下滤
        for (int i = (size >> 1) - 1; i >= 0; i--){
            siftDown(i);
        }
    }

    // 扩容g
    private void ensuerCapacity(int capacity) {
        int oldCapacity = elements.length;      // 旧数组的长度
        if (oldCapacity >= capacity) return;

        // 新容量为旧容量的1.5倍
        int newCapacityLength = oldCapacity + (oldCapacity >> 1);  // 等价于 oldCapacity * 1.5
        E[] newElements = (E[]) new Object[newCapacityLength];
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;
    }

    /**
     * 让index位置的元素上滤
     * @param index
     */
    private void siftUp(int index){
        E element = elements[index];        // 获取要上滤的节点
        while (index > 0) {
            int parentIndex = (index - 1) >> 1;  // 获取父节点 (index - 1) / 2
            E parent = elements[parentIndex];
            if (compare(element, parent) < 0) break;
            // 交换p、e的内容,重新赋值index
            elements[index] = parent;
            index = parentIndex;
        }
        elements[index] = element;
    }

    /**
     * 让index位置的元素下滤
     * @param index
     */
    private void siftDown(int index) {
        E element = elements[index];
        int half = size >> 1;   // 计算非叶子节点数量
        // 必须保证index位置是非叶子节点
        while(index < half) {   // 小于第一个叶子节点的索引(第一个叶子节点的索引 == 非叶子节点的数量)
            // index的节点有2种情况:1.只有左子节点 2.同时有左右节点
            int childIndex = (index << 1) + 1;  // 左子节点 2i + 1
            E childElement = elements[childIndex];

            // 右子节点
            int rightIndex = childIndex + 1;
            if (rightIndex < size && compare(elements[rightIndex], childElement) > 0){
                childElement = elements[childIndex = rightIndex];
            }

            if (compare(element, childElement) >= 0) break;
            // 将子节点存放到index位置
            elements[index] = childElement;
            // 重新设置index
            index = childIndex;
        }
        elements[index] = element;
    }

    // 检测堆是否为空
    private void emptyCheck(){
        if (size == 0){
            throw new IndexOutOfBoundsException("Heap is Empty!");
        }
    }

    // 检测元素是否为空
    private void elementNotNullCheck(E element){
        if (element == null){
            throw new IllegalArgumentException("element must not be null!");
        }
    }
}
利用最大堆实现最小堆
public static void main(String[] args) {
    Integer[] data = {1, 2, 3, 4, 87, 54, 346, 154};
    BinaryHeap_1<Integer> heap = new BinaryHeap_1<>(data, new Comparator<Integer>() {
        @Override
        public int compare(Integer t1, Integer t2) {
            return t2 - t1;		// 最小堆;	t1 - t2 最大堆
        }
    });
    System.out.println(heap.toString());
}
练习:Top K

从海量数据n中找出前k个最大值(使用小顶堆实现)

  1. 新建一个小顶堆
  2. 扫描n个整数
    1. 先将遍历到的前k个数据放入小顶堆中
    2. 从第k + 1个数开始,如果大于堆顶元素,就使用replace操作(删除堆顶元素,将第k + 1个数据添加到堆中)
  3. 扫描完毕剩下的就是最大的前k个数
public static void main(String[] args) {
    // 新建一个小顶堆
    BinaryHeap_1<Integer> heap = new BinaryHeap_1<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer t1, Integer t2) {
            return t2 - t1;	
        }
    });
    // 找出最大的前k个数
    int k = 5;
    Integer[] data = {51, 30, 39, 92, 73, 24, 87, 54, 346, 154};
    for (int i = 0; i < data.length; i++){
        if(heap.size() < k){	// 前k个数添加到小顶堆
            heap.add(data[i])	// logk
        }else if(heap.get() < data[i]){	// k + 1个数大于堆顶元素
            heap.replace(data[i]);	// logk
        }
    }
}

优先级队列(Priority Queue)

  • 优先级队列也是一个队列,因此也是提供以下接口:

    • int size();元素的数量
    • boolean isEmpty();是否为空
    • void enQueue(E element);入队
    • E deQueue();出队
    • E front();获取队列的头元素
    • void clear();清空
  • 普通的队列是FIFO原则,也就是先进先出

  • 优先级队列则是按照优先级高低进行出队,比如将优先级最高的元素作为队头优先出队

优先队列的底层实现

  • 根据优先队列的特点,很容易想到:可以直接利用二叉堆作为优先队列的底层实现
package com.zimo..优先级队列;

import com.zimo..BinaryHeap_1;

import java.util.Comparator;

/**
 * 优先级队列 - 使用堆实现
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/11/27 11:32:40
 */
public class PriorityQueue<E> {
    private BinaryHeap_1<E> heap;

    public PriorityQueue() {
        this(null);
    }

    public PriorityQueue(Comparator<E> comparator) {
        this.heap = new BinaryHeap_1<>(comparator);
    }

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

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

    public void enQueue(E element){
        heap.add(element);
    }

    /**
     * 从队头弹出元素
     * @return 弹出的元素
     */
    public E deQueue(){
        return heap.remove();
    }

    /**
     * 获取队头元素
     * @return 队头元素
     */
    public E front(){
        return heap.get();
    }

    /**
     * 清空队列元素
     */
    public void clear(){
        heap.clear();
    }
}

哈夫曼树

哈夫曼编码(Huffman Coding)

  • 哈夫曼编码,又称为霍夫曼编码,它是现代压缩算法的基础

  • 假设要把字符【ABBBCCCCCCCCDDDDDDEE】转成二进制编码进行传输

    1. 可以转成ASCII编码(6569,10000011000101),但是有点冗长,如果希望编码更短呢?“
      1000001 1000010 1000010 1000010 …

    2. 可以事先约定5个字母对应的二进制

      ABCDE
      000001010011100
      • 对应的二进制编码:000001001001010010010010010010010010011011011011011011100100
      • 一共20个字母,转成了60个二进制位
    3. 如果使用哈夫曼编码,可以压缩至41个二进制位,约为原来长度的68.3%

      • 先计算出每个字母的出现频率(权值,这里直接用出现次数)

        ABCDE
        13862
      • 利用这些权值,构建一棵哈夫曼树(又称为霍夫曼树、最优二叉树)

        1. 以权值作为根节点构建n棵二叉树,组成森林
        2. 在森林中选出两个根节点最小的树合并,作为新树的左右子树,且新树的根节点为其左右子树根节点之和
        3. 从森林中删除刚才选取的2棵树,并将新树加入森林
        4. 重复2、3步骤,直到森林只剩一棵树为止,该树即为哈夫曼树

构建哈夫曼树

构建哈夫曼编码

  • left为0,right为1,可以得出5个字母对应的哈夫曼编码

    ABCDE
    11101100101111
    • 【ABBBCCCCCCCCDDDDDDEE】的哈夫曼编码是:1110110110110000000001010101010101111
  • 总结:

    • n个权值构建出来的哈夫曼树拥有n个叶子节点
    • 每个哈夫曼编码都不是另一个哈夫曼编码的前缀
    • 哈夫曼树是带权路径长度最短的树,权值较大的节点离根较近
    • 带权路径长度:树中所有的叶子节点的权值乘上其到根节点的路径长度。与最终的哈夫曼编码总长度成正比关系。

Trie

  • 如何判断一堆不重复的字符串是否以某个前缀开头?

    • 用Set/Map存储字符串
    • 遍历所有字符串进行判断
    • 时间复杂度O(n)
  • 有没有更优的数据结构实现前缀搜索?

    • Trie
  • Trie也叫做字典树、前缀树(Prefix Tree)、单词查找树

  • Trie搜索字符串的效率主要跟字符串的长度有关

  • 假设使用Trie存储cat、dog、doggy、does、cast、add六个单词

Trie

接口设计与实现

  • int size();元素的数量

  • boolean isEmpty();是否为空

  • boolean contains(String str);是否存在这个元素

  • void add(String str);添加(类似set)

    V add(String str, V value);(类似map)

  • void remove(String str);删除

    V remove(String str);

  • void clear();清空

  • boolean starsWith(String prefix);是否包含prefix前缀

package com.zimo.Trie;

import java.util.HashMap;

/**
 * Trie实现
 *
 * @author Liu_zimo
 * @version v0.1 by 2020/11/27 18:09
 */
public class Trie<V> {
    private int size;
    private Node<V> root;

    public int size(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public void clear(){
        size = 0;
        root = null;
    }

    public V get(String str){
        Node<V> node = node(str);
        return node != null && node.word ? node.value : null;
    }

    public boolean contains(String key){
        Node<V> node = node(key);
        return node != null && node.word;
    }

    public V add(String key, V value){
        keyCheck(key);
        if (root == null){
            root = new Node<>(null);
        }
        Node<V> node = this.root;
        int length = key.length();
        for (int i = 0; i < length; i++) {
            char c = key.charAt(i);
            boolean emptyChildren = node.children == null;
            Node<V> childNode = emptyChildren ? null : node.children.get(c);
            if (childNode == null) {
                childNode = new Node<>(node);
                childNode.character = c;
                node.children = emptyChildren ? new HashMap<>() : node.children;
                node.children.put(c, childNode);
            }
            node = childNode;
        }
        if (!node.word){
            node.word = true;
            node.value = value;
            size++;
            return null;
        }
        V oldValue  = node.value;
        node.value = value;
        return oldValue;
    }

    public V remove(String key){
        // 找到最后一个节点
        Node<V> node = node(key);
        // 如果不是单词结尾,不用做任何处理
        if (node == null || !node.word) return null;
        size--;
        V oldValue = node.value;
        if (node.children != null && node.children.isEmpty()){
            node.word = false;
            node.value = null;
            return oldValue;

        }
        // 没有子节点
        Node<V> parent = null;
        while ((parent = node.parent) != null){
            parent.children.remove(node.character);
            if (parent.word || !parent.children.isEmpty()) break;
            node = parent;
        }
        return oldValue;
    }

    public boolean startsWith(String prefix){
        return node(prefix) != null;
    }

    private Node<V> node(String key){
        keyCheck(key);

        Node<V> node = this.root;
        int length = key.length();
        for (int i = 0; i < length; i++) {
            if (node == null || node.children == null || node.children.isEmpty()) return null;
            char c = key.charAt(i);
            node = node.children.get(c);
        }
        return node;
    }

    private void keyCheck(String key){
        if (key == null || key.length() == 0){
            throw new IllegalArgumentException("key must not be null");
        }
    }

    private static class Node<V>{
        Node<V> parent;
        Character character;
        HashMap<Character, Node<V>> children;
        V value;
        boolean word;   // 是否为单词的结尾

        public Node(Node<V> parent) {
            this.parent = parent;
        }
    }
}

Trie总结

  • Trie的优点:搜索前缀的效率主要跟前缀的长度有关
  • Trie的缺点:需要耗费大量的内存,因此还有待改进
  • 更多Trie相关的数据结构和算法
    • Double-array Trie、Suffix Tree、Patricia Tree、Crit-bit Tree、AC自动机

总结:常用的经典数据结构

zig、zag

  • 有些教程里面
    • 把右旋转叫做zig,旋转之后的状态叫做zigged
    • 把左旋转叫做zag,旋转之后的状态叫做zagged

满二叉树定义

  • 满二叉树:最后一层节点的度都为0,其他节点的度都为2

完全二叉树

  • 完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与相同高度的满二叉树中的编号对应

完全二叉树描述

二叉树

四则运算
  • 四则运算的表达式可分为三种:
    • 前缀表达式(prefix expression),又称为波兰表达式
    • 中缀表达式(infix expression)
    • 前缀表达式(postfix expression),又称为逆波兰表达式
前缀表达式中缀表达式后缀表达式
+ 1 21 + 21 2 +
+ 2 * 342 + 3 * 42 3 4 * +
+ 9 * - 4 1 29 + (4 - 1) * 29 4 1 - 2 * +
表达式树
  • 如果将表达式的操作数作为叶子节点,运算符作为父节点(假设只是四刚运算)

    • 这些节点刚好可以组成一棵二叉树
    • 比如表达式:A / B + C * D - E
  • 如果对这棵二叉树进行遍历

  • 前序遍历
    - + / A B * C D E
    刚好就是前缀表达式(波兰表达式)

  • 中序遍历
    A / B + C * D - E

    刚好就是中缀表达式

  • 后序遍历
    A B / C D * + E -
    刚好就是后缀表达式(逆波兰表达式)

表达式树

非递归遍历二叉树(前中后序)

非递归遍历

前序遍历 - 非递归
  • 利用栈实现

    1. 设置node = root
    2. 循环执行以下操作
    • 如果node != null
      • 对node进行访问
      • node.right入栈
      • 设置node = node.left
    • 如果node == null
      • 如果栈为空遍历结束
      • 如果栈不为空,弹出栈顶元素并赋值给node
// 非递归前序遍历
public void preorder(Visitor<E> visitor){
    if (visitor == null || root == null)return;
    Node<E> node = this.root;
    Stack<Node<E>> stack = new Stack<>();
    while (true){
        if (node != null) {
            // 访问节点
            if (visitor.visitor(node.element)) return;
            // 右子节点入栈
            if (node.right != null) stack.push(node.right);
            // 向左走之前,把右节点入栈
            node = node.left;
        }else if (stack.isEmpty()){
            return;
        }else {
            node = stack.pop();	// 处理右边
        }
    }
}
前序遍历 - 非递归2
  • 利用栈实现(类似层序遍历,层序遍历先左后右,这个是先右后左)

    1. 将root入栈
    2. 循环执行以下操作
    • 如果栈不为空
      • 弹出栈顶,进行访问
      • node.right != null,入栈
      • node.left != null, 入栈
// 非递归前序遍历2
public void preorder2(Visitor<E> visitor){
    if (visitor == null || root == null)return;
    Stack<Node<E>> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()){
        Node<E> node = stack.pop();
        if (visitor.visitor(node.element)) return;  // 访问node节点
        if (node.right != null){
            stack.push(node.right);
        }
        if (node.left != null){
            stack.push(node.left);
        }
    }
}
中序遍历 - 非递归
  • 利用栈实现

    1. 设置node = root
    2. 循环执行以下操作
    • 如果node != null
      • node 入栈
      • 设置node = node.left
    • 如果node == null
      • 如果栈为空遍历结束
      • 如果栈不为空,弹出栈顶元素并赋值给node
      • 对node访问
      • 设置node = node.right
// 非递归中序遍历
public void inorder(Visitor<E> visitor){
    if (visitor == null || root == null)return;
    Node<E> node = this.root;
    Stack<Node<E>> stack = new Stack<>();
    while (true){
        if (node.left!=null) {
            stack.push(node);
            node = node.left;       // 往左走
        }else if (stack.isEmpty()){
            return;
        }else {
            node = stack.pop();
            if (visitor.visitor(node.element)) return;  // 访问node节点
            node = node.right;  // 让右边进行中序遍历
        }
    }
}
后序遍历 - 非递归
  • 利用栈实现

    1. 将root入栈
    2. 循环执行以下操作,直到栈为空
    • 如果栈顶节点是叶子节点或者上一次访问的节点是栈顶节点的子节点
      • 弹出栈顶节点,进行访问
    • 否则
      • 将栈顶节点的right、left按顺序入栈
// 非递归后序遍历
public void postorder(Visitor<E> visitor){
    if (visitor == null || root == null)return;
    Node<E> prev = null;    // 记录上次弹出的栈的节点
    Stack<Node<E>> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()){
        Node<E> top = stack.peek();
        if (top.isLeaf() || (prev != null && prev.parent == top)){
            prev = stack.pop();
            if (visitor.visitor(prev.element)) return;  // 访问node节点
        }else {
            if (top.right != null){
                stack.push(top.right);
            }
            if (top.left != null) {
                stack.push(top.left);
            }
        }
    }
}

源码地址:https://gitee.com/Liu_zimo/JavaNote/tree/master/%E7%AC%AC%E4%B8%80%E5%AD%A3%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/src/com/zimo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序.txt 二叉.txt 二叉实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳子陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值