جداول هش توضیح داده شده: از آدرس‌دهی مستقیم تا آدرس‌دهی باز

جداول هش جستجو، درج، و حذف زمان-ثابت مورد انتظار فراهم می‌کنند، که آن‌ها را به یکی از پراستفاده‌ترین ساختارهای داده در عمل تبدیل می‌کند. این راهنمای جامع ایده آدرس‌دهی مستقیم که هشینگ را انگیزه می‌دهد، نحوه مدیریت برخوردها از طریق زنجیره‌بندی، ویژگی‌های توابع هش خوب، آدرس‌دهی باز به‌عنوان یک جایگزین کارآمد از نظر حافظه، و ملاحظات عملی برای پیاده‌سازی‌های جدول هش دنیای واقعی را پوشش می‌دهد.

جدول هشتابع هشآدرس‌دهی باز

~7 min read · Updated Sep 7, 2026

ایده انگیزاننده: جداول آدرس‌دهی مستقیم

اگر جهان کلیدهای ممکن کوچک باشد، یک Direct-Address Table (جدول آدرس‌دهی مستقیم) یک راه‌حل ساده و بسیار سریع ارائه می‌دهد: یک آرایه که مستقیماً توسط خود کلید ایندکس می‌شود، جایی که هر اسلات عنصر با آن کلید را نگه می‌دارد (یا خالی است).

DIRECT-ADDRESS-SEARCH(T, k):
  T[k] را برگردان

DIRECT-ADDRESS-INSERT(T, x):
  T[x.key] = x

DIRECT-ADDRESS-DELETE(T, x):
  T[x.key] = NIL

هر عملیات در زمان O(1) اجرا می‌شود — به‌سرعتی که ممکن است. مشکل حافظه است: این نیازمند تخصیص یک آرایه به‌بزرگی کل جهان کلیدهای ممکن است، که هروقت جهان بزرگ باشد (مانند همه رشته‌های ممکن) یا وقتی فقط بخش کوچکی از کلیدهای ممکن واقعاً استفاده می‌شوند، غیرعملی است.

جداول هش: مبادله برخی تضمین‌ها برای صرفه‌جویی عظیم فضا

یک Hash Table (جدول هش) این مسئله فضا را با استفاده از یک آرایه بسیار کوچک‌تر به اندازه m، و یک Hash Function (تابع هش) h(k) که هر کلید از جهان بزرگ را به یک ایندکس در [0, m-1] نگاشت می‌کند، حل می‌کند. این به‌طور چشمگیری مصرف حافظه را کاهش می‌دهد، به قیمت یک مسئله جدید: از آنجا که بسیاری کلیدهای ممکن به همان مجموعه کوچک از ایندکس‌ها نگاشت می‌شوند، دو کلید متفاوت می‌توانند به همان اسلات هش شوند، رویدادی به نام Collision (برخورد).

مدیریت برخوردها با زنجیره‌بندی

Chaining (زنجیره‌بندی) برخوردها را با ذخیره همه عناصری که به همان اسلات هش می‌شوند در یک لیست پیوندی، که پیش‌تر در این مجموعه بحث شد، ریشه‌دار در آن اسلات، حل می‌کند.

CHAINED-HASH-INSERT(T, x):
  x را در ابتدای لیست T[h(x.key)] درج کن

CHAINED-HASH-SEARCH(T, k):
  یک عنصر با کلید k را در لیست T[h(k)] جستجو کن

CHAINED-HASH-DELETE(T, x):
  x را از لیست T[h(x.key)] حذف کن

درج همیشه O(1) است، چون صرفاً به ابتدای یک لیست اضافه می‌کند بدون نیاز به جستجو. جستجو و حذف به طول لیست بستگی دارند، که به این بستگی دارد چند کلید در آن اسلات خاص برخورد کرده‌اند.

تحلیل کارایی مورد انتظار

Load Factor (فاکتور بار) α = n/m را تعریف کن، میانگین تعداد عناصر به‌ازای هر اسلات، جایی که n تعداد عناصر ذخیره‌شده و m تعداد اسلات‌هاست. تحت فرض Simple Uniform Hashing (هشینگ یکنواخت ساده) — که هر کلید مشخص به‌طور برابر احتمال دارد به هر یک از m اسلات هش شود، مستقل از کلیدهای دیگر — طول مورد انتظار هر زنجیره دقیقاً α است.

زمان مورد انتظار برای یک جستجوی ناموفق: Θ(1 + α)
زمان مورد انتظار برای یک جستجوی موفق:     Θ(1 + α)

اگر m متناسب با n انتخاب شود (پس α = O(1))،
همه عملیات‌ها در زمان مورد انتظار Θ(1) اجرا می‌شوند

این نتیجه مستقیماً به تکنیک‌های تحلیل احتمالاتی که پیش‌تر در این مجموعه بحث شد متصل می‌شود: زمان جستجوی مورد انتظار هزینه O(1) محاسبه تابع هش را با طول زنجیره مورد انتظار ترکیب می‌کند، که تا زمانی که اندازه جدول متناسب با تعداد عناصر ذخیره‌شده رشد کند ثابت می‌ماند.

طراحی توابع هش خوب

کل تحلیل زمان-مورد-انتظار بالا به توزیع تقریباً یکنواخت کلیدها در سراسر اسلات‌ها توسط تابع هش بستگی دارد. یک تابع هش ضعیف‌انتخاب‌شده می‌تواند کارایی را به همان یک لیست پیوندی واحد تنزل دهد، Θ(n)، صرف‌نظر از تضمین‌های نظری، اگر بسیاری کلید به همان اسلات‌ها برخورد کنند.

روش تقسیم

ساده‌ترین رویکرد یک کلید را با استفاده از باقی‌مانده پس از تقسیم به یک اسلات نگاشت می‌کند:

h(k) = k mod m

این محاسبه‌اش سریع است اما به انتخاب m حساس است. انتخاب m به‌عنوان یک توان دو یک اشتباه رایج است، چون باعث می‌شود h(k) فقط به بیت‌های کم‌ارزش k بستگی داشته باشد، و بیت‌های با‌ارزش‌تر را کاملاً نادیده می‌گیرد، که می‌تواند توزیع ضعیفی برای الگوهای کلید خاص ایجاد کند. انتخاب m به‌عنوان یک عدد اول که خیلی نزدیک به یک توان دو نیست معمولاً توزیع‌های بهتر و یکنواخت‌تری در عمل تولید می‌کند.

روش ضرب

یک رویکرد جایگزین کلید را در یک ثابت A بین ۰ و ۱ ضرب می‌کند، بخش اعشاری را استخراج می‌کند، و آن را به اندازه جدول مقیاس می‌کند:

h(k) = ⌊m · (k · A mod 1)⌋

این روش این مزیت را دارد که مقدار خاص m برای کارایی‌اش حیاتی نیست، برخلاف روش تقسیم، که انعطاف‌پذیری بیشتری در انتخاب اندازه جدول (اغلب یک توان دو، برای راحتی پیاده‌سازی) می‌دهد.

آدرس‌دهی باز: یک جایگزین برای زنجیره‌بندی

Open Addressing (آدرس‌دهی باز) کاملاً از لیست‌های پیوندی اجتناب می‌کند، و همه عناصر را مستقیماً درون خود آرایه جدول هش ذخیره می‌کند. وقتی برخوردی رخ می‌دهد، الگوریتم به‌طور سیستماتیک اسلات‌های جایگزین را کاوش می‌کند تا یکی خالی پیدا شود، با دنبال کردن یک Probe Sequence (توالی کاوش) قطعی تعیین‌شده توسط کلید.

HASH-INSERT(T, k):
  i = 0
  تکرار کن:
      j = h(k, i)
      اگر T[j] == NIL:
          T[j] = k
          j را برگردان
      در غیر این صورت:
          i = i + 1
  تا i == m
  خطا "hash table overflow"

کاوش خطی

ساده‌ترین توالی کاوش، Linear Probing (کاوش خطی)، اسلات‌های پیاپی را پس از یک برخورد بررسی می‌کند:

h(k, i) = (h'(k) + i) mod m

این ساده است و کارایی کش خوبی دارد، که پیش‌تر در این مجموعه درباره سلسله‌مراتب حافظه بحث شد، چون مکان‌های حافظه پیاپی بررسی می‌شوند. با این حال، از Primary Clustering (خوشه‌بندی اولیه) رنج می‌برد: دنباله‌های طولانی از اسلات‌های اشغال‌شده پیاپی تمایل به شکل‌گیری دارند، چون هر کلیدی که به هرجایی درون یک خوشه هش شود آن را بیشتر گسترش می‌دهد، که برخوردهای آینده را به‌طور فزاینده محتمل‌تر می‌کند.

کاوش درجه‌دوم و هشینگ دوگانه

Quadratic Probing (کاوش درجه‌دوم) از یک تابع درجه‌دوم از شماره کاوش برای پخش بیشتر کاوش‌ها استفاده می‌کند:

h(k, i) = (h'(k) + c₁i + c₂i²) mod m

Double Hashing (هشینگ دوگانه) از یک تابع هش دوم و مستقل برای تعیین اندازه گام بین کاوش‌ها استفاده می‌کند، که معمولاً یکنواخت‌ترین توزیع را از میان سه رویکرد تولید می‌کند و در عمل نزدیک‌ترین به ایده‌آل نظری هشینگ یکنواخت است.

h(k, i) = (h₁(k) + i · h₂(k)) mod m

مسئله حذف در آدرس‌دهی باز

حذف در آدرس‌دهی باز ظریف‌تر از زنجیره‌بندی است. صرفاً علامت‌گذاری یک اسلات به‌عنوان خالی پس از حذف می‌تواند جستجوهای آینده را بشکند، چون توالی کاوش برای برخی کلید دیگر ممکن است به عبور از آن اسلات اکنون-خالی برای یافتن موقعیت واقعی‌اش جلوتر متکی باشد. راه‌حل استاندارد از یک نشانگر خاص DELETED متمایز از NIL استفاده می‌کند: جستجوها فراتر از یک نشانگر DELETED ادامه می‌یابند، اما درج‌ها می‌توانند آن اسلات را دوباره استفاده کنند.

ملاحظات عملی

پیاده‌سازی‌های جدول هش دنیای واقعی باید چند مسئله عملی فراتر از الگوریتم اصلی را مدیریت کنند. با رشد فاکتور بار به‌اندازه‌ای زیاد، کارایی تنزل می‌یابد، پس پیاده‌سازی‌ها معمولاً Rehash (دوباره‌هش) می‌کنند — یک جدول بزرگ‌تر تخصیص می‌دهند و هر عنصر را دوباره درج می‌کنند — وقتی فاکتور بار از یک آستانه، مانند ۰.۷۵، فراتر رود. این عملیات تغییر اندازه وقتی رخ می‌دهد گران است، اما به‌اندازه کافی به‌ندرت رخ می‌دهد (تقریباً دو برابر کردن اندازه جدول هر بار) که هزینه‌اش، وقتی میانگین‌گیری شود (یا Amortized (مستهلک)، مفهومی که بعداً در این مجموعه به‌طور عمیق بررسی می‌شود) در سراسر بسیاری درج، O(1) به‌ازای هر درج باقی می‌ماند.

چرا جداول هش این‌قدر گسترده استفاده می‌شوند

جداول هش زیربنای بسیاری از پیاده‌سازی‌های آرایه‌های انجمنی، دیکشنری، و مجموعه‌ای هستند که در تقریباً هر کتابخانه استاندارد زبان برنامه‌نویسی امروزی یافت می‌شوند. کارایی مورد انتظار O(1)شان برای جستجو، درج، و حذف، آن‌ها را به انتخاب پیش‌فرض هروقت جستجوی سریع مبتنی-بر-کلید مورد نیاز باشد تبدیل می‌کند، بهبود چشمگیری نسبت به O(log n) درخت‌های جستجوی متوازن، که بعداً در این مجموعه بحث می‌شوند، هروقت ترتیب کلیدها مورد نیاز نباشد و فقط جستجوی سریع اهمیت داشته باشد.

Written & researched by Dr. Shahin Siami

Related Articles

الگوریتم‌های نظریه اعداد: GCD، توان‌رسانی پیمانه‌ای، و RSA

رمزنگاری مدرن و بی‌شمار کاربرد الگوریتمی به تعداد کمی الگوریتم ظریف نظریه اعداد متکی است. این راهنمای جامع الگوریتم اقلیدس برای محاسبه بزرگ‌ترین مقسوم‌علیه مشترک، توان‌رسانی پیمانه‌ای سریع برای محاسبه کارآمد توان‌های بزرگ، و پایه ریاضی رمزنگاری RSA، یکی از پراستفاده‌ترین سیستم‌های رمزنگاری در جهان، را پوشش می‌دهد.

Continue

اصول هندسه محاسباتی: جهت، تقاطع پاره‌خط، و پوسته محدب

الگوریتم‌های هندسی مسائل شامل نقاط، خطوط، و اشکال را حل می‌کنند، که در گرافیک کامپیوتری، برنامه‌ریزی مسیر رباتیک، و سیستم‌های اطلاعات جغرافیایی ظاهر می‌شوند. این راهنمای جامع آزمون جهت مبتنی بر ضرب خارجی که زیربنای تقریباً هر الگوریتم هندسی است، تشخیص تقاطع پاره‌خط ساخته‌شده روی آن آزمون، و الگوریتم اسکن گراهام برای محاسبه پوسته محدب یک مجموعه از نقاط را پوشش می‌دهد.

Continue

الگوریتم‌های تطبیق رشته: جستجوی ساده، Rabin-Karp، و فراتر از آن

جستجو برای یک الگو درون یک متن بزرگ‌تر یکی از رایج‌ترین عملیات‌ها در محاسبات است، از ویرایشگرهای متن تا تحلیل توالی DNA. این راهنمای جامع الگوریتم تطبیق رشته ساده و بدترین‌حالت درجه‌دومش را پوشش می‌دهد، سپس استفاده ماهرانه الگوریتم Rabin-Karp از هشینگ برای دستیابی به کارایی سریع حالت‌میانگین را توضیح می‌دهد، شامل نحوه مدیریت درست برخوردهای هش.

Continue

الگوریتم‌های تقریبی: نزدیک‌شدن اثبات‌پذیر به بهینه برای مسائل سخت

وقتی یک مسئله NP-complete اثبات شود، یک راه‌حل کارآمد دقیق بعید است وجود داشته باشد، اما این به معنای رهاکردن کامل مسئله نیست. این راهنمای جامع الگوریتم‌های تقریبی را توضیح می‌دهد، که تضمین بهینه‌بودن را در ازای تضمین کارایی معامله می‌کنند، و مسائل پوشش رأس و فروشنده دوره‌گرد را به‌عنوان مثال‌های کلاسیک با نسبت‌های تقریبی اثبات‌پذیر پوشش می‌دهد.

Continue

NP-Completeness توضیح داده شده: P، NP، و چرا برخی مسائل در برابر راه‌حل‌های کارآمد مقاومت می‌کنند

برخی مسائل برای دهه‌ها در برابر هر تلاشی برای یک الگوریتم کارآمد مقاومت کرده‌اند، با این حال هیچ‌کس اثبات نکرده یک راه‌حل کارآمد غیرممکن است. این راهنمای جامع کلاس‌های P و NP، مفهوم تقلیل‌های زمان-چندجمله‌ای مورد استفاده برای مقایسه سختی مسئله، و چگونگی اینکه اثبات NP-complete بودن یک مسئله شواهد قوی، هرچند نه اثبات، فراهم می‌کند که هیچ الگوریتم کارآمدی وجود ندارد را توضیح می‌دهد.

Continue

جریان بیشینه: فورد-فالکرسون و قضیه برش-کمینه/جریان-بیشینه

مسائل جریان بیشینه بیشترین توان عملیاتی ممکن از میان یک شبکه با اتصالات محدود-به-ظرفیت را مدل‌سازی می‌کنند، از لوله‌های آب تا شبکه‌های داده. این راهنمای جامع شبکه‌های جریان را معرفی می‌کند، روش فورد-فالکرسون برای یافتن جریان بیشینه با استفاده از مسیرهای تقویتی را مرور می‌کند، و قضیه ظریف برش-کمینه/جریان-بیشینه که دو مسئله به‌ظاهر متفاوت را به یکی متصل می‌کند را توضیح می‌دهد.

Continue