《第9章内部排序》习题解答概论Word文档格式.docx
《《第9章内部排序》习题解答概论Word文档格式.docx》由会员分享,可在线阅读,更多相关《《第9章内部排序》习题解答概论Word文档格式.docx(28页珍藏版)》请在冰豆网上搜索。
i++)cin>
>
A[i];
}
2.数组输出操作
函数voidOutput(intA[],intn)的功能是,依次显示输出数组A[]中的n个整数。
voidOutput(intA[],intn)
结果为:
i++)cout<
A[i]<
"
endl;
3.数组排序操作
函数voidMainSort(void(*sort)(int*,int))的功能是,首先通过函数调用Input(A,n)输入n个整数到数组A[]中,再通过函数调用sort(A,n)对A[]中的n个整数排序,最后通过函数调用Out(A,n)显示输出数组A[]中的n个整数。
voidMainSort(void(*sort)(int*,int))
{int*A,n;
请输入整数的个数:
cin>
A=newint[n];
Input(A,n);
原顺序"
Output(A,n);
sort(A,n);
//调用sort对数组A进行处理
排序后的顺序"
Out(A,n);
delete[]A;
9.2插入排序法
9.2.1直接插入排序(StraightInsertionSort)
1.算法思想
对于任意排列的序列(a0,a1,a2,…,an-1),先将第一个记录看成是一个有序序列L1=(a0),再将第2个记录插入到L1中得到有序序列L2=(a0,a1)
一般的,将第k+1个记录插入到有序序列Lk中得到有序序列Lk+1,如此重复插入n-1次即可得到有序序列Ln=(a0,a1,a2,…,an-1)。
2.举例说明
设原始序列为:
8、3、5、2、9、7、*5、4
第1轮:
(8)3,5,2,9,7,*5,4
第2轮:
(3,8)5,2,9,7,*5,4
第3轮:
(3,5,8)2,9,7,*5,4
第4轮:
(2,3,5,8)9,7,*5,4
第5轮:
(2,3,5,8,9)7,*5,4
第6轮:
(2,3,5,7,8,9)*5,4
第7轮:
(2,3,5,*5,7,8,9)4
第8轮:
(2,3,4,5,*5,7,8,9)
3.算法实现
voidInsertSort(intA[],intn)
{inttemp,i,j;
n-1;
i++)
{for(temp=A[i+1],j=i+1;
j>
0;
j--)
{if(temp>
=A[j-1])break;
A[j]=A[j-1];
}
A[j]=temp;
//将temp插入到相应的位置
}
4.算法分析
从排序过程可知该算法是稳定的;
算法的比较次数为f(n)=1+2+3+…+n-1,所以该算法的时间复杂度为T(n)=O(n2);
由于在排序过程中仅有一个辅助变量(temp),所以该算法的空间复杂度为S(n)=O
(1)。
说明:
(1)从算法的基本操作部分可以看出,当待排序数组A[n]基本有序时可以大大减少其比较的执行次数,所以该算法适用于序列为基本有序的场合。
(2)使用直接插入排序算法排序时可能出现下列情况:
在最后一趟开始之前,所有的元素都不在其最终的位置上。
比如,初始序列的最后一个元素为最小元素时就会出现以上情况。
*9.2.2折半插入排序
折半插入排序是直接插入排序的一个改进算法,该算法在排序过程中,通过折半查找法来实现寻找插入位置的操作,从而减少了数据元素中关键字的比较次数。
1.折半插入排序函数
voidInertSort1(inta[],intn)
{inti,j,k,t,l,h;
for(i=1;
if(a[i]<
a[i-1])
{l=0;
h=i-1;
while(l<
=h)//用二分法查找插入位置
{t=(l+h)/2;
if(a[t]==a[i])break;
elseif(a[t]<
a[i])l=t+1;
elseh=t-1;
}
if(h<
l)t=l;
//查找失败
for(k=a[i],j=i;
t;
j--)a[j]=a[j-1];
a[j]=k;
2.分步实现折半查找插入排序
(1)函数intFound(intA[],intn,intx)通过折半查找法求元素x在数组A[n]中的插入下标。
intFound(intA[],intn,intx)
{intt,l,h;
l=0;
h=n-1;
if(x>
=A[h])t=n;
elseif(x<
=A[0])t=0;
else
{while(l<
=h)
if(A[t]==x)break;
elseif(A[t]<
x)l=t+1;
returnt;
(2)调用函数Found()实现插入排序。
voidInertSort2(inta[],intn)
{inti,j,x,t;
{x=a[i];
t=Found(a,i,x);
for(j=i;
j--)a[j]=a[j-1];
a[t]=x;
9.2.3希尔排序法(ShellSort)
先将整个待排的n个记录序列按照某个间隔值d1(通常取n/2)分割成为若干个子序列,再对其分别进行直接插入排序,下一步取间隔值d2<
d1(通常取di+1=di/2)重复前面的过程
直到间隔值为1时对整体序列进行最后一次直接插入排序。
7、5、9、13、10、2、3、*5、8、1,其长度为n=10。
取间隔值d=10/2=5,先将序列看成如图9.1(a)所示的5个子序列:
(7,2)(5,3)(9,*5)(13,8)(10,1)。
再分别对每个子序列进行插入排序,其结果为如图9.1(b)所示的子序列:
(2,7)(3,5)(*5,9)(8,13)(1,10)。
第1轮排序结果为:
2,3,*5,8,1,7,5,9,13,10(注意:
在对各子序列排序时,子序列在主序列中的相对位置不变)。
取间隔值d=5/2=2,先将序列分成如图9.2(a)所示的2个子序列:
(2,*5,1,5,13)(3,8,7,9,10)。
再分别进行插入排序,其结果为如图9.2(b)所示的子序列:
(1,2,*5,5,13)(3,7,8,9,10)。
第2轮排序结果为:
1,3,2,7,*5,8,5,9,13,10。
取d=2/2=1,此时将整体序列进行一次直接插入排序。
第3轮排序结果为:
1,2,3,*5,5,7,8,9,10,13。
(1)算法Insert(A,k,n)的功能是,对数组A[n]按间隔k进行分组插入排序
voidInsert(intA[],intk,intn)
{inti,j,temp;
for(i=k-1;
{//A[i+1]为要插入的元素
for(temp=A[i+1],j=i+1;
j-k>
=0;
j-=k)
{//在相应的子序列中寻找插入位置。
if(temp>
=A[j-k])break;
A[j]=A[j-k];
A[j]=temp;
(2)希尔排序函数
voidShellSort(intA[],intn)
{intk=n;
while(k=k/2){Insert(A,k,n);
该算法是不稳定的;
希尔排序算法的时间复杂度与间隔值d的取法有关,当取d1=n,di+1=di/2(i=1,2,3,…)时,该算法的时间复杂度为T(n)=O(nlog2n),空间复杂度为S(n)=O
(1)。
9.3交换排序法
交换排序是借助于交换操作来完成的排序方法。
它的基本思想是:
在待排序的序列中,找到不满足序性的两个关键字,交换相应元素的相对位置,以满足序性;
重复该过程,直到序列中所有元素的关键字都有序为止。
本节主要介绍最为常见的两个交换排序算法:
起泡排序法和快速排序法。
9.3.1起泡排序法(BubbleSort)
1.算法思想(大数沉底法)
起泡排序算法的基本思想是:
先将第1个与第2个记录的关键字比较,如果关键字逆序,则交换这两个记录,否则不交换;
再将第2个与第3个记录的关键字比较,如果关键字逆序,则交换第2个与第3个记录,否则不交换,…,以此类推,最后将第n-1个与第n个记录的关键字比较,如果关键字逆序,则交换,否则不交换。
上述过程称为一趟起泡排序,其结果是使关键字值最大的记录移到第n个记录的位置。
然后对前n-1个记录进行同样的操作,第2趟起泡排序的结果是使关键字值次大的记录移到第n-1个记录的位置。
一般地,第i趟起泡排序的结果是从前n-i个记录中将关键字最大的记录移到第n-i+1个记录的位置上。
如果在某一趟排序中没有发生交换操作,则整个排序提前结束,否则最后排到仅剩一个记录为止。
设原始记录序列为:
第1趟排序结果:
3,5,2,8,7,*5,4,(9)
第2趟排序结果:
3,2,5,7,*5,4,(8,9)
第3趟排序结果:
2,3,5,*5,4,(7,8,9)
第4趟排序结果:
2,3,5,4,(*5,7,8,9)
第5趟排序结果:
2,3,4,(5,*5,7,8,9)
第6趟排序结果:
2,3,(4,5,*5,7,8,9)
由于没有发生交换操作,排序提前结束。
voidBubbleSort(intA[],intn)
{inti,j,t,f;
//f=1时表示发生了交换操作
for(f=1,i=n-1;
i>
0&
&
f;
i--)
{for(f=0,j=0;
j<
i;
j++)
if(A[j]>
A[j+1])//如果产生逆序
{f=1;
t=A[j];
A[j]=A[j+1];
A[j+1]=t;
//交换A[j]与A[j+1]的值
起泡排序算法是稳定的。
容易算出,该算法的时间复杂度为T(n)=O(n2),空间复杂度为S(n)=O
(1)。
算法的基本操作是交换操作,如果待排序的数组基本有序,那么可以大量地减少排序过程中的交换操作。
所以,起泡排序算法适用于序列为基本有序的场合。
9.3.2快速排序法(QuickSort)
快速排序算法的基本思想是:
通过一趟排序将待排记录分割成独立的两部分,其中一部分的关键字均比另一部分的关键字小。
接着对独立的每一组记录再分别进行排序和分割,直到每一组中都只有一个记录为止。
2.一趟快速排序的具体做法
附设两个指针low,high,其初值分别指向首个记录和最后一个记录,另设关键字为Key的枢轴记录变量(初始值为Low所指记录)。
首先从High所指记录开始起向前搜索,找到第1个关键字小于Key的记录同时将该记录存储到Low所指记录中,然后从Low所指记录开始向后搜索,找到第1个关键字大于Key的记录存于High所指记录中,再重复以上步骤,直到Low=High为止。
最后将Key中记录存储到Low所指记录中。
3.举例说明
38,13,27,76,65,49,49,97,其初始状态如图9.3所示。
第1趟排序:
27,13,(38),76,65,49,49,97
第2趟排序:
13,(27),(38),76,65,49,49,97
第3趟排序:
13,(27),(38),49,65,49,(76),97
第4趟排序:
13,(27),(38),(49),65,49,(76),97
第5趟排序:
13,(27),(38),(49),49,(65),(76),97
4.快速排序的算法实现
(1)算法intPart(int*H,intlow,inthigh)返回下标值i,使得在数组元素H[low]到H[high]中,H[i]前面的元素均小于等于H[i],H[i]后面的元素均大于等于H[i]。
intPart(int*H,intlow,inthigh)
{
intkey=H[low];
while(low<
high)
{while(low<
high&
H[high]>
=key)high--;
H[low]=H[high];
while(low<
H[low]<
=key)low++;
H[high]=H[low];
H[low]=key;
return(low);
//返回H[low]到H[high]中的枢轴变量下标
(2)函数voidQsort(int*H,intlow,inthigh)功能是,通过递归算法对数组H[]中从H[low]到H[high]的元素进行快速排序。
voidQsort(int*H,intlow,inthigh)//对数组元素H[low]到H[high]进行快速排序
intp;
//p为枢轴变量下标
if(low<
{p=Part(H,low,high);
Qsort(H,low,p-1);
Qsort(H,p+1,high);
voidQuickSort(intA[],intn){Qsort(A,0,n-1);
5.算法分析
快速排序算法是不稳定的,该算法的时间复杂度一般被认为是T(n)=O(nlog2n)中速度最快的排序法。
需要说明的是:
快速排序算法在最坏的情况下(序列为基本有序)将退化为起泡排序算法,其时间复杂度是T(n)=O(n2)。
因此,该算法不适用于序列为基本有序的场合。
9.4选择排序法(Selectionsort)
选择排序算法是另一类常用的排序方法。
选择排序的基本思想是:
第一趟,从n个元素中选取关键字值最小的元素与第一个元素互换;
第二趟,从剩余的n-1个元素中选取关键字值最小的元素与第二个元素互换;
一般地,第i趟,从剩余的n-i+1个元素中选取关键字值最小的元素与第i个元素互换。
重复以上过程,直到剩余元素仅有一个为止。
9.4.1简单选择排序法(SimpleSelectionSort)
1.基本算法思想
简单选择排序的算法思想是:
第一趟,取下标值i=0,下标值j从1到n-1逐个由A[i]与A[j]进行比较,如果A[i]>
A[j]则i=j,最后,如果i!
=0时执行A[0]与A[i]的交换操作;
第二趟,取下标值i=1,下标值j从2到n-1逐个由A[i]与A[j]进行比较,如果A[i]>
=1时执行A[1]与A[i]的交换操作;
重复以上过程n-1次后,对数组A[n]的排序过程结束。
(2),3,5,8,9,7,*5,4
(2,3),5,8,9,7,*5,4
(2,3,4),8,9,7,*5,5
(2,3,4,*5),9,7,8,5
(2,3,4,*5,5),7,8,9
(2,3,4,*5,5,7),8,9
第7趟排序结果:
(2,3,4,*5,5,7,8),9
3.选择排序的算法实现
voidSelectSort(intA[],intn)
{inti,j,k,t;
{for(k=i,j=i+1;
j++)if(A[j]<
A[k])k=j;
//查找最大关键字元素的下标k
if(k!
=i){t=A[i],A[i]=A[k],A[k]=t;
}//A[i]与A[k]交换
4.算法分析:
直接选择排序算法是不稳定的;
该算法的基本操作主要是元素的比较,其比较次数与待排序数组的初始状态无关,比较次数为f(n)=1+2+…+(n-1),时间复杂度为T(n)=O(n2),空间复杂度为S(n)=O
(1)。
9.4.2堆排序法(HeapSort)
堆排序是借助于一种称为堆的结构所进行的排序方法,它仅需要一个元素大小的辅助空间。
1.堆的定义
n个元素序列
当且仅当满足以下关系时称之为堆。
对任意的i=1,2,...,[n/2]满足:
(1)
或
(2)
。
当元素序列满足
(1)式的条件时称序列
为小顶堆;
当序列满足
(2)式时称序列
为大顶堆。
事实上,若将此序列对应的一维数组看成是一棵完全二叉树的顺序存储,则堆的含义表明,该二叉树中所有非终端结点的值均不大于(或小于)其左右孩子的值。
2.堆排序
在输出堆顶的最小(最大)值之后,使得剩余n-1个元素序列重又调整成为一个堆,则可得到n-1个元素中的次最小(大)值。
如此反复执行n-1轮后,便能得到一个有序序列,这个过程称之为堆排序。
3.堆排序的算法演示举例
(1)由初始序列构造成一个初始(大顶)堆
假设待排序的初始序列为:
49,38,65,97,76,13,27,49。
其中,n=8,n/2=8/2=4,根据完全二叉树的有关性质可知,其最后一个非叶结点的编号为4(即第4个结点)。
调整成为初始(大顶)堆的过程是,从第4个元素97开始逐个向前进行调整,使它们都满足
(2)式的条件。
第1步:
(4)97>
=(8)49;
(3)65>
=(6)13和(7)27;
自然满足;
如图9.4(a)所示。
第2步:
(2)38>
=(4)97和(5)76不满足,执行操作:
先38与97互换,再38与49互换。
调整后到结果如图9.4(b)所示,其结果为:
(1)49,
(2)97,(3)65,(4)49,(5)76,(6)13,(7)27,(8)38
第3步:
(1)49>
=
(2)97和(3)65不满足,先执行操作49与97互换,再49与76互换,调整结果如图9.4(c)所示。
最终结果为:
(1)97,
(2)76,(3)65,(4)49,(5)49,(6)13,(7)27,(8)38
(2)通过对序列的重复调整进行排序
将
(1),(8)互换,并将
(1)到(7)的元素调整成为大顶堆,其结果如图9.5(a)所示。
第1步调整的结果为:
(1)76,
(2)49,(3)65,(4)49,(5)38,(6)13,(7)27,97
将
(1),(7)互换,并将
(1)到(6)的元素调整成为大顶堆,其结果如图9.5(b)所示。
第2步调整的结果为:
(1)65,
(2)49,(3)27,(4)49,(5)38,(6)13,76,97
将
(1),(6)互换,并将
(1)到(5)的元素调整成为大顶堆,其结果如图9.5(c)所示。
第3步调整的结果为:
(1)49,
(2)49,(3)27,(4)13,(5)38,65,76,97
第4步:
将
(1),(5)互换,并将
(1)到(4)的元素调整成为大顶堆,其结果如图9.5(d)所示。
第4步调整的结果如为:
(1)49,
(2)38,(3)27,(4)13,49,65,76,97
依此类推,
第5步为:
(1)38,
(2)13,(3)27,49,49,65,76,97;
第6步为:
(1)27,
(2)13,38,49,49,65,76,97;
第7步为:
13,27,38,49,49,65,76,97。
3.堆排序的算法实现
(1)调整为大顶堆
算法voidHeapAdjust(int*H,ints,intm)的功能是,调整序列H中元素,使序列中第s个到第m个元素成为大顶堆。
voidHeapAdjust(int*H,ints,intm)
{intj,t;
t=H[s-1];
j=s*2;
while(j<
=m)//循环中s-1为双亲结点下标,j-1为孩子结点下标,t为要调整的值
{if(j<
m&
H[j-1]<
H[j])j++;
if(t>
=H[j-1])break;
H[s-1]=H[j-1];
s=j;
j=j*2;
H[s-1]=t;
(2)堆排序程序
voidHeapSort(int*H,intn)
{inti,t;
for(i=n/2;
i--)//从最后一个分支结点开始通过循环调用HeapAdjust()构造初始堆
HeapAdjust(H,i,n);
初始堆为:
Output(H,n);
for(i=n-1;
i--)//对初始堆H进行调整的排序过程
{t=H[0];
H[0]=H[i];
H[i]=t;
HeapAdjust(H,1,i);
//调整H中H[0]到H[i