制作自己的PQueue类
pqueue:队列的拓展,有优先级的队列,即出队时 是先出优先级最大的。
动手设计类的接口,并通过4种方式实现
一. The interface
基本是以下这些,还加个几个debug用的函数
class PQueue
{
public:
PQueue();
~PQueue();
int size();
bool isEmpty();
void enqueue(int newElem);
int dequeueMax();
};
pqueue.h文件:
/*
* File: pqueue.h
* --------------
* Defines the interface for the priority queue class.
*
* Julie Zelenski, CS106, Fall 2007
*/
#ifndef _pqueue_h
#define _pqueue_h
#include "genlib.h"
#include "vector.h"
#include "disallowcopy.h"
/*
* Class: PQueue
* -------------
* This is the class for a priority queue. This is not
* simple FIFO queue, it is a priority queue, where elements are
* retrieved in order of priority, not just by longevity in queue.
* The elements are integers and the integer is assumed to represent
* the priority (larger integer is higher priority).
*/
class PQueue
{
public:
/*
* Constructor: PQueue
* Usage: PQueue pq;
* PQueue *ppq = new PQueue;
* ---------------------------------
* Initializes a new pqueue to have no elements.
*/
PQueue();
/*
* Destructor: ~PQueue
* Usage: delete ppq;
* ------------------
* Deallocates all the storage associated with this pqueue.
*/
~PQueue();
/*
* Member function: isEmpty
* Usage: if (pq.isEmpty()) . . .
* -------------------------------
* Returns true if this pqueue contains no elements.
*/
bool isEmpty();
/*
* Member function: size
* Usage: nElemes = pq.size();
* ---------------------------
* Returns number of elements contained in this pqueue.
*/
int size();
/*
* Member function: enqueue
* Usage: pq.enqueue(val);
* -----------------------
* Adds the specified element to this pqueue. No effort is made to
* avoid duplicates.
*/
void enqueue(int newElem);
/*
* Member function: eequeueMax
* Usage: maxElem = pq.dequeueMax();
* ---------------------------------
* Removes the largest priority element from this pqueue and returns it.
* If this pqueue is empty, this function raises an error.
*/
int dequeueMax();
/*
* Member function: bytesUsed
* Usage: numBytes = pq.bytesUsed();
* ----------------------------------
* This function would not usually be included as part of the class,
* but this is here as part of evaluating the tradeoffs betweem
* implementations. Given a pqueue, this function counts up
* and return the total amount of space used given its
* current contents.
*/
int bytesUsed();
/*
* Member function: implementationName
* Usage: cout << pq.implementationName();
* ---------------------------------------
* This operation would not usually be included as part of the class
* class, but is included to help with analyzing and reporting results.
* This member function returns a string that describes the PQueue
* implementation strategy ("sorted linked list", "vector", etc.).
*/
string implementationName();
/*
* Member function: printDebuggingInfo
* Usage: pq.printDebuggingInfo();
* -------------------------------
* This operation would not usually be included as part of the class,
* but is included to give you a hook to put any helpful debugging
* print code (for example, something that prints the goopy details of
* the internal structure). You don't need to implement this routine and can
* ignore it entirely, but you may find it helpful to use as you are
* doing development.
*/
void printDebuggingInfo();
private:
// If implemented using Vector data mamber, default memberwise copy
// works fine, but if implemented as linked list, (ie pointer data member)
// copying would create unintended sharing.
// It's fine is to disallow copying for all implementations as
// a precaution
DISALLOW_COPYING(PQueue)
//unsortedvector的数据/
Vector<int> entries;
//
//sortedlist的数据/
struct Cell {
int value;
Cell *next;
};
Cell *head;
//
//chunklist的数据//
struct Cell{
int * values;
Cell* next;
int BlockInUse;
};
Cell* head;
PQueue::Cell *initCell(int newValue, Cell *nextCell);
void shiftAdd(Cell *cell, int newValue);
void splitAdd(Cell *cur, int newValue);
//
///heap的数据//
int* array;
int capacity;
int count;
void swap (int indexA, int indexB);
void expandCapacity();
//
};
#endif
二. The implementation
1. Unsorted vector implementation
用未排序的Vector实现,之所叫未排序,因为入队时是无序入队,出队是采用一个个比较出队
关键出队函数:
int PQueue::dequeueMax()
{
if (isEmpty())
Error("Tried to dequeue max from an empty pqueue!");
int maxIndex = 0; // assume first element is largest until proven otherwise
int maxValue = entries[0];
for (int i = 1; i < entries.size(); i++) {
if (entries[i] > maxValue) {
maxValue = entries[i];
maxIndex = i;
}
}
entries.removeAt(maxIndex); // remove entry from vector
return maxValue;
}
2. Sorted linked list implementation
排序好的链表实现。排序好的意思是入队是按次序接入链表
关键入队函数:
void PQueue::enqueue(int newValue)
{
Cell *cur, *prev, *newOne = new Cell;
newOne->value = newValue;
for (prev = NULL, cur = head; cur != NULL; prev=cur, cur = cur->next) {
if (newValue > cur->value) break;
}
newOne->next = cur;
if (prev)
prev->next = newOne;
else
head = newOne;
}
3. Sorted chunklist implementation
采用chunklist的思想设计
像这样每个链表存储一个vector,vector的大小自己设定
pqchunk.cpp:
#include "pqueue.h"
#include "genlib.h"
#include <iostream>
int const MaxElemsPerBlock = 4; //设定每个chunk的大小
PQueue::PQueue()
{
head = NULL; //创建头指针
}
PQueue::~PQueue()
{
while (head != NULL)
{
Cell* next = head->next;
delete[]head->values; //删除数组
delete head; //删除指针
head = next;
}
}
bool PQueue::isEmpty()
{
return (head == NULL);
}
int PQueue::size()
{
int count = 0;
for(Cell *cur = head; cur != NULL; cur = cur->next)//把所有的BlockInUse加起来就可以
count += cur->BlockInUse;
return count;
}
void PQueue::enqueue(int newValue)
{
//加入第一个数,并得到head
if(head == NULL)
{
Cell *newChunk = initCell(newValue, NULL);
head = newChunk;
return;;
}
Cell *cur, *prev, *newChunk = new Cell;
for(prev = NULL,cur = head; cur != NULL; prev = cur,cur = cur->next)//遍历链表的套路模版
{
for (int i = 0; i < cur->BlockInUse; i++)
{
if (newValue > cur->values[i])
{
// 数组未满直接插入
if (cur->BlockInUse < MaxElemsPerBlock)
{
shiftAdd(cur,newValue);
return;
}
// 如果数是最大的,需要创建新的头
else if (i == 0 && prev == NULL)
{
Cell *newChunk1 = initCell(newValue, cur);
head = newChunk1;
return;
}
// 把值插入前一块中,且前一块 块数未满
else if (i == 0 && prev != NULL && prev->BlockInUse < MaxElemsPerBlock)
{
shiftAdd(prev,newValue);
return;
}
// 这种情况当前块和之前块都满链链,于是采用splidAdd
else
{
splitAdd(cur,newValue);
return;
}
}
}
}
}
/*
* 把值插入Cell的数组中
* 此函数只适用于数组未满的情况
*/
void PQueue::shiftAdd(Cell *cell, int newValue)
{
int index;
for(index = 0; index < MaxElemsPerBlock; index++)
{
if(newValue > cell->values[index]) //找到插入位置
break;
}
for(int i=MaxElemsPerBlock-1; i > index; i--)
cell->values[i] = cell->values[i-1]; //shift 数组里面的值
cell->values[index] = newValue; //插入
cell->BlockInUse++;
}
/*
*把值插入已经满的Cell的数组
*步骤:新Cell-->复制满的一半到新Cell中-->连接新的Cell-->把值插入
*/
void PQueue::splitAdd(Cell *cur, int newValue)
{
Cell* newChunk = new Cell;
newChunk->values = new int[MaxElemsPerBlock];
for(int i = MaxElemsPerBlock/2; i < MaxElemsPerBlock; i++)
{
newChunk->values[i-(MaxElemsPerBlock/2)] = cur->values[i]; //复制一半到新Cell中
//将cur的后一半归0
}
cur->BlockInUse = MaxElemsPerBlock/2;
newChunk->BlockInUse = MaxElemsPerBlock/2;
newChunk->next = cur->next;//连接新的Cell
cur->next = newChunk;
//把值插入cur中
for (int j = 0; j < cur->BlockInUse; j++)
{
if (newValue > cur->values[j])
{
shiftAdd(cur,newValue);
return;
}
}
// 把值插入newchunk中
for (int k = 0; k < newChunk->BlockInUse; k++)
{
if (newValue > newChunk->values[k])
{
shiftAdd(newChunk,newValue);
return;
}
}
}
/*
* 创建一个新的Cell,并返回Cell的指针
* 该Cell指针指向传入的指针,若传NULL,表明Cell位于链表末端
*/
PQueue::Cell* PQueue::initCell(int newValue, Cell *nextCell)
{
Cell* newChunk = new Cell;
newChunk->values = new int[MaxElemsPerBlock];
newChunk->values[0] = newValue;
newChunk->BlockInUse = 1;
newChunk->next = nextCell;
return newChunk;
}
int PQueue::dequeueMax()
{
if (isEmpty())
Error("Tried to dequeue max from an empty pqueue!");
int num = head->values[0]; // 需要返回的值,因为入队已经排序好,所以在第一个
// 如果本来含有>1个数,则直接移动
if (head->BlockInUse > 1)
{
for (int i = 0; i < head->BlockInUse-1; i++)
{
head->values[i] = head->values[i+1];
}
}
head->BlockInUse--;
// 没有剩余数
if (head->BlockInUse == 0)
{
Cell *toBeDeleted = head;
head = head->next; // 删除Cell
delete[] toBeDeleted->values;
delete toBeDeleted;
}
return num;
}
int PQueue::bytesUsed()
{
int total = sizeof(*this);
for (Cell *cur = head; cur != NULL; cur = cur->next)
total += sizeof(*cur);
return total;
}
string PQueue::implementationName()
{
return "chunk list";
}
void PQueue::printDebuggingInfo() {
int count = 0;
cout << "------------------ START DEBUG INFO ------------------" << endl;
for (Cell *cur = head; cur != NULL; cur = cur->next) {
cout << "Cell #" << count << " (at address " << cur << ") values = ";
for (int i = 0; i < cur->BlockInUse; i++) {
cout << cur->values[i] << " ";
}
cout << " next = " << cur->next << endl;
count++;
}
cout << "------------------ END DEBUG INFO ------------------" << endl;
}
测试结果:
----------- Testing Basic PQueue functions -----------
The pqueue was just created. Is it empty? true
Now enqueuing integers from 1 to 10 (increasing order)
Pqueue should not be empty. Is it empty? false
Pqueue should have size = 10. What is size? 10
------------------ START DEBUG INFO ------------------
Cell #0 (at address 0x7f96a0500170) values = 10 9 next = 0x7f96a05000c0
Cell #1 (at address 0x7f96a05000c0) values = 8 7 6 5 next = 0x7f96a0500010
Cell #2 (at address 0x7f96a0500010) values = 4 3 2 1 next = 0x0
------------------ END DEBUG INFO ------------------
Dequeuing the top 5 elements: 10 9 8 7 6
Pqueue should have size = 5. What is size? 5
------------------ START DEBUG INFO ------------------
Cell #0 (at address 0x7f96a05000c0) values = 5 next = 0x7f96a0500010
Cell #1 (at address 0x7f96a0500010) values = 4 3 2 1 next = 0x0
------------------ END DEBUG INFO ------------------
Dequeuing all the rest: 5 4 3 2 1
Pqueue should be empty. Is it empty? true
------------------ START DEBUG INFO ------------------
------------------ END DEBUG INFO ------------------
4. Heap implementation
采用堆排序的思想,用array存放数据,且当存的数的数量超过范围时,array会自动变大
pqheap.cpp:
#include "pqueue.h"
#include "genlib.h"
#include <iostream>
PQueue::PQueue()
{
capacity = 10;
array = new int [capacity];
count = 1;
array[0] = 0;
}
PQueue::~PQueue()
{
delete[] array;
}
bool PQueue::isEmpty()
{
if(count <= 1)
return true;
else return false;
}
int PQueue::size()
{
if(count ==0)
return 0;
else
return count-1;
}
void PQueue::enqueue(int newElem)
{
if(count == capacity) expandCapacity();
array[count++]= newElem;
int index = count -1;
//堆插入过程
while(index >1 && array[index] > array[index/2])
{
swap(index, index/2);
index /= 2;
}
}
int PQueue::dequeueMax()
{
if(isEmpty())
Error("Tried to dequeue max from an empty pqueue!");
int value = array[1]; //取出最大值
array[1] = array[--count];
int index =1;
//堆重新生成过程
while(index*2 < count)
{
if((index*2 +1) < count &&
array[(index*2) +1] > array[index*2] &&
array[index] < array[(index*2 +1)])
{
swap(index, (index*2)+1);
index = (index*2)+1;
}
else if(array[index] < array[index*2])
{
swap(index, index*2);
index = index*2;
}
else break;
}
return value;
}
void PQueue::swap(int indexA, int indexB)
{
int temp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
int PQueue::bytesUsed()
{
return sizeof(*this) + count;
}
string PQueue::implementationName()
{
return "heap";
}
void PQueue::printDebuggingInfo()
{
cout << "------------------ START DEBUG INFO ------------------" << endl;
cout << "Pqueue contains " << size() << " entries" << endl;
for (int i = 1; i < count ; i++)
cout << array[i] << " ";
cout << endl;
cout << "------------------ END DEBUG INFO ------------------" << endl;
}
void PQueue::expandCapacity()
{
int* oldarray = array;
capacity *=2;
array = new int[capacity];
for(int i=0; i< count; i++)
{
array[i] = oldarray[i];
}
delete[] oldarray;
}
测试结果:
----------- Testing Basic PQueue functions -----------
The pqueue was just created. Is it empty? true
Now enqueuing integers from 1 to 10 (increasing order)
Pqueue should not be empty. Is it empty? false
Pqueue should have size = 10. What is size? 10
------------------ START DEBUG INFO ------------------
Pqueue contains 10 entries
10 9 6 7 8 2 5 1 4 3
------------------ END DEBUG INFO ------------------
Dequeuing the top 5 elements: 10 9 8 7 6
Pqueue should have size = 5. What is size? 5
------------------ START DEBUG INFO ------------------
Pqueue contains 5 entries
5 4 2 1 3
------------------ END DEBUG INFO ------------------
Dequeuing all the rest: 5 4 3 2 1
Pqueue should be empty. Is it empty? true
------------------ START DEBUG INFO ------------------
Pqueue contains 0 entries
------------------ END DEBUG INFO ------------------
Enqueuing 500 numbers into pqueue in increasing order.
Using dequeue to pull out numbers in sorted order. Are they sorted? 1
Enqueuing 500 random values into the pqueue.
Using dequeue to pull out numbers in sorted order. Are they sorted? 1
Now, let's run an empirical time trial.
How large a pqueue to time? (10000 to 1000000, or 0 to quit): 10000
---- Performance for 10000-element pqueue (heap) -----
Time to enqueue into 10000-element pqueue: 0.127 usecs
Time to dequeue from 10000-element pqueue: 0.185 usecs
Time to pqsort random sequence of 10000 elements: 2.295 msecs
Time to pqsort sorted sequence of 10000 elements: 3.262 msecs
Time to pqsort reverse-sorted sequence of 10000 elements: 1.593 msecs
Running memory trial on 10000-element pqueue
After consecutive enqueues, 10000-element pqueue is using 41 KB of memory
After more enqueue/dequeue, 9936-element pqueue is using 41 KB of memory
------------------- End of trial ---------------------