今天被问到有哪几种排序算法,当时我只知道名字,并不知道怎么实现,所以这里列出几种比较常见的排序算法,便于自己以后可以看看,回忆一下
1、冒泡排序:冒泡排序其实就是在保证每一轮循环之后得到的结果就是将前一个数与后一个数进行比较,最后经过第一轮循环后最后一个数为该数组中的最大值,同理,第二轮应该是倒数第二个值为前n-1个值中的最大值,一次类推,得到的是一个有序的数组:
具体的算法如下:
public void bubble(int[] date){
if(date==null)return ;
if(date.length<=0)return ;
for(int i=0;i<date.length;i++){
for(int j=date.length-1;j>i;j--){
if(date[j]<date[j-1]){
int temp=date[j];
date[j]=date[j-1];
date[j-1]=temp;
}
}
}
}
时间复杂度为o(n^2),空间复杂度为o(1);
总结:适合小数据的运算,对于大数据运算效率极低
2、快速排序,在每一轮循环,都可以保证在一个数前面的所有数都比他小,后面的数比他大,然后通过递归得到一个完整的数组:
public int get(int[] date, int start, int end) {//将首元素插入到数组date中,使得前面的所有值比他小,后面所有值比他大,并确定他的位置
int temp = date[start];
while (start < end) {
while (date[end] > temp && start < end) {
end--;
}
if (start < end) {
date[start++] = date[end];
}else{
break;
}
while (date[start] < temp && start < end) {
start++;
}
if (start < end) {
date[end] = date[start];
}
}
date[start] = temp;
return start;
}
public void quick(int[] date, int start, int end) {
if (date == null)
return;
if (date.length <= 0)
return;
if (start < end) {
int center = get(date, start, end);
quick(date, start, center - 1);
quick(date, center + 1, end);
}
}
快速排序时间复杂度为o(n*log2(n));空间复杂度为o(1)
时间复杂度计算:第一层循环n次,第二轮循环2*n/2次,第三轮循环4*n/4次,总共会循环log2(n),所以最后的结果就是log2(n)个n相加,得到n*log2(n);
快速排序一般是比较实用的一种排序算法
直接插入排序:直接插入排序就是从第i(i>1)个元素开始依次与前几个有序的元素进行比较,知道找到比这个元素小的值就将这个小的值依次后摞,使得前面可以形成一个有序即数组
算法实现:
public void insert(int[] date){
if(date==null) return;
if(date.length<=0) return;
//从第1个位置开始往前比较
for(int i=1;i<date.length;i++){
//这里需要设置一个临时的值,因为可能date[i]的值可能会发生改变
int temp=date[i];
int j=i-1;
while(j>=0&&date[j]>temp){
date[j+1]=date[j];
j--;
}
//因为date[j]刚好是第一个小与或等于temp的值,所以这里将date[j+1]作为temp的值
date[++j]=temp;
}
}
时间复杂度为o(n^2),空间复杂度为o(1)
希尔排序:希尔排序是在直接插入排序的情况下进行了改进,首先希尔排序会设置一个值d作为一个跨度,也就是将每隔d个元素的所有元素利用直接插入排序方法进行一个排序,然后缩小跨度,有进行一次排序,最后当d=1时可以做到基本有序,比直接插入排序的效率更高,因为在直接插入排序的基础上,缩短了元素之间移动的距离,提高了效率
理解了插入排序,希尔排序就非常简单了,算法实现如下:
public void shell(int[] date){
if(date==null) return;
if(date.length<=0)return;
int d=date.length-1;
while(true){
d/=2;//跨度为原来了二分之一
for(int count=0;count<d;count++){//对跨度内的其他数也要排列
//和直接插入排序基本一致,就是跨度设置成了d
for(int i=d;i<date.length;i+=d){
int temp=date[i];
int j=i-d;
while(j>=0&&date[j]>temp){
date[j+d]=date[j];
j=j-d;
}
date[j+d]=temp;
}
}
if(d==1){
break;
}
}
}
希尔排序的平均时间复杂度为o(n^1.3),其实他的平均复杂度是个复杂的问题,目前还没有解决,空间复杂度为o(1)
希尔排序的效率比直接插入排序的效率高,使用上也比较普遍
归并排序,归并排序其实就是将两个有序的序列归并成一个有序的序列,实现的思想就是利用递归算法,将数组划分为有序的几个部分,然后利用归并将其组合成一个有序的数组。
实现的算法:
//start为第一个序列的第一个元素,center和end分别为第二个序列的第一个元素和最后一个元素
public void merge(int[] date,int start,int end,int center){
if(start<end){
int[] temp=new int[date.length];
//m为临时数组的索引
int m=0;int c=center;int s=start;
while(start<=c-1&¢er<=end){
if(date[start]<date[center]){
temp[m++]=date[start++];
}else{
temp[m++]=date[center++];
}
}
while(start<=c-1){
temp[m++]=date[start++];
}
while(center<=end){
temp[m++]=date[center++];
}
while(end>=s){
date[end--]=temp[--m];
}
}
}
public void mergeSort(int[]date,int start,int end){
if(date==null)return;
if(date.length<=0)return;
if(start<end){
int center=(start+end)/2;
mergeSort(date,start,center);
mergeSort(date,center+1,end);
merge(date,start,end,center+1);
}else{
return ;
}
}
归并排序的时间复杂度为o(n*log2(n)),空间复杂度为o(1)
基数排序:基数排序算法思想是通过依次判断元素的个位,十位,百位等的大小来进行排序的,也就是说对于浮点类型的数组排序无法使用基数排序,具体的的实现可以用下面的例子来说明:
例如有一个数组:23,4,25,123,54,355,1456,34,37
那么首先判读最大的一个数为1456,为四位数,所以需要进行四次循环,然后在尽量不打乱原数组的情况下依次比较个十百千位来排序:
先按个位:23,123,4,54,34,25,355,1456,37
再按十位:4,23,123,25,34,37,54,355,1456
再按百位:4,23,25,34,37,54,123,355,1456
最后按千位:4,23,25,34,37,54,123,355,1456
最后得到的结果就会是一个有序的数组,这中排序算法对于数字非常分散的数组非常有利
具体的算法实现:
public void radix(int[] date){
if(date==null)return ;
if(date.length<=0)return;
//首先获取最大值得位数来判断循环次数
int max=0;int count=1;
for(int i=0;i<date.length;i++){
if(date[i]>max) max=date[i];
}
for(;(max=max/10)>0;count++);
//save数组用来存放每一位对应的所有值,num用来存放每一位值的个数
int[][] save=new int[10][date.length-1];
int[] num=new int[10];
int n=1;
while(count>0){
for(int i=0;i<date.length;i++){
int rad=date[i]/n%10;
save[rad][num[rad]++]=date[i];
}
int index=0;//重建date数组索引
for(int i=0;i<num.length;i++){
int m=0;
while(num[i]>0){
date[index++]=save[i][m++];
num[i]--;
}
num[i]=0;
}
n*=10;
count--;
}
}
时间复杂度为o(d(n+radix)),因为要进行n趟分配,radix次收集,同时要执行d次分配和收集,空间复杂度为o(n+d*radix);因为要分配一个二维数组,所以后消耗d*radix空间
选择排序:选择排序是要求在每一轮比较之后选出最大值,然后与较后的元素进行交换,保证每轮排序下来,靠后的元素是有序的,也可以选最小值
具体是实现算法:
public void choice(int[] date) {
if (date == null)return ;
if(date.length<=0)return;
for (int i = 0; i < date.length; i++) {
int max = 0;
int j = 0;
int position = 0;
while (j < date.length - i) {
if (date[j] > max) {
max = date[j];
position = j;
}
j++;
}
if (date[position] != date[j - 1]) {
int temp = date[position];
date[position] = date[j - 1];
date[j - 1] = temp;
}
}
}
时间复杂度为o(n^2),空间复杂度为o(1);
堆排序:堆排序主要是利用二叉树的特点进行一个排序的,堆排序分为大顶堆和小顶堆,排序的思想就是,假设该节点为i,那么它的左节点久违2i+1,右节点为2i+2,对于大顶堆而言,首先从头节点开始会和左节点和右节点进行比较,如果比他们小就会和大的节点进行交换,保证根节点的值比他的子节点大,然后子节点和他的左右节点比较,依次类推,最后保证根节点是最大值,然后与最后一个元素交换,继续遍历前n-1个节点,最后得到一个有序的数组,
具体的算法如下:
public void createMaxHeap(int[] date, int last) {
//(last-1)/2-1代表的节点都是非叶子节点,并且是从下往根节点遍历的
for (int i = (last - 1) / 2; i >= 0; i--) {
int k = i;
//向下遍历直到叶子节点
while (2 * k + 1 <= last) {
int bigger = 2 * k + 1;
if (bigger < last) {
if (date[bigger + 1] > date[bigger]) {
bigger++;
}
}
/*
* 因为本来是从子节点向父节点来遍历,所以可以断言如果根节点值
* 大于它的两个子节点的值,那么就没有必要继续访问他的叶子节点了
* 因为已经保证了根节点的值一定是所有子节点中最大的,所以可以直接退出循环
*/
if (date[bigger] > date[k]) {
int temp = date[k];
date[k] = date[bigger];
date[bigger] = temp;
k = bigger;
}else{
break;
}
}
}
}
public void heap(int[] date){
if(date==null) return;
if(date.length<=0) return;
for(int i=0;i<date.length;i++){
createMaxHeap(date,date.length-1-i);
int temp=date[date.length-1-i];
date[date.length-1-i]=date[0];
date[0]=temp;
}
}
其实在没有接触堆排序时还挺怕,懂了原理之后其实蛮简单的,只要记住节点的左右子节点的索引分别是2*k+1和2*k+2,然后就是保证根节点的值一定比子节点的值大,最后将根节点与最后一个叶子节点交换,就可以保证最后一个元素是最大的了,然后用相同的方法来遍历前n-1个节点就可以了
堆排序的时间复杂度为:o(n*log2(n),时间复杂度为o(1)