排序
概述
对于排序概念,附上度娘解释
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序。
引用上面第一篇博客中的图(有个错别字,已凃):
第二篇博文中有9大排序算法,加了1个计数排序。
稳定性
- 稳定:插入排序、冒泡排序、归并排序、计数排序、基数排序、桶排序。
- 不稳定:选择排序(5 8 5 2 9)、快速排序、堆排序、(希尔排序)。
详解
1.冒泡排序
- 思想:将待排序的元素看作“气泡”,较小的元素比较轻从而上浮。自底向上检查序列,判断两个相邻的元素的顺序是否正确(比较),若其顺序不对就交换两者位置(交换)。第一遍处理后,“最轻”的元素就浮到了最高位置;经第二遍“次轻”的元素就浮到了次高位置。在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。
- 时间复杂度:平均、最佳、最差时间复杂度均为O(n^2)
- 代码如下:
/**
* 冒泡排序,升序
* @param arr
* @param n
*/
public static void bubbleSort(int[] arr,int n){
int tmp; //局部变量未初始化
for(int i=0;i<n;i++){
for(int j=n-1;j>0;j--){
if(arr[j]<arr[j-1]){ //较小值像上冒泡
tmp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=tmp;
}
}
}
}
/**
* 改进的冒泡排序,第二个循环条件
* @param arr
* @param n
*/
public static void bubbleSort2(int[] arr,int n){
for(int i=0;i<n;i++){
for(int j=n-1;j>i;j--){ //循环条件!前i次已经排好序,无需再比较!
if(arr[j]<arr[j-1]){
int tmp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=tmp;
}
}
}
}
2.选择排序
- 思想:在待排序元素中,选出最小(或者最大)的一个数(比较)与第1个位置的元素交换;再在剩下的元素中找最小(或者最大)的与第2个位置的元素交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
- 时间复杂度:平均、最佳、最差均为O(n^2)
- 代码如下:
/**
* 选择排序,升序
* @param arr
* @param n
*/
public static void selectSort(int[] arr,int n){
int min;
for(int i=0;i<n-1;i++){ //最后一位元素不用再操作,确定在正确位置上,所以为:n-1
min=i;
for(int j=i+1;j<n;j++){ //在待排序元素中找最小值
if(arr[j]<arr[min]){
min=j;
}
}
if(i!=min){ //将选择的最小值与arr[i]交换
swap(arr,i,min);
}
}
}
/**
* 交换数组中索引分别为i,j的值
* @param arr
* @param i
* @param j
*/
//因为 Java 中简单类型没有传引用
//swap(int[] arr, int i, int j) 方法在内部实际上是改变了arr所指示的对象的成员数据
//
public static void swap(int[] arr,int i,int j){
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
3.插入排序
- 思想:将一个记录插入到已排序好的有序表中,从而得到一个记录数增1的新有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入(依次与前一个值比较,若符合要求则往后移动),直至整个序列有序为止。
- 时间复杂度:最佳:当输入数组就是排好序的时候,复杂度为O(n);倒序时,复杂度为O(n^2);平均:O(n^2)
- 代码如下:
/**
* 插入排序,升序
* @param arr
* @param n
*/
public static void insertSort(int[] arr,int n){
//之前理解插入排序有误,找到合适插入位置后不是交换即可,而是要同数组插入,后面元素要往后移!!!
/*int index;
for(int i=1;i<n;i++){ //从第二个元素开始,将其插入至序列合适位置
index=i;
for(int j=i-1;j>=0;j--){ //找到合适插入位置
if(arr[i]<arr[j]){
index=j;
}
}
System.out.println("index="+index);
if(i!=index){
swap(arr,i,index);
}
}*/
//参见算法导论!!
int i,j,key;
for(i=1;i<n;i++){
key=arr[i];
j=i-1;
while(j>=0&&arr[j]>key){ //注意j的范围取值
arr[j+1]=arr[j];
j--;
}
// System.out.println("j="+j);
arr[j+1]=key;
}
}
4.希尔排序
- 思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
- 时间复杂度:比较复杂,且依赖于增量大小。但比直接插入排序算法更优
- 代码如下:
/**
* 希尔排序,缩小增量排序;核心依旧是插入排序
* dk 缩小增量,如果是直接插入排序,dk=1
* @param arr
* @param n
*/
public static void shellSort(int[] arr,int n){
//
int dk=n/2;
while(dk>=1){
shellInsertSort(arr,n,dk);
dk=dk/2;
}
}
/**
* 基本同插入排序
* @param arr
* @param n
* @param dk
*/
public static void shellInsertSort(int[] arr,int n,int dk){
int i,j,key;
for(i=dk;i<n;i++){
key=arr[i];
j=i-dk;
while(j>=0&&arr[j]>key){
arr[j+dk]=arr[j];
j-=dk;
}
arr[j+dk]=key;
// display(arr);
}
}
5.快速排序
- 思想:分治法。先从数组中取出一个数作为基数;分区,将比这个基数小的数全部放到它的左边,比基数大的数全部放到其右边,对左右两个区间重复第二步,直到每个区间只有一个数(递归)。
- 时间复杂度:平均:O(nlogn);最差:O(n*n),数组基本有序时;平均:O(nlogn)
- 代码如下:
- 第一次划分详细情况,看图一目了然!!!
/**
* 快速排序,参考算法导论
* 递归!!,升序
* @param arr
* @param n
*/
public static void quickSort(int[] arr,int p,int r){
if(p<r){
int middleIndex=partition(arr,p,r);
quickSort(arr,p,middleIndex-1); //右半部分
quickSort(arr,middleIndex+1,r); //左半部分
}
}
/**
* 一次划分
* @param arr
* @param p
* @param r
* @return
*/
public static int partition(int[] arr, int p, int r){
int i=p-1,j;
for(j=p;j<r;j++){
//
if(arr[j]<arr[r]){ //arr[r]作为pivot轴值
swap(arr,++i,j); //保证比pivot小的值都在[p,i]内
}
}
//最后交换pivot和中间大于pivot的值
swap(arr,++i,r);
return i; //返回下标i,i右边值均大于等于i.....
}