数据结构——(2)树

目录

1  二叉树

1.1  树的定义

1.2  二叉树

1.3  完全二叉树

2  二叉搜索树

2.1  插入节点

2.2  搜索节点

2.3  删除节点

2.4  遍历

2.4.2  中序遍历

2.4.3  后序遍历

2.4.4  层次遍历

3  自平衡二叉查找树(AVL树)

3.1  需要调整的情况以及方法

​3.2  LL、RR、RL、LR实现

4  堆(heap)——以最大堆为例

4.1  堆的定义

4.2  堆的实现

4.2.1  用数组实现堆

4.2.3  访问父节点、子节点

4.2.4 入堆

4.2.5  出堆

5  哈夫曼树(Huffman Tree)、哈夫曼编码

5.1  哈夫曼树

5.2  哈夫曼树的构造

5.3  哈夫曼编码

5.2.1  主要思想

5.2.2  编码方法

6  红黑树(留坑)

7  B树、B+树(留坑)


 




1  二叉树

1.1  树的定义

  •   树(tree)是包含n(n>=0)个结点的有穷集,其中:
  1. 每个元素称为结点(node);
  2. 有一个特定的结点被称为根结点或树根(root)。
  3. 除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。

1.2  二叉树

  • 二叉树定义:每个节点最多有2个子节点的数
  • 性质
  • (1)二叉树第i层的最大节点数为2^{^{i}}, i\geq 0;
  • (2)深度为k(第0层深度为1)的二叉树有最大节点数2^{^{k}}-1, k\geq1;
  • (3)度为2的非叶节点个数+1 = 叶节点个数 

1.3  完全二叉树

  • 满二叉树定义:除了叶节点的所有节点都有左右子节点的二叉树
  • 完美二叉树定义:叶节点都位于同一层上的满二叉树
  • 完全二叉树定义:对应元素的位置跟完美二叉树相同的二叉树

2  二叉搜索树

  • 定义:满足以下条件的二叉树
  • (1)非空左子树的键值小于当前节点键值;
  • (2)非空右子树的键值小于当前节点键值;
  • (3)每棵子树都是二叉搜索树。

2.1  插入节点

  • 二叉搜索树插入节点时,主要考虑的问题是维护整棵树的有序性,即保证上面定义中的性质。
  • 插入节点的流程(迭代思想):
  1. 当前节点是否为空:如果是,返回一个值为新元素的新节点指针;
  2. 判断插入的元素是否比当前节点的元素大,如果是,将其插入至右子树;
  3. 判断插入的元素是否比当前节点的元素小,如果是,将其插入至左子树;
  4. 判断插入的元素是否等于当前节点元素,如果是,返回当前节点。
  • 用迭代方法实现(以下包含AVL树的平衡调整):
    TreeNode* insert_(TreeNode* node, T element){
        if(node == nullptr){  //如果当前树是空树:直接返回一个新节点
            return new TreeNode(element);
        }
        if(node->data < element) { //如果元素比当前节点元素大:插入到右子树
            node->rightChild = insert_(node->rightChild, element);
            if(getHeight_(node->leftChild) - getHeight_(node->rightChild) == -2){  //如果不平衡
                if(element < node->rightChild->data ){ // RL型
                    node = rightLeftRotate_(node);
                }else{ //RR型
                    node = rightRotate_(node);
                }
            }
        }else if(node->data > element){  //如果元素比当前节点元素小:插入到左子树
            node->leftChild = insert_(node->leftChild, element);
            int a = getHeight_(node->leftChild);
            int b = getHeight_(node->rightChild);
            if((getHeight_(node->leftChild) - getHeight_(node->rightChild)) == 2){  //如果不平衡
                if(element > node->leftChild->data ){ // LR型
                    node = leftRightRotate_(node);
                }else{ //LL型
                    node = leftRotate_(node);
                }
            }
        }
        return node;
    }

2.2  搜索节点

  • 搜索流程:
  • (1)要找的节点键值是不是比当前节点键值小:如果是,去左子树找;如果不是,去右子树找
  • (2)如果当前节点键值就是目标键值,则返回true;
  • (3)如果遍历完了都没有,就返回false
    bool find(const T& data){
        TreeNode *tmp = root_;
        while(tmp != nullptr){
            if(tmp->data > data){
                tmp = tmp->leftChild;
            }else if(tmp->data == data){
                return true;
            } else{
                tmp = tmp->rightChild;
            }
        }
        return false;
    }

2.3  删除节点

    TreeNode* deleteElement(TreeNode *node, T data){
        if(node == nullptr){  //如果是空树
            return nullptr;
        } else{  //如果不是空树
            if(node->data == data){  //如果当前节点是目标节点
                if(node->rightChild != nullptr && node->leftChild != nullptr){  //如果这个节点有两个子节点
                    TreeNode* tmp = findMax(node->leftChild);
                    node->data = tmp->data;  //用左子树中最大数据代替这个元素的数据
                    node->leftChild = deleteElement(node->leftChild, tmp->data);  //删除左子树中最大数据节点
                } else if(node->leftChild != nullptr || node->rightChild != nullptr){  //如果这个节点有一个子节点
                    TreeNode* tmp = node->leftChild == nullptr ? node->rightChild : node->leftChild;
                    delete node;
                    return tmp;
                }else{  //如果这个节点没有子节点
                    delete node;
                    return nullptr;
                }
            } else {  //如果当前节点不是目标节点
                if(data > node->data){
                    node->rightChild = deleteElement(node->rightChild, data);
                }else{
                    node->rightChild = deleteElement(node->leftChild, data);
                }
                return node;
            }
        }
    }

2.4  遍历

    void firstOrderTraversal( TreeNode* node, std::ostream &os){
        if(node == nullptr)return;
        os << " "<< node->data;
        firstOrderTraversal(node->leftChild, os);
        firstOrderTraversal(node->rightChild, os);
    }

2.4.2  中序遍历

    void midOrderTraversal(TreeNode* node, std::ostream &os){
        if(node == nullptr)return;
        midOrderTraversal(node->leftChild, os);
        os << " " << node->data;
        midOrderTraversal(node->rightChild, os);
    }

2.4.3  后序遍历

    void lastOrderTraversal( TreeNode* node, std::ostream &os){
        if(node == nullptr)return ;
        lastOrderTraversal(node->leftChild, os);
        lastOrderTraversal(node->rightChild, os);
        os << " " << node->data ;
    }

2.4.4  层次遍历

    void levelOrderTraversal(TreeNode* node, std::ostream &os){
        if(node == nullptr)return;
        std::queue<TreeNode*> queue;
        queue.push_back(root_);
        while(queue.front() != nullptr){
            TreeNode* tmp = queue.pop();
            os << tmp->data <<" ";
            queue.push(tmp->leftChild);
            queue.push(tmp->rightChild);
        }
    }

3  自平衡二叉查找树(AVL树)

3.1  需要调整的情况以及方法

3.2  LL、RR、RL、LR实现

    TreeNode* rightRotate_(TreeNode* n3){
        //1.获取n2的指针
        TreeNode* n2 = n3->rightChild;
        //2.n2的左子树成为n3右子树
        n3->rightChild = n2->leftChild;
        //3.n3成为n2左子树
        n2->leftChild = n3;
        return n2;
    }
    TreeNode* leftRightRotate_(TreeNode* n3){
        //1.n1对n2(n3的左子树)进行右单旋
        n3->leftChild = rightRotate_(n3->leftChild);
        //2.n1对n3进行左单旋
        return leftRotate_(n3);
    }
    TreeNode* rightLeftRotate_(TreeNode* n3){
        //1.n1对n2(n3的右子树)进行左单旋
        n3->rightChild = leftRotate_(n3->rightChild);
        //2.n1对n3进行右单旋
        return rightRotate_(n3);
    }
    TreeNode* leftRotate_(TreeNode* n3){
        //1.获取n2的指针
        TreeNode* n2 = n3->leftChild;
        //2.n2的右子树成为n3左子树
        n3->leftChild = n2->rightChild;
        //3.n3成为n2右子树
        n2->rightChild = n3;
        return n2;
    }

4  堆(heap)——以最大堆为例

4.1  堆的定义

  • :每个节点的键值都大于(或小于)所有子节点键值的完全二叉树
  • 最大堆:每个节点的键值都大于所有子节点键值的堆
  • 最小堆:每个节点的键值都小于所有子节点键值的堆
  • 堆的性质:所有子节点的键值不大于父节点键值的值

4.2  堆的实现

4.2.1  用数组实现堆

  • 由于堆是一种特殊的完全二叉树,且近似于满二叉树(自左向右填充),因此可用数组实现堆
  • 用数组实现堆时。需要维护的变量如下:
private:
    T* _data;             //各节点键值
    size_type _capacity;  //堆最大容量
    size_type _size;      //堆当前大小
  • _data是存放节点数据的数组

  • _capacity是最大容量,在申请内存时要多申请一个T类型的空间,原因下面解释:

4.2.3  访问父节点、子节点

  • 如果让堆顶元素的引索为1,则i号节点的父节点为(i+1)/2\frac{i+1}{2},左子节点为2i,右子节点为2i+1
  • 因此申请内存时要多申请一个

4.2.4 入堆

  • 入堆流程如下:
  • (1)新加入的元素放在最后一个节点的后面:即_size+1处
  • (2)判断这个新加入元素和父节点的关系:若大于父节点,则与父节点交换位置;否则该元素留在当前位置,完成入堆;
  • (3)交换位置后,继续与当前的父节点比较;直至遇到比这个元素大的父节点或该节点已到堆顶(到了_data[1]的位置),则完成入堆。
  • 以上步骤由_shiftUp函数完成
    bool push(T element){
        if(_size == _capacity)return false;
        _data[++_size] = element;
        _shiftUp();
        return true;
    }
    void _shiftUp() {
        size_type index = _size;
        size_type nextIdx;
        while(index != 1){
            nextIdx = _getParentNodeIndex(index);
            if( _data[index] > _data[nextIdx] ){
                _swap(index, nextIdx);
                index = nextIdx;
            }else{
                break;
            }
        }
    }

4.2.5  出堆

  • 出堆流程如下:
  • (1)用临时变量保存堆顶元素(_data[1])
  • (2)用堆尾元素代替堆顶元素,称为新的堆顶元素
  • (3)新堆顶元素与子节点对比(这里涉及三种情况:左右子节点的引索都没超出当前堆大小、左子节点引索超出当前堆大小、没有子节点,具体处理见代码),若比两个子节点中的较小值小,则与该节点交换位置;否则该元素留在这个位置,并跳过第(4)步
  • (5)交换位置后,继续与当前子节点对比,直到该节点比两个子节点大或无子节点为止。
  • (6)返回(1)中的临时变量
    T pop(){
        if(_size == 0)throw std::runtime_error("Empty heap.");
        T ret = _data[1];
        _data[1] = _data[_size--];
        _sink();
        return ret;
    }
    void _sink() {
        size_type index = 1;
        //三种情况:(1)左子节点在范围内,右节点不在;(2)左右节点都在范围内;(3)子节点不在范围内(不用处理)
        while(_getLeftChildNodeIndex(index) <= _size ){  //while处理(1)、(2)两种情况
            size_type nextIdx;
            if(_getRightChildNodeIndex(index) <= _size){  //(2)情况
                nextIdx =  _data[_getRightChildNodeIndex(index)] > _data[_getLeftChildNodeIndex(index)] ?
                                     _getRightChildNodeIndex(index) : _getLeftChildNodeIndex(index);
            }else{  //(1)情况
                nextIdx = _getLeftChildNodeIndex(index);
            }
            if(_data[index] < _data[nextIdx]){
                _swap(index, nextIdx);
                index = nextIdx;
            }else{  //比子节点都大(包括(3))
                break;
            }
        }
    }

5  哈夫曼树(Huffman Tree)、哈夫曼编码

5.1  哈夫曼树

  • 哈夫曼树定义:带权路径长度最小的二叉树(也称为最优二叉树)

5.2  哈夫曼树的构造

  • 构造流程:
  • (1)将所有目标元素存放在一个个哈夫曼树节点中,这些节点保存在一个最小堆中;
  • (2)创建一个新的哈曼夫树节点,弹出堆的顶部两个节点,分别设为该新节点的左右子树;
  • (3)将该新节点放入堆中。
  • (4)重复(2),直至堆中只剩一个元素,这个元素就是哈夫曼树的根节点。

(实现放在哈夫曼编码中一起讲)

5.3  哈夫曼编码

5.2.1  主要思想

  • 当一段文本(如电报)中有一些字符出现频率较高时(比如"ABCABCABCQUAAAA",ABC字符的比例明显高于其他),可以对这些字符使用一些较短的编码,以达到压缩编码的目的。

5.2.2  编码方法

HuffmanTree(initializer_list<elementType> initList){
        vector<_TreeNode*> parametersVector;
        //获得参数
        for(auto e : initList){
            parametersVector.push_back(new _TreeNode(e));
        }
        //构造Huffman树
        auto lessThan = [](_TreeNode* lhs, _TreeNode* rhs)->bool {return !(lhs->element.second < rhs->element.second);};  //make_heap
        make_heap(parametersVector.begin(), parametersVector.end(), lessThan);  //根据键值创建最小堆
        while(parametersVector.size() > 1){
            auto huffmanTree = new _TreeNode();

            huffmanTree->left = parametersVector.front();
            huffmanTree->left->code = "0";
            pop_heap(parametersVector.begin(), parametersVector.end(), lessThan);
            parametersVector.pop_back();

            huffmanTree->right = parametersVector.front();
            huffmanTree->right->code = "1";
            pop_heap(parametersVector.begin(), parametersVector.end(), lessThan);
            parametersVector.pop_back();

            huffmanTree->element.second = (huffmanTree->right->element.second + huffmanTree->left->element.second);
            parametersVector.push_back(huffmanTree);
            push_heap(parametersVector.begin(), parametersVector.end(), lessThan);
        }
        _root = parametersVector[0];
        _root->code = "";
    }
string getCode(_TreeNode* node, char c){
        if(node == nullptr)return "";  //如果是空节点
        if(node->left == nullptr && node->right == nullptr){  //如果是子节点
            if(node->element.first != c)return "";//不是目的字符
            else return node->code;
        }else{  //如果不是子节点
            string ret = getCode(node->left, c) + getCode(node->right, c);
            if(ret == "")return ret;  //如果左子树加右子树
            else{
                return node->code + ret;
            }
        }

6  红黑树(留坑)

网上资料说红黑树也是一种平衡树,但是一种不太严格的平衡树;AVL树每次插入或删除都有可能导致树结构的调整,而红黑树不会,所以红黑树比AVL树更适合频繁插入删除的场合(但搜索效率较AVL低)。这里先留一个坑,以后再补上~

7  B树、B+树(留坑)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值