راهنمای پشته ها در پایتون

راهنمای پشته ها در پایتون

معرفی

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

بنابراین، پشته چیست؟ در هسته خود، پشته یک ساختار داده خطی است که از آن پیروی می کند LIFO اصل (Last In First Out). آن را به عنوان یک پشته بشقاب در یک کافه تریا در نظر بگیرید. شما فقط صفحه ای را می گیرید که در بالا قرار دارد، و هنگام قرار دادن یک صفحه جدید، به بالای پشته می رود.

آخرین عنصر اضافه شده اولین عنصری است که حذف می شود

اصل LIFO

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

در این راهنما، مفاهیم پشته‌ها، پیاده‌سازی آن‌ها، موارد استفاده و موارد دیگر را عمیقاً بررسی خواهیم کرد. ما تعریف می‌کنیم که پشته‌ها چیست، چگونه کار می‌کنند، و سپس، نگاهی به دو تا از رایج‌ترین روش‌های پیاده‌سازی ساختار داده پشته در پایتون خواهیم داشت.

مفاهیم اساسی یک ساختار داده پشته

در اصل، یک پشته به طرز فریبنده ای ساده است، اما دارای تفاوت های ظریفی است که به آن برنامه های کاربردی همه کاره در حوزه محاسباتی می دهد. قبل از اینکه به پیاده سازی و کاربردهای عملی آن بپردازیم، بیایید از درک کاملی از مفاهیم اصلی اطراف پشته ها اطمینان حاصل کنیم.

اصل LIFO (Last In First Out).

LIFO اصل راهنمای پشته است. به این معنی است که آخرین موردی که وارد پشته می شود اولین موردی است که خارج می شود. این مشخصه پشته ها را از سایر ساختارهای داده خطی، مانند صف، متمایز می کند.

توجه داشته باشید: مثال مفید دیگری که به شما کمک می کند سر خود را در مورد نحوه عملکرد پشته ها بپیچید این است که تصور کنید افراد وارد و خارج می شوند. آسانسور - آخرین کسی که وارد آسانسور می شود اولین کسی است که از آن خارج می شود!

عملیات اساسی

هر ساختار داده با عملیاتی که پشتیبانی می کند تعریف می شود. برای پشته ها، این عملیات ساده اما حیاتی هستند:

  • فشار - یک عنصر را به بالای پشته اضافه می کند. اگر پشته پر باشد، این عملیات ممکن است منجر به سرریز پشته شود.

عملیات فشار LIFO

  • پاپ – بالاترین عنصر پشته را حذف و برمی گرداند. اگر پشته خالی باشد، تلاش برای پاپ می‌تواند باعث پایین‌رفتن پشته شود.

عملیات پاپ LIFO

  • زیرچشمی (یا بالا) – بالاترین عنصر را بدون حذف آن مشاهده می کند. این عملیات زمانی مفید است که بخواهید عنصر بالای فعلی را بدون تغییر وضعیت پشته بررسی کنید.

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

چگونه یک پشته را از ابتدا در پایتون پیاده سازی کنیم

با درک اصول اساسی پشت پشته ها، وقت آن رسیده است که آستین ها را بالا بزنیم و به جنبه عملی کارها بپردازیم. پیاده‌سازی یک پشته، در عین سادگی، می‌تواند به روش‌های متعددی انجام شود. در این بخش، دو روش اصلی برای پیاده‌سازی یک پشته را بررسی می‌کنیم – با استفاده از آرایه‌ها و لیست‌های پیوندی.

پیاده سازی پشته با استفاده از آرایه ها

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

از طرف دیگر، آرایه های سنتی اندازه ثابتی دارند، به این معنی که پس از مقداردهی اولیه، نمی توان اندازه آنها را تغییر داد. این می تواند منجر به الف شود سرریز پشته اگر نظارت نشود این را می توان با آرایه های پویا (مانند پایتون) غلبه کرد list) که می تواند اندازه را تغییر دهد، اما این عملیات بسیار پرهزینه است.

با تمام این مشکلات، اجازه دهید شروع به پیاده سازی کلاس پشته خود با استفاده از آرایه ها در پایتون کنیم. اول از همه، بیایید خود یک کلاس ایجاد کنیم، با سازنده ای که اندازه پشته را به عنوان پارامتر می گیرد:

class Stack: def __init__(self, size): self.size = size self.stack = [None] * size self.top = -1

همانطور که می بینید، ما سه مقدار را در کلاس خود ذخیره کردیم. را size اندازه دلخواه پشته است stack آرایه واقعی مورد استفاده برای نمایش ساختار داده پشته است و top شاخص آخرین عنصر در است stack آرایه (بالای پشته).

از این به بعد، یک روش برای هر یک از عملیات پشته اصلی ایجاد و توضیح خواهیم داد. هر یک از این روش ها در داخل موجود خواهد بود Stack کلاسی که به تازگی ایجاد کرده ایم.

بیایید با شروع push() روش. همانطور که قبلاً بحث شد، عملیات فشار یک عنصر را به بالای پشته اضافه می کند. اول از همه، بررسی می کنیم که آیا پشته برای عنصری که می خواهیم اضافه کنیم، فضای خالی باقی مانده است یا خیر. اگر پشته پر باشد، آن را بالا می بریم Stack Overflow استثنا. در غیر این صورت، ما فقط عنصر را اضافه می کنیم و آن را تنظیم می کنیم top و stack بر این اساس:

def push(self, item): if self.top == self.size - 1: raise Exception("Stack Overflow") self.top += 1 self.stack[self.top] = item

اکنون، می‌توانیم روشی را برای حذف یک عنصر از بالای پشته تعریف کنیم - the pop() روش. حتی قبل از اینکه یک عنصر را حذف کنیم، باید بررسی کنیم که آیا عناصری در پشته وجود دارد یا خیر، زیرا تلاش برای بیرون آوردن یک عنصر از یک پشته خالی فایده ای ندارد:

def pop(self): if self.top == -1: raise Exception("Stack Underflow") item = self.stack[self.top] self.top -= 1 return item

در نهایت، ما می توانیم تعریف کنیم peek() روشی که فقط مقدار عنصری را که در حال حاضر در بالای پشته قرار دارد برمی‌گرداند:

def peek(self): if self.top == -1: raise Exception("Stack is empty") return self.stack[self.top]

و بس! اکنون کلاسی داریم که رفتار پشته ها را با استفاده از لیست ها در پایتون پیاده سازی می کند.

پیاده سازی یک پشته با استفاده از لیست های پیوندی

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

همانطور که قبلاً در مورد بحث کردیم "فهرست های پیوندی پایتون" در مقاله، اولین چیزی که باید قبل از لیست پیوندی واقعی پیاده سازی کنیم، یک کلاس برای یک گره است:

class Node: def __init__(self, data): self.data = data self.next = None

راهنمای عملی و عملی ما برای یادگیری Git را با بهترین روش ها، استانداردهای پذیرفته شده در صنعت و برگه تقلب شامل بررسی کنید. دستورات Google Git را متوقف کنید و در واقع یاد گرفتن آی تی!

این پیاده سازی تنها دو نقطه داده را ذخیره می کند - مقدار ذخیره شده در گره (data) و ارجاع به گره بعدی (next).

مجموعه 3 قسمتی ما درباره لیست های پیوندی در پایتون:

اکنون می‌توانیم به خود کلاس پشته واقعی برویم. سازنده کمی متفاوت از قبلی خواهد بود. فقط شامل یک متغیر خواهد بود - ارجاع به گره در بالای پشته:

class Stack: def __init__(self): self.top = None

همانطور که انتظار می رفت ، push() متد یک عنصر جدید (در این مورد گره) به بالای پشته اضافه می کند:

def push(self, item): node = Node(item) if self.top: node.next = self.top self.top = node

La pop() متد بررسی می کند که آیا عناصری در پشته وجود دارد یا خیر و اگر پشته خالی نباشد، بالاترین عنصر را حذف می کند:

def pop(self): if not self.top: raise Exception("Stack Underflow") item = self.top.data self.top = self.top.next return item

در نهایت، peek() متد به سادگی مقدار عنصر را از بالای پشته می خواند (در صورت وجود):

def peek(self): if not self.top: raise Exception("Stack is empty") return self.top.data

توجه داشته باشید: رابط هر دو Stack کلاس ها یکسان است - تنها تفاوت در پیاده سازی داخلی متدهای کلاس است. این بدان معناست که شما به راحتی می توانید بین پیاده سازی های مختلف بدون نگرانی در مورد داخلی کلاس ها جابجا شوید.

انتخاب بین آرایه ها و لیست های پیوندی به الزامات و محدودیت های خاص برنامه بستگی دارد.

نحوه پیاده سازی یک پشته با استفاده از ساختارهای داخلی پایتون

برای بسیاری از توسعه دهندگان، ساختن پشته از ابتدا، اگرچه آموزشی است، ممکن است کارآمدترین راه برای استفاده از پشته در برنامه های کاربردی دنیای واقعی نباشد. خوشبختانه، بسیاری از زبان های برنامه نویسی محبوب مجهز به ساختارهای داده داخلی و کلاس هایی هستند که به طور طبیعی از عملیات پشته پشتیبانی می کنند. در این بخش، پیشنهادات پایتون را در این زمینه بررسی خواهیم کرد.

پایتون که یک زبان همه کاره و پویا است، کلاس پشته اختصاصی ندارد. با این حال، ساختارهای داده داخلی آن، به ویژه لیست ها و کلاس deque از collections ماژول، بدون زحمت می تواند به عنوان پشته خدمت کند.

استفاده از لیست های پایتون به عنوان پشته

لیست های پایتون به دلیل ماهیت پویا و وجود روش هایی مانند این، می توانند یک پشته را به طور کاملاً مؤثر تقلید کنند append() و pop().

  • عملیات فشار – افزودن یک عنصر به بالای پشته به سادگی استفاده از آن است append() روش:

    stack = []
    stack.append('A')
    stack.append('B')
    
  • عملیات پاپ - حذف بالاترین عنصر را می توان با استفاده از pop() روش بدون هیچ آرگومانی:

    top_element = stack.pop() 
  • عملیات زیرچشمی دسترسی به بالا بدون پاپ می تواند با استفاده از نمایه سازی منفی انجام شود:

    top_element = stack[-1] 

با استفاده از دیک کلاس از مجموعه ماژول ها

La deque کلاس (مخفف صف دو پایانی) ابزار همه کاره دیگری برای پیاده سازی پشته است. برای ضمیمه‌های سریع و باز شدن از هر دو طرف بهینه‌سازی شده است، که باعث می‌شود برای عملیات پشته‌ای نسبت به فهرست‌ها کارآمدتر باشد.

  • دهی اولیه:

    from collections import deque
    stack = deque()
    
  • عملیات فشار - مشابه لیست ها، append() روش استفاده می شود:

    stack.append('A')
    stack.append('B')
    
  • عملیات پاپ - مانند لیست ها، pop() روش کار را انجام می دهد:

    top_element = stack.pop() 
  • عملیات زیرچشمی - رویکرد مانند لیست ها است:

    top_element = stack[-1] 

چه زمانی از کدام استفاده کنیم؟

در حالی که هر دو لیست و deque را می توان به عنوان پشته استفاده کرد، اگر در اصل از ساختار به عنوان پشته استفاده می کنید (با ضمائم و باز شدن از یک انتها)، deque به دلیل بهینه سازی آن می تواند کمی سریعتر باشد. با این حال، برای اکثر اهداف عملی و به جز مواردی که با برنامه های کاربردی حیاتی سروکار داریم، فهرست های پایتون کافی است.

توجه داشته باشید: این بخش به پیشنهادات داخلی پایتون برای رفتار پشته مانند می پردازد. وقتی چنین ابزار قدرتمندی در دست دارید، لزوماً نیازی به اختراع مجدد چرخ (با اجرای پشته از ابتدا) ندارید.

مشکلات بالقوه مرتبط با پشته و نحوه غلبه بر آنها

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

سرریز پشته

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

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

اگر سرریز پشته به دلیل تماس های بازگشتی بیش از حد اتفاق می افتد، راه حل های تکراری را در نظر بگیرید یا اگر محیط اجازه می دهد، حد بازگشت را افزایش دهید.

پشته Underflow

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

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

محدودیت های حافظه

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

نگرانی های ایمنی موضوع

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

  • Mutexes و Locks - از mutexes (اشیاء حذف متقابل) یا قفل استفاده کنید تا مطمئن شوید که فقط یک رشته می تواند در یک زمان معین عملیات روی پشته انجام دهد.
  • عملیات اتمی - از عملیات اتمی، اگر توسط محیط پشتیبانی می شود، برای اطمینان از سازگاری داده ها در طول عملیات فشار و پاپ استفاده کنید.
  • پشته های موضوعی محلی – در سناریوهایی که هر رشته به پشته خود نیاز دارد، استفاده از ذخیره‌سازی محلی رشته‌ای را در نظر بگیرید تا به هر رشته نمونه پشته جداگانه‌ای بدهید.

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

نتیجه

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

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

تمبر زمان:

بیشتر از Stackabuse