def functionX(L):
""" L is a non-empty list of length len(L) = n. """
i= 1
while i< len(L) -1:
j = i-1
while j <= i+ 1:
L[j] = L[j] + L[i]
j = j + 1
i= i+ 1
For j loop why do we have 3 iterations each with 3 steps instead of i iterations? I have hard time figuring it out.
Is it clearer with for loops than while loops?
def functionY(L):
N = len(L)
for i in range(1,N-1):
for j in range(i-1,i+2):
L[j] = L[j] + L[i]
How about pseudo-code?
for i in range(N): # drop the -1s on both ends; O(n-2) = O(n)
for j in range(3): # (i-1) to (i+2) covers 3 elements
do something
This makes it pretty clear that Tony's answer is correct, we're in the class O(n). Specifically, the line L[j] = L[j] + L[i] will be accessed 3n-6 times. This is in the complexity class O(3n) = O(n). If you're looking at array accesses as your atomic operation, then we have O(3*(3n-6)) = O(n), still. The complexity class would not change if the line read L[j] += L[i], though the total number of array accesses would go down.
You have n iterations of the outer loop and in every outer-loop-iteration, 3 iterations of the inner loop, because for given i, variable j has a value of i - 1, i and i + 1. Therefore the complexity equals O(3 * n) = O(n).
Related
Because selection sort divides the input list into two parts: a sublist of sorted items and a sublist of still unsorted items, would it be possible to create two different functions and connect them together? I have my sorting algorithm done but I cannot figure out how to split them.
def selection_sort(l):
for i in range(len(l)):
start_index = i
for j in range(i + 1, len(l)):
if l[start_index] > l[j]:
min_value = j
l[i], l[start_index] = l[start_index], l[i]
l = [64, 25, 12, 22, 11]
print(l)
What I would like to have is to split it into this:
def min_index(l, start_index) & def selection_sort(l)
First of all, the code you have presented is not correct:
There is only one swap happening, unconditionally, as the last statement of the function. This cannot be right of course. The number of swaps needed to sort a list varies depending its order.
The second loop never iterates: i has reached the length of the input, and so range(i + 1, len(i)) is an empty range.
The value of min_value is never used.
The first two issues are probably because you copied the code in a wrong way, and missed some necessary indentation. The second issue is because of two different names which should really be the same name.
Here is a corrected version:
def selection_sort(l):
for i in range(len(l)):
start_index = i
for j in range(i + 1, len(l)):
if l[start_index] > l[j]:
start_index = j
l[i], l[start_index] = l[start_index], l[i]
Now to your question: you can indeed put the logic of the inner loop into another function. Like this:
def min_index(l, i):
for j in range(i + 1, len(l)):
if l[i] > l[j]:
i = j
return i
def selection_sort(l):
for i in range(len(l)):
j = min_index(l, i)
l[i], l[j] = l[j], l[i]
Your selection sort was buggy, so I corrected it. In short:
The indentation was incorrect so the loops were not nested (which I assume probably happened due to formatting issue on this platform).
in your inner loop you test start_index against j but update min_value.
at the end of the inner loop you use start_index for swapping. start_index is always equal to i.
Here's the corrected version with some variable renaming for better reading.
def selection_sort(arr):
for i in range(len(arr)):
min_index = i
for j in range(i + 1, len(arr)):
if arr[min_index] > arr[j]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
The refactored version into 2 parts:
def selection_sort2(arr):
for i in range(len(arr)-1):
min_index = get_min_index(i, arr)
arr[i], arr[min_index] = arr[min_index], arr[i]
def get_min_index(i, arr):
min_index = i
for j in range(i + 1, len(arr)):
if arr[min_index] > arr[j]:
min_index = j
return min_index
I'm teaching a coding class and need an intuitive and obvious way to explain the time complexity of merge sort. I tried including a print statement at the start of my merge_sort() function, anticipating that the print statement would execute O(log n) times. However, as best as I can tell, it executes 2*n-1 times instead (Python code below):
merge_sort() function:
def merge_sort(my_list):
print("hi") #prints 2*n-1 times??
if(len(my_list) <= 1):
return
mid = len(my_list)//2
l = my_list[:mid]
r = my_list[mid:]
merge_sort(l)
merge_sort(r)
i = 0
j = 0
k = 0
while(i < len(l) or j < len(r)):
#print("hey") #prints nlogn times as expected
if(i >= len(l)):
my_list[k] = r[j]
j += 1
elif(j >= len(r)):
my_list[k] = l[i]
i += 1
elif(l[i] < r[j]):
my_list[k] = l[i]
i += 1
elif(l[i] > r[j]):
my_list[k] = r[j]
j += 1
k += 1
Driver code:
#print("Enter a list")
my_list = list(map(int, input().split()))
#print("Sorted list:")
#merge_sort(my_list)
print(my_list)
Input:
1 2 3 4 5 6 7 8
Expected output:
hi
hi
hi
or some variation thereof which varies proportional to log n.
Actual output:
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi
hi #15 times, i.e. 2*n-1
A few more iterations of this with different input sizes have given me the impression that this is 2*n-1, which makes no sense to me. Does anyone have an explanation for this?
It is not true that there are only O(logn) recursive calls. The thing that is O(logn) is the depth of the recursion tree, not the number of nodes in the recursion tree.
When we look at one level of the recursion tree, then we can note that each call in that level deals with a distinct partition of the array. Together, the "nodes" in that recursion level, deal with all elements of the array, which gives that level a O(n) time complexity. This is true for each level.
As there are O(logn) levels, the total complexity comes down to O(nlogn).
Here is a suggestion on how to illustrate this:
statistics = []
def merge_sort(my_list, depth=0):
if len(my_list) <= 1:
return
# manage statistics
if depth >= len(statistics):
statistics.append(0) # for each depth we count operations
mid = len(my_list)//2
l = my_list[:mid]
r = my_list[mid:]
merge_sort(l, depth+1)
merge_sort(r, depth+1)
i = 0
j = 0
k = 0
while i < len(l) or j < len(r):
statistics[depth] += 1 # count this as a O(1) unit of work
if i >= len(l):
my_list[k] = r[j]
j += 1
elif j >= len(r):
my_list[k] = l[i]
i += 1
elif l[i] < r[j]:
my_list[k] = l[i]
i += 1
elif l[i] > r[j]:
my_list[k] = r[j]
j += 1
k += 1
import random
my_list = list(range(32))
random.shuffle(my_list)
merge_sort(my_list)
print(my_list)
print(statistics)
The statistics will output the number of units of work done at each level. In the example of an input size of 32, you'll get a list with 5 such numbers.
NB: In Python, if conditions don't need parentheses
I've seen this interesting question, and wonder if there are more ways to approach it:
Given a permutation of numbers from 1 to n, count the number of
quadruples indices (i,j,k,l) such that i<j<k<l and A[i]<A[k]<A[j]<A[l]
e.g.
Input : [1,3,2,6,5,4]
Output : 1 (1,3,2,6)
Desired algorithm is O(n^2)
Approach:
I've tried to solve it using stack, in a similiar manner to Leetcode 132 pattern - but it seems to fail.
def get_smaller_before(A):
smaller_before = [0] * len(A)
for i in range(len(A)):
for j in range(i):
if A[j] < A[i]:
smaller_before[i] += 1
return smaller_before
def get_larger_after(A):
larger_after = [0] * len(A)
for i in range(len(A)):
for j in range(i+1, len(A)):
if A[i] < A[j]:
larger_after[i] += 1
return larger_after
def countQuadrples(nums):
if not nums:
return False
smaller_before = get_smaller_before(nums)
larger_after = get_larger_after(nums)
counter = 0
stack = []
for j in reversed(range(1, len(nums))):
# i < j < k < l
# smaller_before < nums[k] < nums[j] < larger_after
while stack and nums[stack[-1]] < nums[j]:
counter += smaller_before[j] * larger_after[stack[-1]]
stack.pop()
stack.append(j)
return counter
Does anyone has a better idea?
What you need is some sort of 2-dimensional tree that allows you to quickly answer the question "How many points after k have value bigger than A[j]," and the question "How many points before j have value less than A[k]?" These will usually be time O(n log(n)) to build and those queries should run in time something like O(log(n)^2)).
A number of such data structures exist. One option is a variant on a Quadtree. You you turn each array element into a point with x-coordinate the position in the array and y-coordinate being its value. And your queries are just counting how many elements are in a box.
And now you can do a double loop over all j, k and count how many zig-zag quadruples have those as the inner pair.
The entry Y [i][j] stores the sum of the subarray X[i..j], but can I get a better time complexity?
def func(X, n):
Y = [[0 for i in range(n)] for j in range(n)]
for i in range(n):
for j in range(i, n):
for k in range(i, j+1):
Y[i][j] += X[k]
return Y
if __name__ == "__main__":
n = 500
X = list(range(n))
for i in range(30, 50):
print(X[i], end=" ")
print()
print(func(X, n)[30][49])
You could use a prefix sum array.
The idea is that you have an array where the entry ps[i] denotes the sum of all elements arr[0..i]. You can calculate it in linear time:
ps[0] = arr[0]
for i in range(1, len(arr)):
ps[i] = ps[i - 1] + arr[i]
Can you guess how to retrieve a sum Y(i, j) in constant time?
Solution: Y(i, j) = ps[j] - ps[i - 1]. You take the entire sum of the array from j to the start and subtract the part that you don't want again (which is from i-1 to the start).
Note: It is possible that I messed up some edge cases. Be wary for things like i=0, j=0, j<i, etc.
I want to count how many inversions there are in a list while sorting the list using mergesort. This is my code so far where 'x' counts the ammount of inversions while the rest sorts it:
import sys
x = 0
def merge_sort(A):
merge_sort2(A, 0, len(A) - 1)
def merge_sort2(A, first, last):
if first < last:
middle = (first + last) // 2
merge_sort2(A, first, middle)
merge_sort2(A, middle + 1, last)
merge(A, first, middle, last)
def merge(A, first, middle, last):
global x
L = A[first:middle + 1]
R = A[middle + 1:last + 1]
L.append(sys.maxsize)
R.append(sys.maxsize)
i = j = 0
for k in range(first, last + 1):
if L[i] <= R[j]:
A[k] = L[i]
i += 1
else:
A[k] = R[j]
j += 1
x += 1
x += len(L[first + 1:])
When I call merge sort using a list, the variable x is support to give the amount of inversions in the list. So If the list was '[4,3,2,1], x would be 6. If the list was [1,2,3] x would be 0. I change the value of x whenever the right is greater than the left in the merge definition however, the number always gets way too big. What am I doing wrong?
Check my work but, I think instead of:
x += 1
x += len(L[first + 1:])
you want:
x += middle + 1 + j - k
basically, you want to add the difference between where item k is actually coming from, and where you'd expect it to come from if everything was already sorted.
Your merge step is a little hard for me to understand — I'm not sure why you are doing this (maybe just another way to merge?):
L.append(sys.maxsize)
R.append(sys.maxsize)
but I couldn't get everything to work out with the extra elements added to the partitions. And I think you end up counting the extra element in L as an inversion with each merge move from R
I think that's causing some of the problems. But you also have two other issues:
Your last line isn't quite the right logic:
x += len(L[first + 1:])
the number of inversions will the number of elements in L that you jump over. You're counting almost every element of L each time. Something like this works better:
x += len(L[i:])
and then at the end, you may have elements left over whose inversions you haven't counted yet. Maybe that's not an issue with your extra elements but in a more traditional merge it is. Here's the way I would count the inversions:
def merge(A, first, middle, last):
global x
L = A[first:middle+1]
R = A[middle+1:last+1]
i = j = 0
k = first
print(L, R)
while i<len(L) and j<len(R):
if L[i] <= R[j]:
A[k] = L[i]
i += 1
else:
A[k] = R[j]
j += 1
# count how many left in L
x += len(L[i:])
k += 1
# take care of any leftovers in L or R
while i < len(L):
A[k] = L[i]
i += 1
k+=1
while j < len(R):
A[k] = R[j]
j += 1
k+=1
x += len(L[i:])