Предположим, у меня есть скремблированный вектор последовательных целых чисел 1: n, скажем {3,6,2,1,4,5}. Моя проблема состоит в том, чтобы найти для каждого элемента количество элементов слева от него, которые меньше его самого. Поэтому я бы хотел, чтобы программа вернула {0,1,0,0,3,4} для этого примера. Вот что я написал на Фортране:

subroutine iterrank(n,invec,outvec,tempvec)
    implicit none

    integer :: n, i, currank
    integer, dimension(n) :: invec, outvec, tempvec

    tempvec = 0
    outvec = 0
    do i = 1,n
        currank = invec(i)
        outvec(i) = tempvec(currank)
        tempvec(currank:n) = tempvec(currank:n) + 1
    end do

    return
end subroutine

Он принимает временный массив (вектор), и для каждой цифры d цикл добавляет 1 к каждому элементу за позицией d во временном векторе. Следующая итерация затем принимает соответствующий элемент во временном векторе как количество элементов, меньших, чем он сам. Мои вопросы:

1) Я считаю, что это сложность O (n ^ 2), так как O (n) записывает во временный вектор на каждой итерации цикла. Я прав?

2) Есть ли более эффективный способ сделать это для больших n (скажем,> 100k)?

3
David 3 Янв 2016 в 22:36

2 ответа

Лучший ответ

Я считаю, что это было бы более эффективно, и вы также могли бы уменьшить временный целочисленный массив до одного байта.

subroutine iterrank(n,invec,outvec,tempvec)
    implicit none

    integer :: n, i, currank
    integer, dimension(n) :: invec, outvec, tempvec

    tempvec = 0
    !outvec = 0 ! no need to initialize something overwritten below
    do i = 1 , n
        currank = invec(i)
        outvec(i) = sum( tempvec(1:currank) )
        tempvec(currank) = 1
    end do

end subroutine

Преимущество состоит в том, что вы пишете только дважды для каждого индекса, однако вы читаете элементы не более n*n раз.

РЕДАКТИРОВАТЬ:

Я не пробовал этого, но он должен делать меньше операций чтения с возможными накладными расходами на ветвление. Возможно, это быстрее для очень больших массивов, но я бы ожидал, что он будет медленнее для коротких массивов:

subroutine iterrank(n,invec,outvec,tempvec)
  implicit none

  integer :: n, i, currank, prevrank
  integer, dimension(n) :: invec, outvec, tempvec

  tempvec = 0
  outvec(1) = 0
  tempvec(invec(1)) = 1
  do i = 2 , n
     prevrank = invec(i-1)
     currank = invec(i)
     if ( abs(prevrank-currank) > currank ) then
        outvec(i) = sum( tempvec(1:currank) )
     else if ( prevrank < currank ) then
        outvec(i) = outvec(i-1) + sum( tempvec(prevrank:currank) )
     else
        outvec(i) = outvec(i-1) - sum( tempvec(currank:prevrank-1) )
     end if
     tempvec(currank) = 1
  end do

end subroutine iterrank
3
zeroth 5 Янв 2016 в 17:58

Полный переписанный ответ. Если память не является проблемой, вы можете добавить еще один вектор и использовать алгоритм, подобный приведенному ниже. Дополнительный вектор используется для вычисления перестановки. Благодаря тому, что исходный вектор представляет собой перестановку целого числа от 1 до n, перестановка вычисляется за O (n). с векторами размером 100k на моем компьютере этот алгоритм работает в среднем за 1,9 секунды (100 прогонов), а первоначальное предложение нуля составляет в среднем 2,8 секунды. Я предложил это решение просто потому, что zeroth сказал, что не тестировал свое новое решение, вы протестируете и воспользуетесь лучшим.

subroutine iterrank(n,invec,outvec,tempvec,ord)
    implicit none
    !
    integer :: n, i, currPos, prevPos, currOut, prevOut
    integer, dimension(n) :: invec, outvec, tempvec,ord
    !
    tempvec = 0
    do i = 1, n
        ord(invec(i)) = i
    end do
    !
    currPos = ord(1)
    tempvec(currPos) = 1
    currOut = 0
    outvec(currPos) = currOut
    ! last = 0
    do i = 2 , n
        prevPos = currPos
        currPos = ord(i)
        !
        if(currPos>prevPos)then
            currOut = currOut+sum( tempvec(prevPos:currPos) )
        else
            currOut = sum( tempvec(1:currPos) )
        end if
        !
        outvec(currPos) = currOut
        tempvec(currPos) = 1
    end do
    !
end subroutine iterrank

Обратной стороной этого решения является произвольный доступ к векторам outvec и tempvec, при котором кэш и регистры используются не лучшим образом. Это можно решить и значительно сократить время, возможно, за счет дополнительных временных векторов.

2
innoSPG 5 Янв 2016 в 17:43