排序是在开发过程中经常要面临的操作,很多复杂的问题往往能够通过将序列排序而变得更加简单。一个好的排序算法往往能节省大量的时间和空间资源。
由于待排序的记录数量不同,时的排序过程中使用到的存储器不同 ,可将排序方法分为两大类:内部排序,指的是待排序记录全部存放在计算机随机存储器中的排序过程;外部排序,指待排序记录数量非常大,一次不能全部放到内存中,需要对访问外存中的记录进行访问的排序过程。
内部排序
-
分类
按照内部排序的原则进行分类可分为:插入排序、交换排序、选择排序、归并排序和计数排序五大类。
按照排序过程中所需要的工作量来排序可分为:简单排序(时间复杂度为O(n^2))、先进的排序方法(时间复杂度O(nlogn))和基数排序(时间复杂度为O(d*n))。 -
插入排序
插入排序是一种最简单的排序方法,他的基本操作是将一个记录插入到一个有序的列表中,然后获得一个新的、有序的、记录数加一的新的列表。
//直接插入排序
void insertSort (int[] a) { //非递减排序
int n = a.length;
if (n < 2) return; //待排序的记录少于两个
for (int i = 1; i < n; i ++) { //a[0]为初始有序序列,进行n - 1次插入排序
int t = a[i]; //待插入元素
int j = 0;
for (j = i - 1; j >= 0 && a[j] > a[t]; j --) { //对于大于待插入元素的的记录右移
a[j + 1] = a[j];
}
a[j + 1] = t;
}
}
插入排序是在一个有序序列的基础上,查找插入记录的插入位置,然后在此位置上进行插入而实现的,倘若我们将查找插入位置的过程采用折半查找则可以演化出显得插入排序方式 ——折半插入。
//折半插入排序
void bInsertSort( int[] a) {
int n = a.length;
if (n < 2) return;
for (int i = 1; i < n; i ++) {
int left = 0, right = i - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (a[mid] < a[i]) left = mid + 1;
else right = mid - 1;
}
int t = a[i];
for (int j = i - 1; j >= right + 1; j --) {
a[j + 1] = a[j];
}
a[right + 1] = t;
}
}
折半插入减少了待插入记录与有序表中记录比较的次数,但是移动元素的次数并没有减少,所以时间复杂度仍旧为O(n^2)。为了减少记录移动的次数又提出了2路排序算法。
//2路插入排序
int[] insertSort2(int[] a) {
int n = a.length;
if (n < 2) return a;
int[] b = new int[n];
b[0] = a[0];
int first = 0;
int last = 0;
for (int i = 1; i < n; i ++) {
if (a[i] >= b[0]) {
int j = 0;
for (j = last; a[i] < b[j]; j --) b[j + 1] = b[j];
b[j + 1] = a[i];
last ++;
} else {
if (first == 0) {
b[n - 1] = a[i];
first = n - 1;
} else {
int j = 0;
for (j = first; b[j] < a[i]; j ++) b[j - 1] = b[j];
b[j - 1] = a[i];
first --;
}
}
}
return b;
}
希尔排序又称缩小增量排序,它也属于插入排序类的方法,但是在时间效率上大有改进。其时间复杂度与所取得增量序列有关,计算方式较为复杂,由于涉及到数学上一些尚未解决的问题,所以没有计算方法。
希尔排序的基本思想是将整个待排序列划分为多个子序列,然后对每个子序列进行直接排序,根据增量序列进行多次排序后,整个序列便会变得基本有序,这样一来再使用直接插入排序将变得更加简单。一般选择d[k] = 2^(t - k + 1)作为增量序列(有人指出按照此序列排序的时间复杂的为O(n^(3/2))),t 为排序趟数。
//希尔排序
void sellSort(int a[],int[] d) {
int n = a.length;
int m = d.length;
if(n < 2 || m < 1) return;
for (int i = 0; i < m; i ++) { //对a进行m趟希尔排序
insertSort(a,d[i]);
}
}
/*
* 一趟希尔排序
* */
void insertSort (int[] a,int d) {
int n = a.length;
for (int i = d; i < n;i ++) {
if (a[i] < a[i - d]) {
int t = a[i];
int j = i - d;
while (j >= 0 && t < a[j]) {
a[j + d] = a[j];
j -= d;
}
a[j + d] = t;
}
}
}
- 交换排序
冒泡排序
大的向下下沉。
//冒泡排序
void guLuGuLuSort(int[] a) {
int n = a.length;
if (n < 2) return;
for (int i = 0; i < n - 1; i ++) {
for (int j = 0; j < n - 1 - i; j ++) {
if (a[j] > a[j + 1]) {
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
}
快速排序(O(n*lgn))
//递归实现
void quickSort(int[] a,int l,int r) {
if (l >= r) return;
int i = l,j = r;
int mid = a[l];
while (l < r) {
while (l < r && a[r] >= mid) r --;
a[l] = a[r];
while (l < r && a[l] <= mid) l ++;
a[r] = a[l];
}
a[l] = mid;
quickSort(a,i,l - 1);
quickSort(a,l + 1,r);
}
-
选择排序
简单选择排
就是选出n个元素中的最小值放到第一个位置,选出n - 1个元素的最小值放到第二个位置…选出n - i个元素的最小值放到第i + 1个位置。
//简单选择排序
void selectSort(int[] a) {
int n = a.length;
if (n < 2) return;
for(int i = 0; i < n; i ++) {
int min = a[i];
int p = i;
for (int j = i;j < n; j++) {
if (a[j] < a[i]) {
min = a[j];
p = j;
}
}
a[p] = a[i];
a[i] = min;
}
}
- 归并排序
归并排序是建立在两个有序了列表的基础上进行的链表的合并。排序开始我们将每一个元素都看作做一个有序列表,这样一来就可以两两合并为多个有两个元素的有序列表。然后再把这有两个元素的有序列表两两合并,组成多个有四个元素的有序列表… 以此类推直到合成一个有n个元素的有序列表。
时间复杂度O(n*lgn),空间复杂度O(n)
public void mergeSort (int[] a, int l,int t) {
if (l == t) return;
int m = (l + t) / 2;
mergeSort(a,l,m);
mergeSort(a,m + 1,t);
merge(a,l,m,t);
}
public void merge (int[] a, int l, int m, int t) {
int n = a.length;
int[] b = new int[n];
int i = l, j = m + 1;
int k = l;
while (i <= m && j <= t) {
if (a[i] <= a[j]) {
b[k ++] = a[i ++];
} else {
b[k ++] = a[j ++];
}
}
while (i <= m) {
b[k ++] = a[i ++];
}
while (j <= t) {
b[k++] = a[j++];
}
i = l;
while (i <= t) {
a[i] = b[i ++];
}
}
- 基数排序
/*
* 假设待排对象的元素为正整数
* */
public void sort(int[] a) {
if (a.length < 2) return;
Note head = new Note();
Note p = head;
int max = Integer.MIN_VALUE;
for (int i = 0; i < a.length; i ++) { //将数组转换链表,顺便找一下最大值
if (a[i] > max) {
max = a[i];
}
Note note = new Note();
note.data = a[i];
note.next = null;
p.next = note;
p = p.next;
}
int i = 1;
while (max != 0) { //进行多次分发和收集
head = FAC(head, i);
max /= 10;
i ++;
}
p = head.next;
i = 0;
while (p != null) { //放回到数组
a[i ++] = p.data;
p = p.next;
}
}
/*
* 参数l表示关键字的位置,如l = 1则表示个位为此次分发和收集的关键字
* */
public Note FAC(Note head,int l) {
Note[] h = new Note[10]; //十个分发链的头
Note p = head.next;
while (p != null) { //分发
int k = (p.data / (int)Math.pow(10,l -1)) % 10;
if (h[k] == null) {
h[k] = new Note();
}
Note q = h[k];
while (q.next != null) {
q = q.next;
}
q.next = p;
p = p.next;
q.next.next = null;
}
Note newHead = new Note();
for (int i = 0; i< 10; i++) { //合并
if (h[i] != null) {
Note q = newHead;
while (q.next != null) {
q = q.next;
}
q.next = h[i].next;
}
}
return newHead;
}
class Note {
int data;
Note next;
}