Data Structures Notes PDF
Document Details
Uploaded by NoteworthyZombie
Menoufia National University
Dr.Asmaa Awad
Tags
Summary
These lecture notes cover fundamental data structures in Python, including lists, tuples, sets, and dictionaries. Examples and operations are demonstrated for each structure. The notes are suitable for an undergraduate computer science course.
Full Transcript
Data Structures lec1 Dr.Asmaa Awad List Tuple General purpose Immutable (can’t add/change) Most widely used data structure Useful for fixed data Grow and shrink size as needed Faster than Lists Sequence type...
Data Structures lec1 Dr.Asmaa Awad List Tuple General purpose Immutable (can’t add/change) Most widely used data structure Useful for fixed data Grow and shrink size as needed Faster than Lists Sequence type Sequence type Sortable Set Dict Store non-duplicate items Key/Value pairs Very fast access vs Lists Associative array, like Java HashMap Math Set ops (union, intersect) Unordered Unordered SEQUENCES (String, List, Tuple) indexing: x slicing: x[1:4] adding/concatenating: + multiplying: * checking membership: in/not in iterating for i in x: len(sequence1) min(sequence1) max(sequence1) sum(sequence1[1:3]]) sorted(list1) sequence1.count(item) sequence1.index(item) SEQUENCES String List Tuple indexing – Access any item in the sequence using its index String x = 'frog' print (x) # prints 'g' List x = ['pig', 'cow', 'horse'] print (x) # prints 'cow' SEQUENCES String List Tuple slicing – Slice out substrings, sublists, subtuples using indexes [start : end+1 : step] x = 'computer' Code Result Explanation x[1:4] 'omp' Items 1 to 3 x[1:6:2] 'opt' Items 1, 3, 5 x[3:] 'puter' Items 3 to end x[:5] 'compu' Items 0 to 4 x[-1] 'r' Last item x[-3:] 'ter' Last 3 items x[:-2] 'comput' All except last 2 items SEQUENCES String List Tuple adding / concatenating – Combine 2 sequences of the same type using + String x = 'horse' + 'shoe' print (x) # prints 'horseshoe' List x = ['pig', 'cow'] + ['horse'] print (x) # prints ['pig', 'cow', 'horse'] SEQUENCES String List Tuple multiplying – Multiply a sequence using * String x = ‘bug' * 3 print (x) # prints ‘bugbugbug' List x = [8, 5] * 3 print (x) # prints [8, 5, 8, 5, 8, 5] SEQUENCES String List Tuple checking membership – Test whether an item is in or not in a sequence String x = 'bug' print ('u' in x) # prints True List x = ['pig', 'cow', 'horse'] print ('cow' not in x) # prints False SEQUENCES String List Tuple iterating – Iterate through the items in a sequence Item x = [7, 8, 3] for item in x: print (item * 2) # prints 14, 16, 6 Index & Item x = [7, 8, 3] for index, item in enumerate(x): print (index, item) # prints 0 7, 1 8, 2 3 SEQUENCES String List Tuple number of items – Count the number of items in a sequence String x = 'bug' print (len(x)) # prints 3 List x = ['pig', 'cow', 'horse'] print (len(x)) # prints 3 SEQUENCES String List Tuple minimum – Find the minimum item in a sequence lexicographically – alpha or numeric types, but cannot mix types String x = 'bug' print (min(x)) # prints 'b' List x = ['pig', 'cow', 'horse'] print (min(x)) # prints 'cow' SEQUENCES String List Tuple maximum – Find the maximum item in a sequence – alpha or numeric types, but cannot mix types String x = 'bug' print (max(x)) # prints 'u' List x = ['pig', 'cow', 'horse'] print (max(x)) # prints 'pig' SEQUENCES String List Tuple sum – Find the sum of items in a sequence – entire sequence must be numeric type String -> Error x = [5, 7, 'bug‘] print (sum(x)) # error! List x = [2, 5, 8, 12] print (sum(x)) # prints 27 print (sum(x[-2:])) # prints 20 SEQUENCES String List Tuple sorting – Returns a new list of items in sorted order – Does not change the original list String x = 'bug' print (sorted(x)) # prints ['b', 'g', 'u'] List x = ['pig', 'cow', 'horse'] print (sorted(x)) # prints ['cow', 'horse', 'pig'] SEQUENCES String List Tuple count (item) – Returns count of an item String x = 'hippo' print (x.count('p')) # prints 2 List x = ['pig', 'cow', 'horse', 'cow'] print (x.count('cow')) # prints 2 SEQUENCES String List Tuple index (item) – Returns the index of the first occurrence of an item String x = 'hippo' print (x.index('p')) # prints 2 List x = ['pig', 'cow', 'horse', 'cow'] print (x.index('cow')) # prints 1 SEQUENCES String List Tuple unpacking – Unpack the n items of a sequence into n variables x = ['pig', 'cow', 'horse'] a, b, c = x # now a is 'pig' # b is 'cow', # c is 'horse' Note: The number of variables must exactly match the length of the list. LISTS LISTS All operations from Sequences, plus: constructors: del list1 delete item from list1 list1.append(item) appends an item to list1 list1.extend(sequence1) appends a sequence to list1 list1.insert(index, item) inserts item at index list1.pop() pops last item list1.remove(item) removes first instance of item list1.reverse() reverses list order list1.sort() sorts list in place LISTS constructors – creating a new list x = list() x = ['a', 25, 'dog', 8.43] x = list(tuple1) List Comprehension: x = [m for m in range(8)] resulting list: [0, 1, 2, 3, 4, 5, 6, 7] x = [z**2 for z in range(10) if z>4] resulting list: [25, 36, 49, 64, 81] LISTS delete – Delete a list or an item from a list x = [5, 3, 8, 6] del(x) # [5, 8, 6] del(x) # deletes list x LISTS append – Append an item to a list x = [5, 3, 8, 6] x.append(7) # [5, 3, 8, 6, 7] LISTS extend – Append an sequence to a list x = [5, 3, 8, 6] y = [12, 13] x.extend(y) # [5, 3, 8, 6, 7, 12, 13] LISTS insert – Insert an item at given index x.insert(index, item) x = [5, 3, 8, 6] x.insert(1, 7) # [5, 7, 3, 8, 6] x.insert(1,['a','m']) # [5, ['a', 'm'], 7, 3, 8, 6] LISTS pop – Pops last item off the list, and returns item x = [5, 3, 8, 6] x.pop() # [5, 3, 8] # and returns the 6 print(x.pop()) # prints 8 # x is now [5, 3] LISTS remove – Remove first instance of an item x = [5, 3, 8, 6, 3] x.remove(3) # [5, 8, 6, 3] LISTS reverse – Reverse the order of the list x = [5, 3, 8, 6] x.reverse() # [6, 8, 3, 5] LISTS sort – Sort the list in place x = [5, 3, 8, 6] x.sort() # [3, 5, 6, 8] Note: sorted(x) returns a new sorted list without changing the original list x. x.sort() puts the items of x in sorted order (sorts in place). TUPLES TUPLES Support all operations for Sequences Immutable, but member objects may be mutable If the contents of a list shouldn’t change, use a tuple to prevent items from accidently being added, changed or deleted Tuples are more efficient than lists due to Python’s implementation TUPLES constructors – creating a new tuple x = () # no-item tuple x = (1,2,3) x = 1, 2, 3 # parenthesis are optional x = 2, # single-item tuple x = tuple(list1) # tuple from list TUPLES immutable – But member objects may be mutable x = (1, 2, 3) del(x) # error! x = 8 # error! x = ([1,2], 3) # 2-item tuple: list and int del(x) # (, 3) SETS SETS A set is an unordered collection with no duplicate elements. Basic uses include membership testing and eliminating duplicate entries. Set objects also support mathematical operations like union, intersection, difference, and symmetric difference. SETS constructors – creating a new set x = {3,5,3,5} # {5, 3} x = set(‘) # empty set list1=[5,6,8,5,2] x = set(list1) # {8,2,5,6} basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} Set Comprehension: x = {3*x for x in range(10) if x>5} resulting set: {18, 21, 24, 27} but in random order SETS basic set operations Description Code Add item to set x x.add(item) Remove item from set x x.remove(item) Get length of set x len(x) item in x Check membership in x item not in x Pop random item from set x x.pop() Delete all items from set x x.clear() SETS basic set operations a = set('abracadabra’) a.add('w’) #{'d', 'a', 'c', 'w', 'b', 'r’} a.remove(‘c’) # {'d', 'a', 'w', 'b', 'r’} Len(a) #5 ‘x’ in a #False a.pop() # {'a', 'w', 'b', ‘r’} a.clear() # set() SETS standard mathematical set operations Set Function Description Code Intersection AND set1 & set2 Union OR set1 | set2 Symmetric Difference XOR set1 ^ set2 Difference In set1 but not in set2 set1 – set2 Subset set2 contains set1 set1 = set2 SETS standard mathematical set operations a = set('abracadabra') b = set('alacazam') a # {'a', 'r', 'b', 'c', 'd'} a - b # {'r', 'd', 'b'} a & b # letters in both a and b{'a', 'c'} a ^ b # {'r', 'd', 'b', 'm', 'z', 'l’} a | b # {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l’} c=set('acz’) c> different argument types or different number of arguments Output of print(2 + 3) print("Hello " + "Ahmed") + print(len([1,3,5,8])) Output of print(len("Faculty of AI")) Len() print(len({"name":"Ahmed", "ID":30020, "age":30})) Polymorphism - Overloading Polymorphism - Overloading class Test: class Test: def sum(self, a=None,b=None, c=None): s=0 def sum(self, *a): if a!=None and b!=None and c!=None: s=0 s=a+b+c for x in a: elif a!=None and b!=None: s+=x s=a+b return s elif a!=None: s=a else: s= None d= Test() return s print(d.sum()) print(d.sum(1)) d= Test() print(d.sum(2,5)) print(d.sum()) print(d.sum(1)) print(d.sum(2,7,9)) print(d.sum(2,5)) print(d.sum(2,7,9,10,12,10)) print(d.sum(2,7,9)) dunder methods a="faculty of AI" b= [1,5,9] print(len(a)) print(b) print("\n") print(a.__len__()) print(b.__getitem__(2)) print(dir(a)) dunder methods- overload len() method class Order: class Order: def __init__(self,cart,customer): def __init__(self,cart,customer): self.cart=cart self.cart=cart self.customer=customer self.customer=customer order= Order(["T- def __len__(self): shirt","jacket"],"Ahmed") return(len(self.cart)) print(len(order)) order= Order(["T- shirt","jacket"],"Ahmed") print(len(order)) dunder methods- overload __str__() method class Pizza: def __init__(self,ingredients): self.ingredients=ingredients @classmethod The effect of @classmethod def veg(cls): return cls(["mushrooms","olives","onion"]) A different way to construct an object @classmethod def margherita(cls): return cls(["mozarella","sauce"]) pizza1=Pizza(["tomatos","olives"]) pizza2=Pizza.veg() pizza3=Pizza.margherita() print(pizza2,pizza3) dunder methods - overload __str__() method class Pizza: def __init__(self,ingredients): self.ingredients=ingredients @classmethod def veg(cls): return cls(["mushrooms","olives","onion"]) @classmethod def margherita(cls): return cls(["mozarella","sauce"]) def __str__(self): return f"ingredients are {self.ingredients}" pizza1=Pizza(["tomatos","olives"]) pizza2=Pizza.veg() pizza3=Pizza.margherita() print(pizza2,pizza3) dunder methods - overload __add__() method class Pizza: class Pizza: def __init__(self,ingredients): def __init__(self,ingredients): self.ingredients=ingredients self.ingredients=ingredients def __str__(self): def __str__(self): return f"ingredients are return f"ingredients are {self.ingredients}" {self.ingredients}" def add_ingredient(self,other): def __add__(self,other): return self.ingredients.append(other) return self.ingredients.append(other) pizza1=Pizza(["tomatos","olives"]) pizza1=Pizza(["tomatos","olives"]) print(pizza1) print(pizza1) pizza1.add_ingredient("onion") pizza1 + "onion" print(pizza1) print(pizza1) dunder methods – overload of similar methods __mul__ __div__ __sub__ Grades of students Shopping cart Polymorphism- Overriding Overriding class French: def sayHello(self): print("Bonjour") Same format class English: def sayHello(self): print("Good Morning") different outputs class Arabic: def sayHello(self): print)”)"صباح الخیر f=French() e=English() a=Arabic() f.sayHello() e.sayHello() a.sayHello() Polymorphism - Overriding the Functionality of a Parent Class class Dog: # Parent class species = 'mammal' # Class Attribute def speak(self): print("I do not know what to say") class SomeBreed(Dog): # Child class (inherits Child classes can also override attributes from Dog class) and behaviors from the parent class def speak(self): print("speaks high") speak() species class SomeOtherBreed(Dog): # Child class (inherits from Dog class) species = 'reptile' def speak(self): print("speaks low") d= Dog() frank = SomeBreed() print(frank.species) beans = SomeOtherBreed() print(beans.species) d.speak() frank.speak() beans.speak() Data structure Dr. Asmaa Awad What are Data Structures? ▪ Data Structures are different ways of organizing data on your computer, that can be used effectively. ▪ A data structure is a way to store and organize data in order to facilitate the access and modifications Data Structure Types Data Structure Types ▪ Linear data structure: data elements are arranged sequentially or linearly, where each element is attached to its previous and next adjacent elements Static data structure: Static data structure has a fixed memory size. It is easier to access the elements in a static data structure. Dynamic data structure: In the dynamic data structure, the size is not fixed. It can be randomly updated during the runtime which may be considered efficient concerning the memory (space) complexity of the code. Data Structure Types ▪ Arrays: it is a collection of items stored at contiguous memory locations. It allows the processing of a large amount of data in a relatively short period. The first element of the array is indexed by a subscript of 0. Data Structure Types ▪ Stack: Stack is a linear data structure that follows a particular order in which the operations are performed. The order is LIFO(Last in first out). The entering and retrieving of data is also called push and pop operation in a stack. Data Structure Types ▪ Queue: Queue is a linear data structure that follows a particular order in which the operations are performed. The order is First In First Out(FIFO) i.e. the data item stored first will be accessed first. In this, entering and retrieving data is not done from only one end. Data Structure Types ▪ Linked list: A linked list is a linear data structure in which elements are not stored at contiguous memory locations. The elements in a linked list are linked using pointers as shown in the below image: Data Structure Types ▪ Tree: A tree is a non-linear and hierarchical data structure where the elements are arranged in a tree-like structure. In a tree, the topmost node is called the root node. Each node contains some data, and data can be of any type. It consists of a central node, structural nodes, and sub-nodes which are connected via edges. Data Structure Types ▪ Graph: A graph is a non-linear data structure that consists of vertices (or nodes) and edges. It consists of a finite set of vertices and set of edges that connect a pair of nodes. The graph is used to solve the most challenging and complex programming problems. Data Structure Operations ▪ Navigating Accessing each data element exactly once so that certain items in the data may be processed ▪ Searching Finding the location of the data element (Key) in the structure ▪ Insertion Adding a new data element to the structure Data Structure Operations ▪ Deletion Removing a data element from the structure ▪ Sorting Arrange the data elements in a logical order (ascending/descending) ▪ Merging Combining data elements from two or more data structures into one What is an Algorithm? ▪ An algorithm, is a step-by-step procedure for solving a problem in a finite amount of time. What is a good algorithm? ▪ It must be correct ▪ It must be finite (in terms of time and size) ▪ It must terminate ▪ It must be unambiguous Which step is next? ▪ It must be space and time efficient A program is an instance of an algorithm, written in some specific programming language What is Complexity? ▪ Complexity is a measure of how difficult a problem or a solution is. ▪ There are two types of complexity that are relevant for data structures: time complexity and space complexity. ▪ Time complexity measures how long it takes to execute an operation on a data structure, such as inserting, deleting, searching, or sorting. ▪ Space complexity measures how much memory or storage it takes to store a data structure. ▪ Both time and space complexity depend on the size of the input, which is usually denoted by n. Measurement of Complexity of an Algorithm 1. Best Case Analysis (Omega Notation) ▪ In the best-case analysis, we calculate the lower bound on the running time of an algorithm. ▪ We must know the case that causes a minimum number of operations to be executed. ▪ In the linear search problem, the best case occurs when x is present at the first location. Measurement of Complexity of an Algorithm 2. Worst Case Analysis (Big-O Notation) ▪ In the worst-case analysis, we calculate the upper bound on the running time of an algorithm. (maximum number of operations to be executed.) ▪ For Linear Search, the worst case happens when the element to be searched (x) is not present in the array. When x is not present, the search() function compares it with all the elements of arr[] one by one. ▪ The worst-case time complexity of the linear search would be O(n). Measurement of Complexity of an Algorithm ▪ 3. Average Case Analysis (Theta Notation) ▪ In average case analysis, we take all possible inputs and calculate the computing time for all of the inputs. ▪ Sum all the calculated values and divide the sum by the total number of inputs. We must know (or predict) the distribution of cases. ▪ For the linear search problem,we sum all the cases and divide the sum by (n+1). Following is the value of average-case time complexity. Complexity ▪ Big O notation is a powerful tool used in computer science to describe the time complexity or space complexity of algorithms. ▪ It provides a standardized way to compare the efficiency of different algorithms in terms of their worst-case performance. ▪ Big O notation is essential for analyzing and designing efficient algorithms. Complexity Complexity Complexity (O(n)) ▪ Sum number from 1 to n //O(n) //O(1) ▪ Time complexity = 1+n=O(n) Complexity (O(1)) ▪ Sum number from 1 to n //O(n) Formula: //O(1) Sum=n*(n+1)/2 //O(1) ▪ Time complexity = 1+n=O(n) Complexity Complexity ▪ In O(log n) algorithms , the execution time grows logarithmically with respect to the input size.an example is binary search. (increase multiply or division ) //O(log(n)) //O(1) //O(1) ▪ Time complexity =1+log(n)=O( log2 ) Complexity Complexity (O( )) ▪ Example : nested loop //O(n) //O(n) //O(1) ▪ Time complexity =1+n*n=O( 2) Complexity Complexity (n*log(n)) ▪ Example : //O(n) //O(log(n)) //O(1) //O(1) ▪ Time complexity =1+n*log(n)=O(n*log(n)) Frequency count method Frequency count method Big-O Notation Big-O Notation O(1) - Constant Time: Accessing an element by index in a list. Big-O Notation O(n) - Linear Time: Summing all elements in a list. Big-O Notation O(n^2) - Quadratic Time: Bubble Sort, where you compare every element with every other element. Big-O Notation O(log n) - Logarithmic Time: Big-O Notation O(n*log n) time complexity of O(n2) Selection Sort Selection Sort algorithm, which sorts an array by repeatedly finding the minimum element from the unsorted part and moving it to the beginning. The algorithm divides the array into two subarrays: 1.Sorted array 2.Unsorted array In each iteration, it selects the minimum element from the unsorted subarray and moves it to the sorted subarray. This process continues until the array is fully sorted. Selection Sort Recursion & Search Prepared By: Dr. Asmaa Awad Factorial Factorial (5)=5*4*3*2*1=120 O(n) 2 Factorial Factorial (5)=5*4*3*2*1=120 O(n) OR Recursion 3 Content What is Recursion? Why Recursion Recursion Function Example Recursion & Iterative Using Recursion in Binary Search 4 Recursion Recursion: A technique for solving a large computational problem by repeatedly applying the same procedure to reduce it to successively smaller problems. Recursion: Recursion refers to a programming technique in which a function calls itself either directly or indirectly. Recursion: Recursion is a common mathematical and programming concept. 5 Recursion Definition: Recursion is a process in which a function calls itself directly or indirectly. Key Concept: Each recursive function has two main parts: Base Case: Stops the recursion when a specific condition is met. Recursive Case: The function calls itself with modified parameters to approach the base case. Note: Without a base case, a recursive function will result in infinite recursion. 6 Why learn recursion? "cultural experience" - A different way of thinking of problems Can solve some kinds of problems better than iteration Leads to elegant, simplistic, short code (when used well) Many programming languages ("functional" languages such as Scheme, ML, and Haskell) use recursion exclusively (no loops) 7 The idea Recursion is all about breaking a big problem into smaller occurrences of that same problem. – Each person can solve a small part of the problem. What is a small version of the problem that would be easy to answer? What information from a neighbor might help me? 8 Recursive algorithm Number of people behind me: – If there is someone behind me, ask him/her how many people are behind him/her. When they respond with a value N, then I will answer N + 1. – If there is nobody behind me, I will answer 0. 9 How Recursive Works Overview of how recursive function works: Recursive function is called by some external code. If the base condition is met then the program do something meaningful and exits. Otherwise, function does some required processing and then call itself to continue recursion. Here is an example of recursive function used to calculate factorial. Example: Factorial is denoted by number followed by (!) sign i.e 5! Steps: 10 Recursive Function Example O(N) 11 Fibonacci Function 0 1 1 2 3 5 8 12 12 Fibonacci Function Writing a Recursive Function: The Fibonacci numbers are easy to write as a Python function. It’s more or less a one to one mapping from the mathematical definition: 13 Fibonacci Function 14 Searching Algorithms in Python 15 Searching Algorithms Searching is an operation or a technique that helps find the place of a given element or value in the list. Any search is said to be successful or unsuccessful depending upon whether the element that is being searched is found or not. Searching Algorithms Linear Search. Binary Search. 16 Linear search Linear search is the simplest searching algorithm. It sequentially checks each element of the list until it finds the target value. Steps: Start from the first element of the list. Compare each element of the list with the target value. If the element matches the target value, return its index. If the target value is not found after iterating through the entire list, return -1. 17 Linear Search O(n) 18 Binary Search 19 Binary Search 20 Binary Search 21 Binary Search 22 Binary Search Binary search is a more efficient searching algorithm suitable for sorted lists. It repeatedly divides the search interval in half until the target value is found. Steps: 1.Start with the entire sorted list. 2.Compute the middle element of the list. 3.If the middle element is equal to the target value, return its index. 4.If the middle element is less than the target value, search in the right half of the list. 5.If the middle element is greater than the target value, search in the left half of the list. 6.Repeat steps 2-5 until the target value is found or the search interval is empty. 23 Binary Search O(log(n)) 24 Binary Search O(log(n)) 25 Binary Search Advantages of Binary Search Binary search is faster than linear search, especially for large arrays. More efficient than other searching algorithms with a similar time complexity, such as interpolation search or exponential search. Binary search is well-suited for searching large datasets that are stored in external memory, such as on a hard drive or in the cloud. Disadvantages of Binary Search The array should be sorted. Binary search requires that the data structure being searched be stored in contiguous memory locations. Binary search requires that the elements of the array be comparable, meaning that they must be able to be ordered. 26