درخت جستجوی دودویی
در علوم رایانه، درخت جستجوی دودویی (به انگلیسی: Binary search tree یا به اختصار BST) که گاهی اوقات درخت دودویی مرتب هم نامیده میشود، یک ساختار داده است و نوعی درخت دودویی است.
درخت جستجوی دودویی | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
گونه | درخت | ||||||||||||||||||||
زمان اجرای الگوریتم بر پایه نماد O بزرگ | |||||||||||||||||||||
|
یک درخت باینری نوعی ساختارداده برای ذخیره کردن داده است مانند عددهاییی که مرتب شده هستند. درخت جست و جوی دودویی این امکان را فراهم میکند که جست و جوی یک عدد، اضافه کردن آن و حذف کردن آن با سرعت بیشتری انجام شود. همچنین این امکان را فراهم میکند که مجموعه های پویا و جداول جست و جو را پیاده سازی کنیم.
ترتیب گره ها در دخت جست و جوی دودویی به صورتی است که در هر مقایسه نیمی از درخت باقی مانده بررسی نمیشود. بنابراین زمان جست و جوی درخت متناسب با لگاریتم تعداد عددهای ذخیره شده در درخت است. این زمان بسیار کمتر از زمان جست و جوی خطی در یک آرایه مرتب نشده است اما کندتر از انجام عملیات درهم سازی است.
انواع مختلفی از درخت های جست و جوی دودویی مورد بررسی و مطالعه قرار گرفته اند.
تعریف
درخت جستجوی دودویی، نوعی درخت دودویی است که ممکن است تهی باشد، اگر تهی نباشد، دارای خصوصیات زیر است:
- از تعدادی گره تشکیل شده که هر گره دارای یک کلید است. کلیدها منحصربهفرد هستند و در درخت کلید تکراری وجود ندارد.
- تمام کلیدهایی که در زیردرخت سمت چپ واقع شدهاند، کوچکتر از کلید گره ریشه هستند.
- تمام کلیدهایی که در زیردرخت سمت راست واقع شدهاند، بزرگتر از کلید گره ریشه هستند.
- زیردرخت سمت راست و زیردرخت سمت چپ خود درختان جستجوی دودویی هستند.
این ویژگی تضمین میکند که پیمایش میانترتیب یک درخت جستجوی دودویی، کلیدها را به ترتیب صعودی نمایش میدهد.
در ادامه برخی اصطلاحهای مهم مربوط به درخت مورد اشاره قرار گرفته است:
- مسیر (path) – منظور از مسیر در یک درخت، یک توالی از گرهها در راستای یالهای درخت است.
- ریشه (Root) – گرهی که در بخش فوقانی درخت قرار دارد، ریشه نامیده میشود. هر درختی تنها یک ریشه دارد و از گره ریشه تنها یک مسیر به هر گره دیگر وجود دارد.
- والد (parent) – هر گرهی به جز گره ریشه یالی به سمت بالا به یک گره دارد، که والد آن گره نامیده میشود.
- فرزند (child) – گرهی که زیر یک گره مفروض قرار دارد و از طریق یالی به سمت پایین به آن وصل شده است، گره فرزند آن نامیده میشود.
- برگ (leaf) – گرهی که هیچ فرزندی دارد، برگ نامیده میشود.
- زیردرخت (Subtree) – به مجموعه فرزندان یک گره، زیردرخت گفته میشود.
- بازدید (Visiting) – منظور از بازدید، بررسی ارزش یک گره است، هنگامی که کنترل روی گره قرار دارد.
- پیمایش (Traversing) – منظور از پیمایش، عبور از میان گرههای یک درخت با ترتیب خاص است.
- سطوح (Levels) – سطح یک گره نماینده نسل آن فرزند است. اگر گره ریشه در سطح 0 باشد، در این صورت فرزندان آن در سطح 1، نوههای آن در سطح 2 و همینطور تا آخر … قرار دارند.
- کلیدها (Keys) – کلید نماینده ارزش یک گره بر مبنای نوع عملیات جستجویی است که قرار است روی گره انجام شود.
عملیات اصلی بر روی یک درخت جستجوی دودویی به زمانی متناسب با ارتفاع درخت احتیاج دارد. برای یک درخت دودویی کامل با n گره چنین عملیاتی در بدترین حالت در زمان (o(logn اجرا میشود. بنابراین اگر درخت یک زنجیر خطی با n گره باشد همین عملیات در زمان بدترین حالت (o(n اجرا میشود. امید ریاضی ارتفاع یک درخت جستجوی دودویی که به تصادف ساخته شده است برابر با (o(logn است.بنابراین عملیات اصلی مجموعه پویا بر روی چنین درختی در حالت میانگین به زمان (o(logn احتیاج دارد.
در عمل، همیشه نمیتوانیم تضمین کنیم که درخت های جستجوی دودویی به تصادف ساخته میشوند. اما میتوانیم انواع مختلفی از درخت های جستجوی دودویی را با کارایی بدترین حالتی که بر روی عملیات اصلی به خوبی تضمین شده باشد طراحی کرد.
بعد از ارائه ویژگی های اصلی درخت های جستجوی دودویی در بخش های بعد نشان میدهیم چگونه برای چاپ مقادیر یک درخت جستجوی دودویی به صورت مرتب، عملیات روی آن را انجام میدهیم.
عملیات اصلی بر روی درخت جستجوی دودویی
معمولاً عملیات زیر بر روی یک درخت جستجوی دودویی تعریف میشود:
- ایجاد یک درخت جستجوی خالی
- آزمایش خالی بودن درخت
- درج کردن یک کلید جدید در درخت، بدون برهم خوردن خاصیت درخت
- جستجو کردن و یافتن یک کلید خاص در درخت
- حذف کردن یک کلید از درخت، با حفظ خاصیت درخت
- پیمایش درخت جستجوی دودویی، بهطوری تمام گرهها دقیقاً یک بار مورد دسترسی قرار گیرند.
جستجو
انواع جست و جو و پیمایش در درخت جست و جوی دودویی
- جستجو (Search) – به دنبال یک عنصر در درخت میگردد.
- پیمایش پیشترتیبی (Preorder Traversal) – یک درخت را به روش پیشترتیبی پیمایش میکند
- پیمایش میانترتیبی (Inorder Traversal) – یک درخت را به روش میانترتیبی پیمایش میکند.
- پیمایش پسترتیبی (Postorder Traversal) – یک درخت را به روش پسترتیبی پیمایش میکند.
پیمایش میانترتیبی
در این روش از پیمایش، زیردرخت چپ ابتدا بازدید میشود، سپس از گره ریشه بازدید میشود و در نهایت زیردرخت راست پیمایش میشود. همواره باید به خاطر داشته باشید که هر گره میتواند خود نماینده یک زیردرخت باشد.
اگر یک درخت باینری به صوت میانترتیبی پیموده شود، مقادیر کلیدهای ذخیره شده در خروجی به ترتیب نزولی نمایش داده میشود
الگوریتم
- تا زمانی که همه گرهها بازدید شوند:
- گام 1- زیردرخت چپ را به صورت بازگشتی پیمایش کن
- گام 2 – گره ریشه را بازدید کن
- گام 3 – زیردرخت سمت راست را به صورت بازگشتی پیمایش کن.
پیمایش پیشترتیبی
در این روش پیمایش، گره ریشه ابتدا مورد بازدید قرار میگیرد، سپس زیردرخت چپ و در نهایت زیردرخت راست پیموده میشوند.
الگوریتم
- تا زمانی که همه گرهها پیمایش شوند:
- گام 1 – از گره ریشه بازدید کن.
- گام 2 – زیردرخت چپ را به صورت بازگشتی پیمایش کن.
- گام 3 – زیردرخت راست را به صورت بازگشتی پیمایش کن.
پیمایش پسترتیبی
در این روش پیمایش، جنان که از روی نام مشخص است گره ریشه در آخر بازدید میشود. بنابراین ابتدا زیردرخت سمت چپ و بعد از آن زیردرخت راست و در نهایت گره ریشه بازدید میشوند.
الگوریتم
- تا زمانی که همه گرهها پیمایش شوند:
- گام 1 – زیردرخت چپ را به صورت بازگشتی پیمایش کن.
- گام 2 – زیردرخت راست را به صورت بازگشتی پیمایش کن.
- گام 3 – از گره ریشه بازدید کن.
برای پیدا کردن گرهی با یک کلید خاص مانند key در درخت، ابتدا باید از ریشه درخت شروع کنیم. اگر ریشه تهی باشد، درخت فاقد هر عنصری بوده و جستجو ناموفق خواهد بود. در غیر این صورت، key را با مقدار گره ریشه مقایسه میکنیم، اگر برابر بودند، جستجو موفق است و گره ریشه همان گره مورد نظر است. در غیر این صورت، دو حالت پیش خواهد آمد:
- key از گره ریشه کوچکتر است. در این حالت، هیچ عنصری در زیردرخت سمت راست وجود ندارد که مقدار کلید آن برابر با key باشد؛ بنابراین جستجو باید در زیردرخت سمت چپ ادامه یابد.
- 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 است (چپ یا راست). در مورد حذف یک گره از درخت دودویی، سه حالت مختلف پیش میآید:
- i یک گره برگ است.
- i یک فرزند دارد.
- i دو فرزند دارد.
در حالت اول، کافیست اشارهگر مناسبی (چپ یا راست) از P(i) را برابر تهی قرار دهیم، عمل حذف خاتمه مییابد. در حالت دوم که i یک فرزند دارد، ابتدا گره i را حذف میکنیم، سپس فرزند i یا همان C(i) را جانشین گره i میکنیم.
در حالتی که i دو فرزند دارد، باید بزرگترین عنصر در زیردرخت چپ یا کوچکترین عنصر در زیردرخت راست را پیدا کرده و سپس آن را با گره i جایگزین و تعویض میکنیم. سپس ما این فرایند را با حذف کردن این عنصر جایگزین از زیردرختی که از آن گرفته شدهاست، دنبال میکنیم. به عبارتی دیگر، اگر بخواهیم گره N را از درخت حذف کنیم (که این گره دو فرزند دارد)، به جای اینکه خود گره N را پاک کنیم، یا گره بعد از N در پیمایش میانوندی یا گره قبلی N در پیمایش میانوندی را انتخاب میکنیم و آن گره را R مینامیم. سپس مقادیر N و R را تعویض کرده و سپس R را از درخت حذف میکنیم.
تابع transplant
این تابع در تابع حذف گره از درخت استفاده می شود ، این تابع دوگره 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 هایی که داریم را به نحوی تغییر دهیم که الگوریتم جست وجوی بهینه ای داشته باشیم از روش زیر مب توان استفاده کرد:
گره هایی که بیشترین استفاده را دارند و دسترسی به آن ها مورد نیاز است را نزدیک ریشه قرار میدهیم . همچین گره هایی که کمترین به آن دسترسی داریم را نزدیک برگ ها قرار می دهیم . با این روش بهینه هزینه جیت و جوی کمتری خواهیم داشت.
جستارهای وابسته
پیوند به بیرون
- معرفی، درج و حذف از درخت جستجوی دودویی
پانویس
- ↑ گره برگ به گرهی میگویند که درجه آن صفر باشد. به عبارتی دیگر، هیچ فرزندی نداشته باشد.
منابع
- هورویتز، الیس (۱۳۷۷). ساختمان دادهها به زبان سی. انتشارات خراسان.
- سایت فرادرس
- جعفرنژاد قمی، عینالله (۱۳۹۰). ساختمان دادهها در C++. انتشارات علوم رایانه.
- https://en.wikipedia.org/wiki/Binary_search_tree