跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 实现 AVL 树:结构、插入与旋转操作详解

介绍 C++ 中 AVL 树的实现。AVL 树是一种自平衡二叉查找树,通过平衡因子控制左右子树高度差不超过 1。内容包括节点结构定义、插入操作(含更新平衡因子)、四种旋转操作(左旋、右旋、左右双旋、右左双旋)以恢复平衡,以及查找、高度计算等辅助功能。代码展示了完整实现及测试用例,时间复杂度为 O(log N)。

古灵精怪发布于 2026/3/29更新于 2026/10/2498 浏览
C++ 实现 AVL 树:结构、插入与旋转操作详解

从结构到操作:手撕 AVL 树的实现

一、AVL 树介绍

1.1 什么是 AVL 树

AVL 树是一种自平衡的二叉查找树(BST),由 G.M. Adelson-Velsky 和 E.M. Landis 于 1962 年提出。AVL 树的核心特点是它保证树的每个节点的左右子树的高度差(平衡因子)不超过 1,从而保证了 AVL 树在插入、删除和查找操作时的时间复杂度始终为 O(log N)。

1.2 平衡因子的定义

每个节点都有一个 平衡因子 (_bf),它表示节点左子树的高度减去右子树的高度: 平衡因子 = 左子树的高度 - 右子树的高度

  • 如果平衡因子为 0,表示左右子树的高度相等。
  • 如果平衡因子为 1,表示左子树比右子树高 1。
  • 如果平衡因子为 -1,表示右子树比左子树高 1。
1.3 平衡的意义

AVL 树的自平衡特性确保了其查询、插入和删除操作的最坏时间复杂度为 O(log N),避免了普通二叉查找树的退化(比如变成链表)。因此,AVL 树非常适用于需要频繁插入和删除操作的场景。

1.4 AVL 树的操作

AVL 树的主要操作有:

  • 插入操作:在树中插入节点,并在插入后调整平衡因子。如果平衡因子为 2 或 -2,执行旋转操作恢复平衡。
  • 删除操作:删除节点,并且在删除节点后,可能需要调整树的结构以保持平衡。由于 AVL 树的删除操作涉及复杂的旋转和调整,因此在本文中我们不会详细讲解该操作。如果你希望深入了解,可以参考《算法导论》中的相关章节来获取详细内容。
  • 查找操作:通过比较节点值来查找特定的元素。
  • 旋转操作:当树失衡时,使用旋转来恢复平衡,常见的旋转有左旋、右旋、左右旋和右左旋。

二、AVL 树的节点结构

在实现 AVL 树之前,首先要定义节点结构。每个节点存储以下信息:

  • 键值对 (_kv):存储数据 (pair<K, V>)。
  • 左右子树指针 (_left, _right):指向左右子树。
  • 父节点指针 (_parent):指向父节点。
  • 平衡因子 (_bf):用于标识该节点的左右子树的高度差。
2.1 节点结构的定义:
template <class K, class V>
struct AVLTreeNode {
    pair<K, V> _kv; // 存储键值对
    AVLTreeNode<K, V>* _left; // 左子树指针
    AVLTreeNode<K, V>* _right; // 右子树指针
    AVLTreeNode<K, V>* _parent; 
     _bf; 

    
    ( pair<K, V>& kv) : _kv(kv), _left(), _right(), _parent(), _bf() {}
};
// 父节点指针
int
// 平衡因子
// 构造函数
AVLTreeNode
const
nullptr
nullptr
nullptr
0

三、插入操作

3.1 插入操作概述

AVL 树的插入操作包括三步:

  1. 按二叉查找树规则插入节点;
  2. 更新每个节点的平衡因子;
  3. 如果需要,进行旋转操作 来恢复树的平衡。
bool Insert(const pair<K, V>& kv);
3.2 步骤 1:按二叉查找树规则插入节点

在插入节点时,我们根据值的大小,递归地找到插入位置,并在该位置插入新节点:

if (_root == nullptr) {
    _root = new Node(kv); // 如果树为空,直接插入根节点
    return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
    if (cur->_kv.first < kv.first) {
        parent = cur;
        cur = cur->_right; // 在右子树中查找插入位置
    } else if (cur->_kv.first > kv.first) {
        parent = cur;
        cur = cur->_left; // 在左子树中查找插入位置
    } else {
        return false; // 如果节点已存在,不插入
    }
}
cur = new Node(kv);
if (parent->_kv.first < kv.first) {
    parent->_right = cur;
} else {
    parent->_left = cur;
}
// 更新插入节点的_parent
cur->_parent = parent;
3.3 步骤 2:更新平衡因子

插入节点后,我们从新插入的节点开始,逐步更新路径上每个节点的平衡因子。插入时会对父节点的平衡因子进行调整:

while (parent) {
    if (cur == parent->_left) {
        parent->_bf++;
    } else {
        parent->_bf--;
    }
    if (parent->_bf == 0) {
        break;
    } else if (parent->_bf == 1 || parent->_bf == -1) {
        cur = parent;
        parent = cur->_parent;
    } else if (parent->_bf == 2 || parent->_bf == -2) {
        // 旋转
        if (parent->_bf == 2 && cur->_bf == 1) {
            RotateR(parent);
        } else if (parent->_bf == -2 && cur->_bf == -1) {
            RotateL(parent);
        } else if (parent->_bf == 2 && cur->_bf == -1) {
            RotateLR(parent);
        } else if (parent->_bf == -2 && cur->_bf == 1) {
            RotateRL(parent);
        } else assert(false);
        break;
    } else {
        assert(false);
    }
}
return true;
3.4 步骤 3:旋转操作详解

如果某个节点的平衡因子为 2 或 -2,表示该节点不平衡。我们需要通过旋转操作来恢复平衡。旋转操作有四种:

  1. 右单旋 (RotateR)
  2. 左单旋 (RotateL)
  3. 左右双旋 (RotateLR)
  4. 右左双旋 (RotateRL)
3.4.1 右单旋

右单旋适用于当左子树较高时,执行旋转操作来恢复平衡。

void RotateR(Node* parent) {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    Node* ppNode = parent->_parent;
    parent->_left = subLR;
    if (subLR) {
        subLR->_parent = parent;
    }
    subL->_right = parent;
    parent->_parent = subL;
    if (parent == _root) {
        subL->_parent = nullptr;
        _root = subL;
    } else {
        subL->_parent = ppNode;
        if (parent == ppNode->_right) ppNode->_right = subL;
        else ppNode->_left = subL;
    }
    parent->_bf = 0;
    subL->_bf = 0;
}

3.4.2 左单旋

左单旋适用于当右子树较高时,执行旋转操作来恢复平衡。

void RotateL(Node* parent) {
    Node* subR = parent->_right;
    Node* ppNode = parent->_parent;
    Node* subRL = subR->_left;
    parent->_right = subRL;
    parent->_parent = subR;
    if (subRL) {
        subRL->_parent = parent;
    }
    subR->_left = parent;
    if (parent == _root) {
        _root = subR;
        subR->_parent = nullptr;
    } else {
        subR->_parent = ppNode;
        if (parent == ppNode->_right) ppNode->_right = subR;
        else ppNode->_left = subR;
    }
    parent->_bf = 0;
    subR->_bf = 0;
}

3.4.3 左右双旋

当左子树的右子树较高时,首先进行左旋,再进行右旋,恢复平衡。

void RotateLR(Node* parent) {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;
    RotateL(subL); // 左旋
    RotateR(parent); // 右旋
    if (bf == 1) {
        subLR->_bf = 0;
        subL->_bf = 0;
        parent->_bf = -1;
    } else if (bf == -1) {
        subLR->_bf = 0;
        subL->_bf = 1;
        parent->_bf = 0;
    } else if (bf == 0) {
        subL->_bf = subLR->_bf = parent->_bf = 0;
    } else {
        assert(false);
    }
}

总结:找失衡原因,决定如何旋转

  • 左子树的左子树高:进行 R
  • 右子树的右子树高:进行 L
  • 左子树的右子树高:进行 LR
  • 右子树的左子树高:进行 RL

四、其他操作

4.1 查找操作 (Find)

查找操作与普通的二叉查找树类似。通过不断比较当前节点的键值和目标值,沿树的路径查找目标节点。

Node* Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first < key) cur = cur->_right; // 目标键值比当前节点大,往右子树查找
        else if (cur->_kv.first > key) cur = cur->_left; // 目标键值比当前节点小,往左子树查找
        else return cur; // 找到目标节点,返回该节点
    }
    return nullptr; // 如果没有找到,返回空
}

该方法的时间复杂度为 O(log N),因为 AVL 树是平衡的,树的高度是对数级别的。


4.2 计算树的高度 (Height)

树的高度指的是从根节点到最远叶子节点的最长路径上的边数。AVL 树的高度是平衡的,计算时我们需要递归地计算左右子树的高度并返回较大的一个。

int Height() {
    return _Height(_root); // 从根节点开始计算树的高度
}
int _Height(Node* root) {
    if (root == nullptr) return 0; // 空树的高度为 0
    int leftHeight = _Height(root->_left); // 递归计算左子树的高度
    int rightHeight = _Height(root->_right); // 递归计算右子树的高度
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; // 返回较大值,并加 1
}

该方法的时间复杂度为 O(N),其中 N 为节点总数。虽然 AVL 树是平衡的,但在计算高度时需要遍历整个树。


4.3 计算节点数量 (Size)

节点数量是树中所有节点的总数。通过递归遍历树,计算左右子树的节点数量,并加上当前节点。

int Size() {
    return _Size(_root); // 从根节点开始计算树的大小
}
int _Size(Node* root) {
    if (root == nullptr) return 0; // 空树节点数量为 0
    return _Size(root->_left) + _Size(root->_right) + 1; // 左右子树的节点数量加 1
}

该方法的时间复杂度为 O(N),需要遍历整个树来计算节点总数。


4.4 树是否平衡 (IsBalanceTree)

为了验证 AVL 树的平衡性,我们需要检查每个节点的平衡因子,并确保它们的绝对值不超过 1。如果树的任意节点的平衡因子大于 1 或小于 -1,那么树就不平衡。

bool _IsBalanceTree(Node* root) {
    if (root == nullptr) return true; // 空树是平衡的
    int leftHeight = _Height(root->_left); // 左子树高度
    int rightHeight = _Height(root->_right); // 右子树高度
    int diff = leftHeight - rightHeight; // 计算平衡因子
    if (abs(diff) >= 2) {
        cout << root->_kv.first << "高度差异常" << endl;
        return false; // 如果高度差大于等于 2,树不平衡
    }
    if (root->_bf != diff) {
        cout << root->_kv.first << "平衡因子异常" << endl;
        return false; // 如果平衡因子与实际高度差不符,树不平衡
    }
    // 递归检查左右子树是否平衡
    return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

该方法的时间复杂度为 O(N),需要递归遍历整个树并计算每个节点的平衡因子。


五、性能分析与测试

5.1 性能分析

AVL 树通过保持平衡,保证了插入、查找和删除操作的时间复杂度都为 O(logN),相比于普通的二叉查找树,AVL 树避免了退化成链表的情况,极大提高了性能。

5.2 测试代码

下面是两个测试代码,用于验证 AVL 树的插入和查找操作。

// 测试代码
void TestAVLTree1() {
    AVLTree<int, int> t;
    // 常规的测试用例
    // int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    // 特殊的带有双旋场景的测试用例
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    for (auto e : a) {
        t.Insert({ e, e });
        cout << "Insert:" << e << "->";
        cout << t.IsBalanceTree() << endl;
    }
    t.InOrder();
    cout << t.IsBalanceTree() << endl;
}

// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2() {
    const int N = 1000000;
    vector<int> v;
    v.reserve(N);
    srand((unsigned int)time(0));
    for (int i = 0; i < N; i) {
        v.push_back(rand() + i);
    }
    size_t begin2 = clock();
    AVLTree<int, int> t;
    for (auto e : v) {
        t.Insert(make_pair(e, e));
    }
    size_t end2 = clock();
    cout << t.IsBalanceTree() << endl;
    cout << "Insert:" << end2 - begin2 << endl;
    cout << "Height:" << t.Height() << endl;
    cout << "Size:" << t.Size() << endl;
    size_t begin1 = clock();
    // 确定在的值
    /*for (auto e : v) { t.Find(e); }*/
    // 随机值
    for (int i = 0; i < N; i) {
        t.Find((rand() + i));
    }
    size_t end1 = clock();
    cout << "Find:" << end1 - begin1 << endl;
}

六、全部代码

AVLTree.h
#pragma once
template <class K, class V>
struct AVLTreeNode {
    pair<K, V> _kv;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    AVLTreeNode<K, V>* _parent;
    int _bf;

    AVLTreeNode(const pair<K, V>& kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0) {}
};

template <class K, class V>
class AVLTree {
    typedef AVLTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv) {
        if (_root == nullptr) {
            _root = new Node(kv);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) {
            if (cur->_kv.first < kv.first) {
                parent = cur;
                cur = cur->_right;
            } else if (cur->_kv.first > kv.first) {
                parent = cur;
                cur = cur->_left;
            } else {
                return false;
            }
        }
        cur = new Node(kv);
        if (parent->_kv.first < kv.first) {
            parent->_right = cur;
        } else {
            parent->_left = cur;
        }
        cur->_parent = parent;
        while (parent) {
            if (cur == parent->_left) {
                parent->_bf++;
            } else {
                parent->_bf--;
            }
            if (parent->_bf == 0) {
                break;
            } else if (parent->_bf == 1 || parent->_bf == -1) {
                cur = parent;
                parent = cur->_parent;
            } else if (parent->_bf == 2 || parent->_bf == -2) {
                // 旋转
                if (parent->_bf == 2 && cur->_bf == 1) {
                    RotateR(parent);
                } else if (parent->_bf == -2 && cur->_bf == -1) {
                    RotateL(parent);
                } else if (parent->_bf == 2 && cur->_bf == -1) {
                    RotateLR(parent);
                } else if (parent->_bf == -2 && cur->_bf == 1) {
                    RotateRL(parent);
                } else assert(false);
                break;
            } else {
                assert(false);
            }
        }
        return true;
    }

    void InOrder() {
        _InOrder(_root);
    }

    bool IsBalanceTree() {
        return _IsBalanceTree(_root);
    }

    Node* Find(const K& key) {
        Node* cur = _root;
        while (cur) {
            if (cur->_kv.first < key) cur = cur->_right;
            else if (cur->_kv.first > key) cur = cur->_left;
            else return cur;
        }
        return nullptr;
    }

    int Height() {
        return _Height(_root);
    }

    int Size() {
        return _Size(_root);
    }

private:
    int _Size(Node* root) {
        if (root == nullptr) return 0;
        return _Size(root->_left) + _Size(root->_right) + 1;
    }

    int _Height(Node* root) {
        if (root == nullptr) return 0;
        int leftHeight = _Height(root->_left);
        int rightHeight = _Height(root->_right);
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

    bool _IsBalanceTree(Node* root) {
        if (root == nullptr) return true;
        int leftHeight = _Height(root->_left);
        int rightHeight = _Height(root->_right);
        int diff = leftHeight - rightHeight;
        if (abs(diff) >= 2) {
            cout << root->_kv.first << "高度差异常" << endl;
            return false;
        }
        if (root->_bf != diff) {
            cout << root->_kv.first << "平衡因子异常" << endl;
            return false;
        }
        return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    }

    void _InOrder(Node* root) {
        if (root == nullptr) {
            return;
        }
        _InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << " ";
        _InOrder(root->_right);
    }

    void RotateR(Node* parent) {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* ppNode = parent->_parent;
        parent->_left = subLR;
        if (subLR) {
            subLR->_parent = parent;
        }
        subL->_right = parent;
        parent->_parent = subL;
        if (parent == _root) {
            subL->_parent = nullptr;
            _root = subL;
        } else {
            subL->_parent = ppNode;
            if (parent == ppNode->_right) ppNode->_right = subL;
            else ppNode->_left = subL;
        }
        parent->_bf = 0;
        subL->_bf = 0;
    }

    void RotateL(Node* parent) {
        Node* subR = parent->_right;
        Node* ppNode = parent->_parent;
        Node* subRL = subR->_left;
        parent->_right = subRL;
        parent->_parent = subR;
        if (subRL) {
            subRL->_parent = parent;
        }
        subR->_left = parent;
        if (parent == _root) {
            _root = subR;
            subR->_parent = nullptr;
        } else {
            subR->_parent = ppNode;
            if (parent == ppNode->_right) ppNode->_right = subR;
            else ppNode->_left = subR;
        }
        parent->_bf = 0;
        subR->_bf = 0;
    }

    void RotateLR(Node* parent) {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int bf = subLR->_bf;
        RotateL(subL);
        RotateR(parent);
        if (bf == 1) {
            subLR->_bf = 0;
            subL->_bf = 0;
            parent->_bf = -1;
        } else if (bf == -1) {
            subLR->_bf = 0;
            subL->_bf = 1;
            parent->_bf = 0;
        } else if (bf == 0) {
            subL->_bf = subLR->_bf = parent->_bf = 0;
        } else {
            assert(false);
        }
    }

    void RotateRL(Node* parent) {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        int bf = subRL->_bf;
        RotateR(subR);
        RotateL(parent);
        if (bf == 0) {
            parent->_bf = subR->_bf = subRL->_bf = 0;
        } else if (bf == 1) {
            subRL->_bf = 0;
            parent->_bf = 0;
            subR->_bf = -1;
        } else if (bf == -1) {
            subRL->_bf = 0;
            parent->_bf = 1;
            subR->_bf = 0;
        } else {
            assert(false);
        }
    }

    Node* _root = nullptr;
};

// 测试代码
void TestAVLTree1() {
    AVLTree<int, int> t;
    // 常规的测试用例
    // int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    // 特殊的带有双旋场景的测试用例
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    for (auto e : a) {
        /*if (e == 14) { int x = 0; }*/
        t.Insert({ e, e });
        cout << "Insert:" << e << "->";
        cout << t.IsBalanceTree() << endl;
    }
    t.InOrder();
    cout << t.IsBalanceTree() << endl;
}

// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2() {
    const int N = 1000000;
    vector<int> v;
    v.reserve(N);
    srand((unsigned int)time(0));
    for (int i = 0; i < N; i) {
        v.push_back(rand() + i);
    }
    size_t begin2 = clock();
    AVLTree<int, int> t;
    for (auto e : v) {
        t.Insert(make_pair(e, e));
    }
    size_t end2 = clock();
    cout << t.IsBalanceTree() << endl;
    cout << "Insert:" << end2 - begin2 << endl;
    cout << "Height:" << t.Height() << endl;
    cout << "Size:" << t.Size() << endl;
    size_t begin1 = clock();
    // 确定在的值
    /*for (auto e : v) { t.Find(e); }*/
    // 随机值
    for (int i = 0; i < N; i) {
        t.Find((rand() + i));
    }
    size_t end1 = clock();
    cout << "Find:" << end1 - begin1 << endl;
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <vector>
using namespace std;
#include "AVLTree.h"

int main() {
    TestAVLTree1();
    TestAVLTree2();
    return 0;
}

七、总结与展望

随着数据量的不断增长,如何保持高效的查找与插入操作成为了现代计算机科学的核心问题之一。AVL 树作为一种自平衡二叉查找树,凭借其稳定的时间复杂度和高效的性能,已广泛应用于各类需要动态数据结构支持的场景。未来,随着算法和硬件的进一步发展,AVL 树的实现和优化将迎来更多挑战与机遇。

目录

  1. 从结构到操作:手撕 AVL 树的实现
  2. 一、AVL 树介绍
  3. 1.1 什么是 AVL 树
  4. 1.2 平衡因子的定义
  5. 1.3 平衡的意义
  6. 1.4 AVL 树的操作
  7. 二、AVL 树的节点结构
  8. 2.1 节点结构的定义:
  9. 三、插入操作
  10. 3.1 插入操作概述
  11. 3.2 步骤 1:按二叉查找树规则插入节点
  12. 3.3 步骤 2:更新平衡因子
  13. 3.4 步骤 3:旋转操作详解
  14. 3.4.1 右单旋
  15. 3.4.2 左单旋
  16. 3.4.3 左右双旋
  17. 四、其他操作
  18. 4.1 查找操作 (Find)
  19. 4.2 计算树的高度 (Height)
  20. 4.3 计算节点数量 (Size)
  21. 4.4 树是否平衡 (IsBalanceTree)
  22. 五、性能分析与测试
  23. 5.1 性能分析
  24. 5.2 测试代码
  25. 六、全部代码
  26. AVLTree.h
  27. Test.cpp
  28. 七、总结与展望

更多推荐文章

查看全部
  • 大模型 RAG 技术详解:架构、优势与实战案例
  • C++ 模板编程入门:从零理解泛型核心
  • 医疗 AI 时代生物医学 Go 编程:高性能计算与精准医疗案例分析
  • OpenClaw.ai:Agentic AI 时代的 Spring Framework 时刻
  • Linux TCP 可靠性与性能优化详解:从确认应答到拥塞控制
  • NativeScript-Vue 实战:用 Vue 语法构建高性能原生应用
  • 智能客服情感化升级实战:降低投诉率的技术方案
  • AI 大模型发展三波浪潮:探讨中国如何在人工智能新浪潮中突破与领先
  • OpenCode 开源 AI 编程助手实战指南
  • ES6 核心语法进阶:进制表示、Symbol 及类继承
  • Node.js 22+ 环境搭建与 OpenAI、Vercel AI SDK 集成指南
  • Python 实现 MCP 客户端调用高德地图天气查询示例
  • 医疗 NLP 实战:从电子病历分析到智能问答
  • 无人机 Remote ID Beacon 帧结构深度解析
  • 大模型对比:算法导论散列表装载因子探查期望数计算
  • 9 款免费 AI 论文写作工具功能对比与使用指南
  • 中国电信发布星辰语音大模型 支持 30 种方言混说
  • C++ 继承进阶:友元、静态成员与菱形继承解析
  • 零基础读懂大型语言模型的工作原理与训练机制
  • Python 自动化测试工具 Selenium 核心详解与实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online