1 排序相关
稳定性:通俗地讲就是能保证排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同。
1.1 冒泡排序
1.1.1 传统冒泡
/**
* 冒泡排序算法(从小到大)
* 原理:依次比较两个相邻的数,如果第一个数比第二个数大则交换这连个数的位置
* @param array
* @return
*/
public int[] bubbleSort(int[] array) {
for(int i=array.length-1; i>=0; i--) {
for(int j=0; j<i; j++) {
if(array[j] > array[j+1]) {
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
冒泡排序是最简单的排序算法,时间复杂度为O(n2);
算法思路:依次比较相邻的两个数,如果按照判断条件则进行相应的交换两个数字的操作。因为每次都是把一个数放到最前或最后,很像泡泡从水底冒出的样子,故命名为冒泡算法。
如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个元素相邻起来,最终也不会交换它俩的位置,所以相同元素经过排序后顺序并没有改变。所以冒泡排序是一种稳定排序算法。
1.1.2 升级版(设置判断标志位)
/**
* 冒泡排序升级版(从小到大)
* 原理:通过一个标志判断如果发现一趟循环中没有交换的元素了就说明已经排好序了后面就不用再比较
* @return
*/
public int[] bubbuleSortUpgrade(int[] array) {
boolean exitFlag = false; // 退出标志位
for(int i=array.length-1; i>=0; i--) {
exitFlag = true; // 循环之前把标志位置为退出
for(int j=0; j<i; j++) {
if(array[j] > array[j+1]) {
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
exitFlag = false; // 如果一次循环中有交换的则把标志位置为false
}
}
if(exitFlag == true) { // 如果退出标志位exitFlag为true则说明,这次循环中数组已经有序了则直接退出循环
break;
}
}
return array;
}
1.2 选择排序
/**
* 选择排序算法(从小到大)
* 原理:每一次从待排序的序列中选择最小和初始位置的元素进行交换
* @param array
* @return
*/
public int[] selectionSort(int[] array) {
int minIndex = 0;
int temp;
for(int i=0; i<array.length; i++) {
minIndex = i; // 初始设置最小的值下标为i
for(int j=i+1; j<array.length; j++) {
if(array[j] < array[minIndex]) {
minIndex = j;
}
}
// 每一次循环,找到最小的值的下标后,交换两个数的位置
temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
return array;
}
时间复杂度为O(n2);
算法不稳定; 举个例子:序列5 8 5 2 9, 我们知道第一趟选择第1个元素5会与2进行交换,那么原序列中两个5的相对先后顺序也就被破坏了。所以选择排序是一个不稳定的排序算法。
1.3 插入排序
插入排序有可以分为直接插入排序和希尔排序。
1.3.1 直接插入排序
直接插入版本一:
/**
* 插入排序(从小到大)
* 原理:每次循环找到最小元素的下标,记录这个元素的值,之后把从判断位的元素到最小值下标的元素全部后移一位,最后把记录的最小值赋值到判断位
* @param array
* @return
*/
public int[] insertSort(int[] array) {
int minIndex;
for(int i=0; i<array.length; i++) {
minIndex = i; // 初始设置最小值的下标为i
for(int j=i+1; j<array.length; j++) { // 循环找到最小值的下标
if(array[j]<array[minIndex]) {
minIndex = j;
}
}
int minValue = array[minIndex]; // 最小值
System.arraycopy(array, i, array, i+1, minIndex-i); // 把从i到minIndex(共 minIndex-i)的下标的元素向后移1位
array[i] = minValue;
}
return array;
}
直接插入版本2:
/**
* 插入排序(从小到大)版本2
* 把当前下标的值和当前值前面的值一一比较,如果当前值小于前面的值则进行交换,直到前面一个值大于比较值,
* 循环以上步骤,使数组全部有序
* @param a
* @return
*/
public int[] insertSort2(int[] a) {
for(int i=0; i<a.length; i++) {
for(int j=i-1;j>=0;j--) {
if(a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
System.out.println(Arrays.toString(a));
}
}
return a;
}
1.3.2 希尔排序
/**
* 希尔排序(从小到大)
* 先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,
* 然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素
* 进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,
* 因此希尔排序在时间效率上比前两种方法有较大提高。
* @param array
* @return
*/
public int[] shellSort(int[] array) {
int arrayLength = array.length;
for(int gap = arrayLength/2; gap>0; gap/=2) { // 元素间隔的长度初始为(数组长度/2),每次循环间隔减少一倍,直到gap为0
for(int i=0; i<gap; i++) { // 对相隔gap个增量的元素进行直接插入排序
for(int j=i+gap; j<arrayLength; j++) {
if(array[j] < array[j-gap]) {
int temp = array[j];
int k = j-gap;
while(k>=0 && array[k]>temp) {
array[k+gap] = array[k];
k-=gap;
}
array[k+gap] = temp;
}
}
}
}
return array;
}
以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例
第一次 gap = 10 / 2 = 5
49 38 65 97 26 13 27 49 55 4
1A 1B
2A 2B
3A 3B
4A 4B
5A 5B
1A,1B,2A,2B等为分组标记,数字相同的表示在同一组,大写字母表示是该组的第几个元素, 每次对同一组的数据进行直接插入排序。即分成了五组(49, 13) (38, 27) (65, 49) (97, 55) (26, 4)这样每组排序后就变成了(13, 49) (27, 38) (49, 65) (55, 97) (4, 26),下同。
第二次 gap = 5 / 2 = 2
排序后
13 27 49 55 4 49 38 65 97 26
1A 1B 1C 1D 1E
2A 2B 2C 2D 2E
第三次 gap = 2 / 2 = 1
4 26 13 27 38 49 49 55 97 65
1A 1B 1C 1D 1E 1F 1G 1H 1I 1J
第四次 gap = 1 / 2 = 0 排序完成得到数组:
4 13 26 27 38 49 49 55 65 97
1.4 快速排序
/**
* 快速排序(从小到大)
* 思路:每次把一个值当做中间值,操作数组使得数组左边的值比中间值小,右边的值比中间值大,递归调用直至数组有序
* 具体实现:把第一个值当做为中间值(额外变量记录下来),设置leftIndex==left,设置rightIndex==right,从rightIndex开始判断
* 如果rightIndex的值大于中间值则--,如果rightIndex的值小于中间值则把rightIndex的值赋值给leftIndex(使得小的值在左边),
* 反过来再从leftIndex开始进行上面操作,直到数组左边的值比中间值小,右边的值比中间值大,最后递归调用方法直至数组有序
* @param a
* @return
*/
public int[] quickSort(int[] a, int left, int right) {
if(left >= right)
return a;
int temp = a[left]; // 记录临界值
int lowIndex = left;
int highIndex = right;
while(lowIndex < highIndex) {
while( a[highIndex] >= temp && lowIndex < highIndex) {
highIndex--;
}
a[lowIndex] = a[highIndex];
while( a[lowIndex] < temp && lowIndex < highIndex) {
lowIndex++;
}
a[highIndex] = a[lowIndex];
}
int centerIndex = lowIndex; // 退出上面的循环时中心下标centerIndex == lowIndex == rightIndex
a[centerIndex] = temp; // 把最初判断的值赋值到centerIndex的位置
quickSort(a, left, centerIndex-1); // 递归调用方法
quickSort(a, centerIndex+1, right);
return a;
}
上图是第一次排序的结果,以第一个数3为基准是的数组左边的小于3,右边的大于3。