排序
1.快速排序﹣分治
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
这里定义了一个名为 quick_sort
的函数,它接受一个整数数组 q
,以及两个整数参数 l
和 r
,分别表示要排序的数组区间的左边界和右边界(左闭右闭区间)。
当要排序的区间长度小于等于 1 时(即 l
大于等于 r
),说明该区间已经有序或者只有一个元素,此时直接返回,不再进行后续的排序操作。
这里选择数组中间位置的元素作为基准元素 x
,通过 q[l + r >> 1]
来获取(这里的 >>
是右移运算符,相当于除以 2 取整,用于快速计算中间位置的索引)。
同时初始化两个指针 i
和 j
,i
初始化为区间左边界 l
的左边一个位置(l - 1
),j
初始化为区间右边界 r
的右边一个位置(r + 1
)。
外层的 while
循环用于控制整个划分过程,只要 i
小于 j
,就继续进行划分操作。
内部的两个 do-while
循环分别用于移动指针 i
和 j
:
do i ++ ; while (q[i] < x);
这个循环会让 i
指针不断向右移动,直到找到一个大于等于基准元素 x
的元素。
do j -- ; while (q[j] > x);
这个循环会让 j
指针不断向左移动,直到找到一个小于等于基准元素x
的元素。
当 i
和 j
都找到相应的元素后,如果 i
小于 j
,就说明这两个元素的位置不符合划分要求(左边应该是小于等于基准的,右边应该是大于等于基准的),所以通过 swap(q[i], q[j])
交换这两个元素的位置,使得数组在 i
和 j
位置的元素相对顺序符合划分要求。
2.归并排序-分治
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
这里定义了一个名为 merge_sort
的函数,它接受一个整数数组 q
,以及两个整数参数 l
和 r
,分别表示要排序的数组区间的左边界和右边界(左闭右闭区间)。
当要排序的区间长度小于等于 1 时(即 l
大于等于 r
),说明该区间已经有序或者只有一个元素,此时直接返回,不再进行后续的排序操作。
首先通过 mid = l + r >> 1
计算出区间 [l, r]
的中间位置(这里的 >>
是右移运算符,相当于除以 2 取整)。
然后分别对左半区间 [l, mid]
和右半区间 [mid + 1, r]
递归地调用 merge_sort
函数进行排序,这就是分治策略的体现,将原问题不断分解为更小的子问题来解决。
k
用于记录临时数组 tmp
的索引,初始化为 0。
i
指向左半区间的起始位置 l
,用于遍历左半区间的元素。
j
指向右半区间的起始位置 mid + 1
,用于遍历右半区间的元素。
这个 while
循环用于比较左半区间和右半区间当前位置的元素(通过指针 i
和 j
指向),并将较小的元素放入临时数组 tmp
中。如果 q[i]
小于等于 q[j]
,就将 q[i]
放入 tmp
中,并将 i
和 k
都向后移动一位;否则将 q[j]
放入 tmp
中,并将 j
和 k
都向后移动一位。
当上面的比较循环结束后,可能存在左半区间或者右半区间还有剩余元素未放入临时数组的情况。这两个 while
循环分别用于将左半区间(如果有剩余)和右半区间(如果有剩余)的剩余元素依次放入临时数组 tmp
中。
最后,通过这个 for
循环将临时数组 tmp
中已经排好序的元素依次复制回原数组 q
的对应位置,完成整个合并过程,使得原数组在当前区间 [l, r]
内的元素有序。
二分
1.整数二分
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
int bsearch_1(int l, int r)
函数接受两个整数参数l
和r
,分别表示查找区间的左端点和右端点,其目的是在闭区间[l, r]
中进行二分查找操作。 使用while (l < r)
循环来不断缩小区间范围,只要左端点小于右端点,就继续执行查找过程。 在每次循环中,计算中间元素的索引mid
,采用的是int mid = l + r >> 1
这种位运算的方式来计算(等同于mid = (l + r) / 2
)。 调用check(mid)
函数来判断中间元素mid
是否满足特定的性质。
-
如果
check(mid)
返回true
,意味着中间元素mid
满足期望的性质,那么就将右端点r
更新为mid
,因为要查找的元素可能就在左半区间[l, mid]
中,后续继续在这个缩小后的区间里查找。
-
如果
check(mid)
返回false
,说明中间元素不满足性质,那么将左端点l
更新为mid + 1
,这样就可以把查找范围缩小到右半区间[mid + 1, r]
,继续查找满足性质的元素。 当l
不再小于r
时,循环结束,此时返回l
,这个返回值代表着最终查找到的满足条件(或者是边界情况)的元素位置。
int bsearch_2(int l, int r)
同样接受区间的左端点l
和右端点r
作为参数,用于在[l, r]
区间内进行二分查找,不过它的划分区间方式与bsearch_1
有所不同。也是通过while (l < r)
循环来控制查找过程,不断缩小区间范围。 计算中间元素索引时,采用int mid = l + r + 1 >> 1
(等同于mid = (l + r + 1) / 2
)。这种计算方式与bsearch_1
有所区别,是为了应对不同的区间划分和边界情况。 同样调用check(mid)
来判断中间元素是否满足性质:
-
若
check(mid)
为true
,说明mid
满足性质,此时将左端点l
更新为mid
,意味着要查找的元素可能就在右半区间[mid, r]
中,后续继续在这个缩小后的区间查找。
-
若
check(mid)
为false
,则把右端点r
更新为mid - 1
,将查找范围缩小到左半区间[l, mid - 1]
继续查找。循环结束(即l >= r
时),返回l
,这个l
值就是最终确定的满足条件(或边界情况)的元素位置。
2.浮点数二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
代码定义了一个名为bsearch_3
的函数,它实现了一种基于二分查找思想的算法,目的是在给定的区间[l, r]
内,通过不断缩小区间范围,来找到满足check
函数所定义性质的一个近似值(精确到给定的精度eps
),最后返回这个近似值。
这里定义了一个常量eps
,用于控制二分查找的终止条件,即当区间长度(r - l
)小于等于这个精度值时,就认为找到了满足要求的足够精确的结果,停止循环。
在循环中,首先计算当前区间[l, r]
的中间值mid
。然后调用check
函数来检查中间值mid
是否满足某种特定的性质(该性质由check
函数具体定义,但在给出的代码中其内部实现被省略了,用/*... */
表示)。
如果mid
满足该性质(即check(mid)
返回true
),说明满足性质的值可能在区间[l, mid]
中,所以将右边界r
更新为mid
,缩小区间范围继续查找。
如果mid
不满足该性质(即check(mid)
返回false
),说明满足性质的值可能在区间[mid, r]
中,所以将左边界l
更新为mid
,同样是缩小区间范围以便后续查找。
当循环结束时(也就是区间长度小于等于精度eps
时),返回左边界l
的值作为最终找到的满足性质的近似值。这里返回l
是符合二分查找最后确定结果的逻辑的,因为经过不断缩小区间,最终的近似值就在l
(或者非常接近l
,取决于精度要求)的位置上。