《大話數據結構》讀書筆記(四)

第8章 查找(Searching)


查找:根據給定的某個值,在查找表中確定一個字等於給定值的數據元素(或記錄)


順序查找(Sequential Search)
順序查找又叫線性查找,從表中第一個(或最後一個)記錄開始,逐個進行記錄的關鍵字和給定值比。

順序查找的時間複雜度爲O(n)

/* 順序查找,a爲數組,n爲要查找的數組個數,key爲要查找的關鍵字 */
int Sequential_Search(int *a, int n, int key)
{
	int i;
	
	for(i=1;i<=n;i++)
	{
		if (a[i] ==key)
			return i;
	}
	
	return 0;
}

/* 有哨兵順序查找 */
int Sequential_Search2(int *a,int n,int key)
{
	int i;
	
	a[0] = key;
	
	i = n;
	
	while(a[i] != key)
	{
		i--;
	}
	
	return i;    /* 返回0則說明查找失敗 */
}

有序表查找


折半查找(Binary Search)
折半查找的時間複雜度爲O(logn)

int Binary_Search(int *a, int n, int key)
{
	int low,high,mid;
	
	low=1;
	
	high=n;
	
	while(low<=high)
	{
		mid = (low+high)/2;
		if(key<a[mid])
			high = mid-1;
		else if (key>a[mid])
			low = mid+1;
		else
			return mid;
	}	
	
	return 0;
}

插值查找(Interpolation Search)
根據要查找的關鍵字key與查找表中最大最小記錄的關鍵字比較後的查找方法,其核心就在與插值的計算公式。
mid = low+(high-low)*(key-a[low])/(a[high]-a[low])
時間複雜度O(logn)


斐波那契查找(Fibonacci Search)

/* 斐波那契查找 */
int Fibonacci_Search(int *a, int n, int key)
{
	int low, high, mid, i, k;
	low =1;
	high =n;
	k =0;
	
	while(n>F[k]-1)    /* 計算n位於斐波那契數列的位置 */
		k++;
	
	for(i=n;i<F[k]-1;i++)  /* 將不滿的數值補全 */
		a[i] = a[n];
	
	while(low<=high)
	{
		mid=low+F[k-1]-1;    /* 計算當前分隔的下標 */
		
		if(key<a[mid])       /* 若查找記錄小於當前分隔記錄 */
		{
			high = mid-1;      /* 最高下標調整到分隔下標mid-1處 */
			k = k-1;           /* 斐波那契數列下標減一位 */
		}
		else if (key>a[mid]) /* 若查找記錄大於當前分隔記錄 */
		{
			low=mid+1;         /* 最低下標調整到分隔下標mid+1處 */
			k = k-2;           /* 斐波那契數列下標減兩位 */
		}
		else
		{
			if (mid<=n)
				return mid;      /* 若相等則說明mid即爲查找到的位置 */
			else
				return n;        /* 若mid>n說明是補全數值,返回n */
		}
		
		return 0;
	}	
}

/* 斐波那契遞歸函數 */
int Fbi(int i)
{
	if (i < 2)
		return (i==0)? 0 : 1;
	
	return Fbi(i-1)+Fbi(i-2);
}

線性索引查找
線性索引就是將索引項集合組織爲線性結構,包括稠密索引、分塊索引和倒排索引。


二叉排序樹(Binary Sort Tree)
二叉排序樹又稱二叉查找樹。它或者是一棵空樹,或者是具有下列性質的二叉樹。
1)左子樹均小於它的根結構的值。
2)右子樹均大於它的根結構的值。
3)它的左右子樹也分別爲二叉排序樹。


/* 二叉樹的二叉鏈表結點結構定義 */
typedef struct BiTNode
{
	int data;  
	
	struct BiTNode *lchild, *rchild;
	
}BiTNode, *BiTree;


二叉樹的查找操作

/* 遞歸查找二叉排序樹T中是否存在key */
/* 指針f指向T的雙親,其初始調用值爲NULL */
/* 若查找成功,則指針p指向該數據元素結點,並返回true */
/* 否則指針p指向查找路徑上訪問的最後一個結點並返回false */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
	if (! T)
	{
		*p = f;
		return false;
	}
	else if (key==T->data)
	{
		*p = T;
		return true;
	}
	else if (key <T->data)
		return SearchBST(T->lchild,key,T,p);
	else
		return SearchBST(T->rchild,key,T,p);
}

二叉樹插入操作

/* 當二叉排序樹T中不存在關鍵字等於key的元素時*/
/* 插入key並返回true,否則返回false*/
Status InsertBST(BiTree *T, int key)
{
	BiTree p,s;
	
	if (!SearchBST(*T,key,NULL,&p))
	{
		s = (BiTree) malloc (sizeof(BiTNode));
		s->data = key;
		s-lchild = s->rchild = NULL;
		
		if (!p)
			*T = s;        /* 插入s爲新的根結點 */
		else if (key<p->data)
			p->lchild = s;
		else
			p->rchild = s;
		
		return true;
	}
	else
		return false;
}

二叉排序樹刪除操作

/* 若二叉排序樹T中存在關鍵字等於key的數據元素時,則刪除該數據元素結點,*/
/* 並返回true,否則返回false */
Status DeleteBST(BiTree *T, int key)
{
	if (! *T)
		return false;
	else
	{
		if (key == (*T)->data)
			return Delete(T);
		else if (key < (*T)->data)
			return DeleteBST(& (*T)->lchild,key);
		else
			return DeleteBST(& (*T)->rchild,key);
	}
}

/* 從二叉排序樹中刪除結點p,並重接它的左或右子樹 */
Status Delete(BiTree *p)
{
	BiTree q,s;
	
	if ((*p)->rchild == NULL)   /* 右子樹空則只需重接它的左子樹 */
	{
		q=*p;*p=(*p)->lchild;free(q);
	}
	else if ((*p)->lchild == NULL)  /* 左子樹空則只需重接它的右子樹 */
	{
		q=*p;*p=(*p)->rchild;free(q);
	}
	else
	{
		q=*p;
		s = (*p)->lchild;
		while(s->rchild)      /* 轉左,然後向右到盡頭(找待刪結點的前驅)*/
		{
			q=s;s=s->rchild;
		}
		(*p)->data = s->data; /* s指向被刪結點的直接前驅 */
		
		if (q!=*p)
			q->rchild = s->lchild;  /* 重接q的右子樹 */
		else
			q->lchild = s->lchild;  /* 重接q的左子樹 */
		
		free(s);		
	}
	
	return true;
}

平衡二叉樹(AVL樹)


平衡二叉樹(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree),
是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至少等於1.


平衡因子BF(Balance Factor):將二叉樹上結點的左子樹深度減去右子樹深度的值。
平衡二叉樹上所有結點的平衡因子只可能是-1、0和1.


平衡二叉樹實現算法

/* 二叉樹的二叉鏈表結點結構定義 */
typedef struct BiTNode    /* 結點結構 */
{
	int data;               /* 結點數據 */
	
	int bf;                 /* 結點的平衡因子 */
	
	struct BiTNode *lchild, *rchild; /* 左右孩子指針 */
	
}BiTNode, *BiTree;

右旋操作

/* 對以p爲根的二叉排序樹作右旋處理, */
/* 處理之後p指向新的樹根結點,即旋轉處理之前的左子樹的根結點 */
void R_Rotate(BiTree *P)
{
	BiTree L;
	
	L = (*P)->lchild;            /* L指向P的左子樹根結點 */
	
	(*P)->lchild = L->rchild;    /* L的右子樹掛接爲P的左子樹 */
	
	L->rchild = (*P); 
	
	*P = L;                      /* P指向新的根結點 */
}

左旋操作

/* 對以p爲根的二叉排序樹作左旋處理, */
/* 處理之後p指向新的樹根結點,即旋轉處理之前的右子樹的根結點 */
void R_Rotate(BiTree *P)
{
	BiTree R;
	
	R = (*P)->rchild;            /* R指向P的右子樹根結點 */
	
	(*P)->rchild = R->lchild;    /* R的左子樹掛接爲P的右子樹 */
	
	R->lchild = (*P); 
	
	*P = R;                      /* P指向新的根結點 */
}

左平衡旋轉處理

#define  LH +1   /* 左高 */
#define  EH  0   /* 等高 */
#define  RH -1   /* 右高 */

/* 對以指針T所指結點爲根的二叉樹作左平衡旋轉處理 */
/* 本算法結束時,指針T指向新的根結點 */
void LeftBalance(BiTree *T)
{
	BiTree L, Lr;
	
	L = (*T)->lchild;    /* L指向T的左子樹根結點 */
	
	switch(L->bf)
	{
		/* 檢查T的左子樹的平衡度,並作相應的平衡處理 */
		case LH:                  /* 新結點插入在T的左孩子的左子樹上,要作單右旋處理 */
			(*T)->bf = L->bf = EH;
			R_Rotate(T);
			break;
		
		case RH:                  /* 新結點插入在T的左孩子的右子樹上,要作雙旋處理 */
			Lr = L->rchild;         /* Lr指向T的左孩子的右子樹根 */
			switch(Lr->bf)          /* 修改T及其左孩子的平衡因子 */
			{
				case LH:
					(*T)->bf = RH;
					L->bf = EH;
					break;
				
				case EH:
					(*T)->bf = L->bf = EH;
					break;
				
				case RH:
					(*T)->bf = EH;
					L->bf = LH;
					break;
			}
			
			Lr->bf = EH;
			L_Rotate(& (*T)->lchild);   /* 對T的左子樹作左旋平衡處理 */
			R_Rotate(T);			          /* 對T作右旋平衡處理 */
	}	
}

插入實現代碼

/* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/* 數據元素e的新結點並返回1,否則返回0.若因插入而使二叉排序樹失去 */
/* 平衡,則作平衡旋轉處理,布爾變量taller反映T長高與否 */
Status InsertAVL(BiTree *T, int e, Status *taller)
{
	if (!*T)
	{
		/* 插入新結點,樹“長高”,置taller爲true */
		*T = (BiTree) malloc (sizeof(BiTNode));
		(*T)->data =e;
		(*T)->lchild = (*T)->rchild = NULL;
		(*T)->bf = EH;
		*taller = true;
	}
	else
	{
		if (e == (*T)->data)
		{
			/* 樹中已存在和e有相同關鍵字的結點則不再插入 */
			*taller = false;
			return false;
		}
		
		if (e<(*T)->data)
		{
			/* 應繼續在T的左子樹中進行搜索 */
			if (!InsertAVL(&(*T)->lchild,e,taller))   /* 未插入 */
				return false;
			
			if (*taller)     /* 已插入到T的左子樹中且左子樹“長高” */
			{
				switch((*T)->bf)   /* 檢查T的平衡度 */
				{
					case LH:         /* 原本左子樹比右子樹高,需要作左平衡處理 */
						LeftBalance(T);
						*taller = false;
						break;
					
					case EH:         /* 原本左右子樹等高,現因左子樹增高而樹增高 */
						(*T)->bf = LH
						*taller = true;
						break;
					
					case RH:         /* 原本右子樹比左子樹高,現在左右子樹等高 */
						(*T)->bf = EH;
						*taller = false;
						break;
				}
			}
		}
		else
		{
			/* 應繼續在T的右子樹中進行搜索 */
			if (!InsertAVL(&(*T)->rchild,e,taller))    /* 未插入 */
				return false;

			if (*taller)     /* 已插入到T的右子樹中且右子樹“長高” */
			{
				switch((*T)->bf)
				{
					case LH:
						(*T)->bf = EH;
						*taller = false;
						break;
											
					case EH:
						(*T)->bf = RH
						*taller = true;
						break;
					
					case RH:
						RightBalance(T);
						*taller = false;
						break;
				}
			}			
		}
	}
	return true;
}

平衡二叉樹的查找、插入和刪除的時間複雜度都是Ologn)。

 

多路查找數(B樹)

 

散列表查找(哈希表)

 

散列函數構造方法

 

直接定址法

 

數字分析法

 

平方取中法

 

摺疊法

 

除留餘數法

f(key) = key mod p (p<=m)

 

隨機數法

f(key) = random(key)

 

處理散列衝突方法

 

開放定址法,也稱爲線性探測法,一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入。

fi(key) = (f(key) + di) MOD m (di=1,2,3,……,m-1)

 

二次探測法

fi(key) = (f(key) + di) MOD m (di=1^2,-1^2,2^2,-2^2,……,q^2,-q^2,q<=m/2)

 

在衝突時,對於位移量di採用隨機函數計算得到,稱爲隨機探測法

fi(key) = (f(key) + di) MOD m (di是一個隨機數列)

 

再散列函數法

fi(key) = RHi(key) (i=1,2,……,k),其中RHi就是不同的散列函數

 

鏈地址法

 

公共溢出區法

 

散列表查找實現

#define SUCCESS   1
#define UNSUCCESS 0
#define HASHSIZE  12        /* 定義散列表長爲數組的長度 */
#define NULLKEY   -32768

typedef struct
{
	int *elem;                /* 數據元素存儲基址,動態分配數組 */
	
	int count;                /* 當前數據元素個數 */
	
}HashTable;

int m = 0;                  /* 散列表表長,全局變量 */

/* 初始化散列表 */
Status InitHashTable(HashTable *H)
{
	int i;
	m = HASHSIZE;
	H->count = m;
	H->elem = (int *)malloc(m*sizeof(int));
	for(i=0;i<m;i++)
		H->elem[i] = NULLKEY;
	
	return OK;	
}

/* 散列函數 */
int Hash(int key)
{
	return key % m; /* 除留餘數法 */
}

/* 插入關鍵字進散列表 */
void InsertHash(HashTable *H, int key)
{
	int addr = Hash(key);            /* 求散列地址 */
	
	/* 開放定址法的線性探測 */
	while(H->elem[addr] != NULLKEY)
		addr = (addr+1) %m;
	
	H->elem[addr] = key;
}

/* 散列表查找關鍵字 */
Status SearchHash(HashTable H, int key, int *addr)
{
	*addr = Hash(key);
	
	while(H.elem[*addr] != key)       /* 如果不爲空,則衝突 */
	{
		*addr = (*addr+1) % m;          /* 開放定址法的線性探測 */
		
		/* 如果循環回到原點 */
		if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
		{
			return UNSUCCESS;    /* 說明關鍵字不存在 */
		}
	}
	
	return SUCCESS;
}



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