portaldacalheta.pt
  • หลัก
  • การจัดการวิศวกรรม
  • Kpi และ Analytics
  • เทคโนโลยี
  • ว่องไว
วิทยาศาสตร์ข้อมูลและฐานข้อมูล

โครงสร้างข้อมูล Trie: อัญมณีที่ถูกละเลย



ตั้งแต่วันแรกในชีวิตของเรา โปรแกรมเมอร์ เราทุกคนจัดการกับโครงสร้างข้อมูล: อาร์เรย์รายการที่เชื่อมโยงต้นไม้ชุดสแต็กและคิวเป็นคู่หูประจำวันของเราและ โปรแกรมเมอร์ที่มีประสบการณ์ รู้ว่าเมื่อใดและทำไมจึงควรใช้ ในบทความนี้เราจะดูว่าโครงสร้างข้อมูลที่ถูกละเลยบ่อยครั้งนั้นเป็นอย่างไร ไตร ส่องแสงในโดเมนแอปพลิเคชันที่มีคุณสมบัติเฉพาะเช่นเกมคำศัพท์

เกมคำศัพท์เป็นตัวอย่าง Trie

สำหรับผู้เริ่มต้นลองพิจารณาปริศนาคำศัพท์ง่ายๆ: ค้นหาคำที่ถูกต้องทั้งหมดในกระดานตัวอักษร 4x4 เชื่อมต่อตัวอักษรที่อยู่ติดกันในแนวนอนแนวตั้งหรือแนวทแยงมุม ตัวอย่างเช่นในกระดานต่อไปนี้เราจะเห็นตัวอักษร 'W', 'A', 'I' และ 'T' เชื่อมต่อกันเพื่อสร้างคำว่า 'WAIT'



ปริศนาคำศัพท์ง่ายๆ



วิธีแก้ปัญหาที่ไร้เดียงสาในการค้นหาคำที่ถูกต้องทั้งหมดคือการสำรวจกระดานโดยเริ่มจากมุมบนซ้ายจากนั้นเลื่อนระดับความลึกก่อนไปยังลำดับที่ยาวขึ้นโดยเริ่มจากตัวอักษรตัวที่สองในแถวแรกและอื่น ๆ ในบอร์ด 4x4 ที่อนุญาตให้เคลื่อนที่ในแนวตั้งแนวนอนและแนวทแยงมีลำดับ 12029640 ซึ่งมีความยาวตั้งแต่หนึ่งถึงสิบหกตัวอักษร



ตอนนี้เป้าหมายของเราคือการค้นหาโครงสร้างข้อมูลที่ดีที่สุดเพื่อใช้เครื่องมือตรวจสอบคำที่ถูกต้องนั่นคือคำศัพท์ของเรา บางประเด็นที่ควรทราบ:

  • เราต้องการเพียงสำเนาเดียวของแต่ละคำนั่นคือคำศัพท์ของเราเป็นชุดจากมุมมองเชิงตรรกะ
  • เราจำเป็นต้องตอบคำถามต่อไปนี้สำหรับคำใด ๆ :
    • ลำดับอักขระปัจจุบันประกอบด้วยคำที่ถูกต้องหรือไม่
    • มีคำยาวกว่าที่ขึ้นต้นด้วยลำดับนี้หรือไม่? หากไม่เป็นเช่นนั้นเราสามารถละทิ้งการสำรวจเชิงลึกก่อนได้เนื่องจากการเจาะลึกลงไปจะไม่ได้คำที่ถูกต้อง

เพื่ออธิบายประเด็นที่สองให้พิจารณากระดานต่อไปนี้: ไม่มีประเด็นในการสำรวจการเคลื่อนไหวที่ตามมาเนื่องจากไม่มีคำในพจนานุกรมที่ขึ้นต้นด้วย“ ASF”



microsoft windows เขียนด้วยภาษาอะไร

ไม่มีอะไรเริ่มต้นด้วย asf

เราอยากให้โครงสร้างข้อมูลของเราตอบคำถามเหล่านี้โดยเร็วที่สุด ~ O (1) เวลาเข้าถึง (สำหรับการตรวจสอบลำดับ) จะเหมาะมาก!

เราสามารถกำหนดอินเทอร์เฟซคำศัพท์เช่นนี้ (ดู ที่นี่ สำหรับ GitHub repo):

public interface Vocabulary { boolean add(String word); boolean isPrefix(String prefix); boolean contains(String word); }

โครงสร้างข้อมูล Trie เทียบกับทางเลือก

การใช้งาน contains() วิธีการต้องการโครงสร้างข้อมูลสำรองที่ช่วยให้คุณค้นหาองค์ประกอบได้อย่างมีประสิทธิภาพในขณะที่ isPrefix() วิธีการทำให้เราต้องหา“ องค์ประกอบที่ยิ่งใหญ่กว่าถัดไป” นั่นคือเราต้องจัดเรียงคำศัพท์ไม่ทางใดก็ทางหนึ่ง

เราสามารถแยกชุดที่ใช้แฮชออกจากรายชื่อผู้สมัครของเราได้อย่างง่ายดาย: ในขณะที่โครงสร้างดังกล่าวจะทำให้เราตรวจสอบ contains() ได้ตลอดเวลา แต่มันจะทำงานได้ค่อนข้างแย่บน isPrefix() ในกรณีที่เลวร้ายที่สุดที่ต้องใช้ เราสแกนทั้งชุด

ด้วยเหตุผลตรงกันข้ามเรายังสามารถยกเว้นรายการที่เชื่อมโยงที่เรียงลำดับได้เนื่องจากต้องมีการสแกนรายการอย่างน้อยที่สุดจนถึงองค์ประกอบแรกที่มากกว่าหรือเท่ากับคำที่ค้นหาหรือคำนำหน้า

สองตัวเลือกที่ถูกต้องคือการใช้รายการสำรองอาร์เรย์ที่เรียงลำดับหรือต้นไม้ไบนารี
ในรายการสำรองอาร์เรย์ที่เรียงลำดับเราสามารถใช้การค้นหาแบบไบนารีเพื่อค้นหาลำดับปัจจุบันหากมีอยู่หรือองค์ประกอบที่ใหญ่กว่าถัดไปโดยเสียค่าใช้จ่าย O (log2 (n)) , ที่ไหน n คือจำนวนคำในพจนานุกรม

เราสามารถใช้คำศัพท์ที่ได้รับการสนับสนุนจากอาร์เรย์ซึ่งจะรักษาการเรียงลำดับของสิ่งที่ชอบอยู่เสมอ นี้ โดยใช้มาตรฐาน java.util.ArrayList และ java.util.Collections.binarySeach:

public class ListVocabulary implements Vocabulary { private List words = new ArrayList(); /** * Constructor that adds all the words and then sorts the underlying list */ public ListVocabulary(Collection words) { this.words.addAll(words); Collections.sort(this.words); } public boolean add(String word) { int pos = Collections.binarySearch(words, word); // pos > 0 means the word is already in the list. Insert only // if it's not there yet if (pos = 0) { // The prefix is a word. Check the following word, because we are looking // for words that are longer than the prefix if (pos +1 = 0; } }

หากเราตัดสินใจที่จะใช้ต้นไม้ไบนารีการใช้งานอาจสั้นลงและสวยงามมากขึ้น (อีกครั้งนี่คือไฟล์ เชื่อมโยงไปยังรหัส ):

public class TreeVocabulary extends TreeSet implements Vocabulary { public TreeVocabulary(Collection c) { super(c); } public boolean isPrefix(String prefix) { String nextWord = ceiling(prefix); if (nextWord == null) { return false; } if (nextWord.equals(prefix)) { Set tail = tailSet(nextWord, false); if (tail.isEmpty()) { return false; } nextWord = tail.iterator().next(); } return nextWord.startsWith(prefix); } /** * There is a mismatch between the parameter types of vocabulary and TreeSet, so * force call to the upper-class method */ public boolean contains(String word) { return super.contains(word); } }

ในทั้งสองกรณีเราสามารถคาดหวังได้ O (บันทึก n) ประสิทธิภาพสำหรับแต่ละวิธีการเข้าถึง (contains() และ isPrefix()) สำหรับความต้องการพื้นที่จำเป็นต้องใช้ทั้งการใช้งานอาร์เรย์ที่สำรองไว้และการใช้งานที่มีต้นไม้สำรอง O (n + M) โดยที่ n คือจำนวนคำในพจนานุกรมและ M คือขนาดไบต์ของพจนานุกรมนั่นคือผลรวมของความยาวของสตริงในพจนานุกรม

แอปพลิเคชัน Trie: เมื่อใดและทำไมจึงควรลองใช้

ประสิทธิภาพของลอการิทึมและหน่วยความจำเชิงเส้นก็ไม่เลว แต่ยังมีคุณสมบัติอีกสองสามประการของโดเมนแอปพลิเคชันของเราที่สามารถนำเราไปสู่ประสิทธิภาพที่ดีขึ้น:

  • เราสามารถสรุปได้อย่างปลอดภัยว่าคำทั้งหมดเป็นตัวพิมพ์เล็ก
  • เรายอมรับเฉพาะตัวอักษร a-z - ไม่มีเครื่องหมายวรรคตอนไม่มีขีดกลางไม่มีสำเนียง ฯลฯ
  • พจนานุกรมประกอบด้วยรูปแบบที่ผันแปรหลายรูปแบบ ได้แก่ พหูพจน์กริยาผันคำผสม (เช่น house -> housekeeper) ดังนั้น, หลายคำมีต้นกำเนิดเดียวกัน .
  • คำมีความยาว จำกัด ตัวอย่างเช่นหากเรากำลังทำงานบนกระดาน 4x4 คำทั้งหมดที่มีความยาวมากกว่า 16 ตัวอักษรสามารถละทิ้งได้

นี่คือจุดที่ trie (ออกเสียงว่า 'ลอง') เข้ามา แต่อะไรกันแน่ คือ สามเส้า? ความพยายามเป็นโครงสร้างข้อมูลที่ถูกละเลยซึ่งพบได้ในหนังสือ แต่ไม่ค่อยพบในไลบรารีมาตรฐาน

สำหรับแรงจูงใจก่อนอื่นให้พิจารณาเด็กโปสเตอร์ของ Computer Science นั่นคือต้นไม้ไบนารี ตอนนี้เมื่อเราวิเคราะห์ประสิทธิภาพของต้นไม้ไบนารีและพูดว่าการดำเนินการ x คือ O (บันทึก (n)) เรากำลังพูดถึง log base 2 อยู่ตลอดเวลา แต่ถ้าเป็น binary tree เราใช้ ternary tree แทนซึ่งทุกโหนดจะมีลูกสามลูก (หรือ fan-out ใน 3 ตัว) จากนั้นเราจะพูดถึง log base 3 (นั่นคือการปรับปรุงประสิทธิภาพแม้ว่าจะเป็นเพียงปัจจัยคงที่เท่านั้น) โดยพื้นฐานแล้วต้นไม้ของเราจะกว้างขึ้น แต่สั้นลงและเราสามารถทำการค้นหาได้น้อยลงเนื่องจากเราไม่จำเป็นต้องลดระดับลงมากนัก ลึกมาก.

ก้าวไปอีกขั้นจะเกิดอะไรขึ้นถ้าเรามีต้นไม้ที่มี fan-out เท่ากับจำนวนค่าที่เป็นไปได้ของประเภทข้อมูลของเรา?

นี่คือแรงจูงใจเบื้องหลัง Trie และอย่างที่คุณอาจเดาได้ว่า Trie เป็นต้นไม้จริง ๆ แล้วก็คือต้นไม้ Trie นั่นเอง!

แต่ตรงกันข้ามกับไบนารีทรีส่วนใหญ่ที่คุณจะใช้ในการจัดเรียงสตริงซึ่งจะเก็บคำทั้งหมดไว้ในโหนดของพวกเขาแต่ละโหนดของไตรมีอักขระเพียงตัวเดียว (ไม่ใช่อย่างนั้นอย่างที่เราจะเห็นในไม่ช้า) และ มีพัดลมออกสูงสุดเท่ากับความยาวของตัวอักษร ในกรณีของเราความยาวของตัวอักษรคือ 26 ดังนั้นโหนดของ trie จึงมีพัดลมออกสูงสุด 26 และในขณะที่ต้นไม้ไบนารีที่สมดุลมี log2 (n) ความลึกความลึกสูงสุดของสามเท่ากับความยาวสูงสุดของคำ! (อีกครั้งกว้างกว่า แต่สั้นกว่า)

อัตรารายชั่วโมงของผู้รับเหมาเทียบกับเงินเดือน

ภายใน Trie คำที่มีก้านเดียวกัน (คำนำหน้า) จะแบ่งพื้นที่หน่วยความจำที่ตรงกับก้าน

จุดพักความละเอียดการออกแบบเว็บที่ตอบสนอง

เพื่อให้เห็นภาพความแตกต่างลองพิจารณาพจนานุกรมขนาดเล็กที่ประกอบด้วยคำห้าคำ สมมติว่าตัวอักษรภาษากรีกระบุตัวชี้และสังเกตว่าในสาม อักขระสีแดงหมายถึงโหนดที่มีคำที่ถูกต้อง .

การแสดงภาพสามมิติ

การใช้งาน Java Trie

ดังที่เราทราบในแผนภูมิตัวชี้ไปยังองค์ประกอบลูกโดยปกติจะใช้ตัวแปรซ้ายและขวาเนื่องจากค่าพัดลมออกสูงสุดถูกกำหนดไว้ที่สองตัว

ในการจัดทำดัชนีสามตัวอักษร 26 ตัวแต่ละโหนดมีลูกที่เป็นไปได้ 26 ลูกดังนั้นจึงมี 26 ตัวชี้ที่เป็นไปได้ แต่ละโหนดมีอาร์เรย์ของต้นไม้ย่อย 26 (พอยน์เตอร์ถึง) ซึ่งแต่ละค่าอาจเป็นโมฆะ (ถ้าไม่มีลูกนั้น) หรือโหนดอื่น

ถ้าอย่างนั้นเราจะค้นหาคำใน Trie ได้อย่างไร? นี่คือวิธีการที่กำหนด String s จะระบุโหนดที่ตรงกับตัวอักษรสุดท้ายของคำหากมีอยู่ในแผนภูมิ:

public LowercaseTrieVocabulary getNode(String s) { LowercaseTrieVocabulary node = this; for (int i = 0; i

LOWERCASE.getIndex(s.charAt(i)) เพียงแค่คืนตำแหน่งของไฟล์ ith อักขระในตัวอักษร บนโหนดที่ส่งคืนคุณสมบัติบูลีน node บ่งชี้ว่าโหนดตรงกับตัวอักษรสุดท้ายของคำนั่นคือตัวอักษรที่ทำเครื่องหมายด้วยสีแดงในตัวอย่างก่อนหน้านี้ เนื่องจากแต่ละโหนดมีตัวนับจำนวนลูกถ้าตัวนับนี้เป็นค่าบวกจะมีคำที่ยาวกว่าในพจนานุกรมที่มีสตริงปัจจุบันเป็นคำนำหน้า หมายเหตุ: โหนดไม่จำเป็นต้องอ้างอิงถึงอักขระที่สอดคล้องกันเนื่องจากมีนัยอยู่ในตำแหน่งในสาม

การวิเคราะห์ประสิทธิภาพ

สิ่งที่ทำให้โครงสร้าง trie ทำงานได้ดีในสถานการณ์เหล่านี้คือค่าใช้จ่ายในการค้นหาคำหรือคำนำหน้าจะคงที่และขึ้นอยู่กับจำนวนอักขระในคำเท่านั้นและไม่ขึ้นอยู่กับขนาดของคำศัพท์

ในโดเมนเฉพาะของเราเนื่องจากเรามีสตริงที่มีอักขระไม่เกิน 16 ตัวจึงจำเป็นต้องมี 16 ขั้นตอนในการค้นหาคำที่อยู่ในคำศัพท์ในขณะที่คำตอบเชิงลบใด ๆ เช่นคำหรือคำนำหน้าไม่ได้อยู่ในไตร มากที่สุด 16 ขั้นตอนเช่นกัน! เมื่อพิจารณาว่าก่อนหน้านี้เราได้เพิกเฉยต่อความยาวของสตริงเมื่อคำนวณความซับซ้อนของเวลาทำงานสำหรับทั้งรายการที่เรียงลำดับอาร์เรย์สำรองและแผนภูมิซึ่งซ่อนอยู่ในการเปรียบเทียบสตริงเราสามารถเพิกเฉยได้ที่นี่และระบุอย่างปลอดภัยว่าการค้นหาเสร็จสิ้น ใน O (1) เวลา.

พิจารณาความต้องการพื้นที่ (และจำไว้ว่าเราได้ระบุด้วย ม ไบต์ขนาดของพจนานุกรม) สามมิติสามารถมีได้ ม โหนดในกรณีที่เลวร้ายที่สุดหากไม่มีสองสตริงที่ใช้คำนำหน้าร่วมกัน แต่เนื่องจากเราสังเกตเห็นว่าในพจนานุกรมมีความซ้ำซ้อนสูงจึงมีการบีบอัดข้อมูลจำนวนมาก พจนานุกรมภาษาอังกฤษที่ใช้ในโค้ดตัวอย่างคือ 935,017 ไบต์และต้องการ 250,264 โหนดโดยมีอัตราส่วนการบีบอัดประมาณ 73%

อย่างไรก็ตามแม้จะเป็นเช่นนี้แม้แต่ Trie ที่บีบอัดก็มักต้องการหน่วยความจำมากกว่าต้นไม้หรืออาร์เรย์ เนื่องจากแต่ละโหนดอย่างน้อย 26 x sizeof(pointer) ไบต์เป็นสิ่งที่จำเป็นบวกค่าโสหุ้ยสำหรับอ็อบเจ็กต์และแอ็ตทริบิวต์เพิ่มเติม บนเครื่อง 64 บิตแต่ละโหนดต้องการมากกว่า 200 ไบต์ในขณะที่อักขระสตริงต้องการไบต์เดียวหรือสองตัวหากเราพิจารณาสตริง UTF

ที่เกี่ยวข้อง: ข้อผิดพลาดทั่วไป 10 อันดับแรกที่นักพัฒนา Java ทำ: บทช่วยสอนสำหรับผู้เริ่มต้นใช้งาน Java

การทดลองและการทดสอบประสิทธิภาพ

แล้วประสิทธิภาพล่ะ? การใช้คำศัพท์ได้รับการทดสอบในสองสถานการณ์ที่แตกต่างกัน: ตรวจสอบสตริงสุ่ม 20,000,000 สตริงและค้นหาคำทั้งหมดใน 15,000 กระดานที่สร้างแบบสุ่มจากรายการคำเดียวกัน

โครงสร้างข้อมูลสี่แบบได้รับการวิเคราะห์: รายการที่เรียงลำดับอาร์เรย์สำรองต้นไม้ไบนารีทรีที่อธิบายไว้ข้างต้นและสามส่วนโดยใช้อาร์เรย์ของไบต์ที่สอดคล้องกับดัชนีตัวอักษรของอักขระ (การเพิ่มประสิทธิภาพเล็กน้อยและใช้งานได้ง่าย) นี่คือผลลัพธ์ในหน่วยมิลลิวินาที:

ผลการดำเนินงาน

จำนวนการเคลื่อนไหวโดยเฉลี่ยในการแก้ปัญหากระดานคือ 2,188 สำหรับการย้ายแต่ละครั้งจะมีการค้นหาคำและการค้นหาคำนำหน้าเช่นสำหรับการตรวจสอบกระดานทั้งหมดมีการค้นหาคำมากกว่า 32 ล้านคำและการค้นหาคำนำหน้า 32M หมายเหตุ: สิ่งเหล่านี้สามารถทำได้ในขั้นตอนเดียวฉันแยกมันออกจากกันเพื่อความชัดเจนในการจัดแสดง การกระชับเข้าด้วยกันในขั้นตอนเดียวจะช่วยลดเวลาในการแก้ปัญหาของบอร์ดได้เกือบครึ่งและอาจจะชอบ Trie มากยิ่งขึ้น

ดังที่เห็นได้ข้างต้นการค้นหาคำจะทำงานได้ดีขึ้นกับ trie แม้ว่าจะใช้สตริงและยังเร็วกว่าเมื่อใช้ดัชนีตัวอักษรโดยที่การค้นหาแบบหลังจะเร็วกว่าต้นไม้ไบนารีมาตรฐานถึงสองเท่า ความแตกต่างในการแก้บอร์ดนั้นชัดเจนยิ่งขึ้นด้วยโซลูชันไตร - อักษร - ดัชนีที่เร็วกว่ารายการและแผนภูมิมากกว่าสี่เท่า

ห่อ

Trie เป็นโครงสร้างข้อมูลเฉพาะทางที่ต้องการหน่วยความจำมากกว่าต้นไม้และรายการ อย่างไรก็ตามเมื่อใช้ลักษณะเฉพาะของโดเมนเช่นตัวอักษรที่ จำกัด และมีความซ้ำซ้อนสูงในส่วนแรกของสตริงจะมีประสิทธิภาพมากในการจัดการกับการเพิ่มประสิทธิภาพการทำงาน

อ้างอิง

คำอธิบายอย่างละเอียดเกี่ยวกับการพยายามและตัวอักษรมีอยู่ในบทที่ 5 ของหนังสือ 'Algorithms, 4th edition' ของ Robert Sedgewick เว็บไซต์คู่หูที่ Princeton มีไฟล์ รหัสสำหรับการติดตั้ง ของ Alphabet และ TrieST ที่กว้างขวางกว่าตัวอย่างของฉัน

javascript typeerror ไม่ใช่ฟังก์ชัน

นอกจากนี้ยังสามารถดูคำอธิบายของ Trie และการใช้งานสำหรับภาษาต่างๆได้ใน Wikipedia และคุณสามารถดูสิ่งนี้ ทรัพยากรสามมหาวิทยาลัยบอสตัน เช่นกัน.

ที่เกี่ยวข้อง: เข็มในกองหญ้า: การสอนอัลกอริทึมการค้นหาข้อความขนาดใหญ่ที่ดี

ทำความเข้าใจพื้นฐาน

Trie คืออะไร?

Trie คือโครงสร้างข้อมูลตามลำดับซึ่งเป็นโครงสร้างการค้นหาชนิดหนึ่งที่ใช้ในการจัดเก็บโครงสร้างข้อมูลที่เชื่อมโยงกัน เรียกอีกอย่างว่าต้นไม้ Radix หรือต้นไม้คำนำหน้า

Trie บีบอัดคืออะไร?

Trie ที่บีบอัดเป็นโครงสร้างข้อมูล Trie โดยมีกฎเพิ่มเติมว่าทุกโหนดต้องมีลูกสองคนขึ้นไป

การออกเสียง trie ใดถูกต้อง?

trie ออกเสียงว่า 'try' แม้ว่าชื่อ trie จะมาจาก 'retrieval'

โครงสร้างข้อมูล Trie เหมาะสำหรับอะไร?

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

คู่มือนักพัฒนา iOS: จาก Objective-C ถึง Swift

มือถือ

คู่มือนักพัฒนา iOS: จาก Objective-C ถึง Swift
ข้อมูลเบื้องต้นเกี่ยวกับการเทรดแบบ Deep Learning ในกองทุนป้องกันความเสี่ยง

ข้อมูลเบื้องต้นเกี่ยวกับการเทรดแบบ Deep Learning ในกองทุนป้องกันความเสี่ยง

แบ็คเอนด์

โพสต์ยอดนิยม
Nvidia Shield - สิ่งที่แตกต่างบนคอนโซลเกม Android
Nvidia Shield - สิ่งที่แตกต่างบนคอนโซลเกม Android
แผ่นโกงการจัดการโครงการ
แผ่นโกงการจัดการโครงการ
เริ่มต้นใช้งาน Microservices: บทช่วยสอน Dropwizard
เริ่มต้นใช้งาน Microservices: บทช่วยสอน Dropwizard
การแยกการเรียกเก็บเงิน: เรื่องของการเพิ่มประสิทธิภาพ API ภายใน GraphQL
การแยกการเรียกเก็บเงิน: เรื่องของการเพิ่มประสิทธิภาพ API ภายใน GraphQL
กรณีศึกษา: การใช้ ApeeScape เพื่อม้วนปลาใหญ่
กรณีศึกษา: การใช้ ApeeScape เพื่อม้วนปลาใหญ่
 
การประมาณต้นทุนซอฟต์แวร์ในการจัดการโครงการแบบ Agile
การประมาณต้นทุนซอฟต์แวร์ในการจัดการโครงการแบบ Agile
แชทล่ม - เมื่อ Chatbot ล้มเหลว
แชทล่ม - เมื่อ Chatbot ล้มเหลว
ที่ปรึกษาการระดมทุนกับนายหน้า - ตัวแทนจำหน่าย
ที่ปรึกษาการระดมทุนกับนายหน้า - ตัวแทนจำหน่าย
ทำให้ Web Front-end เชื่อถือได้ด้วย Elm
ทำให้ Web Front-end เชื่อถือได้ด้วย Elm
คู่มือสำหรับนักลงทุนเกี่ยวกับน้ำมันปาล์ม
คู่มือสำหรับนักลงทุนเกี่ยวกับน้ำมันปาล์ม
โพสต์ยอดนิยม
  • ผลตอบแทนจากการลงทุน การวัดผลการปฏิบัติงาน:
  • วิธีการทำการวิเคราะห์ตามการได้มา
  • llc s corp . คืออะไร
  • สุดยอดโปรแกรม c++
  • ฉันเป็น llc แบบไหน
  • ภาวะพร้อมกัน (วิทยาการคอมพิวเตอร์)
หมวดหมู่
  • การจัดการวิศวกรรม
  • Kpi และ Analytics
  • เทคโนโลยี
  • ว่องไว
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt