Algorithm——一般数组元素选择问题(十一)
程序员文章站
2022-03-22 16:26:32
...
Algorithm——一般数组元素选择问题
《算法导论》中引出了一个一般性的数组元素选择问题:即在一个元素各异的数组A中,选择其中第i小的元素(即如果i=1,则表明选择最小的那个元素)。
该算法的伪代码如下,它使用了之前介绍快速排序中的随机子数组划分方法:
RANDOMIZED-SELECT(A, p, r, i)
if p == r
return A [p]
q = RANDOMIZED-PARTITION(A, p, r)
k = q - p + 1
if i == k
return A[q]
else if i < k
return RANDOMIZED-SELECT(A, p, q - 1, i)
else
return RANDOMIZED-SELECT(A, q + 1, r, i - k)
此算法的Java实现如下:
/**
*
* 在给定的数组下标区间范围内,根据选定的主元划分子数组
*
* @param A
* 待排序的主数组
* @param p
* 子数组下标的左边界
* @param r
* 子数组下标的右边界
* @return 得到的子数组划分分界下标值
*/
private int partition(int[] A, int p, int r) {
int pivot = A[r];// pivot element
int i = p - 1;
for (int j = p; j < r; j++) {
// 当pivot >=
// A[j]时,下标为j的元素就是需要与下标为i的元素进行交换的元素;下表为i的元素是前面那些比pivot大的元素的下标;即,在发现一个元素比pivot小时,就将此元素与之前的比pivot元素大的元素进行交换;这样比pivot小的元素总会出现在数组左边,并连续出现.
if (A[j] <= pivot) {
i++;// 下标自加1;每次自加1后,当前下标i指向的元素都比pivot大
exchange(A, i, j);
}
}
// for循环结束后此时数组A[p...r]的状态是:
// 对于任意下标k,如果k在[p,i]区间内,总有A[k] <= pivot;
// 对于任意下标k,如果k在[i+1,j]区间内,总有A[k] > pivot;
// 若下标k = r,则A[r] = pivot;
exchange(A, i + 1, r);// 将A[i+1]与A[r]交换,此时会有小远(i+1)下标的元素的值都小于等于pivot;大于(i+1)下标的元素值都大于pivot
return i + 1;
}
// 用于随机抽样
private Random util = new Random();
/**
*
* 加入随机抽样的数组划分算法
*
* @param A
* 待排序的数组A
* @param p
* 待排序的子数组下标左边界
* @param r
* 带排序的子数组下标右边界
* @return 得到的子数组划分分界下标值
*/
public int randomizedPartition(int[] A, int p, int r) {
int i = util.nextInt(r - p + 1)/* 区间[0,r-p+1) */ + p;// 随机生成一个在区间[p,r]之间的下标值
exchange(A, i, r);// 保证子数组划分所需要的pivot element是随机从A[p...r]中选取的
return partition(A, p, r);// 调用常规子数组划分函数,进行子数组划分
}
/**
* 一种解决选择问题的分治算法:返回数组A(不包含相同元素)中下标范围[p...r]内的第i小的元素;i从1开始算,i=1则说明选择A[p...r]中最小的那个元素
*
*/
public int randomizedSelect(int[] A, int p, int r, int i) {
if (p == r)
return A[p];//如果指定的下标范围内只有一个元素,那当前元素就是所需要的第i小的元素
int q = randomizedPartition(A, p, r);//使用随机子数组划分方法,借助一个随机主元去划分子数组(可能为空);返回的q的下标是当前主元的下标
//在上一步划分完子数组后,A[p...r]实际上可以看做被划分成了三个子数组区间:A[p...q-1] =< A[q] < A[q+1...r];此时主元A[q]在整个子数组A[p...r]中已经是排好序的了
int k = q - p + 1;//计算子数组A[p...q]内的元素个数
if (i == k)//如果i==k,则说明此次的主元A[q]就是要选择的第i小的元素,此时就直接返回;否则下面两步就要确定要选的元素是在子数组A[p...q-1]还是在A[q+1...r]中了
return A[q];
else if (i < k)//i < k,说明待选择的元素在子数组中A[p...q-1]中,随后继续递归查找
return randomizedSelect(A, p, q - 1, i);
else //i > k,说明待选择的元素在子数组A[q+1...r]中,随后继续递归查找;
return randomizedSelect(A, q + 1, r, i - k);//同时要注意,此时我们已知了A[p...q]中的元素(总共有k个)都是比待选择的元素小的,那么在子数组A[q+1...r]中,待选择的元素应该是第(i-k)小的了
}
完整的测试代码是:
public class SortAlgor {
public static void main(String[] args) {
int[] A = { 2, 5, 10, 8, 9, 12, 15, 4 };
SortAlgor algorithm = new SortAlgor();
System.out.println(algorithm.randomizedSelect(A, 0, A.length-1, 3));//返回5
}
/**
*
* 在给定的数组下标区间范围内,根据选定的主元划分子数组
*
* @param A
* 待排序的主数组
* @param p
* 子数组下标的左边界
* @param r
* 子数组下标的右边界
* @return 得到的子数组划分分界下标值
*/
private int partition(int[] A, int p, int r) {
int pivot = A[r];// pivot element
int i = p - 1;
for (int j = p; j < r; j++) {
// 当pivot >=
// A[j]时,下标为j的元素就是需要与下标为i的元素进行交换的元素;下表为i的元素是前面那些比pivot大的元素的下标;即,在发现一个元素比pivot小时,就将此元素与之前的比pivot元素大的元素进行交换;这样比pivot小的元素总会出现在数组左边,并连续出现.
if (A[j] <= pivot) {
i++;// 下标自加1;每次自加1后,当前下标i指向的元素都比pivot大
exchange(A, i, j);
}
}
// for循环结束后此时数组A[p...r]的状态是:
// 对于任意下标k,如果k在[p,i]区间内,总有A[k] <= pivot;
// 对于任意下标k,如果k在[i+1,j]区间内,总有A[k] > pivot;
// 若下标k = r,则A[r] = pivot;
exchange(A, i + 1, r);// 将A[i+1]与A[r]交换,此时会有小远(i+1)下标的元素的值都小于等于pivot;大于(i+1)下标的元素值都大于pivot
return i + 1;
}
// 用于随机抽样
private Random util = new Random();
/**
*
* 加入随机抽样的数组划分算法
*
* @param A
* 待排序的数组A
* @param p
* 待排序的子数组下标左边界
* @param r
* 带排序的子数组下标右边界
* @return 得到的子数组划分分界下标值
*/
public int randomizedPartition(int[] A, int p, int r) {
int i = util.nextInt(r - p + 1)/* 区间[0,r-p+1) */ + p;// 随机生成一个在区间[p,r]之间的下标值
exchange(A, i, r);// 保证子数组划分所需要的pivot element是随机从A[p...r]中选取的
return partition(A, p, r);// 调用常规子数组划分函数,进行子数组划分
}
/**
* 一种解决选择问题的分治算法:返回数组A(不包含相同元素)中下标范围[p...r]内的第i小的元素;i从1开始算,i=1则说明选择A[p...r]中最小的那个元素
*
*/
public int randomizedSelect(int[] A, int p, int r, int i) {
if (p == r)
return A[p];//如果指定的下标范围内只有一个元素,那当前元素就是所需要的第i小的元素
int q = randomizedPartition(A, p, r);//使用随机子数组划分方法,借助一个随机主元去划分子数组(可能为空);返回的q的下标是当前主元的下标
//在上一步划分完子数组后,A[p...r]实际上可以看做被划分成了三个子数组区间:A[p...q-1] =< A[q] < A[q+1...r];此时主元A[q]在整个子数组A[p...r]中已经是排好序的了
int k = q - p + 1;//计算子数组A[p...q]内的元素个数
if (i == k)//如果i==k,则说明此次的主元A[q]就是要选择的第i小的元素,此时就直接返回;否则下面两步就要确定要选的元素是在子数组A[p...q-1]还是在A[q+1...r]中了
return A[q];
else if (i < k)//i < k,说明待选择的元素在子数组中A[p...q-1]中,随后继续递归查找
return randomizedSelect(A, p, q - 1, i);
else //i > k,说明待选择的元素在子数组A[q+1...r]中,随后继续递归查找;
return randomizedSelect(A, q + 1, r, i - k);//同时要注意,此时我们已知了A[p...q]中的元素(总共有k个)都是比待选择的元素小的,那么在子数组A[q+1...r]中,待选择的元素应该是第(i-k)小的了
}
}
下一篇: 使用bat批处理来安装和卸载ASP组件