前言
- 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
- 如果提到排序,通常指的是排升序(非降序)。
- 通常意义上的排序,都是指的原地排序(in place sort)。
- 两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
一、冒泡排序
冒泡排序是一种简单的排序,通过访问下标,重复的遍历数组,将2个相近的值按照给定的顺序将其交换。这里默认按照升序。
例如 :int [] array={1,6,4,9,3,8};
定义下标 j 在数组的最开始 0 位置,依次比较 j 与 j+1 的值 比较完一次 j++;
第一趟 : 1 4 6 3 8 9
第二趟 :1 4 3 6 8 9
第三趟 :1 3 4 6 8 9
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
稳定的排序也可以变成不稳定的排序
代码实现:
public static void bubbleSort(int [] array){
for (int i = 0; i < array.length-1; i++) { //通过外循环控制一共需要比较几趟
for (int j = 0; j < array.length-1; j++) { //通过内循环来比较值的大小
//此时如果代码为if (array[j] >= array[j+1]) 则排序为不稳定排序
if (array[j]>array[j+1]){
int temp =array[j+1];
array[j+1]=array[j];
array[j]=temp;
}
}
}
}
**代码优化**
public static void bubbleSort2(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 == false) {
break;
}
}
}
二、选择排序
简单选择排序是最简单直观的一种算法,遍历元素找到一个最小(或最大)的元素,把它放在第一个位置,然后再在剩余元素中找到最小(或最大)的元素,把它放在第二个位置,依次下去,完成排序。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
代码实现
public static void selectSort(int [] array){
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length; j++) {
if(array[i] > array[j]){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
三、直接插入排序
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入。插入排序,初始数据越接近有序,时间效率越高。
* 时间复杂度:o(n^2)
* 最好情况下:当数据有序的时候,O(n) 当数据越有序越快
* 最坏情况下:当数据逆序的时候,可以达到O(n^2)
* 空间复杂度:o(1)
* 稳定性:稳定
* 任何一个稳定的排序 一定可以变成不稳定的排序
* 但是如果本身就是不稳定的排序 不可能变成稳定的排序的
* 特点:越有序越快 所以很多排序的优化,比如快速排序的优化,就会用到直接插入排序
int [] array = {12,5,9,24,6,8,3};
j i
将i位置的数据保存下来为 int temp = array[i];
此时比较temp 和 array[j]值的大小,比较一次 j-- 一次
直到 j<0;退出内层循环 ,再将temp的值赋值给 j+1位置(此时 j = -1)
如果两值不交换 直接break;跳出循环,再将原来的值放回原位;
所以内循环结束后 将tem的值赋给 j+1 ;
代码实现:
public static void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int tem = array[i];
int j = i-1;
for ( ; j >= 0 ; j--) {
if(tem < array [j]){
array[j+1] = array [j];
}
else{
break;
}
}
array[j+1] = tem;
}
}
四、希尔排序
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。希尔排序是对直接插入排序的优化。
* 时间复杂度: O(n^1.3 - n^1.5)
* 空间复杂度:O(1)
* 稳定性:不稳定的排序
如何分组:
gap = array.length;
while (gap > 1) {
gap = gap / 2; gap 依次等于 5 , 2 , 1
}
9,1,2,5,7,4,8,6,3,5
第一次 gap = 5;
分成5组
依次是i= gap 与j = i-gap 比较
9 4 排序后 4 9
1 8 排序后 1 8
2 6 排序后 2 6
5 3 排序后 3 5
7 5 排序后 5 7
排序完为4,1,2,3,5,9,8,6,5,7
第二次 gap = 2
分成 2 组
4 2 5 8 5 排序后 2 4 5 5 8
1 3 9 6 7 排序后 1 3 6 7 9
排序完 2,1,4,3,5,6,5,7,8,9
第三次 gap = 1
分成一组
2,1,4,3,5,6,5,7,8,9
排序完 1,2,3,4,5,5,6,7,8,9
代码实现:
public static void shell(int[] array,int gap) {
for (int i = gap; i < array.length; i++) {
int tem = array[i];
int j = i-gap;
for ( ; j >= 0 ; j-=gap) {
if(tem < array [j]){
array[j+gap] = array [j];
}
else{
break;
}
}
array[j+gap] = tem;
}
}
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
//gap = (gap / 3) + 1;
gap = gap / 2;
shell(array,gap);
}
}
五、堆排序
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意: 排升序要建大堆;排降序要建小堆。
* 时间复杂度:O(n*logn(log以2为底n的对数))
* 空间复杂度:O(1)
* 稳定性:不稳定
public static void heapSort(int [] array){
}
默认排升序
首先我们需要建立一个大堆:
public static void createHeap(int [] array){
adjust(array,i,array.length);
通过调整让其变成大堆
}
public static void adjust(int[] array,int parent , int len){}每次调整的函数
代码实现:
public static void heapSort(int [] array{
createHeap(array);//调用这个函数已经建立一个大堆
//开始排序
//首尾交换 即将最大值放在尾巴,然候数组长度减1,继续调整;
int end = array.length -1;
while(end < 0){
int tem = array[0];
array[0] = array[end];
array[end] = tem;
adjust(array,0,end--);
//调整完再将数组的长度减1;
}
}
public static void createHeap(int [] array){
//利用for循环来找每次的双亲节点
for(int p = (array.length-1-1)/2; p>=0; p--){
adjust( array, p , array.length);
//通过每次的调整来建立大堆
}
}
public static void adjust(int [] array , int parent , int len){
//通过传入的双亲节点,来找到孩子节点,
int child = 2*parent +1;
//如果child小于数组长度,则进入循环,从而到达向上调整
while(chlid < len){
//找到后判断左右孩子的大小,谁大在和双亲节点交换,如果没有右孩子则小标越界
//所以加入判断child+1小于len数组长度
if(child+1 < len && array[child+1] > array[child]){
child ++;
}
//判断双亲节点与孩子节点的大小
if(array[child] > array[parent]){
int temp = array[child];
array[child] = array[parent];
array[parent] = temp;
//向下调整继续判断
parent = child;
child = 2*parent+1;
}else{
//如果两则不交换说明下面的数已经是大堆,直接break;
break;
}
}
}
六、快速排序
1.从待排序区间选择一个数,作为基准值(pivot);
2. Partion: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
3. 采用分治思想,利用递归手段对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
* 时间复杂度:
* 最好情况:O(n*logn) 均匀分割
* 最坏情况:O(n^2) 数据有序的时候
* 空间复杂度:
* 最好:O(logn)
* 最坏:O(n)
* 稳定性: 不稳定
如何找到基准:
[
参考连接:(https://blog.csdn.net/insistgogo/article/details/7785038)
]
三种办法:
1. 选择边上(左或者右):以下代码默认为这种办法选择基准
2. 随机选择
3. 几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值
代码实现
public static void quickSort(int [] array){
quick(array,0,array.length-1); //调用函数完成递归
}
public static void quick(int [] array , int low , int high){
if(low >= high){ //当low >= high时 递归结束
return;
}
int par = partion(array,low,high);//每次递归重新寻找基准
quick(array,low,par-1);//递归左半段
quick(qrray,par+1,high);//递归右半段
}
public static int partion(int [] array , int low , int high){
//如何寻找基准 这里默认最边上
int tem = array[low];
while(low != high){
//在右边找到比 tem小的值
while(low < high &&array[high] >= tem){
high--;
}
//找到后将high位置的值放在low位置,再去左边找比基准大的值
array[low] = array[high];
//在左边找比tem打的值
while(low < high && array[low] <= tem){
low++;
}
//找到后将low位置的值放在high位置,继续下一次循环
array [high] = array[low];
}
//当low和high相遇的时候;
//将tem里存的值放在low位置上
array[low] = tem;
//这里返回的值就是基准 (可是是low 也可以是high)
return low;
}
优化快速排序
1.三数取中
找到头 尾 和 中间 三个位置的值 排序完后 中间值为基准
array[left], array[mid], array[right]
public static void medianOfThree(int [] array, int low, int mid, int high){
if (array[low] > array [mid]){
int tem = array[low];
array[low] = array[mid];
array[mid] = tem;
}
if (array[low] > array [high]){
int tem = array[low];
array[low] = array[high];
array[high] = tem;
}
if (array[high] < array [mid]){
int tem = array[high];
array[high] = array[mid];
array[mid] = tem;
}
int tem = array[low];
array[low] = array[mid];
array[mid] = tem;
}
public static void quick(int [] array , int low , int high){
if (low >= high){
return;
}
//优化1 : 三数取中
int mid = (low+high)>>>1;
medianOfThree(array,low,mid,high);
int par = partion(array,low,high);
quick(array,low,par-1);
quick(array,par+1,high);
}
2.当左右区间变小时,快趋近与有序,此时可以调用直接插入排序;
public static void insertSort2(int[] array,int low , int high) {
for (int i = low; i <= high; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= low; j--) {
if (array[j] > tmp){
array[j+1] = array[j];
}else {
break;
}
}
array[j+1] =tmp;
}
}
public static void quick(int [] array , int low , int high){
if (low >= high){
return;
}
//优化2 :区间变小时,使用插入排序
if (high-low+1 < 10){
insertSort2(array,low,high);
return;
}
int par = partion(array,low,high);
quick(array,low,par-1);
quick(array,par+1,high);
}
七、归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
* 时间复杂度:
最好与最坏都是 O(n*logn)
空间复杂度:O(n)
稳定性:稳定
public static void mergeSort(int[] array) {
mergeSortArc(array,0,array.length-1);//调用此函数完成递归
}
public static void mergeSortArc(int [] array , int low , int high){
if(low >= high){ //当low >= high时 递归结束 防止死循环
return;
}
int mid = (low+high)>>>1; //无符号右移一位 也就是除以2,这样会快一点
mergeSortArc(array,low,mid); //递归左边
mergeSortArc(array,mid+1,high);//递归右边
merge(array,low,mid,high);//用来进行合并
}
public static void merge(int [] array,int low,int mid, int high){
//就是合并两个有序数组,
//第一个数组开始位置为 low 结束位置为 mid;
int s1 = low;
int e1 = mid;
//第二个数组开始位置为 mid+1 结束位置为 high;
int s2 = mid+1;
int e2 = high;
//定义一个数组同来存储,长度则为第二个数组的结束位置减去第一个数组开始位置加一;
int [] temp = new int [high - low + 1];
int k=0; //temp数组里的下标
//判断s1和e1下标的关系以及s2和e2下标
//如果下标相遇,即有一个数组已经走完
while(s1 <= e1 && s2 <= e2){
//判断大小,谁小谁往temp 数组里存放
if(array[s1] > array[s2]){
temp[k++] = array[s2++];
}else {
temp[k++] = array[s1++];
}
}
//退出循环,无非两个条件
//1. 第二个数组走完,则将第一个数组剩下的值依次赋值给temp数组
while (s1 <= e1){
temp[k++] = array[s1++];
}
//2. 第一个数组走完,则将第二个数组剩下的值依次赋值给temp数组
while (s2 <= e2) {
temp[k++] = array[s2++];
}
//赋值完之后,将temp数组里的值赋值给array数组
for (int i = 0; i < temp.length; i++) {
array[i+low] = temp[i];
//这里为什么要将temp数组里的值赋值到array数组里的 i+low 位置
//因为每次调用这个函数时 temp的大小是不一样的 如果从0下标开始拷贝,则会将之前合并好的值覆盖,
//所以应该放在对应的位置上 即 i+low;
}
}