คืออะไร? คือ queue ที่ access element ตามความสำคัญของตัว element นั้น

Slides:



Advertisements
งานนำเสนอที่คล้ายกัน
Bansomdej Chaopraya Rajabhat University
Advertisements

C# เบื้องต้น ก่อนการเขียนเกมด้วย XNA
Suphot Sawattiwong Function ใน C# Suphot Sawattiwong
Control Statement if..else switch..case for while do-while.
Data Type part.II.
Stack.
การเรียงลำดับและการค้นหาแบบง่าย
บทเรียนคอมพิวเตอร์ช่วยสอน (CAI)
AVL Tree.
แถวคอย (Queue).
บทนำ.
Object and classes.
ลักษณะการทำงานของ Stack
ทศนิยมและเศษส่วน F M B N โดย นางสาวสุพรรษา ธรรมสโรช.
05_3_Constructor.
ตัวแปรชุด.
ครั้งที่ 7 Composition.
Selected Topics in IT (Java)
การควบคุมทิศทางการทำงานของโปรแกรม
Network programming Asst. Prof. Dr. Choopan Rattanapoka
Network programming Asst. Prof. Dr. Choopan Rattanapoka
การสืบทอด (Inheritance)
คลาสและวัตถุ (3) (Class & Object)
คลาสและวัตถุ (4) (Class & Object)
บทที่ 4 Method (2).
Suphot Sawattiwong Lab IV: Array Suphot Sawattiwong
Inheritance การสืบทอดคลาส
โครงสร้างข้อมูลแบบคิว
การจัดเรียงข้อมูล Sorting Internal Sorting External Sorting.
คิว ลักษณะการทำงานของ Queue การดำเนินการของ Queue การตรวจสอบ Queue
ชนิดของเซต เช่น A = เซตว่าง (Empty set or Null set)
คิว (Queue) Queue ADT Queue เป็น List ชนิดหนึ่ง แต่สำหรับ queue การแทรกข้อมูลลงบน queue (Insertion) จะทำที่ปลายใดปลายหนึ่งของ Queue ในขณะที่การลบข้อมูลออกจากคิว.
Method of Class อ.สุพจน์ สิงหัษฐิต
บทที่ 5 Link List Link List.
Object-Oriented Programming
วิชา COSC2202 โครงสร้างข้อมูล (Data Structure)
วิชา COSC2202 โครงสร้างข้อมูล (Data Structure)
บทที่ 3 Class and Object (2).
LOOPLOOP. LOOP คืออะไร ? - วงรอบการทำงาน - ทำงานแบบซ้ำๆ ไปเรื่อยๆตามเงื่อนไข เช่น - การแพ๊คของ 50 ชิ้นใส่กล่อง ทำไปเรื่อยๆ จนกว่า ของจะหมด - ตีดอทไปเรื่อยๆ.
การเรียงข้อมูล 1. Bubble Sort 2. Insertion Sort 3. Heap Sort
School of Information Communication Technology,
Java collection framework
ต้นไม้ Tree (2) ผู้สอน อาจารย์ ยืนยง กันทะเนตร
School of Information Communication Technology,
อาจารย์ปิยศักดิ์ ถีอาสนา
Recursive Method.
โครงสร้างข้อมูลแบบลิงก์ลิสต์
การค้นในปริภูมิสถานะ
TECH30201 Object-Oriented Programming
“คำพูดคุณครู”.
Inheritance และ Encapsulation.  การสร้างหรือพัฒนาคลาสใหม่จากคลาสเดิมที่ มีอยู่แล้ว  คลาสใหม่จะนำแอตทริบิวต์และเมธอดของ คลาสเดิมมาใช้  เป็นการถ่ายทอดคุณสมบัติจากคลาสหนึ่งสู่อีก.
Object-Oriented Programming
เครื่องมือที่ใช้ JUnit4.8.1 on Eclipse SDK3.5.2 ขึ้นไป
การจัดเรียงข้อมูล (sorting)
stack #1 ผู้สอน อาจารย์ ยืนยง กันทะเนตร
โครงสร้างข้อมูลแบบ สแตก (stack)
ต้นไม้ Tree [3] ผู้สอน อาจารย์ ยืนยง กันทะเนตร
WATTANAPONG SUTTAPAK SOFTWARE ENGINEERING, SCHOOL OF INFORMATION COMMUNICATION TECHNOLOGY, UNIVERSITY OF PHAYAO Chapter 9 Heap and Hash 1.
การค้นในปริภูมิสถานะ
Week 13 Basic Algorithm 2 (Searching)
1 Inheritance อุทัย เซี่ยงเจ็น สำนักวิชาเทคโนโลยีสารสนเทศ และการสื่อสาร มหาวิทยาลัยนเรศวร วิทยาเขต สารสนเทศพะเยา.
Chapter 6 Abstract Class and Interface
บทที่ 3 การสืบทอด (Inheritance)
ค32213 คณิตศาสตร์สำหรับคอมพิวเตอร์ โรงเรียนปลวกแดงพิทยาคม
Inheritance and Method Overriding
Inheritance Chapter 07.
Dr.Surasak Mungsing CSE 221/ICT221 การวิเคราะห์และออกแบบขั้นตอนวิธี Lecture 05: การวิเคราะห์ความซับซ้อนของ ขั้นตอนวิธีการเรียงลำดับข้อมูล.
Data Structures and Algorithms 2/2561
4.4 AVL Trees AVL (Adelson-Velskii and Landis) tree เป็น binary search tree กำกับด้วยเงื่อนไขของการสมดุล และประกันว่า depth ของ tree จะเป็น O(log n) อย่างง่ายที่สุดคือกำหนดว่า.
การเขียนโปรแกรมภาษา Java (ต่อ)
ใบสำเนางานนำเสนอ:

คืออะไร? คือ queue ที่ access element ตามความสำคัญของตัว element นั้น เช่นคนป่วยขาหักกับหลังหัก ถึงคนหลังหักมาทีหลังก็ควรถูกรักษาก่อน (ไม่งั้นได้เผาก่อนแน่) Printer queue จำนวนหน้าน้อยควรได้ก่อน คือคิว แต่จัดเรียงสมาชิกตามความสำคัญนั่นเอง คือคนสำคัญแซงคิวได้ ที่ให้จำนวนหน้าน้อยได้ก่อนก็เพื่อเพิ่ม อัตราการ output ให้ได้ดีที่สุด

Priority โดยทั่วไป ค่าน้อยถือว่าสำคัญกว่าค่ามาก (มี priority สูงกว่านั่นเอง) ถ้าสองตัวมีค่าเท่ากัน ก็น่าจะให้ตัวที่อยู่ในคิวมานานกว่าได้ไป การเปรียบเทียบค่า เราใช้ Comparable interface หรือ Comparator interface ค่าน้อยหรือค่ามากจะสำคัญกว่านั้นแล้วแต่จะมอง แต่ในทางทฤษฎีถือว่าค่าน้อยสำคัญกว่า ส่วนถ้ามีค่าเท่ากัน ตัวที่อยู่ในคิวมาก่อนก็ควรจะได้ชื่อว่าสำคัญกว่า เดี๋ยวจะทวนเรื่องการเปรียบเทียบอีกนิดหน่อยในหน้าต่อไป

เหมือน Comparable เพียงแต่มี Comparator interface เหมือน Comparable เพียงแต่มี int compare(Object o1, Object o2) ต้องเป็น class เดียวกัน หรือ เป็นsubclass กัน ไม่งั้น throw CastCastException Output มาเป็นค่าลบถ้า o1 ถือว่ามีค่าน้อยกว่า o2 ค่าบวกถ้า o1 ถือว่ามีค่ามากกว่าo2 ค่า 0 ถ้า o1 ถือว่ามีค่าเท่ากับ o2

ตัว interface // Postcondition: รีเทิร์นจำนวนสมาชิกใน priority queue นี้ // Postcondition: รีเทิร์น true ถ้า priority queue นี้ไม่มีสมาชิก //ไม่งั้นก็รีเทิร์น false ต่อไปนี้เป็นตัวอย่างส่วนโครงสร้างของ priority queue แบบหนึ่ง เราจะใช้แบบนี้เป็นหลักเลย จะมี method ต่างๆที่ priority queue ควรมี เช่นหาจำนวนสมาชิกในคิว หาว่าคิวไม่มีสมาชิกหรือไม่ เติมสมาชิกใหม่ลงในคิว(อย่าลืมว่ามันจะจัดสมาชิกตามความสำคัญเอง) // Postcondition: element จะถูกเติมลงใน priority queue

// Precondition: ต้องไม่เป็นคิวที่ว่าง ไม่งั้นจะ throw // NoSuchElementException // Postcondition: รีเทิร์นออบเจ็คต์ตัวที่สำคัญที่สุดใน PriorityQueue นี้ // Precondition: ต้องไม่เป็นคิวที่ว่าง ไม่งั้นจะ throw // NoSuchElementException // Postcondition: เอาออบเจ็คต์ตัวที่สำคัญที่สุดใน PriorityQueue นี้ // ออกจากคิวและรีเทิร์นออบเจ็คต์นี้เป็นคำตอบของเมธอดด้วย นอกจากนี้ยังต้องมีการดูของที่หัวคิว และเอาของออกจากหัวคิวด้วย

วิธีการ implement มีหลายวิธี ใช้คิวธรรมดา จะไม่ได้ prioriry อาร์เรย์ของคิว ก็ไม่ได้เพราะ priority อาจเยอะมาก ใช้ ArrayList เรียงสมาชิก getMin จะเป็น constant time add กับ removeMin จะใช้ linear time -ซึ่งยังช้าไป อาร์เรย์ของคิวคือมีแต่ละช่องเป็นลิสต์ที่เก็บของแต่ละ priority ไว้ แต่ถ้ามี priority จำนวนมากจะทำให้เปลืองที่มาก ถ้าใช้ arraylist จะทำให้เรียงสมาชิกได้ง่าย แต่ตอนเติมจะเสียเวลาหาตำแหน่งที่เติม และเสียเวลาเลื่อนอาร์เรย์ ตอนเอาออกจะเสียเวลาในการจัดอาร์เรย์(อาจมีการเลื่อนช่องเยอะ) ซึ่งแปรตามขนาดของข้อมูล

วิธีการ implement (ต่อ) ใช้ LinkedList getMin กับ removeMin เป็น constant time add จะเป็น linear time Ok รับได้ ลองทำดู ถ้าใช้ linked list ตอนที่เอาของออกจะไม่ต้องทำอะไรเพิ่ม แค่ update pointer เพราะฉะนั้นเวลาในการ removeMin จะคงที่ แต่ตอนเติมของลงในคิวก็ต้องใช้เวลาหาช่องเติม

ใช้ Comparable โดย default ตัวอย่างนี้เป็น Priority queue ที่สร้างจาก linked list ใช้ Comparable โดย default

จะเห็นว่าเราใช้ method ของลิสต์ได้เยอะเลย

ทำไม? ทำไมจึงต้องย้อน iterator ไปหนึ่ง อย่าลืมว่าตอนที่ while loop exit ออกนั้น แม้จะได้ compare(element,itr.next()) <0 ก็ตาม ตัว itr.next(); ก็จะทำให้iterator เลื่อนไปอีกหนึ่งตำแหน่งอยู่ดี ทำให้ถ้าแทรกสมาชิกใหม่โดยไม่ถอยหลัง จะเป็นการเติมสมาชิกใหม่เลยที่ที่ควรจะเป็นไปหนึ่งตำแหน่ง ส่วนที่เวลาเป็น O(n) ก็เพราะจำนวนลูปขึ้นอยู่กับ n ทำไม?

ทำไมต้อง previous ? ถ้าเรามี 1,2,4,5 และต้องการ add 3 Loop จะหยุดเมื่อ itr.next() เป็น 4 แต่ตัว iterator ได้เลื่อนไปที่ 5 ซะแล้ว ดังนั้น ต้อง previous() กลับมา ก่อนที่จะใส่ 3 หน้านี้เป็นตัวอย่างว่าทำไมต้องใช้ previous

ถ้า comparator = null, return elem1.compareTo (elem2); protected int compare (Object elem1, Object elem2) {   return (comparator==null ? ((Comparable)elem1).compareTo(elem2) : comparator.compare (elem1, elem2)); } // method compare ถ้า comparator = null, return elem1.compareTo (elem2); ถ้าไม่งั้น return comparator.compare (elem1, elem2); ถ้าไม่ได้กำหนด comparator ให้ใช้การ compare ของ Comparable interface โดยตัว object ที่เราเอามาเปรียบเทียบกันจะต้อง implement Comparable interface ส่วนถ้ามี comparator ให้ใช้ method ของ comparator ตัวนั้น

คราวนี้ใช้เซตบ้าง ดูแผ่นถัดไป

หน้านี้เป็นการใช้เซตทำ priority queue โครงสร้างจริงๆคือ treeset และมีการกำหนดตัว comparator ด้วย ตัว constructor ก็รับ comparator เป็น input

public void add (Object element) { set.add (element); } // method add   public Object getMin ( ) { return set.first( ); } // method getMin public Object removeMin ( ) { Object temp = set.first( ); set.remove (set.first( )); return temp; } // method removeMin ทั้ง 3 method นี้มี worstTime(n) เป็น LOGARITHMIC ของ n. เราไม่รู้ว่า setเก็บของยังไง รู้แต่ว่ามันเป็น treeset ซึ่งเป็นต้นไม้ ดังนั้นเวลาจึงอยู่ในรูปแบบของ log ส่วน method ต่างๆนั้นเรียกใช้ของ set ได้เลย

อีกวิธีนึง คราวนี้ใช้ Heap ppublic class Heap implements PriorityQueue {   ยังไม่อยู่ใน JAVA COLLECTIONS FRAMEWORK     ต่อไปจะ implement โดยใช้ heap เดี๋ยวมาดูกันว่า heap คืออะไร

Complete Binary tree ยังจำได้หรือไม่ Complete binary tree คือ tree ที่เต็มทุกระดับ ยกเว้นระดับของใบ ซึ่งระดับของใบนี้ จะต้องถูกเติมจากซ้ายไปขวาเท่านั้น จะไม่มีรูว่างเป็นหย่อมๆ

Heap คือ complete binary tree ซึ่งเป็น empty tree หรือ สมาชิกใน root มีค่าน้อยที่สุด (เรียกว่า minHeap) และ left กับ right subtree ก็เป็น heap ด้วย (ไม่ใช่ binary search tree นะ) อย่าลืมว่า min จะอยู่ที่ root เพราะฉะนั้น ยังไงก็ไม่ใช่ binary search tree

นี่คือตัวอย่างของ heap จะเห็นว่าค่าน้อยที่สุดจะอยู่ที่ root เสมอไม่ว่าจะเป็น subtree ใดๆ

Complete binary tree เอาใส่อาร์เรย์ได้   26 40 31 48 50 85 36 107 48 55 57 88 ง่ายต่อการหา child, parent มีข้อสังเกตที่ดีมากอย่างหนึ่ง นั่นคือ complete binary tree เก็บในอาร์เรย์ได้ง่าย หา parent กับ child ของ node ต่างๆได้ด้วยการใช้ index ดังนั้น heap ก็สามารถเก็บในอาร์เรย์ได้ง่ายเช่นเดียวกัน ในรูปคือ heap จากหน้าที่แล้ว นำมาทำเป็นอาร์เรย์

ก่อนอื่นดูการใช้ Heap (GPA น้อยสุดถือว่า highest priority) ก่อนจะดูวิธีการทำ heap และจัดของใน heap เรามารู้วิธีใช้กันก่อนดีกว่า ตัวอย่างนี้เป็น heap ของข้อมูลนักเรียน โดยให้คนเกรดน้อยมีความสำคัญ ให้ดูส่วนที่ขีดเส้นใต้เท่านั้น ส่วนอี่นๆเป็นแค่ส่วนประกอบที่ให้โปรแกรมทำงานได้เท่านั้น

หน้านี้ไม่มีอะไร เป็นแค่ constructor

โปรแกรมเป็นโปรแกรมที่เติมนักเรียนหนึ่งคน ซึ่งมีชื่อตาม input s ลงใน heap ถ้า s ไม่ใช่ sentinel (ค่าที่บอกว่าเลิกรับ input ได้แล้ว) ก็ให้เติมนักเรียนลงใน heap โดยใช้เมธอด add ถ้าเป็น sentinel ให้เคลียร์ heap แล้ว output สิ่งที่อยู่ใน heap ทั้งหมดลงบนจอภาพ โดยวนลูปเรียกเมธอด removeMin เราก็จะได้รายชื่อของนักเรียนโดยคนไม่เก่งจะมีชื่อก่อน

// this Student ออบเจ็คต์นั้นน้อยกว่า เท่ากัน หรือมากกว่า GPA ของ o1 // มี constructor ที่รับ string เป็น input และก็มีเมธอด toString ด้วย เวลาทำอย่าลืม   // Postcondition: รีเทิร์นจำนวนเต็มที่ < 0, = 0, หรือ > 0 ขึ้นอยู่กับว่า GPA ของ // this Student ออบเจ็คต์นั้นน้อยกว่า เท่ากัน หรือมากกว่า GPA ของ o1 หน้านี้เป็นเมธอด compareTo ของนักเรียน โดยเราดู GPA เป็นหลัก

คราวนี้ดู Heap เลย Heap ที่เราทำนี้เป็นอาร์เรย์ที่เก็บ complete binary tree นั่นเอง

index ของลูกได้แบบนี้ ถ้า node ใดมี index เป็น j ลูกข้างซ้ายจะเป็น node ที่ 2j+1 ลูกข้างขวาจะเป็น node ที่ 2j+2

index ของพ่อได้แบบนี้ ถ้าลูกมี index j หรือ j+1 (สำหรับลูกทางขวา) Parent จะมี index (j-1)/2

ดู method ใน Heap Constructor ก็แค่สร้างตัวอาร์เรย์ขึ้นมา

// Postcondition: element ถูกเติมเป็นสมาชิกของ heap // เวลา worst case = O(n) // ส่วนเวลาเฉลี่ยเป็นค่าคงที่ ถ้าเต็มอาร์เรย์ ก็ขยาย ขึ้นกับเวลา percolateUp O(n) เกิดจากการขยายอาร์เรย์ (ในการก็อปปี้อาร์เรย์นั้นเราดึงเอาเมธอดของ system มาใช้) เวลาเฉลี่ยนั้นขึ้นกับ percolateUp ดังนั้นเดี๋ยวเราค่อยดูที่ percolateUp อย่าลืมว่าพอทำเสร็จแล้ว heap ก็ยังเป็น heap อยู่ //เติมลงไปท้ายอาร์เรย์ //แล้วค่อยปรับเลื่อนตำแหน่งขึ้นมา

สมมติว่าเราจะเติม 30 ลงไป สมมติว่าตอนนี้เราเติมสมาชิกใหม่ลงท้ายอาร์เรย์แล้ว ต่อไปเป็นตัวอย่างการ percolateUp 30

Percolate up คือ... สลับที่ 30 กับพ่อของเขาเรื่อยๆจนกว่าตัวพ่อจะน้อยกว่า พอทำเสร็จเราก็จะได้ heap นี่คือหลักของการ percolateUp

ต่อไปเป็นตัวอย่างการเลื่อนให้ดู

พอเลื่อนมาถึงนี่ก็เลื่อนต่อไม่ได้เพราะตัวพ่อมีค่าน้อยกว่า สังเกตดูจะพบว่าเราได้ heap เรียบร้อย แม้หลักจะดูจากต้นไม้แต่อย่าลืมว่าจริงๆเราใช้อาร์เรย์ ดังนั้นโค้ดจึงต้องเป็นตามอาร์เรย์

ความสูงของต้นไม้ เพราะเวลาแย่สุดเกิดตอนที่ต้องเลื่อนจนถึงยอด // Postcondition: จัด heap โดยเลื่อนตัวท้ายสุดขึ้นต้นไม้ เวลาที่แย่ที่สุดจะเป็น //O(log n) ส่วนเวลาเฉลี่ยจะเป็นค่าคงที่ protected void percolateUp() {   int parent; int child = size-1; Object temp; while (child>0) {   parent = (child-1)/2; if(compare(heap[parent],heap[child]) <=0) break; temp = heap[parent]; heap[parent] = heap[child]; heap[child] = temp; child = parent; } // while  } // method percolateUp ความสูงของต้นไม้ เพราะเวลาแย่สุดเกิดตอนที่ต้องเลื่อนจนถึงยอด หน้านี้คือโค้ดของ percolateUp จะเห็นว่าเป็นการลูปสลับที่ลูกกับพ่อไปเรื่อยๆ จะออกจากลูปก็เมื่อเจอพ่อซึ่ง น้อยกว่าหรือเท่ากับลูก ในที่นี้ใช้อาร์เรย์ แต่ง่ายเพราะว่าการหา index ของ parent นั้นตายตัว

Average time -percolateUp ก็ต้องให้ครึ่งหนึ่งของสมาชิกทั้งหมดมีค่ามากกว่าตัวใหม่ แต่ครึ่งที่มากก็เป็นใบแน่ๆ ฉะนั้นในการสลับที่ขึ้นเพื่อให้เป็น heap ก็ใช้ไม่กี่ขั้นตอน – constant time เวลาโดยเฉลี่ยคิดโดย สมมติว่าตัวที่เติมเข้าไปนี่มีค่าอยู่ตรงกลางของค่าทั้งหมด ดังนั้นต้องมีสมาชิกของอาร์เรย์ครึ่งหนึ่งที่มากกว่ามัน แต่เพราะเป็นต้นไม้ ดังนั้นครึ่งที่มากกว่านี้จะต้องอยู่ที่ใบแน่ๆ การ percolateUp จึงแค่ต้องเลื่อนให้พ้นระดับของใบเท่านั้น ดังนั้นจึงใช้แค่ 1หรือ 2 ครั้งเท่านั้น ดังนั้นจึงถือว่าเวลาคงที่

public Object getMin() {   if (size == 0) throw new NoSuchElementException(“Empty”); return heap[0];    } // method getMin

removeMin() ถ้าไม่ระวัง 4 6 5 8 ต่อไปเป็นการเอาของออกจากหัวคิว (root) ซึ่งจะซับซ้อนกว่าการเติมของ เพราะอาจทำให้เสียรูปของ complete binary tree ถ้าเสียรูปเมื่อไหร่ยุ่งแน่นอน เพราะ percolateUp ซึ่งใช้การคำนวณ index ของ complete binary tree ก็จะเสียไปด้วย เสียความเป็น complete binary tree งั้นทำไงดี??

//เอาท้ายอาร์เรย์ใส่ที่ root // Precondition: ต้องไม่เป็นคิวที่ว่าง ไม่งั้นจะ throw // NoSuchElementException // Postcondition: เอาออบเจ็คต์ตัวที่สำคัญที่สุดใน PriorityQueue นี้ // ออกจากคิวและรีเทิร์นออบเจ็คต์นี้เป็นคำตอบของเมธอดด้วย // เวลาที่แย่ที่สุดจะเป็น O(log n) ขึ้นกับ percolateDown หลักการคือสลับที่ root กับตัวท้ายสุดของอาร์เรย์ แล้วลดขนาดอาร์เรย์ (จริงๆไม่ได้ลดแต่ลดค่า size) เพื่อไม่ให้คนเห็น root ที่สลับไปท้ายอาร์เรย์ ยังไงขนาดของอาร์เรย์ที่เรานับเป็น heap ก็ต้องลดลงอยู่ดี เพราะเราเอาของออกนี่นา จากนั้นปรับอาร์เรย์ด้วยการเลื่อนตัวที่อยู่ที่ root ลงมายังตำแหน่งที่ถูกต้อง ซึ่งเราเรียกว่าการ percolateDown (ในที่นี้ให้เริ่มที่ index 0) สำหรับในบทที่ 12 ของหนังสือ Java Collection Framework จะเป็นเรื่อง heap sort ให้ทุกคนไปดูเพิ่มเติมได้ //เอาท้ายอาร์เรย์ใส่ที่ root //แล้วค่อยปรับเลื่อนตำแหน่ง

เมื่อ removeMin( ) ถูกเรียก 48 จะถูกสลับกับ 26   107 48 เมื่อ removeMin( ) ถูกเรียก 48 จะถูกสลับกับ 26 ส่วน size ก็จะถูกลดไปหนึ่ง จากนั้นก็จะ percolateDown 30 31 32 50 85 36

สลับ 48 กับลูกที่น้อยที่สุด PercolateDown คือ สลับ 48 กับลูกที่น้อยที่สุด ทำจนกว่าจะสลับไม่ได้ พอทำเสร็จเราก็จะได้ heap อย่าลืมว่า 48 อยู่ที่ root แล้วในตอนจะเริ่ม percolateDown

ถึงตรงนี้ก็จะได้ heap

protected void percolateDown(int start) { int parent = start; int child = 2*parent+1; Object temp; while (child<size) { if(child<size-1&&compare(heap[child],heap[child+1]) >0) child++; if (compare(heap[parent],heap[child]) <=0) break; temp = heap[child]; heap[child] = heap[parent]; heap[parent] = temp; parent = child; child= 2*parent+1; } // while  } // method percolateDown // Postcondition: จัด heap โดยเลื่อนตัวบนสุดลงต้นไม้ // เวลาที่แย่ที่สุด และเวลาเฉลี่ยจะเป็น O(log n) ทั้งคู่ ตอนแรกให้ childเป็นลูกซ้ายไว้ก่อน ห้ามเป็นตัวสุดท้าย เพราะจะไม่มีพี่น้องทางขวาให้เทียบ ถ้าลูกขวาน้อยกว่า ก็ปรับ child ให้เป็นลูกขวา หลักการก็เหมือนกับ percolateUp คือวนลูปสลับ child กับ parent เรื่อยๆ จนสลับไม่ได้ก็ออกจากลูป แต่จะต้องทำการเลือกลูกที่น้อยที่สุดมาเป็น child เสมอ ส่วนเรื่องเวลานั้น Worst case ก็คือเมื่อต้องเลื่อนถึงใบ ดังนั้นจึงเป็น log n ตามความสูงของ tree Average case สมมติว่าตัวที่เติมเข้าไปนี่มีค่าอยู่ตรงกลางของค่าทั้งหมด ดังนั้นต้องมีสมาชิกของอาร์เรย์ครึ่งหนึ่งที่มากกว่ามัน แต่เพราะเป็นต้นไม้ ดังนั้นครึ่งที่มากกว่านี้จะต้องอยู่ที่ใบแน่ๆ การ percolatDown จึงต้องเลื่อนให้เกือบระดับของใบ ดังนั้นจึงเกือบเป็น worst case เลยทีเดียว ดังนั้นเวลาจึงเป็น log n ด้วย

ลองทำดู

มาดูปัญหา จะ compress file โดยไม่ให้เสียข้อมูลไปเลยได้อย่างไร ให้ M เป็น ข้อมูลที่เราต้องการ compress สมมติว่ามันมีขนาด 100000 character โดยประกอบไปด้วย a ถึง e เท่านั้น ให้ E คือ version ที่ compress แล้ว

มาดูปัญหา (ต่อ) โดยปกติ 1 character จะใช้ 16 bit

ลองทำดู จำนวน bit ก็จะเหลือ 3 แสน ในเมื่อเรารู้ว่ามีแค่ไม่กี่จำนวน เราก็กำหนดโค้ดเองได้ดังในแผ่นใส จำนวน bit ก็จะเหลือ 3 แสน

ลดได้อีกไหม? จำนวน bit ก็จะน้อยกว่า3แสน แต่ 001010 อาจเป็นได้ทั้ง aababa หรือ cbda ก็ได้ อย่างนี้ถึงจะ compress ได้ แต่ก็ไม่มีทางตีความกลับคืนได้ เพราะความหมายไม่แน่นอน

แล้วจะให้ไม่มั่วได้ไง ใช้ prefix-free encoding โดยสร้าง binary tree กิ่งซ้ายถือเป็น 0 กิ่งขวาถือเป็น 1 ให้ character อยู่ที่ใบ ดูตัวอย่างในหน้าถัดไป

จะไม่มี character ที่อยู่ path เดียวกันจากรากแน่นอน b = 11 c = 00 d = 10 e = 011

ทำได้อีกแบบนะเนี่ย แล้วแบบไหนดี? ขึ้นกับว่า character นั้นเกิดบ่อยไหม bit เยอะไม่ควรเกิดบ่อย นี่ก็เป็น a –e เหมือนกัน แล้วแบบไหนจะดีกว่า แนวคิดก็คือ ตัวอักษรที่แทนด้วยจำนวน bit เยอะๆ ไม่ควรจะมีในข้อความของเรามาก

สร้าง Huffman tree ซึ่งก็คือ binary tree ของรหัสนี่แหละ แต่ว่าสร้างโดยดูจากความถี่ในการเกิดของแต่ละ character เพื่อให้ตัวอักษรที่มีความถี่มากในข้อความของเรา ใช้จำนวน bit น้อยที่สุด

สมมติมีความถี่แบบนี้ a มี 5000 b มี 10000 c มี 20000 d มี 31000 e มี 34000

เอาคู่ (character,ความถี่)ใส่Priority Q แต่ละ node ของ priority Q ก็มี left, right, parent ของมันเอง ตอนแรก คิวของเราจะเป็น (a:5000) (b: 10000) (c: 20000) (d: 31000) (e: 34000) ต่อไป สร้าง Huffman tree ล่ะ ต่อไปนี้เป็นการสร้าง huffman tree โดยใช้ priority queue มาช่วย

เอาสองตัวที่น้อยสุดออกจากคิว ตัวแรกเอามาเป็นกิ่งซ้าย ตัวที่สองมาเป็นกิ่งขวา ส่วนผลบวกของความถี่เราเอามาเป็นราก a b 15000 1 สำหรับ ( : 15000) ที่ใส่กลับเข้าไปนั้น มี tree ของมันเป็นสิ่งที่อยู่หน้าตัว : จากนั้นก็เอาผลใส่กลับลงใน priority Q ซึ่งจะกลายเป็น ( :15000) (c: 20000) (d: 31000) (e: 34000)

เอาสองตัวที่น้อยสุดออกจากคิว ทำเหมือนเดิม a b 15000 c 35000 1 เมื่อเอาต้นไม้นี้กลับเข้า priority Q ตัวคิวจะกลายเป็น (d: 31000) (e: 34000) ( : 35000)

เอาสองตัวที่น้อยสุดออกจากคิว ทำเหมือนเดิม d e 65000 1 นี่กลายเป็นอีก tree หนึ่งเลย เมื่อเอาต้นไม้นี้กลับเข้า priority Q ตัวคิวจะกลายเป็น ( : 35000) ( : 65000)

เอาสองตัวสุดท้ายออกจากคิว ทำเหมือนเดิม a b 15000 c 100000 1 d e 65000 35000 ขั้นนี้ถือว่าเสร็จแล้ว ได้ huffman tree ตามต้องการ priority Q จะกลายเป็น (: 100000)

ลองทำดู

Huffman เป็น Greedy Algorithm เราเลือกสิ่งที่ดีที่สุดในระบบย่อย ในที่นี้คือเอา min ออกจาก tree มีผลต่อระบบใหญ่ ทำให้ได้ optimal solution ไปด้วย ในที่นี้คือได้ prefix-free code ที่ดีสุด หน้านี้เป็นแค่ความหมายของ greedy algorithm

จะเปลี่ยน input ที่ได้ให้เป็นรหัส Huffman ก่อนอื่นดู Entry ก่อนละกัน Huffman class จะเปลี่ยน input ที่ได้ให้เป็นรหัส Huffman ก่อนอื่นดู Entry ก่อนละกัน ต่อไปลองมาดูโค้ดกัน

มีการกำหนดการเปรียบเทียบโดยใช้ความถี่ที่เกิดขึ้น Code นั้นคือตัวรหัสที่ของ entry หนึ่งๆนั่นเอง

ต่อไปก็ทำอาร์เรย์ของใบ เพื่อให้ access ได้เร็ว ดูให้ดี อย่าสับสน นี่คืออาร์เรย์ของใบนะ ไม่ใช่ของทั้งต้น อาร์เรย์ของใบเป็นคนละตัวกับ heap นะ

ตัวอย่างเช่น c มี index 99 ตัวข้างบน Entry ของ c คือ Entry ของ parent แต่มีแค่ reference ไป c เท่านั้นที่ถูกเก็บไว้ในอาร์เรย์ leafEntries เพราะตัว parent ไม่ใช่ใบ นี่คือหนึ่ง Entry

// Postcondition: ‘This’ Huffman object ถูก initialize // Postcondition: ไฟล์ ที่ถูกกำหนดโดย s ถูกจัดการเรียบร้อย // Postcondition: priority queue ถูกสร้างจากข้อมูลของ input //file โดยเวลา worst case = O(n) ผมจะให้ดูหลักๆเท่านั้น พวกเราสามารถไปลองทำเองได้

// Postcondition: สร้าง huffman tree เวลาworst case นั้นคงที่ // Postcondition: คำนวณ huffman code ของแต่ละตัวอักษร // เวลาworst case นั้นคงที่ // Postcondition: Huffman codesและข้อความที่แปลงแล้วถูกจัดเก็บใส่ไฟล์ // เวลาworst case นั้น = O (n).

Fields ใน Huffman class Gui priority queue file reader file writer input file name เก็บเพื่อให้เปิดไฟล์ได้ 2 ที คำนวณความถี่ encode ตัวข้อมูล boolean บอกว่ากำลังอ่านไฟล์ไหนอยู่

Method processInput สร้าง priority Q สร้าง Huffman tree สร้างตัวรหัส เก็บรหัสและตัวข้อความที่แปลงแล้วลงไฟล์

Method createPQ สร้าง leafEntries update freq field ของแต่ละ Entry หลังจากนั้นก็สร้าง priority queue จากการดู freq

Method createHuffmanTree Loop จนกว่า size ของ priority queue จะเหลือ 1 ทำเหมือนที่บอกไปแล้วเป๊ะๆ

Method calculateHuffmanCodes Loop บน leafEntries แต่ละ Entry นั้น มีตัวแปรซึ่งเป็น empty string ดู parent แล้ว เติมเลขที่กำกับ parent ลงไปข้างหน้าของ string เรื่อยๆ (ภาษาอังกฤษเรียก prepend) ไล่ดูไปจนถึง root

ตัวอย่างการสร้างรหัส ถ้ามีต้นไม้แบบซ้ายมือนี้ ดูหน้าถัดไป

รหัสของ f ลูปบันทึกย้อนจากใบต้นไม้ f มีค่า int เป็น 102 ดังนั้นเราจึง set ค่า leafEntries[102].code = “1010”;

Method saveToFile Loop บน leafEntries แล้วก็อ่าน input file อีกที ส่ง character กับ code ของมันไปที่ไฟล์ แล้วก็อ่าน input file อีกที รหัสของ input จะถูกสร้างขึ้น และ save ไว้ในไฟล์

ดู web เพิ่มได้

ถ้ามี output file มา ลองสร้าง Huffman tree ดูและบอกข้อความที่แท้จริง ตัวข้อความคือเลข 0101 ต่อกันยาวๆสองบรรทัดสุดท้าย

จบเรื่อง priority queue