heap的一些實現,二叉堆,左式堆,二項隊列

//這裏的堆指的都是二叉堆,爲了優先隊列產生(優先隊列,使一些特殊的結點在出隊的時候要優先出來。出隊入隊操作變成了insert和delete)
//堆是一個完全二叉樹,除了最後一層,其餘層都是滿的。這樣的話存儲用一個數組就可以,任一個元素位置在i,其左兒子位置是2*i,右兒子位置是2*i+1,父結點是i/2向下取整
//完全二叉樹的高度是logN的下界,所以涉及到完全二叉樹的操作,是跟這個高度相關的,就是O(logN),比如下面的上濾和下濾
//buildheap是O(N),它的界就是堆兩個結點之間的線的條數,這個可以通過計算堆中所有結點的高度和來得到,這個和是O(N)
//d堆,二叉堆是2堆,這樣就理解d堆是什麼了。書上說實踐中4堆可以勝過二叉堆......
//二叉堆和BST(二叉搜索樹)的區別,二叉搜索樹的左結點<父結點<右結點,堆是父結點小於孩子結點,孩子結點的順序沒有比。大於同理。
template <typename Comparable>
class BinaryHeap
{
public:
	expicit BinaryHeap(int capcity = 100);     //構造函數
	expicit BinaryHeap(const vector<Comparable> & items) :array(items.size() + 10), currentSize(items.size())  //拷貝構造函數
	{
		for (int i = 0; i < items.size(); i++)
			array[i + 1] = items[i];
		buildHeap();
	}

	bool isEmpty() const;
	const Comparable & findMin() const;
	void insert(const Comparable & x)   //insert相當於入隊操作
	{
		if (currentSize == array.size() - 1)    //一開始覺得這裏寫的不對,後來明白了,這個vector,0的位置是空的,第一個元素從1開始,所以vector.size()==1的時候,currentSize是0。
			array.resize(array.size() * 2);
		int hole = ++currentSize;
		for (; hole > 1 && x < array[hole / 2]; hole /= 2)      //這裏直接賦值,避免了交換,如果一個元素上濾d層,交換實施的賦值的次數是3d,而這裏是d+1
		{
			array[hole] = array[hole / 2];
		}
		array[hole] = x;
	}

	void deleteMin()                     //deleteMin相當於出隊操作,操作可以迅速執行依賴於堆序性質,最小的在根上,這個規律遞歸到子堆
	{
		if (isEmpty())
			throw UnderflowEception();
		array[1] = array[currentSize--];
		percolateDown(1);
	}

	void deleteMin(Comparable & minItem)  //和上面的區別,就是把出隊的元素存到minItem裏了
	{
		if (isEmpty)
			throw UnderflowException();
		minItem = array[1];
		array[1] = array[currentSize--];
		percolateDown(1);
	}

	void makeEmpty();

private:
	int currentSize;
	vector<Comparable> array;


	void buildHeap()                 //這個堆(現在還不是堆)先是亂排的,然後從下往上一直下濾,這個堆就是有序的了
	{
		for (int i = currentSize / 2; i >= ; i--)
		{
			percolateDown(i);
		}
	}

	void percolateDown(int hole)     //下濾,上面調用的函數
	{
		int child;
		Comparable tmp = array[hole];
		for (; hole * 2 <= currentSize; hole = child)
		{
			child = hole * 2;
			if (child != currentSize && array[child] > array[child + 1])
			{
				child++;
			}
			if (array[child] < tmp)
			{
				array[hole] = array[child];
			}
			else
				break;
		}
		array[hole] = tmp;
	}
};




//左式堆:1.左兒子的零路徑長至少與右路徑的零路徑長一樣大;2.任一結點的零路徑長比它的諸兒子結點的零路徑長的最小值多1。merge用到了這兩個性質
//n個結點的左式樹有一條右路經最多含有log(N+1)的下界個結點。對左式堆操作的一般思路是,將所有的工作放到右路徑上進行,保證樹深短。
//從樹開始,這裏面的類定義都有一個技巧,就是用公有的函數調用私有的函數
template<typename Comparable>
class LeftistHeap
{
public:
	LeftistHeap();
	LeftistHeap(const LeftistHeap & rhs);
	~LefttistHeap();

	bool isEmpty() const;
	const Comparable & findMin() const;

	void insert(const Comparable & x)
	{
		root = merge(new LefttistNode(x), root);
	}

	void deleteMin()
	{
		if (isEmpty())
			throw UnderflowException();
		LeftistNode * oldroot = root;
		root = merge(root->left, root->right);
		delete oldRoot;
	}

	void deleteMin(Comparable & minItem)
	{
		minItem = findMin();
		deleteMin();
	}

	void makeEmpty();
	void merge(LeftistHeap & rhs)                   //合併,左式堆的最主要的算法。遞歸的將具有大的根值的堆和具有小的根值的堆的右子堆合併。所以執行合併的時間與右路徑的長的和成正比,合併兩個左式堆的時間界O(logN)
	{
		if (this == &rhs)
		{
			return;
		}
		root = merge(root, rhs.root);
		rhs.root = NULL;
	}

	const LeftistHeap & operator=(const LiftistHeap & rhs);

private:
	struct LeftistNode
	{
		Comparable element;
		LeftistHeap *left;
		LeftistHeap *right;
		int npl;                    //這個值是零路徑長

		LeftistNode(const Comparable & theElement, LeftistNode *lt = NULL, LeftistNode *rt = NULL, int np = 0) :element(theElement), left(lt), right(rt), npl(np){}
	};

	LeftistNode * root;
	LeftistNode * merge(LeftistNode *h1, LeftistNode *h2)
	{
		if (h1 == NULL)            
			return h2;
		if (h2 == NULL)
			return h1;
		if (h1->element < h2->element)
			return merge1(h1, h2);
		else
			return merge1(h2, h1);
	}

	LeftistNode * merge1(LeftistNode *h1, LeftistNode *h2);
	{
		if (h1->left == NULL)   //左式堆,如果到了這步,那麼其餘的部分都弄好了,只剩下最下邊的了
		{
			h1->left = h2;
		}
		else
		{
			h1->right = merge(h1->right, h2);
			if ((h1->left->npl) < (h1->right->npl))
			{
				swapChildren(h1);
			}
			h1->npl = h1->right->npl + 1;
		}
		return h1;
	}

	void swapChildren(LeftistNode *t);
	void reclainMemory(LeftistNode *t);
	LeftistNode * clone(LeftistNode *t) const;
};




//二項隊列,是一個二項樹的森林,N個結點,用幾棵二項樹組成
template<typename Comparable>
class BinomialQueue
{
public:
	BinomialQueue();
	BinomialQueue(const Comparable & item);
	BinomialQueue(const BinomialQueue & rhs);
	~BinomialQueue();
	bool isEmpty() const;
	const Comparable & findMin() const;
	void insert(const Comparable & x);
	void deleteMin();
	void deleteMin(Comparable & minItem)          //挨個找森林中每棵樹的根,最小的在這個根上,然後找到那棵樹之後,把根刪了,把刪掉根之後的子樹看成是另一個森林,與之前的合併
	{
		if (isEmpty())
		{
			throw UnderflowException();
		}
		int minIndex = findMinIndex();   //找到最小的是哪棵樹的樹根,返回座標
		minItem = theTrees[minIndex]->element;

		BinomialNode *oldRoot = theTrees[minIndex];
		BinomialNode *deletedTree = oldRoot->leftChild;
		delete oldRoot;

		BinomialQueue deletedQueue;       //把找到的那棵樹的根刪除之後,剩下的變成一個新的森林
		deletedQueue.theTrees.resize(minIndex + 1);        //爲什麼我覺得resize(minIndex)也可以......
		deletedQueue.currentSize = (1 << minIndex) - 1;   //就是2^minIndex-1,就是那棵樹去掉根結點之後的結點數
		for (int j = minIndex - 1; j >= 0; j--)           //這個就是造森林的過程,代碼沒問題.......二項隊列的特點,每一棵樹的子樹,都是層數從0往上排的.......只是整個森林不一定每棵樹都有
		{
			deletedQueue.theTrees[j] = deletedTree;
			deletedTree = deletedTree->nextSibling;
			deletedQueue.theTree[j]->nextSibling = NULL;
		}
		theTrees[minIndex] = NULL;
		currentSize -= deletedQueue.currentSize + 1;
		merge(deletedQueue);
	}
	void makeEmpty();
	void merge(BinomialQueue & rhs)
	{
		//合併當前和rhs
		if (this == &rhs)
			return;
		currentSize += rhs.currentSize;

		if (currentSize > capacity())      //擴容......
		{
			int oldNumTrees = theTrees.size();
			int newNumTrees = max(theTrees.size(), rhs.theTrees.size()) + 1;       //+1是這樣的,比如兩個都是最多有高度爲4的樹,那麼合併之後,就會出現高度爲5的樹(4和4合併是5,不是8,看一下combineTrees的函數,合併只多一層的)
			theTrees.resize(newNumTrees);
			for (int i = oldNumTrees; i < newNumTrees; i++)
				theTrees[i] = NULL;
		}

		BinomialNode * carry = NULL;            //合併二項隊列的過程,有兩個森林,然後按照從小往大排每棵樹,把兩個森林對應的位置相加(combineTrees就是幹這個的)。
		                                        //這個carry相當於,一個進位,比如高度爲2的兩個樹合併之後,高度變成了3,carry就要存這個,之前的兩棵樹2的位置清空;
		                                        //然後carry,兩棵樹有8種情況,逐個分析。這裏combine,也只合並兩個高度相同的tree。
		for (int i = 0, j = 1; j <= currentSize; i++; j *= 2)        //這裏的i就是theTrees的標號,j是用來控制這個標號的。i對應這個位置的樹的高度,每棵樹有2^i個結點,所以如果總共有N個結點,樹最大的編號就是logN的下界
		{
			BinomialNode *t1 = theTree[i];
			BinomialNode *t2 = i < rhs.theTrees.size() ? rhs.theTrees[i] : NULL;     //這裏兩行的區別,theTree因爲之前有擴容的操作,但是rhs的沒有,所以要這麼寫
			
			int whichCase = t1 == NULL ? 0 : 1;
			whichCase += t2 == NULL ? 0 : 2;
			whichCase += carry == NULL ? 0 : 4;

			switch (whichCase)
			{
			case 0:     //No Trees
			case 1:     //Only this
				break; 
			case 2:     //Only rhs
				theTrees[i] = t2;
				rhs.theTrees[i] = NULL;
				break;
			case 4:     //Only carry
				theTrees[i] = carry;
				carry = NULL;
				break;
			case 3:     //this and rhs
				carry = conbineTrees(t1, t2);
				theTrees[i] = rhs.theTrees[i] = NULL;
				break;
			case 5:     //this and carry
				carry = combineTrees(t1, carry);
				theTrees[i] = NULL;
				break;
			case 6:
				carry = combineTrees(t2, carry);
				rhs.theTrees[i] = NULL;
				break;
			case 7:
				theTrees[i] = carry;
				carry = combineTrees(t1, t2);
				rhs.theTrees[i] = NULL;
				break;
			}
			for (int k = 0; k < rhs.theTrees.size(); k++)
				rhs.theTrees[k] = NULL;
			rhs.currentSize = 0;
		}
	}

	const BinomialQueue & operator=(const BinomialQueue & rhs);
private:
	struct BinomialNode
	{
		Comparable element;
		BinomialNode * leftChild;        //每個結點存儲數據,大兒子,下一個堂兄弟。二項樹中的兒子以遞減次序(樹由高到低)排列。
		BinomialNode * nextSibling;

		BinomialNode(const Comparable & theElement, BinomialNode *lt, BinomialNode *rt) :element(theElement), leftChild(lt), nextSibling(rt){}
	};

	enum(DEFAULT_TREES = 1);
	//一個二項隊列的森林,包括一共有多少個結點,還有森林每棵樹根結點的vector(vector的座標是這棵樹的高度,這棵樹的結點數是2^i,i是座標。比如只有一個結點的就是0,如果沒有這棵樹,就是NULL)
	int currentSize;                    //總的結點數
	vector<BinomialNode *> theTrees;    //An array of tree roots

	int findMinIndex() const
	{
		int i;
		int minIndex;
		for (i = 0; theTrees[i] == NULL; i++)
			;
		for (minIndex = i; i < theTrees.size(); i++)
		{
			if (theTrees[i] != NULL && theTrees[i]->element < theTrees[minIndex]->element)
			{
				minIndex = i;
			}
		}
		return minIndex;
	}


	int capcity() const;
	BinomialNode* combineTrees(BinomialNode *t1, BinomialNode *t2)         //這個是把兩個大小一樣的tree合併  t1和t2是兩棵樹的root
	{
		if (t2->element < t1->element)
			return combineTrees(t2, t1);
		t2->nextSibling = t1->leftChild;
		t1->leftChild = t2;
		return t1;
	}
	void makeEmpty(BinomialNode * & t);
	BinomialNode * clone(BinomialNode *t) const;
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章