Sorting Algorithms Review

本文深入浅出地介绍了常见的排序算法,包括冒泡排序、选择排序、插入排序等,并提供了直观的解释和C++实现代码。文章还总结了各种排序算法的时间复杂度,帮助读者快速掌握算法特性。

Introduction


Understanding the basic concepts of the popular sorting algorithms, can not only help you better understand the data structure and algorithm from a different perspective, but also helps you make the computational complexity clearer in your mind. Besides, sorting related questions, are also hot questions in your software engineering job interview.

Many books, courses, and websites are providing massive materials of sorting algorithms.  From my own experiences, among the long and tedious sources,  http://www.sorting-algorithms.com/ , and  https://en.wikipedia.org/wiki/Sorting_algorithm  are two good places you can learn the sorting algorithm intuitively, because there are animations shown in the website. If you have already familiar or have the basic concept of the sorting algorithms, it would help you easily memorize the details of specific algorithm.

In this blog, I'd like to review the common popular sorting algorithms, with the basic concepts, and tries to explain each one intuitively. Also the C++ implementation is the core content and are provided detailed with comments. This blog is more suitable for who have already implemented some of the sorting algorithms (at least you may write the selection sorting or bubble sorting in 1 or 2 minutes), but I will try to explain more for the novices.


1. What algorithms are covered ?


  1.     Bubble sort  
  2.     Selection sort
  3.     Inset sort
  4.     Merge sort
  5.     Quick sort
  6.     Heap sort
  7.     Bucket sort

2. Computational Complexity   

  1.     Bubble sort                       O(n^2 )
  2.     Selection sort                    O(n^2)
  3.     Inset sort                           O(n^2)
  4.     Merge sort                        O(nlogn)
  5.     Quick sort                         O(nlogn)
  6.     Heap sort                          O(nlogn)
  7.     Bucket sort                       O(n+k)
    How to know these in a fast way?  Just memorize them!  My experience is that 
  • If there are 2 loops in the code, it is O(n^2)
  • If the algorithm is easily implemented, it is O(n^2)" 
  • Otherwise it is O(nlogn), expect the bucket sort.

3. Concepts and Implementations

The sorting problem is quite straightforward----sort the data array (ascending order). We will not go into the detail that what kind of data structure or data type are used, and not interested in the comparison function.

Here we define the general case:
Input: 
    int A[];  // unsorted int array
Output:
   int A[];      // sorted array (ascending order)

Function:
  swap(int A[], int a, int b) // swap the value ofA[a] and A[b]
Code:
public void swap(int[]A,int a, int b){
		int temp = A[a];
		A[a] = A[b];
		A[b] = temp;
	}

Bubble sort
-----------------------------------------------------------------------------------
Concept: 
Scan from start to end, compare every two elements, swap the max to the end.
Scan from start to end-1, compare every two elements, swap the max to the end-1.
Scan from start to end-2, compare every two elements, swap the max to the end-2.
...
Scan from start to start, end.

Key Point:

if A[j]>A[j+1], swap(A[j], A[j+1]);

How to Memorize:
Compare each  pair and  bubble the max value out and move to the last.

Code:

public int[] bubbleSorting(int []A){
		for(int i=A.length-1;i>=0;i--){
			for(int j=0;j<i;j++){
				if(A[j]>A[j+1])
					swap(A, j, j+1);
			}
		}
		return A;
	}

Selection sort

-----------------------------------------------------------------------------------
Concept:
From 1st to last, find the min value,  store to the 1st position.
From 2nd to last, find the min value, store to the 2nd position.
From 3rd to last, find the min value, store to the 3rd position.
...
From last-1 to last, find the smaller one, store to the last-1 position. End.

Key Point:
k=i;  // store the current start position
if (A[j]<A[k]) {k=j;} // store the index of min value

How to Memorize:
Select  the  min  value and store to the front.

Code

public int[] selectionSorting(int[] A){
		for(int i=0;i<A.length;i++){
			int k=i;
			for(int j=i+1;j<A.length;j++){
				if(A[j]<A[k]) k = j;
			}
			swap(A, i, k);
		}
		return A;
	}

Insertion sort

-----------------------------------------------------------------------------------
Concept:
For each element A[i], the array A[0..i-1] is already sorted. Scan A[0..i-1], find the correct place and insert A[i].

Key Point:
Find the correct place and insert the A[i] in sorted A[0..i-1].
Consider A[0..i]:   [1,3,4,5,8,2],
So, A[i]=2.
Store it to a variable: tmp = A[i];
What we need to do now?
[1,3,4,5,8, 2 ] ---->  [1, 2 ,3,4,5,8]
How to do this?
Keep moving each element to its right (A[j]=A[j-1]), until the next element is less than  A[i].

How to Memorize:
Insert  every element to its previous sorted array.

Code

public int[] insertionSorting(int[] A){
		for(int i=1;i<A.length;i++){
			int temp = A[i];
			int j = i-1;
			while(j>=0 && A[j]>temp){
				A[j+1] = A[j];
				j--;
			}
			A[j+1] = temp;
		}
		return A;
	}

Merge sort

-----------------------------------------------------------------------------------
Concept:
Here I interpret merge sort using the recursion. So hopefully you have the basic idea what recursion is.
The idea is mainly considering the array into smaller subsets,  merge the smallest subsets to smaller subset, merge smaller subsets to small subset ... until merge subset to the whole array. Merging process itself handles the sorting.

This figure (from wikipedia) shows the exact process of merge sort. Please go through the whole tree at the same time thinking it as a recursive problem, this will greatly help you understand the implementation of this algorithm.

Key Point:
Merge sort consist two parts:
(1) Recursion Part.
(2) Merge Part.

Recursive part, handles divided the current set to two parts, just like the concept of  divide-and-conquer:  Find the middle, divide into left and right subset and continue dividing in each subset. Recursion also keeps the two subset sorted for the merging.

Merge Part, is very very important for this algorithm, which merges two array to make a new sorted array.
How to do it ? Let's take a example.
Assume: A1=[1,5,7,8] and A2=[2,6,9,10,11,12,13]
What we need ?  A new sorted array A = [1,2,5,6,7,8,9,10,11,12,13]
OK, now at least we need a new array
A of length A1+A2, say,  A[ , , , , , , , ,].
How to put element in A and considering the order?
Set 3 pointers i,j,k, for A1, A2, and target array A.
A1=[1,5,7,8]
        i
A2=[2,6,9,10,11,12,13]
        j
A =[ , , , , , , , ,].
       k
From above one can clearly see, the 1st element in A (A[k]), should be min(A1[i],A2[j]), it is A1[i] = 1. So A1[i] is already in A, then we go to the next element in A1 using i++.  And the 1st element in A is filled, we have to go to the next one, so k++.
A1=[1,5,7,8]
             i
A2=[2,6,9,10,11,12,13]
        j
A =[1, , , , , , , ,].
          k
Next,  similarly compare A[i] and A[j],  get the smaller one and fill into the array A, and set the pointers properly.
A1=[1,5,7,8]
             i
A2=[2,6,9,10,11,12,13]
             j
A =[1,2, , , , , , ,].
             k     
In such a way, the loop goes until the end of A1. At this time, the merge is NOT finished, we have to combine the rest elements of A2 into A.
Finally,  A = [1,2,5,6,7,8,9,10,11,12,13].


How to Memorize:
(1) Recursion Part: divide from the middle
(2) Merge Part:  merge two sorted array into one sorted array

Code

/*
	 * merge sort algorithm implement
	 * */
	public void mergeSort(int []A, int p, int r){
		if(p<r){
			int q = (p+r)/2;
			mergeSort(A, p, q);
			mergeSort(A, q+1, r);
			merge(A, p, q, r);
		}
	}
	
	public void merge(int[] A, int p, int q, int r){
		int[] L = new int[q-p+1];
		int[] R = new int[r-q];
		for(int i=0;i<L.length;i++)
			L[i] = A[i+p];
		for(int i=0;i<R.length;i++)
			R[i] = A[q+i+1];
		int i = 0;
		int j = 0;
		int k = p;
		while(i<L.length && j<R.length){
			if(L[i]<R[j])
				A[k++] = L[i++];
			else
				A[k++] = R[j++];
		}
		while(i<L.length)
			A[k++] = L[i++];
		while(j<R.length)
			A[k++] = R[j++];
	}

Quick sort

-----------------------------------------------------------------------------------
Concept:
This is also a  Divide and Conquer  algorithm. The idea is:  for an element pivot in the array,  place the elements less than pivot to its left, and elements greater than pivot to its right.  Do this same procedure to the two subsets (left and right), until all the elements are sorted.

Key Point:
Quick sort mainly consisted two parts:
(1) Recursive part: recursively apply the for the subsets.
(2) Reorder the set, where elements < pivot value are placed to its left, and vice versa.
     This is the important part of the algorithm:
     Consider the array
     A=[8,3,5,6,4,1,9]
     Here we choose the middle element as the pivot (also can select the 1st one or randomly select)
     What we want to do ?
    A[1,3,5,4, 6, 8,9],  then sort[1,3,5,4,6] and [8,9] recursively.

     First, put pivot to the front (swap(A[0],A[pivot])):
     A=[ 6 ,3,5,8,4,1,9]
    Then set two pointers i and p, start from the 2nd element.  p points to the first element which is bigger than pivot. 
     A=[ 6 ,3,5,8,4,1,9]
               i         
              p
    Compare A[i] with A[0], if A[i] < A[0], swap A[i] and A[p], goto next.
     A=[ 6 ,3,5,8,4,1,9]
                  i         
                 p
     and
     A=[ 6 ,3,5,8,4,1,9]
                     i         
                    p
     here A[i]>A[0], no swap, i++
     A=[ 6 ,3,5,8,4,1,9]
                        i         
                    p
     4<6, swap A[i] and A[p], because A[p] was found larger than A[0]
     A=[ 6 ,3,5,4,8,1,9]
                           i         
                       p
     Still have to swap:
     A=[ 6 ,3,5,4,1,8,9]
                              i         
                          p
     No swap, i goes to the end, and now p is the place where 0..p-1 < pivot, and p..n > pivot.
     Last step is to swap A[0] and A[p-1]:
    A[1,3,5,4,6,8,9]

How to Memorize:
(1) Recursion (divide-and-conquer)
(2) Select a Pivot
(3) Aim: reorder elements<pivot to the left and elements>pivot to the right
(4) Set pivot to front
(5) Set two pinter

Code

/*
	 * QuickSort algorithm implement
	 * */
	public void QuickSort(int[] A, int p, int r){
		if(p<r){
			int q = Partition(A, p, r);
			QuickSort(A, p, q-1);
			QuickSort(A, q+1, r);
		}
	}
	
	public int Partition(int[] A, int p, int r){
		int x = A[r];
		int i = p-1;
		for(int j=p;j<r;j++){
			if(A[j]<=x){
				i++;
				swap(A, i, j);
			}
		}
		swap(A, i+1, r);
		return i+1;
	}

Heap sort

-----------------------------------------------------------------------------------
Concept:
Heap sort is based on the data structure  heap,  which is a tree structure with a nice ordering property, can be used for sorting.  The heap sort algorithm consists of two parts:
(1) Construct the heap
(2) Get the root node each time, update the heap, until all the node are removed.

First let's see what is heap (in my own word):
A heap, briefly speaking, is a tree structure, where the value of each parent node is greater/smaller than its children. Practically in the heap sort, we use the specific kind of heap----binary heap.

Binary heap,  is a complete binary tree, also keeps the property that each root value is greater or smaller than its left and right children. Thus, the root of the tree is the biggest (called max heap) or the smallest (called min heap) element in the tree.

Caution!!!  A Heap is NOT a binary search tree(BST)! A BST can apply in-order traversal to get the sorted  array, heap CANNOT guarantee the ordering within same level, so it does not have a specific requirement of the order for the left and right children. e.g. see the figure below(from wikipedia)
A heap structure: 
A binary search tree structure: 




  • How to construct the heap?
          Consider a unsorted array, we want to construct a heap. The intuitive way is to obtain every node and add to the tree structure(TreeNode* blablabla...), but a simpler way is just use the array itself, to represent the tree and modify the value to construct the heap.

       Tree  (Array  representation):
            Root node: A[0].
            Left child   of A[i]:  2*i+1
            Right child of A[i]:  2*i+2
            Parent of A[i]:        (i-1)/2

      Construct a heap:
           An operation downshift is used here.
           The idea of downshift is to adjust the current node to its proper position in its downside direction.
           Given a node, compare the value to the bigger one of its left and right children, is the value is smaller than the bigger children, then swap them. And keep checking the node (here in the new position after swapping), until it is no less than its children.
            To construct the heap, from the end of the array, we downshift every node to the first in the array.
          
  • How to get the sorted array according to the heap?
         Given a max heap, the value of root node is the biggest in the heap, each time remove the top node and store to the array. But it's not enough! We have to keep the heap, so there needs a update of the remaining nodes.  An efficient way is just swap the root node and the last element of the tree, remove the last node (just let the length of the array -1), downshift the new root node, a heap is updated.



Key Point:
(1) How to construct the heap?
       Use array to represent the tree structure.
      Recursively downshift every node.
(2) How to get the sorted array according to the heap?
      Each time remove the root of the heap, swap the last node to the root and downshift it.
      Until all the nodes are removed.

How to Memorize:
This algorithm is very particular and requires the skill of heap operations (construct, downshift, update, etc.).
In my opinion, first you get to know the data structure heap, then the heap sort suddenly becomes a piece of cake!

Code

/*
	 * Heap sort algorithm implement
	 * */
	public int parent(int i){
		return (i-1)/2;
	}
	public int left(int i){
		return i*2+1;
	}
	public int right(int i){
		return i*2+2;
	}
	public void MaxHeapify(int[] A, int n, int i){
		int l = left(i);
		int r = right(i);
		int largest = i;
		if(l<n && A[l]>A[i])
			largest = l;
		if(r<n && A[r]>A[largest])
			largest = r;
		if(largest!=i){
			swap(A, i, largest);
			MaxHeapify(A, n, largest);
		}
	}
	
	public void buildMaxHeap(int []A, int n){
		int parent = (n-2)/2;
		for(;parent>=0;parent--)
			MaxHeapify(A, n, parent);
	}
	public void HeapSort(int []A){
		buildMaxHeap(A, A.length);
		for(int i=A.length-1;i>=0;i--){
			swap(A, 0, i);
			MaxHeapify(A, i, 0);
		}
	}

ps. Here is a good demo for heap sort

http://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif





排序算法是指一种将一组数据按照特定顺序重新排列的算法。在计算机科学中,排序是一项基本操作,用于将数据按照某种规律排列,使其更容易查找和使用。排序算法的效率是衡量其好坏的主要指标,通常通过时间复杂度和空间复杂度来衡量。 常见的排序算法有冒泡排序、选择排序、插入排序、归并排序、快速排序等。这些算法的实现方法各有不同,但基本思路都是通过比较和交换元素的位置来实现排序的目的。 最简单的排序算法之一是冒泡排序。冒泡排序通过重复地遍历待排序的数据,每次比较两个相邻的元素,如果它们的顺序错误就交换它们的位置,直到整个序列都是有序的。 选择排序是另一种常见的排序算法,它的思路是每次选择未排序部分的最小元素,将其放在已排序部分的末尾,直到所有元素都被排序。 插入排序则是将数据分成已排序和未排序两部分,每次将未排序部分的第一个元素插入到已排序部分的正确位置,直到所有元素都被排序。 归并排序是一种分治策略的排序算法,它将待排序的序列先分成两个子序列,然后对子序列进行排序,最后将已排序的子序列合并成一个有序的序列。 快速排序是一种效率较高的排序算法,它的基本思想是通过一次划分操作将待排序序列分成两个子序列,其中一个子序列的所有元素都小于划分元素,另一个子序列的所有元素都大于划分元素,然后分别对两个子序列进行排序。 以上仅是一些常见的排序算法,每种算法的适用场景和特点都不同,选择合适的排序算法可以提高排序的效率。总之,排序算法在数据处理和计算领域中起着重要的作用,是计算机科学中的基础知识之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值