堆排序
堆排序的话其实就是把数组看成一颗完全二叉树,因为我们事先就知道数组的话可以等价于一颗完全二叉树,然后再把数组看成完全二叉树的基础之上,在给这个完全二叉树进行建堆,比如说我需要升序排列的话那我应该要建一个大根堆。 然后堆排序,他实际上就是利用了堆的一个性质:堆顶的数据是整个堆当中数据的最值。他实际上就是在不断的选数,将合乎要求的数放到数组尾巴开始。 堆(数组)里面不能随便的去挪动数据,因为一旦挪动数据的话,这个完全二叉树里面的父子关系就会全乱掉。所以说比如说在往堆里面进行插入数据或者删除数据之类,比如说插入的话是先把它插入到数组的尾巴,然后再向上调整;比如说删除数据的话,就是把数组最后一个元素与堆顶元素换一下位置,然后把堆顶元素向下调整。实际上都没有随意挪动数据。 需要注意的是排升序要建大堆,排降序建小堆 首先是两个万金油
void Swap ( int * p1, int * p2)
{
int tmp = * p1;
* p1 = * p2;
* p2 = tmp;
}
void AdjustUp ( int * arr, int child)
{
int parent = ( child - 1 ) / 2 ;
while ( child > 0 )
{
if ( arr[ child] < arr[ parent] )
{
Swap ( arr + child, arr + parent) ;
child = parent;
parent = ( child - 1 ) / 2 ;
}
else
{
break ;
}
}
}
void AdjustDown ( int * arr, int parent, int n)
{
int child = 2 * parent + 1 ;
while ( child < n)
{
if ( child + 1 < n && arr[ child] > arr[ child + 1 ] )
{
child++ ;
}
if ( arr[ parent] > arr[ child] )
{
Swap ( arr + child, arr + parent) ;
parent = child;
child = 2 * parent + 1 ;
}
else
{
break ;
}
}
}
具体堆排序(建堆+调堆)这个堆排序当中的调堆的end可以把它理解成在接下来此时此刻即将要换到堆顶上面的一个数据。
void HeapSort ( int * arr, int n)
{
for ( int i = ( n - 1 - 1 ) / 2 ; i >= 0 ; i-- )
{
AdjustDown ( arr, i, n) ;
}
int end = n - 1 ;
while ( end > 0 )
{
Swap ( arr, arr + end) ;
AdjustDown ( arr, 0 , end) ;
end-- ;
}
}
堆排序时间复杂度
堆排序的话,除了一开始要把原始数据(是数组或者说是完全二叉树)建堆之后,还要去转数据调堆。对于第一步,复杂度是O(N)。 然后调堆的时间复杂度也是logN*N,用错位相减法减减算算也很快的,或者说你用直观的方法去看一下,对于最后一层而言,如果说你想要确定最后一层(堆排序的话,它是确定的顺序是从数组的从后往前慢慢确定下来的),要确定最后一层的话,就需要不断的把堆顶的数据给往下调整,这就相当于就是说节点最多的层数,它需要调整的次数也是最多。 因此对于堆排序而言的话,就是说你在一开始对数组(完全二叉树)建堆的时候,无论是用什么方法去建堆,最后总的堆排序时间复杂度都是O(N*logN)
冒泡排序复习优化与时间复杂度
首先比如说用n个数据的话,因为冒泡排序,每一趟确定一个数据,因此总共需要n-1趟。然后对于每一趟而言,去画个图找一下下标的规律关系就OK了,非常简单。 冒泡排序的话,最坏的情况时间复杂度是O(N^ 2),是一个标准的等差数列。然后如果是纯朴素的冒泡排序的话,也就是说没有进行任何的优化,最好的情况也是O(N^2),因为两个for循环都必须得全部走完嘛。 然后好一点的情况的话,就对这个冒泡排序进行一些优化就可以。就是说如果说在某一趟的过程当中没有发生任何的交换,这就说明这个数据已经是全部都有序了,因为但凡不是有序的话都会发生交换。 然后当这个优化完了之后,这个冒泡排序最好的情况就是O(N) 没优化的
void BubbleSort ( int * arr, int n)
{
for ( int i = 0 ; i < n - 1 ; i++ )
{
for ( int j = 0 ; j <= ( n - i - 2 ) ; j++ )
{
if ( arr[ j] > arr[ j + 1 ] )
{
Swap ( arr + j, arr + j + 1 ) ;
}
}
}
}
优化过的
void BubbleSort ( int * arr, int n)
{
for ( int i = 0 ; i < n - 1 ; i++ )
{
bool exchange = false;
for ( int j = 0 ; j <= ( n - i - 2 ) ; j++ )
{
if ( arr[ j] > arr[ j + 1 ] )
{
exchange = true;
Swap ( arr + j, arr + j + 1 ) ;
}
}
if ( exchange == false)
{
break ;
}
}
}
关于冒泡排序与直接插入排序与选择排序的时间复杂度比较
虽然他们三个排序算法的时间复杂度都是O(N^ 2),但是时间复杂度仅仅只是代表一个量级而已,在具体执行算法过程当中,性能差异还是会显现出来。 首先来看这个选择排序的话,无论怎么样都是铁打不动的O(N^ 2),效率是最慢的。 然后来看冒泡排序与直接插入排序,他们两个的话时间复杂度都是O(N^ 2),并且最好的情况下都是O(N). 但具体而言的话还是直接插入排序要更加性能好一点。首先,如果数组是全部有序的话,两者还是没有区别,都是O(N); 然后在接近有序的情况之下,差距非常小,还是直接插入排序更快一点。因为在接近有序的时候,对于插入排序而言,只需要在某些插入的过程之后,再把新插入的数据融入到原先的有序数组这么去大概操作一下。然后对于冒泡排序而言的话,对于第一趟,那是肯定要走的;然后第一趟走完的话,由于exchange是true,所以说势必会走第二趟,反正就是说如果说在某一趟走完之后,整个数组已经是有序的情况之下,对于冒泡排序而言,他还需要再去走一趟,走这一趟实际上是白走的,因为数组已经有序了。 差距体现的最大的就是在部分有序的情况之下,在部分有序的情况之下,对于冒泡排序而言,他对于部分有序并不敏感,仍然需要去走大量的趟数。然而对于插入排序而言,部分有序的存在就可以省略掉很多的调整过程。