简单排序
1.冒泡排序(Bubble Sort)
-
原理:相邻元素两两比较,大的放后
-
API设计
public class Bubble {
/*
* 对数组a中元素进行排序
* */
public static void sort(Comparable[] a){
for(int i=a.length-1;i>0;i--){ // i表示每一轮最大的数所放的索引位置
for(int j=0;j<i;j++){ //j 表示当前比较数所在索引位置
if(greater(a[j],a[j+1])){
exch(a,j,j+1);
}
}
}
}
/*
* 判断v是否大于w
* */
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
* 交换a数组中索引i和j处值
* */
private static void exch(Comparable[] a,int i,int j){
Comparable temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
- 时间复杂度
2.选择排序(Selection Sort)
- 原理:索引i处元素依次和其后面所有的元素相比较,小的元素放在i索引处
- API设计
public class Selection {
/*
* 对数组内元素进行排序
* */
public static void sort(Comparable[] a){
for(int i=0;i<a.length-1;i++){ //i表示每一轮要比较元素的索引
for(int j=i+1;j<=a.length-1;j++){ //j表示i后每一个元素索引
if(greater(i,j)){
exch(a,i,j);
} }
}
}
/*
for(int i=0;i<=a.length-2;i++){
//定义一个变量,记录最小元素所在的索引,默认为参与选择排序的第一个元素所在的位置
int minIndex = i;
for(int j=i+1;j<a.length;j++){
//需要比较最小索引minIndex处的值和j索引处的值;
if (greater(a[minIndex],a[j])){
minIndex=j; //这里一个小小的优化,先记录下最小索引,然后再交换
}
}
//交换最小元素所在索引minIndex处的值和索引i处的值
exch(a,i,minIndex);
}
* */
/*
* 判断v是否大于w
* */
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
* 交换数组a中,索引i和j处的值
* */
private static void exch(Comparable[] a,int i,int j){
Comparable temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
- 时间复杂度
3.插入排序(Insert Sort)
- 原理:假定两堆序列(有序序列和无序序列),每次拿出无序序列第一个索引处值插入有序序列中
- API设计
public class Insertion {
/*
* 对数组内元素进行排序
* */
public static void sort(Comparable[] a){
//从第一个到最后一个元素插入到有序数组中
for (int i=1;i<a.length;i++){
//比较索引j处的值和索引j-1处的值,如果索引j-1处的值比索引j处的值大,则交换数据,如果不大,那么就找到合适的位置了,退出循环即可;
for(int j=i;j>0;j--){
if(greater(a[j-1],a[j])){
exch(a,j-1,j);
}else{
break;
}
}
}
}
/*
* 判断v是否大于w
* */
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
* 交换a数组中,索引i和j处值
* */
private static void exch(Comparable[] a,int i,int j){
Comparable temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
- 时间复杂度
复杂排序
1. 希尔排序
- 原理
【注】关于增长量h的确定
int h=1;
while(h<a.length){
h=2h+1;
}
h=h/2; //h减小规则
- API设计
public class Shell {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a){
//1.根据数组a的长度,确定增长量h的初始值;
int h = 1;
while(h<a.length/2){
h=2*h+1;
}
//2.希尔排序
while(h>=1){
//排序
//2.1.找到待插入的元素
for (int i=h;i<a.length;i++){
//2.2把待插入的元素插入到有序数列中
for (int j=i;j>=h;j-=h){
//待插入的元素是a[j],比较a[j]和a[j-h]
if (greater(a[j-h],a[j])){
//交换元素
exch(a,j-h,j);
}else{
//待插入元素已经找到了合适的位置,结束循环;
break;
}
}
}
//减小h的值
h= h/2;
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
2.归并排序
递归
定义:函数不断调用本身
【注】通常用于把一个大的问题层层转换成与原问题类似的多个小问题来求解,避免多次类似重复代码
但递归要有边界条件,不能无限制调用,否则容易出现栈内存溢出。
- 原理:将数组不断一分为二,直到分成若干个元素个数为1的数组,再两两比较合并
- API设计
public class Merge {
//归并所需要的辅助数组
private static Comparable[] assist;
/*
比较v元素是否小于w元素
*/
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w)<0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
//1.初始化辅助数组assist;
assist = new Comparable[a.length];
//2.定义一个lo变量,和hi变量,分别记录数组中最小的索引和最大的索引;
int lo=0;
int hi=a.length-1;
//3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a,lo,hi);
}
/*
对数组a中从lo到hi的元素进行排序
*/
private static void sort(Comparable[] a, int lo, int hi) {
//做安全性校验;
if (hi<=lo){
return;
}
//对lo到hi之间的数据进行分为两个组
int mid = lo+(hi-lo)/2;
//分别对每一组数据进行排序
sort(a,lo,mid);
sort(a,mid+1,hi);
//再把两个组中的数据进行归并
merge(a,lo,mid,hi);
}
/*
对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
*/
private static void merge(Comparable[] a, int lo, int mid, int hi) {
//定义三个指针
int i=lo;
int p1=lo;
int p2=mid+1;
//遍历,移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的对应索引处
while(p1<=mid && p2<=hi){
//比较对应索引处的值
if (less(a[p1],a[p2])){
assist[i++] = a[p1++];
}else{
assist[i++]=a[p2++];
}
}
//遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
while(p1<=mid){
assist[i++]=a[p1++];
}
//遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
while(p2<=hi){
assist[i++]=a[p2++];
}
//把辅助数组中的元素拷贝到原数组中
for(int index=lo;index<=hi;index++){
a[index]=assist[index];
}
}
}
- 时间复杂度:O(nlogn),虽然归并排序时间复杂度降低,但是递归调用需要额外申请内存空间,空间复杂度提升,是典型的的以空间换时间操作
快速排序
- 原理:按照某个标准(一般是数组第一个元素)将原数组分为两部分(比标准小、比标准大),同理分开的两个数组再以同样操作划分…
- API设计
public class Quick {
/*
比较v元素是否小于w元素
*/
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
//对数组内的元素进行排序
public static void sort(Comparable[] a) {
int lo = 0;
int hi = a.length-1;
sort(a,lo,hi);
}
//对数组a中从索引lo到索引hi之间的元素进行排序
private static void sort(Comparable[] a, int lo, int hi) {
//安全性校验
if (hi<=lo){
return;
}
//需要对数组中lo索引到hi索引处的元素进行分组(左子组和右子组);
int partition = partition(a, lo, hi); //返回的是分组的分界值所在的索引,分界值位置变换后的索引,
//partition位置放置比较元素,比较完成后放置位置即为最后排序后位置
//让左子组有序
sort(a,lo,partition-1);
//让右子组有序
sort(a,partition+1,hi);
}
//对数组a中,从索引 lo到索引 hi之间的元素进行分组,并返回分组界限对应的索引
public static int partition(Comparable[] a, int lo, int hi) {
//确定分界值
Comparable key = a[lo];
//定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left=lo;
int right=hi+1;
//切分
while(true){
//先从右往左扫描,移动right指针,找到一个比分界值小的元素,停止 这里必须从右边开始扫描才可以保证最后停止扫描元素和第一个元素交换不发生错误(若从左边开始,当左边找到一个比第一个大的元素,那么最后交换大的会跑到第一个元素位置)
while(less(key,a[--right])){
if (right==lo){
break;
}
}
//再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
while(less(a[++left],key)){
if (left==hi){
break;
}
}
//判断 left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
if (left>=right){
break;
}else{
exch(a,left,right);
}
}
//交换分界值
exch(a,lo,right);
return right;
}
}
- 时间复杂度:最好情况下O(nlogn),最坏情况下O(n^2),平均情况O(nlogn)
算法稳定性
总结
简单排序
冒泡排序: O(n^2) 稳定
选择排序: O(n^2) 不稳定
插入排序:O(n^2) 稳定
复杂排序
希尔排序:O(nlogn) 不稳定
归并排序:O(nlogn) 稳定
快速排序:O(nlogn) 不稳定