常见的十种排序算法归纳总结

数据结构与算法 专栏收录该内容
2 篇文章 0 订阅

对常见的方法进行总结便于自己复习,若有偏差的地方欢迎批评指正,文中实现代码为Java。


首先贴一张前七个排序方法的运行时间分析图:


一、冒泡排序(Bubble Sort

下图和部分内容来自维基百科,很形象吧。


Bubble sort animation.gif



冒泡排序算法的运作如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

从上面步骤可以看出,冒泡排序对n个项目需要O(n^2)的比较次数,且可以原地排序。尽管这个算法是最简单了解和实现的排序算法之一,但它对于少数元素之外的数列排序是很没有效率的。

改进思路1:设置标志位,明显如果有一趟没有发生交换(flag = false),说明排序已经完成

改进思路2:记录一轮下来标记的最后位置,下次只需从头部遍历到这个位置.


Tips:冒泡排序最大的缺点在于程序通常会对已经排好序的数列继续执行直到O(n^2),通过设置标志位可以让程序适时停止,可以把最好的复杂度降低到O(n)


实现代码:

/**
 *冒泡排序
 */
public class BubbleSort{
	public static void swap(int left, int right){
		int temp = left;
		left = right;
		right = temp;
	}
	//原始冒泡排序
	public static void sort1[int[] a]{
		for(int i = 0; i < a.length-1; i++){
			for (int j = 0; j < a.length-1-i; j++ ) {
				if(a[j] > a[j+1]){
					swap(a[j], a[j+1]);
				}
			}
		}
	}
	//改进思路设置标志位,如果有一趟明显没有发生交换,说明排序已经完成
	public static void sort2[int[] a]{
		boolean flag = true;
		while(flag){
			flag = false;
			for(int i = 0; i < a.length-1; i++){
			if(a[i] > a[i+1]){
				//一旦程序不进入这段代码代表排序已经完成,flag = false,跳出while循环完成排序 
				swap(a[i], a[i+1]);
				flag = true;
				} 
			}
		}
	}
	//改进思路:设置标志位,记录一轮下来标记的最后位置,下次只需从头遍历到这个位置
	public static void sort3[int[] a]{
		//该标志位为发生交换的位置,假定两两交换发生在数组最后的两个位置
		boolean flag = a.length-1;
		while(flag){
			//记录下发生数据交换的位置
			int bound = flag;
			for(int i = 0; i < bound; i++){
			if(a[i] > a[i+1]){
				swap(a[i], a[i+1]);
				flag = i;
				} 
			}
		}
	}
}



二、直接插入排序(Insertion Sort


插入排序非常简单直观,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。



Insertion sort animation.gif


具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

看了这张图应该就明白了吧,下面是Java实现代码:

/**
 *插入排序
 */
public class InsertionSort{
	public static void insertionSort(Comparable[] a){
		for(int i = 1; i < a.length; i++){
			Comparable key = a[i];
			int position = i;
			//将最大的值传递到右边
			while(position > 0 && a[position-1].compareTo(key)>0){
				a[position] = a[position-1];
				position--;
			}
			a[position] = key;
		}
	}
}



三、简单选择排序(Selection Sort


选择排序也是一种简单直观的排序算法。它的工作原理为:将待排序序列分为两部分,一部分为有序序列,另一部分为无序序列,首先在无序序列中找到最小(大)元素,存放到有序序列的起始位置,然后,再从剩余无序元素中继续寻找最小(大)元素,然后放到有序序列的末尾。以此类推,直到所有元素均排序完毕。

选择排序动画演示




实现代码如下:

/**
 *选择排序
 */
public class SelectionSort{
	public static void selectionSort(Comparable[] a){
		Comparable temp;
		//min存放最小元素的索引值
		int min;
		for(int i = 0; i < a.length-1; i++){
			//假定第一个元素为最小元素
			min = i;
			//循环遍历元素,每遍历一个元素,与当前最小元素比较,若此元素比当前元素小,则将此元素设置为最小元素
			for(int j = i + 1; j < a.length; j++ ){
				if(a[j].compareTo(a[min])<0) {
					min = j;
				}
			}
			//将最小的一个元素放到第一个
			temp = a[i];
			a[i] = a[min];
			a[min] = temp;
		}
	}
}


Tips:

总的来说,插入排序比冒泡排序和选择排序的性能要更好一些

冒泡排序已经很少用到了,虽然它非常稳定。(冒泡排序曾经一直我的首选暴力法敲打)

在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。


四、归并排序(Merge Sort

归并排序相信大家都非常熟悉,学习任何算法的书籍都会在递归那一章来讲解归并排序。它是建立在归并操作上的一种有效的排序算法。其时间复杂度为O(nlogn),空间复杂度为O(n+logn).如果非递归实现归并,则避免了递归时深度为logn的栈空间,空间复杂度为O(n)。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。


Merge sort animation2.gif

归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。

归并操作的过程如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾


代码实现如下:

/**
*归并排序
*/
public class MergeSort{
	public static void merge(int[] a, int[] tmp_array, int lpos, int rpos, int rightn){
		int i,leftn,num_elements, tmpos;
		leftn = rpos - 1;
		tmpos = lpos;
		num_elements = rightn - lpos + 1;
		//主循环
		while(lpos <= leftn && rpos <= rightn)
			if(a[lpos] <= a[rpos]){
				tmp_array[tmpos++] = a[lpos++];
			}
			else{
				tmp_array[tmpos++] = a[rpos];
			}
		while(lpos <=leftn)
			tmp_array[tmpos++] = a[lpos]++;
		while(rpos <= rightn)
			tmp_array[tmpos++] = a[rpos++];

		for (i = 0; i < num_elements; i++,rightn){
			a[rightn] = tmp_array[rightn];
		}
	}

	public static void msort(int a[], int[] tmp_array, int left, int right){
		int center;
		if(left < right){
			center = (right + left) / 2;
			msort(a, tmp_array, left, center);
			msort(a, tmp_array, center+1, right);
			merger(a, tmp_array, left, center+1, right);
		}
	}

	public static void mergeSort(int[] a, int n){
		int[] tmp_array;
		tmp_array = new int[sizeof(int)];
		if(tmp_array != null){
			msort(a, tmp_array, 0, n-1);
		}
	}
}


五、希尔排序(Shell Sort

希尔排序也称递减增量排序算法,是插入排序的一种更高效的改进版本。值得一提的是,上文介绍的四种排序算法都是稳定排序方法,二希尔排序是非稳定排序算法,其效果图如下:

Step-by-step visualisation of Shellsort

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
  • 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位


算法实现:

原始的算法实现在最坏的情况下需要进行O(n2)的比较和交换。V. Pratt的书[1] 对算法进行了少量修改,可以使得性能提升至O(n log2 n)。这比最好的比较

算法的O(n log n)要差一些。


希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步

长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序插入排序),可能会进行n次的比较和交换才能将该数

据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。


一个更好理解的希尔排序实现:将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数

组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size而不是i++)。

例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表

中来更好地描述算法,这样他们就应该看起来是这样:

13 14 94 33 82			
25 59 94 65 23
45 27 73 25 39
10

然后我们对每列进行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].这时10已经移至正确位置了,然后再以3为步长进行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序之后变为:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后以1步长进行排序(此时就是简单的插入排序了)。

步长的选择是希尔排序的重要部分。只要最终步长为1任何步长串行都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算

法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。

Donald Shell 最初建议步长选择为\frac{n}{2}并且对步长取半直到步长达到 1。虽然这样取可以比\mathcal{O}(n^2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。 可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。比如,如果一个数列以步长5进行了排序然后再以步

长3进行排序,那么该数列不仅是以步长3有序,而且是以步长5有序。如果不是这样,那么算法在迭代过程中会打乱以前的顺序,那就不会以如此短的时间完成

排序了。

步长串行 最坏情况下复杂度
{n/2^i}\mathcal{O}(n^2)
2^k - 1\mathcal{O}(n^{3/2})
2^i 3^j\mathcal{O}( n\log^2 n )

已知的最好步长串行是由Sedgewick提出的 (1, 5, 19, 41, 109,...),该串行的项来自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 这两个算式[1].这

项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长串行的希尔排序比插入排序堆排序都要快,甚至在小数组中比快速排序

快,但是在涉及大量数据时希尔排序还是比快速排序慢。

另一个在大数组中表现优异的步长串行是(斐波那契数列除去0和1将剩余的数以黄金分区比的两倍的进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713, …)[2]

Java代码实现:

/**
*希尔排序
*/
public class ShellSort{
	public static void shellSort(comparable[] a){
		int i, j, increment;
		int tmp;
		for (increment = a.length /2;increment > 0; increment /=2) {
			for(i = increment; i < a.length; i++){
				for(j = i; j >= increment; j -=increment){
					if(a[j].compareTo(a[i]) > 0){
						a[j] = a[j] - increment;
					}
					else{
						break;
					}
				}
				a[j] = a[i];
			}
		}
	}
}


六、堆排序(HeapSort)

堆排序(Heapsort)是指利用这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。示意图如下:


Sorting heapsort anim.gif
堆结点的访问:

通常堆是通过一维数组来实现的。在起始数组为 0 的情形中:

  • 父节点i的左子节点在位置 (2*i+1);
  • 父节点i的右子节点在位置 (2*i+2);
  • 子节点i的父节点在位置 floor((i-1)/2);


堆的操作

在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:

  • 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
  • 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
  • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算


堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值.如此反复执行,便能得到一个有序序列了。


实现代码:
/**
*堆排序
*/
public class HeapSort {
	private static void buildMaxHeapify(int[] data){
		//没有子节点的才需要创建最大堆,从最后一个的父节点开始
		int startIndex = getParentIndex(data.length - 1);
		//从尾端开始创建最大堆,每次都是正确的堆
		for (int i = startIndex; i >= 0; i--) {
			maxHeapify(data, data.length, i);
		}
	}
	/**
	 * 创建最大堆
	 * @param data
	 * @param heapSize 需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了
	 * @param index 当前需要创建最大堆的位置
	 */
	private static void maxHeapify(int[] data, int heapSize, int index){
		// 当前点与左右子节点比较
		int left = getChildLeftIndex(index);
		int right = getChildRightIndex(index);
 
		int largest = index;
		if (left < heapSize && data[index] < data[left]) {
			largest = left;
		}
		if (right < heapSize && data[largest] < data[right]) {
			largest = right;
		}
		//得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整
		if (largest != index) {
			int temp = data[index];
			data[index] = data[largest];
			data[largest] = temp;
			maxHeapify(data, heapSize, largest);
		}
	}
 
	/**
	 * 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的
	 * @param data
	 */
	private static void heapSort(int[] data) {
		//末尾与头交换,交换后调整最大堆
		for (int i = data.length - 1; i > 0; i--) {
			int temp = data[0];
			data[0] = data[i];
			data[i] = temp;
			maxHeapify(data, i, 0);
		}
	}
 
	/**
	 * 父节点位置
	 * @param current
	 * @return
	 */
	private static int getParentIndex(int current){
		return (current - 1) >> 1;
	}
 
	/**
	 * 左子节点position 注意括号,加法优先级更高
	 * @param current
	 * @return
	 */
	private static int getChildLeftIndex(int current){
		return (current << 1) + 1;
	}
 
	/**
	 * 右子节点position
	 * @param current
	 * @return
	 */
	private static int getChildRightIndex(int current){
		return (current << 1) + 2;
	}
	
	/**
	 * 以2为底的对数
	 * @param param
	 * @return
	 */
	private static double getLog(double param){
		return Math.log(param)/Math.log(2);
	}
}

七、快速排序(Quick Sort)

快速排序排序n个项目要O(nlogn)次比较,但实际上快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率的被实现出来,其示意图如下所示:

Sorting quicksort anim.gif

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

步骤为:

  1. 从数列中挑出一个元素,称为 "基准"(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。


快速排序的实现代码较长,传送门:右键这里新窗口打开看代码

进一步理解快速排序戳这里:右键这里新窗口打开

看完了上面的其中比较排序,下面是三种非比较排序,理论上比比较排序要快,可以达到(O(n)),但值得注意的是这三种排序法在特定应用背景下才能用,否则适得其反。

八、计数排序(Counting Sort)

计数排序是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

通俗地理解,例如有10个年龄不同的人,统计出有8个人的年龄比A小,那A的年龄就排在第9位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去1的原因。 算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1


实现代码:

/**
*计数排序
*/
public class CountingSort {
    public static void main(String[] argv) {
        int[] A = CountingSort.countingSort(new int[]{16, 4, 10, 14, 7, 9, 3, 2, 8, 1});
        Utils.print(A);
    }
 
    public static int[] countingSort(int[] A) {
        int[] B = new int[A.length];
        // 假设A中的数据a'有,0<=a' && a' < k并且k=100
        int k = 100;
        countingSort(A, B, k);
        return B;
    }
 
    private static void countingSort(int[] A, int[] B, int k) {
        int[] C = new int[k];
        // 计数
        for (int j = 0; j < A.length; j++) {
            int a = A[j];
            C[a] += 1;
        }
        Utils.print(C);
        // 求计数和
        for (int i = 1; i < k; i++) {
            C[i] = C[i] + C[i - 1];
        }
        Utils.print(C);
        // 整理
        for (int j = A.length - 1; j >= 0; j--) {
            int a = A[j];
            B[C[a] - 1] = a;
            C[a] -= 1;
        }
    }
}
//针对c数组的大小,优化过的计数排序
public class CountSort{
	public static void main(String []args){
		//排序的数组
		int a[] = {100, 93, 97, 92, 96, 99, 92, 89, 93, 97, 90, 94, 92, 95};
		int b[] = countSort(a);
		for(int i : b){
			System.out.print(i + "  ");
		}
		System.out.println();
	}
	public static int[] countSort(int []a){
		int b[] = new int[a.length];
		int max = a[0], min = a[0];
		for(int i : a){
			if(i > max){
				max = i;
			}
			if(i < min){
				min = i;
			}
		}
		//这里k的大小是要排序的数组中,元素大小的极值差+1
		int k = max - min + 1;
		int c[] = new int[k];
		for(int i = 0; i < a.length; ++i){
			c[a[i]-min] += 1;//优化过的地方,减小了数组c的大小
		}
		for(int i = 1; i < c.length; ++i){
			c[i] = c[i] + c[i-1];
		}
		for(int i = a.length-1; i >= 0; --i){
			b[--c[a[i]-min]] = a[i];//按存取的方式取出c的元素
		}
		return b;
	}
}

九、桶排序(Bucket Sort)

也叫箱排序,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θn))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

桶排序以下列程序进行:

  1. 设置一个定量的数组当作空桶子。
  2. 寻访串行,并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的串行中。

伪代码:

function bucket-sort(array, n) is
  buckets ← new array of n empty lists
  for i = 0 to (length(array)-1) do
    insert array[i] into buckets[msbits(array[i], k)]
  for i = 0 to n - 1 do
    next-sort(buckets[i])
  return the concatenation of buckets[0], ..., buckets[n-1]


十、基数排序(Radix Sort)

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

基数排序的时间复杂度是 O(k·n),其中n是排序元素个数,k是数字位数。注意这不是说这个时间复杂度一定优于O(n·log(n)),因为k的大小一般会受到 n 的影响。 以排序n个不同整数来举例,假定这些整数以B为底,这样每位数都有B个不同的数字,k就一定不小于logB(n)。由于有B个不同的数字,所以就需要B个不同的桶,在每一轮比较的时候都需要平均n·log2(B) 次比较来把整数放到合适的桶中去,所以就有:

  • k 大于或等于 logB(n)
  • 每一轮(平均)需要 n·log2(B) 次比较

所以,基数排序的平均时间T就是:

T ≥ logB(n)·n·log2(B) = log2(n)·logB(2)·n·log2(B) = log2(n)·n·logB(2)·log2(B) =n·log2(n)

所以和比较排序相似,基数排序需要的比较次数:T ≥ n·log2(n)。 故其时间复杂度为Ω(n·log2(n)) = Ω(n·log n) 。


九、十的Java实现代码待补充.


附加几道题测试一下对各种排序算法的理解吧!


判断题

1.快速排序是在所有情况下,排序速度最快。(0 )

2.给定一个关键字序列{24,19,32,43,38,6,13,22},堆排序辅助空间最少,最坏情况下快速排序时间复杂度最差。(1 )

3.堆排序优点是在时间性能与树形选择排序属同一量级的同时,堆排序只需要一个记录大小供交换用的辅助空间,调堆时子女只和双亲比较。避免了过多的辅助存储空间及和最大值的比较。(1 )

4.对于n个元素组成的顺序表进行快速排序时,所需进行的比较次数与这n个元素的初始排序有关。问:当n=7时,在最好情况下需进行10次比较。 以下选项正确的是( 1)。

5.在执行某种排序算法的过程中出现了排序码朝着最终排序序列相反的方向移动,从而认为该排序算法是不稳定的。 以下选项正确的是( 0)。

    6.冒泡排序算法关键字比较的次数与记录的初始排列次序无关。(0 )

    7.在待排序的记录集中,存在多个具有相同键值的记录,若经过排序,这些记录的相对次序仍然保持不变,称这种排序为稳定排序。(1 )

    8.对于n个元素组成的顺序表进行快速排序时,所需进行的比较次数与这n个元素的初始序列有关。则当n=7时,在最好情况下快速排序的原始序列实例之一为:4,1,3,2,6,5,7。 以下选项正确的是( 1)。

    9.在快速排序算法中,不可以用队列替代栈。( 0)

    10.对于n个记录的集合进行冒泡排序,在最坏情况下所需要的时间是O(n2)。( 1)

    11.在具有n个元素的集合中找第k(1≤k≤n)个最小元素,应使用快速排序方法。(1 )

    12.对冒泡算法而言,初始序列为反序时交换次数最少。若要求从大到小排序,则表现为初始是上升序。(0 )


    单选

    1.设输入的关键字满足k1>k2>…>kn,缓冲区大小为m,用置换-选择排序方法可产生( )个初始归并段。 

      • A、(1)
      • B、(2)
      • C、(3)
      • D、(4)

    2.为实现快速排序算法,待排序序列宜采用的存储方式是( )。

      • A、顺序存储
      • B、散列存储
      • C、链式存储
      • D、索引存储

    3.对一组数据(2,12,16,88,5,10)进行排序,若前三趟排序结果如下: 第一趟:2,12,16,5,10,88 第二趟:2,12,5,10,16,88 第三趟:2,5,10,12,16,88 则采用的排序方法可能是( )。

      • A、起泡排序
      • B、希尔排序
      • C、归并排序
      • D、基数排序

    4.设有以下四种排序方法,则( )的空间复杂度最大。

      • A、冒泡排序
      • B、快速排序
      • C、堆排序
      • D、希尔排序


    5.排序方法有许多种,( )法从未排序的序列中挑选元素,并将其依次放入已排序序列(初始时为空)的一端; 交换排序方法是对序列中的元素进行一系列比较,当被比较的两元素逆序时,进行交换;

      • A、选择排序
      • B、快速排序
      • C、插入排序
      • D、起泡排序

    6.下列内部排序算法中: a.快速排序 b.直接插入排序 c. 二路归并排序 d. 简单选择排序 e. 起泡排序 f. 堆排序 在初始序列已基本有序(除去n个元素中的某k个元素后即呈有序,k<<n)的情况下,排序效率最高的算法是( )。

      • A、a
      • B、b
      • C、c
      • D、d

    • 0
      点赞
    • 1
      评论
    • 3
      收藏
    • 一键三连
      一键三连
    • 扫一扫,分享海报

    ©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

    余额充值