排序的基本概念
算法优劣的描述
对于一个排序算法一般从以下三个方面来评估算法的优劣:
- 时间复杂度:主要分析关键字的比较次数和记录的移动次数
- 空间复杂度:分析排序算法所需要的额外内存
- 稳定性:若两个记录的关键字的值相等,且排序后二者的相对次序没有发生变化,则次排序算法是稳定的,否则就不是稳定的。
内排和外排
内部排序和外部排序:整个过程不需要借助外部存储器,均在内存中完成,成为内部排序,否则成为外部排序。
外部排序需要将带排序的数据一部分一部分的读到内存,然后按照内部排序的方法,对该部分进行排,并将拍好的数据输出到外部存储器,不断重复这一过程,直到所有记录被处理;
将上一步排好序的记录一组一组的进行归并排序。
内排的分类
选择排序算法
选择排序算法是最简单的一种排序算法:首先选出数组中最小的那个 元素,然后和第一个元素进行交换(如果第一个元素就是最小的,那么和自己交换);然后在余下的元素中选出最小的元素和第二个元素交换,如此往复,直到所有的元素拍完序。因为这种方法在不断地选择剩余元素中最小的元素,因此叫做选择排序。
public class SelectSort {
public static void selectSort(DataWrap[] data) {
int N = data.length;// 数组长度
for (int i = 0; i < N; i++) {
int min = i;// 假定i为数组中的最小值
for (int j = i + 1; j < N; j++) {
// 如果第i位置的元素大于第j位置的元素,交换他们
if (data[min].compareTo(data[j]) > 0) {
min = j;
}
}
swap(data, i, min);
}
}
// 交换两个元素
private static void swap(DataWrap[] data, int i, int j) {
DataWrap temp = data[i];
data[i] = data[j];
data[j] = temp;
}
public static void main(String[] args) {
DataWrap[] data = { new DataWrap(21, " "), new DataWrap(49, " "),
new DataWrap(30, " "), new DataWrap(16, " "),
new DataWrap(30, "*"), new DataWrap(9, " ") };
System.out.println("排序之前:\n"+java.util.Arrays.toString(data));
selectSort(data);
System.out.println("排序之后:\n"+java.util.Arrays.toString(data));
}
}
待排序的数据如下:
public class DataWrap implements Comparable<DataWrap>{
int data;
String flag;
public DataWrap(int data,String flag){
this.data = data;
this.flag = flag;
}
public String toString(){
return data + flag;
}
//根据data的大小决定DataWrap的大小
public int compareTo(DataWrap o) {
return this.data > o.data ? 1 : (this.data == o.data ? 0 : -1);
}
}
对于长度为N的数组,选择排序大约需要N*N/2次比较和N次交换。
选择排序有以下三个鲜明的特点:
- 运行时间和输入无关:上一次的循环对于本次的选择没有影响。其他算法更善于利用数据输入的初始状态。
- 数据移动是最小的:每次循环都会改变两个数组元素的值,因此只是用了N次交换。
- 选择排序是不稳定的。
堆排序算法
假设有n个元素的序列k1,k2,k3……kn-1,当且仅当满足如下 关系时成为小顶堆(小根堆),ki<=k2i+1且ki<=k2i+2(其中i=0,1,2,…,(n-1)/2);
当且仅当满足如下 关系时成为大顶堆(大根堆),ki>=k2i+1且ki>=k2i+2(其中i=0,1,2,…,(n-1)/2).
建堆的过程如下:
4. 先将数据转换为完全二叉树;
5. 将完全二叉树的最后一个非叶子节点,也即最后一个节点的父节点与它的左右子节点(如果存在)比较,将三者的最大值设为父节点;
6. 重复步骤二,向前处理上一个节点;
7. 如果某个节点交换后该节点又有子节点,那么还需要比较其子节点;
堆排序的步骤如下:
- 建堆;
- 将堆顶元素与最后一个元素交换。
public class HeapSort {
public static void heapSort(DataWrap[] data) {
int N = data.length;
// 循环建堆
for (int i = 0; i < N; i++) {
buildMaxHeap(data, N - 1 - i);
// 交换堆顶元素和最后一个元素
swap(data, 0, N - 1 - i);
System.out.println(java.util.Arrays.toString(data));
}
}
// 对数组建堆
private static void buildMaxHeap(DataWrap[] data, int lastIndex) {
// 从最后一个节点的父节点开始
for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
// k保存当前正在判断的节点
int k = i;
// 如果当前k节点的子节点存在
while (k * 2 + 1 <= lastIndex) {
// k节点的左子节点索引
int bigger = k * 2 + 1;
// 如果右子节点比较大
if(bigger < lastIndex){
if (data[bigger].compareTo(data[bigger + 1]) < 0) {
bigger = bigger + 1;
}
}
// 如果k节点的值小于其较大子节点的值
if (data[k].compareTo(data[bigger]) < 0) {
swap(data, k, bigger);
k = bigger;
} else {
break;
}
}
}
}
// 交换两个元素
private static void swap(DataWrap[] data, int i, int j) {
DataWrap temp = data[i];
data[i] = data[j];
data[j] = temp;
}
public static void main(String[] args) {
DataWrap[] data = { new DataWrap(21, " "), new DataWrap(49, " "), new DataWrap(30, " "), new DataWrap(16, " "),
new DataWrap(30, "*"), new DataWrap(9, " ") };
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
heapSort(data);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}
堆排序有以下三个鲜明的特点:
- 对于具有n个元素的数组,需要进行n-1次建堆,建堆的时间复杂度为log2(n),每次建堆后需要进行一次交换,因此堆排序的时间复杂度为nlog2(n);
- 堆排序的空间利用率很高,为O(1);
- 堆排序是不稳定的。