package sort;
/**
* 插入排序
* 1)直接插入排序
* 2)折半插入排序
* 3)希尔排序
*
* 选择排序
* 1)简单选择排序
* 2)堆排序
*
* 交换排序
* 1)起泡排序
* 2)快速排序
*
* 归并排序
*/
public class sorts {
/**
* 直接插入排序 稳定排序
* 空间复杂度:o(1)
* 时间复杂度:
* 最坏情况: o(n^2)
* 最好情况: o(n)
* 平均情况: o(n^2)
* @param data 待排数据数组
*/
public static void InsertSort(int[] data){
for(int i = 1; i < data.length; i++){//数组下标从0开始,第一个元素有序,所以从第二个开始处理
int tmp = data[i];//用来临时存放待排元素
int j = i - 1;//标记开始比较的位置
//完成了从待排元素之前的元素开始扫描,如果大于待排元素,则后移一位
while(j >= 0 && tmp < data[j]){
data[j+1] = data[j];
--j;
}
data[j+1] = tmp;//找到插入位置,将tmp中暂存的待排元素插入
}
}
/**
* 折半插入排序 稳定排序 折半查找法的基本要求是序列已经基本有序
* 空间复杂度:o(1)
* 时间复杂度:
* 最坏情况: o(n^2)
* 最好情况: o(n)
* 平均情况: o(n^2)
* @param data 待排数据数组
*/
public static void binaryInsertSort(int[] data){
for(int i = 1; i < data.length; i++){//数组下标从0开始,第一个元素有序,所以从第二个开始处理
int tmp = data[i];
int low = 0;
int high = i - 1;
while(low <= high){//当low > high 折半查找结束
int mid = (low + high) / 2;
if(tmp < data[mid]){
high = mid - 1; // 插入点在低半区
}else{ //关键字相同时,使low = mid + 1,到高半区,保证稳定性
low = mid + 1;// 插入点在高半区
}
}
//依次向后移动记录
for(int t = i-1; t > high; t--){// 记录后移
data[t+1] = data[t];
}
data[high+1] = tmp; // 插入
}
}
/**
* 冒泡(起泡)排序 稳定排序
* 空间复杂度:o(1) tmp
* 时间复杂度:
* 最坏情况: o(n^2)
* 最好情况: o(n)
* 平均情况: o(n^2)
* @param data 待排数据数组
*/
public static void BubbleSort(int[] data){
int n = data.length;
for(int k = 0; k < n-1; k++){//进行(n-1)趟冒泡
boolean flag = false;//变量flag用来标记本趟排序是否发生交换
for(int i = 1; i < n-k; i++){
if(data[i-1] > data[i]){
int tmp = data[i];
data[i] = data[i-1];
data[i-1] = tmp;
flag = true;//如果没发生交换,则flag为false,否则为true
}
}
if(!flag){
return;
}
}
}
/**
* 快速排序 对从data[l]到data[r]的元素进行排序 通常都选取第一个元素作为枢纽
* 空间复杂度:o(logn) 快速排序是递归进行的, 递归需要栈的辅助
* 时间复杂度:
* 最坏情况: o(n^2)
* 最好情况: o(nlogn)
* 平均情况: o(nlogn) 就平均时间而言,快速排序是所有排序算法中最好的 快速排序的排序趟数和初始序列有关
* @param data 待排数据数组
* @param l 待排数据数组起始位置
* @param r 待排数据数组终止位置
*/
public static void QuickSort(int[] data, int l, int r){
int i = l;
int j = r;
if(l < r){//l >= r 相当于递归终止条件
int tmp = data[l];
//下面这个循环完成了一趟排序,即将数组中小于等于tmp的元素放在左边,大于等于tmp的元素放在右边
while(i != j){
while(j>i&&data[j]>=tmp) --j;//从右到左扫描找到一个小于tmp的元素
if(i<j){
data[i] = data[j];
i++;
}
while(j>i&&data[i]<=tmp) i++;
if(i<j){
data[j] = data[i];
j--;
}
}
data[i] = tmp;
QuickSort(data, l, i-1);//递归地对tmp左边元素进行排序
QuickSort(data, i+1, r);//递归地对tmp右边元素进行排序
}
}
/**
* 简单选择排序 从头到尾顺序扫描序列,找出最小的一个记录,和第一个记录交换,接着从剩下的记录中继续这种选择和交换,最终使序列有序
* 空间复杂度:o(1) tmp
* 时间复杂度:o(n^2)
* @param data 待排数据数组
*/
public static void SelectSort(int[] data, int n){
for(int i = 0; i < (n-1); i++){
int k = i;//k用来记录最小值的位置
for(int j = i + 1; j < n; j++){
if(data[j] < data[k]){
k = j;
}
}
if(k != i){
int tmp = data[k];
data[k] = data[i];
data[i] = tmp;
}
}
}
}
快排划分函数非常重要:可以用来求第k大的数,前k小个数(剑指offer面试题40),数组中出现次数超过数组长度一半的数字(剑指offer面试题39)
public static void Partition(int[] data, int start, int end){
int i = start;
int j = end;
int tmp = data[i];
if (data == null || data.length <= 0 || start <0 || end >= data.length ) {
return ;
}
while(i != j) {
while (i < j && data[j] >= tmp) --j;//从右到左依次遍历找到小于tmp的元素
if (i < j){
data[i] = data[j];
i++;
}
while (i < j && data[i] <= tmp) i++;//从左到右依次遍历找到大于tmp的元素
if (i < j){
data[j] = data[i];
j--;
}
}
data[i] = tmp;
}
与直接插入排序相比,折半插入排序寻找插入位置上面所花的时间大大减少。折半插入排序在记录移动次数方面和直接插入排序是一样的,所以时间复杂度和直接插入排序还是一样的。
希尔排序:
又叫做缩小增量排序,其本质是插入排序,只不过是将待排序的序列按某种规则分成几个子序列,分别对这几个子序列进行直接插入排序。
希尔排序是不稳定的排序
空间复杂度:o(1)
平均情况下时间复杂度: o(nlogn)
注意:希尔排序的增量取法要注意,首先增量序列的最后一个值一定是1; 其次增量序列中的值没有除1之外的公因子,如8、4、2、1这样的序列就不要取(8、4、2有公共因子2)
堆排序:
堆排序是不稳定的。可以把堆看成一棵完全二叉树,这棵完全二叉树满足:任何一个非叶子结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子节点,则这样的堆叫做大顶堆,反之叫小顶堆。
从无序序列所确定的完全二叉树的第一个非叶子节点,开始从右到左,从下到上,对每个非叶子节点进行调整,最终得到一个大顶堆。
对结点调整方法:
1) 将当前结点(假设为a)的值与其孩子结点进行比较,如果存在大于a值的孩子结点,则从中选出最大的一个与a交换。当a来到下一层的时候重复以上过程,直到a的孩子结点值都小于a的值为止。
2) 将当前无序序列中第一个元素,反映在树中是根结点(假设为a)与无序序列中最后一个元素交换(假设为b)。a进入有序序列,到达最终位置,无序序列中的元素减少1个,有序序列中元素增加1个。此时只有结点b可能不满足堆的定义,对其调整
3) 重复2)过程,直到无序序列中的元素剩下1个时排序结束。
适用范围:
记录数很多的情况,典型的例子是从10000个记录中选出前10个最小的。
这幅图归并排序的空间复杂度为O(n)