目录
一. 插入排序
1. 直接插入排序
代码演示
2.希尔排序( 缩小增量排序 )
二. 选择排序
1.直接选择排序
代码:
2. 堆排序
代码
三. 交换排序
1. 冒泡排序
代码
2. 快速排序
代码(有注释):
动图来自网路
一. 插入排序
1. 直接插入排序
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
由上图可清晰的看到,如何直接插入,从第二个(下标为 1 )开始 ,向前比较,以此类推
代码演示
/**
* 插入排序
* @param array
*/
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i - 1;
for (j = i - 1; j >= 0 ; j--) {
if(array[j] > tmp){
//就是让大的往后挪
array[j + 1] = array[j];
}else {
break;
}
}
array[j + 1] = tmp;
}
}
直接插入总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定
2.希尔排序( 缩小增量排序 )
先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序
/**
* 希尔排序
* @param array
*/
public static void shellSort(int[] array){
int gap = array.length;//增量
while(gap > 1){
gap = gap/2;
shell(array, gap);
}
}
private static void shell(int[] array, int gap){
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i - gap;
for(; j >= 0; j-=gap){
if(array[j] > tmp){
array[j + gap] = array[j];
}else {
break;
}
}
array[j + gap] = tmp;
}
}
希尔排序总结:
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
3. 希尔排序的时间复杂度不好计算,(O(n^1.3) ~O(n^1.5))因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定。4.不稳定
二. 选择排序
1.直接选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
代码:
/**
* 选择排序
* @param array
*/
public static void selectSort(int[] array) {
int i = 0;
for(i = 0; i < array.length; i++){
int minIndex = i;
int j = i + 1;
for(j = i + 1; j < array.length; j++){
if(array[minIndex] > array[j]){
minIndex = j;
}
}
int tmp = array[i];
array[i] = array[minIndex];
array[minIndex] = tmp;
}
}
直接选择排序总结:
1. 直接选择排序,效率不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定
2. 堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
简单来说,把数组变成大根堆,之后让第一个和最后一个交换,然后再把换后的变成大根堆,这样最大的就都再后面了
代码
/**
* 堆排序
* 把数组变成大根堆,之后让第一个和最后一个交换,然后再把换后的变成大根堆,这样最大的就都再后面了
* @param array
*/
public static void heapSort(int[] array){
int end = array.length - 1;
for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
//和孩子节点换
shiftDown(parent, array.length, array);
}
while (end > 0){
swap(array, 0, end);
shiftDown(0, end, array);
end--;
}
}
private static void swap(int[] array, int begin, int end){
int tmp = array[begin];
array[begin] = array[end];
array[end] = tmp;
}
private static void shiftDown(int parent,int usedSize,int[] array) {
int child = (2*parent) + 1;//左孩子节点
//不越界
while (child < usedSize){
//先比较一下孩子们的大小,找到当中最大的
if(child + 1 < usedSize && array[child] < array[child + 1]){
child = child + 1;
}
//然后再和parent比较大小
if(array[child] > array[parent]){
swap(array,child, parent);
//比完一组,parent得往下走,动起来
parent = child;
child = (2*parent) + 1;
}else {
break;
}
}
}
堆排序总结:
1. 时间复杂度:O(nlogn)
2. 空间复杂度:O(1)
3. 稳定性:不稳定
三. 交换排序
1. 冒泡排序
这应该是我们最早接触的一种排序,很好理解,不说了 (以前写过)
代码
/**
* 冒泡排序
* @param array
*/
public static void bubbleSort(int[] array){
for (int i = 0; i < array.length - 1; i++) {
boolean flg = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
flg = true;
}
}
if(!flg){
break;
}
}
}
冒泡排序总结
1. 时间复杂度:O(N^2)
2. 空间复杂度:O(1)
3. 稳定性:稳定
2. 快速排序
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
代码(有注释):
下面介绍两种常用找基准方法
1.Hoare版
看上图,直到key(也就是6) 的左边都比key小,右边都比key大
/**
* 快速排序
* @param array
*/
public static void quickSort(int[] array){
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end){
if(start >= end){
return;
}
//防止出现都在一边的情况
int index = midOfThree(array, start, end);//找到这三个数里(start, mid, end)第二大的下标
swap(array, start, index);//让start处是这三个里第二大的数
//找到基准
int pivot = partition(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
private static int partition(int[] array, int left, int right){
int key = array[left];
int i = left;
while(left < right){
//左边的都比key小,右边的都比key大
//必须right先走,因为如果是left先走的话,left哪里一定是一个大于key的数,因为left遇到大于key的数才停,之后和right相遇,再换,
//那左边的被交换数一定是一个大于key的数
//因为右边应该都是大于key的值,所以要把小于key的数抓出来
while (left < right && array[right] >= key){
right--;
}
//同理
while(left < right && array[left] <= key){
left++;
}
swap(array, left, right);
}
swap(array, i, left);
return left;
}
private static int midOfThree(int[] array, int left, int right){
int mid = (left + right)/2;
if(array[left] > array[right]){
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else {
return mid;
}
}else {
if(array[mid] > array[right]){
return right;
}else if(array[mid] < array[left]){
return left;
}else {
return mid;
}
}
}
}
2.挖坑法
看上图,直到走完,把pivot按到最后空出的位置,这样 左边都pivot比小,右边都比pivot大了
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
int pivot = array[left];//坑挖出来了
while (i < j) {
while (i < j && array[j] >= pivot) {
j--;
}
array[i] = array[j];//坑位变成[j]
while (i < j && array[i] <= pivot) {
i++;
}
array[j] = array[i];//坑位变成[i]
}
array[i] = pivot;
return i;
}