algorithm - دراسة - تحليل السوق pdf



ما هو أسرع ، بحث تجزئة أو بحث ثنائي؟ (10)

أتساءل لماذا لم يذكر أحد تجزئة مثالية .

إنه ملائم فقط إذا كانت مجموعة البيانات الخاصة بك ثابتة لفترة طويلة ، ولكن ما تفعله هو تحليل البيانات وإنشاء دالة هاش مثالية تضمن عدم حدوث أي تصادم.

أنيق للغاية ، إذا كانت مجموعة البيانات الخاصة بك ثابتة ووقت حساب الدالة صغير مقارنة بوقت تشغيل التطبيق.

عند إعطاء مجموعة ثابتة من الكائنات (ثابتة بمعنى أنه بمجرد تحميلها نادراً ما يتم تغييرها) التي تحتاج إلى عمليات بحث متزامنة متكررة مع الأداء الأمثل ، أيهما أفضل ، أو HashMap أو مصفوفة مع بحث ثنائي باستخدام بعض المقارنة المخصصة؟

هل الإجابة هي وظيفة كائن أو نوع هيكل؟ تجزئة و / أو أداء وظيفة متساوية؟ التفرد تجزئة؟ حجم القائمة؟ حجم Hashset / حجم مجموعة؟

يمكن أن يكون حجم المجموعة التي أراها في أي مكان من 500 كيلو إلى 10 أمتار - في حال كانت المعلومات مفيدة.

بينما أبحث عن إجابة C # ، أعتقد أن الإجابة الرياضية الحقيقية لا تكمن في اللغة ، لذلك لا أذكر ذلك. ومع ذلك ، إذا كانت هناك أشياء محددة يجب أن تكون على علم بها ، فإن هذه المعلومات مطلوبة.


Answer #1

أظن بقوة أنه في مشكلة مجموعة من حجم ~ 1M ، سيكون التجزئة أسرع.

فقط للأرقام:

يتطلب البحث الثنائي 20 مقارنة (2 ^ 20 == 1M)

يتطلب البحث في تجزئة حساب تجزئة واحدًا على مفتاح البحث ، وربما عددًا من المقارنات بعد ذلك لحل التعارضات المحتملة

تحرير: الأرقام:

    for (int i = 0; i < 1000 * 1000; i++) {
        c.GetHashCode();
    }
    for (int i = 0; i < 1000 * 1000; i++) {
        for (int j = 0; j < 20; j++)
            c.CompareTo(d);
    }

times: c = "abcde"، d = "rwerij" hashcode: 0.0012 seconds. قارن: 2.4 ثانية.

إخلاء المسئولية: في الواقع ، قد يكون المقارنة بين البحث عن التجزئة وعكس البحث الثنائي أفضل من هذا الاختبار غير الملائم تمامًا. لست متأكدًا حتى من أن GetHashCode سيحصل على مذكرات تحت غطاء المحرك


Answer #2

إجابات بوبي وبيل وكوربن خاطئة. O (1) ليس أبطأ من O (log n) ل n / ثابت n:

السجل (n) ثابت ، لذلك يعتمد على الوقت الثابت.

ولعملية تجزئة بطيئة ، سمعت من md5؟

من المحتمل أن تلامس خوارزمية تجزئة السلسلة الافتراضية جميع الأحرف ، ويمكن أن تكون أبطأ 100 مرة من متوسط ​​المقارنة لمفاتيح السلسلة الطويلة. ذهبت هناك وقمت بذلك.

قد تكون قادرًا (جزئيًا) على استخدام radix. إذا تمكنت من التقسيم في 256 قالبًا بالحجم نفسه تقريبًا ، فستبحث في البحث الثنائي من 2 إلى 40 كيلو بايت. من المرجح أن يوفر أداء أفضل بكثير.

[عدل] الكثير من الناس يصوتون على ما لا يفهمونه.

تشتمل مقارنات مجموعات البحث الثنائي التي تم فرزها على خاصية مثيرة جدًا: حيث تصبح أبطأ كلما اقتربوا من الهدف. أولا سوف كسر على الشخصية الأولى ، في النهاية فقط على الماضي. افتراض وقت ثابت بالنسبة لهم غير صحيح.


Answer #3

إذا كانت مجموعة الكائنات لديك ثابتة ولا تتغير ، يمكنك استخدام تجزئة مثالية للحصول على أداء O (1) مضمون. لقد رأيت gperf المذكورة عدة مرات ، على الرغم من أنني لم أجد فرصة لاستخدامها بنفسي.


Answer #4

الجواب يعتمد. لنفترض أن عدد العناصر 'n' كبير جدًا. إذا كنت جيدًا في كتابة وظيفة تجزئة أفضل والتي تقل حدة التصادم ، فإن التجزئة هي الأفضل. لاحظ أنه يتم تنفيذ وظيفة التجزئة مرة واحدة فقط عند البحث وأنها توجه إلى الدلو المقابل. لذلك فهي ليست كبيرة في حالة ارتفاع n.
مشكلة في Hashtable: ولكن المشكلة في جداول التجزئة هي إذا كانت وظيفة التجزئة ليست جيدة (يحدث المزيد من الاصطدامات) ، فإن عملية البحث ليست O (1). يميل إلى O (n) لأن البحث في مجموعة بيانات هو بحث خطي. يمكن أن يكون أسوأ من شجرة ثنائية. مشكلة في الشجرة الثنائية: في الشجرة الثنائية ، إذا لم تكن الشجرة متوازنة ، فإنها تميل أيضًا إلى O (n). على سبيل المثال ، إذا قمت بإدراج 1،2،3،4،5 إلى شجرة ثنائية من المحتمل أن تكون قائمة. لذا ، إذا كان بإمكانك رؤية منهجية تجزئة جيدة ، فاستخدم علامة التصنيف إذا لم يكن الأمر كذلك ، فمن الأفضل استخدام شجرة ثنائية.


Answer #5

بالنسبة للمجموعات الصغيرة جدًا ، سيكون الفرق مهملاً. عند الحد الأدنى لنطاقك (500 كيلو عنصر) ، ستبدأ في معرفة الفرق إذا كنت تقوم بالكثير من عمليات البحث. سوف يكون البحث الثنائي هو O (log n) ، في حين سيكون بحث التجزئة هو O (1) ، amortized . هذا ليس هو نفسه ثابتًا حقًا ، ولكن لا يزال يتعين عليك الحصول على وظيفة تجزئة رهيبة جدًا للحصول على أداء أسوأ من البحث الثنائي.

(عندما أقول "التجزئة الرهيبة" ، أعني شيئًا مثل:

hashCode()
{
    return 0;
}

نعم ، إنه سريع للغاية ، لكنه يتسبب في أن تصبح خريطة التجزئة الخاصة بك قائمة مرتبطة.)

كتب ialiashkevich بعض C # رمز باستخدام مصفوفة وقاموس لمقارنة الطريقتين ، لكنه استخدم قيم طويلة للمفاتيح. أردت اختبار شيئًا من شأنه فعليًا تنفيذ وظيفة هاش أثناء البحث ، لذا قمت بتعديل هذا الرمز. لقد غيرت ذلك لاستخدام قيم السلسلة ، وقمت بإعادة إنشاء أقسام الملء والبحث في الطرق الخاصة بهم بحيث يسهل رؤيتها في منشئ ملفات التعريف. غادرت أيضًا في الرمز الذي استخدم قيمًا طويلة ، فقط كنقطة مقارنة. أخيرًا ، لقد تخلصت من وظيفة البحث الثنائي المخصصة واستخدمت الدالة الواحدة في فئة Array .

هذا هو الرمز:

class Program
{
    private const long capacity = 10_000_000;

    private static void Main(string[] args)
    {
        testLongValues();
        Console.WriteLine();
        testStringValues();

        Console.ReadLine();
    }

    private static void testStringValues()
    {
        Dictionary<String, String> dict = new Dictionary<String, String>();
        String[] arr = new String[capacity];
        Stopwatch stopwatch = new Stopwatch();

        Console.WriteLine("" + capacity + " String values...");

        stopwatch.Start();

        populateStringArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Populate String Array:      " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        populateStringDictionary(dict, arr);

        stopwatch.Stop();
        Console.WriteLine("Populate String Dictionary: " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        Array.Sort(arr);

        stopwatch.Stop();
        Console.WriteLine("Sort String Array:          " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchStringDictionary(dict, arr);

        stopwatch.Stop();
        Console.WriteLine("Search String Dictionary:   " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchStringArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Search String Array:        " + stopwatch.ElapsedMilliseconds);

    }

    /* Populate an array with random values. */
    private static void populateStringArray(String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            arr[i] = generateRandomString(20) + i; // concatenate i to guarantee uniqueness
        }
    }

    /* Populate a dictionary with values from an array. */
    private static void populateStringDictionary(Dictionary<String, String> dict, String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            dict.Add(arr[i], arr[i]);
        }
    }

    /* Search a Dictionary for each value in an array. */
    private static void searchStringDictionary(Dictionary<String, String> dict, String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            String value = dict[arr[i]];
        }
    }

    /* Do a binary search for each value in an array. */
    private static void searchStringArray(String[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            int index = Array.BinarySearch(arr, arr[i]);
        }
    }

    private static void testLongValues()
    {
        Dictionary<long, long> dict = new Dictionary<long, long>(Int16.MaxValue);
        long[] arr = new long[capacity];
        Stopwatch stopwatch = new Stopwatch();

        Console.WriteLine("" + capacity + " Long values...");

        stopwatch.Start();

        populateLongDictionary(dict);

        stopwatch.Stop();
        Console.WriteLine("Populate Long Dictionary: " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        populateLongArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Populate Long Array:      " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchLongDictionary(dict);

        stopwatch.Stop();
        Console.WriteLine("Search Long Dictionary:   " + stopwatch.ElapsedMilliseconds);

        stopwatch.Reset();
        stopwatch.Start();

        searchLongArray(arr);

        stopwatch.Stop();
        Console.WriteLine("Search Long Array:        " + stopwatch.ElapsedMilliseconds);
    }

    /* Populate an array with long values. */
    private static void populateLongArray(long[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            arr[i] = i;
        }
    }

    /* Populate a dictionary with long key/value pairs. */
    private static void populateLongDictionary(Dictionary<long, long> dict)
    {
        for (long i = 0; i < capacity; i++)
        {
            dict.Add(i, i);
        }
    }

    /* Search a Dictionary for each value in a range. */
    private static void searchLongDictionary(Dictionary<long, long> dict)
    {
        for (long i = 0; i < capacity; i++)
        {
            long value = dict[i];
        }
    }

    /* Do a binary search for each value in an array. */
    private static void searchLongArray(long[] arr)
    {
        for (long i = 0; i < capacity; i++)
        {
            int index = Array.BinarySearch(arr, arr[i]);
        }
    }

    /**
     * Generate a random string of a given length.
     * Implementation from https://.com/a/1344258/1288
     */
    private static String generateRandomString(int length)
    {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var stringChars = new char[length];
        var random = new Random();

        for (int i = 0; i < stringChars.Length; i++)
        {
            stringChars[i] = chars[random.Next(chars.Length)];
        }

        return new String(stringChars);
    }
}

فيما يلي النتائج مع عدة أحجام مختلفة من المجموعات. (الأوقات بالمللي ثانية.)

500000 قيم طويلة ...
Populate Long Dictionary: 26
Populate Long Array: 2
بحث قاموس طويل: 9
ابحث عن مصفوفة طويلة: 80

500000 قيم السلسلة ...
Populate String Array: 1237
Populate String Dictionary: 46
ترتيب سلسلة المصفوفة: 1755
البحث سلسلة القاموس: 27
البحث في سلسلة المصفوفة: 1569

1000000 قيم طويلة ...
Populate Long Dictionary: 58
Populate Long Array: 5
بحث قاموس طويل: 23
ابحث عن مصفوفة طويلة: 136

1000000 سلسلة القيم ...
Populate String Array: 2070
Populate String Dictionary: 121
ترتيب سلسلة المصفوفة: 3579
البحث سلسلة القاموس: 58
البحث في سلسلة المصفوفة: 3267

3000000 قيم طويلة ...
Populate Long Dictionary: 207
Populate Long Array: 14
بحث قاموس طويل: 75
ابحث عن مصفوفة طويلة: 435

3000000 سلسلة قيم ...
Populate String Array: 5553
Populate String Dictionary: 449
ترتيب سلسلة المصفوفة: 11695
Search String Dictionary: 194
ابحث في مجموعة المصفوفات: 10594

10000000 قيم طويلة ...
Populate Long Dictionary: 521
Populate Long Array: 47
بحث قاموس طويل: 202
ابحث عن مصفوفة طويلة: 1181

10000000 قيم السلسلة ...
Populate String Array: 18119
Populate String Dictionary: 1088
ترتيب سلسلة المصفوفة: 28174
Search String Dictionary: 747
ابحث في سلسلة المصفوفة: 26503

وعلى سبيل المقارنة ، وهنا ناتج التعريف من أجل تشغيل الماضي للبرنامج (10 مليون السجلات وعمليات البحث). أبرزت المهام ذات الصلة. وهم يوافقون بشكل كبير على مقاييس توقيت ساعة التوقيت المذكورة أعلاه.

يمكنك أن ترى أن عمليات البحث في القاموس أسرع بكثير من البحث الثنائي ، و (كما هو متوقع) يكون الفرق أكثر وضوحًا كلما كبر حجم المجموعة. لذا ، إذا كان لديك وظيفة تجزئة معقولة (سريعة إلى حد ما مع القليل من التصادمات) ، فيجب أن يفحص بحث التجزئة بحثًا ثنائيًا عن مجموعات في هذا النطاق.


Answer #6

تكون الزوائد أسرع بشكل نموذجي ، على الرغم من أن عمليات البحث الثنائية لها خصائص أسوأ حالة. عادةً ما يكون الوصول إلى تجزئة عملية حسابية للحصول على قيمة تجزئة لتحديد أي "دلو" سوف يكون السجل فيه ، وبالتالي فإن الأداء يعتمد بشكل عام على كيفية توزيع السجلات بالتساوي ، والطريقة المستخدمة للبحث في الدلو. ستؤدي وظيفة التجزئة السيئة (التي تترك بعض الدلاء مع مجموعة كبيرة من السجلات) مع البحث الخطي عبر الجرافات إلى البحث البطيء. (من ناحية ثالثة ، إذا كنت تقرأ قرصًا بدلاً من الذاكرة ، فمن المحتمل أن تكون دلاء التجزئة متجاورة بينما تضمن الشجرة الثنائية إلى حد كبير الوصول غير المحلي.)

إذا كنت تريد بسرعة بشكل عام ، فاستخدم التجزئة. إذا كنت تريد بالفعل أداء مضمونًا مضمونًا ، فقد تذهب مع الشجرة الثنائية.


Answer #7

حسنا ، سأحاول أن أكون قصيرة.

ج # إجابة قصيرة:

اختبار نهجين مختلفين.

يمنحك .NET الأدوات اللازمة لتغيير النهج الخاص بك باستخدام سطر من التعليمات البرمجية. وإلا استخدم System.Collections.Generic.Dictionary وتأكد من تهيئته برقم كبير كقدرة مبدئية أو سوف تمر بقية حياتك بإدخال عناصر بسبب الوظيفة GC للقيام بجمع صفائف الجرافة القديمة.

اجابه أطول:

يحتوي hashtable ALMOST مرات البحث المستمر والوصول إلى عنصر في جدول تجزئة في العالم الحقيقي لا يتطلب فقط حساب تجزئة.

للوصول إلى عنصر ما ، ستعمل بطاقة التعريف الخاصة بك على شيء من هذا القبيل:

  • احصل على تجزئة المفتاح
  • الحصول على رقم الجرافة لهذه التجزئة (عادة ما تبدو وظيفة الخريطة مثل هذه المجموعة = hash٪ bucketsCount)
  • اجتياز سلسلة العناصر (وهي في الأساس عبارة عن قائمة بالعناصر التي تشترك في نفس مجموعة البيانات ، حيث تستخدم معظم هذه الجداول طريقة التعامل مع تصادمات الجرافة / التجزئة) التي تبدأ عند هذا الدلو وتُقارن كل مفتاح مع العنصر الذي تحاول إضافته / حذف / تحديث / التحقق من وجودها.

تعتمد أوقات البحث على كيفية "جيد" (كيف يكون الناتج هو الناتج) وبسرعة هي دالة هاش ، وعدد الدلاء الذي تستخدمه ، ومدى سرعة مقارنة المفاتيح ، وليس دائمًا أفضل حل.

تفسير أفضل وأعمق: http://en.wikipedia.org/wiki/Hash_table


Answer #8

يستخدم القاموس / Hashtable المزيد من الذاكرة ويستغرق المزيد من الوقت لملء مقارنة الصفيف. ولكن يتم البحث بشكل أسرع من خلال قاموس بدلاً من البحث الثنائي داخل الصفيف.

فيما يلي الأرقام لـ 10 مليون عنصر Int64 للبحث و الملء. بالإضافة إلى رمز عينة يمكنك تشغيله بنفسك.

ذاكرة القاموس: 462،836

صفيف الذاكرة: 88،376

قاموس شعبية: 402

Populate Array: 23

بحث القاموس: 176

صفيف البحث: 680

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace BinaryVsDictionary
{
    internal class Program
    {
        private const long Capacity = 10000000;

        private static readonly Dictionary<long, long> Dict = new Dictionary<long, long>(Int16.MaxValue);
        private static readonly long[] Arr = new long[Capacity];

        private static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                Dict.Add(i, i);
            }

            stopwatch.Stop();

            Console.WriteLine("Populate Dictionary: " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                Arr[i] = i;
            }

            stopwatch.Stop();

            Console.WriteLine("Populate Array:      " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                long value = Dict[i];
//                Console.WriteLine(value + " : " + RandomNumbers[i]);
            }

            stopwatch.Stop();

            Console.WriteLine("Search Dictionary:   " + stopwatch.ElapsedMilliseconds);

            stopwatch.Reset();

            stopwatch.Start();

            for (long i = 0; i < Capacity; i++)
            {
                long value = BinarySearch(Arr, 0, Capacity, i);
//                Console.WriteLine(value + " : " + RandomNumbers[i]);
            }

            stopwatch.Stop();

            Console.WriteLine("Search Array:        " + stopwatch.ElapsedMilliseconds);

            Console.ReadLine();
        }

        private static long BinarySearch(long[] arr, long low, long hi, long value)
        {
            while (low <= hi)
            {
                long median = low + ((hi - low) >> 1);

                if (arr[median] == value)
                {
                    return median;
                }

                if (arr[median] < value)
                {
                    low = median + 1;
                }
                else
                {
                    hi = median - 1;
                }
            }

            return ~low;
        }
    }
}

Answer #9

يعتمد ذلك على كيفية التعامل مع التكرارات لجداول التجزئة (على كل حال). إذا كنت ترغب في السماح بتكرار مفتاح التجزئة (لا توجد دالة هاش مثالية) ، فستبقى O (1) للبحث عن المفتاح الأساسي ولكن البحث عن القيمة "الصحيحة" قد يكون مكلفًا. الجواب هو ، نظريا في معظم الوقت ، تجزئته أسرع. YMMV حسب البيانات التي تضعها هناك ...





binary-search