散列(一)


定義:
  • 散列是一種用于以常數(shù)平均時(shí)間執(zhí)行插入,刪除和查找的技術(shù)。
  • 我們將每個(gè)關(guān)鍵字被映射到從0到TableSize - 1這個(gè)范圍中的某個(gè)數(shù)。這個(gè)映射稱為散列函數(shù)。
  • 散列函數(shù)需要保證任何兩個(gè)不同的關(guān)鍵字映射到不同的單元。
  • 當(dāng)兩個(gè)關(guān)鍵字散列到同一個(gè)值時(shí)(這叫做沖突), 應(yīng)該做什么以及如何確定散列表的大小。
hash_table.png
散列函數(shù):
  • 一般簡單合理的方法是直接返回Key mod TableSize, 并且保證表的大小為素?cái)?shù)。

a. 假設(shè)關(guān)鍵字是字符串,一種選擇辦法是將字符串的ASCII碼(或Unicode碼)值加起來

public static int hash(String key, int tableSize){
        int hashVal = 0;
        for (int i = 0; i < key.length(); i ++){
            hashVal += key.charAt(i);
        }
        return hashVal % tableSize;
}

這種方法實(shí)現(xiàn)起來簡單合理,但如果表很大的話,并不會(huì)很好地分配關(guān)鍵字。例如,TableSize = 10007,假設(shè)所有的關(guān)鍵字至多8個(gè)字符長,由于ASCII碼字符的值最多是127,那么它們的值是在0到1016之間,顯然這不是一種均勻的分配。

*b. *假設(shè)關(guān)鍵字至少有三個(gè)字符,可以采用如下方法:

 public static int hash(String key, int tableSize){
        return (key.charAt(0) + key.charAt(1) * 27 + key.charAt(2) * 729) % tableSize;
 }

但這樣依舊不能完全均勻地分配,因?yàn)?個(gè)字母的不同組合數(shù)實(shí)際只有2851,也就是只有大約表的28%被真正散列到。

c. 根據(jù)Horner法則計(jì)算一個(gè)(37)的多項(xiàng)式函數(shù):

Paste_Image.png

public static int hash(String key, int tableSize){
        int hashVal = 0;
        for (int i = 0; i < key.length(); i ++){
            hashVal = 37 * hashVal + key.charAt(i);
        }
        hashVal %= tableSize;
        //產(chǎn)生負(fù)數(shù)的情況
        if (hashVal < 0){
            hashVal += tableSize;
        }
        return hashVal;
    }
解決沖突的兩種方法:
  • 分離鏈接法:將散列到同一個(gè)值的所有元素保留到一個(gè)表中。
  • 開放定址法:嘗試另外一些單元,直到找到空的單元為止。

*a. 分離鏈表法: *

separate_chaining.png
  • 定義表的基本變量:
    //定義默認(rèn)表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定義表的存儲(chǔ)
    private List<AnyType>[] theLists;
    //定義表當(dāng)前的大小
    private int currentSize;
  • 定義其構(gòu)造函數(shù),進(jìn)行初始化:
    public SeparateChainingHashTable(){
        this(DEFAULT_TABLE_SIZE);
    }

    //進(jìn)行初始化操作
    public SeparateChainingHashTable(int size){
        //初始化表,這里調(diào)用了一個(gè)nextPrime函數(shù),以保證其表的大小為素?cái)?shù)
        theLists = new LinkedList[nextPrime(size)];
        //初始化表中的鏈表
        for (int i = 0; i < theLists.length; i ++){
            theLists[i] = new LinkedList<AnyType>();
        }
    }

    //返回下一個(gè)素?cái)?shù)
    private static int nextPrime(int n){
        while (!isPrime(n)){
            n ++;
        }
        return n;
    }
    //判斷是否為素?cái)?shù)
    private static boolean isPrime(int n){
        for (int i = 2; i <= Math.sqrt(n); i ++){
            if (n % i == 0 && n != 2){
                return false;
            }
        }
        return true;
    }
  • 進(jìn)行插入操作:如果這個(gè)元素是新元素,則它將被插入到鏈表的前端,因?yàn)橥陆迦氲脑刈钣锌赡懿痪帽辉L問。
    public void insert(AnyType x){
        //獲取x根據(jù)hash后找到所對應(yīng)的位置
        List<AnyType> whichList = theLists[myHash(x)];
        //如果當(dāng)前位置鏈表不包含元素,則進(jìn)行插入操作
        if (!whichList.contains(x)){
            //進(jìn)行添加
            whichList.add(x);
            //判斷是否達(dá)到表的長度,若達(dá)到則進(jìn)行rehash操作,以擴(kuò)大表的大小
            if (++ currentSize > theLists.length){
                rehash();
            }
        }
    }

    //根據(jù)值獲取到其對應(yīng)的hash位置
    private int myHash(AnyType x){
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        //考慮到負(fù)數(shù)的情況
        if (hashVal < 0){
            hashVal += theLists.length;
        }
        return hashVal;
    }

    private void rehash(){
        //獲取到原來的表
        List<AnyType>[] oldLists = theLists;
        //對現(xiàn)有的表大小進(jìn)行擴(kuò)大
        theLists = new List[nextPrime(2 * theLists.length)];
        //初始化新表
        for (int j = 0; j < theLists.length; j ++){
            theLists[j] = new LinkedList<AnyType>();
        }
        //初始化大小
        currentSize = 0;
        //將舊表中的數(shù)據(jù)重新插入到新表中
        for (int i = 0; i < oldLists.length; i ++){
            for (AnyType item : oldLists[i]){
                insert(item);
            }
        }
    }
  • 刪除操作:
    //刪除操作
    public void remove(AnyType x){
        //獲取到對應(yīng)位置的鏈表
        List<AnyType> whichList = theLists[myHash(x)];
        //檢查是否包含元素,包含則進(jìn)行刪除操作,并大小--
        if (whichList.contains(x)){
            whichList.remove(x);
            currentSize --;
        }
    }
  • 檢查元素:
    //檢查是否包含某個(gè)元素
    public boolean contains(AnyType x){
        List<AnyType> whichList = theLists[myHash(x)];
        return whichList.contains(x);
    }
  • 打印鏈表:
    //打印鏈表結(jié)構(gòu)
    public void printHashTable(){
        for (int i = 0; i < theLists.length; i ++){
            if (theLists[i] != null && theLists[i].size() > 0){
                System.out.println("current position is : " + i );
                for (AnyType x : theLists[i]){
                    System.out.println(x);
                }
            }
        }
    }
  • 進(jìn)行檢測:為了效果更明顯,我們強(qiáng)制表的結(jié)果為10,當(dāng)然在實(shí)際項(xiàng)目中不可如此,這里只是為了測試作用。
public static void main(String[] args){
        int[] arr = {0, 81,1, 64,4, 25,36,16,49,9};
        SeparateChainingHashTable<Integer> separateChainingHashTable = new SeparateChainingHashTable<>(10);
        //進(jìn)行插入操作
        for (int i = 0; i < arr.length; i ++){
            separateChainingHashTable.insert(arr[i]);
        }
        //打印鏈表
        separateChainingHashTable.printHashTable();
    }
  • 運(yùn)行結(jié)果如下:
current position is : 0
0
current position is : 1
81
1
current position is : 4
64
4
current position is : 5
25
current position is : 6
36
16
current position is : 9
49
9
separate chaning.png
  • 當(dāng)然除了插入默認(rèn)的基本類型,我們還可以插入對象,但一定要重定義對象的equals()和hashCode()方法
public class Employee {
    private String name;
    private double salary;
    private int seniority;
    //構(gòu)造函數(shù)
    public Employee(String name, double salary, int seniority){
        this.name = name;
        this.salary = salary;
        this.seniority = seniority;
    }
    //判斷對象是否相同
    public boolean equals(Object rhs){
        //滿足為Employee類型且名字相同
        return rhs instanceof Employee && name.equals(((Employee) rhs).name);
    }
    //重寫hashCode
    public int hashCode(){
        return name.hashCode();
    }
    //打印對象
    @Override
    public String toString() {
        return "name: " + name + " salary: " + salary + " seniority: " + seniority + "\n";
    }
}
public static void main(String[] args){
        SeparateChainingHashTable<Employee> hashTable = new SeparateChainingHashTable<>(11);
        hashTable.insert(new Employee("worker", 1000,3));
        hashTable.insert(new Employee("worker", 1500,3));
        hashTable.insert(new Employee("worker", 1600,3));
        hashTable.insert(new Employee("Manager", 2000, 2));
        hashTable.insert(new Employee("Manager", 2500, 2));
        hashTable.insert(new Employee("boss", 3000, 1));
        hashTable.printHashTable();
    }

結(jié)果如下:

current position is : 6
name: Manager salary: 2000.0 seniority: 2
current position is : 7
name: boss salary: 3000.0 seniority: 1
current position is : 10
name: worker salary: 1000.0 seniority: 3

我們可以看到對于name相同的對象是不會(huì)重復(fù)插入的。

完整代碼:
public class SeparateChainingHashTable<AnyType> {

    public SeparateChainingHashTable(){
        this(DEFAULT_TABLE_SIZE);
    }

    //進(jìn)行初始化操作
    public SeparateChainingHashTable(int size){
        //初始化表,這里調(diào)用了一個(gè)nextPrime函數(shù),以保證其表的大小為素?cái)?shù)
        theLists = new LinkedList[nextPrime(size)];
        //初始化表中的鏈表
        for (int i = 0; i < theLists.length; i ++){
            theLists[i] = new LinkedList<AnyType>();
        }
    }

    public void insert(AnyType x){
        //獲取x根據(jù)hash后找到所對應(yīng)的位置
        List<AnyType> whichList = theLists[myHash(x)];
        //如果當(dāng)前位置鏈表不包含元素,則進(jìn)行插入操作
        if (!whichList.contains(x)){
            //進(jìn)行添加
            whichList.add(x);
            //判斷是否達(dá)到表的長度,若達(dá)到則進(jìn)行rehash操作,以擴(kuò)大表的大小
            if (++ currentSize > theLists.length){
                rehash();
            }
        }
    }
    //刪除操作
    public void remove(AnyType x){
        //獲取到對應(yīng)位置的鏈表
        List<AnyType> whichList = theLists[myHash(x)];
        //檢查是否包含元素,包含則進(jìn)行刪除操作,并大小--
        if (whichList.contains(x)){
            whichList.remove(x);
            currentSize --;
        }
    }

    //檢查是否包含某個(gè)元素
    public boolean contains(AnyType x){
        List<AnyType> whichList = theLists[myHash(x)];
        return whichList.contains(x);
    }

    public void makeEmpty(){
        for (int i = 0; i < theLists.length; i ++){
            theLists[i].clear();
        }
    }

    //定義默認(rèn)表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定義表的存儲(chǔ)
    private List<AnyType>[] theLists;
    //定義表當(dāng)前的大小
    private int currentSize;

    //根據(jù)值獲取到其對應(yīng)的hash位置
    private int myHash(AnyType x){
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        //考慮到負(fù)數(shù)的情況
        if (hashVal < 0){
            hashVal += theLists.length;
        }
        return hashVal;
    }

    private void rehash(){
        //獲取到原來的表
        List<AnyType>[] oldLists = theLists;
        //對現(xiàn)有的表大小進(jìn)行擴(kuò)大
        theLists = new List[nextPrime(2 * theLists.length)];
        //初始化新表
        for (int j = 0; j < theLists.length; j ++){
            theLists[j] = new LinkedList<AnyType>();
        }
        //初始化大小
        currentSize = 0;
        //將舊表中的數(shù)據(jù)重新插入到新表中
        for (int i = 0; i < oldLists.length; i ++){
            for (AnyType item : oldLists[i]){
                insert(item);
            }
        }
    }

    //返回下一個(gè)素?cái)?shù)
    private static int nextPrime(int n){
        while (!isPrime(n)){
            n ++;
        }
        return n;
    }
    //判斷是否為素?cái)?shù)
    private static boolean isPrime(int n){
        for (int i = 2; i <= Math.sqrt(n); i ++){
            if (n % i == 0 && n != 2){
                return false;
            }
        }
        return true;
    }

    //打印鏈表結(jié)構(gòu)
    public void printHashTable(){
        for (int i = 0; i < theLists.length; i ++){
            if (theLists[i] != null && theLists[i].size() > 0){
                System.out.println("current position is : " + i );
                for (AnyType x : theLists[i]){
                    System.out.println(x);
                }
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評(píng)論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,379評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,831評(píng)論 0 11
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,765評(píng)論 0 33
  • 回溯算法 回溯法:也稱為試探法,它并不考慮問題規(guī)模的大小,而是從問題的最明顯的最小規(guī)模開始逐步求解出可能的答案,并...
    fredal閱讀 13,711評(píng)論 0 89
  • 做寒假工的時(shí)候,體會(huì)到了在社會(huì)生存的不易。 重復(fù)的、機(jī)械的工作有很多,從事這些工作的人也很容易被替換,例如:服務(wù)員...
    008明瑾閱讀 253評(píng)論 0 0