درخت جستجوی دودویی | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
گونه | درخت | ||||||||||||||||||||
زمان اجرای الگوریتم بر پایه نماد O بزرگ | |||||||||||||||||||||
|
در علوم رایانه، درخت جستجوی دودویی (به انگلیسی: Binary search tree یا به اختصار BST) که گاهی درخت دودویی مرتب هم نامیده میشود، یک ساختار داده است و نوعی درخت دودویی است.
یک درخت باینری نوعی ساختار داده برای ذخیره کردن دادهاست مانند عددهاییی که مرتب شده هستند. درخت جست و جوی دودویی این امکان را فراهم میکند که جست و جوی یک عدد، اضافه کردن آن و حذف کردن آن با سرعت بیشتری انجام شود. همچنین این امکان را فراهم میکند که مجموعههای پویا و جداول جست و جو را پیادهسازی کنیم.
ترتیب گرهها در درخت جست و جوی دودویی به صورتی است که در هر مقایسه نیمی از درخت باقی مانده بررسی نمیشود؛ بنابراین زمان جست و جوی درخت متناسب با لگاریتم تعداد عددهای ذخیره شده در درخت است. این زمان بسیار کمتر از زمان جست و جوی خطی در یک آرایه مرتب نشدهاست اما کندتر از انجام عملیات درهم سازی است.
انواع مختلفی از درختهای جست و جوی دودویی مورد بررسی و مطالعه قرار گرفتهاند.
درخت جستجوی دودویی، نوعی درخت دودویی است که ممکن است تهی باشد، اگر تهی نباشد، دارای خصوصیات زیر است:
این ویژگی تضمین میکند که پیمایش میانترتیب یک درخت جستجوی دودویی، کلیدها را به ترتیب صعودی نمایش میدهد.
در ادامه برخی اصطلاحهای مهم مربوط به درخت مورد اشاره قرار گرفتهاست:
عملیات اصلی بر روی یک درخت جستجوی دودویی به زمانی متناسب با ارتفاع درخت احتیاج دارد. برای یک درخت دودویی کامل با n گره چنین عملیاتی در بدترین حالت در زمان (o(logn اجرا میشود؛ بنابراین اگر درخت یک زنجیر خطی با n گره باشد همین عملیات در زمان بدترین حالت (o(n اجرا میشود. امید ریاضی ارتفاع یک درخت جستجوی دودویی که به تصادف ساخته شدهاست برابر با (o(logn است؛ بنابراین عملیات اصلی مجموعه پویا بر روی چنین درختی در حالت میانگین به زمان (o(logn احتیاج دارد.
در عمل، همیشه نمیتوانیم تضمین کنیم که درختهای جستجوی دودویی به تصادف ساخته میشوند. اما میتوانیم انواع مختلفی از درختهای جستجوی دودویی را با کارایی بدترین حالتی که بر روی عملیات اصلی به خوبی تضمین شده باشد طراحی کرد.
بعد از ارائه ویژگیهای اصلی درختهای جستجوی دودویی در بخشهای بعد نشان میدهیم چگونه برای چاپ مقادیر یک درخت جستجوی دودویی به صورت مرتب، عملیات روی آن را انجام میدهیم.
معمولاً عملیات زیر بر روی یک درخت جستجوی دودویی تعریف میشود:
در این روش از پیمایش، زیردرخت چپ ابتدا بازدید میشود، سپس از گره ریشه بازدید میشود و در نهایت زیردرخت راست پیمایش میشود. همواره باید به خاطر داشته باشید که هر گره میتواند خود نماینده یک زیردرخت باشد.
اگر یک درخت باینری به صوت میانترتیبی پیموده شود، مقادیر کلیدهای ذخیره شده در خروجی به ترتیب نزولی نمایش داده میشود
در این روش پیمایش، گره ریشه ابتدا مورد بازدید قرار میگیرد، سپس زیردرخت چپ و در نهایت زیردرخت راست پیموده میشوند.
در این روش پیمایش، جنان که از روی نام مشخص است گره ریشه در آخر بازدید میشود؛ بنابراین ابتدا زیردرخت سمت چپ و بعد از آن زیردرخت راست و در نهایت گره ریشه بازدید میشوند.
برای پیدا کردن گرهی با یک کلید خاص مانند key در درخت، ابتدا باید از ریشه درخت شروع کنیم. اگر ریشه تهی باشد، درخت فاقد هر عنصری بوده و جستجو ناموفق خواهد بود. در غیر این صورت، key را با مقدار گره ریشه مقایسه میکنیم، اگر برابر بودند، جستجو موفق است و گره ریشه همان گره مورد نظر است. در غیر این صورت، دو حالت پیش خواهد آمد:
سپس بسته به حالت یک و دو، زیردرخت سمت چپ یا زیردرخت سمت راست را به روش بازگشتی و با استفاده از الگوریتم بالا جستجو میکنیم. عمل جستجو در درخت جستجوی دودویی، از مرتبه O(h) است که در آن h ارتفاع درخت است. چرا که حداکثر باید به میزان عمق درخت، به طرف پایین حرکت کنیم.
node_t search(node_t *root, T value)
{
if(NULL != root)
{
if(root->key == value)
return root;
else if(value <root->key)
search(root->left, value)
else
search(root->right, value)
}
return NULL;
}
برای درج کردن یک گره جدید در درخت جستجوی دودویی، باید گره را طوری درج کرد که خاصیت درخت برهم نخورد و درخت همچنان یک درخت جستجوی دودویی باقی بماند. برای حفظ خاصیت اول (منحصربهفرد بودن کلیدها)، باید ابتدا درخت را جستجو کنیم تا مطمئن شویم که عنصر قبلاً در درخت درج نشدهاست. پس از اینکه مطمئن شدیم کلید مورد نظر از قبل در درخت وجود ندارد، میتوانیم کلید را در درخت درج کنیم. اگر درخت تهی بود، کافیست گره جدید را به عنوان گره ریشه درج کنیم تا عمل درج خاتمه یابد. اگر درخت خالی نبود، کلید جدید را با ریشه مقایسه میکنیم، که دو حالت جستجو پیش میآید:
عمل درج در درخت جستجوی دودویی، از مرتبه O(h) است.
typedef int T
void insert(node_t **tree, T data)
{
node_t *temp;
if(!(*tree))
{
temp = malloc(sizeof(node_t));
temp->left = temp->right = NULL;
temp->key = data;
*tree = temp;
return;
}
if(data <(*tree)->key)
insert(&(*tree)->left, data);
if(data> (*tree)->key)
insert(&(*tree)->right, data);
}
فرض کنید میخواهیم گره i را از درخت جستجوی دودویی حذف کنیم. بهطوری که P(i) نشاندهنده والد گره i و C(i) نشاندهنده فرزند گره i است (چپ یا راست). در مورد حذف یک گره از درخت دودویی، سه حالت مختلف پیش میآید:
در حالت اول، کافیست اشارهگر مناسبی (چپ یا راست) از P(i) را برابر تهی قرار دهیم، عمل حذف خاتمه مییابد. در حالت دوم که i یک فرزند دارد، ابتدا گره i را حذف میکنیم، سپس فرزند i یا همان C(i) را جانشین گره i میکنیم.
در حالتی که i دو فرزند دارد، باید بزرگترین عنصر در زیردرخت چپ یا کوچکترین عنصر در زیردرخت راست را پیدا کرده و سپس آن را با گره i جایگزین و تعویض میکنیم. سپس ما این فرایند را با حذف کردن این عنصر جایگزین از زیردرختی که از آن گرفته شدهاست، دنبال میکنیم. به عبارتی دیگر، اگر بخواهیم گره N را از درخت حذف کنیم (که این گره دو فرزند دارد)، به جای اینکه خود گره N را پاک کنیم، یا گره بعد از N در پیمایش میانوندی یا گره قبلی N در پیمایش میانوندی را انتخاب میکنیم و آن گره را R مینامیم. سپس مقادیر N و R را تعویض کرده و سپس R را از درخت حذف میکنیم.
این تابع در تابع حذف گره از درخت استفاده میشود، این تابع دوگره u و v را به عنوان ورودی میگیرد و اگر u ریشه درخت باشد آنگاه v را ریشه درخت قرار میدهد، اگر u ریشه دره نباشد پدر u را به v اشاره میدهد، و اگر v نال نبود پدر v را پدر u قرار میدهد، در واقع این تابع v را جای u قرار میدهد و از نظر رابطه با گرهٔ پدر کارها را درست میکند ولی کاری به جابهجا کردن فرزندان u , v ندارد.
پیمایش درخت بدین معنی است که به همه عناصر به ترتیب خاصی دسترسی داشته باشیم، بدون اینکه گرهی جا بیفتد یا اینکه گرهی دو بار در دسترس قرار گیرد. درخت جستجوی دودویی یک درخت دودویی است که میتوان آن را به روشهای معمول پیمایش کرد. اما از آنجا که نحوه قرار گرفتن عناصر در یک درخت جستجوی دودویی، تضمین میکند در هنگام پیمایش درخت به صورت میانوندی، عناصر به ترتیب صعودی در دسترس قرار گیرند، ما این نوع پیمایش را در ادامه بررسی میکنیم. درخت جستجوی دودویی به روش پیشترتیب و پسترتیب هم میتوان پیمایش کرد، اما این نوع پیمایشها در یک درخت جستجوی دودویی بیهوده هستند. پیمایش میانوندی بدین شکل تعریف میشود:
الگوریتم بالا بازگشتی است.
void inorder(node_t *root)
{
if(!root)
{
if(root->left)
inorder(root->left)
process(root->key);
if(root->right)
inorder(root->right);
}
درخت جستجوی دودویی گونههای مختلفی دارد. درخت ایویال و درخت سرخ-سیاه از جمله درخت جستجوی دودویی خود-متوازن هستند. درخت اسپلی نوعی درخت دودویی است که عناصری که مکرراً مورد استفاده قرار میگیرند را به صورت خودکار به ریشه درخت نزدیک میکند تا دسترسی به آنها راحتتر شود. در یک تریپ، (tree heap) هر گره یک اولویت دارد که این اولویت به شکل تصادفی به گره اختصاص مییابد و گره والد اولویت بیشتری نسبت به فرزندش دارد. درختان تانگو نوعی درخت جستجوی دودویی هستند که برای جستجوهای بسیار سریع مورد استفاده قرار میگیرند.
درختان جست و جوی دودویی هستند که برای کاهش حافظهٔ استفاده شده. بهطور گسترده برای پایگاه دادههای درون حافظه ای استفاده میشود.
درخت تباهیده درختی است که در آن هر والد فقط یک فرزند دارد. در واقع میتوان گفت هر والد فقط یک گره دارد. ای درخت متوازن نیست و در بدترین حالت مانند یک لیست پیوندی عمل میکند. اگر با درج عنصر جدید در درخت تابع افزودن گره، ارتفاع درخت را متوازن نکند، میتوان به سادگی از بک درخت تباهیده کنک گرفت بهطوری که آن را با دادههای از قبل مرتب شده پر کرد. در واقع به این معنی که این درخت به صورت یک لیست پیوندی عمل خواهد
کرد.
D. A. Heger در سال ۲۰۰۴ روشی برای مقایسه عملکرد درختهای جست و جوی دودویی ارائه داد. در این مقایسه تریپ را به عنوان بهترین عملکرد در حالت میانگین معرفی کرد در حالی که در همین مقایسه درخت سرخ-سیاه کمترین اختلاف را بین بدترین حالت و بهترین حالتش دارد هرچند بهطور میانگین تریپ بهتر ازآن عمل میکند.
برای این که bstهایی که داریم را به نحوی تغییر دهیم که الگوریتم جست وجوی بهینه ای داشته باشیم از روش زیر مب توان استفاده کرد:
گرههایی که بیشترین استفاده را دارند و دسترسی به آنها مورد نیاز است را نزدیک ریشه قرار میدهیم. همچین گرههایی که کمترین به آن دسترسی داریم را نزدیک برگها قرار میدهیم. با این روش بهینه هزینه جیت و جوی کمتری خواهیم داشت.