本章重点:
如何排序?
计算机程序却不能像人这样通览所有的数据。它只能根据计算机的“比较”操作原理,在同一时间内对两个队员进行比较。算法的这种“管视”将是一个反复出现的问题。在人类看来很简单的事情,计算机的算法却不能看到全景,因此它只能一步一步地解决具体问题和遵循一些简单的规则。
这一章的三个算法都包括如下的两个步骤,这两步循环执行,直到全部数据有序为止:
1.比较两个数据项。
2.交换两个数据项,或复制其中一项。
但是,每种算法具体实现的细节有所不同。
冒泡排序
冒泡排序算法运行起来非常慢,但在概念上它是排序算法中最简单的,因此冒泡排序算法在刚开始研究排序技术时是一个非常好的算法。
冒泡排序的Java代码
在更复杂的程序中,数据很可能由对象组成在,这里为了简单起见,使用的是基本数据类型。
//bubbleSort.java class ArrayBub{ private long[] a; //ref to array a private int nElems; //number of data items //-------------------------------------------------------- public ArrayBub(int max){ //constructor a = new long[max]; //create the array nElems = 0; //no items yet } //-------------------------------------------------------- public void insert(long value){ //put element into array a[nElems] = value; nElems++ } //-------------------------------------------------------- public void display(){ //displays array contents for(int j=0;j
a[in+1]){ swap(in,in+1); } } } } //-------------------------------------------------------- private void swap(int one,int two){ long temp = a[one]; a[one] = a[two]; a[two] = temp; } } class BubbleSortApp{ public static void main(String[] args){ int maxSize = 100; ArrayBub arr; arr = new ArrayBub(maxSize); arr.insert(77); arr.insert(99); arr.insert(44); arr.insert(55); arr.display(); arr.bubbleSort(); arr.display(); } } 这个类的构造函数,以及insert()和display()方法与见过的那些方法是类似的,但是这里多了一个新方法:bubbleSort()。当main()调用这个方法时,数组中的数据就会排列成有序。
不变性
在许多算法中,有些条件在算法执行时是不变的。这些条件被称为不变性。认识不变性对理解算法是有用的。在一定的情况下它们对调试也有用;可以反复地检查不变性是否为真,如果不是的话就标记出错。
在bubbleSort.java程序中,不变性是指out右边的所有数据项为有序。在算法的整个运行过程中这个条件始终为真。(在第一趟排序开始前,尚未排序,因为out开始时再数据项的最右边,没有数据项在out的右边。)
冒泡排序的效率
比较的次数:(N-1)+(N-2)+(N-3)+(N-4)+(N-5)+(N-6)+(N-7)+(N-8)+(N-9)+...+1=N*(N-1)/2,交换的次数少于比较的次数。如果数据是随机的,那么大概有一半数据需要交换,则交换的次数为N2/4。
交换和比较操作次数都和N2成正比。由于常数不算在大O表示法中,可以忽略2和4,并且认为冒泡排序运行需要O(N2)时间级别。这种排序算的速度是很慢的。
无论何时,只要看到一个循环嵌套在另一个循环里,例如在冒泡排序和本章中的其他排序算法中,就可以怀疑这个算法的运行时间为O(N2)级。外层循环执行N次,内部循环对于每一次外层循环都执行N次(或者几分之N次)。这就意味着将大约需要执行N*N某个基本操作。
选择排序
选择排序改进了冒泡排序,将必要的交换次数从O(N2)减少到O(N)。不幸的是比较次数仍然保持为O(N2)。然而,选择排序仍然为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。
class ArraySel{ private long[] a; private int nElems; //----------------------------------------------------- public ArraySel(int max){ //constructor a = new long[max]; nElems = 0; } //----------------------------------------------------- public void insert(long value){ //put element into array a[nElems] = value; //insert it nElems++; //increment size } //----------------------------------------------------- public void display(){ //displays array contents for(int j=0;j
不变性:
在selectSort.java程序中,下标小于或等于out的位置的数据项总是有序的。
选择排序的效率:
选择排序和冒泡排序执行了相同次数的比较:N*(N-1)/2。对于10个数据项,需要45次比较。然而,10个数据项只需要小于10次交换。对于100个数据项,需要4950次比较,但是交换就会大大降低。与冒泡下相比,比较次数运行了O(N2)时间,但是交换次数大大降低,无疑,选择排序更快。
插入排序
在大多数情况下,插入排序算法是本章描述的基本的排序算法中最好的一种,虽然插入排序算法仍然需要O(N2)的时间,但是在一般情况下,它要比冒泡排序快一倍,比选择排序还要快一点。尽管它比冒泡排序算法和选择算法都更麻烦,但它也并不复杂。它经常被用在较复杂的排序算法的最后阶段,例如快速排序。
package com.thinkofdatastruct.threeunit; /** * Created by happy on 2019/04/04 */ public class insertionSort { /* * 直接插入排序 * * 参数说明: * a -- 待排序的数组 * n -- 数组的长度 */ public static void insertSort(int[] a, int n) { int i, j, k; for (i = 1; i < n; i++) { //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置 for (j = i - 1; j >= 0; j--){ if (a[j] < a[i]) break; } //如找到了一个合适的位置 if (j != i - 1) { //将比a[i]大的数据向后移 int temp = a[i]; for (k = i - 1; k > j; k--){ a[k + 1] = a[k]; } //将a[i]放到正确位置上 a[k + 1] = temp; } } } public static void main(String[] args) { int[] a = {20,40,30,10,60,50}; insertSort(a, a.length); for (int i=0; i
插入排序中的不变性
这个算法需要多少次比较和复制呢?在第一趟排序中,它最多比较一次,第二趟最多比较两次,依次类推。最后一趟最多,比较N-1次。因此有
1+2+3+....+N-1 = N*(N-1)/2
然而,因为在每一趟排序发现插入点之前,平均只有全体数据项的一半真的进行了比较,我们除以2得到
N*(N-1)/4
复制的次数大致等于比较的次数。然而,一次复制与一次交换的时间耗费不同,所以相对于随机数据,这个算法比冒泡排序快一倍,比选择排序略快。
对于数据越有序,就越快,几乎接近O(N);
对象排序
为简单起见,前面应用排序算法时使用的是基本数据类型:long(长整型)。但是,排序例程却更多地应用于对象的排序,而不是对基本数据类型的排序。下面是对一组Person对象进行排序的代码。
class Person{ private String lastName; private String firstName; private int age; //----------------------------------------------------- public Person(String last,String first,int a){ //constructor lastName = last; firstName = first; age = a; } //----------------------------------------------------- public void displayPerson(){ System.out.print(" Last name: "+lastName); System.out.print(" ,First name: "+firstName); System.out.print(" ,Age: "+age); } //----------------------------------------------------- public String getLast(){ return lastName; } } class ArrayInOb{ private Person[] a; private int nElems; //----------------------------------------------------- public ArrayInOb(int max){ //constructor a = new Person[max]; nElems=0; } //----------------------------------------------------- public void insert(String last,String first,int age){ a[nElems] = new Person(last,first,age); nElems++; } //----------------------------------------------------- public void display(){ for(int i=0;i
=0;in--){ if(a[in].getLast.compareTo(a[out].getLast())>0){ break; } } if(out!=in-1){ Person temp = a[in]; for(k=in-1;k>out;k++){ a[k+1] = a[k]; } a[k+1] = temp; } } } } class ObjectSortApp{ public static void main(String[] args){ int maxSize = 100; ArrayInOb arr; arr = new ArrayInOb(maxSize); arr.insert("E","P",24); arr.insert("Ee","Pp",244); arr.insert("Eee","Ppp",144); arr.display(); arr.insertionSort(); } } 单词排序:
objectSort.java程序中的insertSort()方法和insertSort.java中的方法类似,只是不再比较基本数据类型,而是比较记录中的lastName(姓)属性。
用String类中的compareTo()方法执行insertSort()方法中的比较工作。compareTo()方法根据两个String的字典顺序(字母序)返回给调用者不同的整数值,这两个String一个是方法的调用者,一个是这个方法的参数。
简单排序的比较
冒泡排序算法一般情况几乎不太使用;
选择排序虽然把交换次数降到了最低,但比较的次数仍然很大。当数据量很小,并且交换数据相对于比较数据更加耗时的情况下,可以应用选择排序;
在大多数情况下,假设当数据量比较小或基本上有序时,插入排序算法是三种简单排序算法中最好的选择。对于更大数据量的排序来说,快速排序通常是最快的方法;
小结:
本章提到的排序算法都假定了数组作为数据存储结构;
排序包括比较数组中数据项的关键字和移动相应的数据项(实际上,是数据项的引用),直到它们排好序为止;
本章所有算法的时间复杂度都是O(N2)。不过,某些情况下某个算法可以比其它算法快很多;
不变性是指在算法运行时保持不变的条件;
冒泡排序算法是效率最差的算法,但它最简单;
插入排序算法是本章介绍的O(N2)排序算法中应用最多的;
如果具有相同关键字的数据项,经过排序它们的顺序保持不变,这样的排序就是稳定的;
本章介绍的所有排序算法除了需要初始数组之外,都只需要一个临时变量。
问题:
1.计算机排序算法与人类排序相比较,它的局限性是:计算机一次只能比较两件东西。
2.简单排序算法中的两个基本操作是 比较数据项和交换数据项。
3.冒泡排序算法总是在所有数据项两两比较完成后停止;错误
4.冒泡排序算法在比较和交换之间交替进行。
5.有N个数据项,冒泡排序算法精确操作了N*N次; 错误
6.选择排序中 b。
a.最大的关键字聚集到左边(较小的下标);b.最小的关键字被重复发现;
c.为了将每个数据项插入到正确排序的位置,很多数据项将被移动;d.有序的数据项聚集到右边。
7.在某一个特定的排序情况中,如果交换与比较相比费时得多,那么选择排序将比冒泡排序快大约一倍。错误
8.复制是交换得三倍;
9.选择排序中不变性是什么?
排完序的下标小于outer的值。
10.插入排序中,文中描述的“被标记的队员”对应于insertSort.java中的哪个变量? 答案是c a.in;b.out;c.temp;d.a[out]
11.在插入排序中,“局部有序”是指:一些数据项已经排好序了,但它们可能需要被移动。
12.向左或右移动一组数据项需要重复地 复制。
13.在插入排序中,一个数据项被插入到局部有序的组合后,它将:永远不会向左边移动。
14.插入排序中的不变性是 排完序的下标小于outer的下标。
15.稳定性是指 在对州进行排序时,每个州的城市还要求按人口递增有序。