1、内部排序方法的稳定性是指( B )。
A、排序算法不允许有相同的关键字记录
B、排序算法允许有相同的关键字记录,排序前后相同关键字记录的相对位置不变
C、平均时间复杂读为O(nlogn)的排序方法
D、排序算法允许有相同的关键字记录,排序前后相同关键字记录的绝对位置不变
2、在对n个元素进行改进的冒泡排序过程中,最好情况下的时间复杂度为( D )。
A、O(1)
B、O(logn)
C、O(n^2)
D、O(n)
3、一组记录的关键字为{46,79,56,38,40,84},则利用快速排序方法,以第一个记录为基准得到的第一次划分结果为( C )。
A、38,40,46,56,79,84
B、40,38,46,79,56,84
C、40,38,46,56,79,84
D、40,38,46,84,56,79
4、用某种排序方法对线性表(25,84,2l,47,15,27,68,35,20)进行排序时,若元素序列的变化情况如下:
25,84,2l,47,15,27,68,35,20
20,15,21,25,47,27,68,35,84
15,20,2l,25,35,27,47,68,84
15,20,21,25,27,35,47,68,84
则采用的排序方法是( D )。
A、选择排序
B、希尔排序
C、归并排序
D、快速排序
5、将两个各有N个元素的有序表归并成一个有序表,其最少的比较次数是( A )
A、N
B、2N-1
C、2N
D、N-1
1、设有5个互不相同的元素a、b、c、d、e,能否通过7次比较就将其排好序?如果能,请列出其比较过程;如果不能,则说明原因。
答案:
可以的,以下是比较过程:
首先需要借鉴三个元素排序和四个元素中对其中三个元素建立全序的思想。
1、对于三个元素a,b,c进行排序:
需要“a与b进行比较”,“a与c进行比较”,“b与c进行比较”三次排序才可以确定a,b,c的大小关系。
2、对于四个元素a,b,c,d进行排序,则通过3次排序可以确定其中三个元素的大小关系:
假设a,b进行比较,较大的数为a,对c,d进行比较,较大的数为c,则我们再对a与c进行一次比较,当a>c时,我们可以确定a>c>d且且a>b的关系。可以看到一共使用了3次对比,实现了对四个元素中三个元素的排序,其他结果同理。
3、对5个元素a,b,c,d,e进行排序:
我们可以先取a,b,c,d四个元素进行三次排序,由步骤2可知a>c>d且a>b的关系,然后我们通过折半查找的方式将元素e放入a>c>d序列中。先让e与c进行比较,比较结果要么在c左边,要么在c右边,如果在c的左边时,让e与a进行对比,就可以找出e的位置了,这样的折半查找总共需要比较2次即可。
假设e在c左边时,我们会得到下面两种情况:一种是e>a>c>d,另一种是a>e>c>d,现在只剩下b还没有进行排序,由于我们已知a>b条件,对于情况1来讲,只需要将b与c和d进行两次比较即可得到b的位置;对于情况2来讲,只需要将b同e,c,d进行折半查找即可得到b的位置,也是只需要两次比较即可。
综上,比较五个元素总共需要的次数是:3+2+2=7次,可证明经过7次比较可以将5个互不相同的元素进行排序。
上面写的是降序过程,如果想实现升序过程将以上过程中的大于小于号进行对调即可。
2、对一个由n个关键字不同的记录构成的序列,能否用比2n-3少的次数选出该序列中关键字取最大值和关键字取最小值的记录?请说明如何实现?在最坏的情况下至少进行多少次比较?
答案:
实现思路:
1、首先让原序列中前后两个元素为一组进行比较,将较大的元素加入序列A,较小的元素加入序列B,如果最后剩下一个元素,则将这个元素同时加入序列A和序列B中。
2、在序列A中求最大值,在序列B中求最小值,如果序列A/序列B中有s个元素,那么得到最大值/最小值需要进行s-1次对比。
如果n为奇数的话,设n=2k+1,那么第一步需要进行k次对比,序列A和序列B有k+1个元素,第二步求出最大值和最小值分别需要进行k次对比。则一共需要进行k+k+k=3k次对比,而k=(n-1)/2,那么可得3k=(3n-3)/2,这种情况下需要进行(3/2)n-3/2次比较,可以看出当n>3的时候,(3n-3)/2小于2*n-3成立。
如果n为偶数的话,设n=2k,那么第一步需要进行k次对比,序列A和系列B有k个元素,第二步求序列A和序列B中最大值和最小值分别需要进行k-1次对比。则一共需要k+(k-1)+(k-1)=3k-2次对比,而k=n/2,那么可得3k-2=(3n)/2-2,即在这种情况下需要进行((3n)/2-2)次对比可以得出最大值和最小值,当n>2的时候,((3n)/2-2)小于2*n-3成立。
3、给出一组关键字T=(12,2,16,30,8,28,4,10,20,6,18),写出用下列算法从小到大排序时第一趟结束时的序列;
(1) 希尔排序(第一趟排序的增量为5) (2) 快速排序(选第一个记录为枢轴(分隔)) (3) 链式基数排序(基数为10)
答案:
(1)希尔排序当增量是5的时候,第一趟排序分出的子数组有“12,28,18”,“2,4”,“16,10”,“30,20”,“8,6”,对这些子数组进行排序交换元素,得到新序列为:12,2,10,20,6,18,4,16,30,8,28。
(2)快速排序,选择12为pivotkey,比12小的元素放在12左边,比12大的元素放在12右边,第一趟结束后得到的新序列为:6,2,10,4,8,12,28,30,20,16,18
(3)链式基数排序以10为基数时,那么第一趟对个位数进行排序,得到序列为:30,10,20,12,2,4,16,6,8,28,18
4、给出如下关键字序列321,156,57,46,28,7,331,33,34,63. 试按链式基数排序方法,列出一趟分配和收集的过程。
答案:
序列为10进制数字,我们取基数10对该序列进行链式基数排序:
将序列全部变成三位数,不够三位数的数字在高位补0,即将7变为007,57变为057,然后第一趟对个位数进行排序。“分配”过程即将个位数相同的数字放在一个序列中,这些序列一共有基数个(此题是10个),分配后的序列如下图:
然后进行“收集”操作,就是按照各队列的顺序,依次把对象从队列中“收集”起来,“收集”完成后所有对象就按照其关键码(这里个位数为关键码)的值从小到大排好序了。最后第一趟得到序列为:321,331,33,063,034,156,046,057,007,028。
同理对十位数进行基数为10的排序,得到第二趟结束的序列为:007,028,321,331,033,034,046,156,057,063。
最后对百位数进行排序,第三趟排序结束的序列为:007,028,033,034,046,057,063,156,321,331。
5、我们知道,对于由n个元素组成的线性表进行快速排序时,所需进行的比较次数与这n个元素的初始排序有关。问:
(1)当n= 7时,在最好情况下需进行多少次比较?请说明理由。
(2)当n=7时,给出一个最好情况的初始排序的实例。
(3)当n=7时,在最坏情况下需进行多少次比较?请说明理由。
(4)当n=7时,给出一个最坏情况的初始排序的实例。
答案:
(1)最好的情况下是每次划分得到的子序列的长度大致相等,当n=7时,第一趟划分得到两个子序列,将设这两个子序列的长度相同,则可之为3,第一趟需要进行6次对比,将序列分成了两个长度为3的子序列,第二遍分别对两个子序列进行排序,每个子序列只需比较两次即可,第二趟就比较了2*2次,也就是4次。这样通过4+6=10次对比可以将原序列排序完毕。所以最好的情况下总共比较10次。
(2)根据1中所答,最好的初始序列就是在每次排序的时候都可以将原序列分为两个长度一样的子序列,即为每个序列的基准值刚好在此序列值的大小居中的位置。则可知其中一种情况是:4,9,5,6,2,0,1。
(3)最坏情况下,每一次划分,基准值(枢轴)都在序列的最左端或者最右端。换句话说,如果每次用来划分的记录的关键字是最大值/最小值,则只能得到左/右子序列,子序列的长度比原长度少1。如果原序列为递增/递减排序,这时排序的时间复杂度为O(n2),当n=7时,最坏情况下比较次数是21次。
(4)当n=7时,最坏情况下的初始排序例子可以为(8,7,6,5,4,3,2)。
6、设有6000个无序元素,希望用最快的速度挑选出其中最大的前10个元素。采用哪种排序方法最好?为什么?
排序思想上来讲,使用选择排序、冒泡排序、堆排序都可以,但是实际操作中应该选择使用堆排序会更快。
这三种排序不需要对整个序列排序后才能得出结果,在排序的过程中就可以找到最大的10个元素。
这里说明以下堆排序:
堆排序算法中,数组以完全二叉树的形式存在,这个完全二叉树有以下性质(大顶堆为例):
(1)每个父节点的键值总是大于其左右孩子的键值。
(2)每个节点的左子树和右子树都是一个大顶堆。
首先让n个无序的原始数据变为大顶堆,root节点的键值就是整个数组中最大的元素,然后交换根节点和最后一个节点,将最大元素放在堆中最后一个元素,形成有序区。然后继续调整由剩余n-1个节点构成新的堆,重复前面的步骤,直至找到前10个最大元素停止。
1、已知奇偶转换排序如下所述:第一趟对所有奇数的i,将a[i]和a[i+1]进行比较,第二趟对所有偶数的i,将a[i]与a[i+1]进行比较,每次比较时若a[i]> a[i+1],则将二者交换,以后重复上述两趟过程,直至整个数组有序。
(1)试问排序结束的条件是什么?
(2)编写一个实现上述排序过程的算法。
答案:
(1)奇位置排序和偶位置排序过程中不再出现元素交换,即为排序结束。
(2)c++实现代码如下:
#include
using namespace std;
#define N 11
int main()
{
int* a= new int[N]{10,21,32,0,21,90,76,23,56,41,11};
//计算奇数序列和偶数序列进行循环时它们最大的下标值
int odd_number;
int even_number;
if(N%2==0){
odd_number=N-2;
even_number=N-3;
}
else{
odd_number=N-3;
even_number=N-2;
}
int flag=1;//判断是否应该结束的条件
while(flag){
flag=0;
//对奇数下标排序
for(int i=0;i<=odd_number;i=i+2){
if(a[i]>a[i+1]){
flag=1;
int temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
}
}
//对偶数下标排序
for(int i=1;i<=even_number;i=i+2){
if(a[i]>a[i+1]){
flag=1;
int temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
}
}
}
for(int i=0;i<N;i++){
cout<<a[i]<<" ";
}
delete[] a;
return 0;
}
运行结果:
2、计数排序(count sorting):计数排序算法对一个待排序的表(用数组表示)进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键码互不相同,计数排序算法针对表中的每个记录,扫描待排序的表一趟,统计表中有多少个记录的关键码比该记录的关键码小,假设针对某一个记录,统计出的计数值为c,那么,这个记录在新的有序表中的合适的存放位置即为c。
(1) 给出适用于计数排序的数据表定义;
(2) 使用Java或C语言编写实现计数排序的算法;
(3) 对于有n个记录的表,关键码比较次数是多少?
(4) 与简单选择排序相比较,这种方法是否更好?为什么?
答案:
(1)计数排序的数据表使用顺序表即可。
(2)使用C++进行实现:
#include
using namespace std;
#define N 10
#define K 50 //桶的最大值
void Output(int a[]){
for(int i=0;i<N;i++){
cout<<a[i]<<" ";
}
}
void CountingSort(int a[],int b[]){
//使用c[a[i]]表示数组a中数值有多少个元素小于等于a[i]
int c[K];
//初始化c数组
for(int i=0;i<K;i++){
c[i]=0;
}
for(int i=0;i<N;i++){
c[a[i]]++;
}
for(int i=1;i<K;i++){
c[i]+=c[i-1];
}
for(int i=0;i<N;i++){
//c[a[i]-1]表示在数组a中,a[i]之前有几个小于a[i]的值
//这个值就可以当作是结果数组的下标,下标对应的值就是a[i]的值
b[c[a[i]]-1]=a[i];
//当a中有重复元素时,不能让他们两个放在同一位置
c[a[i]]--;
}
}
int main()
{
int a[N]={10,13,34,24,14,12,26,2,45,8};
//b为结果数组
int b[N];
CountingSort(a,b);
Output(b);
return 0;
}
(3)时间复杂度为O(n+k),其中k是序列中最大的那个元素,因为k是不可控的,所以计数排序较大程度上会取决于k的值。
(4)两者各有各的好处,计数排序和简单选择排序不同,计数排序是一种非比较排序,并且排序时间是线性的。