(13)搜索

一、簡介

    通過構造可生成一個隨機整數的有序集合(不允許重複)來引出問題的討論。C++相關的代碼使用了set數據結構,具體代碼如下所示:

void gensets(int m, int maxval)
{
    int *v = new int[m];
    IntSetImp S(m, maxval);
    while(S.size() < m)
        S.insert(bigrand() % maxval);
    S.report(v);
    for(int i=0;i < m;i++)
        cout<<v[i]<<"\n";
}
二、鏈表

    而後通過描述線性結構,分別以類似插入排序的數組及鏈表查找插入數據的形式實現分析了數據的插入順序插入操作。這裏只介紹下鏈表的相關實現,下面是鏈表的遞歸插入實現:

    思路:node *rinsert(node *p, int t)是關鍵函數,注意調用的時候,p->next=rinsert(p->next,t); 如果小於則一直等於next,如果p-val > t 則 p =new node(t,p);說明第一個大於t的p被作爲新節點(val=t)的next,代碼實現如下:

class IntSetList {
private:
	int	n;
	struct node {
		int val;
		node *next;
		node(int i, node *p) { val = i; next = p; }
	};
	node *head, *sentinel;
	node *rinsert(node *p, int t) //遞歸插入函數
	{	if (p->val < t) {
			p->next = rinsert(p->next, t);
		} else if (p->val > t) {
			p = new node(t, p);
			n++;
		}
		return p;
	}
public:
	IntSetList(int maxelements, int maxval)  //初始化函數時候,將哨兵置於第一個
	{	sentinel = head = new node(maxval, 0);
		n = 0;
	}
	int size() { return n; }
	void insert(int t) { head = rinsert(head, t); } //調用遞歸插入函數
	void insert2(int t)
	{	node *p;
		if (head->val == t)
			return;
		if (head->val > t) {
			head = new node(t, head);
			n++;
			return;
		}
		for (p = head; p->next->val < t; p = p->next)
			;
		if (p->next->val == t)
			return;
		p->next = new node(t, p->next);
		n++;
	}
	void insert3(int t)//類似數組的插入
	{	node **p;
		for (p = &head; (*p)->val < t; p = &((*p)->next))//找到第一個大於等於p的節點
			;
		if ((*p)->val == t)
			return;
		*p = new node(t, *p);//創建節點的同時,將節點插入到正確位置
		n++;
	}
	void report(int *v)//輸出整個鏈表
	{	int j = 0;
		for (node *p = head; p != sentinel; p = p->next)
			v[j++] = p->val;
	}
};
    第二種實現方法(鏈表非遞歸):主要是找到第一個大於t的節點,然後插入到其前面。實現代碼如下:

class IntSetList2 {
private:
	int	n;
	struct node {
		int val;
		node *next;
	};
	node *head, *sentinel, *freenode;
public:
	IntSetList2(int maxelements, int maxval)//最大元素個數和哨兵
	{	sentinel = head = new node;
		sentinel->val = maxval;
		freenode = new node[maxelements];
		n = 0;
	}
	int size() { return n; }
	void insert(int t)
	{	node **p;
		for (p = &head; (*p)->val < t; p = &((*p)->next))
			;
		if ((*p)->val == t)
			return;
		freenode->val = t; //將t插入到第一個大於t的元素的前面
		freenode->next = *p;
		*p = freenode++;
		n++;
	}
	void report(int *v)
	{	int j = 0;
		for (node *p = head; p != sentinel; p = p->next)
			v[j++] = p->val;
	}
};
    遞歸調用實現的鏈表插入,除了遞歸調用的開銷外,rinsert函數的遞歸深度就是找到元素的位置,即O(n)。遞歸全部結束後,代碼將初值賦給幾乎所有的指針。當將其改爲非遞歸實現版本後,運行時間極虎降低爲原來的三分之一。

三、二分搜索樹

    利用二分搜索樹來實現整數的查找及插入,遞歸實現如下:

class IntSetBST {
private:
	int	n, *v, vn;
	struct node {
		int val;
		node *left, *right;
		node(int v) { val = v; left = right = 0; }
	};
	node *root;
	node *rinsert(node *p, int t)
	{	if (p == 0) {
			p = new node(t);
			n++;
		} else if (t < p->val) {
			p->left = rinsert(p->left, t);
		} else if (t > p->val) {
			p->right = rinsert(p->right, t);
		} // do nothing if p->val == t
		return p;
	}
	void traverse(node *p)//中序遍歷輸出
	{	if (p == 0)
			return;
		traverse(p->left);
		v[vn++] = p->val;
		traverse(p->right);
	}
public:
	IntSetBST(int maxelements, int maxval) { root = 0; n = 0; }
	int size() { return n; }
	void insert(int t) { root = rinsert(root, t); }
	void report(int *x) { v = x; vn = 0; traverse(root); }
};
四、專門用於整數處理的數據結構

    利用位圖的思想,位圖數據中特定索引位記錄此數是否存在,通過清除和設置相應的bit位來標記相應的數是否在集合中。例如x的第一個字節的第三個bit若爲1,則表示數組2在集合中。代碼實現如下所示:

class IntSetBitVec {
private:
	enum { BITSPERWORD = 32, SHIFT = 5, MASK = 0x1F };
	int	n, hi, *x;
	void set(int i)  {        x[i>>SHIFT] |=  (1<<(i & MASK)); }
	void clr(int i)  {        x[i>>SHIFT] &= ~(1<<(i & MASK)); }
	int  test(int i) { return x[i>>SHIFT] &   (1<<(i & MASK)); }
public:
	IntSetBitVec(int maxelements, int maxval)
	{	hi = maxval;
		x = new int[1 + hi/BITSPERWORD];
		for (int i = 0; i < hi; i++)
			clr(i);//所有的位都置零
		n = 0;
	}
	int size() { return n; }
	void insert(int t)
	{	if (test(t))
			return;
		set(t);
		n++;
	}
	void report(int *v)
	{	int j=0;
		for (int i = 0; i < hi; i++)
			if (test(i))
				v[j++] = i;
	}
};
    解釋:其中i>>SHIFT 相當於 i/32得到對應數組下標,二進制右移5位相當於十進制除以32,i&MASK相當於 i mod 32,因爲每一個數32位。最大隻能左移32位。1<<(i&MASK)相當於獲得2的(i&MASK)次冪,就是1左移(i&MASK)位,i=[0,31] 標記在數組第一字節,i=[32,63] 標記在數組第二字節。

五、如何輸出一個數的二進制位

    代碼實現如下:

#include <iostream>
using namespace std; 
int main()
{
    int n;
    cout << "input n:";
    cin >> n;
    for(int i=1; i<=32; i++)
    {
       cout << ( n < 0 ? 1 : 0 ); //如果首位爲1則爲負數 所以小於0 
       n = n<<1;//左移一位
    }
    cout << endl;
    return 0;
}
六、箱

    箱是一個集合了鏈表和位向量優點的數據結構。其思路是每一個箱表示一定範圍的整數,並用鏈表鏈接起來。鏈表插入採用有序插入。實現代碼如下:

class IntSetBins {
private:
	int	n, bins, maxval;
	struct node {
		int val;
		node *next;
		node(int v, node *p) { val = v; next = p; }
	};
	node **bin, *sentinel;
	node *rinsert(node *p, int t)//遞歸  插入結點到合適地方
	{	if (p->val < t) {
			p->next = rinsert(p->next, t);
		} else if (p->val > t) {
			p = new node(t, p);
			n++;
		}
		return p;
	}
public:
	IntSetBins(int maxelements, int pmaxval)
	{	bins = maxelements;
		maxval = pmaxval;
		bin = new node*[bins];
		sentinel = new node(maxval, 0);
		for (int i = 0; i < bins; i++)
			bin[i] = sentinel;
		n = 0;
	}
	int size() { return n; }
	void insert(int t)
	{	int i = t / (1 + maxval/bins);  // CHECK !  映射到某個箱子中
		bin[i] = rinsert(bin[i], t);
	}
	void report(int *v)
	{	int j = 0;
		for (int i = 0; i < bins; i++)
			for (node *p = bin[i]; p != sentinel; p = p->next)
				v[j++] = p->val;
	}
};
七、習題答案

    (1)習題1:Floyd算法的IntSet實現中只需要在插入數據前保存原先的Set大小值,然後插入數據,再判斷Set大小是否改變,若改變,則插入成功,若未改變,則插入當前的j值。具體見書中的P214。

    (2)習題5:如何避免多次調用存儲分配器來提高效率:爲了用一次分配來取代多次分配,需要有一個指向下一個可用節點的指針:

node * freenode;
    在構造類的時候就分配出足夠的空間:

freenode = new node[maxelems];
    然後在插入函數中根據需要加以使用:

if(p == 0)
{
    p = freenode++;
    p->val = t;
    p->left = p->right = 0;
    n++
}
else if ...
    同樣的方法可以應用到箱中。

    (3)習題7:如何將哨兵用於二分搜索樹:把以前null指針都指向哨兵節點,哨兵在構造函數中進行初始化,插入代碼先將目標值t放入哨兵節點,然後用一個指向指針的指針自頂向下遍歷數直到找到t。代碼實現如下所示:

class IntSetBST2 {
private:
	int	n, *v, vn;
	struct node {
		int val;
		node *left, *right;
	};
	node *root, *freenode, *sentinel;

	void traverse(node *p)
	{	if (p == sentinel)
			return;
		traverse(p->left);
		v[vn++] = p->val;
		traverse(p->right);
	}
public:
	IntSetBST2(int maxelements, int maxval)
	{	root = sentinel = new node;  
		n = 0;
		freenode = new node[maxelements];
	}
	int size() { return n; }

	void insert(int t)
	{	sentinel->val = t;
		node **p = &root;       //指向指針的指針
		while ((*p)->val != t)
			if (t < (*p)->val)
				p = &((*p)->left);
			else
				p = &((*p)->right);
		if (*p == sentinel) {
			*p = freenode++;
			(*p)->val = t;
			(*p)->left = (*p)->right = sentinel;
			n++;
		}
	}
	void report(int *x) { v = x; vn = 0; traverse(root); }
};
    (4)說明如何通過使用低開銷的邏輯移位替代高開銷的除法運算來對箱進行加速。

    爲了用移位取代除法,通過類似下面的僞代碼對變量進行初始化:

goal  = n/m;
binshift = 1;
for(int i=2;i<goal;i*=2)
    binshift++;
nbins = 1 + (n >> binshift);
    插入函數從該結點開始:

p = &(bin[t >> binshift]);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章