排序算法一般都会有几个较为基础通用的方法,如下表
方法名 | 作用 |
---|---|
isSorted() | 判断数组是否有序 |
less(int item1, int item2) | 比较第一个参数是否小于等于第二个参数 |
exch(int a, int b) | 交换这两个索引位置的元素 |
show() | 打印数组 |
sort() | 具体待实现的排序算法 |
具体参考如下
public abstract class Sort {
int[] mList;
public Sort(int[] list) {
this.mList = list;
}
public boolean isSorted() {
if (mList.length > 1) {
for (int i = 1; i < mList.length; i++)
assert less(mList[i - 1], mList[i]);
}
return true;
}
public boolean less(int item1, int item2) {
return item1 <= item2;
}
public void exch(int a, int b) {
if (a != b) {
int item = mList[a];
mList[a] = mList[b];
mList[b] = item;
}
}
public void show() {
for (Integer item : mList)
System.out.print(item + " ");
}
public abstract void sort();
}
之前是用泛型来做的,后考虑学习算法主要的是理解逻辑,所以使用简单的int简单而且更直观
1 选择排序
主要过程是:首先找到数组中最小的那个元素,其次,将它和数组中低一个元素交换位置(若最小的元素是自己,则与自己交换一次)。然后在剩下的元素中依次找出最小的元素,与第二个位置的元素做交换。以此类推,直到排序到最后一个元素,则完成整个数组的排序。
最大的特点是,运行时间与输入无关,且数据移动是最少的。对于长度是N的数组,选择排序需要大约N²/2次比较和N次交换。
class ChoiceShort extends Sort {
public ChoiceShort(int[] list) {
super(list);
}
@Override
public void sort() {
for (int i = 0; i < mList.length; i++) {
//最小元素的索引位置
int min = i;
for (int r = i + 1; r < mList.length; r++) {
//若最小元素比当前元素大了,则更新最小元素的索引
if (!less(mList[min], mList[r])) min = r;
}
exch(i, min);
}
}
}
2 插入排序
从第二个元素开始,依次从右往左比较,遇到比当前元素大的则交换位置,反之则为已经插入到了当前元素对应的位置,然后选取下一个元素开始下一轮的比较。
class InsertShort extends Sort {
public InsertShort(int[] list) {
super(list);
}
@Override
public void sort() {
for (int i = 1; i < mList.length; i++) {
for (int r = i; r > 0 && !less(mList[r - 1], mList[r]); r--) {
//若当前位置元素比前一位置元素小,则交换这个两个位置的元素,反之则此次循环结束
exch(r, r - 1);
}
}
}
}
因为每个元素向前寻找对应的位置时都经历了多次比较以及交换,所以可以在内循环中先找到对应应该插入的位置,然后之间的元素都做一个向后移动即可。
@Override
public void sort() {
for (int i = 1; i < mList.length; i++) {
//记录当前需要插入的元素以及对应的位置
int item = mList[i];
int pos = i;
for (int r = i; r > 0 && !less(mList[r - 1], item); r--) {
//若当前元素是比需要插入的元素大的,则把当前元素向后移动,然后插入的位置改为当前元素的位置
mList[r] = mList[r - 1];
pos = r - 1;
}
//最后把item插入到对应的位置
mList[pos] = item;
}
}
插入排序适合情况:
- 数组中每个元素距离它的最终位置都不远
- 一个有序的大数组接一个小数组
- 数组中只有几个元素的位置不正确
当倒置的数量很少时,插入排序很可能是最快的算法,总的来说插入排序适合部分有序的数组,也很适合小规模的数组,往往用于高级排序中分为小规模部分的排序。
3 希尔排序
因为在大规模乱序数组的插入排序很慢,因为它只会交换相邻的元素,所以希尔排序是在这个基础上进行的改进,使数组中任意间隔为h的元素都是有序的。意思为h个间隔为h的有序数组编制在一起组成的一个数组,进行排序时移动的距离和h的大小有关,也为更小的h有序创造方便。希尔排序更高效的原因是因为它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序只会子数组都是部分有序的,这两种情况都很适合插入排序。子数组部分有序的程度取决于递增序列的选择,透彻理解希尔排序的性能在今天也是一项挑战。
class ShellShort extends Sort {
public ShellShort(int[] list) {
super(list);
}
@Override
public void sort() {
int h = 0;
int N = mList.length;
while (h <= N / 3) h = 3 * h + 1;
//直到进行h为1的插入排序
while (h >= 1) {
for (int i = h; i < N; i++) {
//从h的位置开始,依次往后进行以h为间距的向前插入排序
for (int r = i; r >= h && !less(mList[r - h], mList[r]); r -= h) {
exch(r - h, r);
}
}
h = h / 3;
}
}
}