You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CACmb/src/sorts.f90

855 lines
20 KiB

! These various sort routines are provided as
! examples, rather than being fully optimised
! for the best possible performance. MJR 9/2019
!
! So far the collection is:
!
! bubble_sort (order N**2)
! selection_sort (order N**2)
! inline_selection_sort (order N**2)
! insertion_sort (order N**2)
! binary_insertion_sort (order N**2)
! pair_insertion_sort (order N**2)
! double_insertion_sort (order N**2)
! shell_sort (order N**(4/3)) (possibly)
! merge_sort (order N log N)
! alt_merge_sort (order N log N)
! heap_sort (order N log N)
! heap_sort_nr (order N log N)
! smoothsort (order N log N)
! quicksort (order N log N) (usually)
! quicksort_nr (order N log N) (usually)
! dpquicksort (order N log N) (usually)
! 14/10/19 and 18/10/19 indexing errors in heap sorts corrected
! 4/4/20 smooth sort and non-recursive quicksort added, and some
! minor code tidying
module sorts
use parameters
implicit none
integer, parameter :: prec=dp, i64=selected_int_kind(15)
! array L used by smoothsort
integer, parameter :: L(0:43) = (/ 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, &
177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, &
13529, 21891, 35421, 57313, 92735, 150049, &
242785, 392835, 635621, 1028457, 1664079, 2692537, &
4356617, 7049155, 11405773, 18454929, 29860703, &
48315633, 78176337, 126491971, 204668309, 331160281, &
535828591, 866988873, 1402817465 /)
private :: binary_search, merge_arrays, heapify, heapify_nr, &
smooth_heapify, smooth_extract, sift_in, interheap_sift, L
contains
recursive subroutine quicksort(array)
real(prec), intent(inout)::array(:)
real(prec) :: temp,pivot
integer :: i,j,last,left,right
last=size(array)
if (last.lt.50) then ! use insertion sort on small arrays
do i=2,last
temp=array(i)
do j=i-1,1,-1
if (array(j).le.temp) exit
array(j+1)=array(j)
enddo
array(j+1)=temp
enddo
return
endif
! find median of three pivot
! and place sentinels at first and last elements
temp=array(last/2)
array(last/2)=array(2)
if (temp.gt.array(last)) then
array(2)=array(last)
array(last)=temp
else
array(2)=temp
endif
if (array(1).gt.array(last)) then
temp=array(1)
array(1)=array(last)
array(last)=temp
endif
if (array(1).gt.array(2)) then
temp=array(1)
array(1)=array(2)
array(2)=temp
endif
pivot=array(2)
left=3
right=last-1
do
do while(array(left).lt.pivot)
left=left+1
enddo
do while(array(right).gt.pivot)
right=right-1
enddo
if (left.ge.right) exit
temp=array(left)
array(left)=array(right)
array(right)=temp
left=left+1
right=right-1
enddo
if (left.eq.right) left=left+1
call quicksort(array(1:left-1))
call quicksort(array(left:))
end subroutine quicksort
! This version maintains its own stack, to avoid needing to call
! itself recursively. By always pushing the larger "half" to the
! stack, and moving directly to calculate the smaller "half",
! it can guarantee that the stack needs no more than log_2(N)
! entries
subroutine quicksort_nr(array)
real(prec), intent(inout)::array(:)
real(prec) :: temp,pivot
integer :: i,j,left,right,low,high
! If your compiler lacks storage_size(), replace
! storage_size(i) by 64
integer :: stack(2,storage_size(i)),stack_ptr
low=1
high=size(array)
stack_ptr=1
do
if (high-low.lt.50) then ! use insertion sort on small arrays
do i=low+1,high
temp=array(i)
do j=i-1,low,-1
if (array(j).le.temp) exit
array(j+1)=array(j)
enddo
array(j+1)=temp
enddo
! now pop from stack
if (stack_ptr.eq.1) return
stack_ptr=stack_ptr-1
low=stack(1,stack_ptr)
high=stack(2,stack_ptr)
cycle
endif
! find median of three pivot
! and place sentinels at first and last elements
temp=array((low+high)/2)
array((low+high)/2)=array(low+1)
if (temp.gt.array(high)) then
array(low+1)=array(high)
array(high)=temp
else
array(low+1)=temp
endif
if (array(low).gt.array(high)) then
temp=array(low)
array(low)=array(high)
array(high)=temp
endif
if (array(low).gt.array(low+1)) then
temp=array(low)
array(low)=array(low+1)
array(low+1)=temp
endif
pivot=array(low+1)
left=low+2
right=high-1
do
do while(array(left).lt.pivot)
left=left+1
enddo
do while(array(right).gt.pivot)
right=right-1
enddo
if (left.ge.right) exit
temp=array(left)
array(left)=array(right)
array(right)=temp
left=left+1
right=right-1
enddo
if (left.eq.right) left=left+1
! call quicksort(array(1:left-1))
! call quicksort(array(left:))
if (left.lt.(low+high)/2) then
stack(1,stack_ptr)=left
stack(2,stack_ptr)=high
stack_ptr=stack_ptr+1
high=left-1
else
stack(1,stack_ptr)=low
stack(2,stack_ptr)=left-1
stack_ptr=stack_ptr+1
low=left
endif
enddo
end subroutine quicksort_nr
! dual pivot quicksort
recursive subroutine dpquicksort(array)
real(prec), intent(inout)::array(:)
real(prec) :: temp,p1,p2
integer :: i,j,last,l,k,g
last=size(array)
if (last.lt.40) then ! use insertion sort on small arrays
do i=2,last
temp=array(i)
do j=i-1,1,-1
if (array(j).le.temp) exit
array(j+1)=array(j)
enddo
array(j+1)=temp
enddo
return
endif
p1=array(last/3)
p2=array(2*last/3)
if (p2.lt.p1) then
temp=p1
p1=p2
p2=temp
endif
array(last/3)=array(1)
array(1)=p1
array(2*last/3)=array(last)
array(last)=p2
g=last
l=2
do while (array(l).lt.p1)
l=l+1
enddo
k=l
do while(k.lt.g)
temp=array(k)
if (temp.lt.p1) then
array(k)=array(l)
array(l)=temp
l=l+1
else if (temp.gt.p2) then
do while(array(g-1).gt.p2)
g=g-1
enddo
if (k.ge.g) exit
g=g-1
if (array(g).lt.p1) then
array(k)=array(l)
array(l)=array(g)
array(g)=temp
l=l+1
else
array(k)=array(g)
array(g)=temp
endif
endif
k=k+1
enddo
if (l.gt.2) then
array(1)=array(l-1)
array(l-1)=p1
call dpquicksort(array(1:l-2))
endif
call dpquicksort(array(l:g-1))
if (g.lt.last) then
array(last)=array(g)
array(g)=p2
call dpquicksort(array(g+1:last))
endif
end subroutine dpquicksort
function binary_search(array, x, start, end)
integer binary_search
integer, intent(in) :: start,end
real(prec), intent(in) :: x,array(:)
integer :: a,b,mid
a=start
b=end
do
if (a.eq.b) then
if (array(a).gt.x) then
binary_search=a
return
else
binary_search=a+1
return
endif
endif
if (a.gt.b) then
binary_search=a
return
end if
mid=(a+b)/2
if (array(mid).lt.x) then
a=mid+1
else if (array(mid).gt.x) then
b=mid-1
else
binary_search=mid
return
endif
end do
end function binary_search
subroutine binary_insertion_sort(array)
real(prec), intent(inout) :: array(:)
integer :: i,j,pos
real(prec) :: x
do i=2,size(array)
x=array(i)
pos=binary_search(array,x,1,i-1)
do j=i,pos+1,-1
array(j)=array(j-1)
enddo
array(pos)=x
enddo
end subroutine binary_insertion_sort
subroutine insertion_sort(array)
real(prec), intent(inout) :: array(:)
integer :: i,j
real(prec) :: temp
do i=2,size(array)
temp=array(i)
do j=i-1,1,-1
if (array(j).le.temp) exit
array(j+1)=array(j)
enddo
array(j+1)=temp
enddo
end subroutine insertion_sort
subroutine double_insertion_sort(array)
real(prec), intent(inout) :: array(:)
integer :: i,j,left,right,last
real(prec) :: temp,p,next
last=size(array)
p=0.5*(array(1)+array(last))
if (array(1).gt.array(last)) then
temp=array(last)
array(last)=array(1)
array(1)=temp
endif
left=1
right=last
temp=array(2)
do i=2,last-1
if (temp.lt.p) then
do j=left,1,-1
if (array(j).le.temp) exit
array(j+1)=array(j)
enddo
array(j+1)=temp
temp=array(left+2)
left=left+1
else
next=array(right-1)
do j=right,last
if (array(j).ge.temp) exit
array(j-1)=array(j)
enddo
array(j-1)=temp
temp=next
right=right-1
endif
enddo
end subroutine double_insertion_sort
subroutine pair_insertion_sort(array)
real(prec), intent(inout) :: array(:)
integer :: i,j,last
real(prec) :: t1,t2
last=size(array)
do i=2,last-1,2
t1=min(array(i),array(i+1))
t2=max(array(i),array(i+1))
j=i-1
do while((j.ge.1).and.(array(j).gt.t2))
array(j+2)=array(j)
j=j-1
enddo
array(j+2)=t2
do while((j.ge.1).and.(array(j).gt.t1))
array(j+1)=array(j)
j=j-1
enddo
array(j+1)=t1
end do
if(mod(last,2).eq.0)then
t1=array(last)
do j=last-1,1,-1
if (array(j).le.t1) exit
array(j+1)=array(j)
end do
array(j+1)=t1
endif
end subroutine pair_insertion_sort
subroutine merge_arrays(a,b,out)
! routine used by merge_sort()
real(prec), intent(in) :: a(:),b(:)
real(prec), intent(out) :: out(:)
integer :: ai,bi,oi,i
ai=1
bi=1
oi=1
do
if (ai.gt.size(a)) then
do i=bi,size(b)
out(oi)=b(i)
oi=oi+1
enddo
return
endif
if (bi.gt.size(b)) then
do i=ai,size(a)
out(oi)=a(i)
oi=oi+1
enddo
return
endif
if (a(ai).lt.b(bi)) then
out(oi)=a(ai)
ai=ai+1
else
out(oi)=b(bi)
bi=bi+1
endif
oi=oi+1
enddo
end subroutine merge_arrays
recursive subroutine merge_sort(array)
! With a little skill, the extra copy of "array=temp"
! can be avoided
real(prec), intent(inout) :: array(:)
real(prec), allocatable :: temp(:)
integer :: last
last=size(array)
if (last.lt.40) then
call insertion_sort(array)
return
endif
call merge_sort(array(1:last/2))
call merge_sort(array(last/2+1:last))
allocate(temp(last))
call merge_arrays(array(1:last/2),array(last/2+1:last),temp)
array=temp
deallocate(temp)
end subroutine merge_sort
recursive subroutine alt_merge_sort(array)
! Alternate merges between array and temp
! to avoid copy
real(prec), intent(inout) :: array(:)
real(prec), allocatable :: temp(:)
integer :: last
last=size(array)
if (last.lt.40) then
call insertion_sort(array)
return
endif
call alt_merge_sort(array(1:last/4))
call alt_merge_sort(array(last/4+1:last/2))
call alt_merge_sort(array(last/2+1:last/2+last/4))
call alt_merge_sort(array(last/2+last/4+1:last))
allocate(temp(last))
call merge_arrays(array(1:last/4),array(last/4+1:last/2),temp(1:last/2))
call merge_arrays(array(last/2+1:last/2+last/4), &
array(last/2+last/4+1:last),temp(last/2+1:last))
call merge_arrays(temp(1:last/2),temp(last/2+1:last),array)
deallocate(temp)
end subroutine alt_merge_sort
subroutine selection_sort(array)
! This version uses Fortran's minloc intrinsic
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: i,j
do i=1,size(array)-1
j=minloc(array(i:),1)+i-1
temp=array(i)
array(i)=array(j)
array(j)=temp
enddo
end subroutine selection_sort
subroutine inline_selection_sort(array)
! This version does not use Fortran's minloc intrinsic
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: i,j,k
do i=1,size(array)-1
temp=array(i)
j=i
do k=i+1,size(array)
if (array(k).lt.temp) then
temp=array(k)
j=k
endif
enddo
array(j)=array(i)
array(i)=temp
enddo
end subroutine inline_selection_sort
subroutine shell_sort(array)
! There are many choices of intervals
! The sequence (3**k-1)/2 used below is given by Knuth
! The sort relies on the insertion sort of nearly sorted
! data being fast (i.e. not order N**2)
real(prec), intent(inout) :: array(:)
integer :: i,interval,last
interval=1
last=size(array)
do while(interval<last/3)
interval=3*interval+1
enddo
do while(interval>0)
do i=1,interval
! The following syntax passes a subarray with elements
! array(i),array(i+interval),array(i+2*interval),...
call insertion_sort(array(i::interval))
enddo
interval=(interval-1)/3
enddo
end subroutine shell_sort
subroutine bubble_sort(array)
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: i,j,last
last=size(array)
do i=last-1,1,-1
do j=1,i
if (array(j+1).lt.array(j)) then
temp=array(j+1)
array(j+1)=array(j)
array(j)=temp
endif
enddo
enddo
end subroutine bubble_sort
subroutine heap_sort(array)
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: i,last
last=size(array)
! Build heap
do i=last/2,1,-1
call heapify(array,i)
enddo
! Unpick heap
do i=last,2,-1
temp=array(1)
array(1)=array(i)
array(i)=temp
call heapify(array(1:i-1),1)
end do
end subroutine heap_sort
recursive subroutine heapify(array,root)
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: left,right,root,last,largest
last=size(array)
left=2*root
right=left+1
largest=root
if (left.le.last) then
if(array(left).gt.array(largest)) largest=left
endif
if (right.le.last) then
if(array(right).gt.array(largest)) largest=right
endif
if (largest.ne.root) then
temp=array(root)
array(root)=array(largest)
array(largest)=temp
call heapify(array,largest)
end if
end subroutine heapify
subroutine heap_sort_nr(array)
! The same but with a non-recursive heapify
real(prec), intent(inout) :: array(:)
real(prec) :: temp
integer :: i,last
last=size(array)
! Build heap
do i=last/2,1,-1
call heapify_nr(array,i)
enddo
! Unpick heap
do i=last,2,-1
temp=array(1)
array(1)=array(i)
array(i)=temp
call heapify_nr(array(1:i-1),1)
end do
end subroutine heap_sort_nr
subroutine heapify_nr(array,i)
! A non-recursive heapify
real(prec), intent(inout) :: array(:)
real(prec) :: root_val
integer :: i,left,right,root,last,largest
last=size(array)
root=i
left=2*root
root_val=array(root)
largest=root
do while(left.le.last)
right=left+1
if (left.le.last) then
if(array(left).gt.array(largest)) largest=left
endif
if (right.le.last) then
if(array(right).gt.array(largest)) largest=right
endif
if (largest.eq.root) exit
array(root)=array(largest)
array(largest)=root_val
root=largest
left=2*root
enddo
! array(largest)=root_val
end subroutine heapify_nr
! Smoothsort is introduced by Edsger Dijkstra
! https://doi.org/10.1016/0167-6423(82)90016-8
!
! This Fortran version owes much to https://www.keithschwarz.com/smoothsort/
! and the C version given by Martin Knoblauch Revuelta at
! http://code.google.com/archive/p/combsortcs2p-and-other-sorting-algorithms
subroutine smoothsort(array)
real(prec), intent(inout) :: array(:)
integer(i64) :: mask
integer :: offset
if (size(array).le.1) return
call smooth_heapify(array,mask,offset)
call smooth_extract(array,mask,offset)
end subroutine smoothsort
subroutine smooth_heapify(array,mask,offset)
real(prec), intent(inout) :: array(0:)
integer(i64), intent(out) :: mask
integer, intent(out) :: offset
integer :: i,last
logical :: wbf
mask=1
offset=1
last=size(array)
do i=1,last-1
if (iand(mask,2_i64).eq.2) then
mask=ior(ishft(mask,-2),1_i64)
offset=offset+2
else if (offset.eq.1) then
mask=ior(ishft(mask,1),1_i64)
offset=0
else
mask=ior(ishft(mask,offset-1),1_i64)
offset=1
endif
wbf=(((iand(mask,2_i64).eq.2).and.(i+1.lt.last)).or. &
((offset.gt.0).and.(1+i+L(offset-1).lt.last)))
if (wbf) then
call sift_in(array,i,offset)
else
call interheap_sift(array,i,mask,offset)
endif
enddo
end subroutine smooth_heapify
subroutine smooth_extract(array,mask,offset)
real(prec), intent(inout) :: array(0:)
integer :: i,j,last,ch(2)
integer(i64),value :: mask
integer, value :: offset
last=size(array)
do i=last-1,2,-1
if (offset.lt.2) then
do
mask=ishft(mask,-1)
offset=offset+1
if (iand(mask,1_i64).eq.1) exit
enddo
else
ch(2)=i-1
ch(1)=ch(2)-L(offset-2)
mask=iand(mask,not(1_i64))
do j=1,2
mask=ior(ishft(mask,1),1_i64)
offset=offset-1
call interheap_sift(array,ch(j),mask,offset)
end do
end if
end do
end subroutine smooth_extract
subroutine sift_in(array,root,sz)
real(prec), intent(inout) :: array(0:)
real(prec) :: tmp
integer :: left,right,next
integer,value :: root,sz
if (sz.lt.2) return
tmp=array(root)
do
right=root-1
left=right-L(sz-2)
if (array(right).lt.array(left)) then
next=left
sz=sz-1
else
next=right
sz=sz-2
end if
if (array(next).le.tmp) exit
array(root)=array(next)
root=next
if (sz.le.1) exit
end do
array(root)=tmp
end subroutine sift_in
subroutine interheap_sift(array,root,mask,offset)
real(prec), intent(inout) :: array(0:)
real(prec) :: tmp,mx
integer :: left,right,next
integer(i64),value :: mask
integer, value :: root,offset
tmp=array(root)
do while (mask.ne.1)
mx=tmp
if (offset.gt.1) then
right=root-1
left=right-L(offset-2)
mx=max(mx,array(left),array(right))
end if
next=root-L(offset)
if (array(next).le.mx) exit
array(root)=array(next)
root=next
do
mask=ishft(mask,-1)
offset=offset+1
if (iand(mask,1_i64).eq.1) exit
end do
end do
array(root)=tmp
call sift_in(array,root,offset)
end subroutine interheap_sift
end module sorts