题目:
R5-1另类选择排序
分数 12
作者 陈越
单位 浙江大学
下列代码的功能是将一列元素{ r[1] … r[n] }按其键值 key 的非递减顺序排序。
普通选择排序是每次仅将一个待排序列的最小元放到正确的位置上,
而这个另类的选择排序是每次从待排序列中同时找到最小元和最大元,把它们放到最终的正确位置上。
void sort(list r[], int n)
{
int i, j, mini, maxi;
for (i = 1; i < n - i + 1; i++)
{
mini = maxi = i;
for (j = i + 1;____①_____; ++j)
{
if (____②_____)
mini = j;
else if (r[j]->key > r[maxi]->key)
maxi = j;
}
if (____③_____)
swap(&r[mini], &r[i]);
if (maxi != n - i + 1)
{
if (____④_____)
swap(&r[mini], &r[n - i + 1]);
else
swap(&r[maxi], &r[n - i + 1]);
}
}
}
参考答案:
①:j <= n - i + 1【考察内循环右边界】
②:r[j]->key < r[mini]->key【考察选择排序底层逻辑】
③:mini != i【考察选择排序优化】
④:maxi == i 【考察特殊情况处理】
题目的陷阱:
为了避免套题,题目设置了2个陷阱:
1.排序元素范围从1开始,而不是通常的从0开始
2.待排元素的表达用r[j]->key,而不是通常的r[j]
题目分析:
相比一般的选择排序,此代码的区别在于,每轮循环查找最小值和最大值,数据交换后,同时移动2端的查找范围。
要点:
1.循环边界的表达
1.1待排元素的范围是1-n,对应的数组下标是r[1],r[n+1],每轮循环上下边界向中间移动一个位置,左边界i++/++j[或j++,没区别],右边界n+1-i。
1.2内外循环边界的范围
外循环右边界可以选择循环到n-1或n,代码中i
2.特殊情况的处理
mini,maxi在内层for循环中会被更新,如果maxi=i,则说明最大值在序列头部,而swap(&r[mini], &r[i])会交换原来i位置的元素到mini。
所以需要判断当maxi==i,将mini位置的值和最后位置做交换swap(&r[mini], &r[n - i + 1])。
void sort(list r[], int n)
{
int i, j, mini, maxi;
for (i = 1; i < n - i + 1; i++)
{
mini = maxi = i;
for (j = i + 1;
j <= n - i + 1; ++j)
{
if (
r[j]->key < r[mini]->key)
mini = j;
else if (r[j]->key > r[maxi]->key)
maxi = j;
}
if (
mini != i)
swap(&r[mini], &r[i]);
if (maxi != n - i + 1)
{
if (
maxi == i)
swap(&r[mini], &r[n - i + 1]);
else
swap(&r[maxi], &r[n - i + 1]);
}
}
}
小结:
需要对选择排序算法的实现有一定熟练度和对细节的深刻理解才能搞定。
另附一个完整的选择排序代码
#include
#include
#include
#define ElementType int
typedef struct LNode *List;
struct LNode
{
int Data;
List Next;
};
ElementType *initA(int N);
void showA(ElementType A[], int N);
void swap(ElementType *a, ElementType *b);
void SelectionSort(ElementType A[], int N);
void SelectionSortSelectMinMax(ElementType A[], int N);
void SelectionSortWithCnt(ElementType A[], int N, int cnt);
List FindKth(List L, int K);
List Insert(List L, int K, ElementType X);
void showList(List L);
List initList(int N);
double run(int N, int num, int flag);
/*
选择排序每轮确定一个最小数,可以利用该特性,指定获取序列的前n个最小(大)数。
性能:
1.95e-01
*/
int main()
{
srand(time(0));
int N = 5;
// int num = 100;
// int flag = 0;
// int cnt = 5;
ElementType *A = initA(N);
showA(A, N);
// SelectionSort(A, N);
// SelectionSortWithCnt(A, N, cnt);
SelectionSortSelectMinMax(A, N);
showA(A, N);
// showA(A, cnt);
// printf("%6.2e", run(N, num, flag));
return 0;
}
ElementType *initA(int N)
{
ElementType *A = (ElementType *)malloc(N * sizeof(ElementType));
// ElementType B[] = {12, 23, 8, 65, 61};
ElementType B[] = {68, 23, 8, 65, 61};//该测试数据可以验证最大值在序列头部的case
// 12 23 8 65 61
for (int i = 0; i < N; i++)
{
if (N < 100)
{
// A[i] = rand() % 100;
A[i] = B[i];
}
else
{
A[i] = rand() % N; // 随机
// A[i] = 1; // 全1
// A[i] = i; // 顺序
// A[i] = N - 1 - i; // 逆序
}
}
return A;
}
void showA(ElementType A[], int N)
{
for (int i = 0; i < N; i++)
{
printf("%d ", A[i]);
}
printf("\n");
}
void swap(ElementType *a, ElementType *b)
{
ElementType t;
t = *a;
*a = *b;
*b = t;
}
void SelectionSort(ElementType A[], int N)
{
int i, j, min;
for (i = 0; i < N - 1; i++)
{
min = i;
for (j = i + 1; j < N; j++)
{
if (A[min] > A[j])
{
min = j;
}
}
if (min != i)
{
swap(&A[min], &A[i]);
}
}
}
void SelectionSortSelectMinMax(ElementType A[], int N)
{
int i, j, min, max;
for (i = 0; i < N - 1 - i; i++)
{
min = max = i;
for (j = i + 1; j < N - i; j++)
{
if (A[min] > A[j])
{
min = j;
}
else if (A[max] < A[j])
{
max = j;
}
}
if (min != i)
{
swap(&A[min], &A[i]);
}
if (max != N - 1 - i)//N-1-i代表序列尾部
{//max==i,表示最大值在序列头部,而min交换在前,会把max指向的值换到min位置,所以需要矫正
if (max == i)
{
swap(&A[min], &A[N - 1 - i]);
}
else
{
swap(&A[max], &A[N - 1 - i]);
}
}
}
}
void SelectionSortWithCnt(ElementType A[], int N, int cnt)
{
int i, j, min, count = 0;
for (i = 0; i < N - 1; i++)
{
if (count >= cnt)
break;
min = i;
for (j = i + 1; j < N; j++)
{
if (A[min] > A[j])
{
min = j;
}
}
if (min != i)
{
swap(&A[min], &A[i]);
}
count++;
}
}
List FindKth(List L, int K)
{
int loc = 1;
List S = L;
for (; S; S = S->Next)
{
if (K == loc)
{
break;
}
else
{
loc++;
}
}
return S;
}
List Insert(List L, int K, ElementType X)
{
List S, P;
if (K == 1)
{
S = (List)malloc(sizeof(struct LNode));
S->Next = L;
S->Data = X;
return S;
}
P = FindKth(L, K - 1);
if (P != NULL)
{
S = (List)malloc(sizeof(struct LNode));
S->Data = X;
S->Next = P->Next;
P->Next = S;
return L;
}
else
{
printf("Error:Index %d out of range.\n", K);
return NULL;
}
}
void showList(List L)
{
List S = L->Next;
if (S)
{
for (; S; S = S->Next)
{
printf("%d ", S->Data);
}
printf("\n");
}
}
List initList(int N)
{
int i, K = 1;
List L = NULL;
for (i = 0; i < N; i++)
{
if (N < 100)
{
L = Insert(L, K, rand() % 100);
}
else
{
L = Insert(L, K, rand() % N);
// L = Insert(L, K, 1);
// L = Insert(L, K, i);
// L = Insert(L, K, N-1-i);
}
}
List H = (List)malloc(sizeof(struct LNode));
H->Next = L;
return H;
}
double run(int N, int num, int flag)
{
clock_t start, stop;
double duration, sum = 0.0;
for (int i = 0; i < num; i++)
{
if (flag == 0)
{
int *A = initA(N);
start = clock();
SelectionSort(A, N);
stop = clock();
}
else
{
List L = initList(N);
start = clock();
//
stop = clock();
}
duration = ((double)(stop - start)) / CLK_TCK;
sum += duration;
}
return sum / num;
}