概述
插入类排序
直接插入排序
public class SF{
public int [ ] sortArray ( int [ ] nums) {
for ( int i = 1 ; i < nums. length; i++ ) {
int temp = nums[ i] ;
int k = i- 1 ;
for ( ; k >= 0 && nums[ k] > temp; k-- ) {
nums[ k+ 1 ] = nums[ k] ;
}
nums[ k+ 1 ] = temp;
}
return nums;
}
}
直接插入排序是稳定 的,即关键字相等的两个记录的顺序在排序前后不会有所改变 最好情况下(待排序记录已经有序 ),总的比较次数为nums.length-1次,移动记录的次数为2(nums.length-1)(每次只对待插记录nums[i]移动两次),因此最好时间复杂度为O(n) 最坏情况下(待排序记录为逆序 ) 总的比较次数 总的移动次数 因此最坏情况下总的时间复杂度为O(n²) 当记录随机分布 时,平均状况下的时间复杂度也为O(n²) 排序过程中用到的额外空间为temp变量,且与问题规模无关,因此空间复杂度为O(1)
折半插入排序
class SF {
public int [ ] sortArray ( int [ ] nums) {
for ( int i = 1 ; i < nums. length; i++ ) {
int temp = nums[ i] ;
int high = i- 1 ;
int low = 0 ;
while ( low <= high) {
int mid = low+ ( high- low) / 2 ;
if ( temp < nums[ mid] ) {
high = mid- 1 ;
} else {
low = mid+ 1 ;
}
}
for ( int j = i- 1 ; j >= low; j-- ) {
nums[ j+ 1 ] = nums[ j] ;
}
nums[ low] = temp;
}
return nums;
}
}
折半插入排序是稳定 的 当记录随机分布(平均时间复杂度 ),折半插入排序在寻找插入位置时,采用二分查找法,可以将比较次数的数量级降低到O(nlogn),但是移动次数的数量级依然是O(n²),因此总的平均时间复杂度仍然为O(n²) 最好情况下(待排序记录有序 ),只需要对待插入记录移动2(nums.length-1)次,因此最好的时间复杂度为O(nlogn) 最坏情况下(待排序记录逆序 ),移动次数的数量级是O(n²),因此最坏的时间复杂度为O(n²) 空间复杂度为O(1)
希尔排序
class SF{
public int [ ] sortArray ( int [ ] nums) {
int delta = nums. length/ 2 ;
while ( delta >= 1 ) {
for ( int i = 0 ; i < delta; i++ ) {
shell ( nums, delta, i) ;
}
delta/= 2 ;
}
return nums;
}
public void shell ( int [ ] nums, int delta, int init) {
for ( int i = delta+ init; i < nums. length; i+= delta) {
int temp = nums[ i] ;
int k = i - delta;
for ( ; k >= 0 && temp < nums[ k] ; k -= delta) {
nums[ k + delta] = nums[ k] ;
}
nums[ k + delta] = temp;
}
}
}
希尔排序将待排序序列划分成若干个较小的子序列,然后对子序列进行直接插入排序。当增量缩小为1时,待排序记录已基本有序,这时再进行直接插入排序时,序列已基本有序,此时直接插入排序的性能最佳,一般认为希尔排序的平均时间复杂度为O(n∧1.5),比直接对待排序序列使用直接插入排序的性能要好 希尔排序是不稳定 的
交换类排序
冒泡排序
基本的冒泡排序,没有优化
class SF{
public int [ ] sortArray ( int [ ] nums) {
if ( nums == null || nums. length == 0 ) {
return null ;
}
for ( int i = 0 ; i < nums. length; i++ ) {
for ( int j = 0 ; j < nums. length- i- 1 ; j++ ) {
if ( nums[ j] > nums[ j+ 1 ] ) {
int temp = nums[ j] ;
nums[ j] = nums[ j+ 1 ] ;
nums[ j+ 1 ] = temp;
}
}
}
return nums;
}
}
基本的冒泡排序,不论序列的分布情况如何,时间复杂度都为O(n²)。额外空间为temp,且与问题规模无关,因此空间复杂度为O(1) 冒泡排序是稳定 的
冒泡排序的优化
class SF {
public int [ ] sortArray ( int [ ] nums) {
if ( nums == null || nums. length == 0 ) {
return null ;
}
for ( int i = 0 ; i < nums. length; i++ ) {
boolean flag = false ;
for ( int j = 0 ; j < nums. length- i- 1 ; j++ ) {
if ( nums[ j] > nums[ j+ 1 ] ) {
int temp = nums[ j] ;
nums[ j] = nums[ j+ 1 ] ;
nums[ j+ 1 ] = temp;
flag = true ;
}
}
if ( flag == false ) {
break ;
}
}
return nums;
}
}
如果某一轮中没有发生元素交换,则整个待排序序列已经有序,不需要再进行排序,直接跳出循环。 最好情况下(待排序序列一开始就有序 ),外循环只进行一次,内循环循环一轮,此时的时间复杂度为O(n)
冒泡排序的优化
class SF {
public int [ ] sortArray ( int [ ] nums) {
if ( nums == null || nums. length == 0 ) {
return null ;
}
int lastExchangeIndex = 0 ;
int sortBorder = nums. length- 1 ;
for ( int i = 0 ; i < nums. length; i++ ) {
boolean isSorted = false ;
for ( int j = 0 ; j < sortBorder; j++ ) {
if ( nums[ j] > nums[ j+ 1 ] ) {
int temp = nums[ j] ;
nums[ j] = nums[ j+ 1 ] ;
nums[ j+ 1 ] = temp;
isSorted = true ;
lastExchangeIndex = j;
}
}
sortBorder = lastExchangeIndex;
if ( isSorted == false ) {
break ;
}
}
return nums;
}
}
在每一轮比较中,确定实际的有序序列区,这样可以避免在下一轮比较中进行无效比较,从而提高算法性能
快速排序
class SF{
public int [ ] sortArray ( int [ ] nums) {
quickSort ( nums, 0 , nums. length- 1 ) ;
return nums;
}
public void quickSort ( int [ ] nums, int left, int right) {
if ( nums == null || right <= left) {
return ;
}
int mid = move ( nums, left, right) ;
quickSort ( nums, left, mid- 1 ) ;
quickSort ( nums, mid+ 1 , right) ;
}
public int move ( int [ ] nums, int left, int right) {
int pivot = nums[ left] ;
while ( left < right) {
while ( left < right && nums[ right] >= pivot) {
right-- ;
}
nums[ left] = nums[ right] ;
while ( left < right && nums[ left] <= pivot) {
left++ ;
}
nums[ right] = nums[ left] ;
}
nums[ left] = pivot;
return left;
}
}
快速排序的本质是一颗二叉树的前序遍历,因此我们从树的角度来分析快速排序的时间复杂度,二叉树中的每一个节点(除了叶子节点 )都是一个待排序序列,因此总的时间复杂度应为二叉树中每个节点(除了叶子节点 )的长度(每个待排序序列的长度)加起来的总和 由于对序列的划分是由枢轴元素决定的,如果序列一开始是顺序或逆序的,那么每次划分之后枢轴元素位于序列的最左端或最右端,二叉树就会生长为一个类似于单链表的结构,这时候时间复杂度就会上升到O(n²) 快速排序是递归式算法,平均状况下递归栈的深度为logn,因此平均状况下的空间复杂度为O(logn);最坏情况下,即序列一开始是顺序或逆序的,递归栈的深度为n,因此最坏情况下的空间复杂度为O(n) 下面演示一下平均状况下的递归栈深度 快速排序是不稳定 的
选择类排序
简单选择排序
class SF {
public int [ ] sortArray ( int [ ] nums) {
if ( nums == null || nums. length == 0 ) {
return null ;
}
for ( int i = 0 ; i < nums. length- 1 ; i++ ) {
int min = i;
for ( int j = i+ 1 ; j < nums. length; j++ ) {
if ( nums[ j] < nums[ min] ) {
min = j;
}
}
if ( min != i) {
int temp = nums[ i] ;
nums[ i] = nums[ min] ;
nums[ min] = temp;
}
}
return nums;
}
}
简单选择排序是不稳定 的 简单选择排序的最好、最坏、平均复杂度均为O(n²) 额外空间为temp,且与问题规模无关,因此简单选择排序的空间复杂度为O(1)
堆排序
class SF{
public int [ ] sortArray ( int [ ] nums) {
heapSort ( nums) ;
}
public void heapSort ( int [ ] nums) {
creatHeap ( nums) ;
for ( int i = nums. length- 1 ; i >= 1 ; i-- ) {
int temp = nums[ 0 ] ;
nums[ 0 ] = nums[ i] ;
nums[ i] = temp;
sift ( nums, 0 , i- 1 ) ;
}
}
public void creatHeap ( int [ ] nums) {
for ( int i = nums. length/ 2 ; i >= 0 ; i-- ) {
sift ( nums, i, nums. length- 1 ) ;
}
}
public void sift ( int [ ] nums, int k, int m) {
int t = nums[ k] ;
int i = k;
int j = 2 * i;
boolean finished = false ;
while ( j <= m && ! finished) {
if ( j+ 1 <= m && nums[ j] <= nums[ j+ 1 ] ) {
j = j+ 1 ;
}
if ( t >= nums[ j] ) {
finished = true ;
} else {
nums[ i] = nums[ j] ;
i = j;
j = 2 * i;
}
}
nums[ i] = t;
}
}
堆排序是不稳定 的 即使最坏情况下,时间复杂度也为O(nlogn);空间复杂度为O(1)
归并排序
class SF{
public int [ ] sortArray ( int [ ] nums) {
int [ ] temp = new int [ nums. length] ;
mergeSort ( nums, 0 , nums. length- 1 , temp) ;
return nums;
}
public void mergeSort ( int [ ] nums, int left, int right, int [ ] temp) {
if ( nums == null || left >= right) {
return ;
}
int mid = left+ ( right- left) / 2 ;
mergeSort ( nums, left, mid, temp) ;
mergeSort ( nums, mid+ 1 , right, temp) ;
merge ( nums, left, mid, right, temp) ;
}
public void merge ( int [ ] nums, int left, int mid, int right, int [ ] temp) {
int i = left;
int j = mid+ 1 ;
int t = 0 ;
while ( i <= mid && j <= right) {
if ( nums[ i] <= nums[ j] ) {
temp[ t++ ] = nums[ i++ ] ;
} else {
temp[ t++ ] = nums[ j++ ] ;
}
}
while ( i <= mid) {
temp[ t++ ] = nums[ i++ ] ;
}
while ( j <= right) {
temp[ t++ ] = nums[ j++ ] ;
}
t = 0 ;
i = left;
while ( i <= right) {
nums[ i++ ] = temp[ t++ ] ;
}
}
}
归并排序算法的时间复杂度分析和快速排序算法的时间复杂度分析十分相似,但是归并排序算法对序列的每次划分都是等分的,因此不会像快速排序算法那样出现斜二叉树(类似于单链表结构),故归并排序算法的最好、最坏、平均时间复杂度都为O(nlogn),但是需要长度为nums.length的辅助空间,因此空间复杂度为O(n) 归并排序算法是稳定 的
基数排序
基数排序是一种低位优先的排序法。排序时先按最低位的值对记录进行初步排序,在此基础上再按次低位的值进行进一步排序,以此类推,直至最高位
class SF{
public int [ ] sortArray ( int [ ] nums) {
int max = getMax ( nums) ;
int bits = ( max+ "" ) . length ( ) ;
for ( int i = 0 , n = 1 ; i < bits; i++ , n*= 10 ) {
int [ ] [ ] bucket = new int [ 10 ] [ nums. length] ;
int [ ] bucketElementsCount = new int [ 10 ] ;
for ( int j = 0 ; j < nums. length; j++ ) {
int bitDigit = ( nums[ j] / n) % 10 ;
bucket[ bitDigit] [ bucketElementsCount[ bitDigit] ] = nums[ j] ;
bucketElementsCount[ bitDigit] ++ ;
}
int index = 0 ;
for ( int k = 0 ; k < bucketElementsCount. length; k++ ) {
if ( bucketElementsCount[ k] != 0 ) {
for ( int m = 0 ; m < bucketElementsCount[ k] ; m++ ) {
nums[ index] = bucket[ k] [ m] ;
index++ ;
}
}
}
}
return nums;
}
int getMax ( int [ ] nums) {
int max = nums[ 0 ] ;
for ( int i = 1 ; i < nums. length; i++ ) {
if ( max < nums[ i] ) {
max = nums[ i] ;
}
}
return max;
}
}
基数排序是稳定 的 最好、最坏及平均复杂度都为O(d(n+k)),其中d为待排序数组中元素的最大位数,n为待排序数组的长度,k为基数 我们这里的算法实现的空间复杂度为O(nk + k)