《数据结构与算法分析》学习笔记三——树

 二叉树

        一棵二叉树binary tree)由结点(node)的有限集合组成,这个集合或者为空(empty),或者由一个根结点(root)以及两棵不相交的二叉树组成,这两棵二叉树分别称为这个根的左子树(left subtree)和右子树(right subtree)。这两棵子树的根称为此二叉树根结点的子结点(children);从一个结点到其两个子结点都有边(edge)相连,这个结点称为其子结点的父结点(parent)。

        如果一棵树有一串结点n1,n2,...,nk,且ni是ni+1的父结点(1<=i<k),则n1,n2,...,nk称为一条有n1到nk的路径(path),其长度为k-1。有一条路径从结点R至结点M,则R称为M的祖先(ancestor),M称为R的子孙(descendant)。

        结点M的深度(depth)就是从根结点到M的路径的长度。任何深度为d的结点的层数(level)都为d,其中根结点的层数为0,深度也为0。树的高度(height)等于最深结点的深度加1

        满二叉树full binary tree)的每一个结点或者是一个分支结点,并恰好有两个非空子结点,或者是叶节点。

        完全二叉树complete binary tree)有严格的形状要求:从根节点起每一层从左到右填充。一棵高度为d的完全二叉树除了d-1层,每一层都是满的,且按层编号,则编号连续

        特点:叶子结点只出现在最下两层,倒数第二层层叶结点集中在右边连续位置上,最底层叶结点集中在左边连续位置上。如果结点度为1,则只有左孩子;同样结点数的二叉树,完全二叉树的深度最小。

        性质:二叉树第i层至多2^(i-1)个结点

                 深度为k的二叉树至多有2^k - 1个结点

                 具有n个结点的完全二叉树的深度为[logN] + 1

                 具有n个结点的完全二叉树的结点按层编号,如果i=1,无双亲,i>1,双亲为[i/2]

                           如果2i > n, 则i无左孩子,否则左孩子为2i;如果2i + 1> n, 则i无右孩子,否则右孩子为2i +1

                           (在堆排序中有用到,是一棵有序的完全二叉树)                                  

        二叉树顺序存储结构:对于深度为k的树,需要2^k - 1个存储空间,浪费空间。

        二叉链表:用一个数据域加两个指针域表示结点

        按照一定的顺序访问二叉树的结点,称为一次遍历(traversal),先访问结点,后访问其子结点,这种访问方法称为前序遍历(preorder traversal);后序遍历(postorder traversal)先访问结点的子结点(包括其子树),再访问该结点;中序遍历(inorder traversal)先访问左子结点(包括其子树),然后访问该结点,最后访问右子结点(包括其子树)。逐层从左至右访问结点成为层次遍历(level order traversal)。

         注意:已知前序或后序和中序遍历序列可以唯一确定一棵二叉树,但前序和后序是不能确定一颗二叉树的。

        完全二叉树有其实际的用途,例如堆数据结构,被用来实现优先队列和外排序算法。n个结点的完全二叉树只可能有一种形状,假设逐层而下、从左到右,其结点的位置可完全由序号确定。因此,数组可以有效地存储完全二叉树的结构,把每一个数据存放在其结点对应序号的位置上,这意味着不存在结构性开销

 

叉查找树

        二叉查找树Binary Search TreeBST),或称二叉检索树、二叉排序树。二叉查找树的任何一个结点,设其值为K,则该结点左子树中任意一个结点的值都小于K,该结点右子树中任意一个结点的值都大于K。查找、插入和删除的时间代价均取决于对应结点的深度,最差的情况等于树的深度,故尽量保持二叉查找树的平衡。平衡二叉树每次操作的平均时间代价为O(logn),而严重不平衡的BST在最差情况下平均每次操作的时间代价为O(n)。二叉树平衡的时候其实现简单且效率高,但它为非平衡状态的可能性很大。

 

AVL树

        具有平衡条件的二叉查找树,每个结点的左子树和右子树的高度最多差1的二叉查找树,除了插入可能破坏平衡性外,其他操作的时间复杂度均为O(logn),为了解决插入带来的失衡,可以对从插入点到根节点路径上的结点进行简单修正,称其为旋转。

 

B树(多路搜索树)

        一个m阶B树(balanced tree of order m)定义为有以下特性:

  • 根或者是一个叶结点,或者至少有两个子女(不是二叉树);
  • 除了根结点以外,每个内部结点有m/2(向上取整)到m个子女;
  • 所有叶结点在树结构的同一层,因此树结构总是树高平衡的。

        2-3树是一个三阶B树。B树插入是2-3树插入的推广:第一步是找到应当包含待插入关键码的叶结点,并检查是否有空间;如果该结点中有地方,则插入关键码,否则把结点分裂成两个结点,并把中间的关键码提升到父结点;如果父结点也满了,就再分裂父结点,并再次提升中间的关键码。插入过程保证所有结点至少半满。

B+树

        B+树只在叶结点存储记录,内部结点存储关键码值,用于引导检索,并把每个关键码与一个指向子女结点的指针关联起来。B+树的叶结点一般链接起来,形成一个双链表,这样通过访问链表中的所有叶结点,就可以按排序的次序遍历全部记录。

 

Huffman编码树

        Huffman编码树Huffman coding tree),简称Huffman树,是一棵满二叉树,其每个叶结点对应一个字母,叶结点的权重为对应字母的出现频率。Huffman树具有最小外部路径权重(minimum external path weight),即对于给定的叶结点集合,具有最小加权路径长度。一个叶结点的加权路径长度(weighted path length)定义为权乘以深度。

        建立n个结点的Huffman树的过程很简单。首先,创建n棵初始的Huffman树,每课树只包含单一的叶结点,这n棵树按照权值(如频率)大小顺序排列;接着,拿走前两棵树(即权值最小的两棵),把它们标记为一棵新的Huffman树的一个分支结点的两个叶子结点,分支结点的权值为两个叶结点权值之和,新树放回序列中适当的位置。重复上述步骤,直至序列中只剩下一个元素,则Huffman建立完毕。

        一旦Huffman树构造完成,可把每个字母用代码标记。从根结点开始,分别把“0”或“1”标于树的每条边上,“0”对应于连接左子结点的那条边,“1”则对应于连接右子结点的边。字母的Huffman编码就是从根结点到对应于该字母叶结点路径的二进制代码。对信息代码反编码的过程为:从左到右逐位判别代码串,直至确定一个字母。如果一组代码中的任何一个代码都不是另一个代码的前缀,则称这组代码符合前缀特性prefix property),这种前缀特性保证了代码串被反编码时不会有多种可能。

附关键代码:

二叉树的前序遍历递归实现

void preOrder(TreeNode* root)
{
	if (root != NULL)
	{
		cout << root -> val << endl;
		preOrder(root -> left);
		preOrder(root -> right);
	}
}

二叉树的前序遍历非递归实现

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> st;
        st.push(root);
        while(! st.empty())
        {
            TreeNode* cur = st.top();
            st.pop();
            res.push_back(cur -> val);
            if(cur -> right != NULL)
                st.push(cur -> right);
            if(cur -> left != NULL)
                st.push(cur -> left); 
        }
        return res;
    }

二叉树的中序遍历递归实现

void inOrder(TreeNode* root)
{
	if (root != NULL)
	{
		inOrder(root -> left);
		cout << root -> val << endl;
		inOrder(root -> right);
	}
}

二叉树的中序遍历非递归实现

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while(cur || !st.empty())
        {
            if(cur != NULL)
            {
                st.push(cur); 
                cur = cur -> left;
            }
            else
            {
                cur = st.top();
                st.pop();
                res.push_back(cur -> val);
                cur = cur -> right; 
            }
        }
        return res;
    }

二叉树的后序遍历递归实现

void postOrder(TreeNode* root)
{
	if (root != NULL)
	{
		postOrder(root -> left);
		postOrder(root -> right);
		cout << root -> val << endl;
	}
}

二叉树的后序遍历非递归实现

思路:对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了 每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> st;
        st.push(root);
        TreeNode* pre = NULL;
        while(! st.empty())
        {
            TreeNode* cur = st.top();
            if((cur -> left == NULL && cur -> right == NULL) || (pre != NULL && (pre == cur -> left || pre == cur -> right)))
            {
                res.push_back(cur -> val);
                st.pop();
                pre = cur;
            }
            else
            {
                if(cur -> right)
                     st.push(cur -> right);
                if(cur -> left)    
                     st.push(cur -> left);
            }
        }
        return res;
    }

二叉树的后序遍历非递归实现(另一种简便方法)

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* p = st.top();
            res.push_back(p -> val);
            st.pop();
            if(p -> left != NULL)
                st.push(p -> left);
            if(p -> right != NULL)
                st.push(p -> right);    
        }
        reverse(res.begin(), res.end());
        return res;    
    }

二叉树的层次遍历(一维输出)

    vector<int> levelOrder(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            return res;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
            TreeNode* cur = q.front();
            q.pop();
            res.push_back(cur -> val);
            if(cur -> left)
                q.push(cur -> left);
            if(cur -> right)
                q.push(cur -> right);
        }
        return res;
    }

二叉树的层次遍历(二维输出)

    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
        if(root == NULL)
            return res;
        queue<TreeNode*> q1, q2;
        q1.push(root);
        while(!q1.empty())
        {
            vector<int> temp;
            while(!q1.empty())
            {
                TreeNode* p = q1.front();
                q1.pop();
                temp.push_back(p -> val);
                if(p -> left)
                     q2.push(p -> left);
                if(p -> right)
                     q2.push(p -> right);
            }
            res.push_back(temp);
            swap(q1,q2);
        }
        return res;
    }

猜你喜欢

转载自blog.csdn.net/qy724728631/article/details/81835654