优先队列ADT
优先队列的定义
- 0个或多个元素的集合
- 每个元素都有一个优先权值
- 两个元素可以有相同的权值
线性表描述最大优先队列
- 无序线性表:插入在表头(表尾),删除时要查找最大优先级元素
- 有序线性表:删除时删除表头(表尾),插入时要查找应在的合法位置
堆及堆排序
堆的定义
- 最大树:每个节点的值都大于或等于其子节点(若存在)的值
- 最大堆:是一棵最大树,同时是一棵完全二叉树
堆的描述
- 特殊的完全二叉树:一维数组有效描述
- 父子节点位置关系:堆节点从0开始编号,则节点i的左孩子是2i+1,右孩子是2i+2
- n个元素,高度
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)
- 创建最大堆:见思路二
- 重复操作:删除元素 ➡️ 放入末尾(但不把它看成堆里的元素) ➡️ 重整
- 原址排序:空间消耗与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;
}