1、概念
1.1排序
排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
1.2稳定性
两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则称该算法是具备稳定性的排序算法。(!!!常见的排序中具备稳定性的
有:插入排序、冒泡排序和归并排序。)
2、七大基于比较的排序
2.1插入排序
2.1.1直接插入排序
2.1.1.1直接插入排序-原理
整个区间被分为
1. 有序区间
2. 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入,直到无序区间为空为止。
2.1.1.2直接插入排序-实现
public static void insertSort(int[] array) {
//区间:[0,i)
//无序区间:[i,array.length)
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i - 1;
while (j >= 0) {
if (array[j] > tmp) {
array[j + 1] = array[j--];
} else break;
}
array[j + 1] = tmp;
}
}
2.1.1.3直接插入排序-性能分析
!!!稳定性:稳定
!!!插入排序,初始数据越接近有序,时间效率越高
2.1.2希尔排序
2.1.2.1希尔排序-原理
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一
组内的记录进行排序。重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
2.1.2.2希尔排序-实现
public static void shellSort(int[] array) {
int gap = array.length / 2 + 1;
while (gap > 1) {
insertSortGap(array, gap);
gap = gap / 3 + 1;
}
insertSortGap(array, 1);
}
/**
* 本质是一个直接插入排序
*
* @param array 待排序数据序列
*/
public static void insertSortGap(int[] array, int gap) {
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i - gap;
while (j >= 0) {
if (array[j] > tmp) {
array[j + gap] = array[j];
j -= gap;
} else break;
}
array[j + gap] = tmp;
}
}
2.1.2.3希尔排序-性能分析
!!!稳定性:不稳定
2.2选择排序
2.2.1直接选择排序
2.2.1.1直接选择排序-原理
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。
2.2.1.2直接选择排序-实现
public static void selectSort(int[] array) {
// 无序区间: [0, i)
// 有序区间: [i,array.length)
for (int i = 0; i < array.length - 1; i++) {
int min = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[min]) {
min = j;
}
}
if (min != i) {
int tmp = array[min];
array[min] = array[i];
array[i] = tmp;
}
}
}
2.2.1.3直接选择排序-性能分析
!!!稳定性:不稳定
2.2.2堆排序
2.2.2.1堆排序-原理
基本原理也是选择排序,只是不再使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
!!!注意: 排升序要建大堆,排降序要建小堆。
2.2.2.2堆排序-实现
public static void heapSort(int[] array) {
createMaxHeap(array);
for (int i = array.length - 1; i > 0; i--) {
//交换前
//无序区间:[0,i]
//有序区间:[i+1,array.length - 1]
swap(array, i, 0);
//交换后
//无序区间:[0,i-1]
//有序区间:[i,array.length - 1]
shiftDown(array, 0, i);
}
}
public static void createMaxHeap(int[] array) {
int parent = (array.length - 2) / 2;
while (parent >= 0) {
shiftDown(array, parent, array.length);
parent--;
}
}
public static void shiftDown(int[] array, int parent, int size) {
int child = 2 * parent + 1;
while (child < size) {
if (child + 1 < size && array[child] < array[child + 1]) {
child++;
}
if (array[child] > array[parent]) {
swap(array, child, parent);
parent = child;
child = 2 * parent + 1;
} else break;
}
}
public static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
2.2.2.3堆排序-性能分析
!!!稳定性:不稳定
2.3交换排序
2.3.1冒泡排序
2.3.1.1冒泡排序-原理
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序。
2.3.1.2冒泡排序-实现
public static void bubbleSort(int[] array) {
for (int i = 1; i < array.length; i++) {
boolean flag = true;
for (int j = 0; j < array.length - i; j++) {
// 相等不交换,保证稳定性
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
flag = false;
}
}
if (flag) {
break;
}
}
}
2.3.1.3冒泡排序-性能分析
!!!稳定性:稳定
2.3.2快速排序
2.3.2.1快速排序-原理
1. 从待排序区间选择一个数,作为基准值(pivot);
2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
2.3.2.2快速排序-实现
2.3.2.2.1快速排序-实现-递归
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
public static void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
int pivot = partition(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
public static int partition(int[] array, int left, int right) {
int tmp = array[left];
while (left < right) {
while (array[right] >= tmp && right > left) {//不能取array[right]>tmp&&right>left------->死循环
right--;
}
array[left] = array[right];
while (array[left] <= tmp && right > left) {
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
2.3.2.2.2快速排序-实现-非递归
public static void quickSort2(int[] array) {
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length - 1;
int pivot;
stack.push(left);
stack.push(right);
while (!stack.empty()) {
right = stack.pop();
left = stack.pop();
pivot = partition(array, left, right);
if (left + 1 < pivot) {
stack.push(left);
stack.push(pivot - 1);
}
if (pivot + 1 < right) {
stack.push(pivot + 1);
stack.push(right);
}
}
}
2.3.2.3快速排序-性能分析
!!!稳定性:不稳定
2.3.2.4快速排序-基准值的选择
1. 选择边上(左或者右)。
2. 随机选择。
3. 几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值。
2.4归并排序
2.4.1归并排序
2.4.1.1归并排序-原理
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
2.4.1.2归并排序-实现
2.4.1.2.1归并排序-实现-递归
public static void mergeSort(int[] array) {
mergeSortInternal(array, 0, array.length);
}
public static void mergeSortInternal(int[] array, int low, int high) {
if (low + 1 == high) {
return;
}
int mid = (low + high) / 2;
//左边
mergeSortInternal(array, low, mid);
//右边
mergeSortInternal(array, mid, high);
//归并
merge(array, low, mid, high);
}
public static void merge(int[] array, int low, int mid, int high) {
int i = low;
int j = mid;
int k = 0;
int[] ret = new int[high - low];
while (i < mid && j < high) {
if (array[i] < array[j]) {
ret[k++] = array[i++];
} else {
ret[k++] = array[j++];
}
}
while (i < mid) {
ret[k++] = array[i++];
}
while (j < high) {
ret[k++] = array[j++];
}
//拷贝ret数组的元素到array数组当中
for (i = 0; i < ret.length; i++) {
array[low + i] = ret[i];
}
}
2.4.1.2.2归并排序-实现-非递归
public static void mergeSort2(int[] array) {
//gap为有序的子数组长度
int gap=1;
while (gap<array.length){
for (int i = 0; i < array.length; i+=gap*2) {
int left=i;
int right=left+gap*2;
if(right>array.length){
right=array.length;
}
int mid=left+gap;
if(mid>array.length){
continue;//如果mid大于数组长度,说明该归并不需要排序了
}
merge(array,left,mid,right);
}
gap*=2;
}
}
2.4.1.3归并排序-性能分析
!!!稳定性:稳定