cf. Binary Search
Given a sorted array arr[], of n elements, write a function to search a given element x in arr[]
In [19]:
    
def binarySearch(arr,l,r,x):
    """ 
        @details denote L = len(arr)
        @param indices l,r=0,1...L-1
        @note l <= r expected
        base case is if l>r, then stop, nothing was found 
    """
    if (r<l):
        return -1 # x was not found
    else:
        mid = l + (r-l)/2
        
        if arr[mid] == x: # found it, success!  
            return mid
        # if arr[mid] > x, then we know x is "to the left" of mid
        elif arr[mid]> x:
            r = mid-1
            return binarySearch(arr,l,r,x)
        else: # then we know x is "to the right" of mid
            l=mid+1
            return binarySearch(arr,l,r,x)
    
In [20]:
    
arr=[2,3,4,10,40]
    
In [21]:
    
x=10
    
In [22]:
    
result=binarySearch(arr,0,len(arr)-1,x)
print(result)
    
    
In [23]:
    
# Returns index of x in arr if present, else -1
def binarySearch (arr, l, r, x):
 
    # Check base case
    if r >= l:
 
        mid = l + (r - l)/2
 
        # If element is present at the middle itself
        if arr[mid] == x:
            return mid
         
        # If element is smaller than mid, then it can only
        # be present in left subarray
        elif arr[mid] > x:
            return binarySearch(arr, l, mid-1, x)
 
        # Else the element can only be present in right subarray
        else:
            return binarySearch(arr, mid+1, r, x)
 
    else:
        # Element is not present in the array
        return -1
    
In [25]:
    
def binarySearch_iter(arr,l,r,x):
    while (l<=r):
        mid = l + (r-l)/2
        if arr[mid] == x:
            return mid
        elif arr[mid] > x: # x is "to the left" of mid and it's not at mid
            r = mid-1
        else: # x is "to the right" of mid and it's not mid
            l=mid+1
        
    return -1 # x was not found at all
    
In [26]:
    
print(binarySearch_iter(arr,0,len(arr)-1,x))
    
    
Time complexity, time complexity of Binary Search is $T(n) = T(n/2)+c$, above recurrence can be solved using Recurrence, $O(\log{n})$.
http://www.geeksforgeeks.org/merge-sort/
http://interactivepython.org/runestone/static/pythonds/SortSearch/TheMergeSort.html
base case is sublist contains only 1 item or is empty.
If list has more than 1 item, split list and recursively invoke a merge sort on both halves.
Merging is the process of taking 2 smaller sorted lists and combining them together into a single sorted, new list.
In [39]:
    
def mergeSort(arr):
    print("splitting",arr)
    if len(arr) > 1: # then we continue to split
        mid = len(arr) // 2
        l_arr=arr[:mid] # left half of arr
        r_arr = arr[mid:] # right half of arr
        
        mergeSort(l_arr)
        mergeSort(r_arr)
        
        # now difficult part; how do we "blend" or merge together 2 sorted halves into 1 sorted list?
        i=0 # index for l_arr, i=0,1...len(l_arr)-1
        j=0 # index for r_arr, j=0,1...len(r_arr)-1
        k=0 # index for arr,   k=0,1...len(arr)-1
        while i<len(l_arr) and j < len(r_arr):
            if l_arr[i] < r_arr[j]: # so l_arr into arr, and increment l_arr's index, i 
                arr[k] = l_arr[i]
                i+=1 
            else:
                arr[k] = r_arr[j]
                j+=1
            k=k+1
        # then deal with "leftovers"
        while i < len(l_arr):
            arr[k] = l_arr[i]
            i+=1
            k+=1
        
        while j < len(r_arr):
            arr[k] = r_arr[j]
            j+=1
            k+=1
    print("Merging",arr)
#    return arr
    
In [40]:
    
alist = [54,26,93,17,77,31,44,55,20]
#alist_1 = mergeSort(alist)
mergeSort(alist)
    
    
In [35]:
    
alist
    
    Out[35]:
In [36]:
    
def mergeSort(alist):
    print("Splitting ",alist)
    if len(alist)>1:
        mid = len(alist)//2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]
        mergeSort(lefthalf)
        mergeSort(righthalf)
        i=0
        j=0
        k=0
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
            else:
                alist[k]=righthalf[j]
                j=j+1
            k=k+1
        while i < len(lefthalf):
            alist[k]=lefthalf[i]
            i=i+1
            k=k+1
        while j < len(righthalf):
            alist[k]=righthalf[j]
            j=j+1
            k=k+1
    print("Merging ",alist)
    
In [37]:
    
alist = [54,26,93,17,77,31,44,55,20]
mergeSort(alist)
print(alist)
    
    
In [42]:
    
def quickSort(arr,l,r):
    if l < r:
        ip = partition(arr,l,r)
        
        quickSort(arr,l,ip-1)
        quickSort(arr,ip+1,r)
        
def partition(arr,l,r):
    # the goal of the partition process is to move items that are on the wrong side 
    # with respect to the pivot value, while also converging on the split point
    i = (l-1) # index of smaller element
    pivot = arr[r]
    
    for j in range(l,r):
        if arr[j]<= pivot:
            i +=1 # increment smaller element
            arr[i],arr[j] = arr[j],arr[i] # swap
    arr[i+1],arr[r] = arr[r],arr[i+1]  # position of i+1 is now the split point, 
    # pivot value can be exchanged with contents of split point
    return (i+1)
    
In [43]:
    
arr = [10,7,8,9,1,5]
n=len(arr)
quickSort(arr,0,n-1)
print(arr)
    
    
In [ ]: