7. 内排序
7.1 排序的基本概念
1. 排序的定义
所谓排序,简单地说就是将一组“无序”的记录序列调整为“有序”的举例序列的一种操作。通常待排序的记录有多个数据项,把用于作为排序依据的数据项称为关键字。例如,学生成绩表由学号、姓名和各科成绩等数据项组成,这些数据项都可作为关键字来进行排序。
2. 排序的分类
1)内部排序和外部排序
按照排序过程中所涉及的存储器的不同可分为内部排序与外部排序。内部排序是指待排序序列完全存放在内存中进行的排序过程,这种方法适合数量不太大的数据元素的排序。外部排序是指待排序的数据元素非常多,以至于它们必须存储在外部存储器上,这种排序过程中需要访问外存储器,这样的排序称为外排序。
2)稳定排序与不稳定排序
若对任意一组数据元素序列,使用某种排序算法对它进行按照关键字的排序,若相同关键字间的前后位置关系在排序前与排序后保持一致,则称此排序方法是稳定的,而不能保持一致的排序方法则称为不稳定的。例如,一组关键字序列{3,4,2,3,1},若经过排序后变为{1,2,3,3,4},则此排序方法是稳定的;若经过排序后变为{1,2,3,3,4},则此排序方法是不稳定的。
3. 内排序的方法
内部排序的过程是一个逐步扩大记录的优先序列长度的过程。基于不同的“扩大”有序序列长度的方法,内部排序方法大致可分为以下几种类型:插入类、交换类、选择类、归并类和其他类。
1)插入类排序方法是指将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。
2)交换类排序方法是指通过“交换”无序序列中的记录,从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
3)选择类排序方法是指从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
4)归并类排序方法是指通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。
4. 排序算法的性能评价
排序算法有很多种,在众多的排序算法中,简单地评价哪一种算法是好的很困难。通常认为某种算法是用于某些情况,而这种算法在另外情况下性能也就不如其他算法。评价排序算法好换的标准主要有两条:算法的时间复杂度和空间复杂度。
5. 待排序记录的类描述
内部排序方法可在不同的存储结构上实现,但待排序的数据元素集合通常以线性表为主,因此存储结构多选用顺序表和链表。此外,顺序表又具有随机存取的特性,因此这儿介绍的排序算法都是针对顺序表进行操作的。
待排序的顺序表记录类描述如下:
package chapter7;
public class RecordNode {
public KeyType key;// 关键字
public ElementType element;// 数据类型
public RecordNode(KeyType key) {
this.key = key;
}
public RecordNode(KeyType key, ElementType element) {
this.key = key;
this.element = element;
}
}
顺序表记录关键字类
package chapter7;
/**
* 顺序表记录关键字类
*
* @author Oner.wv
*
*/
public class KeyType implements Comparable<KeyType> {
public int key;// 关键字
public KeyType() {
}
public KeyType(int key) {
this.key = key;
}
@Override
public String toString() {// 覆盖toString方法
return key + " ";
}
@Override
public int compareTo(KeyType another) {// 覆盖Comparable接口中比较关键字大小的方法
int thisVal = this.key;
int anotherVal = another.key;
return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));
}
}
顺序表记录节点类
package chapter7;
/**
* 顺序表记录节点类
*
* @author Oner.wv
*
*/
public class ElementType {
public String data;// 用户可自定义其他数据类型
public ElementType() {
}
public ElementType(String data) {
this.data = data;
}
public String toString() {
return data;
}
}
待排序的顺序表类描述如下:
package chapter7;
public class SeqList {
public RecordNode[] r;// 顺序表记录节点数组
public int curlen;// 顺序表长度
// 顺序表的构造方法,构造一个存储空间为maxSize个存储单元
public SeqList(int maxSize) {
this.r = new RecordNode[maxSize];// 为顺序表分配maxSize个存储单元
int curlen = 0;// 置顺序表的当前长度为0
}
// 在当前顺序表的第i个结点之前插入一个RecordNode类型的节点x
public void insert(int i, RecordNode x) throws Exception {
if (curlen == r.length) {// 判断顺序表是否已满
throw new Exception("顺序表已满");
}
if (i < 0 || i > curlen) {
throw new Exception("插入位置不合理");
}
for (int j = curlen; j > i; j--) {
r[i] = r[i - 1];// 插入位置及以后的数据元素后移
}
r[i] = x;// 插入x
this.curlen++;// 表长度加1
}
}
7.2 插入排序
这儿介绍两种插入排序方法:直接插入排序和希尔排序
7.2.1 直接插入排序
直接插入排序(Straight Insertion Sort)是一种简单地排序方法,其基本思想先将原序列分为有序区和无序区,然后再经过比较和后移操作将无序区元素插入到有序区中。
看下面的一个例子:
下标: 0 1 2 3 4
初始关键字:[52] 39 67 70 52
i=1:[39 52] 67 70 52
i=2:[39 52 67] 70 52
i=3:[39 52 67 70] 52
i=4:[39 52 52 67 70]
直接插入排序算法的主要步骤归纳如下:
1)将r[i]暂存在临时变量temp中。
2)将temp与r[j](j=i-1,i-2,...,0)依次比较,若temp.key<r[j].key,则将r[j]后移一个位置,知道temp.key>=r[j].key为止(此时j+1即为r[i]插入位置)。
3)将temp插入到第j+1个位置上。
4)令i=1,2,3,...,n