文章目錄
一、線性表查找
1、順序查找
這裏順序查找比較簡單,順序查找與底層結構無關。(可以是順序存儲,也可時鏈式存儲)
這裏爲了方便,使用數組來展示。
思路:逐個比較,如果找到,返回數據或索引,如果全部比較完畢,還找到,返回null.
在各個結點查找概率相同的情況下。默認查詢長度爲一半。所以時間複雜度爲 T(n) = O(n)。
代碼實現:
package com.gwz.datastructure.search;
/**
* 順序查找,使用順序存儲結構
* 時間複雜度 T(n) = O(n)
* 空間複雜度 S(n) = O(1)
*/
public class SortSearch {
public static void main(String[] args) {
int[] arrNums = {12,3,4,67,267,2,721};
int num = 67;
int index = -1;
index = sortSearch(arrNums,num);
if (index != -1) {
System.out.println("所要查找的數-》" + num + "的下標爲-》" + index);
} else {
System.out.println("此數不存在!");
}
}
public static int sortSearch(int[] arrNums, int num) {
for (int i = 0; i < arrNums.length; i++) {
if (num == arrNums[i]) {
return i;
}
}
return -1;
}
}
2、折半查找
使用折半(二分)查找的條件:
1、首先是順序存儲結構,
2、排列是有序的。
思路:
代碼實現:
package com.gwz.datastructure.search;
/**
* 二分查找,使用順序存儲結構
*/
public class BinarySearch {
public static void main(String[] args) {
int[] arrNums = {1,2,3,4,5,6,7,78,89};
int num = 5;
int index = -1;
index = binarySearch2(arrNums,num);
if (index != -1) {
System.out.println("所要查找的數-》" + num + "的下標爲-》" + index);
} else {
System.out.println("此數不存在!");
}
}
/**
* 不是使用遞歸
* 時間複雜度 T(n) = (log2^n)
* 空間複雜度 S(n) = O(1)
* @param arrNums
* @param num
* @return
*/
public static int binarySearch(int[] arrNums, int num) {
// 定義 low ,high
int low = 0;
int high = arrNums.length - 1;
int mid = (low + high) / 2;
while (low <= high) {
mid = (low + high) / 2;
if (num == arrNums[mid]) return mid;
else if (num < arrNums[mid])
high = mid - 1;
else low = mid + 1;
}
return -1;
}
/**
* 使用遞歸
* 時間複雜度 T(n) = (log2^n)
* 空間複雜度 S(n) = O(log2^n)
* @return
*/
public static int binarySearch2(int[] arrNums, int num) {
int low = 0;
int high = arrNums.length - 1;
return binarySearch3(arrNums, num, low, high);
}
private static int binarySearch3(int[] arrNums, int num, int low, int high) {
if (low <= high) {
int mid = (low + high) / 2;
if (num == arrNums[mid]) {
return mid;
} else if(num < arrNums[mid]){
return binarySearch3(arrNums, num, low, mid - 1 );
} else {
return binarySearch3(arrNums, num, mid + 1, high);
}
}else {
return -1;
}
}
}
二、查找樹
1、二叉查找、搜索、排序樹(binary search/ sort tree)
特點:一顆空樹,或者具有以下性質的二叉樹
-
如果左子樹不爲空,則左子樹上所有結點的值均小於根結點的值
-
如果右子樹不爲空,則右子樹上所有結點的值均大於根結點的值
-
它的左、右子樹也分別爲二叉排序樹。
-
對二叉排序樹進行中序遍歷,會得到一個遞增序列。
2、平衡二叉樹(Self-balanceing binary search tree)
是自平衡二叉查找樹,又被稱爲AVL樹(有別於AVL算法)
特點:一顆空樹,或者具有以下性質的二叉樹
-
首先具有二叉查找樹的性質
-
其次它的左子樹、與右子樹的高度差(平衡因子)的絕對值不不超過1.
-
左、右子樹也是平衡二叉樹。
-
總的來說,就是每個結點的左、右、子樹高度差爲只能爲-1、0、1的二叉排序樹。
平衡二叉樹的出現是爲了減少二叉排序樹的層次,以提高查找效率。
注意:平衡二叉樹的常用實現方法有AVL、紅黑樹、替罪羊樹、Treap、伸展樹等。具體有關算法這裏不一一探索,有興趣的小夥伴可以去研究一下。絕對燒腦。
3、紅黑樹
紅黑樹是一種平衡二叉樹、紅黑樹使用比較廣泛,這裏就總結一下。
java集合中的TreeSet 和 TreeMap, C++ STL 中的set 、map 以及linux虛擬內存管理,都是通過紅黑樹去實現的。值得一學。
特點:
- 每個結點或者是黑色,或者是紅色。
- 根結點是黑色。
- 每個葉子結點是黑色,這裏說的葉子結點是指爲Null的葉子結點。
- 如果一個葉子結點是紅色的,則它的子結點必須是黑色的。
- 從一個結點到該結點的子孫結點的所有路徑上包含相同的數量的黑結點。
4、B樹、B+樹、B*樹
有興趣的小夥伴可以瞭解一下。這些結構有它們自己的特殊性,使用空間來換取時間。
數據庫索引就是採用B+樹實現的。
三、哈希表查找
hashtable:散列表。
1、結構、特點
特點:一個字就是快。存儲結構可以由很多種
使用最多的就是:順序表 + 鏈表。
主結構:順序表,每個順序表的結點可以單獨引用一個鏈表。
2、哈希表如何添加數據?
-
計算哈希碼(調用hashcode(),結果是一個int值,整數哈希碼取自身即可。
-
計算哈希表中存儲的位置 y = k(x) % key
x:哈希碼,k(x) 函數 y:在哈希表中的存儲位置
-
存入哈希表
- 一次存入成功
- 多次成功(出現衝突,調用equals方法和對應鏈表上的元素比較。比到最後,返回結果都是false,創建新結點,存入數據,加入鏈表末尾。
- 不添加,出現衝突,一次,或多次比較,返回都是true,表面重複,不添加。
結論:哈希表添加數據只需三步。
唯一、無序、
3、hashCode()和equals()方法
hashCode():計算哈希碼,是一個整數,可以通過哈希碼,計算出元素在哈希表中存儲的位置。
equals():添加數據出現衝突時,需要調用equals方法進行比較。判斷是否相同,總是相同,不添加,查詢是也需要調用equals比較,判斷是否相同。
4、hashCode的計算
不同類型的哈希碼,有不同的計算規則,可以參考源碼計算規則。
5、如何減少哈希衝突
1、哈希表的長度和表中記錄數,簡單來說就是控制裝填因子
裝填因子(儘量控制在0.5) = 需要存儲的數據個數 / 哈希表長度。
2、哈希函數的選擇:直接定地址法、平凡取中法、摺疊法、除留取餘方法、
3、處理衝突的方法: 鏈地址法、開放地址法、再散列法、
四、java中的查找樹和哈希表
1、java中TreeSet和TreeMap 底層實現是紅黑樹。
TreeSet底層是TreeMap.
添加結點的過程,會通過旋轉法,保證每次添加前後都是平衡樹。
2、java中HashSet和HashMap底層使用了哈希表Hashtable
- HashSet的底層結構:HashSet底層採用HashMap,HashSet的元素作爲HashMap的key,統一使用Object對象作爲value.
- jdk8之後爲了解決查詢效率的問題。鏈表查詢時間複雜度爲O(n),紅黑樹爲O(logn),不過鏈表變換成紅黑樹是否條件的,當衝突過多時,》8,就會轉出成紅黑樹。