排序
定理:通过交换相邻元素进行排序的任何算法平均需要 O(N2) O ( N 2 )
插入排序
插入排序就是从数组下标为1的数字开始,取出当前下标的数字(tmp = a[j]),然后和下标之前的数字按照逆序一一比对。插入排序保证从位置0到位置j上的元素都是已排序状态。在插入排序中,逆序对数即是算法执行的交换次数。
时间复杂度(假设要进行升序操作):
最佳情况就是(升序排序) | 最坏情况就是(逆序排序) | 算法稳定性 |
---|---|---|
O(N) O ( N ) | O(N2) O ( N 2 ) | 稳定 |
public static void main(String[] args) {
Comparable[] a = {34, 8, 64, 51, 32, 21};
int j;
for(int i = 1; i<a.length; i++){
Comparable tmp = a[i];
for(j = i; j>0 && tmp.compareTo(a[j-1])<0; j--)
a[j] = a[j-1];
a[j] = tmp;
}
printSort(a);
}
public static void printSort(Comparable[] a){
for(int i = 0; i<a.length; i++)
System.out.print(a[i] + " ");
}
希尔排序(缩量增减排序)
希尔排序是冲破二次时间屏障的第一批算法之一。它通过比较相距一定距离间隔(增量)的元素来工作,每一次比较所用的距离随着时间的进行而减少,知道只比较相邻元素的最后一趟排序为止。因为这一点,所以也被称为缩减增量排序。
希尔能突破
O(N2)
O
(
N
2
)
的界,可以用逆序数来理解,假设我们要从小到大排序,一个数组中取两个元素如果前面比后面大,则为一个逆序,容易看出排序的本质就是消除逆序数,可以证明对于随机数组,逆序数是
O(N2)
O
(
N
2
)
的,而如果采用“交换相邻元素”的办法来消除逆序,每次正好只消除一个,因此必须执行
O(N2)
O
(
N
2
)
的交换次数,这就是为啥冒泡、插入等算法只能到平方级别的原因,反过来,基于交换元素的排序要想突破这个下界,必须执行一些比较,交换相隔比较远的元素,使得一次交换能消除一个以上的逆序,希尔、快排、堆排等等算法都是交换比较远的元素,只不过规则各不同罢了
作者:冒泡
链接:https://www.zhihu.com/question/24637339/answer/84079774
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度(假设要进行升序操作):
最佳情况就是(已升序排列) | 最坏情况就是(偶数位置上有N/2个最大的元素,奇数位置上有N/2个最小的元素) | 算法稳定性 |
---|---|---|
O(N) O ( N ) | O(N2) O ( N 2 ) | 不稳定 |
public static void shellsort(Comparable[] a){
for(int gap = a.length/2; gap > 0; gap /= 2){
for(int i = gap; i < a.length; i++){
Comparable tmp = a[i];
int j = i;
while( j >= gap && tmp.compareTo(a[j - gap ]) < 0){
a[j] = a[ j - gap ];
j -= gap;
}
a[j] = tmp;
}
}
printSort(a);
}
堆排序
讲到堆排序,就要牵扯到堆和二叉树的概念:
(1)完全二叉树——只有最下面的两层结点度小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树;
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶结点都处在最底层的二叉树;
(3)堆:就是一种特殊性质的完全二叉树,堆分为最大堆(大根堆)、最小堆(小根堆)。
堆排序的原理:
1. 将根节点的数据和最后一个叶子节点数据进行替换
2. 对前N-1个元素进行调整,得到包含前N-1个元素的堆
3. 重复以上过程,直到排序完毕
时间复杂度(假设要进行升序操作):
最佳情况就 | 最坏情况 | 算法稳定性 |
---|---|---|
O(Nlog2N) O ( N log 2 N ) | O(Nlog2N) O ( N log 2 N ) | 不稳定 |
/**
* 建立堆 小顶堆
* @param a 可比较数组
*/
public static void createHeap(Comparable[] a){
for(int i = a.length / 2 - 1 ; i >= 0; i--){
int hole = i;
int child = 0;
// 保存父节点数据
Comparable tmp = a[hole];
for(; hole * 2 <= a.length; hole = child){
child = hole * 2 + 1;
//找出子节点中比较小的那个,用child记录下来
if(child < a.length && a[ child ].compareTo(a[child + 1]) > 0)
child++;
//和父节点比较,如果大于父节点,则替换
if(tmp.compareTo(a[child]) > 0)
a[ hole ] = a[ child ];
else
break;
}
// 更改被替换的子节点的值
a[ hole ] = tmp;
}
printSort(a);
}
/**
* 实现堆调整,相当于一种二分法,来进行的调整
* @param a 可比较数组
* @param i 需要调整的节点位置 ,这里是0
* @param n 需要调整的节点数目
*/
public static void heapAdjust(Comparable[] a, int i, int n){
int child = 0;
// 保存父节点数据 a[i]
Comparable tmp = a[i];
for(; i * 2 < n; i = child){
// 找到左子节点
child = i * 2 + 1;
// 找到子节点中值较小的那个子节点
if(child < n && a[ child ].compareTo(a[child + 1]) > 0)
child++;
// 如果父节点大于子节点,就进行替换
if(tmp.compareTo(a[child]) > 0)
a[ i ] = a[ child ];
else
break;
}
// 更改被替换的子节点的值
a[ i ] = tmp;
}
/**
* 堆排序
* 1.将根节点上的值和最后一个叶子节点的值进行替换
* 2.将所替换的叶子节点之前的数组部分,进行调整,使其符合堆的性质
* 3.重复以上过程
* @param a
*/
public static void heapSort(Comparable[] a){
//Comparable[] a = {81, 94, 11, 96, 12, 35, 17, 95, 28, 58, 41, 75, 15};
for(int i = a.length - 1; i > 0; i--){
Comparable tmp = a[ 0 ];
a[ 0 ] = a[ i ];
a[ i ] = tmp;
heapAdjust(a, 0, i - 1);
}
printSort(a);
}
归并排序
归并排序是分治法的典型代表,所以归并排序的原理,将待排序数组分为两份,然后每一份再次分为两份,迭代下去,直到无法均分为止。然后对相邻的两个队列进行比较,比较方法是,比较队列的第一个值,将最大(小)的取出,然后将这个队列的下标向后移动一位,接着比较,知道其中一个队列中的值被取完,然后将另一个队列的剩余的值都放入缓存队列中。
图片来源:
作者: dreamcatcher-cx
出处: https://www.cnblogs.com/chengxiao/p/6194356.html/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在页面明显位
置给出原文链接。
最佳情况 | 最坏情况 | 算法稳定性 |
---|---|---|
O(Nlog2N) O ( N log 2 N ) | O(Nlog2N) O ( N log 2 N ) | 稳定 |
/**
* 归并排序入口
* @param a 待排序数组
*/
public static void mergeSort(Comparable[] a){
Comparable [] tmpArray = new Comparable[ a.length ];
mergeSort(a, tmpArray, 0, a.length - 1);
}
/**
* 归并排序重载,迭代实现
* @param a 待排序数组
* @param tmpArray 中间数组
* @param left 数列左边界
* @param right 数列右边界
*/
public static void mergeSort(Comparable[] a, Comparable[] tmpArray, int left, int right){
if(left < right){
// 寻找队列中间元素的下标
int center = (left + right) / 2;
// 分治法策略:先将大问题尽可能的分成等价的小问题
// 然后计算小问题,将小问题的结果汇总。
mergeSort(a, tmpArray, left, center);
mergeSort(a, tmpArray, center + 1, right);
merge(a, tmpArray, left, center + 1, right);
}
}
/**
* 数组部分数据排序
* @param a 带排序数组
* @param tmpArray 缓存数组
* @param leftPos 左队列边界
* @param rightPos 右队列左边界
* @param rightEnd 右队列右边界
*/
public static void merge(Comparable[] a, Comparable[] tmpArray,
int leftPos, int rightPos, int rightEnd){
// 左队列右边界
int leftEnd = rightPos - 1;
// 记录左队列左边界
int tmpPos = leftPos;
// 队列数目
int numElements = rightEnd - leftPos + 1;
// 进行查找排序,判断条件左右两个队列其中一个队列查找完毕结束。
while( leftPos <= leftEnd && rightPos <= rightEnd)
if( a[ leftPos ].compareTo( a[ rightPos ]) <= 0)
tmpArray[ tmpPos++ ] = a[ leftPos++ ];
else
tmpArray[ tmpPos++ ] = a[ rightPos++ ];
// 相邻的;两个元素比较
while( leftPos <= leftEnd)
tmpArray[ tmpPos++ ] = a[ leftPos++ ];
while( rightPos <= rightEnd )
tmpArray[ tmpPos++ ] = a[ rightPos++ ];
// 拷贝缓存数组中的元素到数组a中
for(int i = 0; i < numElements; i++, rightEnd--)
a[ rightEnd ] = tmpArray[ rightEnd ];
}
快速排序
快速排序的基本思想也是基于分治法,但是并不是均分。快排首先会寻找一个基准值来对数组进行划分,小(大)于基准值的数组分一边(
S1
S
1
),大(小)于基准值的数组在分一边(
S2
S
2
)。所以,能够看出来,基准值的选择是影响算法性能的一大元素,如果恰巧选择了最大值或者最小值,就会导致数组全在
S1
S
1
或
S2
S
2
中。所以我选择了三数中值分割理论。即取数组第一个、中间、和最后一个元素,进行比较,然后选择中间值作为基准值。
在选择了基准值后,我们将基准值和数组最后一个元素进行对调。然后对数组的前
N−1
N
−
1
个元素进行查找,从数组的最左边,自左往右,寻找小(大)于基准值的元素,记为i,从第
tmpArray.length−2
t
m
p
A
r
r
a
y
.
l
e
n
g
t
h
−
2
元素开始,自右往左,寻找大(小)于基准值的元素,记为j。找到后,将其元素互换。直到i>j为止,从基准值的位置将数组划分为两份,迭代进行以上计算。
最佳情况 | 最坏情况 | 算法稳定性 |
---|---|---|
O(Nlog2N) O ( N log 2 N ) | O(N2) O ( N 2 ) | 不稳定 |
/**
* 快速排序入口
* @param a 待排序数组
*/
public static void quickSort(Comparable[] a){
quickSort(a, 0, a.length - 1);
}
/**
* 快速排序实现核心代码
* @param a 待排序内部小数组
* @param leftPos 数组左边界
* @param rightPos 数组右边界
*/
public static void quickSort(Comparable[] a, int leftPos, int rightPos){
// 快速排序不适合小数组,所以元素个数在10以内的数组,使用插入排序
if( leftPos + 10 < rightPos ){
// 获取基准值
Comparable baseLine = arrayBaseLine(a, leftPos, rightPos);
// 将基准值和待排序数组的最后一个元素互换位置
swapArrayValue(a, (leftPos + rightPos) / 2, rightPos);
int i = leftPos;
int j = rightPos;
for(;;){
// 寻找大于基准值的元素位置
while( i < j && a[ ++i ].compareTo(baseLine) < 0) {}
// 寻找小于基准值的元素位置
while( j >= 0 && a[ --j ].compareTo(baseLine) > 0) {}
// 位置互换,前决条件:i < j
if(i < j)
swapArrayValue(a, i, j);
else
break;
}
swapArrayValue(a, i, rightPos);
// 迭代部分数组元素
quickSort(a, leftPos, i - 1);
quickSort(a, i + 1, rightPos);
}
else
// 元素个数小于10的数组,使用插入排序
insertionSort(a, leftPos, rightPos);
}
/**
* 寻找基准值
* @param a 待排序数组
* @param left 数组左边界
* @param right 数组右边界
*/
public static Comparable arrayBaseLine( Comparable[] a, int left, int right){
// 三数中值分割策略
int center = ( left + right ) / 2;
// 比较左值和中间值
if( a[ left ].compareTo( a[ center ]) > 0)
swapArrayValue(a, left, center);
// 比较中间值和右值
if( a[ center ].compareTo( a[ right ]) > 0)
swapArrayValue(a, center, right);
// 比较左值和中间值
if( a[ left ].compareTo( a[ center]) > 0)
swapArrayValue(a, left, center);
return a[ center ];
}
/**
* 交换数组中指定位置上的元素
* @param a 指定数组
* @param leftPos 交换位置1
* @param leftRigth 交换位置2
*/
public static void swapArrayValue( Comparable[] a, int leftPos, int rigthPos){
Comparable tmp = a[leftPos];
a[ leftPos ] = a[ rigthPos ];
a[ rigthPos ] = tmp;
}