CS106B Assignment #6: Priority Queue
阿新 • • 發佈:2018-12-16
製作自己的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 ---------------------