1. 程式人生 > >CS106B Assignment #6: Priority Queue

CS106B Assignment #6: Priority Queue

製作自己的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 ---------------------