1. संग्रहांची यादी

तुम्हाला आठवत असेल की, Java मध्ये संग्रह आहेत - समान प्रकारच्या वस्तू साठवण्यासाठी एक सुलभ साधन.

चला मुख्य संग्रह-संबंधित इंटरफेस आठवण्याचा प्रयत्न करूया:

यादी , सेट , नकाशा आणि रांग .

नेहमीप्रमाणे, साधने चांगली किंवा वाईट असतातच असे नाही - तुम्ही ते त्यांच्या हेतूसाठी वापरत आहात की नाही हे महत्त्वाचे आहे. आणि ते करण्यासाठी, कोणता संग्रह आणि कधी वापरायचा हे जाणून घेण्यासाठी आपण त्यांची विशिष्ट वैशिष्ट्ये पूर्णपणे समजून घेतली पाहिजेत.

1. यादी

चला सर्वात जास्त वापरल्या जाणार्‍या संग्रहासह प्रारंभ करूया.

साध्या जुन्या अॅरेच्या शक्य तितक्या जवळ सूची करा .

हा संग्रह आपल्याला संग्रहाच्या आकाराची काळजी न करता समान प्रकारच्या वस्तूंची सूची सोयीस्करपणे संग्रहित करू देतो, जसे की आपण अॅरे वापरत असल्यास. संग्रहातील घटक अनुक्रमणिकेद्वारे ऍक्सेस केले जातात. एखादी वस्तू नेमकी कुठे आहे हे आम्हाला माहीत असल्यास आणि घटक जोडण्याची किंवा काढून टाकण्याची गरज न पडता वारंवार त्यात प्रवेश करणे आवश्यक असल्यास, सूची आदर्श आहे.

2. सेट करा

सेटची रचना पूर्णपणे वेगळी आहे.

जेव्हा आपल्याला अनन्य वस्तू संग्रहित करण्याची आवश्यकता असते तेव्हा सेट सर्वात योग्य असतो. उदाहरणार्थ, लायब्ररीतील लेखकांचा संच जिथे प्रत्येक लेखक अद्वितीय असतो. पण आपण फक्त जाऊन कोणत्याही विशिष्ट लेखकाला पकडू शकत नाही. सेट मुळे आपल्या लायब्ररीमध्ये एखादा विशिष्ट लेखक उपस्थित आहे की नाही हे त्वरीत तपासू शकतो, म्हणजे सेटमध्ये एक अद्वितीय ऑब्जेक्ट आहे की नाही हे आपण तपासू शकतो . आम्ही प्रत्येक घटकात प्रवेश करून संपूर्ण संग्रहावर पुनरावृत्ती देखील करू शकतो, परंतु ते करणे इष्टतम नाही.

दुसऱ्या शब्दांत, आमच्या लायब्ररीसाठी, कोणताही विशिष्ट लेखक उपस्थित आहे की नाही हे त्वरित तपासण्यासाठी एक संच सर्व अद्वितीय लेखकांच्या संग्रहाचे प्रतिनिधित्व करू शकतो.

3. नकाशा

नकाशा फाइलिंग कॅबिनेटसारखा आहे, जिथे प्रत्येक फाइलवर स्वाक्षरी केली जाते आणि वैयक्तिक वस्तू किंवा संपूर्ण संरचना संग्रहित करू शकतात. नकाशाचा वापर अशा प्रकरणांमध्ये केला पाहिजे जेथे आम्हाला एका मूल्यावरून दुसर्‍या मूल्याचे मॅपिंग राखणे आवश्यक आहे.

नकाशासाठी , या संबंधांना की-व्हॅल्यू जोड्या म्हणतात.

आम्ही आमच्या लायब्ररीमध्ये लेखक वस्तूंचा वापर की म्हणून आणि पुस्तकांच्या सूची ( लिस्ट ऑब्जेक्ट्स) मूल्य म्हणून वापरून करू शकतो. अशा प्रकारे, लायब्ररीमध्ये लेखक ऑब्जेक्ट अस्तित्वात आहे की नाही हे पाहण्यासाठी सेट तपासल्यानंतर , आपण नकाशावरून त्याच्या किंवा तिच्या पुस्तकांची यादी मिळविण्यासाठी त्याच लेखक ऑब्जेक्टचा वापर करू शकतो .

4. रांग

रांग एक संग्रह आहे की — आश्चर्य! — रांगेचे वर्तन लागू करते. आणि रांग LIFO (लास्ट इन, फर्स्ट आउट) किंवा FIFO (फर्स्ट इन, फर्स्ट आउट) असू शकते. इतकेच काय, रांग द्विदिशात्मक किंवा "डबल-एंडेड" असू शकते.

जेव्हा वर्गात जोडलेल्या वस्तू प्राप्त झाल्या त्या क्रमाने वापरण्याची आवश्यकता असते तेव्हा ही रचना उपयुक्त ठरते. उदाहरणार्थ, आमची लायब्ररी घ्या.

आम्ही नवीन आलेल्या अभ्यागतांना रांगेत जोडू शकतो आणि त्यांच्यासाठी आलेली पुस्तके जारी करून त्यांना सेवा देऊ शकतो.

आपण बघू शकतो की, यातील प्रत्येक रचना त्याच्या हेतूसाठी वापरल्यास चांगली आहे. आणि आम्हाला एकाच लायब्ररीच्या उदाहरणात चारही प्रकारच्या संग्रहांसाठी चांगले उपयोग आढळले.

2. जटिलता

आधीच नमूद केल्याप्रमाणे, आम्ही वर विचार केलेले संग्रह हे इंटरफेस आहेत, याचा अर्थ आम्हाला ते वापरता यावेत यासाठी त्यांची अंमलबजावणी असणे आवश्यक आहे.

ज्याप्रमाणे सूक्ष्मदर्शकाने नखे मारणे ही सर्वोत्तम कल्पना नाही, त्याचप्रमाणे संग्रहाची प्रत्येक अंमलबजावणी प्रत्येक परिस्थितीसाठी योग्य नाही.

नोकरीसाठी योग्य साधन निवडताना, आम्ही सामान्यतः 2 वैशिष्ट्ये पाहतो:

  • हे साधन कामासाठी किती योग्य आहे?
  • ते काम किती वेगाने पूर्ण होईल?

नोकरीसाठी योग्य साधन कसे निवडायचे हे शोधण्यात आम्ही थोडा वेळ घालवला आहे, परंतु त्याचा वेग काहीतरी नवीन आहे.

संगणनामध्ये, साधनाचा वेग बहुतेक वेळा वेळेच्या जटिलतेच्या संदर्भात व्यक्त केला जातो आणि कॅपिटल अक्षर O द्वारे दर्शविला जातो.

जगात वेळेची जटिलता काय आहे?

सोप्या भाषेत, वेळेची जटिलता संग्रहातील अल्गोरिदमला विशिष्ट क्रिया करण्यासाठी लागणारा वेळ दर्शवते (घटक जोडणे/काढणे, घटक शोधणे).

अॅरेलिस्ट वि लिंक्डलिस्ट

लिस्ट इंटरफेसची दोन अंमलबजावणी वापरून हे पाहू - ArrayList आणि LinkedList .

बाह्य स्वरूपासाठी, या संग्रहांसह कार्य करणे समान आहे:


List<String> arrayList = new ArrayList<>();
arrayList.add(String);
arrayList.get(index);
arrayList.remove(index);
arrayList.remove(String);
 
List<String> linkedList = new LinkedList<>();
 
linkedList.add(String);
 
linkedList.get(index);
linkedList.remove(index);
linkedList.remove(String);
    

तुम्ही बघू शकता, दोन्ही संकलन प्रकारांसाठी, घटक जोडणे, मिळवणे आणि काढणे सारखेच दिसते. कारण ही एकाच इंटरफेसवरील अंमलबजावणी आहेत. पण तिथेच समानता संपते.

सूची इंटरफेसच्या त्यांच्या भिन्न अंमलबजावणीमुळे , या दोन संरचना इतरांपेक्षा अधिक कार्यक्षमतेने भिन्न क्रिया करतात.

घटक काढून टाकण्याचा आणि जोडण्याचा विचार करा.

जर आपल्याला ArrayList च्या मधोमध एखादे घटक काढायचे असतील तर , आपण काढलेल्या घटकाचे अनुसरण करून यादीतील कोणताही भाग आपल्याला ओव्हरराइट करावा लागेल.

समजा आपल्याकडे 5 घटकांची यादी आहे आणि आपल्याला तिसरा घटक काढायचा आहे.


List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.remove(2);
    

या प्रकरणात, काढून टाकल्याने एक सेल मोकळा होतो, म्हणून आपल्याला 4 था घटक जिथे 3 रा होता आणि 5 वा जिथे 4 था लिहावा लागेल.

हे अत्यंत अकार्यक्षम आहे.

सूचीच्या मध्यभागी एक घटक जोडताना असेच होते.

LinkedList वेगळ्या पद्धतीने संरचित आहे. घटक जोडणे किंवा काढून टाकणे जलद आहे, कारण आम्हाला फक्त मागील आणि पुढील घटकांमधील संदर्भ बदलणे आवश्यक आहे, ज्यामुळे आम्ही घटकांच्या साखळीतून काढून टाकत आहोत.

5 घटकांच्या समान सूचीच्या उदाहरणाकडे परत जाताना, 3 रा घटक काढून टाकल्यानंतर, आपल्याला फक्त 2 रा घटकाचा संदर्भ पुढील घटकासाठी आणि चौथ्या घटकाचा मागील घटकाचा संदर्भ बदलणे आवश्यक आहे.

जेव्हा एखादा घटक सूचीमध्ये जोडला जातो तेव्हा तीच प्रक्रिया होते, परंतु उलट होते.

ArrayList च्या तुलनेत LinkedList मध्ये आम्हाला किती कमी काम करावे लागेल ते पहा . आणि ते फक्त 5 घटक आहेत. आमच्या सूचीमध्ये 100 किंवा अधिक घटक असल्यास, LinkedList ची श्रेष्ठता आणखी लक्षणीय होईल.

पण जर आपण एखाद्या घटकात अनुक्रमणिकेद्वारे प्रवेश केला तर परिस्थिती कशी बदलते?

येथे सर्व काही अगदी उलट आहे.

ArrayList ही एक सामान्य अ‍ॅरे म्हणून संरचित असल्याने, कोणताही घटक त्याच्या इंडेक्सद्वारे मिळवणे आपल्यासाठी खूप सोपे आहे. आम्ही फक्त पॉइंटर एका विशिष्ट ठिकाणी हलवतो आणि संबंधित सेलमधून घटक मिळवतो.

परंतु लिंक्डलिस्ट फक्त अशा प्रकारे कार्य करत नाही. विशिष्ट निर्देशांकासह घटक शोधण्यासाठी आपल्याला सूचीतील सर्व घटकांमधून जावे लागेल.

हे सर्व आपण मोठ्या O च्या संदर्भात व्यक्त करण्याचा प्रयत्न करू का?

चला अनुक्रमणिकेद्वारे घटक ऍक्सेस करून प्रारंभ करूया.

ArrayList मध्ये , हे घटक सूचीमध्ये कोठे आहे याची पर्वा न करता एका चरणात होते. शेवटी असो वा सुरुवातीला.

या प्रकरणात, वेळेची जटिलता O(1) असेल .

LinkedList मध्ये , आपल्याला आवश्यक असलेल्या निर्देशांकाच्या मूल्याच्या समान अनेक घटकांवर पुनरावृत्ती करावी लागेल.

अशा क्रियेसाठी वेळेची जटिलता O(n) आहे , जिथे n ही आपल्याला आवश्यक असलेल्या घटकाची अनुक्रमणिका आहे.

येथे तुम्ही पहात आहात की आम्ही मोठ्या-O कंसात टाकलेली संख्या केलेल्या क्रियांच्या संख्येशी संबंधित आहे.

शेल आम्ही काढणे आणि जोडणे परत?

चला LinkedList सह प्रारंभ करूया.

कारण घटक जोडण्यासाठी किंवा काढून टाकण्यासाठी आम्हाला मोठ्या प्रमाणात क्रिया करण्याची आवश्यकता नाही आणि या ऑपरेशनचा वेग हा घटक कुठे आहे यावर कोणत्याही प्रकारे अवलंबून नाही, त्याची जटिलता O(1) म्हणून व्यक्त केली जाते आणि असे म्हटले जाते . स्थिर असणे

ArrayList साठी या ऑपरेशनची वेळ जटिलता देखील O(n) आहे , ज्याला आपण रेखीय जटिलता म्हणतो.

रेखीय जटिलतेसह अल्गोरिदममध्ये, चालण्याची वेळ थेट प्रक्रिया करण्याच्या घटकांच्या संख्येवर अवलंबून असते. हे घटकाच्या स्थानावर देखील अवलंबून असू शकते, मग ते सूचीच्या सुरूवातीस किंवा शेवटी आहे.

वेळेची जटिलता लॉगरिदमिक देखील असू शकते. हे O(log n) म्हणून व्यक्त केले जाते .

उदाहरण म्हणून, 10 अंकांचा समावेश असलेल्या एका वर्गीकृत ट्रीसेटचा विचार करा. आम्हाला क्रमांक 2 शोधायचा आहे.

यादी क्रमवारी लावलेली असल्यामुळे आणि त्यात कोणतेही डुप्लिकेट नसल्यामुळे, आम्ही ती अर्ध्यामध्ये विभाजित करू शकतो आणि कोणत्या अर्ध्यामध्ये इच्छित संख्या असेल ते तपासू शकतो, असंबद्ध भाग टाकून देऊ शकतो आणि नंतर आम्ही इच्छित घटकापर्यंत पोहोचेपर्यंत ही प्रक्रिया पुन्हा करू शकतो. शेवटी, घटकांच्या लॉग(n) संख्येवर प्रक्रिया केल्यानंतर आम्हाला संख्या सापडेल (किंवा सापडत नाही).

येथे एक सारणी आहे जी उर्वरित संग्रहांच्या वेळेची जटिलता सारांशित करते.

निर्देशांकानुसार की करून शोधा शेवटी समाविष्ट करणे शेवटी समाविष्ट करणे काढणे
अॅरेलिस्ट O(1) N/A O(n) O(1) O(n) O(n)
लिंक्डलिस्ट O(n) N/A O(n) O(1) O(1) O(1)
हॅशसेट N/A O(1) O(1) N/A O(1) O(1)
ट्रीसेट N/A O(1) O(लॉग n) N/A O(लॉग n) O(लॉग n)
हॅशमॅप N/A O(1) O(1) N/A O(1) O(1)
ट्रीमॅप N/A O(1) O(लॉग n) N/A O(लॉग n) O(लॉग n)
ArrayDeque N/A N/A O(n) O(1) O(1) O(1)

आता आमच्याकडे लोकप्रिय संग्रहांची वेळ जटिलता दर्शविणारी एक सारणी आहे, आम्ही या प्रश्नाचे उत्तर देऊ शकतो की, अनेक संग्रहांपैकी, आम्ही बहुतेकदा ArrayList , HashSet आणि HashMap का वापरतो .

हे फक्त असे आहे की ते बहुतेक कार्यांसाठी सर्वात कार्यक्षम आहेत :)