排序根据是否使用外存分为内排序和外排序,内排序只使用内存进行数据存储,外排序由于数据量比较大需要借助外存。
排序的稳定性:排序的稳定性是指排序之后相同的数据元素相对位置不变则为稳定排序,否则为不稳定排序。
插入排序
直接插入排序
思想:将一个记录插入到已经排序的有序表中,从而得到一个新的、个数加1的有序表。这个过程在查找位置过程中进行记录移动,而不用显示的交换元素
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定排序
public void insertSort(int[] a){
int j;
for(int i= 1;i<a.length;i++){
int tmp = a[i];
for( j=i;j>0 && compare(a[j-1],tmp) ; j--)
a[j] = a[j-1];
a[j] = tmp;
}
}
private boolean compare(int a,int b){
return a>b;
}
折半插入排序
思想:直接插入排序的过程就是查找、比较,如果比较的次数相对少了其效率 也会有所提高,因为需要在已经排好序的序列查找插入位置,所以使用折半查找降低查找次数
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定排序
public void bInsertSort(int[] a){
int j;
for(int i=1;i<a.length;i++){
int tmp = a[i];
int low = 0;
int high = i-1;
while(low < high){
int mid = (low + high)/2;
if(tmp < a[mid])
high = mid -1;
else
low = mid + 1;
}
for(j=i;j>low;j--)
a[j] = a[j-1];
a[j] = tmp;
}
}
希尔排序
思想:在插入排序的基础上,由于插入排序如果序列基本有序的话他的效率会很高,如果直接有序的话其时间复杂度是O(n);所以希尔排序将待排序列分割成若干个子序列,分别进行插入排序,待整个序列基本有序再进行一次整体排序。希尔排序的时间复杂度依赖于其增量序列。
时间复杂度:可达到O(n3/2)
空间复杂度:O(1)
稳定性:不稳定(跳跃性插入)
public void shellSort(int[] a){
int j;
int length = a.length;
for(int skp=length/2;skp>0;skp/=2){
for(int i=skp;i<length;i++){
int tmp =a[i];
for(j = i;j>=skp && compare(a[j-skp],tmp);j-=skp)
a[j] = a[j-skp];
a[j] = tmp;
}
}
}
private boolean compare(int a,int b){
return a>b;
}
我这里使用的是希尔增量序列,他并不是最好的增量序列,比如当其个数是2的幂次方时候,每次其skp都是偶数位,如果偶数为都是方最大值,基数为方最小值,这时希尔排序并没有其什么作用,增量序列应该保证其相邻增量互质,最小值为1
交换排序
冒泡排序
思想:通过第一记录与第二个记录比较如果若为逆序则交换其位置,然后比较第二个与第三个记录,直到n-1项与第n项比较结束。这个过程是一次冒泡排序,选择出最大值,然后继续对前n-1个值进行比较位置,以此类推。整个排序过程需要n-1次冒泡排序。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定排序
public void bubbleSort(int[] a){
for(int i=0;i<a.length-1;i++)
for(int j=0;j<a.length-i-1;j++)
if(a[j] > a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
快速排序
思想:冒泡排序的改进,一趟排序将待排序序列分为两部分,其中一部分关键词都大于另一部分关键词,然后在对这两部分序列继续进行排序,以达到整个序列有序。其实现需要找到一个枢纽元素将序列与枢纽元素进行对比,小于枢纽元素放在一侧,大于枢纽元素放在另一侧。枢纽元素的选择很重要,一般是选择序列的第一个元素,但是着存在问题,当待排序列是正序或者逆序的时候所有元素都会倾向一侧,这样在整个递归过程中都是这样,使其效率地下。可以选择待排序列的第一元素、中间元素、最后一个元素进行比较然后选择中间值,作为枢纽元素。
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定排序
public void quickSort(int[] a,int left ,int right){
int dp;
if(left < right){
dp = partition(a, left, right);
quickSort(a, left, dp-1);
quickSort(a, dp+1, right);
}
}
private int partition(int[] a,int left, int right){
int pivot = a[left];
while(left < right){
while(left < right && a[right] >= pivot)
right--;
if(left < right)
a[left++] = a[right];
while(left < right && a[left] < pivot)
left++;
if(left < right)
a[right--] = a[left];
}
a[left] = pivot;
return left;
}
选择排序
简单选择排序
思想:每一次从n-i个记录中选取最小的元素,作为有序序列中的第i个记录
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定排序
public void selectSort1(int[] a){
for(int i=0;i<a.length;i++){
int k = i;
for(int j=i+1;j<a.length-1;j++){
if(a[j] < a[k])
k = j;
}
if(k != i){
int tmp = a[i];
a[i] = a[k];
a[k] = tmp;
}
}
}
堆排序
思想:堆排序的思想就是将待排序列看做完全二叉树,堆分为大堆和小堆,大根堆的意思就是每棵子树的根节点大于其左右子树的根节点,小根堆的与之相反。由待排序列建堆的过程就是筛选最大值的过程,查找出最大值后将该值存放在待排序数组的最后一个位置length-1,下一个最大值放在length-2位置。由于是完全二叉树,所以左后一个非终端节点是n-1/2(因为数组下标从0开始),用此值开始和左右子树进行比较选取最大值后以此向上推。由于大根堆得出的序列是由小到大所以,如果是需要正序,则构造大根堆,如果需要逆序则构造小根堆。
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定排序
public void heapSort(int[] a){
for(int i=0;i<a.length;i++){
createMaxHeap(a, a.length-i-1);
swap(a,i,a.length-i-1);
}
}
private void createMaxHeap(int[] a,int lastIndex){
for(int i=(lastIndex-1)/2;i>=0;i--){
int k = i; //记录当前节点
while(2*k+1 <= lastIndex){ //判断子节点是否存在
int bigIndex = 2*k+1; //记录最大值节点,默认赋值为左孩子节点
if(bigIndex < lastIndex){ //判断是否存在右孩子
if(a[bigIndex +1] > a[bigIndex]) //找孩子节点中的最大值
bigIndex++;
}
if(a[k] < a[bigIndex]){ //判断孩子节点的最大值与根节点值进行比较
swap(a,k,bigIndex);
k = bigIndex;
}else
break;
}
}
}
private void swap(int[] a,int i,int j){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
归并排序
思想:将两个或两个以上的有序表合并成一个新的有序表。假如有n个记录的待排序列,首先将其分为n个子序列,然后两两合并得到n/2个子序列,然后再两两合并,重复执行。直到得到一个长度为n的有序序列,这种排序方法称为2-路归并排序;
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定排序
public static void mergeSort(int[] a){
sort(a,0,a.length-1);
for (int i : a) {
System.out.println(i);
}
}
private static void sort(int[] a, int left,int right){
if(left < right){
int center = (left + right)/2;
sort(a,left,center);
sort(a,center+1,right);
merge(a,left,center,right);
}
}
private static void merge(int[] a,int left,int center,int right){
int[] tmpArr = new int[a.length];
int mid = center+1;
int count = left;
int tmp = left;
while(left <=center && mid <= right){
if(a[left] <= a[mid])
tmpArr[count++] = a[left++];
else
tmpArr[count++] = a[mid++];
}
while(left<=center)
tmpArr[count++] = a[left++];
while(mid<=right)
tmpArr[count++] = a[mid++];
while(tmp <= right)
a[tmp] = tmpArr[tmp++];
}
桶式排序
思想:对于使用桶式排序,创建一个数组,数组大小为其待排序元素的最小值和最大值只差,使用该数组下标存储待排序列,然后使用公式a[i] = a[i] + a[i-1];计算各个记录在序列中的位置。桶式排序需要保证待排序列其范围与最大值最小值差不多如果,其最大值最小值差远远大于元素个数,那么辅助空间花销会很大。
时间复杂度:O(n+m) m为范围
空间复杂度:O(n)
稳定性:稳定排序
public void bucketSort(int[] a,int max,int min ){
//缓存数据
int[] tmp = new int[a.length];
//桶数组,他的大小应该为所给数组中最大值减去最小值
int [] buckets = new int[max - min];
//将每个记录掉入桶中
for(int i=0;i<a.length;i++)
buckets[a[i]-min]++;
//计算桶中元素在有序序列中的位置
for(int i=0;i<max-min;i++)
buckets[i] = buckets[i] + buckets[i-1];
tmp = Arrays.copyOf(a, a.length);
for(int i=a.length-1;i>=0;i--)
a[--buckets[tmp[i]-min]] = tmp[i];
}
基数排序
思想:基数排序是一种借助多关键字排序思想的排序。他将待排记录分解成多个关键字,根据子关键字进行排序 。多关键字排序有两种排序方式,最高位优先法(MSD)和最低位优先法(LSD),他是按照关键字的位数,比如123,分解成三个关键字百位、十位、分位。计算机更合适于LSD最低位优先法,关键字之间的排序还需要借助另一种稳定的排序,如果待排序列是整数,那么每一位都在[0-9]之间,因此可以使用桶式排序。
时间复杂度:依赖于子关键字排序算法
空间复杂度:依赖于字关健排序算法
稳定性:稳定排序
//radix=10,d为位数
public void radixSort(int[] a,int radix ,int d){
//创建缓存数据
int[] tmp = new int[a.length];
//buckets记录排序元素的信息
int[] buckets = new int[radix];
//总共排序位数最多次
for(int i=0,rate=1;i<d;i++){
//重置count数组
Arrays.fill(a, 0);
//复制缓冲数据
tmp = Arrays.copyOf(a, a.length);
//将每一位放入桶中
for(int j=0;j<a.length;j++){
int subKey = (a[j]/rate)%radix;
buckets[subKey]++;
}
//计算桶中元素在序列中的位置
for(int j=1;j<radix;j++)
buckets[j] = buckets[j] + buckets[j-1];
//按子关键字对指定的数据排序
for(int j=a.length-1;j>=0;j--){
int subKey = (a[j]/rate)%radix;
a[--buckets[subKey]] = tmp[j];
}
rate *=radix;
}
}
总结:
算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
直接插入排序 | O(n^2) | O(1) | 稳定 |
折半插入排序 | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n3/2) | O(1) | 不稳定 |
冒泡排序 | O(n^2) | O(1) | 稳定 |
快速排序 | O( nlogn) | O(1) | 不稳定 |
简单选择排序 | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(n) | 稳定 |
桶式排序 | O(K+n) | O(K+n) | 稳定 |