1. 程式人生 > >資料結構之查詢

資料結構之查詢

查詢就是根據給定的某個值,在查詢表中確定一個其關鍵字等於給定值的資料元素。

順序表查詢

順序表查詢(sequential search)又叫線性查詢,是最基本的查詢技術,它的查詢過程是:從表中第一個記錄開始,逐個進行記錄的關鍵字和給定值比較,若某個記錄的關鍵字和給定值相等,則查詢成功,找到所查的記錄;如果查到最後一個記錄,其關鍵字和給定值比較都不等時,則表中沒有所查的記錄,查詢不成功

  1. 順序表查詢
    a為陣列,key為要查詢的關鍵字,a[i]==key則查詢成功。
  2. 順序表查詢優化
    以上演算法每次迴圈時都要對i是否越界,即是否小於等於n作判斷。事實上,還可以有更好一點的辦法,設定一個哨兵
    ,可以解決不需要每次讓i與n作比較,時間複雜度為O(n)

有序表查詢

折半查詢

折半查詢又稱為二分查詢,它的前提是線性表中的記錄必須是關鍵碼有序,線性表必須採用順序儲存,時間複雜度O(logn)
在這裡插入圖片描述

插值查詢

現在我們的問題是,為什麼一定要折半,而不是折1/4或者折更多呢?插值查詢時根據要查詢的關鍵字key與查詢表中最大最小記錄的關鍵字比較後的查詢方法,其核心就在於插值的計算公式:
k e

y a [ l o w ]
a [ h i g h ] a [ l o w ] \frac{key-a[low]}{a[high]-a[low]}
mid = low + (high-low)*(key-a[low])/(a[high]-a[low]);
時間複雜度為O(logn),但對於表長較長,而關鍵字分佈又比較均勻的查詢表來說,插值查詢演算法的平均效能比折半查詢好得多。

斐波那契查詢

除了插值查詢,還可以利用黃金分割原理來實現查詢,即斐波那契查詢。

  1. 陣列不夠斐波那契對應數字長度,得補全a[i]=a[n]
  2. mid=low+F[k-1]-1
  3. 如果key<a[mid],則high=mid-1;k=k-1
  4. 如果key>a[mid],則low=mid+1;k=k-2
  5. 直到key=a[mid],程式結束
  6. 如果mid>n,則說明是補全數值,返回n

在這裡插入圖片描述

#include "stdio.h"    
#include "stdlib.h"   
#include "io.h"  
#include "math.h"  
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */

typedef int Status;	/* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */

int F[100]; /* 斐波那契數列 */

/* 無哨兵順序查詢,a為陣列,n為要查詢的陣列個數,key為要查詢的關鍵字 ,i返回角標*/
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;
}
/* 有哨兵順序查詢 */
//a為表,n為表長,key為要查詢的數字,i返回數字角標
int Sequential_Search2(int *a, int n, int key)
{
	int i;
	a[0] = key;
	i = n;
	while (a[i] != key)
	{
		i--;
	}
	return i;
}

/* 折半查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字角標
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;		/* 若相等則說明mid即為查詢到的位置 */
		}

	}
	return 0;
}

/* 插值查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字角標
int Interpolation_Search(int *a, int n, int key)
{
	int low, high, mid;
	low = 1;	/* 定義最低下標為記錄首位 */
	high = n;	/* 定義最高下標為記錄末位 */
	while (low <= high)
	{
		mid = low + (high - low)*(key - a[low]) / (a[high] - a[low]); /* 插值 */
		if (key<a[mid])		/* 若查詢值比插值小 */
			high = mid - 1;		/* 最高下標調整到插值下標小一位 */
		else if (key>a[mid])/* 若查詢值比插值大 */
			low = mid + 1;		/* 最低下標調整到插值下標大一位 */
		else
			return mid;		/* 若相等則說明mid即為查詢到的位置 */
	}
	return 0;
}

/* 斐波那契查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字的角標
int Fibonacci_Search(int *a, int n, int key)
{
	int low, high, mid, i, k = 0;
	low = 1;	/* 定義最低下標為記錄首位 */
	high = n;	/* 定義最高下標為記錄末位 */
	while (n>F[k] - 1)
		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;
			k = k - 1;
		}
		else if (key>a[mid])
		{
			low = mid + 1;
			k = k - 2;
		}
		else
		{
			if (mid <= n)
				return mid;		/* 若相等則說明mid即為查詢到的位置 */
			else
				return n;
		}

	}
	return 0;
}

int main(void)
{
	int a[MAXSIZE + 1], i, result;
	int arr[MAXSIZE] = { 0,1,16,24,35,47,59,62,73,88,99 };
	for (i = 0; i <= MAXSIZE; i++)
	{
		a[i] = i;
	}
	result = Sequential_Search(a, MAXSIZE, MAXSIZE);
	printf("Sequential_Search:%d \n", result);
	result = Sequential_Search2(a, MAXSIZE, 1);
	printf("Sequential_Search2:%d \n", result);

	result = Binary_Search(arr, 10, 62);
	printf("Binary_Search:%d \n", result);

	result = Interpolation_Search(arr, 10, 62);
	printf("Interpolation_Search:%d \n", result);

	F[0] = 0;//斐波那契數列,全域性變數
	F[1] = 1;
	for (i = 2; i < 100; i++)
	{
		F[i] = F[i - 1] + F[i - 2];
	}
	result = Fibonacci_Search(arr, 10, 62);
	printf("Fibonacci_Search:%d \n", result);

	system("pause");
	return 0;
}

執行結果:

Sequential_Search:100
Sequential_Search2:1
Binary_Search:7
Interpolation_Search:7
Fibonacci_Search:7
請按任意鍵繼續. . . 

線性索引查詢

稠密索引

稠密索引是指線上性索引中,將資料集中的每個記錄對應一個索引項,索引項一定是按照關鍵碼有序排列

  1. 關鍵字有序,可以使用折半、插值、斐波那契等有序查詢法
  2. 但對於記憶體有限的計算機來說,可能需要反覆的訪問磁碟,查詢效能下降。
    在這裡插入圖片描述

分塊索引

分塊索引好比圖書館的書架,塊間有序,塊內無序,需要指定塊的的最大關鍵碼,使得下一個塊的最小關鍵字比上一個最大關鍵字大。

  1. 塊間使用折半、插值等演算法
  2. 塊內使用順序查詢演算法
  3. 效率比順序查詢O(n)高,比折半O(logn)低。
    在這裡插入圖片描述

二叉排序樹

1. 順序儲存插、刪快,查詢慢
2. 對於有序表,折半、插值、斐波那契查詢快,增刪慢

二叉排序樹是以連結的方式儲存,保持了連結儲存結構在執行插入或刪除操作時不用移動元素的優點,插入刪除的時間效能好。二叉樹的查詢效能取決於二叉樹的形狀,不平衡的極端情況下可能需要查詢n次。

在這裡插入圖片描述

#include "stdio.h"    
#include "stdlib.h"   
#include "io.h"  
#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */

typedef int Status;	/* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */
					/* 二叉樹的二叉連結串列結點結構定義 */
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;	/*  插入s為左孩子 */
		else
			p->rchild = s;  /*  插入s為右孩子 */
		return TRUE;
	}
	else
		return FALSE;  /*  樹中已有關鍵字相同的結點,不再插入 */
}

/* 從二叉排序樹中刪除結點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;
}

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

	}
}

int main(void)
{
	int i;
	int a[10] = { 62,88,58,47,35,73,51,99,37,93 };
	BiTree T = NULL;

	for (i = 0; i<10; i++)
	{
		InsertBST(&T, a[i]);
	}

	BiTree p = NULL;
	int result1 = SearchBST(T, 93, NULL, &p);
	printf("查詢'93'的結果為(1:存在,0:不存在):%d\n",result1);
	
	int result2 = DeleteBST(&T, 93);
	printf("刪除'93'的結果為(1:成功,0:不成功):%d\n", result2);

	int result3 = SearchBST(T, 93, NULL, &p);
	printf("查詢'93'的結果為(1:存在,0:不存在):%d\n", result3);

	//printf("本樣例建議斷點跟蹤檢視二叉排序樹結構\n");
	system("pause");
	return 0;
}

執行結果為:

查詢'93'的結果為(1:存在,0:不存在):1
刪除'93'的結果為(1:成功,0:不成功):1
查詢'93'的結果為(1:存在,0:不存在):0
請按任意鍵繼續. . .

平衡二叉樹(AVL樹)

平衡二叉樹是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1。

  1. 圖2中58<59不滿足二叉排序樹定義
  2. 圖3中58左子樹高度2,右子樹為空,相差大於1,故不滿足
    在這裡插入圖片描述
    距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹,我們稱為最小不平衡子樹。當插入結點37時,距離它最近的平衡因子絕對值超過1的結點是58(左子樹高度2減去右子樹高度0),所以從58開始以下的子樹為最小不平衡子樹
    在這裡插入圖片描述
    平衡二叉樹實現原理:每當插入一個結點時,先檢查是否因為插入結點而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保持二叉樹特性的前提下,調整最小不平衡子樹中結點的連結關係,進行相應的旋轉,使之成為新的平衡二叉樹。
    (1)插入結點5,結點3的BF值為-2說明要旋轉了,對這棵樹的最小平衡子樹進行左旋,以此達到平衡。
    在這裡插入圖片描述
    (2)插入結點6,結點2的BF值為-2,把樹的最小平衡子樹左旋,旋轉後為滿足二叉排序樹的特性,結點3需要作出改變
    在這裡插入圖片描述
    (3)新增結點7,結點5的BF值為-2,左旋。
    在這裡插入圖片描述
    (4)新增結點10,結構不變化。新增結點9,結點7的BF值為-2,而結點10的BF為1,一正一負,符號不統一。所以先把結點9、10右旋符號統一,再左旋。新增結點8同理可得。
    在這裡插入圖片描述
    在這裡插入圖片描述
#include "stdio.h"    
#include "stdlib.h"   
#include "io.h"  
#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */

typedef int Status;	/* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */


					/* 二叉樹的二叉連結串列結點結構定義 */
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指向新的樹根結點,即旋轉處理之前的右子樹的根結點0  */
void L_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