插入排序
非常简单的排序算法,时间复杂度为O(n2),是稳定的排序算法
9.在下列算法中,( )算法可能出现下列情况:在最后一趟开始之前,所有的元素都不在其最终的位置上。
A)堆排序 B)冒泡排序 C)插入排序 D)快速排序
【答案】C
【解析】在插入排序中,如果待排序列中的最后一个元素其关键字值为最小,则在最后一趟开始之前,前n-1个排好序的元素都不在其最终位置上,与排好序后的位置相差一个位置。因此,选C。
1 void insertsort(int a[], int n) { 2 for (int i = 2; i <= n; i++) { 3 if (a[i] < a[i - 1]){ 4 a[0] = a[i]; 5 a[i] = a[i - 1]; 6 int j; 7 for (j = i - 2; a[j] > a[0]; j--) //比a[0]大的都要往后移 8 a[j + 1] = a[j]; 9 a[j + 1] = a[0]; //边界可以这么考虑,假设j=0了,也就是a[0]最小,那么它应该放在a[1]处,也就是a[j+1] 10 } 11 } 12 }
希尔排序
希尔排序是直接插入排序的一种改进算法,按照不同步长对元素进行插入排序,其时间复杂度与步长的选取有关,小于O(n2) ,如何选取步长增量序列是一个数学难题,当今还无人解决,是一种不稳定排序算法。
8.在文件“局部有序”或文件长度较小的情况下,最佳内部排序方法是( )
A)直接插入排序 B)冒泡排序 C)简单选择排序 D)归并排序
【答案】A
【解析】当待排序列基本有序时,对冒泡排序来说,若最大关键字位于序列首部,则每趟排序仅能使其“下沉”一个位置,要使其下沉到底部仍需n-1趟排序,也即时间复杂度仍为O(n2)。而对简单选择排序来说,其比较次数与待排序列的初始状态无关;归并排序要求待排序列已经部分有序,而部分有序的含义是待排序列由若干有序的子序列组成,即每个子序列必须有序,并且其时间复杂度为O(n log2n);直接插入排序在待排序列基本有序时,每趟的比较次数大为降低,也即n-1趟比较的时间复杂度由O(n2)降至O(n)。
6.用希尔方法排序时,若关键字的初始排序杂乱无序,则排序效率就低( )
【答案】×
【解析】希尔排序又称“缩小增量排序”,即每趟只对相同增量距离的关键字进行比较,这与关键字序列初始有序或无序无关。
1 void shell(int a[], int n) { 2 int gap = n / 2; 3 while (gap >= 1) { // gap=1结束 4 for (int i = gap + 1; i <= n; i++) { // i=gap+1,从第一组的第二个元素开始往前插入排序 5 if (a[i] < a[i - gap]) { 6 a[0] = a[i]; 7 a[i] = a[i - gap]; 8 int j; 9 for (j = i - gap ; j > 0 && a[j] > a[0]; j = j - gap) //比a[0]大的都要往后移动 j>0 这个条件很关键 否则容易越界,因为与直接插入排序不同 10 a[j + gap] = a[j]; 11 a[j + gap] = a[0]; 12 } 13 } 14 gap /= 2; 15 } 16 }
冒泡排序
冒泡排序比较简单粗暴,其时间复杂度为O(n2) ,是一种稳定的排序算法,两两元素互相比较并交换,每一趟选出最大的一个放在末尾
1 void bubble(int a[], int n) 2 { 3 for (int i = 1; i < n; i++) //n-1趟 4 for (int j = 1; j < n - i + 1; j++) 5 if (a[j] > a[j + 1]){ 6 int temp = a[j]; 7 a[j] = a[j + 1]; 8 a[j + 1] = temp; 9 } 10 }
快速排序
快速排序是目前被认为最好的一种排序方法,平均情况下快速排序的时间复杂度是Θ(nlogn),最坏情况是Θ(n^2)。当划分产生的两个子问题分别包含 n-1 和 0 个元素时,最坏情况发生。划分操作的时间复杂度为Θ(n),T(0)=Θ(1),这时算法运行时间的递归式为 T(n)=T(n−1)+T(0)+Θ(n)=T(n−1)+Θ(n),解为T(n)=Θ(n2)。当划分产生的两个子问题分别包含⌊n/2⌋和⌈n/2⌉−1个元素时,最好情况发生。算法运行时间递归式为 T(n)=2T(n/2)+Θ(n),解为T(n)=Θ(nlgn)。事实上只要划分是常数比例的,算法的运行时间总是O(nlgn)。 假设按照 9:1 划分,每层代价最多为 cn,递归深度为 log10/9n=Θ(lgn),故排序的总代价为 O(nlgn)。
快速排序方法在( )情况下最不利于发挥其长处。
A)要排序的数据量太大
B)要排序的数据中含有多个相同值
C)要排序的数据已基本有序
D)要排序的数据个数为奇数
【答案】C (基本有序时就变成了冒泡排序了)
17.n个元素进行快速排序的过程中,第一次划分最多需要移动( )次元素(包括开始将基准元素移到临时变量的那一次)。
A)n/2 B)n-1 C)n D)n+l
【答案】D
【解析】移动次数最多的情况是对n-1个元素比较时都需移动,加上开始将基准元素移到临时变量以及由临时变量移至正确位置的二次,即共需n+1次,故选D。
12.设表中元素的初态是按键值递增的,若分别用堆排序、快速排序、冒泡排序和归并排序方法对其仍按递增顺序进行排序,则_____________最省时间,_____________最费时间。
【答案】(1)冒泡排序 (2)快速排序
【解析】若初始序列已经有序,则冒泡排序仅需一趟(比较n-1次);而快速排序则需n-1趟,其时间复杂度升至O(n2)。因此填:冒泡排序,快速排序。
6.对一个具有7个记录的文件进行快速排序,请问:
(1)在最好情况下需进行多少次比较?并给出一个最好情况初始排列的实例。
(2)在最坏情况下需进行多少次比较?为什么?并给出此时的实例。
【答案】
(1)在最好情况下,由于快速排序是一个划分子区间的排序,每次划分最好能得到两个长度相等的子表,设表的长度为n=2k-1,显然有,第一遍划分得到两个长度均为 n/2 的子表。第二遍划分得到4个长度均为 n/4 的子表,以此类推,总共进行k=log2(n+1)遍划分,各子表的长度均为1时,此时排序结束。
由于n=7,k=3,在最好情况下,第一遍经过6次,可找到一个其基准是正中间的元素,第二遍分别对两个子表(此时长度为3)进行排序,各需要2次,这样就可将整个数据序列排序完毕,从而知7个数据的最好情况下需进行10次比较。如:4,7,5,6,3,1,2。
(2)在最坏情况下,若每次划分时用的基准,它的关键字值是当前记录中最大(或最小值),那么每次划分只能得到左子表(或右子表),子表长度只比原表减少了一个。因此,若初始排列的记录是按关键字递增或递减的,而所得的结果须为递减或递增排列的,此时快速排序就退化为与冒泡排序相似,而且时间复杂度为O(n2),此时反而不快了。对于n=7的数据序列,显然最坏情况下的比较次数为21。例如:7,6,5,4,3,2,1。
int Partition(int a[], int low, int high) { //这个函数是用来划分的 a[0] = a[low]; // a[0]是用来暂存 枢轴 的 所以数组从 1 开始到 10; int pivotloc = a[low]; while (low < high) { //长度为1就不需要划分了 while (low < high && a[high] >= pivotloc) --high; //a[high]大于等于枢轴才能往前移动 漏了=号会造成死循环 a[low] = a[high]; while (low < high && a[low] <= pivotloc) ++low; //a[low]小于等于枢轴才能往后移动 漏了=号会造成死循环 a[high] = a[low]; } a[low] = a[0]; //这句别漏了 枢轴要填回去 return low; } void Qsort(int a[], int low, int high) { if (low < high) { //这句别漏了 递归出口 int pivotloc; pivotloc = Partition(a, low, high); //比pivotloc小的放左边 大的放右边 Qsort(a, low, pivotloc - 1); //递归排序左边 中间的pivotloc就不用排了 是枢轴 Qsort(a, pivotloc + 1, high); //递归排序右边 } } void QuickSort(int a[], int n) { Qsort(a, 1, n); //数组从1开始到n结束 }
堆排序
10.设有5000个无序的元素,希望用最快速度挑选出其中前10个最大的元素,在以下的排序方法中,采用( )方法最好
A)快速排序 B)堆排序 C)基数排序
【答案】B
【解析】用堆排序最好,因为堆排序不需要等整个排序结束就可挑出前10个最大元素,而快速排序和基数排序都需等待整个排序结束才能知道前10个最大元素。
12.以下序列不是堆的是( )
A)100,85,98,77,80,60,82,40,20,10,66
B)100,98,85,82,80,77,66,60,40,20,10
C)10,20,40,60,66,77,80,82,85,98,100
D)100,85,40,77,80,60,66,98,82,10,20
【答案】D
【解析】根据堆采用完全二叉树的顺序存储形式及堆的特点,因第一个结点即根结点关键字值最大,则应建立一个大根堆,但依据此数据序列建立起堆后关键字值为40的左右孩子结点分别为60、66,不符合大根堆特点。
堆不是二叉排序树,只要满足父节点比孩子节点大就行了(最大堆)
14.一组记录的关键字为{45,80,55,40,42,85},则利用堆排序的方法建立的初始堆为( )
A)80,45,50,40,42,85
B)85,80,55,40,42, 45
C)85,80,55,45,42,40
D)85,55,80,42,45,40
【答案】B
7.对于关键字序列(12,13,11,18,60,15,7,20,25,100),用筛选法建堆,必须从键值为_____________的关键字开始。
【答案】60
【解析】建堆必须从n/2结点开始,而10/2=5位置的结点值为60,故填60。
1.对一个堆,按二叉树层次进行遍历可以得到一个有序序列( )
【答案】×
【解析】堆的定义只规定了结点与其左、右孩子结点间的大小关系,而同一层上属不同父母的结点之间并无明确的大小关系,所以堆的层次遍历并不能得到一个有序序列。
void sift(int arr[],int low,int high) { int i=low,j=2*i+1; int temp=arr[i];//用来暂时存储要修改的元素 while(j<=high) { if(j<high && arr[j]<arr[j+1]) ++j;//在i所指节点有左右孩子的情况下找左右孩子中较大的 if(temp<arr[j]) //小的放到下面去 { arr[i]=arr[j]; i=j; j=2*i+1; } else break; } arr[i]=temp; } void heatsort(int arr[],int n) { int i,temp; //建立最大堆 for(i=n/2-1; i>=0; i--) sift(arr,i,n-1); for(i=n-1;i>=0;i--) { temp=arr[0]; arr[0]=arr[i]; arr[i]=temp; sift(arr,0,i-1); } }
2-路归并排序
18.下述几种排序方法中,要求内存量最大的是( )
A)插入排序 B)选择排序 C)快速排序 D)归并排序
【答案】D
【解析】插入排序和选择排序需要的辅助空间为O(1),快速排序需要的辅助空间为O(log2n ),归并排序需要的辅助空间为O(n),因此选D。
14.在归并排序中,若待排序记录的个数为20,则共需要进行_____________趟归并,在第三趟归并中,是把长度为_____________的有序表归并为长度为_____________的有序表。
【答案】(1)5 (2)4 (3)8
【解析】第一次把长度为1的归并为长度的2的子表共10个,第二次把长度为2的归并成长度为4的子表共5个,第三次把长度为4的归并为长度为8的共3个,第四次长度为8归并为长度为16的,第5次归并成一个有序表。
//递归实现
void merge(int a[], int b[], int first, int mid, int last) { int i, j, k; i = first; j = mid + 1; for (k = 0; i <= mid && j <= last; k++) { if (a[i] < a[j]) b[k] = a[i++]; else b[k] = a[j++]; } while (i <= mid) b[k++] = a[i++]; while (j <= last) b[k++] = a[j++]; for (i = 0; i < k; i++) a[first + i] = b[i]; //不是让a从头开始赋值,是让a的某一个划分块被赋予b } void mergesort(int a[], int b[], int first, int last) { if (first < last) { int mid = (first + last) / 2; mergesort(a, b, first, mid); mergesort(a, b, mid + 1, last); merge(a, b, first, mid, last); } } int main() { int *a = new int[5]{ 3,4,2,1,6 }; int *b = new int[5]; mergesort(a, b, 0, 4); for (int i = 0; i < 5; i++) cout << a[i] << " "; cout << endl; return 0; }
//非递归 void Merge(string *a, int first, int second, int len) { //合并[first...first+len-1][secont...second+len-1] string *b = new string[n]; int i = first, j = second; int k = 0; for (; i <first+len && j <second+len && j<n; k++) { if (a[i] > a[j]) b[k] = a[i++]; else b[k] = a[j++]; } while (i < first + len) b[k++] = a[i++]; while (j < second + len && j< n) //注意这个区间的判断,因为并不是能保证第二个区间的长度也是len //比如 [4,3,2,1] [5] 第一个区间长度是4 但是第二个是1啊 不加这个会越界 b[k++] = a[j++]; for (i = 0; i < k; i++) a[first + i] = b[i]; } void MergeSort(string *a,int first, int last) { int len = 1; int one_start, two_start;//前后两个区间的起始 while (len <= n) { //len=n就只有一个区间了 one_start = first, two_start = one_start + len; while (1) { if (two_start>=n) //当没有第二个区间的时候这一趟就结束了 break; Merge(a, one_start, two_start, len); //归并one two两个区间,区间长度为len one_start = two_start + len; two_start = one_start + len; } print(a, n); len = len * 2; } }
6.对n个数据进行简单选择排序,所需进行的关键字间的比较次数为_____________,时间复杂度为_____________。
【答案】(1)n(n-1)/2 (2)O(n2)
每一趟选出最大(小)的一个,第一趟比较除自身以外的n-1个元素,第二趟n-2,……,最后一趟 0,全部相加 等差数列求和Sn=(a1+an)*n/2