排序
选择排序
思想
每次都找到数组中最小的那个元素,并依次与第一个,第二个直到最后一个元素交换位置;
不断地选择剩余元素中的最小者。
codes
public static void selectionSort(int[]a)
{
for(int i = 0; i < a.length;i++)
{
int min = i;
for(int j = i+1;j < a.length - 1;j++)
{
//找出最小值
if(a[j] < a[min])
min = j;
//交换
int temp = a[min];
a[min] = a[i];
a[i] = temp;
}
}
}
特点
- 运行时间和输入无关。
- 数据移动最少。
N次。
插入排序
思想
对于循环中的每一个元素a[i],将其与a[0]到a[i-1]中比它小的所有元素依次有序交换。
索引左侧的元素总是有序的。
codes
public static void InsertionSort(int[] a)
{
for(int i = 0; i < a.length;i++)
{
for(int j = i; j > 0;j--)
{
//判断
if(a[j] < a[j-1])
{
//交换
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
}
特点
- 所需时间取决于输入中元素的初始顺序。
- 对于部分有序的数组很有效,也适合小规模数组。
练习与补充
public static void InsertSort0(int[] a) {
//2.1.24 插入排序的哨兵 先找出最小元素放在最左边作为哨兵,这样可以去掉内循环的判断条件j > 0.
int N = a.length;
boolean isExchanged = false;
// 找出最小元素
for (int i = N - 1; i > 0; i--) {
if (a[i] < a[i-1]) {
int temp = a[i];
a[i] = a[i-1];
a[i-1] = temp;
isExchanged = true;
}
}
//若没变,则原本就是升序
if (!isExchanged) {
return;
}
for (int i = 2; i < N; i++) {
for (int j = i; ; j--) {
if(a[j] < a[j-1])
{
//交换
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
}
public static void InsertionSort1(int[] a)
//2.1.25 不需要交换的插入排序,使当前下标之前的较大元素右移
//思想:若当前下标元素比它前面的元素大,符合升序规则, 则不需要插入;
//若比它前面的元素小,则找出该插入的位置,因为有一个位置的空闲,所以较大元素可以右移一位。
{
for(int i = 0; i < a.length;i++)
{
int temp = a[i];
int j = i;
for(; j > 0;j--)
{
//判断
if( temp < a[j-1])
{
a[j] = a[j-1];
}
a[j] = temp;
}
}
}
希尔排序
介绍
基于插入排序;
对于大规模乱序数组,为了加快速度,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
思想
使数组中任意间隔为h的元素都是有序的。
即h个互相独立的有序数组编织在一起组成最终的有序数组。
使用增量序列1/2(3^k - 1),从N/3开始递减至1
方法
在对希尔排序理解时,我们倾向于对于每一个分组,逐组进行处理。
更简单的实现是在h子数组中将每个元素交换到比它大的元素之前去(将比它大的元素向右移动一格)。
codes
public static void shellSort(int[] a)
{
int N = a.length;
int h = 1;
while(h < N/3 ) h = 3*h + 1;//1 4 13 40 121 364……
while(h >= 1)
{
//将数组变为h有序
for(int i = h;i < N;i++)
{
for(int j = i; j>=h;j-=h)
{
if(a[j] < a[j-h])
{
//交换
int temp = a[j];
a[j] = a[j-1];
a[j-h] = temp;
}
}
h = h/3;
}
}
}
//简洁的希尔排序:在插入排序中加入一个外循环将h按照递增序列递减
特点
- 希尔排序可以用于大型数组
- 运行时间达不到平方级别
归并排序
思想
递归;
若要将一个数组排序,可以先递归的将它分成两半分别排序,然后将结果归并起来。
原地归并:
将两个子数组归并成一个有序的数组并将结果存放在原数组。
自顶向下的归并排序:
分治;
递归归并
自底向上的归并排序:
先归并微型数组,再成对归并得到的子数组,直到将整个数组归并到一起。
即两两归并、四四归并、八八归并……
codes
//自顶向下
public class mergeSort {
private static int[] aux;//归并所需的辅助数组
public static void sort(int[] a)
{
aux = new int[a.length];
sort(a,0,a.length - 1);
}
private static void sort(int[] a,int lo, int hi)
{
//将数组排序
if(hi <= lo) return;
int mid = lo + (hi - lo)/2;
sort(a,lo,mid);
sort(a,mid+1,hi);
merge(a,lo,mid,hi);
}
public static void merge(int[] a,int lo, int mid, int hi)
{
int i = lo, j = mid + 1;
for(int k = lo; k <= hi;k++)
aux[k] = a[k];
for(int k = lo;k <= hi;k++)
{
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
}
调用轨迹
//自底向上
public class mergeSortBU {
private static int[] aux;
public static void mergeSortBU(int[] a)
{
aux = new int[a.length];
for(int size = 1;size < a.length;size = size+size)
for(int lo = 0;lo < a.length - size; lo += size+size)
merge(a,lo,lo+size-1,Math.min(lo+size+size-1, a.length-1));
}
public static void merge(int[] a,int lo, int mid, int hi)
{
int i = lo, j = mid + 1;
for(int k = lo; k <= hi;k++)
aux[k] = a[k];
for(int k = lo;k <= hi;k++)
{
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
}
练习与补充
//2.2.10 去掉内循环中检测某半边是否用尽的代码(结果不稳定)
private static void merge(Comparable[] a, int lo, int mid, int hi) {
for (int i = lo; i <= mid; i++)
aux[i] = a[i];
for (int j = mid+1; j <= hi; j++)
aux[j] = a[hi-j+mid+1];
int i = lo, j = hi;
for (int k = lo; k <= hi; k++)
if (less(aux[j], aux[i])) a[k] = aux[j--];
else a[k] = aux[i++];
}
快速排序
思想
分治:将一个数组分为两个子数组,将两部分独立地排序。
当两个子数组有序时,整个数组也就有序了。
快速排序递归的将子数组排序,先切分,排定一个元素,然后再递归调用将其他位置的元素排序。
切分:排定一个元素,使得在该数组中,这个元素之前的元素都不大于它,之后的元素都不小于它。
切分方法的实现;
codes
public class quickSort {
public static void sort(int[] a)
{
sort(a,0,a.length-1);
}
private static void sort(int[] a,int lo, int hi)
{
if(hi <= lo) return;
int j = partition(a,lo,hi);
sort(a,lo,j-1);
sort(a,j+1,hi);
}
public static int partition(int[] a,int lo,int hi)
{
//将数字切分为a[lo……i-1],a[i],a[i+1..hi]
int i = lo, j = hi+1;
int v = a[lo];//切分元素
while(i < j)
{
while(a[++i] < v) if(i == hi) break;
while(v > a[--j]) if(j == lo) break;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
int temp = a[lo]; //v=a[j]放入正确位置
a[lo] = a[j];
a[j] = temp;
return j;
}
}
改进
- 前提:多次执行快排或用于大型数组
1.排序小数组时,切换到插入排序
将
if (hi <= lo) return;
替换为
if (hi <= lo + M) {Insertion.sort(a,lo,hi); return;}
2.使用数组的一小部分元素的中位数来切分数组,取样大小为3效果最好。
优先队列
思想
删除最大元素和插入元素
使用(二叉)堆实现
当一棵二叉树的每个结点都大于等于它的两个子结点时,被称为堆有序
二叉堆是一组能够用堆有序的完全二叉树排序的元素。
位置k的结点的父结点的位置为k/2下取整,子结点为2k,2k+1
//插入新元素时加到数组末尾,增加堆的大小并上浮;
//删除最大元素时从数组顶端删除最大的元素,并把最后一个元素放到顶端,减小堆的大小并下沉;
堆排序
思想
分为两个阶段:
1.在堆的构造阶段中,将原始数组安排进一个堆中
2.在下沉排序阶段,从堆中按递减顺序取出所有元素并得到排序结果
从右至左用sink函数构造子堆。
codes
public static void sort(int[] a)
{
int N = A.length;
for(int k = N/2;k >=1;k--) //构造堆
sink(a,k,n);
while(N > 1) //将最大的元素a[1]和a[n]交换,并修复堆
{
exch(a,1,N--);
sink(a,1,N);
}
}