算法思路
重复地走访待排序数列(数组),每次比较两个相邻元素,顺序错误就交换位置(大的下沉放后面、小的上浮放前面),重复进行直到没有元素需要替换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。冒泡排序名称由来的就是因为越小或越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序),像一个个上升的气泡。
算法步骤
① 比较相邻的两个元素,如果前一个比后一个大,就交换它们;
② 对每一对相邻元素作同样的操作,从开始第一对到结尾的最后一对,这步完成后,最后的元素是最大的数;
③ 对除了最后一个以外的剩余元素重复以上的步骤;
④ 继续对越来越少的元素重复以上步骤,直到没有任何一对数字需要比较,完成排序。
算法演示
算法时间复杂度、空间复杂度和稳定性分析
时间复杂度分析
最好:当输入的数据序列已经是正序时,不需要进行排序,时间复杂度为 O(n)
最坏:当输入的数据序列是逆序时,n 个元素都要交换 n 次,时间复杂度为 O(n2)
平均:最好情况下,初始状态的有序度是 n(n−1)/2,不需要进行交换;最坏情况下,初始状态的有序度是 0,所以要进行 n(n−1)/2 次交换。取中间值 n(n−1)/4,来表示初始有序度既不很高也不很低的平均情况。即在平均情况下,需要 n(n−1)/4 次交换操作,因此,时间复杂度依然是 O(n2)。
空间复杂度分析
冒泡排序的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为O(1),是一个原地排序算法。
稳定性分析
从以上结果可见,相邻的两个元素大小相等时不做交换,相同大小的数据在排序前后不改变顺序,所以冒泡排序是稳定的排序算法。
总结
适用场合:待排序数据元素较少的情况和数组基本有序的情况;优点
:实现简单、空间复杂度低、稳定排序;缺点
:时间复杂度高、效率低。
关键代码:
void Bubble_Sort(int arr[],int len)
{int i, j, temp;
for ( i = 0; i < len - 1; i++ ) // 总共需要比较的次数为 len-1 次
for ( j = 0; j < len - 1 - i; j++ ) // 每次取待排序数据的相邻两个进行大小比较
if ( arr[j] > arr[j + 1] ) // 如果前一个比后一个大,则进行交换
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
算法思路和步骤
选择排序是一种简单直观的排序算法,其算法思路和步骤:首先在未排序序列 n 个数据中找到最小元素,存放到排序序列的起始位置;再从剩余未排序元素 n-1 个中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
算法演示
算法时间复杂度、空间复杂度和稳定性分析
时间复杂度分析
选择排序在最好、最坏、平均情况下的时间复杂度均为 O(n2),因为选择排序的时间复杂度与数据原本有序度没有关系,它需要遍历的次数是固定的,不受数据原本有序度的影响。
虽然选择排序和冒泡排序的时间复杂度一样,但实际上,选择排序所进行的交换操作很少,最多发生 n−1 次交换;而冒泡排序最坏的情况下要发生 n(n-1)/2 次交换,从这个角度看,选择排序的性能略优于冒泡排序。而且,选择排序比冒泡排序的思想更加直观。
空间复杂度分析
选择排序只涉及无序区中最小元素和无序区中第一个元素交换,只需要常量级临时空间,不需要额外空间辅助排序,所以空间复杂度为 O(1),是一个原地排序算法。
稳定性分析
选择排序是不稳定排序算法,因为每次都要找剩余未排序元素中(无序区中)的最小值,并和前面的元素交换位置,值相等的元素可能会被置换到后面,发生相对位置改变,破坏了稳定性。
选择排序总结
适用场合:待排序数据元素较少的情况和数组基本有序的情况;优点
:交换次数少,元素移动次数为 n-1 次;缺点
:效率低、不稳定。
关键代码:
void swap( int *a,int *b ) // 交换两个数据元素的自定义函数 swap
{ int temp = *a;
*a = *b;
*b = temp; }
void Selection_Sort( int arr[], int len )
{ int i,j;
for ( i = 0 ; i < len - 1 ; i++ )
{ int min = i;
for ( j = i + 1; j < len; j++ ) // 走访未排序的数据元素(无序区)
{ if ( arr[j] < arr[min] ) // 进行元素值大小比较
{min = j; // 找到无序区中数据元素的最小值
swap( &arr[min], &arr[i] ); // 将最小值与无序区第一个元素进行交换
}
}
}
算法思路
插入排序的思路是,将待排序数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,即数组中的第一个元素。取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据始终有序。重复该过程,直到未排序区间中元素为空,算法结束。
算法步骤
① 从数组第一个元素开始,认为该元素已排序;
② 取下一个元素,在已排序的元素序列中从后向前扫描;
③ 如果该元素(已排序)大于新元素,将该元素移到下一位置;
④ 重复步骤 ③,直到找到已排序元素小于或等于新元素的位置;
⑤ 将新元素插入到该位置后;
⑥ 重复步骤 ②~⑤。
算法演示
算法时间复杂度、空间复杂度和稳定性分析
时间复杂度分析
最好:如果待排序数据已经是有序的,不需要移动任何数据,只涉及从头到尾遍历一次有序数据的操作,因此,最好情况的时间复杂度为 O(n)。
最坏:如果待排序数据是完全逆序的,每次插入操作都相当于在数组的第一个位置插入新数据,需要大量移动数据,因此,最坏情况时间复杂度为 O(n2)。
平均:在数组中插入一个数据的平均时间复杂度是 O(n)。对于插入排序而言,每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,因此,平均时间复杂度为 O(n2)。
空间复杂度分析
插入排序算法的运行不需要额外空间来进行排序,所以它的空间复杂度为 O(1),是一个原地排序算法。
稳定性分析
等值元素可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。
总结
适用场合:数据量小且数组大部分有序时;优点
:稳定,相对于冒泡和选择排序效率更高;缺点
:比较的次数不固定,当前位置与正确位置的距离越远,需要移动的次数越多,特别是当数据量庞大的时候。
关键代码:
void Insertion_Sort( int arr[], int len )
{ int i,j,key;
for ( i=1;i<len;i++ )
{
key = arr[i];
j=i-1;
while( ( j>=0 ) && ( arr[j]>key ) )
{ arr[j+1] = arr[j];
j--;
}
arr[j+1] = key; } }