├── .gitignore
├── 10_tree_example.py
├── 1_queue_example.py
├── 2_stack_example.py
├── 4_doubly_linked_list_example.py
├── 5_singly_linked_list_example.py
├── 6_selection_sort.py
├── 7_insertion_sort.py
├── 8_bubble_sort_example.py
├── 9_recursive_algorithm_example.py
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,git
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,git
3 |
4 | ### Git ###
5 | # Created by git for backups. To disable backups in Git:
6 | # $ git config --global mergetool.keepBackup false
7 | *.orig
8 |
9 | # Created by git when using merge tools for conflicts
10 | *.BACKUP.*
11 | *.BASE.*
12 | *.LOCAL.*
13 | *.REMOTE.*
14 | *_BACKUP_*.txt
15 | *_BASE_*.txt
16 | *_LOCAL_*.txt
17 | *_REMOTE_*.txt
18 |
19 | ### PyCharm ###
20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
21 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
22 |
23 | # User-specific stuff
24 | .idea/**/workspace.xml
25 | .idea/**/tasks.xml
26 | .idea/**/usage.statistics.xml
27 | .idea/**/dictionaries
28 | .idea/**/shelf
29 |
30 | # AWS User-specific
31 | .idea/**/aws.xml
32 |
33 | # Generated files
34 | .idea/**/contentModel.xml
35 |
36 | # Sensitive or high-churn files
37 | .idea/**/dataSources/
38 | .idea/**/dataSources.ids
39 | .idea/**/dataSources.local.xml
40 | .idea/**/sqlDataSources.xml
41 | .idea/**/dynamic.xml
42 | .idea/**/uiDesigner.xml
43 | .idea/**/dbnavigator.xml
44 |
45 | # Gradle
46 | .idea/**/gradle.xml
47 | .idea/**/libraries
48 |
49 | # Gradle and Maven with auto-import
50 | # When using Gradle or Maven with auto-import, you should exclude module files,
51 | # since they will be recreated, and may cause churn. Uncomment if using
52 | # auto-import.
53 | # .idea/artifacts
54 | # .idea/compiler.xml
55 | # .idea/jarRepositories.xml
56 | # .idea/modules.xml
57 | # .idea/*.iml
58 | # .idea/modules
59 | # *.iml
60 | # *.ipr
61 |
62 | # CMake
63 | cmake-build-*/
64 |
65 | # Mongo Explorer plugin
66 | .idea/**/mongoSettings.xml
67 |
68 | # File-based project format
69 | *.iws
70 |
71 | # IntelliJ
72 | out/
73 |
74 | # mpeltonen/sbt-idea plugin
75 | .idea_modules/
76 |
77 | # JIRA plugin
78 | atlassian-ide-plugin.xml
79 |
80 | # Cursive Clojure plugin
81 | .idea/replstate.xml
82 |
83 | # SonarLint plugin
84 | .idea/sonarlint/
85 |
86 | # Crashlytics plugin (for Android Studio and IntelliJ)
87 | com_crashlytics_export_strings.xml
88 | crashlytics.properties
89 | crashlytics-build.properties
90 | fabric.properties
91 |
92 | # Editor-based Rest Client
93 | .idea/httpRequests
94 |
95 | # Android studio 3.1+ serialized cache file
96 | .idea/caches/build_file_checksums.ser
97 |
98 | ### PyCharm Patch ###
99 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
100 |
101 | # *.iml
102 | # modules.xml
103 | # .idea/misc.xml
104 | # *.ipr
105 |
106 | # Sonarlint plugin
107 | # https://plugins.jetbrains.com/plugin/7973-sonarlint
108 | .idea/**/sonarlint/
109 |
110 | # SonarQube Plugin
111 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
112 | .idea/**/sonarIssues.xml
113 |
114 | # Markdown Navigator plugin
115 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
116 | .idea/**/markdown-navigator.xml
117 | .idea/**/markdown-navigator-enh.xml
118 | .idea/**/markdown-navigator/
119 |
120 | # Cache file creation bug
121 | # See https://youtrack.jetbrains.com/issue/JBR-2257
122 | .idea/$CACHE_FILE$
123 |
124 | # CodeStream plugin
125 | # https://plugins.jetbrains.com/plugin/12206-codestream
126 | .idea/codestream.xml
127 |
128 | # Azure Toolkit for IntelliJ plugin
129 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
130 | .idea/**/azureSettings.xml
131 |
132 | ### Python ###
133 | # Byte-compiled / optimized / DLL files
134 | __pycache__/
135 | *.py[cod]
136 | *$py.class
137 |
138 | # C extensions
139 | *.so
140 |
141 | # Distribution / packaging
142 | .Python
143 | build/
144 | develop-eggs/
145 | dist/
146 | downloads/
147 | eggs/
148 | .eggs/
149 | lib/
150 | lib64/
151 | parts/
152 | sdist/
153 | var/
154 | wheels/
155 | share/python-wheels/
156 | *.egg-info/
157 | .installed.cfg
158 | *.egg
159 | MANIFEST
160 |
161 | # PyInstaller
162 | # Usually these files are written by a python script from a template
163 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
164 | *.manifest
165 | *.spec
166 |
167 | # Installer logs
168 | pip-log.txt
169 | pip-delete-this-directory.txt
170 |
171 | # Unit test / coverage reports
172 | htmlcov/
173 | .tox/
174 | .nox/
175 | .coverage
176 | .coverage.*
177 | .cache
178 | nosetests.xml
179 | coverage.xml
180 | *.cover
181 | *.py,cover
182 | .hypothesis/
183 | .pytest_cache/
184 | cover/
185 |
186 | # Translations
187 | *.mo
188 | *.pot
189 |
190 | # Django stuff:
191 | *.log
192 | local_settings.py
193 | db.sqlite3
194 | db.sqlite3-journal
195 |
196 | # Flask stuff:
197 | instance/
198 | .webassets-cache
199 |
200 | # Scrapy stuff:
201 | .scrapy
202 |
203 | # Sphinx documentation
204 | docs/_build/
205 |
206 | # PyBuilder
207 | .pybuilder/
208 | target/
209 |
210 | # Jupyter Notebook
211 | .ipynb_checkpoints
212 |
213 | # IPython
214 | profile_default/
215 | ipython_config.py
216 |
217 | # pyenv
218 | # For a library or package, you might want to ignore these files since the code is
219 | # intended to run in multiple environments; otherwise, check them in:
220 | # .python-version
221 |
222 | # pipenv
223 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
224 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
225 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
226 | # install all needed dependencies.
227 | #Pipfile.lock
228 |
229 | # poetry
230 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
231 | # This is especially recommended for binary packages to ensure reproducibility, and is more
232 | # commonly ignored for libraries.
233 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
234 | #poetry.lock
235 |
236 | # pdm
237 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
238 | #pdm.lock
239 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
240 | # in version control.
241 | # https://pdm.fming.dev/#use-with-ide
242 | .pdm.toml
243 |
244 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
245 | __pypackages__/
246 |
247 | # Celery stuff
248 | celerybeat-schedule
249 | celerybeat.pid
250 |
251 | # SageMath parsed files
252 | *.sage.py
253 |
254 | # Environments
255 | .env
256 | .venv
257 | env/
258 | venv/
259 | ENV/
260 | env.bak/
261 | venv.bak/
262 |
263 | # Spyder project settings
264 | .spyderproject
265 | .spyproject
266 |
267 | # Rope project settings
268 | .ropeproject
269 |
270 | # mkdocs documentation
271 | /site
272 |
273 | # mypy
274 | .mypy_cache/
275 | .dmypy.json
276 | dmypy.json
277 |
278 | # Pyre type checker
279 | .pyre/
280 |
281 | # pytype static type analyzer
282 | .pytype/
283 |
284 | # Cython debug symbols
285 | cython_debug/
286 |
287 | # PyCharm
288 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
289 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
290 | # and can be added to the global gitignore or merged into this file. For a more nuclear
291 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
292 | #.idea/
293 |
294 | ### Python Patch ###
295 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
296 | poetry.toml
297 |
298 | # ruff
299 | .ruff_cache/
300 |
301 | # LSP config files
302 | pyrightconfig.json
303 |
304 | # End of https://www.toptal.com/developers/gitignore/api/python,pycharm,git
305 |
306 | /.idea
307 |
--------------------------------------------------------------------------------
/10_tree_example.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | class TreeNode:
5 |
6 | def __init__(self, value: Any, parent: Any | None = None) -> None:
7 | self.parent = parent
8 | self.value = value
9 | self.__children = []
10 |
11 | def add_child(self, value) -> None:
12 | new_child = TreeNode(value=value, parent=self)
13 | self.__children.append(new_child)
14 |
15 | def children(self) -> list:
16 | return self.__children
17 |
18 | def get_all_children(self) -> list:
19 | all_children = []
20 | depth_count = 0
21 | for child in self.__children:
22 | depth_count += 1
23 | all_children.append(child)
24 | all_children.extend(child.get_all_children())
25 | return all_children
26 |
27 | def depth(self) -> int:
28 | count = 0
29 | node = self
30 | while node and node.parent:
31 | node = node.parent
32 | count += 1
33 | return count
34 |
35 | def get_depth(self) -> int:
36 | if not self.__children:
37 | return 0
38 | else:
39 | child_depths = [child.get_depth() for child in self.__children]
40 | return max(child_depths) + 1
41 |
42 | def get_height(self) -> int:
43 | if not self.__children:
44 | return 0
45 | else:
46 | child_heights = [child.get_height() for child in self.__children]
47 | return max(child_heights) + 1
48 |
49 | def remove(self) -> None:
50 | parent = self.parent
51 | children = self.children()
52 | if parent:
53 | parent.__children.extend(children)
54 | parent.__children.remove(self)
55 | del self
56 |
57 |
58 | class Tree:
59 |
60 | def __init__(self, value: Any) -> None: self.__root = self.__create_root(value=value)
61 |
62 | @staticmethod
63 | def __create_root(value: Any) -> TreeNode: return TreeNode(value=value)
64 |
65 | @property
66 | def root(self):
67 | return self.__root
68 |
69 | def __str__(self): return f'root name: {self.__root.value}'
70 |
71 |
72 | if __name__ == '__main__':
73 | tree = Tree(value='root')
74 | tree.root.add_child(value='first_child')
75 | tree.root.add_child(value='second_child')
76 | print(tree.root.children())
77 | tree.root.children()[0].add_child(value='third_children')
78 |
79 | print(tree.root.get_all_children())
80 | print(tree.root.get_all_children()[0].children()[0].depth())
81 | print(tree.root.get_all_children()[0].remove())
82 | print(tree.root.get_all_children())
83 | print(tree.root.get_depth())
84 |
--------------------------------------------------------------------------------
/1_queue_example.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | class Queue(list):
5 |
6 | def add(self, value) -> None:
7 | self.append(value)
8 |
9 | def size(self) -> int:
10 | return len(self)
11 |
12 | def get_first(self) -> Any:
13 | try:
14 | return self[0]
15 | except IndexError:
16 | raise IndexError('Queue index out of range')
17 |
18 | def get_last(self) -> Any:
19 | try:
20 | return self[-1]
21 | except IndexError:
22 | raise IndexError('Queue index out of range')
23 |
24 | def take(self) -> Any:
25 | first = self.get_first()
26 | del self[0]
27 | return first
28 |
29 | def head(self) -> Any:
30 | return self.get_first()
31 |
32 | def tail(self) -> Any:
33 | return self.get_last()
34 |
35 |
36 | if __name__ == '__main__':
37 | pass
38 |
--------------------------------------------------------------------------------
/2_stack_example.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | class Stack(list):
5 |
6 | def add(self, value) -> None:
7 | self.append(value)
8 |
9 | def size(self) -> int:
10 | return len(self)
11 |
12 | def get_last(self) -> Any:
13 | try:
14 | return self[-1]
15 | except IndexError:
16 | raise IndexError('Stack index out of range')
17 |
18 | def pop(self) -> Any:
19 | try:
20 | return super().pop()
21 | except IndexError:
22 | raise IndexError('pop from empty stack.')
23 |
24 | def top(self) -> Any:
25 | return self.get_last()
26 |
27 |
28 | if __name__ == '__main__':
29 | pass
30 |
--------------------------------------------------------------------------------
/4_doubly_linked_list_example.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | class Node:
5 |
6 | def __init__(self, data: Any) -> None:
7 | self.data = data
8 | self.next = None
9 | self.pre = None
10 |
11 | def __str__(self):
12 | return f'data: {self.data}'
13 |
14 |
15 | class DoubleLinkedList:
16 |
17 | def __init__(self) -> None:
18 | self.__head = None
19 | self.__tail = None
20 | self.__size = 0
21 |
22 | def size(self) -> int:
23 | return self.__size
24 |
25 | def __update_size(self) -> None:
26 | self.__size += 1
27 |
28 | def insure_index(self, index: int) -> None:
29 | if index > self.size() - 1:
30 | raise IndexError
31 |
32 | def head(self) -> Node:
33 | return self.__head
34 |
35 | def tail(self) -> Node:
36 | return self.__tail
37 |
38 | def __set_head(self, node: Node) -> None:
39 | self.__head = node
40 |
41 | def __set_tail(self, node: Node) -> None:
42 | self.__tail = node
43 |
44 | def append(self, data: Any) -> None:
45 | new_node = Node(data=data)
46 |
47 | if self.head() is None and self.tail() is None:
48 | self.__set_head(node=new_node)
49 | self.__set_tail(node=new_node)
50 | self.__update_size()
51 | return
52 |
53 | self.tail().next = new_node
54 | new_node.pre = self.tail()
55 | self.__set_tail(node=new_node)
56 | self.__update_size()
57 |
58 | def get_node(self, index: int) -> Node:
59 | self.insure_index(index=index)
60 |
61 | count = 0
62 | current = self.head()
63 |
64 | while index != count:
65 | current = current.next
66 | count += 1
67 |
68 | return current
69 |
70 | def get(self, index: int) -> Any:
71 | node = self.get_node(index=index)
72 | return node.data
73 |
74 | def remove(self, index: int) -> None:
75 | current = self.get_node(index=index)
76 |
77 | if current.pre:
78 | current.pre.next = current.next
79 | else:
80 | self.__set_head(node=current.next)
81 |
82 | if current.next:
83 | current.next.pre = current.pre
84 | else:
85 | self.__set_tail(node=current.pre)
86 |
87 | del current
88 |
89 | def insert(self, index: int, data: Any) -> None:
90 | current = self.get_node(index=index)
91 |
92 | new_node = Node(data=data)
93 | new_node.next = current
94 | new_node.pre = current.pre
95 |
96 | if current.next:
97 | pass
98 |
99 | if current.pre:
100 | current.pre.next = new_node
101 | else:
102 | self.__set_head(node=new_node)
103 |
104 | self.__update_size()
105 |
106 | def pop(self):
107 | if self.tail():
108 | if self.tail().pre:
109 | self.tail().pre.next = None
110 | self.__set_tail(node=self.tail().pre)
111 | data = self.tail().data
112 | else:
113 | data = self.tail().data
114 | self.__head = None
115 | self.__tail = None
116 |
117 | return data
118 |
119 | return ValueError('Double linked list is empty.')
120 |
121 | def __str__(self) -> str:
122 | my_list = []
123 |
124 | current = self.head()
125 |
126 | while current:
127 | my_list.append(current.data)
128 | current = current.next
129 |
130 | return str(my_list)
131 |
132 |
133 | if __name__ == '__main__':
134 | d_list = DoubleLinkedList()
135 |
136 | d_list.append(data='a')
137 | d_list.append(data='b')
138 | d_list.append(data='c')
139 |
140 | d_list.insert(index=1, data='d')
141 | d_list.insert(index=1, data='j')
142 | d_list.remove(index=1)
143 | d_list.remove(index=1)
144 | d_list.remove(index=1)
145 |
146 | print(d_list)
147 |
148 | # print(d_list.get(index=0))
149 | print(d_list.pop())
150 | print(d_list)
151 | print(d_list.pop())
152 | print(d_list.pop())
153 |
--------------------------------------------------------------------------------
/5_singly_linked_list_example.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional
2 |
3 |
4 | class Node:
5 | def __init__(self, data):
6 | self.data = data
7 | self.next = None
8 |
9 | def __str__(self):
10 | return f'data: {self.data}'
11 |
12 |
13 | class SinglyLinkedList:
14 |
15 | def __init__(self) -> None:
16 | self.__head = None
17 | self.__tail = None
18 | self.__size = 0
19 |
20 | def size(self) -> int:
21 | return self.__size
22 |
23 | def __update_size(self) -> None:
24 | self.__size += 1
25 |
26 | def insure_index(self, index: int) -> None:
27 | if index > self.size() - 1:
28 | raise IndexError
29 |
30 | def head(self) -> Node:
31 | return self.__head
32 |
33 | def tail(self) -> Node:
34 | return self.__tail
35 |
36 | def __set_head(self, node: Node) -> None:
37 | self.__head = node
38 |
39 | def __set_tail(self, node: Node) -> None:
40 | self.__tail = node
41 |
42 | def append(self, data: Any) -> None:
43 | new_node = Node(data=data)
44 |
45 | if self.head() is None:
46 | self.__set_head(node=new_node)
47 |
48 | if self.tail() is None:
49 | self.__set_tail(node=new_node)
50 | else:
51 | self.tail().next = new_node
52 | self.__set_tail(node=new_node)
53 |
54 | self.__update_size()
55 |
56 | def get_node(self, index: int) -> Node:
57 | self.insure_index(index=index)
58 |
59 | count = 0
60 | current = self.head()
61 |
62 | while index != count:
63 | current = current.next
64 | count += 1
65 |
66 | return current
67 |
68 | def get(self, index: int) -> Any:
69 | node = self.get_node(index=index)
70 | return node.data
71 |
72 | def set(self, index: int, data: Any) -> None:
73 | self.insure_index(index=index)
74 |
75 | count = 0
76 | current = self.head()
77 |
78 | while index != count:
79 | current = current.next
80 | count += 1
81 |
82 | current.data = data
83 |
84 | def remove(self, index: int) -> None:
85 | self.insure_index(index=index)
86 |
87 | count = 0
88 | previous = None
89 | current = self.head()
90 |
91 | while index != count:
92 | previous = current
93 | current = current.next
94 | count += 1
95 |
96 | if previous:
97 | previous.next = current.next
98 | else:
99 | self.__set_head(current.next)
100 | del current
101 |
102 | def pop(self, index: int = 0) -> Any:
103 | self.insure_index(index=index)
104 |
105 | count = 0
106 | previous = None
107 | current = self.head()
108 |
109 | while index != count:
110 | previous = current
111 | current = current.next
112 | count += 1
113 |
114 | if previous:
115 | previous.next = current.next
116 | else:
117 | self.__set_head(node=current.next)
118 |
119 | data = current.data
120 | del current
121 | return data
122 |
123 | def __str__(self) -> str:
124 | my_list = []
125 |
126 | current = self.head()
127 |
128 | while current:
129 | my_list.append(current.data)
130 | current = current.next
131 |
132 | return str(my_list)
133 |
134 |
135 | if __name__ == '__main__':
136 | singly_list = SinglyLinkedList()
137 | singly_list.append(data='eggs')
138 | singly_list.append(data='bread')
139 | singly_list.append(data='cereal')
140 | singly_list.set(index=0, data='boobs')
141 | # singly_list.remove(3)
142 | singly_list.pop()
143 | singly_list.pop()
144 | print(singly_list)
145 |
146 |
--------------------------------------------------------------------------------
/6_selection_sort.py:
--------------------------------------------------------------------------------
1 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
2 |
3 | print(unsorted_list)
4 |
5 |
6 | for i in range(len(unsorted_list)):
7 | min_index = i
8 | for j in range(i + 1, len(unsorted_list)):
9 | if unsorted_list[j] < unsorted_list[min_index]:
10 | min_index = j
11 |
12 | (unsorted_list[i], unsorted_list[min_index]) = (unsorted_list[min_index], unsorted_list[i])
13 |
14 | print(unsorted_list)
15 |
--------------------------------------------------------------------------------
/7_insertion_sort.py:
--------------------------------------------------------------------------------
1 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
2 |
3 |
4 | for i in range(1, len(unsorted_list)):
5 |
6 | temp = unsorted_list[i]
7 |
8 | j = i - 1
9 | while j >= 0 and temp < unsorted_list[j]:
10 | unsorted_list[j + 1] = unsorted_list[j]
11 | j -= 1
12 | unsorted_list[j + 1] = temp
13 |
14 |
15 | print(unsorted_list)
16 |
--------------------------------------------------------------------------------
/8_bubble_sort_example.py:
--------------------------------------------------------------------------------
1 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
2 |
3 | print(unsorted_list)
4 |
5 |
6 | for _ in range(1, len(unsorted_list)):
7 | for j in range(0, len(unsorted_list) - 1):
8 | if unsorted_list[j] > unsorted_list[j + 1]:
9 | unsorted_list[j], unsorted_list[j + 1] = unsorted_list[j + 1], unsorted_list[j]
10 |
11 |
12 | print(unsorted_list)
13 |
--------------------------------------------------------------------------------
/9_recursive_algorithm_example.py:
--------------------------------------------------------------------------------
1 | def rec_factorial(number) -> int:
2 | if number == 1:
3 | return number
4 |
5 | return number * rec_factorial(number - 1)
6 |
7 |
8 | print(rec_factorial(number=1000))
9 |
10 |
11 | def sum_int_list(array: list, length: int) -> int:
12 | if length <= 0:
13 | return 0
14 | return array[length - 1] + sum_int_list(array=array, length=length - 1)
15 |
16 |
17 | def find_number(array: list, number: int) -> bool:
18 | if len(array) == 1:
19 | return bool(array[0] == number)
20 |
21 | half_len = len(array) // 2
22 | if array[half_len] == number:
23 | return True
24 |
25 | if array[half_len] < number:
26 | return find_number(array=array[half_len:], number=number)
27 | else:
28 | return find_number(array=array[0:half_len], number=number)
29 |
30 |
31 | def find_max(array: list):
32 | if len(array) == 1:
33 | return array[0]
34 |
35 | half_len = len(array) // 2
36 | first_half = find_max(array=array[:half_len])
37 | second_half = find_max(array=array[half_len:])
38 |
39 | return max([first_half, second_half])
40 |
41 |
42 | if __name__ == '__main__':
43 | # print(find_number([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], number=5))
44 | print(find_max(array=[1, 2, 5, 4, 88, 41, 6, 9, 0, 11, -666]))
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 |
6 | 1-Data Structures
7 |
8 | 2-Array
9 |
10 | 3-Array List
11 |
12 | 4-Queue
13 |
14 | 5-Stack
15 |
16 | 6-Python list
17 |
18 | 7-How list work in python?
19 |
20 | 8-Introducing the linked list
21 |
22 | 9-Types of linked list
23 |
24 | 10-Singly linked list
25 |
26 | 11-Doubly linked list
27 |
28 | 12-Sorting algorithms
29 |
30 | 13-Selection sort
31 |
32 | 14-Insertion sort
33 |
34 | 15-Bubble sort
35 |
36 |
37 |
38 |
39 |
ساختمان داده (Data Structure) چیست؟
40 |
41 | تا الان هممون حداقل یک مفهوم سطحی از data structure رو یاد گرفتیم.
42 | توی ویکی پدیا و اکثر وب سایت ها همون توضیح کوتاه رو میده.
43 |
44 | اما بیاید یکم عمیق تر شیم
45 |
46 | خب Structure که مشخصه یک آرایش، ساختار، سازمان دهی کردن هستش.
47 |
48 | دیتا (Data) چیه؟
49 |
50 | برای درک دیتا بهتره کمی درباره ساز و کار کامپیوتر بدونیم.
51 |
52 | قلب تپنده کامپیوتر CPU هستش
53 | سی پی یو یک چیزی داره به اسم ALU کار اصلی ایشون در واقع انجام محاسبه ها و انجام عملیات های منطقی روی داده ها توی پردازنده اصلی یا همون CPU هستش.
54 |
55 | داده ها از کجا میان ؟
56 | داده ها از main memory یا همون رم سیستم میان.
57 | تا اونجا که خودتون در جریانید کامپیوتر ها باینری هستن پس اینطور در نظر بگیرید ما کلی صفر و یک توی رم داریم.
58 |
59 | نکته ای که باید توجه کنید اینه کامپیوتر ذاتا فقط عدد میفهمه
60 |
61 | اگه شما با هر زبان برنامه نویسی کار کرده باشید یک چیزی وجود داره به اسم انواع داده (data types)
62 |
63 | هر زبان برنامه نویسی یک سری انواع داده اصلی رو میده.
64 |
65 | اولین نوع داده ای که میشه اشاره کرد Boolean هستش. اینطوریه که اگه صفر بود false هستش و اگه غیر صفر بود true مثلا یک باشه true هستش
66 | کامپیوتر هم اینو خیلی راحت میفهمه، بیسیک ترین نوع داده هستش که با یه بیت هم میشه نمایشش داد
67 |
68 | انواع اعداد رو داریم مثلا int، short, long
69 |
70 | اعداد اعشاری رو داریم مثل float و double
71 |
72 | خب تا اینجا هر نوع تایپی که اشاره کردیم عدد بود و همچنین کل چیزی که CPU میفهمه اعداد هستش
73 |
74 | ولی خب سوال اینجاست. ما که فقط اعداد نداریم مثلا چیزی به اسم رشته (String) رو داریم. پس چطور باهاش کار می کنیم؟
75 |
76 | رشته چطوری ساخته میشه؟
77 | رشته به این شکل ساخته میشه که میایم بر اساس هر حرف عددی رو تعریف میکنیم. اگه یادتون باشه چیزی به اسم جدول ascii codes داریم که توی این جدول مشخص شده مثلا A میشه 65
78 | پس چیزی که توی حافظه برای رشته نوشتیم در واقع عدد هستش
79 |
80 | اگه همه چی عدد هستش. چجوری داده های پیچیده تر رو میسازیم؟
81 |
82 | برای ساخت داده های پیچیده تر میتونیم از composition یعنی ترکیب کردن استفاده کنیم.
83 | یعنی میایم چند تا نوع داده رو با هم ترکیب میکنیم یک نوع داده پیچیده تر رو میسازیم.
84 |
85 | فرض کنید میخوایم اطلاعات دانش آموز رو ذخیره کنیم. برای اینکار نیازه که ما چند توع دیتا تایپ رو با هم ترکیب کنیم تا بتونیم دانشجو رو بسازیم. مثلا دانشجو نام داره که String هستش،کد دانشجویی داره و اطلاعات دیگه. پس چیزی که ما میسازیم به این
86 |
87 |
88 |
89 | ```Java
90 | name: str
91 | Student code: int
92 | age: int
93 | ```
94 |
95 |
96 | تشکیل می شود. توی اینجا ما به Student میگیم Struct که البته توی زمان C بیشتر می شنوید.
97 |
98 | Aggregation:
99 |
100 | یک روش دیگه هم برای مدریت و ساخت انواع جدید، aggregation (البته چند روش دیگه هم داریم) هست. توی Aggregation یک سری از آبجکت ها، اشیا رو کنار هم قرار میدیم تا یک چیز بزرگ تری رو بسازیم
101 |
102 | مثلا تعدادی دانشجو رو کنار هم قرار میدیم و کلاس رو میسازیم
103 |
104 | این آبجکت هایی که کنار هم دیگه میذاریم خودشون بصورت مستقل شخصیت دارن مثلا شما دانشجو رو فرض کنید. بصورت جداگانه هم شخصیت داره اما حالا ما چیزی داریم به اسم کلاس که داخل خودش دانشجو هایی
105 | که توی کلاس هستن رو داره. به این کار که ما آبجکت های مستقل رو کنار هم میذاریم میگن تجمیع و یا Aggregation کردن.
106 |
107 | حالا وقتی که میایم آبجکت ها رو کنار هم قرار میدیم به ساختار و آرایشی نیاز داریم که بتونیم این آبجکت ها رو کنار همدیگه قرار بدیم. اینجاست که ساختمان داده ها (Data structures) به دادمون میرسه🥰
108 |
109 | در واقع اینجا نیاز اینکه ما باید ساختار داده ای و یا ساختار های داده داشته باشیم حس شد و داستان ما از اینجا شروع میشه.
110 |
111 |
112 |
انواع ساختمان داده
113 | ما انواع مختلفی از ساختمان داده داریم که هر کدومشون در جای مناسب کاربرد دارن. در ادامه چند نوع ساختمان داده رو بررسی میکنیم و مثال از نحوه پیاده
114 | کردنش توی پایتون براتون میزنم.
115 |
116 | ساختمان داده آرایه(Array)
117 | پایه ای ترین نوع ساختمان داده هستش.
118 | توی آرایه ما می تونیم set کنیم چیزی رو اضافه کنیم و یا get کنیم و آبجکتی رو بگیریم و تمام.
119 |
120 | مثلا:
121 |
122 |
123 | ```Java
124 | [1, 2, 5, 9]
125 |
126 | set(1, 9) ---> [1, 9, 5, 9]
127 |
128 | get(0) -----> 1
129 | ```
130 |
131 |
132 | همچنین باید lenght آرایه رو موقع تعریف کردنش مشخص کنیم که چقدر طول داره.
133 |
134 | آرایه ها خیلی کاربردی هستن ولی یک سری محدودیت ها و معایبی رو دارن. مثلا:
135 |
136 | شما باید موقع تعریف کردنش مشخص کنید که چقدر طول داره.
137 | اگه بخوایم یک عنصری رو توی index مشخص شده جا بدیم در واقع insert کنیم، قابلیتش رو نداره یا اگه بخوایم عنصری رو از index مشخص پاک کنیم بازم قابلیتش رو نداره.
138 |
139 | برای حل این مشکلات ساختمان داده ای به اسم Array list و یا تو بعضی از زبان های برنامه نویسی بهش «وکتور» میگیم که توی پایتون بهش لیست میگیم.
140 |
141 |
ساختمان داده Array List
142 | ساختمان داده Array list رو میتونیم با استفاده از Array پیاده کنیم.
143 |
144 | یک Array list این ویژگی هارو باید داشته باشه:
145 |
146 | بتونیم insert, remove, add, get_size رو انجام بدیم.
147 |
148 | همونطور که اشاره کردیم آرایه در ابتدا اینکه چقدر طول داشته باشه رو از ما میگیره و قابل تغییر دادن هم که نیسن. پس احتمالا حدس زدید کاری که انجام میدیم اینه که
149 | توی Array list اول از همه بیشتر از فضایی که نیازه رو در نظر میگیریم و خالی قرار میدیمش.
150 | بعد کاری که میکنیم اینه وقتی چیزی add میشه ما میایم تو آخر آرایه اون رو اضافه میکنیم.
151 |
152 | اینکه آخر آرایه بلاک چندم هستش رو با استفاده از size که مشخص کردیم میفهمیم. یعنی مثلا اگه یک لیست با سه عنصر بوجود آوردیم سایزش میشه سه و اگه چیزی رو add کنیم با توجه به اینکه
153 | سایز ما سه بوده پس توی size + 1 ذخیرش میکنیم.
154 | پس در کل نحوه ساخت یک آرایه لیست به این صورت است.
155 |
156 | توی Array list ما یک سری مشکلات رو هم داریم. مثلا خیلی از وقتا یا O(n)
157 | مواجه میشویم. برای مثال اگه size لیست ما به اندازه طولی که مشخص کرده بودیم رسید. اونوقت باید بیایم کل داده رو توی یک Array دیگه که طول بزرگ تری دارد کپی کنیم و یا زمانی که index صفر را remove میکنیم اتفاقی که می افتد این است تمامی عناصر یک index
158 | عقب تر می روند که کار ساده ای نیست.
159 |
160 | نحوه پیاده سازی یک Array List رو بصورت عملی انجام نمیدم. بنظرم توضیحات کافیه
161 |
162 |
163 | صف (Queue)
164 | وقتی حرف از صف زده میشه همیشه یاد صف نونوایی بیوفتید. هر کی اول وارد شده، اولم نون میگیره. توی صف ما میگیم FIFO(First input first output)
165 | کسی که اول وارد شده اول هم خارج میشه.
166 | اما خب صف رو چجوری میتونیم پیادش کنیم؟
167 |
168 | صف رو میتونیم با استفاده از Array list پیادش کنیم. در واقع میایم از دیزاین پترن Adapter استفاده میکنیم و لیست رو سبک و سنگینش میکنیم تا
169 | برای ما کاربرد صف رو داشته باشه
170 |
171 | صف یه سری متد ها رو باید داشته باشه. مثل:
172 |
173 | متد add, take, head, tail, size
174 |
175 | متد add برای اضافه کردن به صف، متد take برای گرفتن اولین نفر و برگردوندنش و حذف کردنش از صف،
176 | متد head برای نشون دادن اولین نفری که توی صفحه و متد tail برای نشون دادن آخرین نفری که توی صف قرار داره و متد size برای دیدن اینکه
177 | صف ما حالا چند نفر منتظرن داخلش.
178 |
179 | نحوه پیاده کردن یک صف ساده توی پایتون با استفاده از لیست:
180 |
181 |
182 | ```Python
183 | from typing import Any
184 |
185 |
186 | class Queue(list):
187 |
188 | def add(self, value) -> None:
189 | self.append(value)
190 |
191 | def size(self) -> int:
192 | return len(self)
193 |
194 | def get_first(self) -> Any:
195 | try:
196 | return self[0]
197 | except IndexError:
198 | raise IndexError('Queue index out of range')
199 |
200 | def get_last(self) -> Any:
201 | try:
202 | return self[-1]
203 | except IndexError:
204 | raise IndexError('Queue index out of range')
205 |
206 | def take(self) -> Any:
207 | first = self.get_first()
208 | del self[0]
209 | return first
210 |
211 | def head(self) -> Any:
212 | return self.get_first()
213 |
214 | def tail(self) -> Any:
215 | return self.get_last()
216 | ```
217 |
218 |
219 |
پشته (Stack)
220 | پشته دقیقا برعکس صف هستش و یه جورایی ناعادلانست 😂
221 |
222 | در پشته (Stack)
223 | هر کی اول بره، اول بیرون نمیاد. هر کی آخر بره اول بیرون میاد. توی پشته میگیم LILO(Last input last output)
224 | یعنی همون هر کی آخر بره اول بیرون میاد
225 |
226 | یک سطح رو در نظر بگیرید که داخلش دیتا رو میریزیم. خب مشخصا وقتی میخوایم چیزی برداریم اونی که آخر وارد شده در دسترس هستش.
227 |
228 | برای پیاده سازی پشته مثل صف دوباره از لیست استفاده می کنیم. متد هایی که نیازه داشته باشیم اینا هستن:
229 |
230 | add, size, pop, top
231 |
232 | متد add برای اضافه کردن توی پشته، متد size برای دیدن سایز، متد pop برای گرفتن و حذف کردن آخرین استفاده میشه و متد top برای دیدن
233 | اونی که آخر از همه وارد شد.
234 |
235 | پیاده کردن یک پشته ساده با استفاده از لیست توی پایتون:
236 |
237 |
238 |
239 |
240 | ```Python
241 | from typing import Any
242 |
243 |
244 | class Stack(list):
245 |
246 | def add(self, value) -> None:
247 | self.append(value)
248 |
249 | def size(self) -> int:
250 | return len(self)
251 |
252 | def get_last(self) -> Any:
253 | try:
254 | return self[-1]
255 | except IndexError:
256 | raise IndexError('Stack index out of range')
257 |
258 | def pop(self) -> Any:
259 | try:
260 | return super().pop()
261 | except IndexError:
262 | raise IndexError('pop from empty stack.')
263 |
264 | def top(self) -> Any:
265 | return self.get_last()
266 | ```
267 |
268 |
269 |
دک (dequeue)
270 | دک رو میشه اینطور در نظر گرفت که صف و پشته رو با هم ترکیب کردیم. یعنی هم میتونیم مانند صف هر کی اول وارد شده، دریافتش کنیم و هم میتونیم مانند پشته هر کی که آخر وارد شده، دریافتش کنیم.
271 |
272 | نکته:
273 | وقتی که داریم از Array List برای صف و پشته استفاده میکنیم یه سری نکات هستش که باید دربارشون تصمیم بگیریم.
274 |
275 | توی صف وقتی take میکنیم و اولین کسی که وارد شده رو میگریم این اتفاق میوفته:
276 |
277 | اونی که ایندکسش مساوی با 0 هستش رو میگیریم.
278 | وقتی که حذف میکنیم همه عناصر یک ایندکس به عقب میرن. مثلا اگه صف ما سایزش یه میلیون باشه خودتون فکر کنید چقدر اینکار سربار اضافی بوجود میاره.
279 |
280 | برای حل این مشکل ما میتونیم از لیست های پیوندی استفاده کنیم.🥳
281 |
282 | بعد از توضیح ساختار لیست پایتون میریم سراغش...
283 |
284 | لیست پایتون
285 | ساختار داده ی لیست پایتون بصورت Dynamic Array و یا همون Array list هستند. به همین دلیل در مثال بالا که برای ساخت صف و پشته از لیست پایتون استفاده کردیم گفتبم که به مشکل o(n)
286 | برخورد میکنیم. چون Dynamic array ها بصورت متوالی دیتا رو ذخیره میکنن.
287 |
288 | یعنی وقتی که ما میایم ایندکس 0 رو حذف میکنیم کل عناصر لیست باید یک ایندکس به عقب بروند و یا موقعی که چیزی رو insert می کنیم باید عناصر بعد از آن ایندکس که اضافه کردیم یک ایندکس به جلو بروند و خب این خوب نیست.
289 |
290 | البته این رو هم در نظر بگیرید در تعداد پایین و سایز کم لیست ها خیلیم عالی هستند و خیلی کاربرد داره اما در big data پیشنهاد نمی شود.
291 |
292 | pointer:
293 |
294 | پایتون در واقع داخل لیست ها دیتا رو ذخیره نمی کته. کاری که می کنه اینه pointer اون دیتا رو ذخیره میکنه یعنی اشاره گری که فضای دیتا رو نشون میده. اینکار سبب میشه که یه سری مزیت هارو لیست های پایتون بدست بیاره
295 |
296 | در کتاب Hands on data structures and algorithms with Python اینگونه توضیح داده:
297 |
298 | Contrary to arrays, pointer structures are lists of items that can be spread out in memory.
299 | This is because each item contains one or more links to other items in the structure. The
300 | types of these links are dependent on the type of structures we have. If we are dealing with
301 | linked lists, then we will have links to the next (and possibly previous) items in the
302 | structure. In the case of a tree, we have parent-child links as well as sibling links.
303 | There are several benefits to pointer structures. First of all, they don't require sequential
304 | storage space. Secondly, they can start small and grow arbitrarily as you add more nodes to
305 | the structure. However, this flexibility in pointers comes at a cost. We need additional space
306 | to store the address. For example, if you have a list of integers, each node is going to take
307 | up space by storing an integer, as well as an additional integer for storing the pointer to the
308 | next node
309 |
310 | لیست پایتون چگونه عمل می کند؟
311 | در این قسمت با چند مثال نحوه کارکرد لیست رو در Cpython بررسی میکنیم.
312 |
313 | قبل از هرچیزی باید معنی size لیست و allocated slots رو بدونید. size در واقع همون مقدار عناصر داخل لیستمون هستش که با len() میشه بدستش آورد.
314 | اما allocated slots مقدار فضایی که موقع ساخن array اختصاص دادیم هستش. همونطور که میدونید لیست پایتون در واقع یک Dynamic array هستش که در نهایت به array می رسد.
315 |
316 | ساختن لیست:
317 | فرض کنید ما یک لیست حالی میسازیم. در این صورت allocated size ما مساوی با 0 است.
318 |
319 | عملیات append:
320 |
321 | اول از همه چک میشود که size لیست از allocated کوچک تر باشد و اگر مساوی بود آرایه رو resize میکنه و یک آرایه بزرگ تر می سازد.
322 | برای resize کردن یک فرمولی براش داره مثل زیر:
323 |
324 | The growth pattern of the list is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, …
325 |
326 | پس الان لیست ما allocated slots مساوی با 4 هستش. یعنی اگه عملیات append رو تا 4 بار انجام بدیم بصورت o(1)
327 | انجام میشود
328 |
329 |
330 |
331 | 
332 |
333 | همچنین میتونیم
334 | append رو سه بار دیگم با o(1)
335 | ادامه بدیم
336 |
337 |
338 | 
339 |
340 |
341 | عملیات insert:
342 |
343 | لیست ما الان allocated مساوی با 4 هستش و size هم مساوی با چهار.
344 | در عملیات insert که میخوایم انجام بدیم اول از همه لیست ما resize میشه و allocated slots مساوی با 8 میشه و سپس عملیات insert به شکل زیر انجام میشه.
345 |
346 |
347 |
348 |
349 | 
350 |
351 |
352 | همانطور که در عکس مشخص است. در insert کردن عناصری که بعد از چیزی که insert کردیم قرار دارند، یک ایندکس به جلو میروند و time complexity ما o(n)
353 | است.
354 |
355 |
356 | عملیات pop و remove:
357 |
358 | در عملیات remove و pop کردن دقیقا چیزی شبیه به insert اتفاق می افتد با این تفاوت عنصر آن ایندکسی که اشاره میکنیم حذف میشود و عناصر بغد آن یک ایندکس به عقب می روند.
359 |
360 |
361 | 
362 |
363 |
364 | توجه کنید ما resize شدن رو هم داریم.
365 | وقتی که allocated ما نسبت به size از یک مقداری بزرگ تر میشود. resize میشود و کوچک تر میشود.
366 |
367 |
368 |
369 | 
370 |
371 | 
372 |
373 |
374 |
لیست های پیوندی
375 | لیست های پیوندی، یک نوع دیگری از ساختمان داده هستند که از نظر ظاهری شاید تفاوت چندانی رو نسبت به لیست های پویا حس نکنید. اما در عمل تفاوت های
376 | با لیست های پویا دارد.
377 |
378 | ما در Array داده هارا بصورت متوالی ذخیره می کردیم. یعنی داده ها کاملا چسبیده به هم ذخیره می شدند و اگه میخواستیم عملیاتی مثل insert
379 | رو انجام بدیم، مجبور میشدیم تمامی عناصر بعدش رو جابجا کنیم.
380 |
381 | اما در لیست های پیوندی اینکار لازم نیست و insert و append و remove همیشه با n(1) انجام میشود.
382 |
383 | لیست های پیوندی چگونه عمل می کنند؟
384 |
385 | لیست های پیوندی در واقع تشکیل شده از composition هایی هستند که در ساده ترین حالت در خود این مقادیر را دارند:
386 |
387 | مقدار value دیتایی که ذخیره کریدم و مقدار next که مکان عنصر بعدی را درونش ذخیره میکنیم.
388 |
389 | همچنین هر لیست پیوندی دو مقدار head و tail را دارد که اول لیست و آخر لیست را مشخص می کند.
390 |
391 | خب احتمالا تا الان حدس زده باشید که در لیست های پیوندی بدست آوردن سایز o(n) هستش و همچنین get کردن و set کردن در یک ایندکس هم o(n)
392 | هست.
393 |
394 | البته می توان size را در کنار head و tail ذخیره کرد تا n(1) شود امابرای بدست آوردن یک ایندکس هیچ چاره ای نداریم و با o(n) مواجه میشویم.
395 |
396 | معایب لیست های پیوندی به یکیش اشاره کردم که گرفتن سایز و یا یک ایندکس o(n) هستش. یکی دیگر از معایبش این است که فضای بیشتری
397 | نسبت به Array ها می گیرد. اما اگه دقت کرده باشید برای صف و پشته خیلی گزینه مناسب تری هستند تا Array ها.
398 |
399 | انواع لیست های پیوندی
400 | لیست های پیوندی (Linked lists) دو نوع اصلی دارد. یکی از آنها Singly linked list و دیگری Doubly linked list است.
401 | تفاوت این دو با هم در مقادیری هست که ذخیره میکنند.
402 |
403 | در Singly linked list ما فقط next را ذخیره میکنیم. این باعث میشود که نتوانیم بصورت معکوس لیست را پیمایش کنیم و هر لیست مقدار قبل از خودش
404 | را نمی داند.
405 |
406 | اما در Doubly linked list بجز next ما مقدار previous را ذخیره میکنیم
407 | یعنی هم به مقدار بعد و همچنین به مقدار قبلش دسترسی داریم.
408 |
409 | همچنین دو نوع دیگر از لیست های پیوندی به نام های Circular linked lists و Circular doubly linked lists را داریم که اگر
410 | علاقه مند هستید درباره اش بخوانید. اما دو نوع پرکاربرش رو با هم دیگه توی پایتون پیاده می کنید.
411 |
412 |
413 | پیاده سازی Singly linked list
414 |
415 | با توجه به توضیحاتی که دادیم، یک Singly linked list در پایتون پیاده میکنیم.
416 |
417 | توجه:
418 |
419 | یک سری مشکلات در کد من وجود دارد و صرفا مثالی از نحوه پیاده کردنش زدم. اگه دوست دارید میتونید اصلاحش کنید و ریکوئست بزنید.
420 |
421 |
422 | ```Python
423 | from typing import Any
424 |
425 |
426 | class Node:
427 | def __init__(self, data):
428 | self.data = data
429 | self.next = None
430 |
431 | def __str__(self):
432 | return f'data: {self.data}'
433 |
434 |
435 | class SinglyLinkedList:
436 |
437 | def __init__(self) -> None:
438 | self.__head = None
439 | self.__tail = None
440 | self.__size = 0
441 |
442 | def size(self) -> int:
443 | return self.__size
444 |
445 | def __update_size(self) -> None:
446 | self.__size += 1
447 |
448 | def insure_index(self, index: int) -> None:
449 | if index > self.size() - 1:
450 | raise IndexError
451 |
452 | def head(self) -> Node:
453 | return self.__head
454 |
455 | def tail(self) -> Node:
456 | return self.__tail
457 |
458 | def __set_head(self, node: Node) -> None:
459 | self.__head = node
460 |
461 | def __set_tail(self, node: Node) -> None:
462 | self.__tail = node
463 |
464 | def append(self, data: Any) -> None:
465 | new_node = Node(data=data)
466 |
467 | if self.head() is None:
468 | self.__set_head(node=new_node)
469 |
470 | if self.tail() is None:
471 | self.__set_tail(node=new_node)
472 | else:
473 | self.tail().next = new_node
474 | self.__set_tail(node=new_node)
475 |
476 | self.__update_size()
477 |
478 | def get_node(self, index: int) -> Node:
479 | self.insure_index(index=index)
480 |
481 | count = 0
482 | current = self.head()
483 |
484 | while index != count:
485 | current = current.next
486 | count += 1
487 |
488 | return current
489 |
490 | def get(self, index: int) -> Any:
491 | node = self.get_node(index=index)
492 | return node.data
493 |
494 | def set(self, index: int, data: Any) -> None:
495 | self.insure_index(index=index)
496 |
497 | count = 0
498 | current = self.head()
499 |
500 | while index != count:
501 | current = current.next
502 | count += 1
503 |
504 | current.data = data
505 |
506 | def remove(self, index: int) -> None:
507 | self.insure_index(index=index)
508 |
509 | count = 0
510 | previous = None
511 | current = self.head()
512 |
513 | while index != count:
514 | previous = current
515 | current = current.next
516 | count += 1
517 |
518 | if previous:
519 | previous.next = current.next
520 | else:
521 | self.__set_head(current.next)
522 | del current
523 |
524 | def pop(self, index: int = 0) -> Any:
525 | self.insure_index(index=index)
526 |
527 | count = 0
528 | previous = None
529 | current = self.head()
530 |
531 | while index != count:
532 | previous = current
533 | current = current.next
534 | count += 1
535 |
536 | if previous:
537 | previous.next = current.next
538 | else:
539 | self.__set_head(node=current.next)
540 |
541 | data = current.data
542 | del current
543 | return data
544 |
545 | def __str__(self) -> str:
546 | my_list = []
547 |
548 | current = self.head()
549 |
550 | while current:
551 | my_list.append(current.data)
552 | current = current.next
553 |
554 | return str(my_list)
555 | ```
556 |
557 |
558 |
559 | پیاده سازی Doubly linked list
560 |
561 | در double linked list به علاوه next ما previous را هم ذخیره میکنیم. در این صورت میتوانیم از آخر به اول پیمایش کنیم و در کل کارمون راحت تر میشه
562 | اما حافظه بیشتری رو نیاز داریم.
563 |
564 | برای مثال از نحوه پیاده سازیش توی پایتون این تکه کد رو زدم، خوشحال میشم برای کامل کردن و بهتر شدنش کمکم کنید.
565 |
566 |
567 | ```Python
568 | from typing import Any
569 |
570 |
571 | class Node:
572 |
573 | def __init__(self, data: Any) -> None:
574 | self.data = data
575 | self.next = None
576 | self.pre = None
577 |
578 | def __str__(self):
579 | return f'data: {self.data}'
580 |
581 |
582 | class DoubleLinkedList:
583 |
584 | def __init__(self) -> None:
585 | self.__head = None
586 | self.__tail = None
587 | self.__size = 0
588 |
589 | def size(self) -> int:
590 | return self.__size
591 |
592 | def __update_size(self) -> None:
593 | self.__size += 1
594 |
595 | def insure_index(self, index: int) -> None:
596 | if index > self.size() - 1:
597 | raise IndexError
598 |
599 | def head(self) -> Node:
600 | return self.__head
601 |
602 | def tail(self) -> Node:
603 | return self.__tail
604 |
605 | def __set_head(self, node: Node) -> None:
606 | self.__head = node
607 |
608 | def __set_tail(self, node: Node) -> None:
609 | self.__tail = node
610 |
611 | def append(self, data: Any) -> None:
612 | new_node = Node(data=data)
613 |
614 | if self.head() is None and self.tail() is None:
615 | self.__set_head(node=new_node)
616 | self.__set_tail(node=new_node)
617 | self.__update_size()
618 | return
619 |
620 | self.tail().next = new_node
621 | new_node.pre = self.tail()
622 | self.__set_tail(node=new_node)
623 | self.__update_size()
624 |
625 | def get_node(self, index: int) -> Node:
626 | self.insure_index(index=index)
627 |
628 | count = 0
629 | current = self.head()
630 |
631 | while index != count:
632 | current = current.next
633 | count += 1
634 |
635 | return current
636 |
637 | def get(self, index: int) -> Any:
638 | node = self.get_node(index=index)
639 | return node.data
640 |
641 | def remove(self, index: int) -> None:
642 | current = self.get_node(index=index)
643 |
644 | if current.pre:
645 | current.pre.next = current.next
646 | else:
647 | self.__set_head(node=current.next)
648 |
649 | if current.next:
650 | current.next.pre = current.pre
651 | else:
652 | self.__set_tail(node=current.pre)
653 |
654 | del current
655 |
656 | def insert(self, index: int, data: Any) -> None:
657 | current = self.get_node(index=index)
658 |
659 | new_node = Node(data=data)
660 | new_node.next = current
661 | new_node.pre = current.pre
662 |
663 | if current.next:
664 | pass
665 |
666 | if current.pre:
667 | current.pre.next = new_node
668 | else:
669 | self.__set_head(node=new_node)
670 |
671 | self.__update_size()
672 |
673 | def pop(self):
674 | if self.tail():
675 | if self.tail().pre:
676 | self.tail().pre.next = None
677 | self.__set_tail(node=self.tail().pre)
678 | data = self.tail().data
679 | else:
680 | data = self.tail().data
681 | self.__head = None
682 | self.__tail = None
683 |
684 | return data
685 |
686 | return ValueError('Double linked list is empty.')
687 |
688 | def __str__(self) -> str:
689 | my_list = []
690 |
691 | current = self.head()
692 |
693 | while current:
694 | my_list.append(current.data)
695 | current = current.next
696 |
697 | return str(my_list)
698 | ```
699 |
700 |
701 |
702 |
الگوریتم مرتب سازی
703 | در ادامه آموزش ساختمان داده ما باید با یک سری مفاهیم الگوریتمی و پیچیدگی زمانی و فضایی آشنایی داشته باشیم. برای همین این قسمت کوتاه رو قرار دادم.
704 | ازتون توقع دارم که درباره sorting algorithms و time complexity آشنایی خوبی داشته باشید. توضیحاتی که برای الگوریتم قرار دادم کامل نیست و فقط مرور و گذری سریع هستش.
705 | همچنین درباره الگوریتم بازگشتی نیز مطالعه کنید.
706 |
707 | برای توضیح الگوریتم مرتب سازی من توضیح ویکی پدیا رو قرار میدم و در ادامه چند تا الگوریتم مرتب سازی رو با هم پیاده می کنیم.
708 |
709 | الگوریتم مرتبسازی، در دانش رایانه و ریاضی، الگوریتمی است که فهرستی از دادهها را به ترتیبی مشخص میچیند.
710 |
711 | پرکاربردترین ترتیبها، ترتیبهای عددی و واژهنامهای هستند. مرتبسازی کارا در بهینهسازی الگوریتمهایی که به فهرستهای مرتب شده نیاز دارند (مثل جستجو و ترکیب)، اهمیت زیادی دارد.
712 |
713 | از آغاز علم رایانه مسائل مرتبسازی بررسیهای فراوانی را متوجه خود ساختند؛ شاید به این علت که در عین ساده بودن، حل آن به صورت کارا پیچیده است. برای نمونه مرتبسازی حبابی در سال ۱۹۵۶ به وجود آمد. در حالی که بسیاری این را یک مسئلهٔ حل شده میپندارند، الگوریتم کارآمد جدیدی همچنان ابداع میشوند (مثلاً مرتبسازی کتابخانهای در سال ۲۰۰۴ مطرح شد).
714 |
715 | مبحث مرتبسازی در کلاسهای معرفی علم رایانه بسیار پرکاربرد است؛ مبحثی که در آن وجود الگوریتمهای فراوان به آشنایی با ایدههای کلی و مراحل طراحی الگوریتمهای گوناگون کمک میکند؛ مانند تحلیل الگوریتم، دادهساختارها، الگوریتمهای تصادفی، تحلیل بدترین و بهترین حالت و حالت میانگین، هزینهٔ زمان و حافظه، و حد پایین.
716 |
717 |
718 | الگوریتم مرتب سازی انتخابی
719 |
720 | در این الگوریتم ما به این شکل عمل می کنیم که خونه به خونه و ایندکس به ایندکس مرتب کردن رو انتجام میدهیم.
721 |
722 | یعنی اول بررسی میکنیم که کوچ ترین عدد چیست و در ایندکس صفر قرارش میدیم و بعدش بررسی میکنیم کوچک ترین برای ایدکس اول کدام عدد است و ...
723 |
724 | این نوع مرتب سازی inplace هستش. یعنی برای sort نیازی نیست که آرایه جدیدی بوجود بیاریم و توی همون حافظه و آرایه ای که هست کار خودشو
725 | انجام میده.
726 |
727 |
728 | 
729 |
730 |
731 | نحوه پیاده سازی در پایتون:
732 |
733 |
734 | ```Python
735 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
736 |
737 | print(unsorted_list)
738 |
739 |
740 | for i in range(len(unsorted_list)):
741 | min_index = i
742 | for j in range(i + 1, len(unsorted_list)):
743 | if unsorted_list[j] < unsorted_list[min_index]:
744 | min_index = j
745 |
746 | (unsorted_list[i], unsorted_list[min_index]) = (unsorted_list[min_index], unsorted_list[i])
747 |
748 | print(unsorted_list)
749 | ```
750 |
751 |
752 |
753 | الگوریتم مرتب سازی درجی
754 |
755 | ویکی پدیا بخوبی نخوه کار کردن این الگوریتم را توضیح داده است:
756 |
757 | این الگوریتم به این صورت عمل میکند که تمام عناصر لیست را یکی یکی برمیدارد و آن را در موقعیت مناسب در بخش مرتب شده قرار میدهد. انتخاب عنصر مورد نظر، اختیاری است و میتواند توسط هر الگوریتم انتخابی، انتخاب شود. مرتبسازی درجی به صورت درجا عمل میکند. نتیجه عمل بعد از k مرحله، حاوی k عنصر انتخاب شده به صورت مرتب شدهاست.
758 |
759 | معمولترین نسخه از این الگوریتم که روی آرایهها عمل میکند، به این صورت است:
760 |
761 | فرض کنید یک تابع به اسم Insert داریم که داده را در بخش مرتب شده در ابتدای آرایه درج میکند. این تابع با شروع از انتهای سری شروع به کار میکند و هر عنصر را به سمت راست منتقل میکند تا وقتی که جای مناسب برای عنصر جدید پیدا شود. این تابع این اثر جانبی را دارد که عنصر دقیقاً بعد از بخش مرتب شده را رونویسی میکند.
762 | برای اعمال مرتبساز درجی، از انتهای سمت چپ آرایه شروع میکنیم و با صدا زدن تابع insert، هر عنصر را به موقعیت درستش میبریم. آن بخشی که عنصر فعلی را در آن میکنیم، در ابتدای آرایه و در اندیسهایی است که آنها را قبلاً آزمایش کردهایم. هر بار صدا زدن تابع insert، یک عنصر را رونویسی میکند، اما این مسئله مشکلی ایجاد نمیکند، زیرا این داده، همانی است که الان در حال درج آن هستیم.
763 |
764 |
765 | 
766 |
767 |
768 |
769 | مثال در پایتون:
770 |
771 |
772 | ```Python
773 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
774 |
775 |
776 | for i in range(1, len(unsorted_list)):
777 |
778 | temp = unsorted_list[i]
779 |
780 | j = i - 1
781 | while j >= 0 and temp < unsorted_list[j]:
782 | unsorted_list[j + 1] = unsorted_list[j]
783 | j -= 1
784 | unsorted_list[j + 1] = temp
785 |
786 |
787 | print(unsorted_list)
788 |
789 | ```
790 |
791 |
792 |
793 | الگوریتم مرتب سازی حبابی
794 |
795 | مرتبسازی حبابی یک الگوریتم مرتبسازی سادهاست که فهرست را پشت سرهم پیمایش میکند تا هر بار عناصر کنارهم را با هم سنجیده و اگر در جای نادرست بودند جابهجایشان کند. در این الگوریتم این کار باید تا زمانی که هیچ جابهجایی در فهرست رخ ندهد، ادامه یابد و در آن زمان فهرست مرتب شدهاست. این مرتبسازی از آن رو حبابی نامیده میشود که هر عنصر با عنصر کناری خود سنجیدهشده و درصورتی که از آن کوچکتر باشد جای خود را به آن میدهد و این کار همچنان پیش میرود تا کوچکترین عنصر به پایین فهرست برسد و دیگران نیز به ترتیب در جای خود قرار گیرند (یا به رتبهای بالاتر روند یا به پایینتر فهرست رانده شوند) این عمل همانند پویش حباب به بالای مایع است.
796 |
797 |
798 | 
799 |
800 |
801 | نمونه کد در پایتون
802 |
803 |
804 | ```Python
805 | unsorted_list = [10, 5, 8, 4, 6, 1, 3, 7, 2, 9, -1]
806 |
807 | print(unsorted_list)
808 |
809 |
810 | for _ in range(1, len(unsorted_list)):
811 | for j in range(0, len(unsorted_list) - 1):
812 | if unsorted_list[j] > unsorted_list[j + 1]:
813 | unsorted_list[j], unsorted_list[j + 1] = unsorted_list[j + 1], unsorted_list[j]
814 |
815 |
816 | print(unsorted_list)
817 | ```
818 |
819 |
820 |
درخت ها
821 | تا الان هر ساختمان داده ای که بررسی کردیم ساختار لیست داشتند. مانند linked list, queue, stack, dynamic array. حالا وقتشه یک نوع ساختار داده ی دیگه ای رو بررسی کنیم
822 | که به ساختار داده ی درخت شناخته میشن.
823 |
824 | همونظور که از اسمش پیداست در این نوع ساختار داده ما یک ریشه (root) داریم و این ریشنه می تواند فرزندانی رو داشته باشند. همچنین
825 | فرزندان هم می توانند فرزندی رو برای خودشون داشته باشن اما هر فرزند فقط و فقط یک ریشه دارد.
826 |
827 |
828 | 
829 |
830 |
831 | در درخت با چند تا مفهوم روبرو میشوید که اینها هستند:
832 |
833 | ریشه:
834 |
835 | با توجه به عکس ریشه شماره دو میشود. ریشه خودش پدر ندارد و بالاتر از همه قرار میگیره
836 |
837 | گره میانی:
838 |
839 | با توجه به عکس 9و 12و 8و ... گره میانی به حساب می آیند. با اینکه خودشان فرزند هستند اما خودشان هم فرزند هایی رو دارند.
840 |
841 | برگ:
842 |
843 | برگ به فرزندی اشاره داره که خودش فرزندی ندارد و به قول معروف شجره نامه تو این قسمت تموم میشه.
844 |
845 | عمق:
846 |
847 | اگر عمق ریشه را 0 در نظر بگیریم، عمق 9 برابر با 1 است و عمق 2 برابر با 2 می شود. عمیق به مکان قرارگیری نسبت به ریشه اشاره دارد که
848 | هرچقدر به ریشه نردیک تر باشد عمق نیز کمتر میشود.
849 |
850 | همچنین ما انواع مختلفی از درخت هارو داریم مثل Binary tree و B-tree که در ادامه بصورت جزئی بررسی میکنیم.
851 |
852 |
پیاده سازی ساختار داده درخت در پایتون
853 | با توجه به توضیحاتی که دادیم. یک ساختار درختی ریشته و فرزندانی را دارد. در یک مثال ساده پایتونی به این شکل می توانیم پیاده سازی کنیم:
854 |
855 |
856 | ```Python
857 | from typing import Any
858 |
859 |
860 | class TreeNode:
861 |
862 | def __init__(self, value: Any, parent: Any | None = None) -> None:
863 | self.parent = parent
864 | self.value = value
865 | self.__children = []
866 |
867 | def add_child(self, value) -> None:
868 | new_child = TreeNode(value=value, parent=self)
869 | self.__children.append(new_child)
870 |
871 | def children(self) -> list:
872 | return self.__children
873 |
874 | def get_all_children(self) -> list:
875 | all_children = []
876 | depth_count = 0
877 | for child in self.__children:
878 | depth_count += 1
879 | all_children.append(child)
880 | all_children.extend(child.get_all_children())
881 | return all_children
882 |
883 | def depth(self) -> int:
884 | count = 0
885 | node = self
886 | while node and node.parent:
887 | node = node.parent
888 | count += 1
889 | return count
890 |
891 | def get_depth(self) -> int:
892 | if not self.__children:
893 | return 0
894 | else:
895 | child_depths = [child.get_depth() for child in self.__children]
896 | return max(child_depths) + 1
897 |
898 | def get_height(self) -> int:
899 | if not self.__children:
900 | return 0
901 | else:
902 | child_heights = [child.get_height() for child in self.__children]
903 | return max(child_heights) + 1
904 |
905 | def remove(self) -> None:
906 | parent = self.parent
907 | children = self.children()
908 | if parent:
909 | parent.__children.extend(children)
910 | parent.__children.remove(self)
911 | del self
912 |
913 |
914 | class Tree:
915 |
916 | def __init__(self, value: Any) -> None: self.__root = self.__create_root(value=value)
917 |
918 | @staticmethod
919 | def __create_root(value: Any) -> TreeNode: return TreeNode(value=value)
920 |
921 | @property
922 | def root(self):
923 | return self.__root
924 |
925 | def __str__(self): return f'root name: {self.__root.value}'
926 | ```
927 |
928 |
929 | طبق نیازمندی ها. باید بتوانیم در یک ساختار داده ی درختی به فرزندان دسترسی داشته باشیم و همچنین عمق و ارتفاع را هم بتوانیم بدست بیاوریم.
930 |
931 |
932 | ساختار داده ی درخت دودویی و درخت کامل:
933 |
934 | در یک درخت دودویی هر گره فقط می تواند دو یک یا دو فرزند داشته باشد. ساختار درخت کامل نیز به درختی گفته میشود که تمامی فرزندانش
935 | بجز برگ ها دو فرزند دارند.
936 |
937 | در پیاده سازی ساختار داده ی درختی خیلی از الگوریتم بازگشتی استفاده میشود.
938 |
939 |
940 | 
941 |
942 |
943 | همچنین می توان یک درخت دودویی را در یک آرایه پیاده سازی کرد.
944 |
945 |
946 | 
947 |
948 |
949 |
درخت جستچوی دودویی
950 |
951 |
952 |
--------------------------------------------------------------------------------