数据结构——树

一、树

  • n个结点的度数和=n-1
  • 高度:
  • 深度:
  • 树的高度/深度:

二、二叉树 

特性

1.包含n (n>0)个节点的二叉树边数为n-1

2.若二叉树的高度为hh≥0,则它最少有h个节点,最多有2^h-1个节点

  (按照节点计算:124…2^h-1)相加)

3.包含n个节点的二叉树的高度最大为n,最小为[log2(n+1)](向上取整)

    n<=2^h-1(由2) ->   h>=log2(n+1)

满二叉树(哈夫曼树):除叶子节点,其余节点均有2个孩子

完美二叉树:高度为h,节点数2^h-1

完全二叉树:由满二叉树引出来的,若设二叉树的深度为h除第 h 层外其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,右侧缺失若干。深度为[log2(n+1)](向上取整)

4.设完全二叉树中,一节点序号为i,1<=i<=n,则:

        1)i>1时,该元素父节点编号为[i/2]

        2)若2i>n,该元素无左孩子,否则其左孩子编号为2i

        3)  若2i+1>n,该元素无右孩子,否则其右孩子编号为2i+1

5.设二叉树中度为2的节点有n2个,度为1的节点有n1个,度为0的节点有n0个,

    则n0=n2+1(度之和=n-1;n=n2+n1+n0 两式推导)

二叉树描述

1.数组描述(顺序存储)——利用特征4

数组位置:节点编号(可能造成空间浪费)

2.链表描述

树节点——节点类对象:

数据域、指针:LeftChildRightChild

n-1条边  -> 2n-(n-1)=n+1个空指针

(合并树和分裂树的操作要好好看,尤其是对指针的操作) 

#include<iostream>
using namespace std;

template <class T>
class BinaryTreeNode {
public:
    BinaryTreeNode() 
    { LeftChild = RightChild = 0; }
    BinaryTreeNode(const T& e) 
    { data = e;  LeftChild = RightChild = 0; }
    BinaryTreeNode(const T& e, BinaryTreeNode* l, BinaryTreeNode* r) 
    { data = e;  LeftChild = l;  RightChild = r; }

private:
    T data;
    BinaryTreeNode<T>* LeftChild,   // left subtree
        * RightChild;  // right subtree
};

template<class T>
class BinaryTree 
{
public:
    BinaryTree() { root = 0; };
    ~BinaryTree() {};
    bool IsEmpty() const
    {
        return ((root) ? false : true);
    }
    bool Root(T& x) const;//取x为根节点,返回成功或失败
    void MakeTree(const T& element,
        BinaryTree<T>& left, BinaryTree<T>& right);
    void BreakTree(T& element, BinaryTree<T>& left,
        BinaryTree<T>& right);
    void PreOrder(void(*Visit)(BinaryTreeNode<T>* u))//先序
    {
        PreOrder(Visit, root);
    }
    void InOrder(void(*Visit)(BinaryTreeNode<T>* u))//中序
    {
        InOrder(Visit, root);
    }
    void PostOrder(void(*Visit)(BinaryTreeNode<T>* u))//后序
    {
        PostOrder(Visit, root);
    }
    void LevelOrder(void(*Visit)(BinaryTreeNode<T>* u));//逐层
private:
    BinaryTreeNode<T>* root;  // pointer to root
    void PreOrder(void(*Visit)
        (BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
    void InOrder(void(*Visit)
        (BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
    void PostOrder(void(*Visit)
        (BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
};
//获取根节点数据
template<class T>
bool BinaryTree<T>::Root(T& x) const
{// Return root data in x.
 // Return false if no root.
    if (root) {
        x = root->data;
        return true;
    }
    else return false;  // no root
}
//创建树
template<class T>
void BinaryTree<T>::MakeTree(const T& element,
    BinaryTree<T>& left, BinaryTree<T>& right)
{// Combine left, right, and element to make new tree.
 // left, right, and this must be different trees.
   // create combined tree
    root = new BinaryTreeNode<T>
        (element, left.root, right.root);
    // deny access from trees left and right
    left.root = right.root = 0;
}
//分裂树
template<class T>
void BinaryTree<T>::BreakTree(T& element,
    BinaryTree<T>& left, BinaryTree<T>& right)
{// left, right, and this must be different trees.
   // check if empty
    if (!root) throw BadInput(); // tree empty
    // break the tree
    element = root->data;
    left.root = root->LeftChild;
    right.root = root->RightChild;

    delete root;
    root = 0;
}

3.另一种链表描述

 一个bnnode由元素、左孩子、指向下一相邻节点的指针构成(在同一层);即向左下和向右的指针。

三、二叉树遍历

方法:递归!

1.先序遍历:前缀表达式;中序遍历:中缀表达式;后序遍历:后缀表达式

#include<iostream>
using namespace std;

//先序遍历
template <class T>
void PreOrder(BinaryTreeNode<T>* t)
{// Preorder traversal of *t.
    if (t) {
        Visit(t);                 // visit tree root
        PreOrder(t->LeftChild);   // do left subtree
        PreOrder(t->RightChild);  // do right subtree
    }
}

//中序遍历
template <class T>
void InOrder(BinaryTreeNode<T>* t)
{// Inorder traversal of *t.
    if (t) {
        InOrder(t->LeftChild);   // do left subtree
        Visit(t);                // visit tree root
        InOrder(t->RightChild);  // do right subtree
    }
}

//后序遍历
template <class T>
void PostOrder(BinaryTreeNode<T>* t)
{// Postorder traversal of *t.
    if (t) 
    {
        PostOrder(t->LeftChild);   // do left subtree
        PostOrder(t->RightChild);  // do right subtree
        Visit(t);                  // visit tree root
    }
}




2.逐层遍历:采用的是 (二3.) 的那种链表描述

//根->叶逐层,同层由左至右
//逐层遍历
template <class T>
void LevelOrder(BinaryTreeNode<T>* t)
{// Level-order traversal of *t.
    LinkedQueue<BinaryTreeNode<T>*> Q;
    while (t) {
        Visit(t);  // visit t
        // put t's children on queue
        if (t->LeftChild) Q.Add(t->LeftChild);
        if (t->RightChild) Q.Add(t->RightChild);

        // get next node to visit
        try { Q.Delete(t); }
        catch (OutOfBounds) { return; }
    }
}

3.销毁二叉树:

后序遍历——删除所有节点

辅助函数 —— 删除单个节点

 static void Free(BinaryTreeNode<T> *t) {delete t;}

删除二叉树

void Delete() {PostOrder(Free, root); root = 0;}

4.计算高度:

后序遍历:左子树高度、右子树高度较大者+1(根节点)->树的高度

递归公式:h=max{hl, hr} + 1   -> 递归函数

//公共接口
int Height() const {return Height(root);}

template <class T>
int BinaryTree<T>::Height(BinaryTreeNode<T> *t) const
{// Return height of tree *t.
   if (!t) return 0;               // empty tree
   int hl = Height(t->LeftChild);  // height of left
   int hr = Height(t->RightChild); // height of right
   if (hl > hr) return ++hl;
   else return ++hr;
}


5.统计节点数目:

方法一:

私有辅助函数 Add1

static void Add1(BinaryTreeNode<T> *t) {_count++;}

节点数统计函数

int Size() {_count = 0; PreOrder(Add1, root); return _count;}

方法二:利用递归公式:s=sl+sr+1

template <class T>
int BinaryTree<T>::Size(BinaryTreeNode<T> *t) const
{
   if (!t) return 0;
   else return Size(t->LeftChild)+Size(t->RightChild)+1;
}

四、优先队列

有限元素集合,每一个元素都有一个优先级,随机输入,按照优先级顺序输出

stl中有默认priority_queue 

//当需要用自定义的数据类型时才需要传入这三个参数,
// 使用基本数据类型时,只需要传入数据类型,默认是大顶堆
//升序
priority_queue <int, vector<int>, greater<int> > q;
//降序
priority_queue<int, vector<int>, less<int>>q;
priority_queue<int> a;//默认降序

q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素

五、堆及堆排序

最大树 :每个节点的值都大于或等于其子节点(若存在)值的树
最小树:每个节点的值都小于或等于其子节点(若存在)值的树
最大堆是一棵最大树,同时是一棵完全二叉树

堆是特殊的完全二叉树——用一维数组描述

插入操作:从最后一个位置开始,不断与父节点比较、交换,直到小于父节点。
删除(根)操作:保存让最后一个元素指向根,同时内存-1; 选取子节点中较大者与父节点交换; 重复,直至符合堆特性;最后根被覆盖,其他元素位置也都做了调整。
//最大堆
template<class T>
class MaxHeap 
{
public:
    MaxHeap(int MaxHeapSize = 10);
    ~MaxHeap() { delete[] heap; }
    int Size() const { return CurrentSize; }
    T Max() 
    {          //查
        if (CurrentSize == 0)
            throw OutOfBounds();
        return heap[1];
    }
    MaxHeap<T>& Insert(const T& x); //增
    MaxHeap<T>& DeleteMax(T& x);   //删
    void Initialize(T a[], int size, int ArraySize);
private:
    int CurrentSize, MaxSize;
    T* heap;  // element array
};

//构造函数
template<class T>
MaxHeap<T>::MaxHeap(int MaxHeapSize)
{// Max heap constructor.
    MaxSize = MaxHeapSize;
    heap = new T[MaxSize + 1];
    CurrentSize = 0;
}

//插入函数!!!!
template<class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x)
{// Insert x into the max heap.
    if (CurrentSize == MaxSize)
        throw NoMem(); // no space

    // 寻找新元素x的位置
    // i——初始为新叶节点的位置,逐层向上,寻找最终位置
    int i = ++CurrentSize;
    while (i != 1 && x > heap[i / 2]) 
    {// i不是根节点,且其值大于父节点的值,需要继续调整
        heap[i] = heap[i / 2]; // 父节点下降
        i /= 2;              // 继续向上,搜寻正确位置
    }
    heap[i] = x;
    return *this;
}


//删除最大元素操作!!!!!
template<class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
{
   // check if heap is empty
    if (CurrentSize == 0)
        throw OutOfBounds(); // empty

    x = heap[1]; // 删除最大元素
    // 重整堆
    T y = heap[CurrentSize--]; 
    // y取最后一个节点,从根开始重整,同时size-1
    // find place for y starting at root
    int i = 1,  // 要放置的节点的指针位置
        ci = 2; // i的孩子,ci刚开始指向左孩子

    while (ci <= CurrentSize) 
    {
        // 使ci指向i的两个孩子中较大者
        if (ci < CurrentSize && heap[ci] < heap[ci + 1])
            ci++;
        // y的值大于等于孩子节点吗?
        if (y >= heap[ci]) break;   // 是,i就是y的正确位置,退出
        // 否,需要继续向下,重整堆
        heap[i] = heap[ci]; // 大于父节点的孩子节点上升
        i = ci;             // 向下一层,继续搜索正确位置
        ci *= 2;
    }
    heap[i] = y;

    return *this;
}

最大堆的创建

思路一:空堆,n次插入——O(nlogn)

思路二:更好的方法,O(n)n个元素构成完全二叉树,可能不是堆,整理它。

从最后一个内部节点(n/2)开始到根节点,将每个节点i的子树(完全二叉树)整理为堆;整理堆的过程是下降过程,在不断向下寻找当前节点i可以插在哪的过程中,也改变了原有节点的位置。

//建堆函数
template<class T>
void MaxHeap<T>::Initialize(T a[], int size,int ArraySize)
{// Initialize max heap to array a.
    delete[] heap;
    heap = a;
    CurrentSize = size;
    MaxSize = ArraySize;
    // 从最后一个内部节点开始,一直到根,对每个子树进行堆重整
    for (int i = CurrentSize / 2; i >= 1; i--) 
    {
        T y = heap[i]; // 子树根节点元素
        // find place to put y
        int c = 2 * i; // y的左孩子位置

        while (c <= CurrentSize) 
        {
            // 若左孩子小,变为右孩子
            if (c < CurrentSize &&heap[c] < heap[c + 1]) c++;
            // can we put y in heap[c/2]?
            if (y >= heap[c]) break;  // yes
            // no
            heap[c / 2] = heap[c]; // move child up
            c *= 2; // move down a level
        }
        //最后一次跳出时多*2要/掉
        heap[c / 2] = y;
    }
}

堆排序

n个元素——建最大堆

1.n个元素先建最大堆(用到最大堆的创建方法2, 和删除max)

2.将根和最后一个节点交换

3.将[0:n - 1]的节点重排为最大堆

4.重复,直到完成。这样,在一维数组中就是有序的

//堆排序
template <class T>
void HeapSort(T a[], int n)
{// Sort a[1:n] using the heap sort method.
   // create a max heap of the elements
    MaxHeap<T> H(1);
    H.Initialize(a, n, n);	 // 建堆,直接使用a作为堆的空间
    // extract one by one from the max heap
    T x;
    for (int i = n - 1; i >= 1; i--) 
    {
        H.DeleteMax(x);//让x的值是根的值
        a[i + 1] = x;
    }
    // 将堆设置为空,但不释放空间——不销毁a
    H.Deactivate();
}

复杂度:建最大堆:O(n);每次删除一个元素,共删n次:O(nlogn)

总的时间代价是O(nlogn)

霍夫曼编码

用于文本压缩

常用文本压缩:

关键字编码:用单个字符代替常用的单词

行程长度编码:把一系列重复字符替换为它们重复出现的次数,eg:aaaaaaa=a7

霍夫曼编码:考虑字符的出现频率进行编码——频率高的短码低的长码

关键:利用树——任何一个编码都不是其他编码的前缀;节点的权重——字符频率

构造霍夫曼树:选择两个w值最小的树合并,权重相加作为新的权重;重复,直至只剩一棵树

//huffman类
template<class T>
class  Huffman {
    friend BinaryTree<int> HuffmanTree(T[], int);
public:
    operator T () const { return weight; }
private:
    BinaryTree<int> tree;
    T weight;//权重
};

//霍夫曼树构造函数
template <class T>
BinaryTree<int> HuffmanTree(T a[], int n)
{// Generate Huffman tree with weights a[1:n].
   // create an array of single node trees
    Huffman<T>* w = new Huffman<T>[n + 1];
    BinaryTree<int> z, zero;
    for (int i = 1; i <= n; i++) 
    {
        z.MakeTree(i, zero, zero);
        w[i].weight = a[i];
        w[i].tree = z;
    }
    // make array into a min heap
    MinHeap<Huffman<T> > H(1);
    H.Initialize(w, n, n);
    // repeatedly combine trees from heap
    Huffman<T> x, y;
    for (i = 1; i < n; i++) 
    {
        H.DeleteMin(x);
        H.DeleteMin(y);
        z.MakeTree(0, x.tree, y.tree);
        x.weight += y.weight; x.tree = z;
        H.Insert(x);
    }

    H.DeleteMin(x); // final tree
    H.Deactivate();
    delete[] w;
    return x.tree;
}

树转换为二叉树

规则:1.某节点第一个孩子是左节点;某节点的右兄弟是它的右节点。

森林转换为二叉树

前提:由于根没有兄弟,所以树转换为二叉树后,二叉树的根一定没有右子树。

1.先将森林中的每一棵树转换为二叉树;

2.再将第一棵二叉树的根作为转换后二叉树的根;第一棵二叉树的左子树作为转换后二叉树的左子树;

3.从第二颗树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子结点,直到所有二叉树都连接到一起

六、Oj

判断是否为堆-堆整理(苏维埃计算机):最大堆的创建

二叉树遍历及二叉树高度:递归,有一定思考

#include<iostream>
using namespace std;

int GetHeight(char* pre, char* in, int n)   //求二叉树的高度
{
    if (n == 0)    //若没有结点,为空树
    {
        return 0;
    }
    int i;
    for (i = 0; i < n; i++)
    {
        if (in[i] == pre[0])  //找到根结点在中序的位置
        {
            break;
        }
    }
    int left = GetHeight(pre + 1, in, i);  //左子树的深度
    int right = GetHeight(pre + i + 1, in + i + 1, n - i - 1);   //右子树的深度
    return max(left, right) + 1;  //返回左右子树深度的较大值中的较大值+根结点
}
int main()
{
    int n;
    cin >> n;
    char* pre = new char[n + 1]; 
    char*in=new char[n + 1];  //先序和中序
    cin >> pre >> in;
    cout << GetHeight(pre, in, n) << endl;
    return 0;
}


哈夫曼树A

//每次求最小值用堆优先队列来做
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
    int n;
    cin >> n;
    //定义小根堆
    priority_queue<int, vector<int>, greater<int>> heap;
    while (n--)
    {
        int x;
        cin >> x;
        //入堆
        heap.push(x);
    }
    int res = 0;//存结果即最小的体力耗费值
    while (heap.size() > 1)//只要堆当中元素个数大于1
    {
        //取出两个最小的值,进行合并
        int a = heap.top(); heap.pop();
        int b = heap.top(); heap.pop();
        res += a + b;
        heap.push(a + b);//把合并结果入堆,注意入堆后又会有顺序
    }
    cout << res << endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值