1、插入排序
基本思想:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
1.1、直接插入排序
假设在排序过程中,待排序的表L[1···n]在某次排序的过程中的某一时刻状态如下:
有序序列L[1···i-1] | L(i) | 无序序列L[i+1···n] |
---|
为了实现将元素L(i)插入到已有序的子序列L[1···i-1]中,我们需要执行以下操作(为了避免混淆,下面用"L[]"表示一个表,而用"L()"表示一个元素):
1)查找出L(i)在L[1···i-1]中的插入位置k
2)将L[K···i-1]中所有元素全部后移一个位置。
3)将L(i)复制到L(k)。
public static int[] insertSort(int[] a) {
int i, j;
for (i = 1; i <= a.length-1; i++) {//依次将a[1]-a[n-1]插入到前面已排序序列
if (a[i] < a[i - 1]) {//若a[i]的值小于其前驱,需将a[i]插入有序表
int temp = a[i];//复制为哨兵
for (j = i - 1; j>=0&&temp < a[j]; j--)//从后往前查找待插入位置
a[j + 1] = a[j]; //向后挪位
a[j + 1] = temp; //复制到插入位置
}
}
return a;
}
空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
时间效率:在排序过程中,向有序子表中逐个地插入元素的操作进行了n-1趟,每趟操作都分为比较关键字和移动元素,而比较次数和移动次数取决于待排序表的初始状态。
在最好的情况下,表中元素已经有序,此时每插入一个元素,都只需比较一次而不用移动元素,因而时间复杂度为O(n)。
在最坏的情况下,表中元素顺序刚好与排序结果中元素顺序相反(逆序)时,总的比较次数达到最大,2+3+···+n,总的移动次数也达到最大,为3+4+···+n+1.
是稳定的算法
适用性:直接插入排序算法适用于顺序存储和链式存储的线性表。当为链式存储时,可以从前往后查找指定元素的位置。
1.2 折半插入排序
先折半查找出元素的待插入位置,然后在统一地移动待插入位置之后的所有元素。
public static int[] insertSort2(int[] a) {
int i, j, low, high, mid;
for (i = 1; i <= a.length - 1; i++) {//依次将a[1]-a[n-1]插入到前面已排序序列
int temp = a[i];//复制为哨兵
low = 0;
high = i - 1; //设置折半查找的范围
while (low <= high) {
mid = (low + high) / 2;//取中间点
if (a[mid] > temp) high = mid - 1;//查找左半子表
else low = mid + 1; //查找右半子表
}
for (j = i - 1; j >= high + 1; j--)
a[j + 1] = a[j]; //统一后移元素,空出插入位置
a[high + 1] = temp; //插入操作
}
return a;
}
折半插入排序仅仅是减少了比较元素的次数,约为O(nlog2n),该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n;而元素的移动次数没有改变,它依赖于待排序表的初始化状态。因此,折半排序的时间复杂度仍为O(n2)。
1.3 希尔排序
先将待排序表分为若干个形如L[i,i+d,i+2d,···i+kd]的子表,分别进行直接插入排序,当整个表中元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。
public static int[] shellSort(int[] array) {
if (array.length < 2) {
return array;
}
int gap = array.length / 2, current, pre;
while (gap > 0) {
for (int i = gap; i < array.length; i++) {
pre = i - gap;
current = array[i];
while (pre >= 0 && current < array[pre]) {
array[pre + gap] = array[pre];
pre -= gap;
}
array[pre + gap] = current;
}
gap /= 2;
}
return array;
}
是不稳定的算法
2、交换排序
2.1 冒泡排序
public static int[] bubleSort2(int[] array) {
boolean flag;
for(int i=0;i<array.length-1;i++){
flag = false;//表示本趟冒泡是否发生交换的标志
for(int j=array.length-1;j>i;j--){//一趟冒泡过程
if(array[j-1]>array[j]){//若为逆序
int temp = array[j];//交换
array[j] = array[j-1];
array[j-1]=temp;
flag=true;
}
}
if(flag==false) return array;//本趟遍历后没有发生交换,说明表已经有序
}
return array;
}
注意:冒泡排序中所产生的有序子序列一定是全局有序的(不同于直接插入排序),每一趟排序都会将一个元素放置到其最终的位置上。
2.2、快速排序
public static int[] quickSort(int[] a, int low, int high) {
if (low < high) {//递归跳出的条件
int pivotpos = Partition(a, low, high);
quickSort(a, low, pivotpos - 1);//依次对两个子表进行递归排序
quickSort(a, pivotpos + 1, high);
}
return a;
}
private static int Partition(int[] a, int low, int high) {
int pivot = a[low];//将当前表中第一个元素设为枢轴值,对表进行划分
while (low < high) {//循环跳出条件
while (low < high && a[high] > pivot) high--;
a[low] = a[high];//将比枢轴值小的元素移动到左端
while (low < high && a[low] < pivot) low++;
a[high] = a[low];//将比枢轴值大的元素移动到右端
}
a[low] = pivot;//枢轴元素存放到最终位置
return low;//返回存放枢轴的最终位置
}
3、选择排序
3.1、简单选择排序
public static int[] selectSort(int[] array) {
int minIndex;
for (int i = 0; i < array.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
3.2 堆排序
算法思想:首先将存放在L[1…n]中的n个元素建成初始堆,由于堆本身的特点(以大根堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根节点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩一个元素为止。
/**
* 堆排序演示
*
* @author Lvan
*/
public class HeapSort {
public static void main(String[] args) {
// int[] arr = {5, 1, 7, 3, 1, 6, 9, 4};
int[] arr = {16, 7, 3, 20, 17, 8};
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
/**
* 创建堆,
* @param arr 待排序列
*/
private static void heapSort(int[] arr) {
//创建堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = arr.length - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0, i);
}
}
/**
* 调整堆
* @param arr 待排序列
* @param parent 父节点
* @param length 待排序列尾元素索引
*/
private static void adjustHeap(int[] arr, int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr[lChild]) {
break;
}
// 把孩子结点的值赋给父结点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
}
4、归并排序
public static int[] mergeSort(int[] array) {
int len = array.length;
if (len < 2) return array;
int mid = len / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(mergeSort(left), mergeSort(right));
}
public static int[] merge(int[] right, int[] left) {
int[] result = new int[right.length + left.length];
for (int i = 0, j = 0, k = 0; k < result.length; k++) {
if (i >= left.length) {
result[k] = right[j++];
} else if (j >= right.length) {
result[k] = left[i++];
} else if (left[i] > right[j]) {
result[k] = right[j++];
} else {
result[k] = left[i++];
}
}
return result;
}