现在的位置: 首页 > 综合 > 正文

《编程之美》学习笔记——2.10寻找数组中的最大值和最小值

2018年03月30日 ⁄ 综合 ⁄ 共 8972字 ⁄ 字号 评论关闭

一、问题

对于一个由N个整数组成的数组,需要比较多少次才能把最大值和最小值的数找出来呢?

问题分析:
输入:一个有限长度且为N的数组。
输出:该数组的最大值max和最小值min。
特点:最大值和最小值都要找到,可以考虑它们之间的关系;这个问题在《编程之美》中侧重解决尽可能减少算法内部比较次数的这个问题。

结合学习:《算法导论》(第九章 中位数和顺序统计量 第一节 最小值和最大值)

  文章给出了更精确的比较次数,因为这里给出了精确的比较次数(与数组长度奇数偶数相关),《编程之美》中的数据都是假设数组长度是偶数的,并且没有去考虑最大值和最小值初始化可以减少比较次数这个情况。

二、解法

版本一:独立求解(最大值和最小值各自独立求解)
  遍历数组,将最大值和最小值各自与数组中的数据进行比较,分别寻找最大值和最小值。
  比较次数:2N - 2。(最大值和最小值均初始化为数组第一个数)

C算法实现:

/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      count  sequence length
 * @param[out]     max    maximum value from the sequence
 * @param[out]     min    minimum value from the sequence
 */
void search_max_and_min_value(TYPE* array, TYPE count, TYPE* max, TYPE* min)
{
    assert(array != NULL && max != NULL && min != NULL && count >= 1);
    /// initialize max and min value
    *max = array[0];
    *min = array[0];
    TYPE i;
    for (i = 1; i < count; i++) {
        /// compare with max value for N - 1 times(N = count).
        if (array[i] > *max)
            *max = array[i];
        /// compare with min value for N - 1 times(N = count).
        if (array[i] < *min)
            *min = array[i];
    }
    return;
}

版本二:奇数位和偶数位比较
算法步骤:
1.按顺序把数组中相邻的两个数分为一组,分别为奇数位数字和偶数位数字。
2.每组各自的两个数字进行比较,将较大的数放在偶数位上,将较小的数放到奇数位上。
3.分别求出偶数位上的最大值和奇数位上的最小值,即为输出。
  比较次数:

  如果数组长度是奇数:times = 3[N/2] = 3(N-1)/2 = 3N/2 - 3/2;

  如果数组长度是偶数:times = 3(N-2)/2 + 1 = 3N/2 - 2。
  分析:虽然比较次数减少到了越1.5N,但是数组中数据也进行了交换,并且总交换次数不超过0.5N。如果机器执行单次交换的效率比执行单次比较的效率要低(实际上机器确实是如此),那么版本二算法总效率很可能比版本一算法总效率低。

C算法实现(由于上面是奇偶配对进行比较,必须考虑数组长度为偶数或奇数的情况,反应到最小值和最大值初值设定这个问题上。如果是奇数可以把最大值和最小值的初值设为第一个元素值;如果是偶数可以先对前两个元素进行比较并根据它们设定最大值和最小值初值)

/**
 * @brief swap value a and value b by thrid value
 *
 * @param[in]      p_a   value a's address
 * @param[in]      p_b   value b's address
 */
static void swap(TYPE* p_a, TYPE* p_b)
{
    TYPE temp = *p_a;
    *p_a = *p_b;
    *p_b = temp;
    return;
}
/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      count  sequence length
 * @param[out]     max    maximum value from the sequence
 * @param[out]     min    minimum value from the sequence
 */
void search_max_and_min_value(TYPE* array, TYPE count, TYPE* max, TYPE* min)
{
    assert(array != NULL && max != NULL && min != NULL && count >= 1);
    TYPE i;
    /// initialize max and min value
    if ((count & 0x1) == 1) {
        /// if N is an odd number.
        /// set max and min value as first element
        *max = array[0];
        *min = array[0];
        i = 1;
    } else {
        /// if N is an even number.
        /// compare first two element and set max and min value 1 times
        if (array[0] < array[1]) {
            /// save large value to even position, small value to odd position.
            swap(array, array + 1);
        }
        *max = array[0];
        *min = array[1];
        i = 2;
    }
    /// if N is odd, each compare times=[N/2], else each compare times=[(N-2)/2]
    for ( ; i < count - 1; i += 2) {
        /// compare odd with even position value.
        if (array[i] < array[i + 1])
            /// save large value to even position, small value to odd position.
            swap(array + i, array + i + 1);
        /// compare in even position value to get max value.
        if (array[i] > *max)
            /// save maximum value
            *max = array[i];
        /// compare in odd position value to get min value.
        if (array[i + 1] < *min)
            /// save minimum value
            *min = array[i + 1];
    }
    return;
}

版本三:奇数位和偶数位比较(版本二基础上优化,避免数据交换)
  算法与版本二的算法思路一致,但是避免了数组内的数据交换,算法在比较奇偶位数字的过程中同时进行最大值和最小值的比较。
  比较次数:与版本二比较次数一致。

  如果数组长度是奇数:times = 3[N/2] = 3(N-1)/2 = 3N/2 - 3/2;

  如果数组长度是偶数:times = 3(N-2)/2 + 1 = 3N/2 - 2。

C算法实现:

/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      count  sequence length
 * @param[out]     max    maximum value from the sequence
 * @param[out]     min    minimum value from the sequence
 */
void search_max_and_min_value(TYPE* array, TYPE count, TYPE* max, TYPE* min)
{
    assert(array != NULL && max != NULL && min != NULL && count >= 1);
    TYPE i;
    /// initialize max and min value
    if ((count & 0x1) == 1) {
        /// if N is an odd number.
        /// set max and min value as first element
        *max = array[0];
        *min = array[0];
        i = 1;
    } else {
        /// if N is an even number.
        /// compare first two element and set max and min value 1 times
        if (array[0] > array[1]) {
            *max = array[0];
            *min = array[1];
        } else {
            *max = array[1];
            *min = array[0];
        }
        i = 2;
    }
    /// if N is odd, each compare times=[N/2], else each compare times=[(N-2)/2]
    for ( ; i < count - 1; i += 2) {
        /// compare odd with even position value.
        if (array[i] > array[i + 1]) {
            /// compare in large position value to get max value.
            if (array[i] > *max)
                /// save maximum value
                *max = array[i];
            /// compare in small position value to get min value.
            if (array[i + 1] < *min)
                /// save minimum value
                *min = array[i + 1];
        } else {
            /// compare in large position value to get max value.
            if (array[i + 1] > *max)
                /// save maximum value
                *max = array[i + 1];
            /// compare in small position value to get min value.
            if (array[i] < *min)
                /// save minimum value
                *min = array[i];
        }
    }
    return;
}

版本四:分治法
  在前面两个算法减少比较次数思想的基础上,我们可以引入分治思想采用递归的形式求解。不断把数组分成前半部分和后半部分,各自求解最大值最小值,再通过比较这些最大值最小值进行归并从而求解。
可以得到比较次数的递归方程:
T(n) = 2T(n/2) + 2, n > 2;(归并子问题时比较两次)
T(n) = 1, n <= 2;(数组元素只有两个时比较一次)
求解得出:T(n) = 1.5n - 2
  比较次数:1.5N - 2,分治法和前面版本相比并没有明显减少比较次数。(这里并未严格考虑数组长度为奇数或偶数时的区别)

C算法实现:

/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      index_begin index begin of array(included)
 * @param[in]      index_end   index end of array(included)
 * @param[out]     max    maximum value from the sequence
 * @param[out]     min    minimum value from the sequence
 */
void search_max_and_min_value(TYPE* array,
        TYPE index_begin, TYPE index_end,
        TYPE* max, TYPE* min)
{
    assert(array != NULL && max != NULL && min != NULL);
    assert(index_begin <= index_end);
    /// the minimum array comparison
    if (index_end - index_begin <= 1) {
        if (array[index_end] < array[index_begin]) {
            *max = array[index_begin];
            *min = array[index_end];
        } else {
            *min = array[index_begin];
            *max = array[index_end];
        }
        return;
    }
    /// divide array to small problem
    TYPE middle = index_begin + ((unsigned)(index_end - index_begin) >> 1);
    TYPE max_left, max_right, min_left, min_right;
    search_max_and_min_value(array, index_begin, middle,
            &max_left, &min_left);
    search_max_and_min_value(array, middle + 1, index_end,
            &max_right, &min_right);
    /// conquer the small problem
    if (max_left > max_right)
        *max = max_left;
    else
        *max = max_right;
    if (min_left < min_right)
        *min = min_left;
    else
        *min = min_right;
    return;
}

三、拓展问题

如果需要找出N个数组中的第二大数,需要比较多少次呢?
是否可以使用类似的分治思想来降低比较的次数呢?

问题分析:

  关键之处:什么是第二大数?我认为有下面两种理解。

  (例子:数组为:4,3,5,5,3;)

  方案一:第一大数是5,第二大数是4(尽管第一大数在数组中有两个,但是按照我们通常的理解,我们假设第二大数和第一大数是不可能相同的,因此第二大数是小于第一大数但大于等于其他数的数;若整个数组的数据都是相等的,那么第二大数不会存在。)

  方案二:第一大数是5,第二大数是5(我们假设第一大数和第二大数是有可能相同的(数据重复时))

  如果采用用方案一,那么我们必须额外的考虑数据中数据相等情况,以避免第一大数和第二大数相同,这时算法比较数据时相对方案二要复杂些;如果采用方案二,算法相对简单。网络上大部分情况使用的是方案二(即认为第二大数是对数组排序后倒数第二个数),下文的解法采用的也是方案二。

四、拓展问题解法

  首先我们考虑最简单的解法,遍历一次数组,把最大的数存给第一大数,接下来进行比较并决定第二大数设定,共需要2N-2次。我们可以发现,这各拓展问题和第一个问题是非常相似的。

版本一:奇数位和偶数位比较

  其次我们从上个问题版本二和版本三的角度去考虑这到题目,分析奇偶位的两个数据a ,b与最大数m1和第二大数m2的关系(要知道第二大数,必须求解第一大数)。

  比较次数:(从算法C实现可以看出,比较次数是不确定的,这里只考虑最坏的情况。比较次数与第一个问题的解一致)
如果数组长度是奇数:times = 3[N/2] = 3(N-1)/2 = 3N/2 - 3/2;
如果数组长度是偶数:times = 3(N-2)/2 + 1 = 3N/2 - 2。

算法C实现:

/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      count  sequence length
 * @param[out]     max_first    maximum value from the sequence
 * @param[out]     max_second   second maximum value from the sequence
 */
void search_max_2_value(TYPE* array, TYPE count,
        TYPE* max_first, TYPE* max_second)
{
    assert(array != NULL && max_first != NULL && max_second != NULL &&
            count >= 1);
    TYPE i;
    /// initialize max_first and max_second value
    if ((count & 0x1) == 1) {
        /// if N is an odd number.
        /// set max_first and max_second value as first element
        *max_first = array[0];
        *max_second = INT_MIN;
        i = 1;
    } else {
        /// if N is an even number.
        /// compare first two element,set max_first and max_second value 1 times
        if (array[0] > array[1]) {
            *max_first = array[0];
            *max_second = array[1];
        } else {
            *max_first = array[1];
            *max_second = array[0];
        }
        i = 2;
    }
    /// if N is odd, each compare times=[N/2], else each compare times=[(N-2)/2]
    for ( ; i < count - 1; i += 2) {
        /// compare odd with even position value.
        if (array[i] > array[i + 1]) {
            if (array[i] > *max_first) {
                /// update max_second value
                if (array[i + 1] > *max_first)
                    *max_second = array[i + 1];
                else
                    *max_second = *max_first;
                /// update max_first value
                *max_first = array[i];
            } else {
                /// update max_second value
                if (array[i] > *max_second)
                    *max_second = array[i];
            }
        } else {
            if (array[i + 1] > *max_first) {
                /// update max_second value
                if (array[i] > *max_first)
                    *max_second = array[i];
                else
                    *max_second = *max_first;
                /// update max_first value
                *max_first = array[i + 1];
            } else {
                /// update max_second value
                if (array[i + 1] > *max_second)
                    *max_second = array[i + 1];
            }
        }
    }
    return;
}

版本二:分治法

  应用第一个问题第四版算法的思路,我们对拓展问题也能够利用分治法解决。

可以得到比较次数的递归方程:
T(n) = 2T(n/2) + 2, n > 2;(归并子问题时比较两次)
T(n) = 1, n <= 2;(数组元素只有两个时比较一次)
求解得出:T(n) = 1.5n - 2
(比较次数与第一个问题的解一致)

算法C实现

/**
 * @brief search the maximum and minimum value in the array
 *
 * @param[in]      array  sequence for search
 * @param[in]      index_begin index begin of array(included)
 * @param[in]      index_end   index end of array(included)
 * @param[out]     max_first    maximum value from the sequence
 * @param[out]     max_second   second maximum value from the sequence
 */
void search_max_2_value(TYPE* array,
        TYPE index_begin, TYPE index_end,
        TYPE* max_first, TYPE* max_second)
{
    assert(array != NULL && max_first != NULL && max_second != NULL);
    assert(index_begin <= index_end);
    /// the minimum array comparison
    if (index_end - index_begin == 1) {
        if (array[index_end] < array[index_begin]) {
            *max_first = array[index_begin];
            *max_second = array[index_end];
        } else {
            *max_second = array[index_begin];
            *max_first = array[index_end];
        }
        return;
    } else if (index_end == index_begin) {
        *max_second = INT_MIN;
        *max_first = array[index_end];
        return;
    }
    /// divide array to small problem
    TYPE middle = index_begin + ((unsigned)(index_end - index_begin) >> 1);
    TYPE max_f_left, max_f_right, max_s_left, max_s_right;
    search_max_2_value(array, index_begin, middle,
            &max_f_left, &max_s_left);
    search_max_2_value(array, middle + 1, index_end,
            &max_f_right, &max_s_right);
    /// conquer the small problem
    if (max_f_left > max_f_right) {
        if (max_s_left > max_f_right)
            *max_second = max_s_left;
        else
            *max_second = max_f_right;
        *max_first = max_f_left;
    } else {
        if (max_s_right > max_f_left)
            *max_second = max_s_right;
        else
            *max_second = max_f_left;
        *max_first = max_f_right;
    }
    return;
}

拓展解法:

这里给出了一种利用最小堆求解的方法:http://www.oschina.net/code/snippet_567775_21867

抱歉!评论已关闭.