Merge branch 'main' into binary_tree

pull/1095/head
Ishita-Jena 2024-06-15 02:00:29 +05:30 zatwierdzone przez GitHub
commit 0c7260cce7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
10 zmienionych plików z 560 dodań i 14 usunięć

Wyświetl plik

@ -0,0 +1,185 @@
# AVL Tree
In Data Structures and Algorithms, an **AVL Tree** is a self-balancing binary search tree (BST) where the difference between heights of left and right subtrees cannot be more than one for all nodes. It ensures that the tree remains balanced, providing efficient search, insertion, and deletion operations.
## Points to be Remembered
- **Balance Factor**: The difference in heights between the left and right subtrees of a node. It should be -1, 0, or +1 for all nodes in an AVL tree.
- **Rotations**: Tree rotations (left, right, left-right, right-left) are used to maintain the balance factor within the allowed range.
## Real Life Examples of AVL Trees
- **Databases**: AVL trees can be used to maintain large indexes for database tables, ensuring quick data retrieval.
- **File Systems**: Some file systems use AVL trees to keep track of free and used memory blocks.
## Applications of AVL Trees
AVL trees are used in various applications in Computer Science:
- **Database Indexing**
- **Memory Allocation**
- **Network Routing Algorithms**
Understanding these applications is essential for Software Development.
## Operations in AVL Tree
Key operations include:
- **INSERT**: Insert a new element into the AVL tree.
- **SEARCH**: Find the position of an element in the AVL tree.
- **DELETE**: Remove an element from the AVL tree.
## Implementing AVL Tree in Python
```python
class AVLTreeNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
class AVLTree:
def insert(self, root, key):
if not root:
return AVLTreeNode(key)
if key < root.key:
root.left = self.insert(root.left, key)
else:
root.right = self.insert(root.right, key)
root.height = 1 + max(self.getHeight(root.left), self.getHeight(root.right))
balance = self.getBalance(root)
if balance > 1 and key < root.left.key:
return self.rotateRight(root)
if balance < -1 and key > root.right.key:
return self.rotateLeft(root)
if balance > 1 and key > root.left.key:
root.left = self.rotateLeft(root.left)
return self.rotateRight(root)
if balance < -1 and key < root.right.key:
root.right = self.rotateRight(root.right)
return self.rotateLeft(root)
return root
def search(self, root, key):
if not root or root.key == key:
return root
if key < root.key:
return self.search(root.left, key)
return self.search(root.right, key)
def delete(self, root, key):
if not root:
return root
if key < root.key:
root.left = self.delete(root.left, key)
elif key > root.key:
root.right = self.delete(root.right, key)
else:
if root.left is None:
temp = root.right
root = None
return temp
elif root.right is None:
temp = root.left
root = None
return temp
temp = self.getMinValueNode(root.right)
root.key = temp.key
root.right = self.delete(root.right, temp.key)
if root is None:
return root
root.height = 1 + max(self.getHeight(root.left), self.getHeight(root.right))
balance = self.getBalance(root)
if balance > 1 and self.getBalance(root.left) >= 0:
return self.rotateRight(root)
if balance < -1 and self.getBalance(root.right) <= 0:
return self.rotateLeft(root)
if balance > 1 and self.getBalance(root.left) < 0:
root.left = self.rotateLeft(root.left)
return self.rotateRight(root)
if balance < -1 and self.getBalance(root.right) > 0:
root.right = self.rotateRight(root.right)
return self.rotateLeft(root)
return root
def rotateLeft(self, z):
y = z.right
T2 = y.left
y.left = z
z.right = T2
z.height = 1 + max(self.getHeight(z.left), self.getHeight(z.right))
y.height = 1 + max(self.getHeight(y.left), self.getHeight(y.right))
return y
def rotateRight(self, z):
y = z.left
T3 = y.right
y.right = z
z.left = T3
z.height = 1 + max(self.getHeight(z.left), self.getHeight(z.right))
y.height = 1 + max(self.getHeight(y.left), self.getHeight(y.right))
return y
def getHeight(self, root):
if not root:
return 0
return root.height
def getBalance(self, root):
if not root:
return 0
return self.getHeight(root.left) - self.getHeight(root.right)
def getMinValueNode(self, root):
if root is None or root.left is None:
return root
return self.getMinValueNode(root.left)
def preOrder(self, root):
if not root:
return
print(root.key, end=' ')
self.preOrder(root.left)
self.preOrder(root.right)
#Example usage
avl_tree = AVLTree()
root = None
root = avl_tree.insert(root, 10)
root = avl_tree.insert(root, 20)
root = avl_tree.insert(root, 30)
root = avl_tree.insert(root, 40)
root = avl_tree.insert(root, 50)
root = avl_tree.insert(root, 25)
print("Preorder traversal of the AVL tree is:")
avl_tree.preOrder(root)
```
## Output
```markdown
Preorder traversal of the AVL tree is:
30 20 10 25 40 50
```
## Complexity Analysis
- **Insertion**: O(logn). Inserting a node involves traversing the height of the tree, which is logarithmic due to the balancing property.
- **Search**: O(logn). Searching for a node involves traversing the height of the tree.
- **Deletion**: O(logn). Deleting a node involves traversing and potentially rebalancing the tree, maintaining the logarithmic height.

Wyświetl plik

@ -51,10 +51,6 @@ print(f"The {n}th Fibonacci number is: {fibonacci(n)}.")
- **Time Complexity**: O(n) for both approaches
- **Space Complexity**: O(n) for the top-down approach (due to memoization), O(1) for the bottom-up approach
</br>
<hr>
</br>
# 2. Longest Common Subsequence
The longest common subsequence (LCS) problem is to find the longest subsequence common to two sequences. A subsequence is a sequence that appears in the same relative order but not necessarily contiguous.
@ -84,13 +80,33 @@ Y = "GXTXAYB"
print("Length of Longest Common Subsequence:", longest_common_subsequence(X, Y, len(X), len(Y)))
```
## Complexity Analysis
- **Time Complexity**: O(m * n) for the top-down approach, where m and n are the lengths of the input sequences
- **Space Complexity**: O(m * n) for the memoization table
## Longest Common Subsequence Code in Python (Bottom-Up Approach)
</br>
<hr>
</br>
```python
def longestCommonSubsequence(X, Y, m, n):
L = [[None]*(n+1) for i in range(m+1)]
for i in range(m+1):
for j in range(n+1):
if i == 0 or j == 0:
L[i][j] = 0
elif X[i-1] == Y[j-1]:
L[i][j] = L[i-1][j-1]+1
else:
L[i][j] = max(L[i-1][j], L[i][j-1])
return L[m][n]
S1 = "AGGTAB"
S2 = "GXTXAYB"
m = len(S1)
n = len(S2)
print("Length of LCS is", longestCommonSubsequence(S1, S2, m, n))
```
## Complexity Analysis
- **Time Complexity**: O(m * n) for both approaches, where m and n are the lengths of the input sequences
- **Space Complexity**: O(m * n) for the memoization table
# 3. 0-1 Knapsack Problem
@ -123,10 +139,98 @@ n = len(weights)
print("Maximum value that can be obtained:", knapsack(weights, values, capacity, n))
```
## 0-1 Knapsack Problem Code in Python (Bottom-up Approach)
```python
def knapSack(capacity, weights, values, n):
K = [[0 for x in range(capacity + 1)] for x in range(n + 1)]
for i in range(n + 1):
for w in range(capacity + 1):
if i == 0 or w == 0:
K[i][w] = 0
elif weights[i-1] <= w:
K[i][w] = max(values[i-1]
+ K[i-1][w-weights[i-1]],
K[i-1][w])
else:
K[i][w] = K[i-1][w]
return K[n][capacity]
values = [60, 100, 120]
weights = [10, 20, 30]
capacity = 50
n = len(weights)
print(knapSack(capacity, weights, values, n))
```
## Complexity Analysis
- **Time Complexity**: O(n * W) for the top-down approach, where n is the number of items and W is the capacity of the knapsack
- **Time Complexity**: O(n * W) for both approaches, where n is the number of items and W is the capacity of the knapsack
- **Space Complexity**: O(n * W) for the memoization table
</br>
<hr>
</br>
# 4. Longest Increasing Subsequence
The Longest Increasing Subsequence (LIS) is a task is to find the longest subsequence that is strictly increasing, meaning each element in the subsequence is greater than the one before it. This subsequence must maintain the order of elements as they appear in the original sequence but does not need to be contiguous. The goal is to identify the subsequence with the maximum possible length.
**Algorithm Overview:**
- **Base cases:** If the sequence is empty, the LIS length is 0.
- **Memoization:** Store the results of previously computed subproblems to avoid redundant computations.
- **Recurrence relation:** Compute the LIS length by comparing characters of the sequences and making decisions based on their values.
## Longest Increasing Subsequence Code in Python (Top-Down Approach using Memoization)
```python
import sys
def f(idx, prev_idx, n, a, dp):
if (idx == n):
return 0
if (dp[idx][prev_idx + 1] != -1):
return dp[idx][prev_idx + 1]
notTake = 0 + f(idx + 1, prev_idx, n, a, dp)
take = -sys.maxsize - 1
if (prev_idx == -1 or a[idx] > a[prev_idx]):
take = 1 + f(idx + 1, idx, n, a, dp)
dp[idx][prev_idx + 1] = max(take, notTake)
return dp[idx][prev_idx + 1]
def longestSubsequence(n, a):
dp = [[-1 for i in range(n + 1)]for j in range(n + 1)]
return f(0, -1, n, a, dp)
a = [3, 10, 2, 1, 20]
n = len(a)
print("Length of lis is", longestSubsequence(n, a))
```
## Longest Increasing Subsequence Code in Python (Bottom-Up Approach)
```python
def lis(arr):
n = len(arr)
lis = [1]*n
for i in range(1, n):
for j in range(0, i):
if arr[i] > arr[j] and lis[i] < lis[j] + 1:
lis[i] = lis[j]+1
maximum = 0
for i in range(n):
maximum = max(maximum, lis[i])
return maximum
arr = [10, 22, 9, 33, 21, 50, 41, 60]
print("Length of lis is", lis(arr))
```
## Complexity Analysis
- **Time Complexity**: O(n * n) for both approaches, where n is the length of the array.
- **Space Complexity**: O(n * n) for the memoization table in Top-Down Approach, O(n) in Bottom-Up Approach.

Wyświetl plik

@ -17,3 +17,5 @@
- [Hashing through Linear Probing](hashing-linear-probing.md)
- [Hashing through Chaining](hashing-chaining.md)
- [Binary Tree](binary_tree.md)
- [AVL Trees](avl-trees.md)
- [Splay Trees](splay-trees.md)

Wyświetl plik

@ -0,0 +1,162 @@
# Splay Tree
In Data Structures and Algorithms, a **Splay Tree** is a self-adjusting binary search tree with the additional property that recently accessed elements are quick to access again. It performs basic operations such as insertion, search, and deletion in O(log n) amortized time. This is achieved by a process called **splaying**, where the accessed node is moved to the root through a series of tree rotations.
## Points to be Remembered
- **Splaying**: Moving the accessed node to the root using rotations.
- **Rotations**: Tree rotations (left and right) are used to balance the tree during splaying.
- **Self-adjusting**: The tree adjusts itself with each access, keeping frequently accessed nodes near the root.
## Real Life Examples of Splay Trees
- **Cache Implementation**: Frequently accessed data is kept near the top of the tree, making repeated accesses faster.
- **Networking**: Routing tables in network switches can use splay trees to prioritize frequently accessed routes.
## Applications of Splay Trees
Splay trees are used in various applications in Computer Science:
- **Cache Implementations**
- **Garbage Collection Algorithms**
- **Data Compression Algorithms (e.g., LZ78)**
Understanding these applications is essential for Software Development.
## Operations in Splay Tree
Key operations include:
- **INSERT**: Insert a new element into the splay tree.
- **SEARCH**: Find the position of an element in the splay tree.
- **DELETE**: Remove an element from the splay tree.
## Implementing Splay Tree in Python
```python
class SplayTreeNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
class SplayTree:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self.splay_insert(self.root, key)
def search(self, key):
self.root = self.splay_search(self.root, key)
return self.root
def splay(self, root, key):
if not root or root.key == key:
return root
if root.key > key:
if not root.left:
return root
if root.left.key > key:
root.left.left = self.splay(root.left.left, key)
root = self.rotateRight(root)
elif root.left.key < key:
root.left.right = self.splay(root.left.right, key)
if root.left.right:
root.left = self.rotateLeft(root.left)
return root if not root.left else self.rotateRight(root)
else:
if not root.right:
return root
if root.right.key > key:
root.right.left = self.splay(root.right.left, key)
if root.right.left:
root.right = self.rotateRight(root.right)
elif root.right.key < key:
root.right.right = self.splay(root.right.right, key)
root = self.rotateLeft(root)
return root if not root.right else self.rotateLeft(root)
def splay_insert(self, root, key):
if not root:
return SplayTreeNode(key)
root = self.splay(root, key)
if root.key == key:
return root
new_node = SplayTreeNode(key)
if root.key > key:
new_node.right = root
new_node.left = root.left
root.left = None
else:
new_node.left = root
new_node.right = root.right
root.right = None
return new_node
def splay_search(self, root, key):
return self.splay(root, key)
def rotateRight(self, node):
temp = node.left
node.left = temp.right
temp.right = node
return temp
def rotateLeft(self, node):
temp = node.right
node.right = temp.left
temp.left = node
return temp
def preOrder(self, root):
if root:
print(root.key, end=' ')
self.preOrder(root.left)
self.preOrder(root.right)
#Example usage:
splay_tree = SplayTree()
splay_tree.insert(50)
splay_tree.insert(30)
splay_tree.insert(20)
splay_tree.insert(40)
splay_tree.insert(70)
splay_tree.insert(60)
splay_tree.insert(80)
print("Preorder traversal of the Splay tree is:")
splay_tree.preOrder(splay_tree.root)
splay_tree.search(60)
print("\nSplay tree after search operation for key 60:")
splay_tree.preOrder(splay_tree.root)
```
## Output
```markdown
Preorder traversal of the Splay tree is:
50 30 20 40 70 60 80
Splay tree after search operation for key 60:
60 50 30 20 40 70 80
```
## Complexity Analysis
The worst-case time complexities of the main operations in a Splay Tree are as follows:
- **Insertion**: (O(n)). In the worst case, insertion may take linear time if the tree is highly unbalanced.
- **Search**: (O(n)). In the worst case, searching for a node may take linear time if the tree is highly unbalanced.
- **Deletion**: (O(n)). In the worst case, deleting a node may take linear time if the tree is highly unbalanced.
While these operations can take linear time in the worst case, the splay operation ensures that the tree remains balanced over a sequence of operations, leading to better average-case performance.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 541 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 12 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 13 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 6.0 KiB

Wyświetl plik

@ -19,4 +19,5 @@
- [Hierarchical Clustering](hierarchical-clustering.md)
- [Grid Search](grid-search.md)
- [Transformers](transformers.md)
- [K-Means](kmeans.md)
- [K-nearest neighbor (KNN)](knn.md)

Wyświetl plik

@ -0,0 +1,92 @@
# K-Means Clustering
Unsupervised Learning Algorithm for Grouping Similar Data.
## Introduction
K-means clustering is a fundamental unsupervised machine learning algorithm that excels at grouping similar data points together. It's a popular choice due to its simplicity and efficiency in uncovering hidden patterns within unlabeled datasets.
## Unsupervised Learning
Unlike supervised learning algorithms that rely on labeled data for training, unsupervised algorithms, like K-means, operate solely on input data (without predefined categories). Their objective is to discover inherent structures or groupings within the data.
## The K-Means Objective
Organize similar data points into clusters to unveil underlying patterns. The main objective is to minimize total intra-cluster variance or the squared function.
![image](assets/knm.png)
## Clusters and Centroids
A cluster represents a collection of data points that share similar characteristics. K-means identifies a pre-determined number (k) of clusters within the dataset. Each cluster is represented by a centroid, which acts as its central point (imaginary or real).
## Minimizing In-Cluster Variation
The K-means algorithm strategically assigns each data point to a cluster such that the total variation within each cluster (measured by the sum of squared distances between points and their centroid) is minimized. In simpler terms, K-means strives to create clusters where data points are close to their respective centroids.
## The Meaning Behind "K-Means"
The "means" in K-means refers to the averaging process used to compute the centroid, essentially finding the center of each cluster.
## K-Means Algorithm in Action
![image](assets/km_.png)
The K-means algorithm follows an iterative approach to optimize cluster formation:
1. **Initial Centroid Placement:** The process begins with randomly selecting k centroids to serve as initial reference points for each cluster.
2. **Data Point Assignment:** Each data point is assigned to the closest centroid, effectively creating a preliminary clustering.
3. **Centroid Repositioning:** Once data points are assigned, the centroids are recalculated by averaging the positions of the points within their respective clusters. These new centroids represent the refined centers of the clusters.
4. **Iteration Until Convergence:** Steps 2 and 3 are repeated iteratively until a stopping criterion is met. This criterion can be either:
- **Centroid Stability:** No significant change occurs in the centroids' positions, indicating successful clustering.
- **Reaching Maximum Iterations:** A predefined number of iterations is completed.
## Code
Following is a simple implementation of K-Means.
```python
# Generate and Visualize Sample Data
# import the necessary Libraries
import numpy as np
import matplotlib.pyplot as plt
# Create data points for cluster 1 and cluster 2
X = -2 * np.random.rand(100, 2)
X1 = 1 + 2 * np.random.rand(50, 2)
# Combine data points from both clusters
X[50:100, :] = X1
# Plot data points and display the plot
plt.scatter(X[:, 0], X[:, 1], s=50, c='b')
plt.show()
# K-Means Model Creation and Training
from sklearn.cluster import KMeans
# Create KMeans object with 2 clusters
kmeans = KMeans(n_clusters=2)
kmeans.fit(X) # Train the model on the data
# Visualize Data Points with Centroids
centroids = kmeans.cluster_centers_ # Get centroids (cluster centers)
plt.scatter(X[:, 0], X[:, 1], s=50, c='b') # Plot data points again
plt.scatter(centroids[0, 0], centroids[0, 1], s=200, c='g', marker='s') # Plot centroid 1
plt.scatter(centroids[1, 0], centroids[1, 1], s=200, c='r', marker='s') # Plot centroid 2
plt.show() # Display the plot with centroids
# Predict Cluster Label for New Data Point
new_data = np.array([-3.0, -3.0])
new_data_reshaped = new_data.reshape(1, -1)
predicted_cluster = kmeans.predict(new_data_reshaped)
print("Predicted cluster for new data:", predicted_cluster)
```
### Output:
Before Implementing K-Means Clustering
![Before Implementing K-Means Clustering](assets/km_2.png)
After Implementing K-Means Clustering
![After Implementing K-Means Clustering](assets/km_3.png)
Predicted cluster for new data: `[0]`
## Conclusion
**K-Means** can be applied to data that has a smaller number of dimensions, is numeric, and is continuous or can be used to find groups that have not been explicitly labeled in the data. As an example, it can be used for Document Classification, Delivery Store Optimization, or Customer Segmentation.
## References
- [Survey of Machine Learning and Data Mining Techniques used in Multimedia System](https://www.researchgate.net/publication/333457161_Survey_of_Machine_Learning_and_Data_Mining_Techniques_used_in_Multimedia_System?_tp=eyJjb250ZXh0Ijp7ImZpcnN0UGFnZSI6Il9kaXJlY3QiLCJwYWdlIjoiX2RpcmVjdCJ9fQ)
- [A Clustering Approach for Outliers Detection in a Big Point-of-Sales Database](https://www.researchgate.net/publication/339267868_A_Clustering_Approach_for_Outliers_Detection_in_a_Big_Point-of-Sales_Database?_tp=eyJjb250ZXh0Ijp7ImZpcnN0UGFnZSI6Il9kaXJlY3QiLCJwYWdlIjoiX2RpcmVjdCJ9fQ)