【解法3】优化解:散列表

原始讲义 · 非增强校订版 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 【解法3】优化解:散列表

(1)设置count数组表示某个数出现的次数,循环遍历数组A并更新count数组,访问A[i]时如果发现count[A[i]]已经>0说明之前A[i]已经出现过,输出A[i]。如果最终都没有发现有重复元素,则输出-1。

(2) 伪代码如下:

int ans(int A[], int n)
{
    int count[n] = {0};
    for (int i = 0; i < n; i++)
    {
        if (count[A[i]] > 0) return A[i];
        count[A[i]]++;
    } return -1;  .c
}
(3) 时间复杂度:O(n) 空间复杂度:O(n) ## 习题2-4 (15 分)已知整数数组A含有n 个不相等的整数(n为奇数,且数组A 中每个元素的绝对值≤n)。设计一个时间上尽可能高效的算法,求数组A的中位数。例:n=5,A={-3,0,4,-2,3},则中位数为0。 (1)给出算法的基本设计思想。 (2)根据设计思想,采用C、C++或Java 语言描述算法,关键之处给出注释。 (3)说明你所设计算法的时间复杂度和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举法</td><td rowspan=1 colspan=1>O(n²)</td><td rowspan=1 colspan=1>0(1)考研m</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>快速排序</td><td rowspan=1 colspan=1>O(nlogn)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>散列表</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>15</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>15</td></tr></table> ## 【解法1】暴力解:枚举法 (1)对每个元素 A[i],计算比它小的元素个数count,如果恰好为 n/2,则为中位数。 (2) 伪代码如下: <!-- page 32 -->
int ans(int A[], int n)
{
    for (int i = 0; i &lt; n; i++)
    {
        int count = 0;
        for (int j = 0; j &lt; n; j++)
        if (A[j] &lt; A[i]) count++;
        if (count == n / 2) return A[i];
        return -1;
    }
}
(3) // 时间复杂度:O(n²) 空间复杂度:O(1)
## 【解法2】暴力解:快速排序 (2) 伪代码如下: (1)将数组A快速排序,输出排序后数组A最中间的数A[n/2]即是中位数。
//快速排序函数
void Qsort(int A[], int L, int R)
{
    if (L &gt;= R) return; //区间内元素个数为0或 1,自然有序,递归
    终止
    int i = L, j = R;
    int pivot = A[L]; // 选取区间第一个元素作为基准
    // 不断交换左边比 pivot 大的数和右边比 pivot 小的数
    while (i &lt; j)
    o m
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右向左找第一个小于 pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左向右找第一个大于 pivot 的数
    } if (i &lt; j) swap(A[i], A[j]);
    //将基准值 A[L]与 A[i] 交换
    swap(A[L], A[i]);
    //递归处理左右两个子区间
    Qsort(A, L, i - 1);
    Qsort(A, i + 1, R);
    int ans(int A[],int n)
    {
        Qsort(A, 0, n-1);
        return A[n/2];
    }
}
(3) // 时间复杂度:O(nlogn) 空间复杂度:O(logn)
<!-- page 33 --> ## 【解法3】优化解:散列表 (1) 设置散列表 count[2n+1],散列函数 f(x) = x - n,其中 count[i] 表示 i-n 这个数出现的次数,循环遍历数组 A 并更新 count 数组。之后i 从 0 遍历到 2n,每次将 count[i]累加,当累加变量>= n / 2 时输出 i - n 并退出程序。 (2) 伪代码如下:
int ans(int A[], int n)
{
    int count[2 * n + 1] = {0}; //散列表记录x-n出现的次数
    for (int i = 0; i &lt; n; i++)
    count[A[i] + n]++;
    int sum = 0; //累计到目前为止已经是第几小的数了
    for (int i = 0; i &lt; 2*n+1; i++)
    {
        sum = sum + count[i];
        if (sum &gt; n/2)
        return i-n;
    }
}
(3) // 时间复杂度:O(n) 空间复杂度:O(n)
## 【解法4】优化解:多指针法 (1)对无序数组使用多指针法,类似快速排序,但是在递归调用区间[L,i-1]和[i+1,R]时,因为中位数只可能出现在下标包含n/2的区间中,所以增加一个判断,只调用其中一个区间。 (2) 伪代码如下:
void Qsort_Select(int A[], int L, int R)
{
    if (L &gt;= R) return; //区间内元素个数为 0 或1,自然有序,递归
    终止
    int i = L, j = R;
    int pivot = A[L]; ∕// 选取区间第一个元素作为基准
    // 不断交换左边比 pivot 大的数和右边比 pivot 小的数
    while (i &lt; j)
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右向左找第一个小于 pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左向右找第一个大于 pivot 的数
        if (i &lt; j) swap(A[i], A[j]);
    }
    //将基准值 A[L]与A[i] 交换
    swap(A[L], A[i]);
    if (L &lt;= n/2 &amp;&amp; i-1 &gt;= n/2)
    Quick_Select(A, L, i-1, n); // 左区间包括中位数
    else
    Quick_Select(A, i+1, R, n); // 右区间包括中位数
}
<!-- page 34 -->
int ans(int A[], int n)
{
    Quick_Select(A, 0, n-1, n);
    return A[n/2];
}
(3) // 时间复杂度:O(n) 空间复杂度:O(logn)
## 习题2-5 (15分)已知数组A含有n个整数,现在要将小于k的元素放在数组右侧,大于等于k的元素放在左侧,设计一个时间上尽可能高效的算法实现这个功能。 例:n = 7,A = {4, 5, 9, 3, 7, 10,1},k = 6,则处理之后的数组可以为 {10, 7,9,3,5, 4, 1},其中 10、7、9 比 6 大,在数组左侧;而3、5、4、1小于等于6,在数组右侧。 (1)给出算法的基本设计思想。 (2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释。 (3)说明你所设计算法的时间复杂度和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>快速排序</td><td rowspan=1 colspan=1>O(nlogn)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>15</td></tr></table> 【解法1】暴力解:快速排序 (1)将数组A快速排序得到升序数组,然后将数组A翻转并输出即可得到降序数组,满足小于k的元素在右侧,大于k的元素在左侧。 K (2) 伪代码如下:
//快速排序函数
void Qsort(int A[], int L, int R)
{
    if (L &gt;= R) return;
    终止
    int i = L, j = R;
    int pivot = A[L]; //选取区间第一个元素作为基准
    // 不断交换左边比 pivot 大的数和右边比 pivot 小的数
    while (i &lt; j)
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右向左找第一个小于 pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左向右找第一个大于 pivot 的数
        if (i &lt; j) swap(A[i], A[j]);
        //将基准值A[L]与 A[i]交换
        swap(A[L], A[i]);
        //递归处理左右两个子区间
        Qsort(A, L, i - 1);
        Qsort(A, i + 1, R);
    }
    int ans(int A[], int n, int k)
    {
        Qsort(A, 0, n-1);
        for (int i&lt;0; i&lt;n/2; i++)
        swap(A[i], A[n-i-1]);
        输出数组 A
    }
}
<!-- page 35 -->
(3) // 时间复杂度: O(nlogn) 空间复杂度:O(logn)
## 【解法2】优化解:多指针法 (1)该方法类似快速排序,只需要把左侧小于k的元素和右侧大于k的元素不断交换即可。左右分别设置指针i、j,j从右往左移动直到A[j]>k,i从左往右移动直到 A[i]<k,交换 A[i]和 A[j]。不断重复该过程直到i和j相遇。 (2) 伪代码如下:
void Multiointers(int A[], int L, int R, int k)
{
    if (L&gt;=R) return; // 当前区间元素个数&lt;=1则退出
    int pivot, i=L, j=R; // i和j是左右两个数组下标移动
    pivot=k;
    while (i&lt;j)
    {
        while (i&lt;j &amp;&amp; A[j]&lt;=pivot) j--; //右边&lt;=则不处理
        while (i&lt;j &amp;&amp; A[i]&gt;=pivot) i++; //左边&gt;=则不处理
        if (i&lt;j) swap(A[i], A[j]); // A[j]&gt;k, A[i]&lt;k
    }
}
int ans(int A[], int n, int k)
{
    Multiointers(A, 0, n-1, k);
    按顺序输出A数组
}
(3) // 时间复杂度:O(n) 空间复杂度:O(1)
## 习题2-6 (13分)有一个数组A由n个互不相同的正整数组成(n为奇数),该序列中的数先减后增,中位数是最小值,输入一个整数key。请设计一个时间和空间上尽可能高效的算法,判断key是否在该数组中,如果是则返回数组下标(下标从0开始),否则返回-1。例如对应 A = {10, 7, 5, 2, 6,13, 15},key = 5 时返回下标 2,若 key = 8 则返回 -1。 (1)给出算法的基本设计思想。 (2) 根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 (3)说明你所设计算法的时间复杂度和空间复杂度。 <!-- page 36 --> <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>10</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>折半查找</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table> ## 【解法1】暴力解:枚举,扫描一遍数组 (1)循环i扫描一次数组,如果A[i]==key则返回i,如果没找到则返回-1。 (2) 伪代码如下:
int ans(int A[], int n, int key)
{
    for (int i = 0; i &lt; n;i++)
    if (A[i] == key) return i;
    return -1;
}
(3) // 时间复杂度:O(n) 空间复杂度:O(1)
## 【解法2】优化解:折半查找两次 (1)A[0...n/2]递减,前半段折半查找一次;A[n/2...n-1]递增,后半段再折半查找一次。 (注意:因为一个是降序一个是升序,这两次写法会有不同,也可以合成一种写法) (2) 伪代码如下:
//在升序数组中查找
int B_Search_up(int A[], int L, int R, int x)
{
    int mid;
    while (L &lt;= R) //只要区间不为空就继续
    {
        mid = (R + L) / 2; //防溢出 首计算
        if (A[mid] == x)
        return mid; // 找到了,立即返回
        else
        if (x &gt; A[mid]) R = mid - 1;
        else L = mid + 1; // x 在右半边,mid 被排除
    }
    return -1; //循环结束,说明没找到
    //在降序数组中查找 m
    int B_Search_down(int A[], int L, int R, int x)
    {
        int mid;
        while (L &lt;= R) // 只要区间不为空就继续
        {
            mid = (R + L) / 2; //防溢出
            if (A[mid] == x)
            return mid; //找到了,立即返回
        }
    }
}
<!-- page 37 -->
else
if (x &lt; A[mid]) R = mid - 1;
else L = mid + 1; // x 在右半边,mid 被排除
}
return -1; //循环结束,说明没找到
int ans(int A[], int n, int key)
{
    int i;
    i=B_Search_down(A,0,n/2-1,key); // 折半查找降序序列
    if (i!=-1)
    return i; //折半查找降序成功
    i=B_Search_up(A, n/2, n-1, key); //折半查找升序序列
    return i;
(3) // 时间复杂度:O(logn) 空间复杂度:O(1)
## 习题2-7 (13 分)定义距离 d = x - yl,给定 2 个长度为 n 的升序数组 a、b,x 是数组 a 中的某个元素,y 是数组 b 中的某个元素。请设计一个尽可能高效的算法,计算并输出所有可能的的最小距离 d。例如数组 a = {-16,-8,5,8,13},b ={-2,0,2, 6, 10},则最小距离为 1,相应的 x=5, y=6, d= |5 − 6|=1。 (1)给出算法的基本设计思想。 (2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 (3)说明你所设计算法的时间复杂度和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>预计得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(n²)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>10</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table>