Академический Документы
Профессиональный Документы
Культура Документы
Most of the BST operations (e.g., search, max, min, insert, delete.. etc) take O(h) time where h is the height
of the BST. The cost of these operations may become O(n) for a skewed Binary tree. If we make sure that
height of the tree remains O(Logn) after every insertion and deletion, then we can guarantee an upper
bound of O(Logn) for all these operations. The height of an AVL tree is always O(Logn) where n is the number
of nodes in the tree (See this video lecture for proof).
Insertion
To make sure that the given tree remains AVL after every insertion, we must augment the standard BST
insert operation to perform some re-balancing. Following are two basic operations that can be performed
to re-balance a BST without violating the BST property (keys(left) < key(root) < keys(right)). 1) Left Rotation
2) Right Rotation
T1, T2 and T3 are subtrees of the tree rooted with y (on the left side) or x (on the right side)
y x
/ \ Right Rotation / \
x T3 –––––––> T1 y
/ \ <------- / \
T1 T2 Left Rotation T2 T3
Keys in both of the above trees follow the following order keys(T1) < key(x) < keys(T2) < key(y) <
keys(T3), So BST property is not violated anywhere.
/* Helper function that allocates a new node with the given key and NULL left and right pointers. */
struct Node* newNode(int key) {
struct Node* node = (struct Node*) malloc(sizeof(struct Node));
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 1; // new node is initially added at leaf
return(node);
}
// A utility function to right rotate subtree rooted with y. See the diagram given above.
struct Node *rightRotate(struct Node *y) {
struct Node *x = y->left;
struct Node *T2 = x->right;
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left), height(y->right))+1;
x->height = max(height(x->left), height(x->right))+1;
// Return new root
return x;
}
// A utility function to left rotate subtree rooted with x. See the diagram given above.
struct Node *leftRotate(struct Node *x) {
struct Node *y = x->right;
struct Node *T2 = y->left;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left), height(x->right))+1;
y->height = max(height(y->left), height(y->right))+1;
// Return new root
return y;
}
// A utility function to print preorder traversal of the tree. The function also prints height of every node
void preOrder(struct Node *root) {
if(root != NULL) {
printf("%d ", root->key);
preOrder(root->left);
preOrder(root->right);
}
}
Time Complexity: The rotation operations (left and right rotate) take constant time as only a few pointers
are being changed there. Updating the height and getting the balance factor also takes constant time. So
the time complexity of AVL insert remains same as BST insert which is O(h) where h is the height of the tree.
Since AVL tree is balanced, the height is O(Logn). So time complexity of AVL insert is O(Logn).
Huffman Tree
This problem is that of finding the minimum length bit string which can be used to encode a string of symbols.
One application is text compression:
What's the smallest number of bits (hence the minimum size of file) we can use to store an arbitrary piece
of text?
Huffman's scheme uses a table of frequency of occurrence for each symbol (or character) in the input. This
table may be derived from the input itself or from data which is representative of the input. For instance,
the frequency of occurrence of letters in normal English might be derived from processing a large number
of text documents and then used for encoding all text documents. We then need to assign a variable-length
bit string to each character that unambiguously represents that character. This means that the encoding for
each character must have a unique prefix. If the characters to be encoded are arranged in a binary tree:
A divide-and-conquer approach might have us asking which characters should appear in the left and right
subtrees and trying to build the tree from the top down. As with the optimal binary search tree, this will lead
to to an exponential time algorithm.
A greedy approach places our n characters in n sub-trees and starts by combining the two least weight nodes
into a tree which is assigned the sum of the two leaf node weights as the weight for its root node.
Sorting
A Sorting Algorithm is used to rearrange a given array or list elements according to a comparison operator
on the elements. The comparison operator is used to decide the new order of element in the respective
data structure.
Selection Sort Pigeonhole Sort Tag Sort (To get both
Bubble Sort Cycle Sort sorted and original)
Recursive Bubble Sort Cocktail Sort Tree Sort
Insertion Sort Strand Sort Cartesian Tree Sorting
Recursive Insertion Sort Bitonic Sort Odd-Even Sort / Brick Sort
Merge Sort Pancake sorting QuickSort on Singly Linked
Iterative Merge Sort Binary Insertion Sort List
Quick Sort BogoSort or permutation QuickSort on Doubly
Iterative Quick Sort Sort Linked List
Heap Sort Gnome Sort 3-Way QuickSort (Dutch
Counting Sort Sleep Sort – The King of National Flag)
Radix Sort Laziness / Sorting while Merge Sort for Linked Lists
Bucket Sort Sleeping Merge Sort for Doubly
ShellSort Structure Sorting (By Linked List
TimSort Multiple Rules) in C++ 3-way Merge Sort
Comb Sort Stooge Sort
Internal & External Sort
In internal sorting all the data to sort is stored in memory at all times while sorting is in progress. In external
sorting data is stored outside memory (like on disk) and only loaded into memory in small chunks. External
sorting is usually applied in cases when data can't fit into memory entirely.
So in internal sorting you can do something like shell sort - just access whatever array elements you want at
whatever moment you want. You can't do that in external sorting - the array is not entirely in memory, so
you can't just randomly access any element in memory and accessing it randomly on disk is usually extremely
slow. The external sorting algorithm has to deal with loading and unloading chunks of data in optimal
manner.
Insertion sort
Insertion sort is a to some extent an interesting algorithm with an expensive runtime characteristic having
O(n2). This algorithm can be best thought of as a sorting scheme which can be compared to that of sorting
a hand of playing cards, i.e., you take one card and then look at the rest with the intent of building up an
ordered set of cards in your hand.
Insertion Algorithms:
1. If it is the first element, it is already sorted.
2. Pick the next element.
3. Compare with all the elements in sorted sub-list.
4. Shift all the elements in sorted sub-list that is greater than the value to be sorted.
5. Insert the value.
6. Repeat until list is sorted.
Important Characteristics of Insertion Sort:
It is efficient for smaller data sets, but very inefficient for larger lists.
Insertion Sort is adaptive, that means it reduces its total number of steps if given a partially sorted list,
hence it increases its efficiency.
Its space complexity is less. Insertion sort requires a single additional memory space.
Overall time complexity of Insertion sort is O(n2).
Input elements: 89 17 8 12 0
Step 1: 89 17 8 12 0 (the bold elements are sorted list and non-bold unsorted list)
Step 2: 17 89 8 12 0 (each element will be removed from unsorted list and placed at the right position in
the sorted list)
Step 3: 8 17 89 12 0
Step 4: 8 12 17 89 0
Step 5: 0 8 12 17 89
If there are n elements to be sorted then, the process mentioned above should be repeated n-1 times to get
required result. But, for better performance, in second step, comparison starts from second element
because after first step, the required number is automatically placed at the first (i.e, In case of sorting in
ascending order, smallest element will be at first and in case of sorting in descending order, largest element
will be at first.). Similarly, in third step, comparison starts from third element and so on.
A figure is worth 1000 words. This figure below clearly explains the working of selection sort algorithm.