1 / 45

Hashing

Hashing. อ ดร วิษณุ โคตรจรัส. เราต้องการทำอะไรกับข้อมูลที่ใช้ hash table. Insert Delete find (constant time) sort ไม่ได้ Findmin findmax ไม่ได้ นั่นก็คือทำได้ไม่เท่า binary search tree . Hash table. เรามี key กับ value แบบเดียวกับ Map ไง

bethesda
Download Presentation

Hashing

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Hashing อ ดร วิษณุ โคตรจรัส

  2. เราต้องการทำอะไรกับข้อมูลที่ใช้ hash table • Insert • Delete • find (constant time) • sort ไม่ได้ • Findmin findmax ไม่ได้ • นั่นก็คือทำได้ไม่เท่า binary search tree

  3. Hash table • เรามี key กับ value แบบเดียวกับ Map ไง • เราเอา key มาเข้า hash function เพื่อให้ได้ index ที่จะเก็บ value • ดังนั้นแฮชฟังก์ชั่นควรจะ • คำนวณง่าย • คีย์สองตัวต้องเข้าฟังก์ชั่นแล้วได้ index คนละตัว (อันนี้ทำได้ยากแต่เดี๋ยวดูไปเรื่อยๆก่อนนะ)

  4. Hash function • พยายามกระจายคีย์ไปทั่วตาราง เราอาจใช้ • จำนวนคีย์ mod tableSize • แต่ถ้าคีย์เป็น 10, 20, 30, …ก็ใช้ฟังก์ชั่นนี้ไม่ดี • แล้วถ้าคีย์เป็น String ล่ะ เราลองมาดูตัวอย่าง hash function แบบต่างๆกัน

  5. Hash function (1st example) • บวกค่า ASCII ของตัวอักษรต่างๆ 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; }

  6. แต่แบบนี้ไม่ดีถ้าตารางใหญ่แต่แบบนี้ไม่ดีถ้าตารางใหญ่ • เพราะถ้าคีย์ไม่ค่อยยาว เช่นมีแปดตัวอักษร • ASCII มีค่าได้มากที่สุดคือ 127 • ฉะนั้นผลบวกของทุกตัวอักษรจะได้ไม่เกิน 127*8 • ถ้าตารางใหญ่ จะไม่กระจาย ตัวที่หมื่น Index ที่ได้จะกองตอนต้นหมด

  7. Hash function (2nd example) • สมมติว่าตารางใหญ่และตัวคีย์เป็นตัวอักษรสุ่มอย่างน้อยสามตัวอักษร • เราดูเฉพาะตัวอักษรสามตัวแรก public static int hash(String key, int tableSize){ return (key.charAt(0) +27*key.charAt(1) +729* key.charAT(2))%tableSize; } 27*27 จำนวนตัวอักษร รวมตัวว่าง กระจายในตารางขนาดหนึ่งหมื่นได้ดี (10007 เป็นค่าแรกสำหรับหนึ่งหมื่นที่เป็น prime เดี๋ยวมาดูว่าทำไมต้องเป็น)

  8. แต่จริงๆคีย์ที่ใช้ก็ไม่สุ่มนี่แต่จริงๆคีย์ที่ใช้ก็ไม่สุ่มนี่ • ฉะนั้นมีการซ้ำมากแน่นอน • มาดูอีกฟังก์ชั่นในหน้าต่อไป

  9. Hash function (3rd example) • คำนวณ polynomial function ของ 37 โดยใช้ Horner’s Rule • เราสามารถคำนวณ k0 + 37k1+ 37*37k2ได้โดยใช้ [(k2*37)+k1]*37 +k0 Horner rule คือการทำแบบนี้ไป n ตัว ซึ่งจริงๆแล้วคือการคำนวณ

  10. 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; if(hashVal<0) hashVal += tableSize; return hashVal; } เผื่อ overflow

  11. อาจจะกระจายไม่ดีนัก แต่ค่อนข้างคำนวณง่าย • แต่ถ้าคีย์ยาวมาก ก็จะคำนวณนาน • แก้โดยไม่ใช้หมดทุกตัวอักษร อาจเลือกตัวอักษรมาบางตัวจากแต่ละส่วนที่สำคัญของคีย์นั้น • แต่ยังไง hash function ก็แยกของลงตารางไม่ได้100% • ต้องมีของที่ลงช่องเดียวกันเกิดขึ้น เราเรียกว่า collision • ต่อไปเราจะดูวิธีแก้ปัญหาเมื่อเกิด collision

  12. วิธีแรก separate chaining • เก็บตัวที่ซ้ำใส่ linked list • ถ้าจะหาของ ก็ใช้ hash function แล้วต่อด้วยการหาในลิสต์ • ถ้าจะใส่ของลงไป ก็ ใช้ hash function เพื่อหาลิสต์ที่จะลง ต่อจากนั้นตรวจลิสต์ว่ามีของที่จะเติมลงไปหรือยัง ถ้ายังก็เติมลงไปหน้าลิสต์ (ตัวที่ถูกใส่ไปใหม่ๆมักถูกเรียกใช้ในเวลาไม่นานต่อมา)

  13. มาดูสิ่งที่ต้องใช้ก่อนมาดูสิ่งที่ต้องใช้ก่อน • public interface Hashable • { • /** • * Compute a hash function for this object. • * @param tableSize the hash table size. • * @return (deterministically) a number between • * 0 and tableSize-1, distributed equitably. • */ • int hash( int tableSize ); • }

  14. ตัวอย่างการใช้งาน Public class Student implements Hashable{ private String name; private double number; private int year; public int hash(int tableSize){ return SeparateChainingHashTable.hash(name, tableSize); } public boolean equals(Object rhs){ return name.equals(((Student)rhs).name); } } จริงๆแล้วคือ static method จากคลาส

  15. public class SeparateChainingHashTable • { • /** • * Construct the hash table. • */ • public SeparateChainingHashTable( ) • { • this( DEFAULT_TABLE_SIZE ); • } • /** • * Construct the hash table. • * @param size approximate table size. • */ • public SeparateChainingHashTable( int size ) • { • theLists = new LinkedList[ nextPrime( size ) ]; • for( int i = 0; i < theLists.length; i++ ) • theLists[ i ] = new LinkedList( ); • }

  16. ใช้ Student ตรงนี้ไง • /** • * Insert into the hash table. If the item is • * already present, then do nothing. • * @param x the item to insert. • */ • public void insert( Hashable x ) • { • LinkedList whichList = theLists[ x.hash( theLists.length ) ]; • LinkedListItr itr = whichList.find( x ); • if( itr.isPastEnd( ) ) • whichList.insert( x, whichList.zeroth( ) ); • } • /** • * Remove from the hash table. • * @param x the item to remove. • */ • public void remove( Hashable x ) • { • theLists[ x.hash( theLists.length ) ].remove( x ); • }

  17. /** • * Find an item in the hash table. • * @param x the item to search for. • * @return the matching item, or null if not found. • */ • public Hashable find( Hashable x ) • { • return (Hashable)theLists[ x.hash( theLists.length ) ].find( x ).retrieve( ); • } • /** • * Make the hash table logically empty. • */ • public void makeEmpty( ) • { • for( int i = 0; i < theLists.length; i++ ) • theLists[ i ].makeEmpty( ); • }

  18. /** • * A hash routine for String objects. • * @param key the String to hash. • * @param tableSize the size of the hash table. • * @return the hash value. • */ • 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; • if( hashVal < 0 ) • hashVal += tableSize; • return hashVal; • }

  19. private static final int DEFAULT_TABLE_SIZE = 101; • /** The array of Lists. */ • private LinkedList [ ] theLists; • /** • * Internal method to find a prime number at least as large as n. • * @param n the starting number (must be positive). • * @return a prime number larger than or equal to n. • */ • private static int nextPrime( int n ) • { • if( n % 2 == 0 ) • n++; • for( ; !isPrime( n ); n += 2 ) • ; • return n; • }

  20. /** • * Internal method to test if a number is prime. • * Not an efficient algorithm. • * @param n the number to test. • * @return the result of the test. • */ • private static boolean isPrime( int n ) • { • if( n == 2 || n == 3 ) • return true; • if( n == 1 || n % 2 == 0 ) • return false; • for( int i = 3; i * i <= n; i += 2 ) • if( n % i == 0 ) • return false; • return true; • }

  21. // Simple main • public static void main( String [ ] args ) • { • SeparateChainingHashTable H = new SeparateChainingHashTable( ); • final int NUMS = 4000; • final int GAP = 37; • System.out.println( "Checking... (no more output means success)" ); • for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS ) • H.insert( new MyInteger( i ) ); • for( int i = 1; i < NUMS; i+= 2 ) • H.remove( new MyInteger( i ) ); • for( int i = 2; i < NUMS; i+=2 ) • if( ((MyInteger)(H.find( new MyInteger( i ) ))).intValue( ) != i ) • System.out.println( "Find fails " + i ); • for( int i = 1; i < NUMS; i+=2 ) • { • if( H.find( new MyInteger( i ) ) != null ) • System.out.println( "OOPS!!! " + i ); • } • } • }

  22. นิยาม • Load factor • ซึ่งก็คือความยาวเฉลี่ยของลิงค์ลิสต์ Search time = time to do hashing + time to search list = constant + time to search list • Unsuccessful search Search time = ความยาวลิสต์ = ค่า load factor

  23. Successful search • ที่ลิสต์ที่จะค้น มีหนึ่งโนดที่มีของ และก็โนดอื่นๆซึ่งมีตั้งแต่ 0 โนดขึ้นไป • ถ้าเราดูที่ตารางซึ่งมีจำนวนสมาชิก N ซึ่งถูกแบ่งไปเป็นลิสต์ M ลิสต์ • จะได้ว่า มีโนดที่ไม่ใช่โนดที่เราต้องการหาอยู่ N-1 โนด • ถ้าแบ่งไปเป็นลิสต์ละเท่าๆกันจะได้ว่าแต่ละลิสต์มี (N-1)/M โนด • = lambda- (1/M) • = lambda นั่นเอง เพราะว่า M มีค่ามาก • โดยเฉลี่ยแล้ว ครึ่งหนึ่งของโนดพวกนี้จะถูกค้น นั่นคือ lambda/2 • ฉะนั้น เวลาโดยเฉลี่ยที่ใช้ในการหาของเจอคือ 1 + (lambda/2) • นี่หมายความว่า ขนาดตารางไม่สำคัญ ที่สำคัญคือ load factor

  24. วิธีที่สอง Open addressing • ถ้าไม่อยากใช้ลิสต์ ก็ต้องใช้นี่เลย • ถ้ามี collision ก็หาช่องใหม่เรื่อยๆจนกว่าจะเจอช่องว่าง • โดยลองช่องที่ h0(x), h1(x), … • hi(x)=[hash(x)+f(i)]%tableSize, f(0)=0 • ข้อมูลทุกตัวต้องลงตาราง ไม่มีลิสต์มาช่วยเก็บ ดังนั้นตารางต้องใหญ่ • Load factor <=0.5

  25. Open addressing แบบ linear probing • F จะเป็นลิเนียร์ฟังก์ชั่นของ i • ส่วนใหญ่จะให้ f(i)=i • ซึ่งคือการหาช่องว่างในตารางโดยดูเรียงทีละช่องไปเรื่อยๆ • มีปัญหาคือ เวลาในการหาช่องว่างจะนานได้ จะเกิดการใช้ช่องติดกัน (primary clustering) ถ้ามีอันไหนมา collide ในช่วงนี้ กว่าจะหาช่องว่างเจอก็จะเป็นเวลานาน

  26. Open addressing แบบ quadratic probing • ช่วยกำจัด primary clustering • ฟังก์ชั่นที่นิยมใช้กันคือ f(i)=i2 • อย่าลืมว่า hi(x)=[hash(x)+f(i)]%tableSize a ถ้า b ลงซ้ำที่เดียวกับ a เราบวกอีก 12 ก็จะเจอที่ว่าง ถ้า c ลงซ้ำที่เดียวกับ a เราบวกอีก 12 ก็จะเจอ b ดังนั้นลองบวก 22ก็จะเจอที่ว่าง

  27. แต่ถ้าตารางใส่เกินครึ่งหนึ่งแล้ว หรือถ้าขนาดตารางไม่เป็นจำนวนเฉพาะ สูตรนี้ก็ไม่แน่ว่าจะหาช่องว่างได้ • แต่ถ้าใช้ตารางยังไม่ถึงครึ่งหนึ่งและขนาดตารางเป็นจำนวนเฉพาะแล้วละก็ ได้มีการพิสูจน์แล้วว่าหาช่องลงได้แน่นอน

  28. พิสูจน์ • ให้ขนาดตารางเป็นจำนวนเฉพาะที่ใหญ่กว่าสาม • ให้ (h(x)+i2) mod tableSize • (h(x)+j2) mod tableSize • Prove by contradiction • สมมติให้ตำแหน่งทั้งสองเป็นตำแหน่งเดียวกันและ i ไม่เท่ากับ j เป็นสองตำแหน่งที่หาที่ลงได้

  29. พิสูจน์(ต่อ) • i-j =0 นั้นเป็นไปไม่ได้ เพราะเราสมมติแล้วว่าสองตัวนี้ไม่เท่ากัน • i+j=0 ก็เป็นไปไม่ได้ เพราะสมมติว่า • ดังนั้นที่พึ่งสมมติให้ ตำแหน่งที่ลงได้เป็นตำแหน่งเดียวกัน จึงผิด • สรุปคือ ตำแหน่งที่ลงได้ เป็นคนละตำแหน่งเสมอ • เราจึงพูดได้ว่าถ้าตารางยังมีการใช้งานไม่ถึงครึ่ง จะต้องได้ช่องลงแน่นอน

  30. ขนาดตารางต้องเป็นจำนวนเฉพาะขนาดตารางต้องเป็นจำนวนเฉพาะ • ถ้าไม่เป็น ตำแหน่งที่ควรจะลงได้จะลดลงมากทีเดียว • ตัวอย่าง ตารางขนาด 16 สมมติว่าการแฮชธรรมดาให้ลงช่องแรก • ใช้ quadratic probing 32 22 12 42 72 62 52 ซ้ำช่องที่เคยใช้หมดเลย

  31. การลบทำแบบธรรมดาไม่ได้การลบทำแบบธรรมดาไม่ได้ • ถ้าเอาตัววงกลมเล็กออก พอทีหลังหาสมาชิก(ที่ตอน insert ต้องกระโดดหลบช่องที่วงกลมอยู่) จะกลายเป็นเจอช่องว่างไม่มีอะไร ซึ่งจะมีความหมายเหมือนการหาไม่เจอ 32 22 12 42 72 62 52 ดังนั้นต้องใช้ lazy deletion คือทำเครื่องหมายว่าลบแล้วหรือยัง

  32. Open addressing implementation class HashEntry { Hashable element; // the element boolean isActive;// false is deleted public HashEntry( Hashable e ){ this( e, true ); } public HashEntry( Hashable e, boolean i ){ element = e; isActive = i; } }

  33. public class QuadraticProbingHashTable{ • private static final int DEFAULT_TABLE_SIZE = 11; • /** The array of elements. */ • private HashEntry [ ] array; // The array of elements • private int currentSize; // The number of occupied cells • public QuadraticProbingHashTable( ){ • this( DEFAULT_TABLE_SIZE ); • } • /** • * Construct the hash table. • * @param size the approximate initial size. • */ • public QuadraticProbingHashTable( int size ){ • allocateArray( size ); • makeEmpty( ); • } nonactive null active

  34. /** • * Internal method to allocate array. • * @param arraySize the size of the array. • */ • private void allocateArray( int arraySize ){ • array = new HashEntry[ arraySize ]; • } • /** • * Make the hash table logically empty. • */ • public void makeEmpty( ){ • currentSize = 0; • for( int i = 0; i < array.length; i++ ) • array[ i ] = null; • }

  35. /** • * Return true if currentPos exists and is active. • * @param currentPos the result of a call to findPos. • * @return true if currentPos is active. • */ • private boolean isActive( int currentPos ){ • return array[ currentPos ] != null && array[ currentPos ].isActive; • }

  36. /** • * Method that performs quadratic probing resolution. • * @param x the item to search for. • * @return the position where the search terminates. • */ • private int findPos( Hashable x ) { • /* 1*/ int collisionNum = 0; • /* 2*/ int currentPos = x.hash( array.length ); • /* 3*/ while( array[ currentPos ] != null && • !array[ currentPos ].element.equals( x ) ){ • /* 4*/ currentPos += 2 * ++collisionNum - 1; // Compute ith probe • /* 5*/ if( currentPos >= array.length ) // Implement the mod • /* 6*/ currentPos -= array.length; • } • /* 7*/ return currentPos; • } f(i)=i2=f(i-1)+2i-1

  37. /** • * Find an item in the hash table. • * @param x the item to search for. • * @return the matching item. • */ • public Hashable find( Hashable x ){ • int currentPos = findPos( x ); • return isActive( currentPos ) ? array[ currentPos ].element : null; • }

  38. /** • * Insert into the hash table. If the item is • * already present, do nothing. • * @param x the item to insert. • */ • public void insert( Hashable x ) • { • // Insert x as active • int currentPos = findPos( x ); • if( isActive( currentPos ) ) • return; //x is already inside, so do nothing • array[ currentPos ] = new HashEntry( x, true ); • // Rehash; see Section 5.5 • if( ++currentSize > array.length / 2 ) • rehash( ); • }

  39. เวลา O(N) เพราะมี N สมาชิกให้ rehash และตารางตอนนี้ก็เป็น 2N แต่ทำไม่บ่อย ก่อนการ rehash ก็ต้องใส่ไปครึ่งตารางก่อนแล้ว ต้องคำนวณ index ใหม่เพราะนี่มันคนละตารางแล้ว • /** • * Expand the hash table. • */ • private void rehash( ) • { • HashEntry [ ] oldArray = array; • // Create a new double-sized, empty table • allocateArray( nextPrime( 2 * oldArray.length ) ); • currentSize = 0; • // Copy table over • for( int i = 0; i < oldArray.length; i++ ) • if( oldArray[ i ] != null && oldArray[ i ].isActive ) • insert( oldArray[ i ].element ); • return; • }

  40. เพิ่มเติม rehash • Rehash ทำได้สามลักษณะคือ • ทำทันทีที่ตารางเต็มครึ่งหนึ่ง • ทำเมื่อ insert ไม่ได้แล้วเท่านั้น หรือ • ทำเมื่อถึงโหลดแฟกเตอร์ค่าหนึ่ง • อย่าลืมว่ายิ่งโหลดแฟกเตอร์มาก ก็จะ insert ได้ช้าลง • อันนี้น่าจะดีที่สุด เลือกโหลดแฟกเตอร์ให้ดีๆ

  41. hash, nextPrime, isPrime เหมือนเดิม • /** • * Remove from the hash table. • * @param x the item to remove. • */ • public void remove( Hashable x ) • { • int currentPos = findPos( x ); • if( isActive( currentPos ) ) • array[ currentPos ].isActive = false; • }

  42. ข้อเสียของ quadratic probing • Secondary clustering • แก้โดย double hashing นั่นคือ • f(i) = i*hash2(x) โดยเราจะดูช่องว่างที่ระยะ hash2(x), 2 *hash2(x), …ไปเรื่อยๆ • ดังนั้นต้องเลือกฟังก์ชั่นที่สองนี้ให้ดีๆ • ถ้ามีเก้าช่องและเอา hash2(x) เป็น x%9 ก็ไม่ได้อะไรเลยตอนเราใส่ 99 เพราะเป็น 0 ตลอด • ฟังก์ชั่นห้ามได้คำตอบเป็น 0 • ต้องให้แน่ใจว่าเข้าถึงทุกช่องในตารางได้

  43. ตัวอย่าง hash2 • hash2(x)=R-(x%R) โดย R เป็นจำนวนเฉพาะที่น้อยกว่า tableSize • ลองกับตารางขนาด 16 เราใส่ 9, 25, 26, 41, 42, 58 สมมติว่า hash อันแรกเป็น x%tableSize 25 ชนดังนั้นต้องบวก 13-(25%13)=1 26 ชนดังนั้นต้องบวก 13-(26%13)=13

  44. 41 ชนดังนั้นต้องบวก 13-(41%13)=11 42 ชนดังนั้นต้องบวก 13-(42%13)=10 แต่ 42 มาชนนี่อีกที ต้องบวกจากตอนแรกเป็น 2*10

  45. 58 ชนนี่ ดังนั้นต้องบวก 13-(58%13)=7

More Related