一、冒泡排序
步骤
- 以正序排序 (从小到大)为例,比较两个相邻的元素,如果第一个元素的值大于第二个,则交换它们的位置
- 从开始的第一对到最后一对,重复上面的操作,第一次遍历完之后,最后一个是最大的元素
- 对前面越来越少的无序元素重复上面的操作,直到排序完成
时间复杂度:
- 平均时间复杂度:O(n)
- 最坏时间复杂度:O(n)
- 最好时间复杂度:O(n)。当一个数组是完全有序时,可以达到O(n)的时间复杂度。
空间复杂度:O(1)
稳定性:稳定
public class BubbleSort implement IArraySort{
public int[] sort(int[] sourceArray)throw Exception{
int[] arr=Arrays.copyOf(sourceArray,sourceArray.length);
for(int i=1;i<arr.length;i++){
boolean flag=true;
for(int j=0;j<arr.length-i;j++){
//如果前一个元素大于后一个,则交换二者的位置
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
flag=false;
}
}
if(flag){
break;
}
}
return arr;
}
}
二、选择排序
步骤
在未排序的数组中,找到最小值,放在数组的第一位,然后再从未排序的数组中找到最小值,放在已排序数组的后面。(将无序分区中的最小值放在有序区的最后位置)
时间复杂度:
平均、最好、最坏时间复杂度均为O(n)
空间复杂度 :O(1)
稳定性 :不稳定。 原因:比如A、B、C三人得分分别为 80、80、70,如果从小到大排序会得到 C(70)、B(80)、A(80)的结果,A跑到了B的后面,所以选择排序是不稳定的。
public class SelectSort{
public int [] sort(int [] sources)throw Exeception{
int [] arr=Arrays.copyOf(sources,sources.length);
for(int i=0;i<arr.length-1;i++){
int min=i;
for(int j=i+1;j<arr.length;j++){
if(arr[j]<arr[min]){
min=j;
}
}
if(i!=min){
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
}
return arr;
}
}
三、插入排序
步骤
-
将序列中的第一个元素作为有序序列,将其之后的元素看作无序序列
-
依次扫描无序序列中的元素,用取出的元素和有序序列按
从后往前的顺序依次比较大小,将元素插入到合适的位置(如果两个元素的值相等,则插入到该元素的后面)数据有序程度越高,插入排序的效率越高
时间复杂度
:平均、最坏时间复杂度均为O(n),最好为O(n)
空间复杂度:O(n)
稳定性: 稳定
public class SelectSort{
public int [] sort(int [] sources)throw Exeception{
int [] arr=Arrays.copyOf(sources,sources.length);
for(int i=1;i<arr.length;i++){
int temp=arr[i];//扫描到的元素
int j=i;
while(j>0 && temp<arr[j-1]){
arr[j]=arr[j-1];
j--;
}
if(i!=j){
arr[j]=temp;
}
}
return arr;
}
}
四、归并排序
典型的分而治之的应用
步骤
- 创建一个和原数组相同大小的数组,用于临时存放数组
- 将数组分为两个数组,用递归将左右两个数组分别进行分裂,直到将每个元素分为一个组,然后比较数组的大小
- 用归并的策略将分组进行两两排序归并,直到只剩下最后一组,排序完成
分: 将数组分成一个个独立的元素;
合: 对这些一个个独立的元素进行两个为一组的排序合并,直到合并成一组。
时间复杂度:
平均、最好、最坏均为O(nlogn)
因为采用了类似完全二叉树的样式,树的深度为log2n,每一层的合并操作平均为O(n),所以为O(nlogn)
空间复杂度: O(n),归并排序不是原地排序,用O(n)的额外空间来存储结果
稳定 :稳定
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/12/8.
*/
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
}
五、希尔排序
希尔排序是插入排序的改进版,他的基本思想是将大的数据集合分成若干个小组,初始增量为gap=length/2,分别对小组内的数据进行插入排序,待增量递减为1时,整个数据集合呈"基本有序"状态,然后对所有数据进行直接插入排序。
希尔排序也叫缩小增量排序。
时间复杂度: 平均:O(n1.3)、最坏:O()、最好:O(n)
空间复杂度: O(1)
稳定性: 不稳定
public class ShellSort implements IArraySort {
public static void shellSort(int[] arr){
//gap增量,逐步缩小gap
for (int gap=arr.length/2;gap>0;gap/=2){
//从gap元素开始,逐个对其所在组进行插入排序
for (int i = gap; i < arr.length; i++) {
int j=i;
while (j>=gap && arr[j]<arr[j-gap]){
//交换法
swap(arr,j,j-gap);
j-=gap;
}
}
}
}
public static void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
}
/** shellSort的移动法
* while (j-gap>=0 && temp<arr[j-gap]){
* arr[j]=arr[j-gap];
* j-=gap;
* }
* arr[j]=temp;
* @param arr
* @param a
* @param b
*/
六、快速排序
快速排序是冒泡排序的一种改进方法
最坏时间复杂度的情况为:每次选取的基准数据为最小或最大的数,无法对数组进行平分递归了。该情况为数组为正序或者倒序排列
步骤: 在数列中找到一个"基准",遍历数列,使小于该基准的数排在基准之前,大于该基准的数排在基准之后,这样就确定了一个基准的位置,递归地把基准前后的子数列进行快速排序。
- 指定一个基准数据,用两个指针low、high分别指向队列的队首和队尾
- 首先比较high和基准数据tmp的值,如果high小于tmp,用high的值替换low的值,否则high向前移动一位
- 然后比较low和temp的值,如果low的值大于tmp的值,用low的值替换high的值,否则low向后移动一位
- low和high交替移动,直到二者相遇,此时确定了基准数据tmp的位置
- 得到基准数据的位置后,分别对该位置两侧的队列递归进行上述1-4的操作,最终就能得到想要的顺序
平均、最好时间复杂度: O(nlogn)
最坏时间复杂度: O()
空间复杂度: O(logn)。栈的深度,最坏情况下为O(n)
public class QuickSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return quickSort(arr, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = getIndex(arr, left, right);
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
public static int getIndex(int[] arr,int low,int high){
int temp=arr[low];
while (low<high){
while (low<high && temp<=arr[high]){
high--;
}
arr[low]=arr[high];
while (low<high && temp>=arr[low]){
low++;
}
arr[high]=arr[low];
}
arr[low]=temp;
return high;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
七、堆排序
前置知识补充:
叶子节点:没有子节点的节点
满二叉树:树是满的。
完全二叉树:由满二叉树引申出来的,除最后一层外树是满的,且最后一层需要左靠齐。
完满二叉树:所有非叶子节点的度都是2(只要有子节点,就必须有两个)
堆:①是一个完全二叉树;②所有父节点的值都要大于等于(或小于等于)子节点的值
满二叉树:
完全二叉树图:
堆排序(HeapSort)是利用堆的数据结构设计的一种排序算法。
算法描述:(以正序排序为例)
- 将数组构建成大顶堆
- 将堆顶元素arr0与数组中最后一个元素arr[length]交换,此时得到一个新的无序区(arr[0]~arr[length-1])和新的有序区(arr[length])
- 由于交换后新的堆可能会违反堆的性质,所有现在需要将新的无序堆进行调整,调整后将堆顶元素arr[0]与数组中倒数第二个元素arr[length-1]交换
- 重复2,3操作,直到新的无序区没有元素,排序完成
正序排序构建大顶堆,倒序排序构造小顶堆
/**
* 堆排序调用该方法
* @param arr
*/
public void heapSort(int[] arr){
int len=arr.length;
//第一个for循环得到第一个大顶堆
for (int i=(len-1)/2;i>=0;i--){
heapify(arr,len,i);
}
//重复len-1次
for (int j=len-1;j>=0;j--){
swap(arr,0,j);
len--; //交换一个值,数组长度都需要-1,这里是关键
heapify(arr,len,0);
}
}
/**
* 调整堆
* @param arr 数组
* @param len 数组长度
* @param parentN 父节点下标
*/
public void heapify(int[] arr,int len,int parentN){
int leftN=parentN * 2+1; //左子节点下标
int rightN=parentN * 2+2; //右子节点下标
int largest=parentN;
if (leftN<len && arr[largest]<arr[leftN]){
largest=leftN;
}
if (rightN<len && arr[largest]<arr[rightN]){
largest=rightN;
}
if (largest!=parentN){
swap(arr,parentN,largest); //如果子节点比父节点的值大,则交换二者的值
heapify(arr,len,largest); //调整堆
}
}
/**
* 交换值
* @param arr
* @param a
* @param b
*/
public void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
八、计数排序
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
算法描述
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
九、基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
算法描述
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点
十、桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
算法描述
- 设置一个定量的数组当作空桶;
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序;
- 从不是空的桶里把排好序的数据拼接起来。