查找算法小結

        數據的查找是爲了能夠快速在數據庫中找出自己想要的關鍵字,進而找出相關數據等。一般的查找方法有靜態查找動態查找,還有就是哈希表查找。

        靜態查找,是指僅僅查找,沒有修改等行爲,也可分爲順序表的查找、有序表的查找和有索引表的查找等等。順序表的查找,顧名思義就是最普通的查找方式——依次查找,就是從第一個找到最好一個,直到找到數據爲止,或者遍歷後發現根本不存在這個關鍵字。這個方法比較簡單,這裏就不贅述了。有序表的查找是指表按照其關鍵字有一定的排列次序,比如從小到大的排列,這種情況下的查找就相對容易點了,可以利用折半查找。折半查找的核心代碼如下所示:

int Binarysearch(int a[], int low, int high, int key){  
	if(low > high) return -1;
	int mid = (low+high)/2;  
	if(a[mid] == key) return mid;  
	else if(a[mid] > key){  
		return Binarysearch(a,low,mid-1,key);  
	}else{  
		return Binarysearch(a,mid+1,high,key);  
	}  
}

       上述a爲待查找的數組,low爲折半查找最小的地方,這裏爲0,high爲數組個數減1,key爲要查找的關鍵字。

        除了折半查找之外,還有一些折半查找的變形,如斐波那契查找,其查找的順序是按照斐波那契數列的特徵進行的。有興趣的可以查看下,這裏不多寫了。

       索引順序表的查找,是指分塊進行查找,是順序查找的一種改進方法。利用這種查找方法的話,就不僅僅需要一張數據表了,還需要一張索引表,用來記錄每一塊的最大關鍵字。其中數據表是有序的,索引表也是有序的,查找的時候先查找索引表,如果key在兩個關鍵字之間,就在後者的塊中尋找是否存在關鍵字,完成查找。

       動態查找,不僅僅能完成查找的功能了,常用二叉樹來實現。其功能一般包括:查找關鍵字是否存在;插入一個關鍵字;刪除一個關鍵字;刪除整個表;構造空的查找表等等。常用的二叉樹有二叉排序樹、平衡二叉樹、B-樹和B+樹等等。下面以二叉排序樹爲例講解:

       首先二叉樹的創建,要有一個結構體用來存放每一個節點,然後依據相關數據創建二叉樹,具體如下所示

typedef struct Node{
	int value;
	struct Node *lNode;
	struct Node *rNode;
}BinaryTree;
void CreateTree(BinaryTree **root, int a[], int n){
	if(n<1)  return ;
	*root = (BinaryTree*)malloc(sizeof(BinaryTree));
	(*root)->value = a[0];
	(*root)->lNode = NULL;
	(*root)->rNode = NULL;
	int i;
	for(i=1; i<n; i++){
		BinaryTree *node = (BinaryTree*)malloc(sizeof(BinaryTree));
		node->value = a[i];
		node->lNode = NULL;
		node->rNode = NULL;
		InsertNode(*root, node);
	}
}


        這裏CreateTree中必須要用到雙指針,因爲我們還要修改(*root)本身的值,雙指針可以起到這樣的所用,使用單指針就只能修改其指向的值(單指針類似於函數中的取值傳遞,雙指針取址傳遞),不能修改本身的值。

        創建二叉樹之後,就要依次插入數據a[]了,插入的程序如下

void InsertNode(BinaryTree *root, BinaryTree *node){
	if(root->value < node->value){
		if(root->rNode == NULL) root->rNode = node;
		else InsertNode(root->rNode, node);
		return ;
	}else if(root->value > node->value){
		if(root->lNode == NULL) root->lNode = node;
		else InsertNode(root->lNode, node);
		return ;
	}else return ;
}

        接下來重要的功能就是查找了,如果單純的查找的話也很簡單,只要遍歷整棵樹判斷是否有與其key值相匹配的就行,但我們還要刪除操作,刪除操作比較複雜了,下面詳細討論,我們的查找程序如下所示

BinaryTree *SearchNode(BinaryTree *root, BinaryTree **parent, int key){
	if(root == NULL) return NULL;
	if(root->value == key){
		return root;
	} 
	else if(root->value > key){
		*parent = root;
		return SearchNode(root->lNode,parent,key);
	}
	else if(root->value < key){
		*parent = root;
		return SearchNode(root->rNode,parent,key);
	}
	return NULL;
}

        如果存在該節點,就返回該節點,檢驗是否存在的話只用判斷節點是否爲NULL就行。parent是其父節點,也需要用雙指針因爲其指向的地址和自身的地址都是要修改的。
        剩下的主要就是刪除一棵樹中的節點的操作了。被刪除的節點可以分爲4中類型:一,該節點沒有子節點,此類型節點的刪除最爲簡單,只需要將其父節點指向爲NULL就行,但要知道其父節點是哪個Node,所以我們在search時添加其父節點;二,該節點只有左子節點,只需將該節點的key值改爲其左子節點的值,其指向的左右子節點爲其左子節點的左右子節點,然後刪除其左子節點即可;三,該節點只有右子節點,同二;四,該節點既有左子節點,也有右子節點,我們採取的辦法是取其左子節點lnode,尋找其左子節點的最右子節點,將其值賦給待刪節點,最右子節點的父節點的右子節點指向最右子節點的左子節點,若lnode沒有右子節點,就用其自身代替,總之就是選其最大值,當然我們也可以選取待刪子節點其右子節點中最小的值,原理是相通的。具體實現如下

void DeleteTree(BinaryTree *root, int key){
	BinaryTree *parent = NULL;
	BinaryTree *deletenode = SearchNode(root,&parent,key);
	if(deletenode == NULL){
		printf("\nIt's not here!\n");
		return ;
	}
	if((deletenode->lNode == NULL) && (deletenode->rNode == NULL)){
		if(parent == NULL){
			root = NULL;
		} 
		else if(parent->lNode == deletenode) {
			parent->lNode = NULL;
		}
		else if(parent->rNode == deletenode) {
			parent->rNode = NULL;
		}
		free(deletenode);
	}else if((deletenode->rNode == NULL) && (deletenode->lNode != NULL)){
		BinaryTree *tmp = deletenode->lNode;
		deletenode->value = tmp->value;
		deletenode->rNode = tmp->rNode;
		deletenode->lNode = tmp->lNode;
		free(tmp);
	}else if(deletenode->lNode == NULL && deletenode->rNode != NULL){
		BinaryTree *tmp = deletenode->rNode;
		deletenode->value = tmp->value;
		deletenode->rNode = tmp->rNode;
		deletenode->lNode = tmp->lNode;
		free(tmp);
	}else{
		BinaryTree *tmp = deletenode;
		BinaryTree *child = deletenode->lNode;
		while(child->rNode){
		tmp = child;
		child = child->rNode;
		}
		deletenode->value = child->value;
		if(tmp!=deletenode) tmp->rNode=child->lNode;
		else tmp->lNode=child->lNode;
		free(child);
	}
}

       剩下的就是刪除整棵樹了,比較簡單,刪左子節點,刪自己,刪右子節點如下所示

void DestoryTree(BinaryTree *root){
	if(root == NULL) return ;
	if(!root->lNode) DestoryTree(root->lNode);
	if(!root->rNode) DestoryTree(root->rNode);
	if(root->lNode == NULL && root->rNode == NULL){
		free(root);
		root = NULL;
	}
}

        具體的請看下面的鏈接:https://github.com/clarkzhang56/Search-method

        平衡二叉樹,是對二叉樹的改進,所謂平衡二叉樹,是指該樹是平衡二叉樹,其子節點所構成的樹也是平衡二叉樹,其左子節點的深度和右子節點的深度相差不能超過1。構建平衡二叉樹需要不停的修改節點的位置,在insert的時候要保證該樹滿足平衡二叉樹的條件。比二叉查找樹複雜。B-樹適用於文件系統,是一種多路搜索樹,不一定是二叉的,一棵m階的B-樹或者是空樹,或者滿足如下的條件如下:

1、根結點至少有兩個子女;
2、每個非根節點所包含的關鍵字個數 j 滿足:┌m/2┐ - 1 <= j <= m - 1;
3、除根結點以外的所有結點(不包括葉子結點)的度數正好是關鍵字總數加1,故內部子樹個數 k 滿足:┌m/2┐ <= k <= m ;
4、所有的葉子結點都位於同一層。
在B-樹中,每個結點中關鍵字從小到大排列,並且當該結點的孩子是非葉子結點時,該k-1個關鍵字正好是k個孩子包含的關鍵字的值域的分劃。
因爲葉子結點不包含關鍵字,所以可以把葉子結點看成在樹裏實際上並不存在外部結點,指向這些外部結點的指針爲空,葉子結點的數目正好等於樹中所包含的關鍵字總個數加1。
B-樹中的一個包含n個關鍵字,n+1個指針的結點的一般形式爲: (n,P0,K1,P1,K2,P2,…,Kn,Pn)
其中,Ki爲關鍵字,K1<K2<…<Kn, Pi 是指向包括Ki到Ki+1之間的關鍵字的子樹的指針。(選自嚴蔚敏的《數據結構》)

        哈希表,不同於一般的查找方法,一般的查找方法都有通過key值大小的比較,使用哈希表不需要進行比較(這個提出類似於桶排序)。將其值和關鍵字通過函數f實現一一對應的關係,這樣一來直接就尋找到了該key值所在的位置和其對應的所有數據。哈希表可描述如下:根據設定的哈希函數(散列函數)和處理衝突的方法將一組關鍵字映像到一個有限的連續的地址集上,並以關鍵字在地址中的“像”作爲記錄在表中的存儲位置,這種表稱爲哈希表,這一影像過程稱爲哈希造表或散列,所得存儲位置稱哈希地址或散列地址。

       常見的哈希函數的構造方法有直接定址法(H(key)=key或H(key)=a*key+b)、數字分析法、平方取中法、摺疊法、除留餘數法和隨機數法。其中除留餘數法用得最多,也最爲簡單。其可表示爲: H(key) = key MOD p, p<=m, m爲哈希表表長。不僅可以對關鍵字直接取模,也可在摺疊、平方取中等運算後取模。不過p的選擇很重要,由經驗得知:一般情況下,可以選p爲質數或不包含小於20的質因數的合數。

       由於哈希表長度有限,哈希函數也不可能完美,所以衝突是在所難免的,常見的處理衝突的方法有開放定址法、再哈希法、鏈地址法和建立公共溢出區等方法。

       開放定址法,就是當有衝突時,Hi = (H(key) + di) MOD m , i = 1,2,3...,k(k<=m-1),其中,di可以有三種取值方法:(1)di = 1,2,3,...,m-1(2)di = 1,-1,4,-4,9,-9,...k*k,-k*k,

k<=m/2(3)di是僞隨機數序列。

       鏈地址法,當一些記錄產生衝突時,將關鍵字爲同義詞的記錄存儲在同一線性鏈表中。在鏈表中的插入位置可以在表頭、表尾和中間等,以便保持此線性鏈表按關鍵字有序。

 

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