数据结构笔记7: 优先队列

优先队列ADT 

 

优先队列的定义

  • 0个或多个元素的集合
  • 每个元素都有一个优先权值
  • 两个元素可以有相同的权值

线性表描述最大优先队列

  • 无序线性表:插入在表头(表尾),删除时要查找最大优先级元素
  • 有序线性表:删除时删除表头(表尾),插入时要查找应在的合法位置

堆及堆排序

堆的定义

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

堆的描述

  • 特殊的完全二叉树:一维数组有效描述
  • 父子节点位置关系:堆节点从0开始编号,则节点i的左孩子是2i+1,右孩子是2i+2
  • n个元素,高度{log_{2}}^{n+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; //堆数组
};

template<class T> MaxHeap<T>::MaxHeap(int MaxHeapSize) {
    MaxSize = MaxHeapSize; 
    heap = new T[MaxSize+1]; 
    CurrentSize = 0;
}
  • 插入操作:要检查是否符合最大堆的定义
template<class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x){
    if (CurrentSize == MaxSize) //堆已满
        throw NoMem(); 
    int i = ++CurrentSize; //更新堆的实际大小,i是新节点应该在的位置(最后)
    while (i != 1 && x > heap[i/2]) { //i还不是根节点,且数据大于父节点
        heap[i] = heap[i/2]; //父节点往下降(根据父子节点位置关系)
        i /= 2; //向上搜索
    }
    heap[i] = x;
    return *this; 
}
  • 删除操作
    (1)删除最大优先级的节点,即删除根
    (2)把树中的最后一个元素换到根上 ➡️ 保持树的结构
    (3)不符合最大树的定义,应选取子节点中较大者与父节点交换,重复,直至符合定义
template<class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x){
    if (CurrentSize == 0) //堆位空
        throw OutOfBounds(); 
    x = heap[1]; //删除最大元素
    T y = heap[CurrentSize--]; //取最后一个元素,同时更新堆的实际大小
    int i = 1, ci = 2; //i表示应该插入的位置,ci表示i的孩子
    while (ci <= CurrentSize) {
        if (ci < CurrentSize && heap[ci] < heap[ci+1]) //使ci指向i的两个孩子中较大者
            ci++;
        if (y >= heap[ci]) //如果取的元素大于等于孩子
            break;
        heap[i] = heap[ci]; //如果取的元素小于孩子,把两个孩子中较大者往上调
        i = ci; //向下找到元素应该在的位置
        ci *= 2; 
    }
    heap[i] = y;
    return *this; 
}

最大堆的创建

  • 思路一:做n次插入操作 ➡️ 插入时需要进行移动,时间复杂度O(n*log n)
  • 思路二:O(n)
    (1)认为n个元素就是一个完全二叉树,最后n/2个元素是叶节点,不会违反堆的定义
    (2)从最后一个可能违反定义的节点往前,检查并调整
template<class T>
void MaxHeap<T>::Initialize(T a[], int size,int ArraySize) {
    delete [] heap;
    heap = a; //默认数组就是一个堆
    CurrentSize = size; 
    MaxSize = ArraySize;
    for (int i = CurrentSize/2; i >= 1; i--) { //从可能违反最大堆定义的节点开始往上检查
        T y = heap[i]; //当前节点
        int c = 2*i; //当前节点的孩子
        while (c <= CurrentSize) {
            if (c < CurrentSize && heap[c] < heap[c+1]) //找到两个孩子中的最大者
                c++;
            if (y >= heap[c]) //如果当前节点比两个孩子大
                break; 
            heap[c/2] = heap[c]; //如果当前节点比两个孩子小,把大的孩子移上去
            c *= 2; //往下查找,找到当前节点应该在的位置
        }
        heap[c/2] = y;
    }
}

堆排序:O(n*log n)

  1. 创建最大堆:见思路二
  2. 重复操作:删除元素 ➡️ 放入末尾(但不把它看成堆里的元素) ➡️ 重整
  3. 原址排序:空间消耗与n无关,没有申请等长的数组
template <class T>
void HeapSort(T a[], int n){
    MaxHeap<T> H(1);
    H.Initialize(a,n,n); //创建最大堆
    T x;
    for (int i = n-1; i >= 1; i--) {
        H.DeleteMax(x); //逐个删除元素,删除的同时堆内会重排
        a[i+1] = x;
    }
    H.Deactivate(); //将堆设置为空,但不释放空间——不销毁a
}

哈夫曼编码

文本压缩

  • 需要找到存储文本信息以及有效地在计算机之间传递它们的方法
  • 如何节省存储空间及提高传输的速度?

关键字编码

  • 方法:将出现频率较高的单词用某一个符号代替,以节省存储空间
  • 缺点:用来代替的符号可能在原来的文本中就存在,出现二义性

形成长度编码

  • 方法:把一系列重复字符替换为它们重复出现的次数
  • 应用:常用于一些大规模数据流中
  • 编码规则:重复字符 ➡️ 标志字符+重复字符+说明字符
  • 说明字符:利用二进制对应的字符,可以表示重复次数4-259

哈夫曼编码

  • 方法:考虑字符的出现频率进行编码,频率高的短码,频率低的长码
  • 注意:编码不能一味考虑短,要考虑解码的时候是否有二义性,任意一个编码都不是其他编码的前缀
  • 构造哈夫曼树
    (1)找到出现频率最小的两个字符,合为一棵小树,根为二者频率的和,左右子树为这两个字符
    (2)重复上述操作,直到所有字符都在树的叶节点,根节点是所有字符出现频率之和
    (3)每个节点的左边标0,右边标1,从根节点开始读数,作为每个字符的编码
    (4)平均编码长度:每个字符出现的频率*编码后的位数
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){ //构造哈夫曼树
    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;
    }
    MinHeap<Huffman<T> > H(1); 
    H.Initialize(w,n,n);
    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); 
    H.Deactivate();
    delete [] w;
    return x.tree;
}

作业7

重新编写maxHeap类

//13 5 2 8 7 9 23 77 0 12

template<typename T>
class maxHeap{
public:
    maxHeap(int size){ //初始化
        n=size; //最开始的实际大小(不包括maxElement,minElement)
        heap=new T[n*2+1]; //给数组分配空间
        heap[0]=maxElement; //设置上界
        heap[n+1]=minElement; //设置下界
    }
    maxHeap& insert(T t){ //插入,不需要增加数组空间
        int i=++n; //先从尾巴开始找
        while(i!=1&&t>heap[i/2]){ //如果要插入的树比父节点大
            heap[i]=heap[i/2]; //把父节点往下移
            i/=2; //继续向上寻找
        }
        heap[i]=t; //插入
        heap[n+1]=minElement; //更新下界的位置
        return *this;
    }
    maxHeap& deleted(){ //删除,不需要重组
        if(n==0){
            cout<<"Heap empty!"<<endl;
            return *this;
        }
        T x=heap[n--]; //获取最后一个元素,想象它放在第一个位置
        int i=1,ci=2; //i记录最后一个元素应该在的位置,ci记录它的孩子
        while(ci<=n){
            if(ci<n&&heap[ci]<heap[ci+1]) //ci是孩子中较大的那个
                ci++;
            if(x>=heap[ci])
                break;
            heap[i]=heap[ci]; //如果x比当前位置的孩子小,把孩子往上移
            i=ci; //自己往下移
            ci*=2; //更新孩子的位置
        }
        heap[i]=x; //把x放到合适的位置
        heap[n+1]=minElement; //更新下界的位置
        return *this;
    }
    void initialize(T *a){ //最大堆的创建
        for(int i=1;i<=n;i++) //先把数组的元素一个个存进去
            heap[i]=a[i-1];
        for(int i=n/2;i>=1;i--){ //从可能违反最大堆定义的地方开始往上检查
            T temp=heap[i];
            int mark=2*i;
            while(mark<=n){
                if(mark<n&&heap[mark]<heap[mark+1]) //获取孩子中较大的那个
                    mark++;
                if(temp>heap[mark]) //如果比孩子大,则合法
                    break;
                heap[mark/2]=heap[mark]; //否则把孩子往上移
                mark*=2;
            }
            heap[mark/2]=temp;
        }
    }
    void print(){ //打印最大堆
        int h=ceil(log(n+1)/log(2)); //求最大堆的高度
        int mark=1;
        cout<<"    "; //这里的空格都没有意义,只是为了好看
        for(int i=1;i<=n;i++){
            for(int j=0;j<h*3;j++)
                cout<<" ";
            if(i==3)
                cout<<"    ";
            cout<<heap[i];
            if(i==pow(2, mark)-1){
                cout<<endl;
                mark++;
                h--;
            }
        }
        cout<<endl;
    }
    T getTop(){ //获取堆最大的元素
        return heap[1];
    }
private:
    int maxElement=2147483647; //上界
    int minElement=-2147483648; //下界
    int n; //堆的有效容量
    T *heap; //堆数组
};

int main(){
    int size;
    cout<<"Please input array's size: ";
    cin>>size;
    cout<<endl;
    maxHeap<int> mh(size);
    int *a=new int[size];
    cout<<"Please input "<<size<<" integers!"<<endl;
    for(int i=0;i<size;i++)
        cin>>a[i];
    cout<<endl;
    mh.initialize(a);
    mh.print();
    int t;
    for(int i=0;i<size/2;i++){
        cout<<"Please input an integer you want to insert:";
        cin>>t;
        cout<<endl;
        mh.insert(t);
        mh.print();
    }
    for(int i=0;i<size/2;i++){
        cout<<"Delete max element:"<<mh.getTop()<<endl;
        mh.deleted();
        mh.print();
        cout<<endl;
    }
}

基于哈夫曼编码的压缩-解压包

//merrychristmashoneywishyouahappynewyear

struct word{ //存储输入的字符
    char c;
    double fre;
} *article; //存储所有的字符(不重复存储)

int capacity=0; //article的实际容量

int find(char ch){ //找到字符
    for(int i=0;i<capacity;i++){
        if(article[i].c==ch)
            return i;
    }
    return capacity;
}

void input(string s){ //输入文章
    article=new word[s.length()]; //初始化
    for(int i=0;i<s.length();i++){
        article[i].c=' ';
        article[i].fre=0;
    }
    for(int i=0;i<s.length();i++){
        int mark=find(s[i]);
        if(article[mark].c!=s[i]){ //如果该字符不存在
            article[mark].c=s[i]; //存储字符
            capacity++;
        }
        article[mark].fre++; //出现的次数+1
    }
    for(int i=0;i<capacity;i++) //计算频率
        article[i].fre/=s.length();
}

int compare(word w1,word w2){ //定义struct如何比较大小(根据频率)
    return w1.fre<w2.fre;
}

class node{ //结点类
public:
    word data; 
    node *next;
};

class BinaryTree;

class treeNode{
    friend BinaryTree; //声明友元
public:
    treeNode(){
        parent=leftchild=rightchild=NULL;
    }
    treeNode(const word& t){
        data=t;
        parent=leftchild=rightchild=NULL;
    }
    treeNode(const word& t,treeNode *l,treeNode*r){
        data=t;
        leftchild=l;
        rightchild=r;
        leftchild->parent=this;
        rightchild->parent=this;
    }
    word data;
    treeNode *parent; //父节点
    treeNode *leftchild;
    treeNode *rightchild;
};

class wordList{ //存储字符的链表
public:
    bool isEmpty(){
        return head==NULL;
    }
    wordList& initialize(string s){ //初始化
        input(s); //输入所有的字符
        sort(article, article+capacity, compare); //按照频率大小整理
        node *p=new node;
        p->data=article[capacity-1]; //获取频率最大的字符
        head=p; //把它放在链表头节点
        head->next=NULL;
        for(int i=capacity-2;i>=0;i--){ 
            //按从大到小的顺序遍历article,好处是每次插入只需要更新头节点,不需要遍历整个链表
            //全部插入后链表依然是从小到大的顺序
            node *q=new node;
            q->data=article[i];
            q->next=head;
            head=q;
        }
        //按顺序输出存进链表后的字符及其频率
        cout<<endl<<"Word list:"<<endl; 
        p=head;
        while(p){
            cout<<p->data.c<<" "<<p->data.fre<<endl;
            p=p->next;
        }
        cout<<endl;
        return *this;
    }
    wordList& deleteHead(){ //删除头节点
        if(!isEmpty()){
            head=head->next;
        }
        return *this;
    }
    wordList& insert(node *w){ 
        //按照频率大小顺序,插入节点
        //由于链表有序,可以推定每次搜索只需要搜索前几个就能找到应该插入的位置
        //因此时间复杂度推定总是远小于n
        node *pre=new node;
        pre=w;
        pre->next=NULL;
        if(isEmpty()){
            head=pre;
            return *this;
        }
        node *p=head;
        while(p->next&&p->next->data.fre<w->data.fre)
            p=p->next;
        pre->next=p->next;
        p->next=pre;
        return *this;
    }
    node *head;
};

class BinaryTree{ //二叉树
public:
    BinaryTree(){
        root=NULL;
        count=0;
    }
    BinaryTree(treeNode *n){
        root=n;
        count=0;
    }
    bool isEmpty(){
        return ((root==NULL)? true:false);
    }
    treeNode* getRoot(){ //返回根节点
        return root;
    }
    void combine(const word& t,BinaryTree& left,BinaryTree& right){ //合并树
        root=new treeNode(t,left.root,right.root);
        left.root=right.root=NULL;
    }
    BinaryTree& huffmanTree(wordList w);
    treeNode* find(treeNode *t,word w){ //根据字符找到叶节点
        queue<treeNode*> q; //存储有左右子树的节点
        while(t){
            if(t->data.c==w.c&&t->data.fre==w.fre)
                return t;
            if(t->leftchild)
                q.push(t->leftchild); //入队
            if(t->rightchild)
                q.push(t->rightchild);
            if(!q.empty()){
                t=q.front();
                q.pop(); //出队
            }
            else //队列为空时退出(仅限于完全二叉树)
                break;
        }
        return NULL;
    }
    void code(){ //根据哈夫曼树,输出哈夫曼编码
        for(int i=0;i<capacity;i++){ //遍历article的所有字符,输出它的编码
            int *hc=new int[100]; //存储每个字符的编码
            int index=0;
            treeNode *p=find(root,article[i]); //找到该字符所在的叶节点
            while(p!=root){
                if(p->parent){ //向上找父节点
                    if(p->parent->leftchild==p)
                        hc[index]=0; //左孩子编码为0
                    else
                        hc[index]=1; //右孩子编码为1
                    index++;
                    p=p->parent; //继续向上,直到根节点
                }
            }
            cout<<article[i].c<<" : ";
            //注意这里要倒序输出,因为存储是从叶节点开始存的
            for(int j=index-1;j>=0;j--) 
                cout<<hc[j];
            cout<<endl;
        }
        cout<<endl;
    }
    void decode(string key){ //根据输入的编码,解码
        int *hc=new int[100]; //把输入的字符串转为编码
        int index=0;
        for(int i=0;i<key.length();i++){
            if(key[index]=='0')
                hc[index]=0;
            else if(key[index]=='1')
                hc[index]=1;
            else{
                cout<<"Wrong input!"<<endl;
                return;
            }
            index++;
        }
        treeNode *p=root; //从根节点开始
        for(int i=0;i<index;i++){
            if(hc[i]==0) //编码为0跳到左孩子
                p=p->leftchild;
            else //编码为1跳到右孩子
                p=p->rightchild;
            if(!p){ //如果结点为空则为错误输入
                cout<<"Wrong input!"<<endl;
                return;
            }
        }
        if(!p){ //如果结点为空则为错误输入
            cout<<"Wrong input!"<<endl;
            return;
        }
        cout<<"Decode: "<<p->data.c<<endl<<endl; //输出解码结果
    }
    treeNode *root; //根节点
    int count; //森林里的树的个数
};

BinaryTree *bt=new BinaryTree[100]; 
//森林,因为哈夫曼编码过程中可能出现多个二叉树

BinaryTree& BinaryTree::huffmanTree(wordList w){ //构造哈夫曼树
    //当字符链表有两个以上元素,即还没有把所有的字符串到一棵树上
    while(w.head->next){ 
        word temp; //把两个频率最低的字符串起来
        temp.c=' ';
        temp.fre=w.head->data.fre+w.head->next->data.fre;
        treeNode *nl=new treeNode; //左节点
        int i=0;
        //在森林里寻找是否有该字符,即频率最低的是多个字符的和还是单个字符
        for(;i<count;i++){
            if(bt[i].root&&bt[i].root->data.c==w.head->data.c&&
               bt[i].root->data.fre==w.head->data.fre)
                break;
        }
        //如果是单个字符
        if(i==count)
            nl->data=w.head->data;
        //如果是多个字符的和
        else{
            nl=bt[i].root;
            bt[i].root=NULL; //用过的树记得删除
        }
        treeNode *nr=new treeNode; //右节点
        i=0;
        for(;i<count;i++){
            if(bt[i].root&&bt[i].root->data.c==w.head->next->data.c&&
               bt[i].root->data.fre==w.head->next->data.fre)
                break;
        }
        if(i==count)
            nr->data=w.head->next->data;
        else{
            nr=bt[i].root;
            bt[i].root=NULL;
        }
        BinaryTree tempLeft(nl);
        BinaryTree tempRight(nr);
        combine(temp,tempLeft,tempRight); //合并树
        bt[count].root=root; //把新的树存到森林里
        count++; //森林的树数目加一
        w.deleteHead(); //删除头节点
        w.deleteHead();
        node *tr=new node;
        tr->data=temp;
        w.insert(tr); //把新的树的频率存进字符链表里,等待下一次运算
    }
    return *this;
}

int main(){
    string s;
    cout<<"Please input str:"<<endl;
    cin>>s;
    wordList w;
    w.initialize(s);
    BinaryTree b;
    b.huffmanTree(w);
    cout<<"Huffman code:"<<endl;
    b.code();
    cout<<"Please input huffman code: ";
    string key;
    for(int i=0;i<4;i++){
        cin>>key;
        b.decode(key);
    }
    return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值