0% found this document useful (0 votes)
10 views

DS-3

The document provides a comprehensive overview of data structures, specifically focusing on linear and non-linear types, including arrays, linked lists, trees, and graphs. It details algorithms for binary search, insertion sort, and methods for finding frequently occurring elements in an array, along with C code implementations. Additionally, it discusses linked lists, their advantages and disadvantages, and provides algorithms for insertion and deletion operations, along with examples.

Uploaded by

24kb1a05n4
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views

DS-3

The document provides a comprehensive overview of data structures, specifically focusing on linear and non-linear types, including arrays, linked lists, trees, and graphs. It details algorithms for binary search, insertion sort, and methods for finding frequently occurring elements in an array, along with C code implementations. Additionally, it discusses linked lists, their advantages and disadvantages, and provides algorithms for insertion and deletion operations, along with examples.

Uploaded by

24kb1a05n4
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

1a)Briefly describe the classification of Data structures with a chart

A) Here’s a brief classification of data structures in a chart format:

Data Structures
|
------------------------------------------------
| |
Linear Data Structures Non-Linear Data Structures
| |
--------------- -----------------------
| | | |
Arrays Linked Lists Trees Graphs
|
Stacks
|
Queues

### Explanation:
1. *Linear Data Structures*: Elements are arranged in a sequential manner.
- *Arrays*: Fixed-size, contiguous memory allocation.
- *Linked Lists*: Elements are stored as nodes, with each node pointing to the next.
- *Stacks*: LIFO (Last In, First Out) structure.
- *Queues*: FIFO (First In, First Out) structure.

2. *Non-Linear Data Structures*: Elements are arranged in a hierarchical or interconnected


manner.
- *Trees*: Hierarchical structure with a root node and child nodes.
- *Graphs*: Consist of vertices (nodes) and edges (connections between nodes). Can be
directed or undirected.
This classification helps to categorize data structures based on their arrangement and access
methods.

1b) Apply the binary search algoritin on the following sorted wray to fiad the targe

SMI element?

12 23 34 45 56 67 78 89 98 123

And the target element is 89.

A) To apply the binary search algorithm on the given sorted array and find the target element (89) in
C, here’s the approach:

Sorted Array:

12,23,34,45,56,67,78,89,98,12312, 23, 34, 45, 56, 67, 78, 89, 98, 123

Target Element:

89

Binary Search Algorithm Steps in C:

1. Initialize:

o Set left to 0 (the first index).

o Set right to 9 (the last index).

2. Iterate:

o Calculate the middle index: mid=left+right2\text{mid} = \frac{\text{left} +


\text{right}}{2}.

o Compare the element at the middle index with the target:

 If the element at mid is equal to the target, return mid.

 If the element at mid is less than the target, update left to mid + 1 to search
the right half.

 If the element at mid is greater than the target, update right to mid - 1 to
search the left half.

3. Repeat the process until left is greater than right.

C Code:

#include <stdio.h>

int binarySearch(int arr[], int left, int right, int target) {


while (left <= right) {

int mid = left + (right - left) / 2;

if (arr[mid] == target) {

return mid;

if (arr[mid] < target) {

left = mid + 1;

else {

right = mid - 1;

return -1;

int main() {

int arr[] = {12, 23, 34, 45, 56, 67, 78, 89, 98, 123};

int n = sizeof(arr) / sizeof(arr[0]);

int target = 89;

int result = binarySearch(arr, 0, n - 1, target);

if (result != -1) {

printf("Target element %d found at index %d.\n", target, result);

} else {

printf("Target element %d not found.\n", target);

return 0;

Output:

Target element 89 found at index 7.


2a) Consider amay ofelements 2 1 5 2 3 6 9 1 2 Find the frequently occurred elements and display
their positions.

A) To solve the problem of finding the frequently occurring elements in an array and displaying their
positions in C, we need to perform the following tasks:

Tasks:

1. Count the frequency of each element in the array.

2. Store the positions where each element occurs.

3. Find the most frequent elements and display their positions.

Given Array:

2,1,5,2,3,6,9,1,22, 1, 5, 2, 3, 6, 9, 1, 2

Steps:

 Use an array or a map-like structure to store the count of each element.

 Use another structure to store the positions of each element.

 Find the maximum frequency and display the elements with that frequency along with their
positions.

C Code Implementation:

#include <stdio.h>

#include <stdlib.h>

void findFrequentElements(int arr[], int n) {

int *freq = (int *)calloc(n, sizeof(int));

int *positions[n];

for (int i = 0; i < n; i++) {

positions[i] = (int *)malloc(n * sizeof(int));

for (int i = 0; i < n; i++) {

freq[arr[i]]++;

positions[arr[i]][freq[arr[i]] - 1] = i;

int maxFreq = 0;

for (int i = 0; i < n; i++) {


if (freq[arr[i]] > maxFreq) {

maxFreq = freq[arr[i]];

printf("Elements with maximum frequency %d are:\n", maxFreq);

for (int i = 0; i < n; i++) {

if (freq[arr[i]] == maxFreq) {

printf("Element %d at positions: ", arr[i]);

for (int j = 0; j < maxFreq; j++) {

printf("%d ", positions[arr[i]][j]);

printf("\n");

for (int i = 0; i < n; i++) {

free(positions[i]);

free(freq);

int main() {

int arr[] = {2, 1, 5, 2, 3, 6, 9, 1, 2};

int n = sizeof(arr) / sizeof(arr[0]);

findFrequentElements(arr, n);

return 0;

Output:-

Elements with maximum frequency 3 are:


Element 2 at positions: 0 3 8

2b) Impiement the algorithm for Insertion sort with an example

A) Insertion Sort Algorithm in C

Insertion sort is a simple sorting algorithm that builds the sorted array one element at a time. It
works similarly to how you might sort playing cards in your hands. You take one card at a time and
insert it into its correct position among the cards you’ve already sorted.

Steps of Insertion Sort:

1. Start from the second element (since a single element is trivially sorted).

2. Compare the current element with the previous elements.

3. If the current element is smaller, shift the larger elements one position to the right.

4. Insert the current element in the correct position.

5. Repeat for all elements.

Example:

Given the array: [5, 2, 9, 1, 5, 6]

Step-by-step Process:

 Start with the second element (2), compare it with 5, and place 2 before 5.

 Continue this process for each element.

C Code Implementation:

#include <stdio.h>

void insertionSort(int arr[], int n) {

int i, key, j;

for (i = 1; i < n; i++) {

key = arr[i];

j = i - 1;

while (j >= 0 && arr[j] > key) {

arr[j + 1] = arr[j

j = j - 1;

arr[j + 1] = key;

}
}

void printArray(int arr[], int size) {

for (int i = 0; i < size; i++) {

printf("%d ", arr[i]);

printf("\n");

int main() {

int arr[] = {5, 2, 9, 1, 5, 6};

int n = sizeof(arr) / sizeof(arr[0]);

printf("Original array: ");

printArray(arr, n);

insertionSort(arr, n);

printf("Sorted array: ");

printArray(arr, n);

return 0;

Output:

Original array: 5 2 9 1 5 6

Sorted array: 1 2 5 5 6 9

3a) Deicribe what is Linked List? Mention its advantages, disadvantages?

A) Linked List: A Linked List is a linear data structure in which elements, called nodes, are stored in
memory in a non-contiguous manner. Each node contains two parts:

1. Data: The actual value or data stored in the node.

2. Pointer (or Link): A reference (or address) to the next node in the sequence. In the last
node, this pointer is typically NULL, indicating the end of the list.

Unlike arrays, linked lists do not require elements to be stored in contiguous memory locations.
Instead, each node points to the next one, allowing for dynamic memory allocation.

Types of Linked Lists

1. Singly Linked List: Each node contains a data part and a pointer to the next node.
2. Doubly Linked List: Each node has two pointers: one pointing to the next node and one
pointing to the previous node.

3. Circular Linked List: The last node points back to the first node instead of having a NULL
pointer.

Basic Structure of a Singly Linked List (in C):

struct Node {

int data;

struct Node* next;

};

Advantages of Linked Lists

1. Dynamic Size:

o The size of a linked list can grow or shrink dynamically at runtime. This is a key
advantage over arrays, where the size is fixed at the time of creation.

o Linked lists don’t need to declare a fixed size at the time of allocation.

2. Efficient Insertion/Deletion:

o Inserting or deleting nodes in a linked list can be done efficiently, especially at the
beginning or in the middle of the list. No shifting of elements is required, unlike in
arrays.

3. No Memory Waste:

o Linked lists use memory as needed (i.e., nodes are allocated dynamically). This
means there is no wasted space due to preallocated memory, which might happen
with arrays when you don’t know the exact size in advance.

4. Efficient Memory Use:

o Since linked lists allocate memory only when needed, they avoid the potential
problem of wasted space when the size of an array is larger than needed.

Disadvantages of Linked Lists

1. Random Access is Not Possible:

o Linked lists do not support random access. To access an element, you must traverse
the list from the beginning, making it slower than arrays for access by index.

2. Extra Memory Usage:

o Each node in a linked list requires extra memory for the pointer(s) that store the
address of the next node. This makes linked lists less memory efficient than arrays.

3. Complexity in Implementation:
o Linked lists are more complex to implement and manage compared to arrays.
Operations like insertion, deletion, and traversal require pointer manipulation,
which is prone to errors if not handled properly.

4. Sequential Access:

o To find an element, you have to traverse the list node by node. This makes
searching for elements slower than arrays where elements can be accessed by
index.

5. Overhead Due to Pointer Storage:

o In addition to the data, each node has to store a pointer (or two in the case of a
doubly linked list), which introduces an overhead compared to simple data
structures like arrays.

Example of a Singly Linked List in C

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;

};

struct Node* createNode(int value) {

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

newNode->data = value;

newNode->next = NULL;

return newNode;

void printList(struct Node* head) {

struct Node* current = head;

while (current != NULL) {

printf("%d -> ", current->data);

current = current->next;

printf("NULL\n");

int main() {
struct Node* head = createNode(10);

head->next = createNode(20);

head->next->next = createNode(30);

printList(head);

return 0;

Output:

10 -> 20 -> 30 -> NULL

3b) Build the logic for single linked list insertion operation with an example?

A) Sure! Let's break down the process of inserting a node in a singly linked list and implement it
step-by-step with a complete example in C.

Singly Linked List

A singly linked list is a data structure where each element (node) contains:

1. Data: The value stored in the node.

2. Next: A pointer that references the next node in the list.

Insertion Operations

We can perform the following types of insertion operations in a singly linked list:

1. Insertion at the beginning (Head):

o Insert a new node at the start of the list.

2. Insertion at the end (Tail):

o Insert a new node at the end of the list.

3. Insertion at a specific position:

o Insert a node at a given position in the list.

Steps for each insertion operation:

1. Insertion at the beginning:

o Create a new node.

o Point the new node's next to the current head of the list.

o Update the head pointer to point to the new node.

2. Insertion at the end:


o Create a new node.

o If the list is empty, make the new node the head.

o Otherwise, traverse the list to the last node and set its next to point to the new
node.

3. Insertion at a specific position:

o Create a new node.

o Traverse the list until you reach the node just before the desired position.

o Update the next pointer of the previous node to point to the new node, and the next
pointer of the new node to point to the next node.

C Code Implementation

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;

};

struct Node* createNode(int data) {

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

newNode->data = data;

newNode->next = NULL;

return newNode;

void insertAtBeginning(struct Node** head, int data) {

struct Node* newNode = createNode(data);

newNode->next = *head;

*head = newNode;

void insertAtEnd(struct Node** head, int data) {

struct Node* newNode = createNode(data);

if (*head == NULL) {

*head = newNode;

return;
}

struct Node* temp = *head;

while (temp->next != NULL) {

temp = temp->next;

temp->next = newNode;

void insertAtPosition(struct Node** head, int data, int position) {

if (position < 0) {

printf("Invalid position!\n");

return;

if (position == 0) {

insertAtBeginning(head, data);

return;

struct Node* newNode = createNode(data);

struct Node* temp = *head;

for (int i = 0; temp != NULL && i < position - 1; i++) {

temp = temp->next;

if (temp == NULL) {

printf("Position out of bounds!\n");

return;

newNode->next = temp->next;

temp->next = newNode;

void printList(struct Node* head) {

struct Node* temp = head;

while (temp != NULL) {


printf("%d -> ", temp->data);

temp = temp->next;

printf("NULL\n");

int main() {

struct Node* head = NULL;

insertAtBeginning(&head, 10);

insertAtBeginning(&head, 20);

insertAtEnd(&head, 30);

insertAtPosition(&head, 25, 2);

printf("Linked List: ");

printList(head);

return 0;

Output:

Linked List: 20 -> 10 -> 25 -> 30 -> NULL

4a) Construct an algorithm to Implement a Singly Linked List delction and traversing operations.

A) Singly Linked List Deletion and Traversing Operations

In a singly linked list, deletion and traversal operations are essential for managing data. Here's an
algorithm to implement these operations in C.

Operations:

1. Traversal:

o Traverse the list from the head to the last node and print each node's data.

2. Deletion:

o Delete at the beginning (head).

o Delete at the end (tail).

o Delete at a specific position (given a position).

Algorithm Explanation
1. Traversal:

o Start from the head of the linked list.

o Traverse the list by following the next pointers until you reach NULL (the end of the
list).

o Print the data stored in each node during the traversal.

2. Deletion at the Beginning:

o Check if the list is empty (head is NULL).

o If not, point the head to the next node of the current head and free the memory of
the current head.

3. Deletion at the End:

o Traverse the list until the second-to-last node (where next is NULL).

o Set the second-to-last node's next pointer to NULL and free the last node.

4. Deletion at a Specific Position:

o Traverse the list until the node just before the specified position.

o Adjust the next pointers to remove the node at that position and free its memory.

C Code Implementation

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;

};

struct Node* createNode(int data) {

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

newNode->data = data; // Set the data

newNode->next = NULL;

return newNode;

void traverseList(struct Node* head) {

struct Node* temp = head;

if (temp == NULL) {

printf("The list is empty.\n");


return;

printf("Linked List: ");

while (temp != NULL) {

printf("%d -> ", temp->data);

temp = temp->next;

printf("NULL\n");

void deleteAtBeginning(struct Node** head) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");

return;

struct Node* temp = *head;

*head = temp->next

free(temp);

printf("Node deleted from the beginning.\n");

void deleteAtEnd(struct Node** head) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");

return;

struct Node* temp = *head;

if (temp->next == NULL) {

*head = NULL;

free(temp);

printf("Node deleted from the end.\n");


return;

Example Output:

Linked List: 10 -> 20 -> 30 -> NULL

Node deleted from the beginning.

Linked List: 20 -> 30 -> NULL

Node deleted from the end.

Linked List: 20 -> NULL

Node deleted at position 0.

Linked List: NULL

Position out of bounds.

4b) Discuss all possible cases for Double Linked List delction operation.

A)

Possible Cases for Deletion in a Doubly Linked List

1. Deletion of the First (Head) Node:

o If the node to be deleted is the first node (head), we need to update the head
pointer and ensure the next node’s previous pointer is NULL.

2. Deletion of the Last (Tail) Node:

o If the node to be deleted is the last node (tail), we need to update the tail pointer
and ensure the previous node’s next pointer is NULL.

3. Deletion of a Node in the Middle:

o If the node to be deleted is neither the head nor the tail, we need to adjust the next
pointer of the previous node and the previous pointer of the next node to bypass the
node being deleted.

4. Deletion from an Empty List:

o If the list is empty (head is NULL), no deletion can occur.

5. Deletion of a Node by Value:


o Instead of deleting by position, we can delete a node by searching for a specific value
in the list.

C Code Implementation for Deletion in a Doubly Linked List

#include <stdio.h>

#include <stdlib.h>

struct Node {

int data;

struct Node* next;

struct Node* prev;

};

struct Node* createNode(int data) {

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

newNode->data = data;

newNode->next = NULL;

newNode->prev = NULL;

return newNode;

void traverseList(struct Node* head) {

struct Node* temp = head;

if (temp == NULL) {

printf("The list is empty.\n");

return;

printf("Doubly Linked List: ");

while (temp != NULL) {

printf("%d <-> ", temp->data);

temp = temp->next;

printf("NULL\n");

}
void deleteAtBeginning(struct Node** head) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");

return;

struct Node* temp = *head;

*head = (*head)->next;

if (*head != NULL) {

(*head)->prev = NULL;

free(temp);

printf("Node deleted from the beginning.\n");

void deleteAtEnd(struct Node** head) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");

return;

struct Node* temp = *head;

if (temp->next == NULL) {

*head = NULL;

free(temp);

printf("Node deleted from the end.\n");

return;

while (temp->next != NULL) {

temp = temp->next;

temp->prev->next = NULL;

free(temp);
printf("Node deleted from the end.\n");

void deleteAtPosition(struct Node** head, int position) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");

return;

struct Node* temp = *head;

if (position == 0) {

deleteAtBeginning(head);

return;

for (int i = 0; temp != NULL && i < position; i++) {

temp = temp->next;

if (temp == NULL) {

printf("Position out of bounds.\n");

return;

if (temp->next != NULL) {

temp->next->prev = temp->prev;

if (temp->prev != NULL) {

temp->prev->next = temp->next;

free(temp);

printf("Node deleted at position %d.\n", position);

void deleteByValue(struct Node** head, int value) {

if (*head == NULL) {

printf("The list is empty. Nothing to delete.\n");


return;

struct Node* temp = *head;

if (temp != NULL && temp->data == value) {

deleteAtBeginning(head);

return;

while (temp != NULL && temp->data != value) {

temp = temp->next;

if (temp == NULL) {

printf("Value not found in the list.\n");

return;

if (temp->next != NULL) {

temp->next->prev = temp->prev;

if (temp->prev != NULL) {

temp->prev->next = temp->next;

free(temp);

printf("Node with value %d deleted.\n", value);

int main() {

struct Node* head = NULL;

head = createNode(10);

head->next = createNode(20);

head->next->prev = head;

head->next->next = createNode(30);

head->next->next->prev = head->next;
traverseList(head);

// Case 1: Delete from the beginning

deleteAtBeginning(&head);

traverseList(head);

// Case 2: Delete from the end

deleteAtEnd(&head);

traverseList(head);

// Case 3: Delete at a specific position (position 0)

deleteAtPosition(&head, 0);

traverseList(head);

// Case 4: Delete by value

deleteByValue(&head, 20);

traverseList(head);

return 0;

Example Output:

Doubly Linked List: 10 <-> 20 <-> 30 <-> NULL

Node deleted from the beginning.

Doubly Linked List: 20 <-> 30 <-> NULL

Node deleted from the end.

Doubly Linked List: 20 <-> NULL

Node deleted at position 0.

Doubly Linked List: NULL

Node with value 20 deleted.

Doubly Linked List: NULL


5a) Compare and Contrast the Linked List and Arrays

A) In C, both Linked Lists and Arrays are used to store collections of data, but they are fundamentally
different in terms of structure, memory allocation, and performance. Below is a detailed comparison
and contrast of Linked Lists and Arrays based on various factors:

Comparison Summary

Property Array Linked List

Memory Allocation Fixed Size, Contiguous Dynamic, Non-contiguous

Access Time O(1) (Direct access by index) O(n) (Linear traversal)

O(n) (Requires shifting


Insertion/Deletion O(1) (For head/tail, O(n) for middle)
elements)

Size Fixed Size Dynamic, can grow/shrink

More memory usage per element due to


Memory Usage Efficient but may waste space
pointers

Cache
Good (Contiguous Memory) Poor (Non-contiguous Memory)
Performance

Use Case Static, known-size data Dynamic data, frequent insertions/deletions

5b) What is selection sort? Write an algorithm to implement Selection Sort with suitable example.

A) Selection Sort

Selection Sort is a simple sorting algorithm that works by repeatedly finding the minimum element
(or maximum, depending on the sorting order) from the unsorted part of the list and swapping it
with the first unsorted element. It is called "selection sort" because it repeatedly selects the smallest
(or largest) element and moves it to its correct position.

Algorithm for Selection Sort

The algorithm works as follows:

1. Start with the first element of the array and assume it is the minimum.
2. Compare it with the rest of the elements in the array to find the smallest element.

3. Swap the smallest element with the current element.

4. Move the boundary between the sorted and unsorted parts of the array to the right (i.e.,
consider the next element).

5. Repeat the process until the entire array is sorted.

C Code for Selection Sort

#include <stdio.h>

void selectionSort(int arr[], int n) {

int i, j, minIndex, temp;

for (i = 0; i < n - 1; i++) {

minIndex = i;

for (j = i + 1; j < n; j++) {

if (arr[j] < arr[minIndex]) {

minIndex = j;

if (minIndex != i) {

temp = arr[i];

arr[i] = arr[minIndex];

arr[minIndex] = temp;

void printArray(int arr[], int size) {

for (int i = 0; i < size; i++) {

printf("%d ", arr[i]);

printf("\n");

}
int main() {

int arr[] = {64, 25, 12, 22, 11};

int n = sizeof(arr) / sizeof(arr[0]);

printf("Original array: \n");

printArray(arr, n);

// Call selection sort function

selectionSort(arr, n);

printf("Sorted array: \n");

printArray(arr, n);

return 0;

6a) Mention the drawback of Single Linked List and explain how to overcome them.

A) Drawbacks of Singly Linked List and How to Overcome Them

A Singly Linked List (SLL) is a linear data structure where each node contains a data element and a
pointer to the next node. While it has many advantages, it also comes with certain drawbacks. Here
are the key drawbacks and how to overcome them:

Drawback Explanation Solution

Can only traverse in one direction Use a doubly linked list or reverse
Reverse Traversal
(head to tail). the list iteratively.

Track previous nodes during


No Direct Access to Cannot access the previous node from
traversal or use a doubly linked
Previous Node a given node.
list.

Searching requires sequential


Use hashing or indexing for faster
Inefficient Searching traversal, leading to O(n) time
searches.
complexity.
Drawback Explanation Solution

Keep track of the previous node


Difficulty in Insertion/deletion at arbitrary
during traversal or use a doubly
Insertion/Deletion positions requires extra traversal.
linked list.

No direct access to the tail node, Maintain a tail pointer or use a


No Direct Access to Tail
leading to O(n) time for tail insertion. doubly linked list.

Optimize memory or use other


Extra memory for next pointers in each
Memory Overhead data structures if memory is
node.
critical.

Reversing is complex as it requires Reverse iteratively or use a doubly


Reversing the List
changing pointers manually. linked list.

Requires multiple traversals for


Handling Multiple Use batch processing or more
multiple operations, leading to
Operations efficient data structures like trees.
inefficiency.

6b) Differentiate Single Linked list and Double Linked List.

A) Difference Between Singly Linked List and Doubly Linked List

Both Singly Linked List (SLL) and Doubly Linked List (DLL) are types of linked lists used to store
collections of data, but they differ significantly in their structure and behavior. Here’s a detailed
comparison between the two:

Summary of Differences

Property Singly Linked List Doubly Linked List

Node Structure Data + Next Pointer Data + Next Pointer + Prev Pointer

One-direction (Head to
Traversal Two-direction (Head to Tail, Tail to Head)
Tail)

Less memory (1 pointer


Memory Usage More memory (2 pointers per node)
per node)

Insertion/Deletion at
O(1) O(1)
Head
Property Singly Linked List Doubly Linked List

Insertion/Deletion at
O(n) O(1)
Tail

Insertion/Deletion in
O(n) O(1)
Middle

Difficult (requires
Reversing Easy (swap next/prev pointers)
traversal)

Simple lists, stacks, Complex navigation systems, undo/redo


Use Case
queues functionality, browser history

You might also like