以前自己看数据结构的时候基本都把那些实现以及该排序方法特点都写了一遍,奈何一个月后又忘掉,现在想把这几天做的总结整理一下。
主要是一些代码整理和算法特点整理。适合面试笔试之前刷一刷的,不适合初学者。
算法名称 | 是否稳定 | 是否需要额外空间 (空间复杂度) | 时间复杂度 | 特点 |
插入排序 | 是 | 否 | O(N*N) | 对于基本有序的输入,该算法适合 (若输入已经有序则时间负责度O(N)) 而对于快速排序,若没有打乱输入序列, 仅仅取第一个或最后一个作为pivot, 那么它的时间复杂度为O(N*N). 插入排序比较适合用于“少量元素的数组” |
希尔排序 也称缩减增量排序 | 否 | 否 | 视具体的增量序列而定, 是亚二次时间界 | 好的增量序列能取得好的时间复杂度 |
堆排序 | 否 | 否 | O(N*logN) |
最优时间:O(nlgn)
最差时间:O(nlgn)
优先队列应用于进程间调度、任务调度等。
堆数据结构应用于Dijkstra、Prim算法。
|
归并排序 | 是 | 是 | O(NlogN) |
思想:运用分治法思想解决排序问题。
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)
|
快速排序 | 否 | 否 | 平均O(N*logN) |
当输入数组已排序时,时间为O(n^2),当然可以通过随机化来改进
(shuffle array 或者 randomized select pivot),使得期望运行时间为O(nlgn)。
最佳运行时间:O(nlgn)
|
冒泡排序 | 是 | 否 | O(N*N) |
最坏运行时间:O(n^2)
最佳运行时间:O(n^2)
|
计数排序 | 是 | 是 | O(N*N) | |
O(N*N) | ||||
O(N*N) |
有些人不懂什么叫稳定,这里简单说明一下:假设 k1=k2 ,且在排序前的序列中k1领先于k2,如果排序算法时稳定的,则排序后k1任然在k2前面。
这里直接简单除暴给出我写的代码,有错欢迎批评指正:
插入排序的:
//总结:对于有序的序列,时间复杂度为O(N),但是对于快速排序确实O(N*N)
// 对于无序的序列,时间复杂度为O(N*N),平均复杂度为O(N*N)
// 是稳定的算法;不需要额外空间。
public class InsertSort {
public void insertionSort(int array[]){
if(array==null||array.length<2)
return ;
for(int i=1;i<array.length;i++)
{
int j;
int key=array[i];//存放带插入的元素
for( j=i;j-1>=0&&key<array[j-1];j--)
{
array[j]=array[j-1];
}
array[j]=key;
}
}
//=================分割线================================
//以下是递归版本
public void insertionSortRecursive(int[] array)
{
if(array==null||array.length<2)
return ;
insertion_Recursive(array,0,array.length-1);
}
private void insertion_Recursive(int[] array, int i, int j) {
if(i>=j)
return ;
insertion_Recursive(array, 0, j-1);
insert(array,j);
}
private void insert(int[] array, int j) {
int key=array[j];
int i;
for(i=j;i-1>=0&&key<array[i-1];i--)
array[i]=array[i-1];
array[i]=key;
}
}
希尔排序:这里用的是流行但是不是最好的增量序列ht=N/2 hk=h(k+1)/2
//希尔算法(缩减增量排序)是亚二次时间界,最坏时间界为O(N*N),好的增量序列可以产生更好的是间界。
// 不需要额外空间 ;是不稳定的算法
public class ShellSort {
public void shellSort(int[] array) {
if (array == null || array.length < 2)
return;
int j;
for (int gap = array.length / 2; gap > 0; gap /= 2)// 定义增量序列为gap=N/2;
// 每一次增量减半
//相当于有gap个序列在进行插入排序,若gap初始为1,则等同于插入排序。
{
//类似插入排序 不过注意这里是i++不是i+=jap
for (int i = gap; i < array.length; i++) {
int key = array[i];
for (j = i; j - gap >= 0 && key < array[j - gap]; j -= gap) {
array[j] = array[j - gap];
}
array[j] = key;
}
}
}
}
下一个是堆排序:
/*特性:unstable sort(不稳定)、In-place sort(不需要额外空间)。
最优时间:O(nlgn)
最差时间:O(nlgn)*/
public class HeapSort {
private int parent(int i)
{
return (i-1)/2;
}
private int leftChild(int i)
{
return 2*i+1;
}
private int rightChild(int i)
{
return 2*i+2;
}
private void buildMaxHeap(int [] array)
{
for(int i=array.length/2;i>=0;i--)
precolateDown(array,array.length,i);
}
private void precolateDown(int[] array,int length, int hole) {
int child;
int tmp= array[hole];
for(;hole*2+1<length;hole=child)//hole*2+1<length 这里length表示个数,实际存放到length-1,
//hole*2+1<length表示该hole有孩子
{
child=leftChild(hole);//
if(child<length-1&&array[child+1]>array[child])//若有右孩子,且右孩子大于左孩子
{
child++;
}
if(array[hole]<array[child])
array[hole]=array[child];//若孩子比父亲大,则该孩子上移,hole下移
else
break;
}
array[hole]=tmp;
}
private void swap(int[] array, int i, int j) {
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
public void heapSort(int[] array)
{
buildMaxHeap(array);
for(int i=array.length-1;i>=0;i--)
{
swap(array,0,i);
precolateDown(array, i, 0);
}
}
}
归并排序:
/*
特点:stable sort(稳定算法)、Out-place sort(需要额外空间)
思想:运用分治法思想解决排序问题。
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)*/
public class MergeSort {
public void mergeSort(int array[])
{
mergeSort(array,0,array.length-1);
}
private void mergeSort(int[] array, int start, int end) //包括end 这个元素
{
if(start>=end)
return ;
int mid=start+(end-start)/2;
mergeSort(array,start,mid);
mergeSort(array,mid+1,end);
merge(array,start,mid+1,end);
}
/**
*
* @param array
* @param index1 为左边待归并的数组下标
* @param index2 为右边待归并的数组下标
* @param end 右边待归并结尾(包含)
*/
private void merge(int[] array, int index1, int index2, int end)
{
int[] tmp=new int[end-index1+1];
System.arraycopy(array, index1, tmp, 0, tmp.length);//复制到tmp数组
int pos1=0;//tmp 指针
int pos2=index2-index1;//tmp 指针
int index=index1;//array指针
while(pos1<(index2-index1)&&pos2<=(end-index1))
{
if(tmp[pos2]<tmp[pos1])
array[index++]=tmp[pos2++];
else
array[index++]=tmp[pos1++];
}
while(pos1<(index2-index1))//左边还有剩 注意指针范围别错,要减去index1
array[index++]=tmp[pos1++];
while(pos2<=(end-index1))//右边还有剩
{
array[index++]=tmp[pos2++];
}
}
}
快速排序:
/*
特性:unstable sort(不稳定)、In-place sort(不需要额外空间)。
最坏运行时间:当输入数组已排序时,时间为O(n^2),当然可以通过随机化来改进(shuffle array 或者 randomized select pivot),
使得期望运行时间为O(nlgn)。
最佳运行时间:O(nlgn)
快速排序的思想也是分治法。
*/
public class QuickSort {
public void quickSort(int[] array)
{
if(array==null||array.length<2)
return;
quickSort(array,0,array.length-1);
}
/***
*
* @param array
* @param start 开始下标
* @param end 结尾下标(包含结尾元素)
*/
private void quickSort(int[] array, int start, int end) {
if(start>=end)
return ;
int p=partition(array,start,end);
quickSort(array, start, p-1);
quickSort(array, p+1, end);
}
private int partition(int[] array, int start, int end) {
int pivotKey=array[end];//这里选择最后一个元素,若输入数组为有序的情况,则不能这么做可以随机选择
int small=start-1;
for(int i=start;i<end;i++)
{
if(array[i]<pivotKey)
{
small++;
if(small!=i)
{
swap(array,i,small);//small以及之前的都是小于pivotKey;small~i都是大于等于pivotKey
}
}
}
++small;
swap(array,small,end);
return small;
}
private void swap(int[] array, int i, int j) {
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
}
冒泡排序:
/*特点:stable sort、In-place sort
思想:通过两两交换,像水中的泡泡一样,小的先冒出来,大的后冒出来。
最坏运行时间:O(n^2)
最佳运行时间:O(n^2*/
public class BubbleSort {
/**
* 正宗的冒泡排序
* @param a
*/
public void bubbleSort(int[] a)
{
for(int i=0;i<a.length-1;i++)
{
for(int j=a.length-1;j>i;j--)
{
if(a[j]<a[j-1])
swap(a, j-1, j);
}
}
}
/**
* 冒泡排序的优化版
* @param a
*/
public void bubbleSortUpdate(int[] a)
{
boolean flag=true;
for(int i=0;i<a.length-1&&flag;i++)
{
flag=false;
for(int j=a.length-1;j>i;j--)
{
if(a[j]<a[j-1])//冒泡关键代码,注意区别交换排序
{
swap(a, j-1, j);
flag=true;
}
}
}
}
private void swap(int[] a, int i, int j) {
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
计数排序:
/*特性:stable sort、out-place sort。
最坏情况运行时间:O(2n+k)
最好情况运行时间:O(2n+k)
当k=O(n)时,计数排序时间为O(n)*/
public class CountSort {
/**
*
* @param array
* @param min 数组中的最大值
* @param max 数组中的最小值
*/
public void countSort(int[] array,int min,int max)
{
int[] count=new int[max-min+1];
Arrays.fill(count, 0);
int [] tmp= new int[array.length];
System.arraycopy(array, 0, tmp, 0, array.length);
for(int i=0;i<array.length;i++)
{
count[array[i]-min]++;
}
for(int i=1;i<count.length;i++)
count[i]=count[i]+count[i-1];
for(int i=array.length-1;i>=0;i--)
{
array[--count[tmp[i]-min]]=tmp[i];//array[i]-min用于计数其放在那个桶中count[tmp[i]-min]用于告诉存放与array什么位置
}
}
}
基数排序:(至于什么叫基数排序,建议大家去查一下,它的实现有部分很类似计数排序)
//基数排序
/*特性:stable sort(稳定)、Out-place sort。
最坏情况运行时间:O((2n+radix)distance)
最好情况运行时间:O((2n+radix)distance)
约等于线性复杂度*/
public class RadixSort {
/**
*
* @param a
* @param radix 基数
* @param distance 数组中最大的位数
*/
public static void radixSort(int []a,int radix,int distance){
int divide=1;
int[] count=new int[radix];
for(int i=0;i<distance;i++){
int[] tmp=Arrays.copyOf(a, a.length);
Arrays.fill(count, 0);
//计算基数
for (int j=0;j<a.length;j++){
int key=tmp[j]/divide%radix;
count[key]++;
}
//储存每个基数个数
for(int j=1;j<count.length;j++){
count[j]=count[j]+count[j-1];
}
//重排
for (int j=a.length-1;j>=0;j--){
int key=tmp[j]/divide%radix;
count[key]--;
a[count[key]]=tmp[j];
}
divide*=radix;
}
}
}
桶排序:这里就不给出实现,因为其做法就是将输入的待排序序列计算各自属于那个桶,当这些都放入桶后,对每个桶可以按照插入排序或快速排序进行排序,最后导出最终的排序序列。
写的有点粗糙,有空再改进一下。